feat: Adds option for a url to list of pub keys for jwt to pre-fetch.

The URL is a link to a json file having a mapping kid -> public key.
The mapping can contain also certificates, which will be used to get the public key.
The list is being updated before the cache-control max-age header value is reached, if such a header is returned from the server.
This commit is contained in:
damencho
2023-07-21 13:55:03 -05:00
committed by Дамян Минков
parent 2bf982452e
commit e58cad0464
2 changed files with 57 additions and 4 deletions

View File

@@ -13,8 +13,13 @@ local main_util = module:require "util";
local ends_with = main_util.ends_with;
local http_get_with_retry = main_util.http_get_with_retry;
local extract_subdomain = main_util.extract_subdomain;
local starts_with = main_util.starts_with;
local cjson_safe = require 'cjson.safe'
local timer = require "util.timer";
local async = require "util.async";
local nr_retries = 3;
local ssl = require "ssl";
-- TODO: Figure out a less arbitrary default cache size.
local cacheSize = module:get_option_number("jwt_pubkey_cache_size", 128);
@@ -34,6 +39,9 @@ function Util.new(module)
self.appId = module:get_option_string("app_id");
self.appSecret = module:get_option_string("app_secret");
self.asapKeyServer = module:get_option_string("asap_key_server");
-- A URL that will return json file with a mapping between kids and public keys
-- If the response Cache-Control header we will respect it and refresh it
self.cacheKeysUrl = module:get_option_string("cache_keys_url");
self.signatureAlgorithm = module:get_option_string("signature_algorithm");
self.allowEmptyToken = module:get_option_boolean("allow_empty_token");
@@ -109,6 +117,35 @@ function Util.new(module)
return nil;
end
if self.cacheKeysUrl then
local update_keys_cache;
update_keys_cache = async.runner(function (name)
content, code, cache_for = http_get_with_retry(self.cacheKeysUrl, nr_retries);
if content ~= nil then
self.cachedKeys = {};
-- Let's convert any certificate to public key
for k, v in pairs(cjson_safe.decode(content)) do
if starts_with(v, '-----BEGIN CERTIFICATE-----') then
self.cachedKeys[k] = ssl.loadcertificate(v):pubkey();
end
end
if cache_for then
cache_for = tonumber(cache_for);
-- let's schedule new update 60 seconds before the cache expiring
if cache_for > 60 then
cache_for = cache_for - 60;
end
timer.add_task(cache_for, function ()
update_keys_cache:run("update_keys_cache");
end);
end
end
end);
update_keys_cache:run("update_keys_cache");
end
return self
end
@@ -202,7 +239,13 @@ function Util:process_and_verify_token(session, acceptedIssuers)
if alg.sub(alg,1,2) ~= "RS" then
return false, "not-allowed", "'kid' claim only support with RS family";
end
key = self:get_public_key(kid);
if self.cachedKeys and self.cachedKeys[kid] then
key = self.cachedKeys[kid];
else
key = self:get_public_key(kid);
end
if key == nil then
return false, "not-allowed", "could not obtain public key";
end