From 09efc175e74cb4b21ac45c739623eb0ed9882386 Mon Sep 17 00:00:00 2001 From: Marcelo Da Cruz Pinto Date: Tue, 28 Jan 2020 16:07:58 -0800 Subject: [PATCH 1/3] Adding support for JWKS-based token validation and token attribute mapping to upstream headers --- kong/plugins/oidc/handler.lua | 11 ++++++++--- kong/plugins/oidc/schema.lua | 4 +++- kong/plugins/oidc/utils.lua | 20 +++++++++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/kong/plugins/oidc/handler.lua b/kong/plugins/oidc/handler.lua index 07f05af5..5465afd0 100644 --- a/kong/plugins/oidc/handler.lua +++ b/kong/plugins/oidc/handler.lua @@ -30,7 +30,7 @@ function handle(oidcConfig) if oidcConfig.introspection_endpoint then response = introspect(oidcConfig) if response then - utils.injectUser(response) + utils.injectUser(response, oidcConfig) end end @@ -38,7 +38,7 @@ function handle(oidcConfig) response = make_oidc(oidcConfig) if response then if (response.user) then - utils.injectUser(response.user) + utils.injectUser(response.user, oidcConfig) end if (response.access_token) then utils.injectAccessToken(response.access_token) @@ -65,7 +65,12 @@ end function introspect(oidcConfig) if utils.has_bearer_access_token() or oidcConfig.bearer_only == "yes" then - local res, err = require("resty.openidc").introspect(oidcConfig) + local res, err + if oidcConfig.use_jwks == "yes" then + res, err = require("resty.openidc").bearer_jwt_verify(oidcConfig) + else + res, err = require("resty.openidc").introspect(oidcConfig) + end if err then if oidcConfig.bearer_only == "yes" then ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. oidcConfig.realm .. '",error="' .. err .. '"' diff --git a/kong/plugins/oidc/schema.lua b/kong/plugins/oidc/schema.lua index ffb55b37..c8328851 100644 --- a/kong/plugins/oidc/schema.lua +++ b/kong/plugins/oidc/schema.lua @@ -13,11 +13,13 @@ return { scope = { type = "string", required = true, default = "openid" }, response_type = { type = "string", required = true, default = "code" }, ssl_verify = { type = "string", required = true, default = "no" }, + use_jwks = { type = "string", required = true, default = "no" }, token_endpoint_auth_method = { type = "string", required = true, default = "client_secret_post" }, session_secret = { type = "string", required = false }, recovery_page_path = { type = "string" }, logout_path = { type = "string", required = false, default = '/logout' }, redirect_after_logout_uri = { type = "string", required = false, default = '/' }, - filters = { type = "string" } + filters = { type = "string" }, + mappings = { type = "array", default = {}, } } } diff --git a/kong/plugins/oidc/utils.lua b/kong/plugins/oidc/utils.lua index 3686bbf6..41c5ef32 100644 --- a/kong/plugins/oidc/utils.lua +++ b/kong/plugins/oidc/utils.lua @@ -53,11 +53,13 @@ function M.get_options(config, ngx) scope = config.scope, response_type = config.response_type, ssl_verify = config.ssl_verify, + use_jwks = config.use_jwks, token_endpoint_auth_method = config.token_endpoint_auth_method, recovery_page_path = config.recovery_page_path, filters = parseFilters(config.filters), logout_path = config.logout_path, redirect_after_logout_uri = config.redirect_after_logout_uri, + mappings = config.mappings, } end @@ -76,13 +78,29 @@ function M.injectIDToken(idToken) ngx.req.set_header("X-ID-Token", ngx.encode_base64(tokenStr)) end -function M.injectUser(user) +function M.injectUser(user, oidcConfig) local tmp_user = user tmp_user.id = user.sub tmp_user.username = user.preferred_username ngx.ctx.authenticated_credential = tmp_user local userinfo = cjson.encode(user) ngx.req.set_header("X-Userinfo", ngx.encode_base64(userinfo)) + + for i, value in ipairs(oidcConfig.mappings) do + local f, from, to + f = string.gmatch(value, "[^:]+") + from = f() + to = f() + if from ~= nil and to ~= nil then + if user[from] ~= nil then + ngx.req.set_header(to, user[from]) + else + ngx.log(ngx.WARN, "Key '" .. from .. "' not present on token") + end + else + ngx.log(ngx.ERR, "Ignoring incorrect configuration: " .. value) + end + end end function M.has_bearer_access_token() From c6ab7d8d84def9a19dfeccd4d173ded66fb05eb1 Mon Sep 17 00:00:00 2001 From: Marcelo Da Cruz Pinto Date: Wed, 29 Jan 2020 09:31:11 -0800 Subject: [PATCH 2/3] Fixing unit tests --- kong/plugins/oidc/utils.lua | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/kong/plugins/oidc/utils.lua b/kong/plugins/oidc/utils.lua index 41c5ef32..dfd19724 100644 --- a/kong/plugins/oidc/utils.lua +++ b/kong/plugins/oidc/utils.lua @@ -86,19 +86,21 @@ function M.injectUser(user, oidcConfig) local userinfo = cjson.encode(user) ngx.req.set_header("X-Userinfo", ngx.encode_base64(userinfo)) - for i, value in ipairs(oidcConfig.mappings) do - local f, from, to - f = string.gmatch(value, "[^:]+") - from = f() - to = f() - if from ~= nil and to ~= nil then - if user[from] ~= nil then - ngx.req.set_header(to, user[from]) + if oidcConfig.mappings ~= nil then + for i, value in ipairs(oidcConfig.mappings) do + local f, from, to + f = string.gmatch(value, "[^:]+") + from = f() + to = f() + if from ~= nil and to ~= nil then + if user[from] ~= nil then + ngx.req.set_header(to, user[from]) + else + ngx.log(ngx.WARN, "Key '" .. from .. "' not present on token") + end else - ngx.log(ngx.WARN, "Key '" .. from .. "' not present on token") + ngx.log(ngx.ERR, "Ignoring incorrect configuration: " .. value) end - else - ngx.log(ngx.ERR, "Ignoring incorrect configuration: " .. value) end end end From 5406f6050c513ee38d5d352245200f71da5cc74c Mon Sep 17 00:00:00 2001 From: Marcelo Da Cruz Pinto Date: Wed, 29 Jan 2020 11:15:57 -0800 Subject: [PATCH 3/3] Increasing coverage --- test/unit/mockable_case.lua | 1 + test/unit/test_handler_mocking_openidc.lua | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/test/unit/mockable_case.lua b/test/unit/mockable_case.lua index 66aa367c..1fa70c79 100644 --- a/test/unit/mockable_case.lua +++ b/test/unit/mockable_case.lua @@ -8,6 +8,7 @@ function MockableCase:setUp() self.mocked_ngx = { DEBUG = "debug", ERR = "error", + WARN = "warn", HTTP_UNAUTHORIZED = 401, ctx = {}, header = {}, diff --git a/test/unit/test_handler_mocking_openidc.lua b/test/unit/test_handler_mocking_openidc.lua index 1898d85d..f3d0de12 100644 --- a/test/unit/test_handler_mocking_openidc.lua +++ b/test/unit/test_handler_mocking_openidc.lua @@ -193,6 +193,25 @@ function TestHandler:test_bearer_only_with_bad_token() lu.assertFalse(self:log_contains("introspect succeeded")) end +function TestHandler:test_introspect_bearer_token_and_property_mapping() + self.module_resty.openidc.bearer_jwt_verify = function(opts) + return {foo = "bar"}, false + end + ngx.req.get_headers = function() return {Authorization = "Bearer xxx"} end + + ngx.encode_base64 = function(x) return "x" end + + local headers = {} + ngx.req.set_header = function(h, v) + headers[h] = v + end + + self.handler:access({introspection_endpoint = "x", bearer_only = "yes", use_jwks = "yes", mappings = {'foo:X-Foo', 'incorrect', 'not:present'}}) + lu.assertEquals(headers["X-Foo"], 'bar') + lu.assertTrue(self:log_contains("not present on token")) + lu.assertTrue(self:log_contains("Ignoring incorrect configuration")) +end + lu.run()