spacewander commented on a change in pull request #3308:
URL: https://github.com/apache/apisix/pull/3308#discussion_r558843232
##########
File path: apisix/plugins/authz-keycloak.lua
##########
@@ -224,31 +239,332 @@ local function authz_keycloak_get_token_endpoint(conf)
end
-local function is_path_protected(conf)
- -- TODO if permissions are empty lazy load paths from Keycloak
- if conf.permissions == nil then
- return false
+local function authz_keycloak_get_resource_registration_endpoint(conf)
+ return authz_keycloak_get_endpoint(conf, "resource_registration_endpoint")
+end
+
+
+-- computes access_token expires_in value (in seconds)
+local function authz_keycloak_access_token_expires_in(opts, expires_in)
+ return (expires_in or opts.access_token_expires_in or 300)
+ - 1 - (opts.access_token_expires_leeway or 0)
+end
+
+
+-- computes refresh_token expires_in value (in seconds)
+local function authz_keycloak_refresh_token_expires_in(opts, expires_in)
+ return (expires_in or opts.refresh_token_expires_in or 3600)
+ - 1 - (opts.refresh_token_expires_leeway or 0)
+end
+
+
+local function authz_keycloak_ensure_sa_access_token(conf)
+ local token_endpoint = authz_keycloak_get_token_endpoint(conf)
+
+ if not token_endpoint then
+ log.error("Unable to determine token endpoint.")
+ return 500, "Unable to determine token endpoint."
end
- return true
+
+ local session = authz_keycloak_cache_get("access_tokens", token_endpoint
.. ":"
+ .. conf.client_id)
+
+ if session then
+ -- Decode session string.
+ local err
+ session, err = core.json.decode(session)
+
+ if not session then
+ -- Should never happen.
+ return 500, err
+ end
+
+ local current_time = ngx.time()
+
+ if current_time < session.access_token_expiration then
+ -- Access token is still valid.
+ log.debug("Access token is still valid.")
+ return session.access_token
+ else
+ -- Access token has expired.
+ log.debug("Access token has expired.")
+ if session.refresh_token
+ and (not session.refresh_token_expiration
+ or current_time < session.refresh_token_expiration) then
+ -- Try to get a new access token, using the refresh token.
+ log.debug("Trying to get new access token using refresh
token.")
+
+ local httpc = http.new()
+ httpc:set_timeout(conf.timeout)
+
+ local params = {
+ method = "POST",
+ body = ngx.encode_args({
+ grant_type = "refresh_token",
+ client_id = conf.client_id,
+ client_secret = conf.client_secret,
+ refresh_token = session.refresh_token,
+ }),
+ ssl_verify = conf.ssl_verify,
+ headers = {
+ ["Content-Type"] = "application/x-www-form-urlencoded"
+ }
+ }
+
+ local current_time = ngx.time()
+
+ local res, err = httpc:request_uri(token_endpoint, params)
+
+ if not res then
+ err = "Accessing token endpoint URL (" .. token_endpoint
+ .. ") failed: " .. err
+ log.error(err)
+ return nil, err
+ end
+
+ log.debug("Response data: " .. res.body)
+ local json, err = authz_keycloak_parse_json_response(res)
+
+ if not json then
+ err = "Could not decode JSON from token endpoint"
+ .. (err and (": " .. err) or '.')
+ log.error(err)
+ return nil, err
+ end
+
+ if not json.access_token then
+ -- Clear session.
+ log.debug("Answer didn't contain a new access token.
Clearing session.")
+ session = nil
+ else
+ log.debug("Got new access token.")
+ -- Save access token.
+ session.access_token = json.access_token
+
+ -- Calculate and save access token expiry time.
+ session.access_token_expiration = current_time
+ + authz_keycloak_access_token_expires_in(conf,
json.expires_in)
+
+ -- Save refresh token, maybe.
+ if json.refresh_token ~= nil then
+ log.debug("Got new refresh token.")
+ session.refresh_token = json.refresh_token
+
+ -- Calculate and save refresh token expiry time.
+ session.refresh_token_expiration = current_time
+ + authz_keycloak_refresh_token_expires_in(conf,
+
json.refresh_expires_in)
+ end
+
+ authz_keycloak_cache_set("access_tokens",
+ token_endpoint .. ":" ..
conf.client_id,
+ core.json.encode(session), 24 *
60 * 60)
Review comment:
Better to allow customizing cache time.
##########
File path: apisix/plugins/authz-keycloak.lua
##########
@@ -224,31 +239,332 @@ local function authz_keycloak_get_token_endpoint(conf)
end
-local function is_path_protected(conf)
- -- TODO if permissions are empty lazy load paths from Keycloak
- if conf.permissions == nil then
- return false
+local function authz_keycloak_get_resource_registration_endpoint(conf)
+ return authz_keycloak_get_endpoint(conf, "resource_registration_endpoint")
+end
+
+
+-- computes access_token expires_in value (in seconds)
+local function authz_keycloak_access_token_expires_in(opts, expires_in)
+ return (expires_in or opts.access_token_expires_in or 300)
+ - 1 - (opts.access_token_expires_leeway or 0)
+end
+
+
+-- computes refresh_token expires_in value (in seconds)
+local function authz_keycloak_refresh_token_expires_in(opts, expires_in)
+ return (expires_in or opts.refresh_token_expires_in or 3600)
+ - 1 - (opts.refresh_token_expires_leeway or 0)
+end
+
+
+local function authz_keycloak_ensure_sa_access_token(conf)
+ local token_endpoint = authz_keycloak_get_token_endpoint(conf)
+
+ if not token_endpoint then
+ log.error("Unable to determine token endpoint.")
+ return 500, "Unable to determine token endpoint."
end
- return true
+
+ local session = authz_keycloak_cache_get("access_tokens", token_endpoint
.. ":"
+ .. conf.client_id)
+
+ if session then
+ -- Decode session string.
+ local err
+ session, err = core.json.decode(session)
+
+ if not session then
+ -- Should never happen.
+ return 500, err
+ end
+
+ local current_time = ngx.time()
+
+ if current_time < session.access_token_expiration then
+ -- Access token is still valid.
+ log.debug("Access token is still valid.")
+ return session.access_token
+ else
+ -- Access token has expired.
+ log.debug("Access token has expired.")
+ if session.refresh_token
+ and (not session.refresh_token_expiration
+ or current_time < session.refresh_token_expiration) then
+ -- Try to get a new access token, using the refresh token.
+ log.debug("Trying to get new access token using refresh
token.")
+
+ local httpc = http.new()
+ httpc:set_timeout(conf.timeout)
+
+ local params = {
+ method = "POST",
+ body = ngx.encode_args({
+ grant_type = "refresh_token",
+ client_id = conf.client_id,
+ client_secret = conf.client_secret,
+ refresh_token = session.refresh_token,
+ }),
+ ssl_verify = conf.ssl_verify,
+ headers = {
+ ["Content-Type"] = "application/x-www-form-urlencoded"
+ }
+ }
+
+ local current_time = ngx.time()
+
+ local res, err = httpc:request_uri(token_endpoint, params)
+
+ if not res then
+ err = "Accessing token endpoint URL (" .. token_endpoint
+ .. ") failed: " .. err
+ log.error(err)
+ return nil, err
+ end
+
+ log.debug("Response data: " .. res.body)
+ local json, err = authz_keycloak_parse_json_response(res)
+
+ if not json then
+ err = "Could not decode JSON from token endpoint"
+ .. (err and (": " .. err) or '.')
+ log.error(err)
+ return nil, err
+ end
+
+ if not json.access_token then
+ -- Clear session.
+ log.debug("Answer didn't contain a new access token.
Clearing session.")
+ session = nil
+ else
+ log.debug("Got new access token.")
+ -- Save access token.
+ session.access_token = json.access_token
+
+ -- Calculate and save access token expiry time.
+ session.access_token_expiration = current_time
+ + authz_keycloak_access_token_expires_in(conf,
json.expires_in)
+
+ -- Save refresh token, maybe.
+ if json.refresh_token ~= nil then
+ log.debug("Got new refresh token.")
+ session.refresh_token = json.refresh_token
+
+ -- Calculate and save refresh token expiry time.
+ session.refresh_token_expiration = current_time
+ + authz_keycloak_refresh_token_expires_in(conf,
+
json.refresh_expires_in)
+ end
+
+ authz_keycloak_cache_set("access_tokens",
+ token_endpoint .. ":" ..
conf.client_id,
+ core.json.encode(session), 24 *
60 * 60)
+ end
+ else
+ -- No refresh token available, or it has expired. Clear
session.
+ log.debug("No or expired refresh token. Clearing session.")
+ session = nil
+ end
+ end
+ end
+
+ if not session then
+ -- No session available. Create a new one.
+
+ core.log.debug("Getting access token for Protection API from token
endpoint.")
+ local httpc = http.new()
+ httpc:set_timeout(conf.timeout)
+
+ local params = {
+ method = "POST",
+ body = ngx.encode_args({
+ grant_type = "client_credentials",
+ client_id = conf.client_id,
+ client_secret = conf.client_secret,
+ }),
+ ssl_verify = conf.ssl_verify,
+ headers = {
+ ["Content-Type"] = "application/x-www-form-urlencoded"
+ }
+ }
+
+ local current_time = ngx.time()
+
+ local res, err = httpc:request_uri(token_endpoint, params)
+
+ if not res then
+ err = "Accessing token endpoint URL (" .. token_endpoint .. ")
failed: " .. err
+ log.error(err)
+ return nil, err
+ end
+
+ log.debug("Response data: " .. res.body)
+ local json, err = authz_keycloak_parse_json_response(res)
+
+ if not json then
+ err = "Could not decode JSON from token endpoint" .. (err and (": "
.. err) or '.')
+ log.error(err)
+ return nil, err
+ end
+
+ if not json.access_token then
+ err = "Response does not contain access_token field."
+ log.error(err)
+ return nil, err
+ end
+
+ session = {}
+
+ -- Save access token.
+ session.access_token = json.access_token
+
+ -- Calculate and save access token expiry time.
+ session.access_token_expiration = current_time
+ + authz_keycloak_access_token_expires_in(conf, json.expires_in)
+
+ -- Save refresh token, maybe.
+ if json.refresh_token ~= nil then
+ session.refresh_token = json.refresh_token
+
+ -- Calculate and save refresh token expiry time.
+ session.refresh_token_expiration = current_time
+ + authz_keycloak_refresh_token_expires_in(conf,
json.refresh_expires_in)
+ end
+
+ authz_keycloak_cache_set("access_tokens", token_endpoint .. ":" ..
conf.client_id,
+ core.json.encode(session), 24 * 60 * 60)
Review comment:
Ditto
##########
File path: apisix/plugins/authz-keycloak.lua
##########
@@ -224,31 +239,332 @@ local function authz_keycloak_get_token_endpoint(conf)
end
-local function is_path_protected(conf)
- -- TODO if permissions are empty lazy load paths from Keycloak
- if conf.permissions == nil then
- return false
+local function authz_keycloak_get_resource_registration_endpoint(conf)
+ return authz_keycloak_get_endpoint(conf, "resource_registration_endpoint")
+end
+
+
+-- computes access_token expires_in value (in seconds)
+local function authz_keycloak_access_token_expires_in(opts, expires_in)
+ return (expires_in or opts.access_token_expires_in or 300)
+ - 1 - (opts.access_token_expires_leeway or 0)
+end
+
+
+-- computes refresh_token expires_in value (in seconds)
+local function authz_keycloak_refresh_token_expires_in(opts, expires_in)
+ return (expires_in or opts.refresh_token_expires_in or 3600)
+ - 1 - (opts.refresh_token_expires_leeway or 0)
Review comment:
Ditto
##########
File path: apisix/plugins/authz-keycloak.lua
##########
@@ -53,10 +54,24 @@ local schema = {
keepalive_timeout = {type = "integer", minimum = 1000, default =
60000},
keepalive_pool = {type = "integer", minimum = 1, default = 5},
ssl_verify = {type = "boolean", default = true},
+ client_id = {type = "string", minLength = 1, maxLength = 100},
+ client_secret = {type = "string", minLength = 1, maxLength = 100},
+ lazy_load_paths = {type = "boolean", default = false},
+ http_method_as_scope = {type = "boolean", default = false},
},
+ required = {"client_id"},
Review comment:
To avoid break change, we should not make `client_id` required. If
`client_id` missed, we should continue the old logic.
##########
File path: apisix/plugins/authz-keycloak.lua
##########
@@ -224,31 +239,332 @@ local function authz_keycloak_get_token_endpoint(conf)
end
-local function is_path_protected(conf)
- -- TODO if permissions are empty lazy load paths from Keycloak
- if conf.permissions == nil then
- return false
+local function authz_keycloak_get_resource_registration_endpoint(conf)
+ return authz_keycloak_get_endpoint(conf, "resource_registration_endpoint")
+end
+
+
+-- computes access_token expires_in value (in seconds)
+local function authz_keycloak_access_token_expires_in(opts, expires_in)
+ return (expires_in or opts.access_token_expires_in or 300)
+ - 1 - (opts.access_token_expires_leeway or 0)
+end
+
+
+-- computes refresh_token expires_in value (in seconds)
+local function authz_keycloak_refresh_token_expires_in(opts, expires_in)
+ return (expires_in or opts.refresh_token_expires_in or 3600)
+ - 1 - (opts.refresh_token_expires_leeway or 0)
+end
+
+
+local function authz_keycloak_ensure_sa_access_token(conf)
+ local token_endpoint = authz_keycloak_get_token_endpoint(conf)
+
+ if not token_endpoint then
+ log.error("Unable to determine token endpoint.")
+ return 500, "Unable to determine token endpoint."
end
- return true
+
+ local session = authz_keycloak_cache_get("access_tokens", token_endpoint
.. ":"
+ .. conf.client_id)
+
+ if session then
+ -- Decode session string.
+ local err
+ session, err = core.json.decode(session)
+
+ if not session then
+ -- Should never happen.
+ return 500, err
+ end
+
+ local current_time = ngx.time()
+
+ if current_time < session.access_token_expiration then
+ -- Access token is still valid.
+ log.debug("Access token is still valid.")
+ return session.access_token
+ else
+ -- Access token has expired.
+ log.debug("Access token has expired.")
+ if session.refresh_token
+ and (not session.refresh_token_expiration
+ or current_time < session.refresh_token_expiration) then
+ -- Try to get a new access token, using the refresh token.
+ log.debug("Trying to get new access token using refresh
token.")
+
+ local httpc = http.new()
+ httpc:set_timeout(conf.timeout)
+
+ local params = {
+ method = "POST",
+ body = ngx.encode_args({
+ grant_type = "refresh_token",
+ client_id = conf.client_id,
+ client_secret = conf.client_secret,
+ refresh_token = session.refresh_token,
+ }),
+ ssl_verify = conf.ssl_verify,
+ headers = {
+ ["Content-Type"] = "application/x-www-form-urlencoded"
+ }
+ }
+
+ local current_time = ngx.time()
Review comment:
We already set `current_time` above?
##########
File path: apisix/plugins/authz-keycloak.lua
##########
@@ -224,31 +239,332 @@ local function authz_keycloak_get_token_endpoint(conf)
end
-local function is_path_protected(conf)
- -- TODO if permissions are empty lazy load paths from Keycloak
- if conf.permissions == nil then
- return false
+local function authz_keycloak_get_resource_registration_endpoint(conf)
+ return authz_keycloak_get_endpoint(conf, "resource_registration_endpoint")
+end
+
+
+-- computes access_token expires_in value (in seconds)
+local function authz_keycloak_access_token_expires_in(opts, expires_in)
+ return (expires_in or opts.access_token_expires_in or 300)
+ - 1 - (opts.access_token_expires_leeway or 0)
Review comment:
Where do we set `access_token_expires_in` and
`access_token_expires_leeway`?
##########
File path: apisix/cli/ngx_tpl.lua
##########
@@ -146,6 +146,9 @@ http {
lua_shared_dict jwks 1m; # cache for JWKs
lua_shared_dict introspection 10m; # cache for JWT verification
results
+ # for authz-keycloak
+ lua_shared_dict access_tokens 1m; # cache for service account
access tokens
Review comment:
Need to add it after:
https://github.com/apache/apisix/blob/bbbdf58d555ee89144b6923e751deabf7e0bbf15/t/APISIX.pm#L248
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]