From 4af92db814a5b5a31e2152d208946fdfe6d3826e Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 30 Nov 2020 21:44:00 +0100 Subject: [PATCH 001/108] Make headers to add to request configurable. --- apisix/plugins/openid-connect.lua | 113 ++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 21 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index 29aca4c85ebd..235c22f89913 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -40,7 +40,11 @@ local schema = { logout_path = {type = "string"}, -- default is /logout redirect_uri = {type = "string"}, -- default is ngx.var.request_uri public_key = {type = "string"}, - token_signing_alg_values_expected = {type = "string"} + token_signing_alg_values_expected = {type = "string"}, + set_access_token_header = {type = "boolean"}, -- default is true + set_userinfo_token_header = {type = "boolean"}, -- default is true + set_id_token_header = {type = "boolean"}, -- default is true + access_token_in_authorization_header = {type = "boolean"} -- default is false }, required = {"client_id", "client_secret", "discovery"} } @@ -82,63 +86,129 @@ function _M.check_schema(conf) conf.logout_path = '/logout' end + if not conf.set_access_token_header then + conf.set_access_token_header = true + end + + if not conf.set_userinfo_token_header then + conf.set_userinfo_token_header = true + end + + if not conf.set_id_token_header then + conf.set_id_token_header = true + end + + if not conf.access_token_in_authorization_header then + conf.access_token_in_authorization_header = false + end + return true end -local function has_bearer_access_token(ctx) +local function check_bearer_access_token(ctx) + -- Get Authorization header, maybe. local auth_header = core.request.header(ctx, "Authorization") if not auth_header then - return false + -- No Authorization header, get X-Access-Token header, maybe. + local access_token_header = core.request.header(ctx, "X-Access-Token") + if not access_token_header then + -- No X-Access-Token header neither. + return false, nil, nil + end + + -- Return extracted header value. + return true, access_token_header, nil end + -- Check format of Authorization header. local res, err = ngx_re.split(auth_header, " ", nil, nil, 2) if not res then - return false, err + return false, nil, err end - if res[1] == "bearer" then - return true + if string.lower(res[1]) == "bearer" then + -- Return extracted token. + return true, res[2], nil end return false end +local function add_user_header(user) + local userinfo = core.json.encode(user) + ngx.req.set_header("X-Userinfo", ngx_encode_base64(userinfo)) +end + + +local function add_access_token_header(ctx, conf, token) + -- Add Authorization or X-Access-Token header, respectively, if not already set. + if conf.set_access_token_header then + if conf.access_token_in_authorization_header then + if not core.request.header(ctx, "Authorization") then + -- Add Authorization header. + ngx.req.set_header("Authorization", "Bearer " .. token) + end + else + if not core.request.header(ctx, "X-Access-Token") then + -- Add X-Access-Token header. + ngx.req.set_header("X-Access-Token", token) + end + end + end +end + + local function introspect(ctx, conf) - if has_bearer_access_token(ctx) or conf.bearer_only then + -- Extract token, maybe. Ignore errors. + local has_token, token, _ = check_bearer_access_token(ctx) + + -- Check if token was extracted or if we always require a token in the request. + if has_token(ctx) or conf.bearer_only then local res, err if conf.public_key then + -- Validate token against public key. res, err = openidc.bearer_jwt_verify(conf) if res then + -- Token is valid. + + -- Add configured access token header, maybe. + add_access_token_header(ctx, conf, token) return res end else + -- Validate token against introspection endpoint. res, err = openidc.introspect(conf) if err then return ngx.HTTP_UNAUTHORIZED, err else + -- Token is valid and res contains the response from the introspection endpoint. + + if conf.set_userinfo_token_header then + -- Set X-Userinfo header to introspection endpoint response. + add_user_header(res) + end + + -- Add configured access token header, maybe. + add_access_token_header(token, conf) return res end end if conf.bearer_only then + -- If we get here, the token could not be validated, but we always require a valid token in the request. ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm .. '",error="' .. err .. '"' return ngx.HTTP_UNAUTHORIZED, err end end + -- Return nil to indicate that a token could not be extracted or validated, but that we don't want to fail quickly. return nil end -local function add_user_header(user) - local userinfo = core.json.encode(user) - ngx.req.set_header("X-Userinfo", ngx_encode_base64(userinfo)) -end - - function _M.access(plugin_conf, ctx) local conf = core.table.clone(plugin_conf) if not conf.redirect_uri then @@ -152,12 +222,10 @@ function _M.access(plugin_conf, ctx) core.log.error("failed to introspect in openidc: ", err) return response end - if response then - add_user_header(response) - end end if not response then + -- A valid token was not in the request. Try to obtain one by authenticatin against the configured identity provider. local response, err = openidc.authenticate(conf) if err then core.log.error("failed to authenticate in openidc: ", err) @@ -165,13 +233,16 @@ function _M.access(plugin_conf, ctx) end if response then - if response.user then + -- Add X-Userinfo header, maybe. + if conf.set_userinfo_token_header and response.user then add_user_header(response.user) end - if response.access_token then - ngx.req.set_header("X-Access-Token", response.access_token) - end - if response.id_token then + + -- Add configured access token header, maybe. + add_access_token_header(ctx, conf, response.access_token) + + -- Add X-ID-Token header, maybe. + if conf.set_id_token_header and response.id_token then local token = core.json.encode(response.id_token) ngx.req.set_header("X-ID-Token", ngx.encode_base64(token)) end From fc57f351aafb82d0f53fa8c5e39e8ff5281cf066 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 30 Nov 2020 22:18:37 +0100 Subject: [PATCH 002/108] Fix lines that were too long. --- apisix/plugins/openid-connect.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index 235c22f89913..a29bdde2de6e 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -197,14 +197,16 @@ local function introspect(ctx, conf) end end if conf.bearer_only then - -- If we get here, the token could not be validated, but we always require a valid token in the request. + -- If we get here, the token could not be validated, but we always require a valid + -- token in the request. ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm .. '",error="' .. err .. '"' return ngx.HTTP_UNAUTHORIZED, err end end - -- Return nil to indicate that a token could not be extracted or validated, but that we don't want to fail quickly. + -- Return nil to indicate that a token could not be extracted or validated, but that we don't + -- want to fail quickly. return nil end @@ -225,7 +227,8 @@ function _M.access(plugin_conf, ctx) end if not response then - -- A valid token was not in the request. Try to obtain one by authenticatin against the configured identity provider. + -- A valid token was not in the request. Try to obtain one by authenticatin against the + -- configured identity provider. local response, err = openidc.authenticate(conf) if err then core.log.error("failed to authenticate in openidc: ", err) From 49e904f708bf10cff05a0fefa80d6519c4aa2585 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 30 Nov 2020 22:29:37 +0100 Subject: [PATCH 003/108] Import global string module. --- apisix/plugins/openid-connect.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index a29bdde2de6e..7e01b5678da2 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -14,6 +14,7 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -- +local string = require("string") local core = require("apisix.core") local ngx_re = require("ngx.re") local openidc = require("resty.openidc") From 4c033d9344be389a354ac0840df91504ba9e3a22 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 1 Dec 2020 09:13:01 +0100 Subject: [PATCH 004/108] Fix an issue when trying to call a boolean value. --- apisix/plugins/openid-connect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index 7e01b5678da2..7e5d941eaf2e 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -166,7 +166,7 @@ local function introspect(ctx, conf) local has_token, token, _ = check_bearer_access_token(ctx) -- Check if token was extracted or if we always require a token in the request. - if has_token(ctx) or conf.bearer_only then + if has_token or conf.bearer_only then local res, err if conf.public_key then From e997baa1d064966dac75b3b3a0f24e9b2e260dea Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 1 Dec 2020 09:44:05 +0100 Subject: [PATCH 005/108] Addressing several pull request comments. --- apisix/plugins/openid-connect.lua | 38 +++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index 7e5d941eaf2e..ef77f447e950 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -14,7 +14,7 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -- -local string = require("string") +local string = string local core = require("apisix.core") local ngx_re = require("ngx.re") local openidc = require("resty.openidc") @@ -42,10 +42,26 @@ local schema = { redirect_uri = {type = "string"}, -- default is ngx.var.request_uri public_key = {type = "string"}, token_signing_alg_values_expected = {type = "string"}, - set_access_token_header = {type = "boolean"}, -- default is true - set_userinfo_token_header = {type = "boolean"}, -- default is true - set_id_token_header = {type = "boolean"}, -- default is true - access_token_in_authorization_header = {type = "boolean"} -- default is false + set_access_token_header = { + description = "Whether the access token should be added as a header to the request for downstream", + type = "boolean", + default = true + }, + set_userinfo_token_header = { + description = "Whether the user info token should be added as a header to the request for downstream.", + type = "boolean", + default = true + }, + set_id_token_header = { + description = "Whether the ID token should be added as a header to the request for downstream.", + type = "boolean", + default = true + }, + access_token_in_authorization_header = { + description = "Whether the acces token should be added in the Authorization header as opposed to the X-Access-Token header.", + type = "boolean", + default = false + } }, required = {"client_id", "client_secret", "discovery"} } @@ -193,7 +209,7 @@ local function introspect(ctx, conf) end -- Add configured access token header, maybe. - add_access_token_header(token, conf) + add_access_token_header(ctx, conf, token) return res end end @@ -207,7 +223,7 @@ local function introspect(ctx, conf) end -- Return nil to indicate that a token could not be extracted or validated, but that we don't - -- want to fail quickly. + -- want to fail quickly. return nil end @@ -238,15 +254,17 @@ function _M.access(plugin_conf, ctx) if response then -- Add X-Userinfo header, maybe. - if conf.set_userinfo_token_header and response.user then + if response.user and conf.set_userinfo_token_header then add_user_header(response.user) end -- Add configured access token header, maybe. - add_access_token_header(ctx, conf, response.access_token) + if response.access_token then + add_access_token_header(ctx, conf, response.access_token) + end -- Add X-ID-Token header, maybe. - if conf.set_id_token_header and response.id_token then + if response.id_token and conf.set_id_token_header then local token = core.json.encode(response.id_token) ngx.req.set_header("X-ID-Token", ngx.encode_base64(token)) end From fbca5d8b82e60bf1103de2bd627acb0d35f2beb3 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 1 Dec 2020 10:21:43 +0100 Subject: [PATCH 006/108] Remove old way of setting defaults for added configuration options. --- apisix/plugins/openid-connect.lua | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index ef77f447e950..d4df7c87af69 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -48,17 +48,17 @@ local schema = { default = true }, set_userinfo_token_header = { - description = "Whether the user info token should be added as a header to the request for downstream.", + description = "Whether the user info token should be added in the X-Userinfo header to the request for downstream.", type = "boolean", default = true }, set_id_token_header = { - description = "Whether the ID token should be added as a header to the request for downstream.", + description = "Whether the ID token should be added in the X-ID-Token header to the request for downstream.", type = "boolean", default = true }, access_token_in_authorization_header = { - description = "Whether the acces token should be added in the Authorization header as opposed to the X-Access-Token header.", + description = "Whether the access token should be added in the Authorization header as opposed to the X-Access-Token header.", type = "boolean", default = false } @@ -103,22 +103,6 @@ function _M.check_schema(conf) conf.logout_path = '/logout' end - if not conf.set_access_token_header then - conf.set_access_token_header = true - end - - if not conf.set_userinfo_token_header then - conf.set_userinfo_token_header = true - end - - if not conf.set_id_token_header then - conf.set_id_token_header = true - end - - if not conf.access_token_in_authorization_header then - conf.access_token_in_authorization_header = false - end - return true end From 8eabdefec78b5b5559059af8cb5b0c35325069a7 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 1 Dec 2020 11:37:18 +0100 Subject: [PATCH 007/108] Fix lines that are too long. --- apisix/plugins/openid-connect.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index c541e2ac6b89..b723a82bd1fe 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -43,22 +43,26 @@ local schema = { public_key = {type = "string"}, token_signing_alg_values_expected = {type = "string"}, set_access_token_header = { - description = "Whether the access token should be added as a header to the request for downstream", + description = "Whether the access token should be added as a header to the request " .. + "for downstream", type = "boolean", default = true }, set_userinfo_token_header = { - description = "Whether the user info token should be added in the X-Userinfo header to the request for downstream.", + description = "Whether the user info token should be added in the X-Userinfo " .. + "header to the request for downstream.", type = "boolean", default = true }, set_id_token_header = { - description = "Whether the ID token should be added in the X-ID-Token header to the request for downstream.", + description = "Whether the ID token should be added in the X-ID-Token header to " .. + "the request for downstream.", type = "boolean", default = true }, access_token_in_authorization_header = { - description = "Whether the access token should be added in the Authorization header as opposed to the X-Access-Token header.", + description = "Whether the access token should be added in the Authorization " .. + "header as opposed to the X-Access-Token header.", type = "boolean", default = false } From 6328c689e636c922a5862ed853993dcf9feeb4bc Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 1 Dec 2020 11:44:37 +0100 Subject: [PATCH 008/108] Remove trailing whitespace. --- apisix/plugins/openid-connect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index b723a82bd1fe..bd53d8bd3ca4 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -61,7 +61,7 @@ local schema = { default = true }, access_token_in_authorization_header = { - description = "Whether the access token should be added in the Authorization " .. + description = "Whether the access token should be added in the Authorization " .. "header as opposed to the X-Access-Token header.", type = "boolean", default = false From bfa4aa7297c14c181a7403daea4afdce2684040f Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 1 Dec 2020 16:06:41 +0100 Subject: [PATCH 009/108] Add auxilliary method to insert/update request header while updating the header cache in the context as well. --- apisix/plugins/openid-connect.lua | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index bd53d8bd3ca4..65094218ad20 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -148,9 +148,21 @@ local function check_bearer_access_token(ctx) end -local function add_user_header(user) +local function set_header(ctx, name, value) + -- Set a request header to the given value and update the cached headers in the context as well. + + -- Set header in request. + ngx.req.set_header(name, value) + + -- Set header in cached table, maybe. + if ctx and ctx.headers then + ctx.headers[name] = value + end + + +local function add_user_header(ctx, user) local userinfo = core.json.encode(user) - ngx.req.set_header("X-Userinfo", ngx_encode_base64(userinfo)) + set_header(ctx, "X-Userinfo", ngx_encode_base64(userinfo)) end @@ -160,12 +172,12 @@ local function add_access_token_header(ctx, conf, token) if conf.access_token_in_authorization_header then if not core.request.header(ctx, "Authorization") then -- Add Authorization header. - ngx.req.set_header("Authorization", "Bearer " .. token) + set_header(ctx, "Authorization", "Bearer " .. token) end else if not core.request.header(ctx, "X-Access-Token") then -- Add X-Access-Token header. - ngx.req.set_header("X-Access-Token", token) + set_header(ctx, "X-Access-Token", token) end end end @@ -200,7 +212,7 @@ local function introspect(ctx, conf) if conf.set_userinfo_token_header then -- Set X-Userinfo header to introspection endpoint response. - add_user_header(res) + add_user_header(ctx, res) end -- Add configured access token header, maybe. @@ -254,7 +266,7 @@ function _M.rewrite(plugin_conf, ctx) if response then -- Add X-Userinfo header, maybe. if response.user and conf.set_userinfo_token_header then - add_user_header(response.user) + add_user_header(ctx, response.user) end -- Add configured access token header, maybe. From 1aeec0bda4d421a0f441299dfddb46a09defae6a Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 1 Dec 2020 16:28:02 +0100 Subject: [PATCH 010/108] Small fixes. --- apisix/plugins/openid-connect.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index 65094218ad20..4bde47826b16 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -154,10 +154,11 @@ local function set_header(ctx, name, value) -- Set header in request. ngx.req.set_header(name, value) - -- Set header in cached table, maybe. + -- Set header in cache, maybe. if ctx and ctx.headers then ctx.headers[name] = value end +end local function add_user_header(ctx, user) @@ -277,7 +278,7 @@ function _M.rewrite(plugin_conf, ctx) -- Add X-ID-Token header, maybe. if response.id_token and conf.set_id_token_header then local token = core.json.encode(response.id_token) - ngx.req.set_header("X-ID-Token", ngx.encode_base64(token)) + set_header(ctx, "X-ID-Token", ngx.encode_base64(token)) end end end From f13fc9d81fa4771e72897a9e0c2ad3159a1b463e Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Wed, 16 Dec 2020 11:41:32 +0100 Subject: [PATCH 011/108] CHange backend from 127.0.0.1:1980 to 127.0.0.1:8888 to use mendhak/http-https-echo container which returns all headers in request. --- t/plugin/openid-connect.t | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index b1113ae74945..27854b0b1ba7 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -91,7 +91,7 @@ done -=== TEST 4: add plugin +=== TEST 4: add plugin to route --- config location /t { content_by_lua_block { @@ -112,7 +112,7 @@ done }, "upstream": { "nodes": { - "127.0.0.1:1980": 1 + "127.0.0.1:8888": 1 }, "type": "roundrobin" }, @@ -134,7 +134,7 @@ done }, "upstream": { "nodes": { - "127.0.0.1:1980": 1 + "127.0.0.1:88": 1 }, "type": "roundrobin" }, @@ -161,7 +161,7 @@ passed -=== TEST 5: access +=== TEST 5: access route w/o bearer token --- config location /t { content_by_lua_block { @@ -213,7 +213,7 @@ true }, "upstream": { "nodes": { - "127.0.0.1:1980": 1 + "127.0.0.1:8888": 1 }, "type": "roundrobin" }, @@ -236,7 +236,7 @@ true }, "upstream": { "nodes": { - "127.0.0.1:1980": 1 + "127.0.0.1:8888": 1 }, "type": "roundrobin" }, @@ -263,7 +263,7 @@ passed -=== TEST 7: access +=== TEST 7: access route w/o bearer token --- timeout: 10s --- request GET /hello @@ -302,7 +302,7 @@ WWW-Authenticate: Bearer realm=apisix }, "upstream": { "nodes": { - "127.0.0.1:1980": 1 + "127.0.0.1:8888": 1 }, "type": "roundrobin" }, @@ -329,7 +329,7 @@ WWW-Authenticate: Bearer realm=apisix }, "upstream": { "nodes": { - "127.0.0.1:1980": 1 + "127.0.0.1:8888": 1 }, "type": "roundrobin" }, @@ -443,7 +443,7 @@ jwt signature verification failed }, "upstream": { "nodes": { - "127.0.0.1:1980": 1 + "127.0.0.1:8888": 1 }, "type": "roundrobin" }, @@ -468,7 +468,7 @@ jwt signature verification failed }, "upstream": { "nodes": { - "127.0.0.1:1980": 1 + "127.0.0.1:8888": 1 }, "type": "roundrobin" }, From f088a8866ea07aa46e7e606281d7470640441eeb Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Wed, 16 Dec 2020 12:24:30 +0100 Subject: [PATCH 012/108] Fix port number. --- t/plugin/openid-connect.t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 27854b0b1ba7..93fa3fecab5a 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -134,7 +134,7 @@ done }, "upstream": { "nodes": { - "127.0.0.1:88": 1 + "127.0.0.1:8888": 1 }, "type": "roundrobin" }, @@ -276,7 +276,7 @@ WWW-Authenticate: Bearer realm=apisix -=== TEST 8: update plugin public key +=== TEST 8: update plugin public key, so tokens can be validated locally --- config location /t { content_by_lua_block { @@ -356,7 +356,7 @@ passed -=== TEST 9: access introspection with correct token +=== TEST 9: access route with valid token --- config location /t { content_by_lua_block { From a4d503da4e2c5cc8dae2017c689982a9cef03d66 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Wed, 16 Dec 2020 14:32:34 +0100 Subject: [PATCH 013/108] Revert local port back to 1980. --- t/plugin/openid-connect.t | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 93fa3fecab5a..5a5d34190712 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -112,7 +112,7 @@ done }, "upstream": { "nodes": { - "127.0.0.1:8888": 1 + "127.0.0.1:1980": 1 }, "type": "roundrobin" }, @@ -134,7 +134,7 @@ done }, "upstream": { "nodes": { - "127.0.0.1:8888": 1 + "127.0.0.1:1980": 1 }, "type": "roundrobin" }, @@ -213,7 +213,7 @@ true }, "upstream": { "nodes": { - "127.0.0.1:8888": 1 + "127.0.0.1:1980": 1 }, "type": "roundrobin" }, @@ -236,7 +236,7 @@ true }, "upstream": { "nodes": { - "127.0.0.1:8888": 1 + "127.0.0.1:1980": 1 }, "type": "roundrobin" }, @@ -302,7 +302,7 @@ WWW-Authenticate: Bearer realm=apisix }, "upstream": { "nodes": { - "127.0.0.1:8888": 1 + "127.0.0.1:1980": 1 }, "type": "roundrobin" }, @@ -329,7 +329,7 @@ WWW-Authenticate: Bearer realm=apisix }, "upstream": { "nodes": { - "127.0.0.1:8888": 1 + "127.0.0.1:1980": 1 }, "type": "roundrobin" }, @@ -443,7 +443,7 @@ jwt signature verification failed }, "upstream": { "nodes": { - "127.0.0.1:8888": 1 + "127.0.0.1:1980": 1 }, "type": "roundrobin" }, @@ -468,7 +468,7 @@ jwt signature verification failed }, "upstream": { "nodes": { - "127.0.0.1:8888": 1 + "127.0.0.1:1980": 1 }, "type": "roundrobin" }, From 76c6b8be25713c87a76d630d111b7b687f3771fe Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Wed, 16 Dec 2020 14:33:02 +0100 Subject: [PATCH 014/108] Add endpoint that reflects OIDC-relevant request headers back in response. --- t/lib/server.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/t/lib/server.lua b/t/lib/server.lua index 6dae39b95912..abb7f25727a0 100644 --- a/t/lib/server.lua +++ b/t/lib/server.lua @@ -352,6 +352,18 @@ function _M.headers() ngx.say("/headers") end +function _M.reflect_oidc_token_headers() + local hdrs = ngx.req.get_headers() + local reflect_headers = {"Authorization", "X-Access-Token", "X-ID-Token", "X-Userinfo"} + + for _, h in ipairs(reflect_headers) do + if hdrs['Authorization'] then + ngx.header['Authorization'] = hdrs['Authorization'] + end + end + + ngx.say("/reflect_oidc_token_headers") +end function _M.log() ngx.req.read_body() From ade0bec715e9c3c451bcdbea55811fe10facd438 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Wed, 16 Dec 2020 14:52:55 +0100 Subject: [PATCH 015/108] Add test cases to check if request bearer token in Authoriztation header gets also sent to downstream in X-Access-Token header (default), or not, if so configured. --- t/plugin/openid-connect.t | 157 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 5a5d34190712..4d0a02e4cee9 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -388,6 +388,163 @@ true +=== TEST 9a: access route with valid token in Authorization header and verify token is sent to upstream in X-Access-Token header as well +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/reflect_oidc_token_headers" + local res, err = httpc:request_uri(uri, { + method = "GET", + headers = { + ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" .. + ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" .. + "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" .. + "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" .. + "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w", + } + }) + ngx.status = res.status + if res.status == 200 then + ngx.say(res.body) + end + } + } +--- request +GET /t +--- response_body +/reflect_oidc_token_headers +--- response_headers +Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w +X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w +!X-ID-Token +!X-Userinfo +--- no_error_log +[error] +--- error_code: 200 + + + +=== TEST 9b: Update plugin to use Authorization header. +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ "plugins": { + "openid-connect": { + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "https://samples.auth0.com/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", + "ssl_verify": false, + "timeout": 10, + "bearer_only": true, + "scope": "apisix", + "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. + [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. + [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. + [[-----END PUBLIC KEY-----", + "token_signing_alg_values_expected": "RS256", + "access_token_in_authorization_header": true + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]], + [[{ "node": { + "value": { + "plugins": { + "openid-connect": { + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "https://samples.auth0.com/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", + "ssl_verify": false, + "timeout": 10000, + "bearer_only": true, + "scope": "apisix", + "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. + [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. + [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. + [[-----END PUBLIC KEY-----", + "token_signing_alg_values_expected": "RS256" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }, + "key": "/apisix/routes/1" + }, + "action": "set" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 9c: Access route with valid token in Authorization header and verify token is still only sent to upstream in Authorization header. +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/reflect_oidc_token_headers" + local res, err = httpc:request_uri(uri, { + method = "GET", + headers = { + ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" .. + ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" .. + "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" .. + "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" .. + "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w", + } + }) + ngx.status = res.status + if res.status == 200 then + ngx.say(res.body) + end + } + } +--- request +GET /t +--- response_body +/reflect_oidc_token_headers +--- response_headers +Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w +!X-Access-Token +!X-ID-Token +!X-Userinfo +--- no_error_log +[error] +--- error_code: 200 + + + === TEST 10: access introspection with wrong token --- config location /t { From 19c62a79a59ad9bbb9cf8c34c81fbfc1de461a26 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Wed, 16 Dec 2020 14:56:16 +0100 Subject: [PATCH 016/108] Fix issue when not all desired headers were reflected. --- t/lib/server.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/lib/server.lua b/t/lib/server.lua index abb7f25727a0..7f0bbd5da5ff 100644 --- a/t/lib/server.lua +++ b/t/lib/server.lua @@ -357,8 +357,8 @@ function _M.reflect_oidc_token_headers() local reflect_headers = {"Authorization", "X-Access-Token", "X-ID-Token", "X-Userinfo"} for _, h in ipairs(reflect_headers) do - if hdrs['Authorization'] then - ngx.header['Authorization'] = hdrs['Authorization'] + if hdrs[h] then + ngx.header[h] = hdrs[h] end end From 84c868450e95ca0698d6b06d82193cd05ea5e3da Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Wed, 16 Dec 2020 15:51:32 +0100 Subject: [PATCH 017/108] Fix header verification tests. --- t/plugin/openid-connect.t | 93 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 4d0a02e4cee9..fb2c73fc968a 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -388,7 +388,87 @@ true -=== TEST 9a: access route with valid token in Authorization header and verify token is sent to upstream in X-Access-Token header as well +=== TEST 9a: update plugin with different upstream endpoint +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ "plugins": { + "openid-connect": { + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "https://samples.auth0.com/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", + "ssl_verify": false, + "timeout": 10, + "bearer_only": true, + "scope": "apisix", + "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. + [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. + [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. + [[-----END PUBLIC KEY-----", + "token_signing_alg_values_expected": "RS256" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/reflect_oidc_token_headers" + }]], + [[{ "node": { + "value": { + "plugins": { + "openid-connect": { + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "https://samples.auth0.com/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", + "ssl_verify": false, + "timeout": 10000, + "bearer_only": true, + "scope": "apisix", + "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. + [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. + [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. + [[-----END PUBLIC KEY-----", + "token_signing_alg_values_expected": "RS256" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/reflect_oidc_token_headers" + }, + "key": "/apisix/routes/1" + }, + "action": "set" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 9b: access route with valid token in Authorization header and verify token is sent to upstream in X-Access-Token header as well --- config location /t { content_by_lua_block { @@ -426,7 +506,7 @@ X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsIml -=== TEST 9b: Update plugin to use Authorization header. +=== TEST 9c: Update plugin to use Authorization header. --- config location /t { content_by_lua_block { @@ -457,7 +537,7 @@ X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsIml }, "type": "roundrobin" }, - "uri": "/hello" + "uri": "/reflect_oidc_token_headers" }]], [[{ "node": { "value": { @@ -475,7 +555,8 @@ X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsIml [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" + "token_signing_alg_values_expected": "RS256", + "access_token_in_authorization_header": true } }, "upstream": { @@ -484,7 +565,7 @@ X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsIml }, "type": "roundrobin" }, - "uri": "/hello" + "uri": "/reflect_oidc_token_headers" }, "key": "/apisix/routes/1" }, @@ -507,7 +588,7 @@ passed -=== TEST 9c: Access route with valid token in Authorization header and verify token is still only sent to upstream in Authorization header. +=== TEST 9d: Access route with valid token in Authorization header and verify token is still only sent to upstream in Authorization header. --- config location /t { content_by_lua_block { From 4ce30acebde2c40b171878e6cd47fbcfcd3a2a7a Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Wed, 16 Dec 2020 16:51:41 +0100 Subject: [PATCH 018/108] Fix test cases. --- t/plugin/openid-connect.t | 82 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index fb2c73fc968a..c7df0f700b0b 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -495,6 +495,7 @@ passed GET /t --- response_body /reflect_oidc_token_headers + --- response_headers Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w @@ -615,6 +616,7 @@ passed GET /t --- response_body /reflect_oidc_token_headers + --- response_headers Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w !X-Access-Token @@ -626,6 +628,86 @@ Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgM +=== TEST 9z: update plugin public key, so tokens can be validated locally +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ "plugins": { + "openid-connect": { + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "https://samples.auth0.com/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", + "ssl_verify": false, + "timeout": 10, + "bearer_only": true, + "scope": "apisix", + "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. + [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. + [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. + [[-----END PUBLIC KEY-----", + "token_signing_alg_values_expected": "RS256" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]], + [[{ "node": { + "value": { + "plugins": { + "openid-connect": { + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "https://samples.auth0.com/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", + "ssl_verify": false, + "timeout": 10000, + "bearer_only": true, + "scope": "apisix", + "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. + [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. + [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. + [[-----END PUBLIC KEY-----", + "token_signing_alg_values_expected": "RS256" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }, + "key": "/apisix/routes/1" + }, + "action": "set" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + === TEST 10: access introspection with wrong token --- config location /t { From 1b9cf288a4debc6c803aef2d97c2ec7cac27ce42 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 13:20:10 +0100 Subject: [PATCH 019/108] Temporarily run only openid-connect plugin tests. --- .travis/linux_openresty_runner.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/linux_openresty_runner.sh b/.travis/linux_openresty_runner.sh index f451bbe053f0..d24948012693 100755 --- a/.travis/linux_openresty_runner.sh +++ b/.travis/linux_openresty_runner.sh @@ -138,7 +138,7 @@ script() { make lint && make license-check || exit 1 # APISIX_ENABLE_LUACOV=1 PERL5LIB=.:$PERL5LIB prove -Itest-nginx/lib -r t - PERL5LIB=.:$PERL5LIB prove -Itest-nginx/lib -r t + PERL5LIB=.:$PERL5LIB prove -Itest-nginx/lib -r t/plugin/openid-connect.t } after_success() { From de7600e554f5eabb8107b7e9715c1811b57da082 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 13:36:19 +0100 Subject: [PATCH 020/108] Use different endpoint to reflect headers back in response body. --- t/plugin/openid-connect.t | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index c7df0f700b0b..0e4463e48af3 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -418,7 +418,7 @@ true }, "type": "roundrobin" }, - "uri": "/reflect_oidc_token_headers" + "uri": "/uri" }]], [[{ "node": { "value": { @@ -445,7 +445,7 @@ true }, "type": "roundrobin" }, - "uri": "/reflect_oidc_token_headers" + "uri": "/uri" }, "key": "/apisix/routes/1" }, @@ -474,7 +474,7 @@ passed content_by_lua_block { local http = require "resty.http" local httpc = http.new() - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/reflect_oidc_token_headers" + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" local res, err = httpc:request_uri(uri, { method = "GET", headers = { @@ -494,13 +494,9 @@ passed --- request GET /t --- response_body -/reflect_oidc_token_headers - ---- response_headers -Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w -X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w -!X-ID-Token -!X-Userinfo +uri: /uri +authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w +x-access-token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w --- no_error_log [error] --- error_code: 200 @@ -538,7 +534,7 @@ X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsIml }, "type": "roundrobin" }, - "uri": "/reflect_oidc_token_headers" + "uri": "/uri" }]], [[{ "node": { "value": { @@ -566,7 +562,7 @@ X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsIml }, "type": "roundrobin" }, - "uri": "/reflect_oidc_token_headers" + "uri": "/uri" }, "key": "/apisix/routes/1" }, @@ -595,7 +591,7 @@ passed content_by_lua_block { local http = require "resty.http" local httpc = http.new() - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/reflect_oidc_token_headers" + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" local res, err = httpc:request_uri(uri, { method = "GET", headers = { @@ -615,13 +611,8 @@ passed --- request GET /t --- response_body -/reflect_oidc_token_headers - ---- response_headers -Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w -!X-Access-Token -!X-ID-Token -!X-Userinfo +uri: /uri +authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w --- no_error_log [error] --- error_code: 200 From 2e19611401c255b097cd78ae7f69f8f127a0af7e Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 13:47:15 +0100 Subject: [PATCH 021/108] Use direct GET request. --- t/plugin/openid-connect.t | 52 +++++---------------------------------- 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 0e4463e48af3..a32107053342 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -469,30 +469,10 @@ passed === TEST 9b: access route with valid token in Authorization header and verify token is sent to upstream in X-Access-Token header as well ---- config - location /t { - content_by_lua_block { - local http = require "resty.http" - local httpc = http.new() - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" - local res, err = httpc:request_uri(uri, { - method = "GET", - headers = { - ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" .. - ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" .. - "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" .. - "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" .. - "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w", - } - }) - ngx.status = res.status - if res.status == 200 then - ngx.say(res.body) - end - } - } --- request -GET /t +GET /uri HTTP/1.1 +--- more_headers +Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w --- response_body uri: /uri authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w @@ -586,30 +566,10 @@ passed === TEST 9d: Access route with valid token in Authorization header and verify token is still only sent to upstream in Authorization header. ---- config - location /t { - content_by_lua_block { - local http = require "resty.http" - local httpc = http.new() - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" - local res, err = httpc:request_uri(uri, { - method = "GET", - headers = { - ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" .. - ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" .. - "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" .. - "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" .. - "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w", - } - }) - ngx.status = res.status - if res.status == 200 then - ngx.say(res.body) - end - } - } --- request -GET /t +GET /uri HTTP/1.1 +--- more_headers +Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w --- response_body uri: /uri authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w From 989f6b9f1ce0c5915d2021bb66a55345b64c54e2 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 13:55:19 +0100 Subject: [PATCH 022/108] Fix unit tests. --- t/plugin/openid-connect.t | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index a32107053342..08dbb16d8800 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -476,7 +476,9 @@ Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgM --- response_body uri: /uri authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w +host: localhost x-access-token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w +x-real-ip: 127.0.0.1 --- no_error_log [error] --- error_code: 200 @@ -572,7 +574,9 @@ GET /uri HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w --- response_body uri: /uri +host: localhost authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w +x-real-ip: 127.0.0.1 --- no_error_log [error] --- error_code: 200 From d865b5d3d8d215a25ec84b1884aba8f5a0861230 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 14:08:03 +0100 Subject: [PATCH 023/108] Fix test case. --- t/plugin/openid-connect.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 08dbb16d8800..656b1c116f1a 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -574,8 +574,8 @@ GET /uri HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w --- response_body uri: /uri -host: localhost authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w +host: localhost x-real-ip: 127.0.0.1 --- no_error_log [error] From 032b0e8d75048c1bde9e7b47f2a043b9ff516df2 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 21:24:46 +0100 Subject: [PATCH 024/108] Remove obsolete helper function. --- t/lib/server.lua | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/t/lib/server.lua b/t/lib/server.lua index 7f0bbd5da5ff..8bf4dbfa0495 100644 --- a/t/lib/server.lua +++ b/t/lib/server.lua @@ -352,19 +352,6 @@ function _M.headers() ngx.say("/headers") end -function _M.reflect_oidc_token_headers() - local hdrs = ngx.req.get_headers() - local reflect_headers = {"Authorization", "X-Access-Token", "X-ID-Token", "X-Userinfo"} - - for _, h in ipairs(reflect_headers) do - if hdrs[h] then - ngx.header[h] = hdrs[h] - end - end - - ngx.say("/reflect_oidc_token_headers") -end - function _M.log() ngx.req.read_body() local body = ngx.req.get_body_data() From 0ead6da65c6e476777d15a03a1aebeed9684e3b1 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 21:25:03 +0100 Subject: [PATCH 025/108] Improve documentation. Start new test case that simulates the authorization code flow. --- t/plugin/openid-connect.t | 87 +++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 656b1c116f1a..04ccac6c5b11 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -23,7 +23,7 @@ run_tests; __DATA__ -=== TEST 1: sanity +=== TEST 1: Sanity check with minimal valid configuration. --- config location /t { content_by_lua_block { @@ -45,7 +45,7 @@ done -=== TEST 2: missing client_id +=== TEST 2: Missing `client_id`. --- config location /t { content_by_lua_block { @@ -68,7 +68,7 @@ done -=== TEST 3: wrong type of string +=== TEST 3: Wrong type for `client_id`. --- config location /t { content_by_lua_block { @@ -91,7 +91,7 @@ done -=== TEST 4: add plugin to route +=== TEST 4: Add plugin to route for path `/hello`. --- config location /t { content_by_lua_block { @@ -161,7 +161,7 @@ passed -=== TEST 5: access route w/o bearer token +=== TEST 5: Access route w/o bearer token. Should redirect to authentication endpoint of ID provider. --- config location /t { content_by_lua_block { @@ -191,7 +191,7 @@ true -=== TEST 6: update plugin with bearer_only=true +=== TEST 6: Update plugin with `bearer_only=true`. --- config location /t { content_by_lua_block { @@ -263,7 +263,7 @@ passed -=== TEST 7: access route w/o bearer token +=== TEST 7: Access route w/o bearer token. Should return 401. --- timeout: 10s --- request GET /hello @@ -276,7 +276,7 @@ WWW-Authenticate: Bearer realm=apisix -=== TEST 8: update plugin public key, so tokens can be validated locally +=== TEST 8: Update plugin with ID provider public key, so tokens can be validated locally. --- config location /t { content_by_lua_block { @@ -356,7 +356,7 @@ passed -=== TEST 9: access route with valid token +=== TEST 9: Access route with valid token. --- config location /t { content_by_lua_block { @@ -388,7 +388,7 @@ true -=== TEST 9a: update plugin with different upstream endpoint +=== TEST 9a: Update route URI to '/uri' where upstream endpoint returns request headers in response body. --- config location /t { content_by_lua_block { @@ -468,7 +468,7 @@ passed -=== TEST 9b: access route with valid token in Authorization header and verify token is sent to upstream in X-Access-Token header as well +=== TEST 9b: Access route with valid token in `Authorization` header. Upstream should additionally get the token in the `X-Access-Token` header. --- request GET /uri HTTP/1.1 --- more_headers @@ -485,7 +485,7 @@ x-real-ip: 127.0.0.1 -=== TEST 9c: Update plugin to use Authorization header. +=== TEST 9c: Update plugin to only use `Authorization` header. --- config location /t { content_by_lua_block { @@ -567,7 +567,7 @@ passed -=== TEST 9d: Access route with valid token in Authorization header and verify token is still only sent to upstream in Authorization header. +=== TEST 9d: Access route with valid token in `Authorization` header. Upstream should not get the additional `X-Access-Token` header. --- request GET /uri HTTP/1.1 --- more_headers @@ -583,7 +583,7 @@ x-real-ip: 127.0.0.1 -=== TEST 9z: update plugin public key, so tokens can be validated locally +=== TEST 9z: Switch route URI back to `/hello`. --- config location /t { content_by_lua_block { @@ -663,7 +663,7 @@ passed -=== TEST 10: access introspection with wrong token +=== TEST 10: Access route with invalid token. Should return 401. --- config location /t { content_by_lua_block { @@ -694,7 +694,7 @@ jwt signature verification failed -=== TEST 11: Update route with keycloak config for introspection +=== TEST 11: Update route with Keycloak introspection endpoint and public key removed. Should now invoke introspection endpoint to validate tokens. --- config location /t { content_by_lua_block { @@ -770,10 +770,52 @@ passed -=== TEST 12: Access keycloak with correct token +=== TEST 12: Obtain authorization code --- config location /t { content_by_lua_block { + -- Call authorization endpoint. Should return a login form. + local http = require "resty.http" + local httpc = http.new() + local uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" + local res, err = httpc:request_uri(uri, { + method = "POST", + body = "response_type=code&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&redirect_uri=https://iresty.com", + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded" + } + }) + + -- Check response from keycloak and fail quickly if there's no response. + if not res then + ngx.say(err) + return + end + + -- Check if response code was ok. + if res.status == 200 then + local url = res.body:match('.*action="(.*)" method="post">') + ngx.say(url) + else + -- Response from Keycloak not ok. + ngx.say(false) + end + } + } +--- request +GET /t +--- response_body +true +--- no_error_log +[error] + + + +=== TEST 12: Obtain valid token and access route with it. +--- config + location /t { + content_by_lua_block { + -- Obtain valid access token from Keycloak using known username and password. local json_decode = require("toolkit.json").decode local http = require "resty.http" local httpc = http.new() @@ -786,14 +828,19 @@ passed } }) + -- Check response from keycloak and fail quickly if there's no response. if not res then ngx.say(err) return end + -- Check if response code was ok. if res.status == 200 then + -- Get access token from JSON response body. local body = json_decode(res.body) local accessToken = body["access_token"] + + -- Access route using access token. Should work. uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" local res, err = httpc:request_uri(uri, { method = "GET", @@ -803,11 +850,14 @@ passed }) if res.status == 200 then + -- Route accessed successfully. ngx.say(true) else + -- Couldn't access route. ngx.say(false) end else + -- Response from Keycloak not ok. ngx.say(false) end } @@ -821,10 +871,11 @@ true -=== TEST 13: Access keycloak with wrong token +=== TEST 13: Access route with an invalid token. --- config location /t { content_by_lua_block { + -- Access route using a fake access token. local http = require "resty.http" local httpc = http.new() local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" From 593853b8f74679b2b685fd49c35c626e05dcca17 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 21:38:02 +0100 Subject: [PATCH 026/108] Further process response from authorization endpoint and print to response body. --- t/plugin/openid-connect.t | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 04ccac6c5b11..423fee2970cf 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -770,7 +770,7 @@ passed -=== TEST 12: Obtain authorization code +=== TEST 12: Obtain authorization code. --- config location /t { content_by_lua_block { @@ -794,8 +794,13 @@ passed -- Check if response code was ok. if res.status == 200 then - local url = res.body:match('.*action="(.*)" method="post">') + local url, params = res.body:match('.*action="(.*)?(.*)" method="post">') + params = params:gsub("&", "&") ngx.say(url) + ngx.say(params) + for k,v in pairs(res.headers) do + ngx.say(k .. ": " .. v) + end else -- Response from Keycloak not ok. ngx.say(false) From a5ece5dd35ff93a876305b2ad7de22f67b823fc0 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 21:46:00 +0100 Subject: [PATCH 027/108] Small fixes. --- t/plugin/openid-connect.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 423fee2970cf..108e466a1e56 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -794,12 +794,12 @@ passed -- Check if response code was ok. if res.status == 200 then - local url, params = res.body:match('.*action="(.*)?(.*)" method="post">') + local url, params = res.body:match('.*action="(.*)%?(.*)" method="post">') params = params:gsub("&", "&") ngx.say(url) ngx.say(params) for k,v in pairs(res.headers) do - ngx.say(k .. ": " .. v) + ngx.say(k) end else -- Response from Keycloak not ok. From f916b7e38974427bd369277a7a135cd7cc62a00d Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 21:57:46 +0100 Subject: [PATCH 028/108] Print out cookies. --- t/plugin/openid-connect.t | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 108e466a1e56..401c91dda8bc 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -796,10 +796,11 @@ passed if res.status == 200 then local url, params = res.body:match('.*action="(.*)%?(.*)" method="post">') params = params:gsub("&", "&") + cookies = res.headers['Set-Cookie'] ngx.say(url) ngx.say(params) - for k,v in pairs(res.headers) do - ngx.say(k) + for k,v in pairs(cookies) do + ngx.say(k .. ": " .. v) end else -- Response from Keycloak not ok. From 8194baa4d4a0c6faf38f4d5ac7c17e3fcd500e1a Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 22:28:57 +0100 Subject: [PATCH 029/108] Concatenate extracted cookies into request header format. --- t/plugin/openid-connect.t | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 401c91dda8bc..89c83c4e9b2b 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -794,14 +794,39 @@ passed -- Check if response code was ok. if res.status == 200 then - local url, params = res.body:match('.*action="(.*)%?(.*)" method="post">') - params = params:gsub("&", "&") - cookies = res.headers['Set-Cookie'] - ngx.say(url) - ngx.say(params) + -- Extract form target URI and parameters. + local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') + -- Need to substitute escaped ampersand. + local params = params:gsub("&", "&") + -- Get all cookies returned. + local cookies = res.headers['Set-Cookie'] + -- Concatenate cookies into one string as expected in request header. + local cookie_str = "" + local i = 1 for k,v in pairs(cookies) do - ngx.say(k .. ": " .. v) + if i > 1 then + cookie_str = cookie_str .. "; " + end + cookie_str = cookie_str .. k .. "=" .. v + i = i + 1 end + + -- Invoke the URL with parameters and cookies, adding username and password. + --local res, err = httpc:request_uri(uri, { + -- method = "POST", + -- body = params .. "&username=teacher@gmail.com&password=123456", + -- headers = { + -- ["Content-Type"] = "application/x-www-form-urlencoded", + -- ["Cookie"] = cookie_str + -- } + -- }) + + ngx.say(url) + ngx.say(params) + --for k,v in pairs(cookies) do + -- ngx.say(k .. ": " .. v) + --end + ngx.say(cookie_str) else -- Response from Keycloak not ok. ngx.say(false) From 12cf14211e957b433bf59bd699e7b1244d29ab11 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Thu, 17 Dec 2020 22:40:02 +0100 Subject: [PATCH 030/108] Fix cookie header format. --- t/plugin/openid-connect.t | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 89c83c4e9b2b..451d1c17fb0f 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -802,13 +802,12 @@ passed local cookies = res.headers['Set-Cookie'] -- Concatenate cookies into one string as expected in request header. local cookie_str = "" - local i = 1 - for k,v in pairs(cookies) do - if i > 1 then - cookie_str = cookie_str .. "; " + local len = #cookies + if len > 0 then + cookie_str = cookies[1] + for i = 2, len do + cookie_str = cookie_str .. "; " .. cookies[i] end - cookie_str = cookie_str .. k .. "=" .. v - i = i + 1 end -- Invoke the URL with parameters and cookies, adding username and password. From a9f4248b6b64ddfde93be91a79893522e9cfa5e4 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 08:55:17 +0100 Subject: [PATCH 031/108] Extract raw cookie values. --- t/plugin/openid-connect.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 451d1c17fb0f..6f5347b3383e 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -806,7 +806,7 @@ passed if len > 0 then cookie_str = cookies[1] for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i] + cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') end end From 728c7506820ecdb428b4824efbefabfed792086f Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 09:08:42 +0100 Subject: [PATCH 032/108] Log out response of initial call to /hello endpoint redirect to ID provider. --- t/plugin/openid-connect.t | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 6f5347b3383e..8f168b72aeec 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -773,10 +773,27 @@ passed === TEST 12: Obtain authorization code. --- config location /t { - content_by_lua_block { - -- Call authorization endpoint. Should return a login form. local http = require "resty.http" local httpc = http.new() + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" + local res, err = httpc:request_uri(uri, {method = "GET"}) + ngx.status = res.status + nginx.say(res.body) + for k, v in pairs(res.headers) do + nginx.say(k .. ": " .. v) + end + return + --local location = res.headers['Location'] + --if location and string.find(location, 'https://samples.auth0.com/authorize') ~= -1 and + -- string.find(location, 'scope=apisix') ~= -1 and + -- string.find(location, 'client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH') ~= -1 and + -- string.find(location, 'response_type=code') ~= -1 and + -- string.find(location, 'redirect_uri=https://iresty.com') ~= -1 then + -- ngx.say(true) + --end + + + -- Call authorization endpoint. Should return a login form. local uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" local res, err = httpc:request_uri(uri, { method = "POST", @@ -804,7 +821,7 @@ passed local cookie_str = "" local len = #cookies if len > 0 then - cookie_str = cookies[1] + cookie_str = cookies[1]:match('([^;]*); .*') for i = 2, len do cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') end From 3de1affe71d22414c691807722b6933c8fb422f8 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 09:15:01 +0100 Subject: [PATCH 033/108] Fix missing content_by_lua_block. --- t/plugin/openid-connect.t | 1 + 1 file changed, 1 insertion(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 8f168b72aeec..9c9c65b760fb 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -773,6 +773,7 @@ passed === TEST 12: Obtain authorization code. --- config location /t { + content_by_lua_block { local http = require "resty.http" local httpc = http.new() local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" From 866fcbd6384173445a3ae38b2cd2b36cfb73e3ba Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 09:24:44 +0100 Subject: [PATCH 034/108] Fix more errors. --- t/plugin/openid-connect.t | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 9c9c65b760fb..7cc7c752ea42 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -779,9 +779,9 @@ passed local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" local res, err = httpc:request_uri(uri, {method = "GET"}) ngx.status = res.status - nginx.say(res.body) + ngx.say(res.body) for k, v in pairs(res.headers) do - nginx.say(k .. ": " .. v) + ngx.say(k .. ": " .. v) end return --local location = res.headers['Location'] @@ -795,8 +795,8 @@ passed -- Call authorization endpoint. Should return a login form. - local uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" - local res, err = httpc:request_uri(uri, { + uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" + res, err = httpc:request_uri(uri, { method = "POST", body = "response_type=code&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&redirect_uri=https://iresty.com", headers = { From a04ca52c5dee43562e8b1fd49f83750941a48f04 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 09:34:00 +0100 Subject: [PATCH 035/108] More fixes. --- t/plugin/openid-connect.t | 70 ++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 7cc7c752ea42..793bea59f195 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -783,50 +783,49 @@ passed for k, v in pairs(res.headers) do ngx.say(k .. ": " .. v) end - return - --local location = res.headers['Location'] - --if location and string.find(location, 'https://samples.auth0.com/authorize') ~= -1 and + -- local location = res.headers['Location'] + -- if location and string.find(location, 'https://samples.auth0.com/authorize') ~= -1 and -- string.find(location, 'scope=apisix') ~= -1 and -- string.find(location, 'client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH') ~= -1 and -- string.find(location, 'response_type=code') ~= -1 and -- string.find(location, 'redirect_uri=https://iresty.com') ~= -1 then -- ngx.say(true) - --end + -- end -- Call authorization endpoint. Should return a login form. - uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" - res, err = httpc:request_uri(uri, { - method = "POST", - body = "response_type=code&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&redirect_uri=https://iresty.com", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded" - } - }) + -- uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" + -- res, err = httpc:request_uri(uri, { + -- method = "POST", + -- body = "response_type=code&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&redirect_uri=https://iresty.com", + -- headers = { + -- ["Content-Type"] = "application/x-www-form-urlencoded" + -- } + -- }) -- Check response from keycloak and fail quickly if there's no response. - if not res then - ngx.say(err) - return - end + -- if not res then + -- ngx.say(err) + -- return + -- end -- Check if response code was ok. - if res.status == 200 then + -- if res.status == 200 then -- Extract form target URI and parameters. - local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') + -- local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') -- Need to substitute escaped ampersand. - local params = params:gsub("&", "&") + -- local params = params:gsub("&", "&") -- Get all cookies returned. - local cookies = res.headers['Set-Cookie'] + -- local cookies = res.headers['Set-Cookie'] -- Concatenate cookies into one string as expected in request header. - local cookie_str = "" - local len = #cookies - if len > 0 then - cookie_str = cookies[1]:match('([^;]*); .*') - for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - end - end + -- local cookie_str = "" + -- local len = #cookies + -- if len > 0 then + -- cookie_str = cookies[1]:match('([^;]*); .*') + -- for i = 2, len do + -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + -- end + -- end -- Invoke the URL with parameters and cookies, adding username and password. --local res, err = httpc:request_uri(uri, { @@ -838,16 +837,13 @@ passed -- } -- }) - ngx.say(url) - ngx.say(params) - --for k,v in pairs(cookies) do - -- ngx.say(k .. ": " .. v) - --end - ngx.say(cookie_str) - else + -- ngx.say(url) + -- ngx.say(params) + -- ngx.say(cookie_str) + -- else -- Response from Keycloak not ok. - ngx.say(false) - end + -- ngx.say(false) + -- end } } --- request From 86589f2b15d41e3c548c29a2dcabf65f20060b18 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 09:47:47 +0100 Subject: [PATCH 036/108] Reorganise test order. --- t/plugin/openid-connect.t | 241 ++++++++++++++++++++++++-------------- 1 file changed, 156 insertions(+), 85 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 793bea59f195..f0a051d0c4c9 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -191,6 +191,162 @@ true +=== TEST 11: Update route with Keycloak introspection endpoint and public key removed. Should now invoke introspection endpoint to validate tokens. +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "openid-connect": { + "client_id": "course_management", + "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", + "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", + "redirect_uri": "http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated", + "realm": "University", + "ssl_verify": false, + "timeout": 10, + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello|/authenticated" + }]], + [[{ + "node": { + "value": { + "plugins": { + "openid-connect": { + "client_id": "course_management", + "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", + "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", + "redirect_uri": "http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated", + "realm": "University", + "ssl_verify": false, + "timeout": 10, + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello|/authenticated" + }, + "key": "/apisix/routes/1" + }, + "action": "set" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 12: Obtain authorization code. +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" + local res, err = httpc:request_uri(uri, {method = "GET"}) + ngx.status = res.status + ngx.say(res.body) + for k, v in pairs(res.headers) do + ngx.say(k .. ": " .. v) + end + -- local location = res.headers['Location'] + -- if location and string.find(location, 'https://samples.auth0.com/authorize') ~= -1 and + -- string.find(location, 'scope=apisix') ~= -1 and + -- string.find(location, 'client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH') ~= -1 and + -- string.find(location, 'response_type=code') ~= -1 and + -- string.find(location, 'redirect_uri=https://iresty.com') ~= -1 then + -- ngx.say(true) + -- end + + + -- Call authorization endpoint. Should return a login form. + -- uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" + -- res, err = httpc:request_uri(uri, { + -- method = "POST", + -- body = "response_type=code&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&redirect_uri=https://iresty.com", + -- headers = { + -- ["Content-Type"] = "application/x-www-form-urlencoded" + -- } + -- }) + + -- Check response from keycloak and fail quickly if there's no response. + -- if not res then + -- ngx.say(err) + -- return + -- end + + -- Check if response code was ok. + -- if res.status == 200 then + -- Extract form target URI and parameters. + -- local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') + -- Need to substitute escaped ampersand. + -- local params = params:gsub("&", "&") + -- Get all cookies returned. + -- local cookies = res.headers['Set-Cookie'] + -- Concatenate cookies into one string as expected in request header. + -- local cookie_str = "" + -- local len = #cookies + -- if len > 0 then + -- cookie_str = cookies[1]:match('([^;]*); .*') + -- for i = 2, len do + -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + -- end + -- end + + -- Invoke the URL with parameters and cookies, adding username and password. + --local res, err = httpc:request_uri(uri, { + -- method = "POST", + -- body = params .. "&username=teacher@gmail.com&password=123456", + -- headers = { + -- ["Content-Type"] = "application/x-www-form-urlencoded", + -- ["Cookie"] = cookie_str + -- } + -- }) + + -- ngx.say(url) + -- ngx.say(params) + -- ngx.say(cookie_str) + -- else + -- Response from Keycloak not ok. + -- ngx.say(false) + -- end + } + } +--- request +GET /t +--- response_body +true +--- no_error_log +[error] + + + + === TEST 6: Update plugin with `bearer_only=true`. --- config location /t { @@ -770,91 +926,6 @@ passed -=== TEST 12: Obtain authorization code. ---- config - location /t { - content_by_lua_block { - local http = require "resty.http" - local httpc = http.new() - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" - local res, err = httpc:request_uri(uri, {method = "GET"}) - ngx.status = res.status - ngx.say(res.body) - for k, v in pairs(res.headers) do - ngx.say(k .. ": " .. v) - end - -- local location = res.headers['Location'] - -- if location and string.find(location, 'https://samples.auth0.com/authorize') ~= -1 and - -- string.find(location, 'scope=apisix') ~= -1 and - -- string.find(location, 'client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH') ~= -1 and - -- string.find(location, 'response_type=code') ~= -1 and - -- string.find(location, 'redirect_uri=https://iresty.com') ~= -1 then - -- ngx.say(true) - -- end - - - -- Call authorization endpoint. Should return a login form. - -- uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" - -- res, err = httpc:request_uri(uri, { - -- method = "POST", - -- body = "response_type=code&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&redirect_uri=https://iresty.com", - -- headers = { - -- ["Content-Type"] = "application/x-www-form-urlencoded" - -- } - -- }) - - -- Check response from keycloak and fail quickly if there's no response. - -- if not res then - -- ngx.say(err) - -- return - -- end - - -- Check if response code was ok. - -- if res.status == 200 then - -- Extract form target URI and parameters. - -- local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') - -- Need to substitute escaped ampersand. - -- local params = params:gsub("&", "&") - -- Get all cookies returned. - -- local cookies = res.headers['Set-Cookie'] - -- Concatenate cookies into one string as expected in request header. - -- local cookie_str = "" - -- local len = #cookies - -- if len > 0 then - -- cookie_str = cookies[1]:match('([^;]*); .*') - -- for i = 2, len do - -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - -- end - -- end - - -- Invoke the URL with parameters and cookies, adding username and password. - --local res, err = httpc:request_uri(uri, { - -- method = "POST", - -- body = params .. "&username=teacher@gmail.com&password=123456", - -- headers = { - -- ["Content-Type"] = "application/x-www-form-urlencoded", - -- ["Cookie"] = cookie_str - -- } - -- }) - - -- ngx.say(url) - -- ngx.say(params) - -- ngx.say(cookie_str) - -- else - -- Response from Keycloak not ok. - -- ngx.say(false) - -- end - } - } ---- request -GET /t ---- response_body -true ---- no_error_log -[error] - - - === TEST 12: Obtain valid token and access route with it. --- config location /t { From 0a1ee783c27555685fc65ea1f84f55498a426cc9 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 10:02:34 +0100 Subject: [PATCH 037/108] More fixes. --- t/plugin/openid-connect.t | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index f0a051d0c4c9..6ad82d77e519 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -204,7 +204,7 @@ true "client_id": "course_management", "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated", + "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/hello_authenticated", "realm": "University", "ssl_verify": false, "timeout": 10, @@ -216,7 +216,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello|/authenticated" + "uri": "/hello*" }]], [[{ "node": { @@ -226,7 +226,7 @@ true "client_id": "course_management", "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated", + "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/hello_authenticated", "realm": "University", "ssl_verify": false, "timeout": 10, @@ -238,7 +238,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello|/authenticated" + "uri": "/hello*" }, "key": "/apisix/routes/1" }, From 009c6bc655e0ec9f66b91714d00247a929319450 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 10:10:46 +0100 Subject: [PATCH 038/108] More fixes. --- t/plugin/openid-connect.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 6ad82d77e519..ea93a06da816 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -216,7 +216,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello*" + "uri": "/hello" }]], [[{ "node": { @@ -238,7 +238,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello*" + "uri": "/hello" }, "key": "/apisix/routes/1" }, From f872a58318140ba92ffa444266bec484743da159 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 10:21:51 +0100 Subject: [PATCH 039/108] Even more fixes. --- t/plugin/openid-connect.t | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index ea93a06da816..4d682234db3a 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -204,10 +204,12 @@ true "client_id": "course_management", "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/hello_authenticated", - "realm": "University", + "redirect_uri": "http://127.0.0.1:3000/authenticated", "ssl_verify": false, "timeout": 10, + "realm": "University", + "introspection_endpoint_auth_method": "client_secret_post", + "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect" } }, "upstream": { @@ -223,13 +225,15 @@ true "value": { "plugins": { "openid-connect": { - "client_id": "course_management", - "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/hello_authenticated", - "realm": "University", - "ssl_verify": false, - "timeout": 10, + "client_id": "course_management", + "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", + "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", + "redirect_uri": "http://127.0.0.1:3000/authenticated", + "ssl_verify": false, + "timeout": 10, + "realm": "University", + "introspection_endpoint_auth_method": "client_secret_post", + "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect" } }, "upstream": { From 797c44a08d1080c5c2137f6be3fd256fae99239a Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 11:10:10 +0100 Subject: [PATCH 040/108] Extract nonce, state and session cookie. --- t/plugin/openid-connect.t | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 4d682234db3a..accd7abd5662 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -271,13 +271,41 @@ passed content_by_lua_block { local http = require "resty.http" local httpc = http.new() + + -- Invoke /hello endpoint w/o any token. Should receive redirect to Keycloak authorization endpoint. local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" local res, err = httpc:request_uri(uri, {method = "GET"}) - ngx.status = res.status - ngx.say(res.body) - for k, v in pairs(res.headers) do - ngx.say(k .. ": " .. v) + + -- Expect redirect (302). + if res.status != 302 then + ngx.status = res.status + ngx.say(res.body) + for k, v in pairs(res.headers) do + ngx.say(k .. ": " .. v) + end + end + + -- Extract nonce and state. + local nonce, state = res.header['Location']:match('.*nonce=([^&]*).*state=([^&]*).*') + + -- Extract cookies. + local cookies = res.headers['Set-Cookie'] + -- Concatenate cookies into one string as expected in request header. + local cookie_str = "" + local len = #cookies + if len > 0 then + cookie_str = cookies[1]:match('([^;]*); .*') + for i = 2, len do + cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + end end + + + ngx.status = res.status + ngx.say("Nonce: " .. nonce) + ngx.say("State: " .. state) + ngx.say("Cookie: " .. cookie_str) + -- local location = res.headers['Location'] -- if location and string.find(location, 'https://samples.auth0.com/authorize') ~= -1 and -- string.find(location, 'scope=apisix') ~= -1 and From 4a9a5a34a2b93bf5da8607ad2ca5f5eadc45c1fa Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 11:21:31 +0100 Subject: [PATCH 041/108] Fixes. --- t/plugin/openid-connect.t | 40 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index accd7abd5662..96b37bd1b332 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -277,35 +277,35 @@ passed local res, err = httpc:request_uri(uri, {method = "GET"}) -- Expect redirect (302). - if res.status != 302 then + if res.status ~= 302 then ngx.status = res.status ngx.say(res.body) for k, v in pairs(res.headers) do ngx.say(k .. ": " .. v) end - end + else + -- Extract nonce and state. + local nonce, state = res.header['Location']:match('.*nonce=([^&]*)&.*state=([^&]*).*') - -- Extract nonce and state. - local nonce, state = res.header['Location']:match('.*nonce=([^&]*).*state=([^&]*).*') - - -- Extract cookies. - local cookies = res.headers['Set-Cookie'] - -- Concatenate cookies into one string as expected in request header. - local cookie_str = "" - local len = #cookies - if len > 0 then - cookie_str = cookies[1]:match('([^;]*); .*') - for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + -- Extract cookies. + local cookies = res.headers['Set-Cookie'] + + -- Concatenate cookies into one string as expected in request header. + local cookie_str = "" + local len = #cookies + if len > 0 then + cookie_str = cookies[1]:match('([^;]*); .*') + for i = 2, len do + cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + end end - end + ngx.status = res.status + ngx.say("Nonce: " .. nonce) + ngx.say("State: " .. state) + ngx.say("Cookie: " .. cookie_str) + end - ngx.status = res.status - ngx.say("Nonce: " .. nonce) - ngx.say("State: " .. state) - ngx.say("Cookie: " .. cookie_str) - -- local location = res.headers['Location'] -- if location and string.find(location, 'https://samples.auth0.com/authorize') ~= -1 and -- string.find(location, 'scope=apisix') ~= -1 and From 3249f596582d3f6ffeb0c6a17f9bc81c3b8dc28b Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 11:36:31 +0100 Subject: [PATCH 042/108] Fixes. --- t/plugin/openid-connect.t | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 96b37bd1b332..ec30ae8cb8f8 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -283,27 +283,27 @@ passed for k, v in pairs(res.headers) do ngx.say(k .. ": " .. v) end - else + -- else -- Extract nonce and state. - local nonce, state = res.header['Location']:match('.*nonce=([^&]*)&.*state=([^&]*).*') + -- local nonce, state = res.header['Location']:match('.*nonce=([^&]*)&.*state=([^&]*).*') -- Extract cookies. - local cookies = res.headers['Set-Cookie'] + -- local cookies = res.headers['Set-Cookie'] -- Concatenate cookies into one string as expected in request header. - local cookie_str = "" - local len = #cookies - if len > 0 then - cookie_str = cookies[1]:match('([^;]*); .*') - for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - end - end + -- local cookie_str = "" + -- local len = #cookies + -- if len > 0 then + -- cookie_str = cookies[1]:match('([^;]*); .*') + -- for i = 2, len do + -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + -- end + -- end - ngx.status = res.status - ngx.say("Nonce: " .. nonce) - ngx.say("State: " .. state) - ngx.say("Cookie: " .. cookie_str) + -- ngx.status = res.status + -- ngx.say("Nonce: " .. nonce) + -- ngx.say("State: " .. state) + -- ngx.say("Cookie: " .. cookie_str) end -- local location = res.headers['Location'] From 385de27de226662e0222a82b5692c57b70b6d877 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 11:46:57 +0100 Subject: [PATCH 043/108] Add back in some code. --- t/plugin/openid-connect.t | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index ec30ae8cb8f8..a061aa3d4077 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -283,27 +283,27 @@ passed for k, v in pairs(res.headers) do ngx.say(k .. ": " .. v) end - -- else + else -- Extract nonce and state. - -- local nonce, state = res.header['Location']:match('.*nonce=([^&]*)&.*state=([^&]*).*') + local nonce, state = res.headers['Location']:match('.*nonce=([^&]*)&.*state=([^&]*).*') -- Extract cookies. - -- local cookies = res.headers['Set-Cookie'] + local cookies = res.headers['Set-Cookie'] -- Concatenate cookies into one string as expected in request header. - -- local cookie_str = "" - -- local len = #cookies - -- if len > 0 then - -- cookie_str = cookies[1]:match('([^;]*); .*') - -- for i = 2, len do - -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - -- end - -- end + local cookie_str = "" + local len = #cookies + if len > 0 then + cookie_str = cookies[1]:match('([^;]*); .*') + for i = 2, len do + cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + end + end - -- ngx.status = res.status - -- ngx.say("Nonce: " .. nonce) - -- ngx.say("State: " .. state) - -- ngx.say("Cookie: " .. cookie_str) + ngx.status = res.status + ngx.say("Nonce: " .. nonce) + ngx.say("State: " .. state) + ngx.say("Cookie: " .. cookie_str) end -- local location = res.headers['Location'] From 58b8e55d6cdf11435102b100a4f89329b875a53c Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 12:03:39 +0100 Subject: [PATCH 044/108] More debugging. --- t/plugin/openid-connect.t | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index a061aa3d4077..7811a00b873a 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -288,22 +288,22 @@ passed local nonce, state = res.headers['Location']:match('.*nonce=([^&]*)&.*state=([^&]*).*') -- Extract cookies. - local cookies = res.headers['Set-Cookie'] + --local cookies = res.headers['Set-Cookie'] -- Concatenate cookies into one string as expected in request header. - local cookie_str = "" - local len = #cookies - if len > 0 then - cookie_str = cookies[1]:match('([^;]*); .*') - for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - end - end + --local cookie_str = "" + --local len = #cookies + --if len > 0 then + -- cookie_str = cookies[1]:match('([^;]*); .*') + -- for i = 2, len do + -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + -- end + --end ngx.status = res.status ngx.say("Nonce: " .. nonce) ngx.say("State: " .. state) - ngx.say("Cookie: " .. cookie_str) + --ngx.say("Cookie: " .. cookie_str) end -- local location = res.headers['Location'] From 071da2821a5de8f65db8ee4fb677fa16d1d14243 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 12:12:41 +0100 Subject: [PATCH 045/108] Debugging. --- t/plugin/openid-connect.t | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 7811a00b873a..bfd4927249c0 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -285,7 +285,8 @@ passed end else -- Extract nonce and state. - local nonce, state = res.headers['Location']:match('.*nonce=([^&]*)&.*state=([^&]*).*') + ngx.say('Location: ' .. res.headers['Location']) + --local nonce, state = res.headers['Location']:match('.*nonce=([^&]*)&.*state=([^&]*).*') -- Extract cookies. --local cookies = res.headers['Set-Cookie'] @@ -301,8 +302,8 @@ passed --end ngx.status = res.status - ngx.say("Nonce: " .. nonce) - ngx.say("State: " .. state) + --ngx.say("Nonce: " .. nonce) + --ngx.say("State: " .. state) --ngx.say("Cookie: " .. cookie_str) end From f39187a37deafe6437b01aefa13abc163bfee1d3 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 12:19:08 +0100 Subject: [PATCH 046/108] Add back in nonce and state extraction. --- t/plugin/openid-connect.t | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index bfd4927249c0..ca200580d5e9 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -286,7 +286,8 @@ passed else -- Extract nonce and state. ngx.say('Location: ' .. res.headers['Location']) - --local nonce, state = res.headers['Location']:match('.*nonce=([^&]*)&.*state=([^&]*).*') + local nonce = res.headers['Location']:match('.*nonce=([^&]+).*') + local state = res.headers['Location']:match('.*state=([^&]+).*') -- Extract cookies. --local cookies = res.headers['Set-Cookie'] @@ -302,8 +303,8 @@ passed --end ngx.status = res.status - --ngx.say("Nonce: " .. nonce) - --ngx.say("State: " .. state) + ngx.say("Nonce: " .. nonce) + ngx.say("State: " .. state) --ngx.say("Cookie: " .. cookie_str) end From 8f669dc0f2e58c56a0b3e0ef68626e348928995c Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 12:52:19 +0100 Subject: [PATCH 047/108] Add back in call to authorization endpoint. --- t/plugin/openid-connect.t | 60 ++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index ca200580d5e9..9bccbb7a7f14 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -290,49 +290,43 @@ passed local state = res.headers['Location']:match('.*state=([^&]+).*') -- Extract cookies. - --local cookies = res.headers['Set-Cookie'] + local cookies = res.headers['Set-Cookie'] -- Concatenate cookies into one string as expected in request header. - --local cookie_str = "" - --local len = #cookies - --if len > 0 then - -- cookie_str = cookies[1]:match('([^;]*); .*') - -- for i = 2, len do - -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - -- end + local cookie_str = "" + local len = #cookies + if len > 0 then + cookie_str = cookies[1]:match('([^;]*); .*') + for i = 2, len do + cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + end --end ngx.status = res.status ngx.say("Nonce: " .. nonce) ngx.say("State: " .. state) - --ngx.say("Cookie: " .. cookie_str) - end - - -- local location = res.headers['Location'] - -- if location and string.find(location, 'https://samples.auth0.com/authorize') ~= -1 and - -- string.find(location, 'scope=apisix') ~= -1 and - -- string.find(location, 'client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH') ~= -1 and - -- string.find(location, 'response_type=code') ~= -1 and - -- string.find(location, 'redirect_uri=https://iresty.com') ~= -1 then - -- ngx.say(true) - -- end + ngx.say("Cookie: " .. cookie_str) + -- Call authorization endpoint. Should return a login form. + uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" + res, err = httpc:request_uri(uri, { + method = "POST", + body = "redirect_uri=http://127.0.0.1:3000/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded" + } + }) + + -- Check response from keycloak and fail quickly if there's no response. + if not res then + ngx.say(err) + return + end - -- Call authorization endpoint. Should return a login form. - -- uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" - -- res, err = httpc:request_uri(uri, { - -- method = "POST", - -- body = "response_type=code&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&redirect_uri=https://iresty.com", - -- headers = { - -- ["Content-Type"] = "application/x-www-form-urlencoded" - -- } - -- }) + ngx.status = res.status + ngx.say(res.body) - -- Check response from keycloak and fail quickly if there's no response. - -- if not res then - -- ngx.say(err) - -- return - -- end + end -- Check if response code was ok. -- if res.status == 200 then From 70e24076f054478f946b4c02411b5aafeb3255da Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 12:59:49 +0100 Subject: [PATCH 048/108] Fix syntax error. --- t/plugin/openid-connect.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 9bccbb7a7f14..197b3d1126ff 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -300,7 +300,7 @@ passed for i = 2, len do cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') end - --end + end ngx.status = res.status ngx.say("Nonce: " .. nonce) From cd3cb32f951021fe36f2f91346caecf1a5fb6c40 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 13:09:23 +0100 Subject: [PATCH 049/108] Debugging. --- t/plugin/openid-connect.t | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 197b3d1126ff..48446abb587a 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -288,6 +288,8 @@ passed ngx.say('Location: ' .. res.headers['Location']) local nonce = res.headers['Location']:match('.*nonce=([^&]+).*') local state = res.headers['Location']:match('.*state=([^&]+).*') + ngx.say("Nonce: " .. nonce) + ngx.say("State: " .. state) -- Extract cookies. local cookies = res.headers['Set-Cookie'] @@ -302,30 +304,27 @@ passed end end - ngx.status = res.status - ngx.say("Nonce: " .. nonce) - ngx.say("State: " .. state) ngx.say("Cookie: " .. cookie_str) + ngx.status = res.status -- Call authorization endpoint. Should return a login form. - uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" - res, err = httpc:request_uri(uri, { - method = "POST", - body = "redirect_uri=http://127.0.0.1:3000/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded" - } - }) + --uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" + --res, err = httpc:request_uri(uri, { + -- method = "POST", + -- body = "redirect_uri=http://127.0.0.1:3000/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", + -- headers = { + -- ["Content-Type"] = "application/x-www-form-urlencoded" + -- } + -- }) -- Check response from keycloak and fail quickly if there's no response. - if not res then - ngx.say(err) - return - end - - ngx.status = res.status - ngx.say(res.body) + --if not res then + -- ngx.say(err) + -- return + --end + --ngx.status = res.status + --ngx.say(res.body) end -- Check if response code was ok. From ad1a5279796de02df9ffe6db8e1f0420c9a09258 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 13:16:52 +0100 Subject: [PATCH 050/108] Debugging. --- t/plugin/openid-connect.t | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 48446abb587a..4e53440a0cad 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -285,7 +285,10 @@ passed end else -- Extract nonce and state. - ngx.say('Location: ' .. res.headers['Location']) + ngx.say(res.body) + for k, v in pairs(res.headers) do + ngx.say(k .. ": " .. v) + end local nonce = res.headers['Location']:match('.*nonce=([^&]+).*') local state = res.headers['Location']:match('.*state=([^&]+).*') ngx.say("Nonce: " .. nonce) @@ -295,16 +298,16 @@ passed local cookies = res.headers['Set-Cookie'] -- Concatenate cookies into one string as expected in request header. - local cookie_str = "" - local len = #cookies - if len > 0 then - cookie_str = cookies[1]:match('([^;]*); .*') - for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - end - end + --local cookie_str = "" + --local len = #cookies + --if len > 0 then + -- cookie_str = cookies[1]:match('([^;]*); .*') + -- for i = 2, len do + -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + -- end + --end - ngx.say("Cookie: " .. cookie_str) + --ngx.say("Cookie: " .. cookie_str) ngx.status = res.status -- Call authorization endpoint. Should return a login form. From 3100ff7bde9b404886303ddfb7ae854761f371d8 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 13:22:49 +0100 Subject: [PATCH 051/108] Debugging. --- t/plugin/openid-connect.t | 67 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 4e53440a0cad..e2431c2132ec 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -276,19 +276,16 @@ passed local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" local res, err = httpc:request_uri(uri, {method = "GET"}) + ngx.say(res.body) + for k, v in pairs(res.headers) do + ngx.say(k .. ": " .. v) + end + -- Expect redirect (302). if res.status ~= 302 then ngx.status = res.status - ngx.say(res.body) - for k, v in pairs(res.headers) do - ngx.say(k .. ": " .. v) - end else -- Extract nonce and state. - ngx.say(res.body) - for k, v in pairs(res.headers) do - ngx.say(k .. ": " .. v) - end local nonce = res.headers['Location']:match('.*nonce=([^&]+).*') local state = res.headers['Location']:match('.*state=([^&]+).*') ngx.say("Nonce: " .. nonce) @@ -298,36 +295,42 @@ passed local cookies = res.headers['Set-Cookie'] -- Concatenate cookies into one string as expected in request header. - --local cookie_str = "" - --local len = #cookies - --if len > 0 then - -- cookie_str = cookies[1]:match('([^;]*); .*') - -- for i = 2, len do - -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - -- end - --end - - --ngx.say("Cookie: " .. cookie_str) + local cookie_str = "" + + if type(cookies) == 'string' then + cookie_str = cookies:match('([^;]*); .*') + else + -- Must be a table. + local len = #cookies + if len > 0 then + cookie_str = cookies[1]:match('([^;]*); .*') + for i = 2, len do + cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + end + end + end + + ngx.say("Cookie: " .. cookie_str) ngx.status = res.status -- Call authorization endpoint. Should return a login form. - --uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" - --res, err = httpc:request_uri(uri, { - -- method = "POST", - -- body = "redirect_uri=http://127.0.0.1:3000/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", - -- headers = { - -- ["Content-Type"] = "application/x-www-form-urlencoded" - -- } - -- }) + uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" + res, err = httpc:request_uri(uri, { + method = "POST", + body = "redirect_uri=http://127.0.0.1:3000/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded" + } + }) -- Check response from keycloak and fail quickly if there's no response. - --if not res then - -- ngx.say(err) - -- return - --end + if not res then + ngx.say(err) + return + end - --ngx.status = res.status - --ngx.say(res.body) + ngx.status = res.status + ngx.say(res.body) end -- Check if response code was ok. From 3f3e447bec35266a2ab627bb01ab57c282d1460e Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 13:55:41 +0100 Subject: [PATCH 052/108] Add back assembling of form submission request. --- t/plugin/openid-connect.t | 77 +++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index e2431c2132ec..fd2f095f6a86 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -331,43 +331,48 @@ passed ngx.status = res.status ngx.say(res.body) - end - -- Check if response code was ok. - -- if res.status == 200 then - -- Extract form target URI and parameters. - -- local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') - -- Need to substitute escaped ampersand. - -- local params = params:gsub("&", "&") - -- Get all cookies returned. - -- local cookies = res.headers['Set-Cookie'] - -- Concatenate cookies into one string as expected in request header. - -- local cookie_str = "" - -- local len = #cookies - -- if len > 0 then - -- cookie_str = cookies[1]:match('([^;]*); .*') - -- for i = 2, len do - -- cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - -- end - -- end - - -- Invoke the URL with parameters and cookies, adding username and password. - --local res, err = httpc:request_uri(uri, { - -- method = "POST", - -- body = params .. "&username=teacher@gmail.com&password=123456", - -- headers = { - -- ["Content-Type"] = "application/x-www-form-urlencoded", - -- ["Cookie"] = cookie_str - -- } - -- }) - - -- ngx.say(url) - -- ngx.say(params) - -- ngx.say(cookie_str) - -- else - -- Response from Keycloak not ok. - -- ngx.say(false) - -- end + -- Check if response code was ok. + if res.status == 200 then + -- Extract form target URI and parameters. + local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') + -- Need to substitute escaped ampersand. + params = params:gsub("&", "&") + -- Get all cookies returned. + local auth_cookies = res.headers['Set-Cookie'] + -- Concatenate cookies into one string as expected in request header. + local auth_cookie_str = "" + if type(auth_cookies) == 'string' then + auth_cookie_str = auth_cookies:match('([^;]*); .*') + else + -- Must be a table. + local len = #auth_cookies + if len > 0 then + auth_cookie_str = auth_cookies[1]:match('([^;]*); .*') + for i = 2, len do + auth_cookie_str = auth_cookie_str .. "; " .. auth_cookies[i]:match('([^;]*); .*') + end + end + end + + -- Invoke the URL with parameters and cookies, adding username and password. + --httpc:request_uri(uri, { + -- method = "POST", + -- body = params .. "&username=teacher@gmail.com&password=123456", + -- headers = { + -- ["Content-Type"] = "application/x-www-form-urlencoded", + -- ["Cookie"] = auth_cookie_str + -- } + -- }) + + ngx.say(uri) + ngx.say(params) + ngx.say(auth_cookie_str) + else + -- Response from Keycloak not ok. + ngx.say(false) + end + end } } --- request From 3d5596f42e9cca2ed56c0ecffd3f599baba718db Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 14:04:16 +0100 Subject: [PATCH 053/108] Send actual request to simulate form submission. --- t/plugin/openid-connect.t | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index fd2f095f6a86..41dfa747f67c 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -355,19 +355,24 @@ passed end end - -- Invoke the URL with parameters and cookies, adding username and password. - --httpc:request_uri(uri, { - -- method = "POST", - -- body = params .. "&username=teacher@gmail.com&password=123456", - -- headers = { - -- ["Content-Type"] = "application/x-www-form-urlencoded", - -- ["Cookie"] = auth_cookie_str - -- } - -- }) - ngx.say(uri) ngx.say(params) ngx.say(auth_cookie_str) + + -- Invoke the URL with parameters and cookies, adding username and password. + res, err = httpc:request_uri(uri, { + method = "POST", + body = params .. "&username=teacher@gmail.com&password=123456", + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded", + ["Cookie"] = auth_cookie_str + } + }) + + ngx.say(res.body) + for k, v in pairs(res.headers) do + ngx.say(k .. ": " .. v) + end else -- Response from Keycloak not ok. ngx.say(false) From ec555844f22b70128a1ea4494f8c035dc5339d40 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 14:21:31 +0100 Subject: [PATCH 054/108] Pass some parameters in URL, not request body. --- t/plugin/openid-connect.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 41dfa747f67c..eeb914d092e4 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -360,9 +360,9 @@ passed ngx.say(auth_cookie_str) -- Invoke the URL with parameters and cookies, adding username and password. - res, err = httpc:request_uri(uri, { + res, err = httpc:request_uri(uri .. "?" .. params, { method = "POST", - body = params .. "&username=teacher@gmail.com&password=123456", + body = "username=teacher@gmail.com&password=123456", headers = { ["Content-Type"] = "application/x-www-form-urlencoded", ["Cookie"] = auth_cookie_str From 7fac7490641fd7b0f993fea6807a695d2901f1ec Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 14:29:41 +0100 Subject: [PATCH 055/108] Set status first. --- t/plugin/openid-connect.t | 1 + 1 file changed, 1 insertion(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index eeb914d092e4..b2dfa2771cfb 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -369,6 +369,7 @@ passed } }) + ngx.status = res.status ngx.say(res.body) for k, v in pairs(res.headers) do ngx.say(k .. ": " .. v) From fe3493f379eb1fc7800430b258ae152e5bd7601c Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 14:38:41 +0100 Subject: [PATCH 056/108] Minor fix. --- t/plugin/openid-connect.t | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index b2dfa2771cfb..cf2c940dfc0c 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -311,7 +311,6 @@ passed end ngx.say("Cookie: " .. cookie_str) - ngx.status = res.status -- Call authorization endpoint. Should return a login form. uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" @@ -329,7 +328,6 @@ passed return end - ngx.status = res.status ngx.say(res.body) -- Check if response code was ok. @@ -370,7 +368,7 @@ passed }) ngx.status = res.status - ngx.say(res.body) + --ngx.say(res.body) for k, v in pairs(res.headers) do ngx.say(k .. ": " .. v) end From 930fd2d18366acf3bd47a7acb5479011d9911bd3 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 14:44:28 +0100 Subject: [PATCH 057/108] Only print out Location header. --- t/plugin/openid-connect.t | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index cf2c940dfc0c..bd185ee90848 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -368,10 +368,11 @@ passed }) ngx.status = res.status - --ngx.say(res.body) + ngx.say(res.body) for k, v in pairs(res.headers) do - ngx.say(k .. ": " .. v) + ngx.say(k) end + ngx.say(res.headers['Location']) else -- Response from Keycloak not ok. ngx.say(false) From 35724992ace2da234b3b5adf3fa0fbbe7375cd74 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 14:54:11 +0100 Subject: [PATCH 058/108] Add /authenticated endpoint to route. --- t/plugin/openid-connect.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index bd185ee90848..0d1777f1781e 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -218,7 +218,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello" + "uri": "/hello|/authenticated" }]], [[{ "node": { @@ -242,7 +242,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello" + "uri": "/hello|/authenticated" }, "key": "/apisix/routes/1" }, From 636b518901996777d52cacbfbcdbb6fbacba2f14 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 16:06:37 +0100 Subject: [PATCH 059/108] Revert previous change. --- t/plugin/openid-connect.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 0d1777f1781e..bd185ee90848 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -218,7 +218,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello|/authenticated" + "uri": "/hello" }]], [[{ "node": { @@ -242,7 +242,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello|/authenticated" + "uri": "/hello" }, "key": "/apisix/routes/1" }, From 3a93ce299a9e3e71584308476cf0d798c6a4578f Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 16:47:59 +0100 Subject: [PATCH 060/108] Use catch-all route. --- t/plugin/openid-connect.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index bd185ee90848..41e4f3a95653 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -218,7 +218,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello" + "uri": "/*" }]], [[{ "node": { @@ -242,7 +242,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello" + "uri": "/*" }, "key": "/apisix/routes/1" }, From c3481431c05f393cd2c94d2871ba905f9216511a Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 16:52:42 +0100 Subject: [PATCH 061/108] Fix redirect URL. --- t/plugin/openid-connect.t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 41e4f3a95653..c6b651cfc19e 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -204,7 +204,7 @@ true "client_id": "course_management", "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://127.0.0.1:3000/authenticated", + "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", "ssl_verify": false, "timeout": 10, "realm": "University", @@ -228,7 +228,7 @@ true "client_id": "course_management", "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://127.0.0.1:3000/authenticated", + "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", "ssl_verify": false, "timeout": 10, "realm": "University", @@ -316,7 +316,7 @@ passed uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" res, err = httpc:request_uri(uri, { method = "POST", - body = "redirect_uri=http://127.0.0.1:3000/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", + body = "redirect_uri=http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", headers = { ["Content-Type"] = "application/x-www-form-urlencoded" } From bf948e8789c51434060f3637ed6255b7c5ee3d3c Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 17:01:35 +0100 Subject: [PATCH 062/108] Actually call redirect_uri. --- t/plugin/openid-connect.t | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index c6b651cfc19e..8d7632b443c7 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -373,6 +373,22 @@ passed ngx.say(k) end ngx.say(res.headers['Location']) + + local redirect_uri = res.headers['Location'] + + -- Invoke the redirect URI with the obtained authorization code. + res, err = httpc:request_uri(redirect_uri, { + method = "GET", + headers = { + ["Cookie"] = cookie_str + } + }) + + ngx.status = res.status + ngx.say(res.body) + for k, v in pairs(res.headers) do + ngx.say(k) + end else -- Response from Keycloak not ok. ngx.say(false) From 5fe5fc2b4bb59c71c179b6b6c59a6a6a84af38fd Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 18:09:29 +0100 Subject: [PATCH 063/108] Add other cookies. --- t/plugin/openid-connect.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 8d7632b443c7..011085458f08 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -380,7 +380,7 @@ passed res, err = httpc:request_uri(redirect_uri, { method = "GET", headers = { - ["Cookie"] = cookie_str + ["Cookie"] = cookie_str .. "; " .. auth_cookie_str } }) From 926729b98d545512117ec96ebb19a2df5ebe196f Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 21:10:26 +0100 Subject: [PATCH 064/108] Use redirect URI from Location header directly. --- t/plugin/openid-connect.t | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 011085458f08..b3f6ac0276d3 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -313,14 +313,17 @@ passed ngx.say("Cookie: " .. cookie_str) -- Call authorization endpoint. Should return a login form. - uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" - res, err = httpc:request_uri(uri, { - method = "POST", - body = "redirect_uri=http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded" - } - }) + res, err = httpc:request_uri(res.headers['Location'], {method = "GET"}) + + --uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" + --res, err = httpc:request_uri(uri, { + -- method = "POST", + -- -- body = "redirect_uri=http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", + -- body = "redirect_uri=http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated&scope=openid&client_id=course_management&response_type=code&state=" .. state .. "", + -- headers = { + -- ["Content-Type"] = "application/x-www-form-urlencoded" + -- } + -- }) -- Check response from keycloak and fail quickly if there's no response. if not res then From 730ef074b18aadc39ec14bdbe4369e482386eda9 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 21:31:34 +0100 Subject: [PATCH 065/108] Final redirect. --- t/plugin/openid-connect.t | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index b3f6ac0276d3..78de4e1d5fb0 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -383,7 +383,7 @@ passed res, err = httpc:request_uri(redirect_uri, { method = "GET", headers = { - ["Cookie"] = cookie_str .. "; " .. auth_cookie_str + ["Cookie"] = cookie_str } }) @@ -392,6 +392,21 @@ passed for k, v in pairs(res.headers) do ngx.say(k) end + ngx.say(res.headers['Location']) + + -- Get the final URI out of the Location response header. + redirect_uri = res.headers['Location'] + res, err = httpc:request_uri(redirect_uri, { + method = "GET", + headers = { + ["Cookie"] = cookie_str + } + }) + ngx.status = res.status + ngx.say(res.body) + for k, v in pairs(res.headers) do + ngx.say(k) + end else -- Response from Keycloak not ok. ngx.say(false) From ce4d79446fc3ae62c61a7e5ace3e855c80fd13e3 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 21:56:44 +0100 Subject: [PATCH 066/108] Debugging. --- t/plugin/openid-connect.t | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 78de4e1d5fb0..cb624723fc7b 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -395,18 +395,18 @@ passed ngx.say(res.headers['Location']) -- Get the final URI out of the Location response header. - redirect_uri = res.headers['Location'] - res, err = httpc:request_uri(redirect_uri, { - method = "GET", - headers = { - ["Cookie"] = cookie_str - } - }) - ngx.status = res.status - ngx.say(res.body) - for k, v in pairs(res.headers) do - ngx.say(k) - end + --redirect_uri = res.headers['Location'] + --res, err = httpc:request_uri(redirect_uri, { + -- method = "GET", + -- headers = { + -- ["Cookie"] = cookie_str + -- } + -- }) + --ngx.status = res.status + --ngx.say(res.body) + --for k, v in pairs(res.headers) do + -- ngx.say(k) + --end else -- Response from Keycloak not ok. ngx.say(false) From c17b3594337fe186f6279260463f23e4be95a1e3 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Fri, 18 Dec 2020 22:05:57 +0100 Subject: [PATCH 067/108] Fix final URI. Update cookie. --- t/plugin/openid-connect.t | 40 +++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index cb624723fc7b..bdf95379b7c8 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -394,19 +394,35 @@ passed end ngx.say(res.headers['Location']) + cookies = res.headers['Set-Cookie'] + + -- Concatenate cookies into one string as expected in request header. + if type(cookies) == 'string' then + cookie_str = cookies:match('([^;]*); .*') + else + -- Must be a table. + local len = #cookies + if len > 0 then + cookie_str = cookies[1]:match('([^;]*); .*') + for i = 2, len do + cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + end + end + end + -- Get the final URI out of the Location response header. - --redirect_uri = res.headers['Location'] - --res, err = httpc:request_uri(redirect_uri, { - -- method = "GET", - -- headers = { - -- ["Cookie"] = cookie_str - -- } - -- }) - --ngx.status = res.status - --ngx.say(res.body) - --for k, v in pairs(res.headers) do - -- ngx.say(k) - --end + redirect_uri = "http://127.0.0.1:" .. ngx.var.server_port .. res.headers['Location'] + res, err = httpc:request_uri(redirect_uri, { + method = "GET", + headers = { + ["Cookie"] = cookie_str + } + }) + ngx.status = res.status + ngx.say(res.body) + for k, v in pairs(res.headers) do + ngx.say(k) + end else -- Response from Keycloak not ok. ngx.say(false) From 431a0fed99969592d53c1013178202bc573e087f Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Sat, 19 Dec 2020 09:30:27 +0100 Subject: [PATCH 068/108] Use /uri endpoint. --- t/plugin/openid-connect.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index bdf95379b7c8..e87443821ec9 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -272,8 +272,8 @@ passed local http = require "resty.http" local httpc = http.new() - -- Invoke /hello endpoint w/o any token. Should receive redirect to Keycloak authorization endpoint. - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" + -- Invoke /uri endpoint w/o any token. Should receive redirect to Keycloak authorization endpoint. + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" local res, err = httpc:request_uri(uri, {method = "GET"}) ngx.say(res.body) From 1e77d123bd09488b958b550c7a86614be06a9aef Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 21 Dec 2020 11:28:53 +0100 Subject: [PATCH 069/108] Start clean up. --- t/plugin/openid-connect.t | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index e87443821ec9..49220c4f99f2 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -91,7 +91,7 @@ done -=== TEST 4: Add plugin to route for path `/hello`. +=== TEST 4: Set up new route with plugin matching URI `/hello`. --- config location /t { content_by_lua_block { @@ -161,7 +161,8 @@ passed -=== TEST 5: Access route w/o bearer token. Should redirect to authentication endpoint of ID provider. +=== TEST 5: Access route w/o bearer token. +Should redirect to authentication endpoint of ID provider. --- config location /t { content_by_lua_block { @@ -191,7 +192,11 @@ true -=== TEST 11: Update route with Keycloak introspection endpoint and public key removed. Should now invoke introspection endpoint to validate tokens. +=== TEST 11: Modify route to match catch-all URI `/*` and point plugin to local Keycloak instance. +Notes: +- Use proper discovery endpoint of the local Keycloak instance. +- Realm, client ID, and secret are specific to the Keycloak Docker image used. +- Use a redirect URL that is matched by route as well. Keycloak will redirect to this with the authorization code once user has been authenticated. --- config location /t { content_by_lua_block { @@ -201,13 +206,13 @@ true [[{ "plugins": { "openid-connect": { + "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", + "realm": "University", "client_id": "course_management", "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", "ssl_verify": false, "timeout": 10, - "realm": "University", "introspection_endpoint_auth_method": "client_secret_post", "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect" } @@ -265,7 +270,9 @@ passed -=== TEST 12: Obtain authorization code. +=== TEST 12: Access route w/o bearer token. +When redirected to authentication endpoint of ID provider, go through the full +OIDC Relying Party authentication process, using the authorization code flow. --- config location /t { content_by_lua_block { @@ -276,11 +283,6 @@ passed local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" local res, err = httpc:request_uri(uri, {method = "GET"}) - ngx.say(res.body) - for k, v in pairs(res.headers) do - ngx.say(k .. ": " .. v) - end - -- Expect redirect (302). if res.status ~= 302 then ngx.status = res.status From 78eddffba91bd13a2e01be2d0d2143835a95e24a Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 21 Dec 2020 11:44:35 +0100 Subject: [PATCH 070/108] Continue clean up. --- t/plugin/openid-connect.t | 51 ++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 49220c4f99f2..52a042973e46 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -279,24 +279,31 @@ OIDC Relying Party authentication process, using the authorization code flow. local http = require "resty.http" local httpc = http.new() - -- Invoke /uri endpoint w/o any token. Should receive redirect to Keycloak authorization endpoint. + -- Invoke /uri endpoint w/o bearer token. Should receive redirect to Keycloak authorization endpoint. local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" local res, err = httpc:request_uri(uri, {method = "GET"}) - -- Expect redirect (302). - if res.status ~= 302 then - ngx.status = res.status + if not err then + -- No response, must be an error. + ngx.say(err) + return + elseif res.status ~= 302 then + -- Not a redirect which we expect. + -- Use 500 to indicate error. + ngx.status = 500 + ngx.say("Initial request was not redirected to ID provider authorization endpoint.") + return else - -- Extract nonce and state. + -- Redirect to ID provider's authorization endpoint. + + -- Extract nonce and state from response header. local nonce = res.headers['Location']:match('.*nonce=([^&]+).*') local state = res.headers['Location']:match('.*state=([^&]+).*') - ngx.say("Nonce: " .. nonce) - ngx.say("State: " .. state) - -- Extract cookies. + -- Extract cookies. Important since OIDC module tracks state with a session cookie. local cookies = res.headers['Set-Cookie'] - -- Concatenate cookies into one string as expected in request header. + -- Concatenate cookies into one string as expected when sent in request header. local cookie_str = "" if type(cookies) == 'string' then @@ -312,29 +319,23 @@ OIDC Relying Party authentication process, using the authorization code flow. end end - ngx.say("Cookie: " .. cookie_str) - - -- Call authorization endpoint. Should return a login form. + -- Call authorization endpoint we were redirected to. + -- Note: This typically returns a login form which is the case here for Keycloak as well. + -- However, how we process the form to perform the login is specific to Keycloak and + -- possibly even the version used. res, err = httpc:request_uri(res.headers['Location'], {method = "GET"}) - --uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/auth" - --res, err = httpc:request_uri(uri, { - -- method = "POST", - -- -- body = "redirect_uri=http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated&nonce=" .. nonce .. "&client_id=course_management&response_type=code&state=" .. state .. "", - -- body = "redirect_uri=http://127.0.0.1:" .. ngx.var.server_port .. "/authenticated&scope=openid&client_id=course_management&response_type=code&state=" .. state .. "", - -- headers = { - -- ["Content-Type"] = "application/x-www-form-urlencoded" - -- } - -- }) - - -- Check response from keycloak and fail quickly if there's no response. if not res then + -- No response, must be an error. ngx.say(err) return + elseif res.status ~= 200 then + -- Unexpected response. + ngx.status = res.status + ngx.say(res.body) + return end - ngx.say(res.body) - -- Check if response code was ok. if res.status == 200 then -- Extract form target URI and parameters. From b98d609083478b613a497eeb720fac092fc6c069 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 21 Dec 2020 11:52:18 +0100 Subject: [PATCH 071/108] Continue clean up. --- t/plugin/openid-connect.t | 43 ++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 52a042973e46..2cecedeabf4c 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -283,8 +283,9 @@ OIDC Relying Party authentication process, using the authorization code flow. local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" local res, err = httpc:request_uri(uri, {method = "GET"}) - if not err then + if not res then -- No response, must be an error. + ngx.status = 500 ngx.say(err) return elseif res.status ~= 302 then @@ -327,6 +328,7 @@ OIDC Relying Party authentication process, using the authorization code flow. if not res then -- No response, must be an error. + ngx.status = 500 ngx.say(err) return elseif res.status ~= 200 then @@ -338,14 +340,18 @@ OIDC Relying Party authentication process, using the authorization code flow. -- Check if response code was ok. if res.status == 200 then - -- Extract form target URI and parameters. + -- From the returned form, extract the submit URI and parameters. local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') - -- Need to substitute escaped ampersand. + + -- Substitute escaped ampersand in parameters. params = params:gsub("&", "&") - -- Get all cookies returned. + + -- Get all cookies returned. Probably not so important since not part of OIDC specification. local auth_cookies = res.headers['Set-Cookie'] - -- Concatenate cookies into one string as expected in request header. + + -- Concatenate cookies into one string as expected when sent in request header. local auth_cookie_str = "" + if type(auth_cookies) == 'string' then auth_cookie_str = auth_cookies:match('([^;]*); .*') else @@ -359,11 +365,8 @@ OIDC Relying Party authentication process, using the authorization code flow. end end - ngx.say(uri) - ngx.say(params) - ngx.say(auth_cookie_str) - - -- Invoke the URL with parameters and cookies, adding username and password. + -- Invoke the submit URI with parameters and cookies, adding username and password in the body. + -- Note: Username and password are specific to the Keycloak Docker image used. res, err = httpc:request_uri(uri .. "?" .. params, { method = "POST", body = "username=teacher@gmail.com&password=123456", @@ -373,16 +376,24 @@ OIDC Relying Party authentication process, using the authorization code flow. } }) - ngx.status = res.status - ngx.say(res.body) - for k, v in pairs(res.headers) do - ngx.say(k) + if not res then + -- No response, must be an error. + ngx.status = 500 + ngx.say(err) + return + elseif res.status ~= 302 then + -- Not a redirect which we expect. + -- Use 500 to indicate error. + ngx.status = 500 + ngx.say("Login form submission did not return redirect to redirect URI.") + return end - ngx.say(res.headers['Location']) + -- Extract the redirect URI from the response header. + -- TODO: Consider validating this against the plugin configuration. local redirect_uri = res.headers['Location'] - -- Invoke the redirect URI with the obtained authorization code. + -- Invoke the redirect URI (which contains the authorization code as an URL parameter). res, err = httpc:request_uri(redirect_uri, { method = "GET", headers = { From 76c613ec9471ddd0030e796fc717e10b1033e866 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 21 Dec 2020 12:25:52 +0100 Subject: [PATCH 072/108] Continue clean up. --- t/plugin/openid-connect.t | 40 ++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 2cecedeabf4c..0ac46456b64a 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -401,16 +401,25 @@ OIDC Relying Party authentication process, using the authorization code flow. } }) - ngx.status = res.status - ngx.say(res.body) - for k, v in pairs(res.headers) do - ngx.say(k) + if not res then + -- No response, must be an error. + ngx.status = 500 + ngx.say(err) + return + elseif res.status ~= 302 then + -- Not a redirect which we expect. + -- Use 500 to indicate error. + ngx.status = 500 + ngx.say("Invoking redirect URI with authorization code did not return redirect to original URI.") + return end - ngx.say(res.headers['Location']) + -- Get all cookies returned. This should update the session cookie maintained by the OIDC module with the new state. + -- E.g. the session cookie should now contain the access token, ID token and user info. + -- The cookie itself should however be treated as opaque. cookies = res.headers['Set-Cookie'] - -- Concatenate cookies into one string as expected in request header. + -- Concatenate cookies into one string as expected when sent in request header. if type(cookies) == 'string' then cookie_str = cookies:match('([^;]*); .*') else @@ -424,14 +433,31 @@ OIDC Relying Party authentication process, using the authorization code flow. end end - -- Get the final URI out of the Location response header. + -- Get the final URI out of the Location response header. This should be the original URI that was requested. + -- TODO: Consider checking the URI against the original request URI. redirect_uri = "http://127.0.0.1:" .. ngx.var.server_port .. res.headers['Location'] + + -- Make the final call back to the original URI. res, err = httpc:request_uri(redirect_uri, { method = "GET", headers = { ["Cookie"] = cookie_str } }) + + if not res then + -- No response, must be an error. + ngx.status = 500 + ngx.say(err) + return + elseif res.status ~= 200 then + -- Not a valid response. + -- Use 500 to indicate error. + ngx.status = 500 + ngx.say("Invoking the original URI didn't return the expected result.") + return + end + ngx.status = res.status ngx.say(res.body) for k, v in pairs(res.headers) do From f0ce4058b56165ee08b10fb40ca40757a8f79693 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 21 Dec 2020 12:43:30 +0100 Subject: [PATCH 073/108] Continue clean up. --- t/plugin/openid-connect.t | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 0ac46456b64a..fd27ba7939b4 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -460,8 +460,6 @@ OIDC Relying Party authentication process, using the authorization code flow. ngx.status = res.status ngx.say(res.body) - for k, v in pairs(res.headers) do - ngx.say(k) end else -- Response from Keycloak not ok. @@ -472,14 +470,20 @@ OIDC Relying Party authentication process, using the authorization code flow. } --- request GET /t ---- response_body -true +--- response_body_like +uri: /uri +cookie: .* +host: 127.0.0.1 +user-agent: .* +x-access-token: ey.* +x-id-token: ey.* +x-real-ip: 127.0.0.1 +x-userinfo: ey.* --- no_error_log [error] - === TEST 6: Update plugin with `bearer_only=true`. --- config location /t { From ec57b247a7f97a058134d0024aea65271ab21c77 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 21 Dec 2020 13:38:20 +0100 Subject: [PATCH 074/108] Fix stray end. --- t/plugin/openid-connect.t | 1 - 1 file changed, 1 deletion(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index fd27ba7939b4..503ac75bd725 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -460,7 +460,6 @@ OIDC Relying Party authentication process, using the authorization code flow. ngx.status = res.status ngx.say(res.body) - end else -- Response from Keycloak not ok. ngx.say(false) From 159672de5c48640efce41922174f6b64cde28bab Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 21 Dec 2020 20:48:33 +0100 Subject: [PATCH 075/108] Make config parametrizable through NGINX variables. --- t/plugin/openid-connect.t | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 503ac75bd725..31ed391ffe3b 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -192,11 +192,17 @@ true -=== TEST 11: Modify route to match catch-all URI `/*` and point plugin to local Keycloak instance. +=== TEST 6: Modify route to match catch-all URI `/*` and point plugin to local Keycloak instance. Notes: - Use proper discovery endpoint of the local Keycloak instance. - Realm, client ID, and secret are specific to the Keycloak Docker image used. - Use a redirect URL that is matched by route as well. Keycloak will redirect to this with the authorization code once user has been authenticated. +- Uses default plugin configuration with respect to token and userinfo headers. +--- main_config +set $set_access_token_header 'true'; +set $access_token_in_authorization_header 'false'; +set $set_id_token_header 'true'; +set $set_userinfo_header 'true'; --- config location /t { content_by_lua_block { @@ -214,7 +220,11 @@ Notes: "ssl_verify": false, "timeout": 10, "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect" + "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", + "set_access_token_header": ]] .. ngx.var.set_access_token_header .. [[, + "access_token_in_authorization_header": ]] .. ngx.var.access_token_in_authorization_header .. [[, + "set_id_token_header": ]] .. ngx.var.set_id_token_header .. [[, + "set_userinfo_token_header": ]] .. ngx.var.set_userinfo_header .. [[ } }, "upstream": { @@ -238,7 +248,11 @@ Notes: "timeout": 10, "realm": "University", "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect" + "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", + "set_access_token_header": ]] .. ngx.var.set_access_token_header .. [[, + "access_token_in_authorization_header": ]] .. ngx.var.access_token_in_authorization_header .. [[, + "set_id_token_header": ]] .. ngx.var.set_id_token_header .. [[, + "set_userinfo_token_header": ]] .. ngx.var.set_userinfo_header .. [[ } }, "upstream": { @@ -270,7 +284,7 @@ passed -=== TEST 12: Access route w/o bearer token. +=== TEST 7: Access route w/o bearer token. When redirected to authentication endpoint of ID provider, go through the full OIDC Relying Party authentication process, using the authorization code flow. --- config From ec8addd68db0678e931dc0fa2e23e07e769a7be0 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 16:04:28 +0100 Subject: [PATCH 076/108] Clean up test cases. --- t/plugin/openid-connect.t | 340 +++++++++++++++++++++++++++++++++++--- 1 file changed, 314 insertions(+), 26 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 31ed391ffe3b..428bad98a5a5 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -198,11 +198,6 @@ Notes: - Realm, client ID, and secret are specific to the Keycloak Docker image used. - Use a redirect URL that is matched by route as well. Keycloak will redirect to this with the authorization code once user has been authenticated. - Uses default plugin configuration with respect to token and userinfo headers. ---- main_config -set $set_access_token_header 'true'; -set $access_token_in_authorization_header 'false'; -set $set_id_token_header 'true'; -set $set_userinfo_header 'true'; --- config location /t { content_by_lua_block { @@ -221,10 +216,10 @@ set $set_userinfo_header 'true'; "timeout": 10, "introspection_endpoint_auth_method": "client_secret_post", "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", - "set_access_token_header": ]] .. ngx.var.set_access_token_header .. [[, - "access_token_in_authorization_header": ]] .. ngx.var.access_token_in_authorization_header .. [[, - "set_id_token_header": ]] .. ngx.var.set_id_token_header .. [[, - "set_userinfo_token_header": ]] .. ngx.var.set_userinfo_header .. [[ + "set_access_token_header": true, + "access_token_in_authorization_header": false, + "set_id_token_header": true, + "set_userinfo_token_header": true } }, "upstream": { @@ -249,10 +244,10 @@ set $set_userinfo_header 'true'; "realm": "University", "introspection_endpoint_auth_method": "client_secret_post", "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", - "set_access_token_header": ]] .. ngx.var.set_access_token_header .. [[, - "access_token_in_authorization_header": ]] .. ngx.var.access_token_in_authorization_header .. [[, - "set_id_token_header": ]] .. ngx.var.set_id_token_header .. [[, - "set_userinfo_token_header": ]] .. ngx.var.set_userinfo_header .. [[ + "set_access_token_header": true, + "access_token_in_authorization_header": false, + "set_id_token_header": true, + "set_userinfo_token_header": true } }, "upstream": { @@ -497,7 +492,300 @@ x-userinfo: ey.* -=== TEST 6: Update plugin with `bearer_only=true`. +=== TEST 8: Re-configure plugin with respect to headers that get sent to upstream. +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "openid-connect": { + "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", + "realm": "University", + "client_id": "course_management", + "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", + "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", + "ssl_verify": false, + "timeout": 10, + "introspection_endpoint_auth_method": "client_secret_post", + "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", + "set_access_token_header": true, + "access_token_in_authorization_header": true, + "set_id_token_header": false, + "set_userinfo_token_header": false + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]], + [[{ + "node": { + "value": { + "plugins": { + "openid-connect": { + "client_id": "course_management", + "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", + "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", + "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", + "ssl_verify": false, + "timeout": 10, + "realm": "University", + "introspection_endpoint_auth_method": "client_secret_post", + "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", + "set_access_token_header": true, + "access_token_in_authorization_header": true, + "set_id_token_header": false, + "set_userinfo_token_header": false + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }, + "key": "/apisix/routes/1" + }, + "action": "set" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 9: Access route w/o bearer token. +When redirected to authentication endpoint of ID provider, go through the full +OIDC Relying Party authentication process, using the authorization code flow. +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + + -- Invoke /uri endpoint w/o bearer token. Should receive redirect to Keycloak authorization endpoint. + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" + local res, err = httpc:request_uri(uri, {method = "GET"}) + + if not res then + -- No response, must be an error. + ngx.status = 500 + ngx.say(err) + return + elseif res.status ~= 302 then + -- Not a redirect which we expect. + -- Use 500 to indicate error. + ngx.status = 500 + ngx.say("Initial request was not redirected to ID provider authorization endpoint.") + return + else + -- Redirect to ID provider's authorization endpoint. + + -- Extract nonce and state from response header. + local nonce = res.headers['Location']:match('.*nonce=([^&]+).*') + local state = res.headers['Location']:match('.*state=([^&]+).*') + + -- Extract cookies. Important since OIDC module tracks state with a session cookie. + local cookies = res.headers['Set-Cookie'] + + -- Concatenate cookies into one string as expected when sent in request header. + local cookie_str = "" + + if type(cookies) == 'string' then + cookie_str = cookies:match('([^;]*); .*') + else + -- Must be a table. + local len = #cookies + if len > 0 then + cookie_str = cookies[1]:match('([^;]*); .*') + for i = 2, len do + cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + end + end + end + + -- Call authorization endpoint we were redirected to. + -- Note: This typically returns a login form which is the case here for Keycloak as well. + -- However, how we process the form to perform the login is specific to Keycloak and + -- possibly even the version used. + res, err = httpc:request_uri(res.headers['Location'], {method = "GET"}) + + if not res then + -- No response, must be an error. + ngx.status = 500 + ngx.say(err) + return + elseif res.status ~= 200 then + -- Unexpected response. + ngx.status = res.status + ngx.say(res.body) + return + end + + -- Check if response code was ok. + if res.status == 200 then + -- From the returned form, extract the submit URI and parameters. + local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') + + -- Substitute escaped ampersand in parameters. + params = params:gsub("&", "&") + + -- Get all cookies returned. Probably not so important since not part of OIDC specification. + local auth_cookies = res.headers['Set-Cookie'] + + -- Concatenate cookies into one string as expected when sent in request header. + local auth_cookie_str = "" + + if type(auth_cookies) == 'string' then + auth_cookie_str = auth_cookies:match('([^;]*); .*') + else + -- Must be a table. + local len = #auth_cookies + if len > 0 then + auth_cookie_str = auth_cookies[1]:match('([^;]*); .*') + for i = 2, len do + auth_cookie_str = auth_cookie_str .. "; " .. auth_cookies[i]:match('([^;]*); .*') + end + end + end + + -- Invoke the submit URI with parameters and cookies, adding username and password in the body. + -- Note: Username and password are specific to the Keycloak Docker image used. + res, err = httpc:request_uri(uri .. "?" .. params, { + method = "POST", + body = "username=teacher@gmail.com&password=123456", + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded", + ["Cookie"] = auth_cookie_str + } + }) + + if not res then + -- No response, must be an error. + ngx.status = 500 + ngx.say(err) + return + elseif res.status ~= 302 then + -- Not a redirect which we expect. + -- Use 500 to indicate error. + ngx.status = 500 + ngx.say("Login form submission did not return redirect to redirect URI.") + return + end + + -- Extract the redirect URI from the response header. + -- TODO: Consider validating this against the plugin configuration. + local redirect_uri = res.headers['Location'] + + -- Invoke the redirect URI (which contains the authorization code as an URL parameter). + res, err = httpc:request_uri(redirect_uri, { + method = "GET", + headers = { + ["Cookie"] = cookie_str + } + }) + + if not res then + -- No response, must be an error. + ngx.status = 500 + ngx.say(err) + return + elseif res.status ~= 302 then + -- Not a redirect which we expect. + -- Use 500 to indicate error. + ngx.status = 500 + ngx.say("Invoking redirect URI with authorization code did not return redirect to original URI.") + return + end + + -- Get all cookies returned. This should update the session cookie maintained by the OIDC module with the new state. + -- E.g. the session cookie should now contain the access token, ID token and user info. + -- The cookie itself should however be treated as opaque. + cookies = res.headers['Set-Cookie'] + + -- Concatenate cookies into one string as expected when sent in request header. + if type(cookies) == 'string' then + cookie_str = cookies:match('([^;]*); .*') + else + -- Must be a table. + local len = #cookies + if len > 0 then + cookie_str = cookies[1]:match('([^;]*); .*') + for i = 2, len do + cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') + end + end + end + + -- Get the final URI out of the Location response header. This should be the original URI that was requested. + -- TODO: Consider checking the URI against the original request URI. + redirect_uri = "http://127.0.0.1:" .. ngx.var.server_port .. res.headers['Location'] + + -- Make the final call back to the original URI. + res, err = httpc:request_uri(redirect_uri, { + method = "GET", + headers = { + ["Cookie"] = cookie_str + } + }) + + if not res then + -- No response, must be an error. + ngx.status = 500 + ngx.say(err) + return + elseif res.status ~= 200 then + -- Not a valid response. + -- Use 500 to indicate error. + ngx.status = 500 + ngx.say("Invoking the original URI didn't return the expected result.") + return + end + + ngx.status = res.status + ngx.say(res.body) + else + -- Response from Keycloak not ok. + ngx.say(false) + end + end + } + } +--- request +GET /t +--- response_body_like +uri: /uri +authorization: Bearer ey.* +cookie: .* +host: 127.0.0.1 +user-agent: .* +x-real-ip: 127.0.0.1 +--- no_error_log +[error] + + + +=== TEST 10: Update plugin with `bearer_only=true`. --- config location /t { content_by_lua_block { @@ -569,7 +857,7 @@ passed -=== TEST 7: Access route w/o bearer token. Should return 401. +=== TEST 11: Access route w/o bearer token. Should return 401. --- timeout: 10s --- request GET /hello @@ -582,7 +870,7 @@ WWW-Authenticate: Bearer realm=apisix -=== TEST 8: Update plugin with ID provider public key, so tokens can be validated locally. +=== TEST 12: Update plugin with ID provider public key, so tokens can be validated locally. --- config location /t { content_by_lua_block { @@ -662,7 +950,7 @@ passed -=== TEST 9: Access route with valid token. +=== TEST 13: Access route with valid token. --- config location /t { content_by_lua_block { @@ -694,7 +982,7 @@ true -=== TEST 9a: Update route URI to '/uri' where upstream endpoint returns request headers in response body. +=== TEST 14: Update route URI to '/uri' where upstream endpoint returns request headers in response body. --- config location /t { content_by_lua_block { @@ -774,7 +1062,7 @@ passed -=== TEST 9b: Access route with valid token in `Authorization` header. Upstream should additionally get the token in the `X-Access-Token` header. +=== TEST 15: Access route with valid token in `Authorization` header. Upstream should additionally get the token in the `X-Access-Token` header. --- request GET /uri HTTP/1.1 --- more_headers @@ -791,7 +1079,7 @@ x-real-ip: 127.0.0.1 -=== TEST 9c: Update plugin to only use `Authorization` header. +=== TEST 16: Update plugin to only use `Authorization` header. --- config location /t { content_by_lua_block { @@ -873,7 +1161,7 @@ passed -=== TEST 9d: Access route with valid token in `Authorization` header. Upstream should not get the additional `X-Access-Token` header. +=== TEST 17: Access route with valid token in `Authorization` header. Upstream should not get the additional `X-Access-Token` header. --- request GET /uri HTTP/1.1 --- more_headers @@ -889,7 +1177,7 @@ x-real-ip: 127.0.0.1 -=== TEST 9z: Switch route URI back to `/hello`. +=== TEST 18: Switch route URI back to `/hello`. --- config location /t { content_by_lua_block { @@ -969,7 +1257,7 @@ passed -=== TEST 10: Access route with invalid token. Should return 401. +=== TEST 19: Access route with invalid token. Should return 401. --- config location /t { content_by_lua_block { @@ -1000,7 +1288,7 @@ jwt signature verification failed -=== TEST 11: Update route with Keycloak introspection endpoint and public key removed. Should now invoke introspection endpoint to validate tokens. +=== TEST 20: Update route with Keycloak introspection endpoint and public key removed. Should now invoke introspection endpoint to validate tokens. --- config location /t { content_by_lua_block { @@ -1076,7 +1364,7 @@ passed -=== TEST 12: Obtain valid token and access route with it. +=== TEST 21: Obtain valid token and access route with it. --- config location /t { content_by_lua_block { @@ -1136,7 +1424,7 @@ true -=== TEST 13: Access route with an invalid token. +=== TEST 22: Access route with an invalid token. --- config location /t { content_by_lua_block { From 61cce4d4f4d8697d25eafbc9f1d3d3635f941c31 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 16:16:33 +0100 Subject: [PATCH 077/108] Fix test cases. --- t/plugin/openid-connect.t | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 428bad98a5a5..72477a591b88 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -1003,7 +1003,11 @@ true [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" + "token_signing_alg_values_expected": "RS256", + "set_access_token_header": true, + "access_token_in_authorization_header": false, + "set_id_token_header": false, + "set_userinfo_token_header": false } }, "upstream": { @@ -1030,7 +1034,11 @@ true [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" + "token_signing_alg_values_expected": "RS256", + "set_access_token_header": true, + "access_token_in_authorization_header": false, + "set_id_token_header": false, + "set_userinfo_token_header": false } }, "upstream": { @@ -1101,7 +1109,10 @@ x-real-ip: 127.0.0.1 [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", "token_signing_alg_values_expected": "RS256", - "access_token_in_authorization_header": true + "set_access_token_header": true, + "access_token_in_authorization_header": true, + "set_id_token_header": false, + "set_userinfo_token_header": false } }, "upstream": { @@ -1129,7 +1140,10 @@ x-real-ip: 127.0.0.1 [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", "token_signing_alg_values_expected": "RS256", - "access_token_in_authorization_header": true + "set_access_token_header": true, + "access_token_in_authorization_header": true, + "set_id_token_header": false, + "set_userinfo_token_header": false } }, "upstream": { From 2afae7bef0fb9a07b6eb14fbafd91300f14692fc Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 17:14:32 +0100 Subject: [PATCH 078/108] Fix test cases. --- t/plugin/openid-connect.t | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 72477a591b88..07d1db6a87a1 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -1034,11 +1034,7 @@ true [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256", - "set_access_token_header": true, - "access_token_in_authorization_header": false, - "set_id_token_header": false, - "set_userinfo_token_header": false + "token_signing_alg_values_expected": "RS256" } }, "upstream": { @@ -1140,7 +1136,6 @@ x-real-ip: 127.0.0.1 [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", "token_signing_alg_values_expected": "RS256", - "set_access_token_header": true, "access_token_in_authorization_header": true, "set_id_token_header": false, "set_userinfo_token_header": false From 0f8a53d5f46b185c02aa97e9d018802784812f51 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 17:30:55 +0100 Subject: [PATCH 079/108] Fix unit tests. --- t/plugin/openid-connect.t | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 07d1db6a87a1..bf655d4a6c1d 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -1004,10 +1004,7 @@ true [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", "token_signing_alg_values_expected": "RS256", - "set_access_token_header": true, - "access_token_in_authorization_header": false, - "set_id_token_header": false, - "set_userinfo_token_header": false + "access_token_in_authorization_header": true } }, "upstream": { @@ -1016,7 +1013,7 @@ true }, "type": "roundrobin" }, - "uri": "/uri" + "uri": "/hello" }]], [[{ "node": { "value": { @@ -1034,7 +1031,8 @@ true [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" + "token_signing_alg_values_expected": "RS256", + "access_token_in_authorization_header": true } }, "upstream": { @@ -1043,7 +1041,7 @@ true }, "type": "roundrobin" }, - "uri": "/uri" + "uri": "/hello" }, "key": "/apisix/routes/1" }, From 40bd35318142f0b264485c4eb9d96b6bf2efdb63 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 17:58:32 +0100 Subject: [PATCH 080/108] Fix test cases. --- t/plugin/openid-connect.t | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index bf655d4a6c1d..dd02439e9870 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -19,6 +19,7 @@ use t::APISIX 'no_plan'; repeat_each(1); no_long_string(); no_root_location(); +no_shuffle(); run_tests; __DATA__ @@ -1003,8 +1004,7 @@ true [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256", - "access_token_in_authorization_header": true + "token_signing_alg_values_expected": "RS256" } }, "upstream": { @@ -1013,7 +1013,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello" + "uri": "/uri" }]], [[{ "node": { "value": { @@ -1031,8 +1031,7 @@ true [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256", - "access_token_in_authorization_header": true + "token_signing_alg_values_expected": "RS256" } }, "upstream": { @@ -1041,7 +1040,7 @@ true }, "type": "roundrobin" }, - "uri": "/hello" + "uri": "/uri" }, "key": "/apisix/routes/1" }, From 8fb5bd3f19e6b53d2e26566f3b9807ce53d8dde5 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 19:13:21 +0100 Subject: [PATCH 081/108] Debugging. --- t/plugin/openid-connect.t | 478 -------------------------------------- 1 file changed, 478 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index dd02439e9870..b583b8624aa6 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -980,481 +980,3 @@ GET /t true --- no_error_log [error] - - - -=== TEST 14: Update route URI to '/uri' where upstream endpoint returns request headers in response body. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/uri" - }]], - [[{ "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10000, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/uri" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 15: Access route with valid token in `Authorization` header. Upstream should additionally get the token in the `X-Access-Token` header. ---- request -GET /uri HTTP/1.1 ---- more_headers -Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w ---- response_body -uri: /uri -authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w -host: localhost -x-access-token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w -x-real-ip: 127.0.0.1 ---- no_error_log -[error] ---- error_code: 200 - - - -=== TEST 16: Update plugin to only use `Authorization` header. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256", - "set_access_token_header": true, - "access_token_in_authorization_header": true, - "set_id_token_header": false, - "set_userinfo_token_header": false - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/uri" - }]], - [[{ "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10000, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256", - "access_token_in_authorization_header": true, - "set_id_token_header": false, - "set_userinfo_token_header": false - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/uri" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 17: Access route with valid token in `Authorization` header. Upstream should not get the additional `X-Access-Token` header. ---- request -GET /uri HTTP/1.1 ---- more_headers -Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w ---- response_body -uri: /uri -authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqXRyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w -host: localhost -x-real-ip: 127.0.0.1 ---- no_error_log -[error] ---- error_code: 200 - - - -=== TEST 18: Switch route URI back to `/hello`. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/hello" - }]], - [[{ "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10000, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/hello" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 19: Access route with invalid token. Should return 401. ---- config - location /t { - content_by_lua_block { - local http = require "resty.http" - local httpc = http.new() - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" - local res, err = httpc:request_uri(uri, { - method = "GET", - headers = { - ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" .. - ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" .. - "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" .. - "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" .. - "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7", - } - }) - ngx.status = res.status - if res.status == 200 then - ngx.say(true) - end - } - } ---- request -GET /t ---- error_code: 401 ---- error_log -jwt signature verification failed - - - -=== TEST 20: Update route with Keycloak introspection endpoint and public key removed. Should now invoke introspection endpoint to validate tokens. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ - "plugins": { - "openid-connect": { - "client_id": "course_management", - "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://localhost:3000", - "ssl_verify": false, - "timeout": 10, - "bearer_only": true, - "realm": "University", - "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/hello" - }]], - [[{ - "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "course_management", - "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://localhost:3000", - "ssl_verify": false, - "timeout": 10000, - "bearer_only": true, - "realm": "University", - "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/hello" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 21: Obtain valid token and access route with it. ---- config - location /t { - content_by_lua_block { - -- Obtain valid access token from Keycloak using known username and password. - local json_decode = require("toolkit.json").decode - local http = require "resty.http" - local httpc = http.new() - local uri = "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token" - local res, err = httpc:request_uri(uri, { - method = "POST", - body = "grant_type=password&client_id=course_management&client_secret=d1ec69e9-55d2-4109-a3ea-befa071579d5&username=teacher@gmail.com&password=123456", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded" - } - }) - - -- Check response from keycloak and fail quickly if there's no response. - if not res then - ngx.say(err) - return - end - - -- Check if response code was ok. - if res.status == 200 then - -- Get access token from JSON response body. - local body = json_decode(res.body) - local accessToken = body["access_token"] - - -- Access route using access token. Should work. - uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" - local res, err = httpc:request_uri(uri, { - method = "GET", - headers = { - ["Authorization"] = "Bearer " .. body["access_token"] - } - }) - - if res.status == 200 then - -- Route accessed successfully. - ngx.say(true) - else - -- Couldn't access route. - ngx.say(false) - end - else - -- Response from Keycloak not ok. - ngx.say(false) - end - } - } ---- request -GET /t ---- response_body -true ---- no_error_log -[error] - - - -=== TEST 22: Access route with an invalid token. ---- config - location /t { - content_by_lua_block { - -- Access route using a fake access token. - local http = require "resty.http" - local httpc = http.new() - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" - local res, err = httpc:request_uri(uri, { - method = "GET", - headers = { - ["Authorization"] = "Bearer " .. "fake access token", - } - }) - - if res.status == 200 then - ngx.say(true) - else - ngx.say(false) - end - } - } ---- request -GET /t ---- response_body -false ---- error_log -failed to introspect in openidc: invalid token From d9bdcef7bf49259b12d94e7cbebea5a93fe7104a Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 20:52:54 +0100 Subject: [PATCH 082/108] Take out another test for debugging. --- t/plugin/openid-connect.t | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index b583b8624aa6..b313d19fd086 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -948,35 +948,3 @@ GET /t passed --- no_error_log [error] - - - -=== TEST 13: Access route with valid token. ---- config - location /t { - content_by_lua_block { - local http = require "resty.http" - local httpc = http.new() - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" - local res, err = httpc:request_uri(uri, { - method = "GET", - headers = { - ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" .. - ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" .. - "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" .. - "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" .. - "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w", - } - }) - ngx.status = res.status - if res.status == 200 then - ngx.say(true) - end - } - } ---- request -GET /t ---- response_body -true ---- no_error_log -[error] From eb3614588aff0f4734db176ee51d996ec95382ab Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 20:56:29 +0100 Subject: [PATCH 083/108] Add back case. --- t/plugin/openid-connect.t | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index b313d19fd086..b583b8624aa6 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -948,3 +948,35 @@ GET /t passed --- no_error_log [error] + + + +=== TEST 13: Access route with valid token. +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" + local res, err = httpc:request_uri(uri, { + method = "GET", + headers = { + ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" .. + ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" .. + "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" .. + "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" .. + "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w", + } + }) + ngx.status = res.status + if res.status == 200 then + ngx.say(true) + end + } + } +--- request +GET /t +--- response_body +true +--- no_error_log +[error] From 239f506d0a66926d0e171b2a85faf3bd3dbd46f0 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 21:05:29 +0100 Subject: [PATCH 084/108] Fix test case. --- t/plugin/openid-connect.t | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index b583b8624aa6..d916f3cb0a65 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -194,11 +194,6 @@ true === TEST 6: Modify route to match catch-all URI `/*` and point plugin to local Keycloak instance. -Notes: -- Use proper discovery endpoint of the local Keycloak instance. -- Realm, client ID, and secret are specific to the Keycloak Docker image used. -- Use a redirect URL that is matched by route as well. Keycloak will redirect to this with the authorization code once user has been authenticated. -- Uses default plugin configuration with respect to token and userinfo headers. --- config location /t { content_by_lua_block { @@ -280,9 +275,7 @@ passed -=== TEST 7: Access route w/o bearer token. -When redirected to authentication endpoint of ID provider, go through the full -OIDC Relying Party authentication process, using the authorization code flow. +=== TEST 7: Access route w/o bearer token and go through the full OIDC Relying Party authentication process --- config location /t { content_by_lua_block { From 8a43122373f6f1944e401e9e0a9d757f5239bc7e Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 21:33:39 +0100 Subject: [PATCH 085/108] Add back another case. --- t/plugin/openid-connect.t | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index d916f3cb0a65..d50468a6dca0 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -973,3 +973,83 @@ GET /t true --- no_error_log [error] + + + +=== TEST 14: Update route URI to '/uri' where upstream endpoint returns request headers in response body. +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ "plugins": { + "openid-connect": { + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "https://samples.auth0.com/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", + "ssl_verify": false, + "timeout": 10, + "bearer_only": true, + "scope": "apisix", + "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. + [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. + [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. + [[-----END PUBLIC KEY-----", + "token_signing_alg_values_expected": "RS256" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/uri" + }]], + [[{ "node": { + "value": { + "plugins": { + "openid-connect": { + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "https://samples.auth0.com/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", + "ssl_verify": false, + "timeout": 10000, + "bearer_only": true, + "scope": "apisix", + "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. + [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. + [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. + [[-----END PUBLIC KEY-----", + "token_signing_alg_values_expected": "RS256" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/uri" + }, + "key": "/apisix/routes/1" + }, + "action": "set" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] From 0a8b046286eb7e51af66e34dffeb7d4c4604fcbd Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 21:36:14 +0100 Subject: [PATCH 086/108] Debugging. --- t/plugin/openid-connect.t | 784 +------------------------------------- 1 file changed, 1 insertion(+), 783 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index d50468a6dca0..2d9dd77b8c20 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -162,8 +162,7 @@ passed -=== TEST 5: Access route w/o bearer token. -Should redirect to authentication endpoint of ID provider. +=== TEST 5: Access route w/o bearer token. Should redirect to authentication endpoint of ID provider. --- config location /t { content_by_lua_block { @@ -272,784 +271,3 @@ GET /t passed --- no_error_log [error] - - - -=== TEST 7: Access route w/o bearer token and go through the full OIDC Relying Party authentication process ---- config - location /t { - content_by_lua_block { - local http = require "resty.http" - local httpc = http.new() - - -- Invoke /uri endpoint w/o bearer token. Should receive redirect to Keycloak authorization endpoint. - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" - local res, err = httpc:request_uri(uri, {method = "GET"}) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 302 then - -- Not a redirect which we expect. - -- Use 500 to indicate error. - ngx.status = 500 - ngx.say("Initial request was not redirected to ID provider authorization endpoint.") - return - else - -- Redirect to ID provider's authorization endpoint. - - -- Extract nonce and state from response header. - local nonce = res.headers['Location']:match('.*nonce=([^&]+).*') - local state = res.headers['Location']:match('.*state=([^&]+).*') - - -- Extract cookies. Important since OIDC module tracks state with a session cookie. - local cookies = res.headers['Set-Cookie'] - - -- Concatenate cookies into one string as expected when sent in request header. - local cookie_str = "" - - if type(cookies) == 'string' then - cookie_str = cookies:match('([^;]*); .*') - else - -- Must be a table. - local len = #cookies - if len > 0 then - cookie_str = cookies[1]:match('([^;]*); .*') - for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - end - end - end - - -- Call authorization endpoint we were redirected to. - -- Note: This typically returns a login form which is the case here for Keycloak as well. - -- However, how we process the form to perform the login is specific to Keycloak and - -- possibly even the version used. - res, err = httpc:request_uri(res.headers['Location'], {method = "GET"}) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 200 then - -- Unexpected response. - ngx.status = res.status - ngx.say(res.body) - return - end - - -- Check if response code was ok. - if res.status == 200 then - -- From the returned form, extract the submit URI and parameters. - local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') - - -- Substitute escaped ampersand in parameters. - params = params:gsub("&", "&") - - -- Get all cookies returned. Probably not so important since not part of OIDC specification. - local auth_cookies = res.headers['Set-Cookie'] - - -- Concatenate cookies into one string as expected when sent in request header. - local auth_cookie_str = "" - - if type(auth_cookies) == 'string' then - auth_cookie_str = auth_cookies:match('([^;]*); .*') - else - -- Must be a table. - local len = #auth_cookies - if len > 0 then - auth_cookie_str = auth_cookies[1]:match('([^;]*); .*') - for i = 2, len do - auth_cookie_str = auth_cookie_str .. "; " .. auth_cookies[i]:match('([^;]*); .*') - end - end - end - - -- Invoke the submit URI with parameters and cookies, adding username and password in the body. - -- Note: Username and password are specific to the Keycloak Docker image used. - res, err = httpc:request_uri(uri .. "?" .. params, { - method = "POST", - body = "username=teacher@gmail.com&password=123456", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded", - ["Cookie"] = auth_cookie_str - } - }) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 302 then - -- Not a redirect which we expect. - -- Use 500 to indicate error. - ngx.status = 500 - ngx.say("Login form submission did not return redirect to redirect URI.") - return - end - - -- Extract the redirect URI from the response header. - -- TODO: Consider validating this against the plugin configuration. - local redirect_uri = res.headers['Location'] - - -- Invoke the redirect URI (which contains the authorization code as an URL parameter). - res, err = httpc:request_uri(redirect_uri, { - method = "GET", - headers = { - ["Cookie"] = cookie_str - } - }) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 302 then - -- Not a redirect which we expect. - -- Use 500 to indicate error. - ngx.status = 500 - ngx.say("Invoking redirect URI with authorization code did not return redirect to original URI.") - return - end - - -- Get all cookies returned. This should update the session cookie maintained by the OIDC module with the new state. - -- E.g. the session cookie should now contain the access token, ID token and user info. - -- The cookie itself should however be treated as opaque. - cookies = res.headers['Set-Cookie'] - - -- Concatenate cookies into one string as expected when sent in request header. - if type(cookies) == 'string' then - cookie_str = cookies:match('([^;]*); .*') - else - -- Must be a table. - local len = #cookies - if len > 0 then - cookie_str = cookies[1]:match('([^;]*); .*') - for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - end - end - end - - -- Get the final URI out of the Location response header. This should be the original URI that was requested. - -- TODO: Consider checking the URI against the original request URI. - redirect_uri = "http://127.0.0.1:" .. ngx.var.server_port .. res.headers['Location'] - - -- Make the final call back to the original URI. - res, err = httpc:request_uri(redirect_uri, { - method = "GET", - headers = { - ["Cookie"] = cookie_str - } - }) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 200 then - -- Not a valid response. - -- Use 500 to indicate error. - ngx.status = 500 - ngx.say("Invoking the original URI didn't return the expected result.") - return - end - - ngx.status = res.status - ngx.say(res.body) - else - -- Response from Keycloak not ok. - ngx.say(false) - end - end - } - } ---- request -GET /t ---- response_body_like -uri: /uri -cookie: .* -host: 127.0.0.1 -user-agent: .* -x-access-token: ey.* -x-id-token: ey.* -x-real-ip: 127.0.0.1 -x-userinfo: ey.* ---- no_error_log -[error] - - - -=== TEST 8: Re-configure plugin with respect to headers that get sent to upstream. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ - "plugins": { - "openid-connect": { - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "realm": "University", - "client_id": "course_management", - "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", - "ssl_verify": false, - "timeout": 10, - "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", - "set_access_token_header": true, - "access_token_in_authorization_header": true, - "set_id_token_header": false, - "set_userinfo_token_header": false - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/*" - }]], - [[{ - "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "course_management", - "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", - "ssl_verify": false, - "timeout": 10, - "realm": "University", - "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", - "set_access_token_header": true, - "access_token_in_authorization_header": true, - "set_id_token_header": false, - "set_userinfo_token_header": false - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/*" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 9: Access route w/o bearer token. -When redirected to authentication endpoint of ID provider, go through the full -OIDC Relying Party authentication process, using the authorization code flow. ---- config - location /t { - content_by_lua_block { - local http = require "resty.http" - local httpc = http.new() - - -- Invoke /uri endpoint w/o bearer token. Should receive redirect to Keycloak authorization endpoint. - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/uri" - local res, err = httpc:request_uri(uri, {method = "GET"}) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 302 then - -- Not a redirect which we expect. - -- Use 500 to indicate error. - ngx.status = 500 - ngx.say("Initial request was not redirected to ID provider authorization endpoint.") - return - else - -- Redirect to ID provider's authorization endpoint. - - -- Extract nonce and state from response header. - local nonce = res.headers['Location']:match('.*nonce=([^&]+).*') - local state = res.headers['Location']:match('.*state=([^&]+).*') - - -- Extract cookies. Important since OIDC module tracks state with a session cookie. - local cookies = res.headers['Set-Cookie'] - - -- Concatenate cookies into one string as expected when sent in request header. - local cookie_str = "" - - if type(cookies) == 'string' then - cookie_str = cookies:match('([^;]*); .*') - else - -- Must be a table. - local len = #cookies - if len > 0 then - cookie_str = cookies[1]:match('([^;]*); .*') - for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - end - end - end - - -- Call authorization endpoint we were redirected to. - -- Note: This typically returns a login form which is the case here for Keycloak as well. - -- However, how we process the form to perform the login is specific to Keycloak and - -- possibly even the version used. - res, err = httpc:request_uri(res.headers['Location'], {method = "GET"}) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 200 then - -- Unexpected response. - ngx.status = res.status - ngx.say(res.body) - return - end - - -- Check if response code was ok. - if res.status == 200 then - -- From the returned form, extract the submit URI and parameters. - local uri, params = res.body:match('.*action="(.*)%?(.*)" method="post">') - - -- Substitute escaped ampersand in parameters. - params = params:gsub("&", "&") - - -- Get all cookies returned. Probably not so important since not part of OIDC specification. - local auth_cookies = res.headers['Set-Cookie'] - - -- Concatenate cookies into one string as expected when sent in request header. - local auth_cookie_str = "" - - if type(auth_cookies) == 'string' then - auth_cookie_str = auth_cookies:match('([^;]*); .*') - else - -- Must be a table. - local len = #auth_cookies - if len > 0 then - auth_cookie_str = auth_cookies[1]:match('([^;]*); .*') - for i = 2, len do - auth_cookie_str = auth_cookie_str .. "; " .. auth_cookies[i]:match('([^;]*); .*') - end - end - end - - -- Invoke the submit URI with parameters and cookies, adding username and password in the body. - -- Note: Username and password are specific to the Keycloak Docker image used. - res, err = httpc:request_uri(uri .. "?" .. params, { - method = "POST", - body = "username=teacher@gmail.com&password=123456", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded", - ["Cookie"] = auth_cookie_str - } - }) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 302 then - -- Not a redirect which we expect. - -- Use 500 to indicate error. - ngx.status = 500 - ngx.say("Login form submission did not return redirect to redirect URI.") - return - end - - -- Extract the redirect URI from the response header. - -- TODO: Consider validating this against the plugin configuration. - local redirect_uri = res.headers['Location'] - - -- Invoke the redirect URI (which contains the authorization code as an URL parameter). - res, err = httpc:request_uri(redirect_uri, { - method = "GET", - headers = { - ["Cookie"] = cookie_str - } - }) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 302 then - -- Not a redirect which we expect. - -- Use 500 to indicate error. - ngx.status = 500 - ngx.say("Invoking redirect URI with authorization code did not return redirect to original URI.") - return - end - - -- Get all cookies returned. This should update the session cookie maintained by the OIDC module with the new state. - -- E.g. the session cookie should now contain the access token, ID token and user info. - -- The cookie itself should however be treated as opaque. - cookies = res.headers['Set-Cookie'] - - -- Concatenate cookies into one string as expected when sent in request header. - if type(cookies) == 'string' then - cookie_str = cookies:match('([^;]*); .*') - else - -- Must be a table. - local len = #cookies - if len > 0 then - cookie_str = cookies[1]:match('([^;]*); .*') - for i = 2, len do - cookie_str = cookie_str .. "; " .. cookies[i]:match('([^;]*); .*') - end - end - end - - -- Get the final URI out of the Location response header. This should be the original URI that was requested. - -- TODO: Consider checking the URI against the original request URI. - redirect_uri = "http://127.0.0.1:" .. ngx.var.server_port .. res.headers['Location'] - - -- Make the final call back to the original URI. - res, err = httpc:request_uri(redirect_uri, { - method = "GET", - headers = { - ["Cookie"] = cookie_str - } - }) - - if not res then - -- No response, must be an error. - ngx.status = 500 - ngx.say(err) - return - elseif res.status ~= 200 then - -- Not a valid response. - -- Use 500 to indicate error. - ngx.status = 500 - ngx.say("Invoking the original URI didn't return the expected result.") - return - end - - ngx.status = res.status - ngx.say(res.body) - else - -- Response from Keycloak not ok. - ngx.say(false) - end - end - } - } ---- request -GET /t ---- response_body_like -uri: /uri -authorization: Bearer ey.* -cookie: .* -host: 127.0.0.1 -user-agent: .* -x-real-ip: 127.0.0.1 ---- no_error_log -[error] - - - -=== TEST 10: Update plugin with `bearer_only=true`. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ - "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10, - "bearer_only": true, - "scope": "apisix" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/hello" - }]], - [[{ - "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10000, - "bearer_only": true, - "scope": "apisix" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/hello" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 11: Access route w/o bearer token. Should return 401. ---- timeout: 10s ---- request -GET /hello ---- error_code: 401 ---- response_headers_like -WWW-Authenticate: Bearer realm=apisix ---- no_error_log -[error] ---- SKIP - - - -=== TEST 12: Update plugin with ID provider public key, so tokens can be validated locally. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/hello" - }]], - [[{ "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10000, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/hello" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - -=== TEST 13: Access route with valid token. ---- config - location /t { - content_by_lua_block { - local http = require "resty.http" - local httpc = http.new() - local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" - local res, err = httpc:request_uri(uri, { - method = "GET", - headers = { - ["Authorization"] = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" .. - ".eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk" .. - "4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCB" .. - "jb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.u1ISx7JbuK_GFRIUqIMP175FqX" .. - "RyF9V7y86480Q4N3jNxs3ePbc51TFtIHDrKttstU4Tub28PYVSlr-HXfjo7w", - } - }) - ngx.status = res.status - if res.status == 200 then - ngx.say(true) - end - } - } ---- request -GET /t ---- response_body -true ---- no_error_log -[error] - - - -=== TEST 14: Update route URI to '/uri' where upstream endpoint returns request headers in response body. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/uri" - }]], - [[{ "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10000, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/uri" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] From 61f6008ef2559c0a37bc396ec3f35276d6ff3a31 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 21:49:55 +0100 Subject: [PATCH 087/108] Debugging. --- t/plugin/openid-connect.t | 82 --------------------------------------- 1 file changed, 82 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 2d9dd77b8c20..1938d53b4bff 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -189,85 +189,3 @@ true --- error_code: 302 --- no_error_log [error] - - - -=== TEST 6: Modify route to match catch-all URI `/*` and point plugin to local Keycloak instance. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ - "plugins": { - "openid-connect": { - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "realm": "University", - "client_id": "course_management", - "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", - "ssl_verify": false, - "timeout": 10, - "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", - "set_access_token_header": true, - "access_token_in_authorization_header": false, - "set_id_token_header": true, - "set_userinfo_token_header": true - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/*" - }]], - [[{ - "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "course_management", - "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", - "ssl_verify": false, - "timeout": 10, - "realm": "University", - "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", - "set_access_token_header": true, - "access_token_in_authorization_header": false, - "set_id_token_header": true, - "set_userinfo_token_header": true - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/*" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] From 5e2ea0aa5018548fc0d023cc9eb5684c4a11bc10 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 21:58:59 +0100 Subject: [PATCH 088/108] Debugging. --- t/plugin/openid-connect.t | 82 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 1938d53b4bff..746fc7af1596 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -189,3 +189,85 @@ true --- error_code: 302 --- no_error_log [error] + + + +=== TEST 6: Modify route to match catch-all URI `/*` and point plugin to local Keycloak instance. +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "openid-connect": { + "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", + "realm": "University", + "client_id": "course_management", + "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", + "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", + "ssl_verify": false, + "timeout": 10, + "introspection_endpoint_auth_method": "client_secret_post", + "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", + "set_access_token_header": true, + "access_token_in_authorization_header": false, + "set_id_token_header": true, + "set_userinfo_token_header": true + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/foo" + }]], + [[{ + "node": { + "value": { + "plugins": { + "openid-connect": { + "client_id": "course_management", + "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", + "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", + "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", + "ssl_verify": false, + "timeout": 10, + "realm": "University", + "introspection_endpoint_auth_method": "client_secret_post", + "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", + "set_access_token_header": true, + "access_token_in_authorization_header": false, + "set_id_token_header": true, + "set_userinfo_token_header": true + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/foo" + }, + "key": "/apisix/routes/1" + }, + "action": "set" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] From 95cd514ce29438c6cfb278b187b0b6a00a5b720c Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 22:06:00 +0100 Subject: [PATCH 089/108] Debuging. --- t/plugin/openid-connect.t | 40 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 746fc7af1596..d2a75c030437 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -202,19 +202,13 @@ true [[{ "plugins": { "openid-connect": { - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "realm": "University", - "client_id": "course_management", - "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "http://127.0.0.1:1980/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", "ssl_verify": false, - "timeout": 10, - "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", - "set_access_token_header": true, - "access_token_in_authorization_header": false, - "set_id_token_header": true, - "set_userinfo_token_header": true + "timeout": 10000, + "scope": "apisix" } }, "upstream": { @@ -223,26 +217,20 @@ true }, "type": "roundrobin" }, - "uri": "/foo" + "uri": "/*" }]], [[{ "node": { "value": { "plugins": { "openid-connect": { - "client_id": "course_management", - "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5", - "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration", - "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated", + "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + "discovery": "http://127.0.0.1:1980/.well-known/openid-configuration", + "redirect_uri": "https://iresty.com", "ssl_verify": false, - "timeout": 10, - "realm": "University", - "introspection_endpoint_auth_method": "client_secret_post", - "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect", - "set_access_token_header": true, - "access_token_in_authorization_header": false, - "set_id_token_header": true, - "set_userinfo_token_header": true + "timeout": 10000, + "scope": "apisix" } }, "upstream": { @@ -251,7 +239,7 @@ true }, "type": "roundrobin" }, - "uri": "/foo" + "uri": "/*" }, "key": "/apisix/routes/1" }, From 3583c3a6f3b319e7143da0ddd12d3403086d7641 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 22:13:55 +0100 Subject: [PATCH 090/108] Debugging. --- t/plugin/openid-connect.t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index d2a75c030437..63f4072dbc98 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -129,7 +129,7 @@ done "discovery": "http://127.0.0.1:1980/.well-known/openid-configuration", "redirect_uri": "https://iresty.com", "ssl_verify": false, - "timeout": 10000, + "timeout": 10, "scope": "apisix" } }, @@ -207,7 +207,7 @@ true "discovery": "http://127.0.0.1:1980/.well-known/openid-configuration", "redirect_uri": "https://iresty.com", "ssl_verify": false, - "timeout": 10000, + "timeout": 10, "scope": "apisix" } }, @@ -229,7 +229,7 @@ true "discovery": "http://127.0.0.1:1980/.well-known/openid-configuration", "redirect_uri": "https://iresty.com", "ssl_verify": false, - "timeout": 10000, + "timeout": 10, "scope": "apisix" } }, From d12aa16f1abdd57ab102688d6f65bf77d83f8c15 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 22:41:29 +0100 Subject: [PATCH 091/108] Add defaults check. --- t/plugin/openid-connect.t | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index e73c4373b274..39a62013274e 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -1529,3 +1529,31 @@ GET /t false --- error_log failed to introspect in openidc: invalid token + + + +=== TEST 23: Check defaults. +--- config + location /t { + content_by_lua_block { + local json = require("t.toolkit.json") + local plugin = require("apisix.plugins.openid-connect") + local s = { + client_id = "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", + client_secret = "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", + discovery = "http://127.0.0.1:1980/.well-known/openid-configuration", + } + local ok, err = plugin.check_schema(s) + if not ok then + ngx.say(err) + end + + ngx.say(json.encode(s)) + } + } +--- request +GET /t +--- response_body +{"access_token_in_authorization_header":false,"bearer_only":false,"client_id":"kbyuFDidLLm280LIwVFiazOqjO3ty8KH","client_secret":"60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa","discovery":"http://127.0.0.1:1980/.well-known/openid-configuration","introspection_endpoint_auth_method":"client_secret_basic","logout_path":"/logout","realm":"apisix","scope":"openid","set_access_token_header":true,"set_id_token_header":true,"set_userinfo_token_header":true,"ssl_verify":false,"timeout":3} +--- no_error_log +[error] From ba4260f7790ab714eed81794d9033f3c965aada0 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 22:48:48 +0100 Subject: [PATCH 092/108] Re-enable all tests. Revert server.lua. --- .travis/linux_openresty_runner.sh | 2 +- t/lib/server.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis/linux_openresty_runner.sh b/.travis/linux_openresty_runner.sh index d24948012693..f451bbe053f0 100755 --- a/.travis/linux_openresty_runner.sh +++ b/.travis/linux_openresty_runner.sh @@ -138,7 +138,7 @@ script() { make lint && make license-check || exit 1 # APISIX_ENABLE_LUACOV=1 PERL5LIB=.:$PERL5LIB prove -Itest-nginx/lib -r t - PERL5LIB=.:$PERL5LIB prove -Itest-nginx/lib -r t/plugin/openid-connect.t + PERL5LIB=.:$PERL5LIB prove -Itest-nginx/lib -r t } after_success() { diff --git a/t/lib/server.lua b/t/lib/server.lua index 256fdde79cfc..e5edde9e9484 100644 --- a/t/lib/server.lua +++ b/t/lib/server.lua @@ -326,6 +326,7 @@ function _M.headers() ngx.say("/headers") end + function _M.log() ngx.req.read_body() local body = ngx.req.get_body_data() From aed18dc2b35e43ed463f24a661164f491410e7a6 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 23:11:39 +0100 Subject: [PATCH 093/108] Clean up. --- apisix/plugins/openid-connect.lua | 71 ++++++++++++++----------------- t/plugin/openid-connect.t | 14 +++--- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index ed96a54e160d..f8aa8e0da884 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -75,11 +75,11 @@ local schema = { type = "boolean", default = true }, - set_userinfo_token_header = { - description = "Whether the user info token should be added in the X-Userinfo " .. - "header to the request for downstream.", + access_token_in_authorization_header = { + description = "Whether the access token should be added in the Authorization " .. + "header as opposed to the X-Access-Token header.", type = "boolean", - default = true + default = false }, set_id_token_header = { description = "Whether the ID token should be added in the X-ID-Token header to " .. @@ -87,11 +87,11 @@ local schema = { type = "boolean", default = true }, - access_token_in_authorization_header = { - description = "Whether the access token should be added in the Authorization " .. - "header as opposed to the X-Access-Token header.", + set_userinfo_header = { + description = "Whether the user info token should be added in the X-Userinfo " .. + "header to the request for downstream.", type = "boolean", - default = false + default = true } }, required = {"client_id", "client_secret", "discovery"} @@ -150,37 +150,18 @@ local function check_bearer_access_token(ctx) end -local function set_header(ctx, name, value) - -- Set a request header to the given value and update the cached headers in the context as well. - - -- Set header in request. - ngx.req.set_header(name, value) - - -- Set header in cache, maybe. - if ctx and ctx.headers then - ctx.headers[name] = value - end -end - - -local function add_user_header(ctx, user) - local userinfo = core.json.encode(user) - set_header(ctx, "X-Userinfo", ngx_encode_base64(userinfo)) -end - - local function add_access_token_header(ctx, conf, token) -- Add Authorization or X-Access-Token header, respectively, if not already set. if conf.set_access_token_header then if conf.access_token_in_authorization_header then if not core.request.header(ctx, "Authorization") then -- Add Authorization header. - set_header(ctx, "Authorization", "Bearer " .. token) + core.request.set_header(ctx, "Authorization", "Bearer " .. token) end else if not core.request.header(ctx, "X-Access-Token") then -- Add X-Access-Token header. - set_header(ctx, "X-Access-Token", token) + core.request.set_header(ctx, "X-Access-Token", token) end end end @@ -213,9 +194,9 @@ local function introspect(ctx, conf) else -- Token is valid and res contains the response from the introspection endpoint. - if conf.set_userinfo_token_header then + if conf.set_userinfo_header then -- Set X-Userinfo header to introspection endpoint response. - add_user_header(ctx, res) + core.request.set_header(ctx, "X-Userinfo", ngx_encode_base64(core.json.encode(res))) end -- Add configured access token header, maybe. @@ -250,6 +231,7 @@ function _M.rewrite(plugin_conf, ctx) if not conf.redirect_uri then conf.redirect_uri = ctx.var.request_uri end + if not conf.ssl_verify then -- openidc use "no" to disable ssl verification conf.ssl_verify = "no" @@ -257,27 +239,35 @@ function _M.rewrite(plugin_conf, ctx) local response, err if conf.introspection_endpoint or conf.public_key then + -- Try to introspect access token from request, if it is present. + -- Returns a nil response if token is not found. response, err = introspect(ctx, conf) + if err then + -- Unable to introspect. Fail quickly. core.log.error("failed to introspect in openidc: ", err) return response end end if not response then - -- A valid token was not in the request. Try to obtain one by authenticatin against the - -- configured identity provider. + -- Response has not yet been determined. Either no token was found in + -- the request or introspection is not set up. + + -- Authenticate the request. This will check and validate the token if + -- it is stored in the openidc module's session cookie, or divert to the + -- authorization endpoint of the ID provider. local response, err = openidc.authenticate(conf) + if err then core.log.error("failed to authenticate in openidc: ", err) return 500 end if response then - -- Add X-Userinfo header, maybe. - if response.user and conf.set_userinfo_token_header then - add_user_header(ctx, response.user) - end + -- If the openidc module has returned a response, it may contain, + -- respectively, the access token, ID token, and userinfo. Add + -- respective headers to the request, if so configured. -- Add configured access token header, maybe. if response.access_token then @@ -287,7 +277,12 @@ function _M.rewrite(plugin_conf, ctx) -- Add X-ID-Token header, maybe. if response.id_token and conf.set_id_token_header then local token = core.json.encode(response.id_token) - set_header(ctx, "X-ID-Token", ngx.encode_base64(token)) + core.request.set_header(ctx, "X-ID-Token", ngx.encode_base64(token)) + end + + -- Add X-Userinfo header, maybe. + if response.user and conf.set_userinfo_header then + core.request.set_header(ctx, "X-Userinfo", ngx_encode_base64(core.json.encode(response.user))) end end end diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 39a62013274e..edb5ef506b61 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -214,7 +214,7 @@ true "set_access_token_header": true, "access_token_in_authorization_header": false, "set_id_token_header": true, - "set_userinfo_token_header": true + "set_userinfo_header": true } }, "upstream": { @@ -242,7 +242,7 @@ true "set_access_token_header": true, "access_token_in_authorization_header": false, "set_id_token_header": true, - "set_userinfo_token_header": true + "set_userinfo_header": true } }, "upstream": { @@ -507,7 +507,7 @@ x-userinfo: ey.* "set_access_token_header": true, "access_token_in_authorization_header": true, "set_id_token_header": false, - "set_userinfo_token_header": false + "set_userinfo_header": false } }, "upstream": { @@ -535,7 +535,7 @@ x-userinfo: ey.* "set_access_token_header": true, "access_token_in_authorization_header": true, "set_id_token_header": false, - "set_userinfo_token_header": false + "set_userinfo_header": false } }, "upstream": { @@ -1176,7 +1176,7 @@ x-real-ip: 127.0.0.1 "set_access_token_header": true, "access_token_in_authorization_header": true, "set_id_token_header": false, - "set_userinfo_token_header": false + "set_userinfo_header": false } }, "upstream": { @@ -1206,7 +1206,7 @@ x-real-ip: 127.0.0.1 "token_signing_alg_values_expected": "RS256", "access_token_in_authorization_header": true, "set_id_token_header": false, - "set_userinfo_token_header": false + "set_userinfo_header": false } }, "upstream": { @@ -1554,6 +1554,6 @@ failed to introspect in openidc: invalid token --- request GET /t --- response_body -{"access_token_in_authorization_header":false,"bearer_only":false,"client_id":"kbyuFDidLLm280LIwVFiazOqjO3ty8KH","client_secret":"60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa","discovery":"http://127.0.0.1:1980/.well-known/openid-configuration","introspection_endpoint_auth_method":"client_secret_basic","logout_path":"/logout","realm":"apisix","scope":"openid","set_access_token_header":true,"set_id_token_header":true,"set_userinfo_token_header":true,"ssl_verify":false,"timeout":3} +{"access_token_in_authorization_header":false,"bearer_only":false,"client_id":"kbyuFDidLLm280LIwVFiazOqjO3ty8KH","client_secret":"60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa","discovery":"http://127.0.0.1:1980/.well-known/openid-configuration","introspection_endpoint_auth_method":"client_secret_basic","logout_path":"/logout","realm":"apisix","scope":"openid","set_access_token_header":true,"set_id_token_header":true,"set_userinfo_header":true,"ssl_verify":false,"timeout":3} --- no_error_log [error] From 700d26cfb9d93bee561ec2ea8c211dec11d5516d Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 22 Dec 2020 23:15:01 +0100 Subject: [PATCH 094/108] Fix linting errors. --- apisix/plugins/openid-connect.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index f8aa8e0da884..d9394f435988 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -196,7 +196,8 @@ local function introspect(ctx, conf) if conf.set_userinfo_header then -- Set X-Userinfo header to introspection endpoint response. - core.request.set_header(ctx, "X-Userinfo", ngx_encode_base64(core.json.encode(res))) + core.request.set_header(ctx, "X-Userinfo", + ngx_encode_base64(core.json.encode(res))) end -- Add configured access token header, maybe. @@ -282,7 +283,8 @@ function _M.rewrite(plugin_conf, ctx) -- Add X-Userinfo header, maybe. if response.user and conf.set_userinfo_header then - core.request.set_header(ctx, "X-Userinfo", ngx_encode_base64(core.json.encode(response.user))) + core.request.set_header(ctx, "X-Userinfo", + ngx_encode_base64(core.json.encode(response.user))) end end end From 8bf14985f6cdbc64e4c6d6504d43fa7ed3d47d7b Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Wed, 23 Dec 2020 23:44:30 +0100 Subject: [PATCH 095/108] Clean up. --- apisix/plugins/openid-connect.lua | 156 +++++++++++++++++------------- 1 file changed, 89 insertions(+), 67 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index d9394f435988..c5b5fa1dd34a 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -120,7 +120,7 @@ function _M.check_schema(conf) end -local function check_bearer_access_token(ctx) +local function get_bearer_access_token(ctx) -- Get Authorization header, maybe. local auth_header = core.request.header(ctx, "Authorization") if not auth_header then @@ -150,6 +150,63 @@ local function check_bearer_access_token(ctx) end + +local function introspect(ctx, conf) + -- Extract token, maybe. + local has_token, token, _ = get_bearer_access_token(ctx) + + if not has_token then + -- Could not find token. + + if conf.bearer_only then + -- Token strictly required in request. + ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm .. '"' + return ngx.HTTP_UNAUTHORIZED, "No bearer token found in request.", nil, nil + else + -- Return empty result. + return nil, nil, nil, nil + end + end + + -- If we get here, token was found in request. + local res, err + + if conf.public_key then + -- Validate token against public key. + -- TODO: In the called method, the openidc module will try to extract + -- the token by itself again -- from a request header or session cookie. + -- It is inefficient that we also need to extract it (just from headers) + -- so we can add it in the configured header. Find a way to use openidc + -- module's internal methods to extract the token. + res, err = openidc.bearer_jwt_verify(conf) + + if err then + -- Error while validating or token invalid. + ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm .. + '", error="invalid_token", error_description="' .. err .. '"' + return ngx.HTTP_UNAUTHORIZED, err, nil, nil + end + + -- Token successfully validated. + return res, err, token, nil + else + -- Validate token against introspection endpoint. + -- TODO: Same as above for public key validation. + res, err = openidc.introspect(conf) + + if err then + ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm .. + '", error="invalid_token", error_description="' .. err .. '"' + return ngx.HTTP_UNAUTHORIZED, err, nil, nil + end + + -- Token successfully validated and response from the introspection + -- endpoint contains the userinfo. + return res, err, token, res + end +end + + local function add_access_token_header(ctx, conf, token) -- Add Authorization or X-Access-Token header, respectively, if not already set. if conf.set_access_token_header then @@ -168,58 +225,6 @@ local function add_access_token_header(ctx, conf, token) end -local function introspect(ctx, conf) - -- Extract token, maybe. Ignore errors. - local has_token, token, _ = check_bearer_access_token(ctx) - - -- Check if token was extracted or if we always require a token in the request. - if has_token or conf.bearer_only then - local res, err - - if conf.public_key then - -- Validate token against public key. - res, err = openidc.bearer_jwt_verify(conf) - if res then - -- Token is valid. - - -- Add configured access token header, maybe. - add_access_token_header(ctx, conf, token) - return res - end - else - -- Validate token against introspection endpoint. - res, err = openidc.introspect(conf) - if err then - return ngx.HTTP_UNAUTHORIZED, err - else - -- Token is valid and res contains the response from the introspection endpoint. - - if conf.set_userinfo_header then - -- Set X-Userinfo header to introspection endpoint response. - core.request.set_header(ctx, "X-Userinfo", - ngx_encode_base64(core.json.encode(res))) - end - - -- Add configured access token header, maybe. - add_access_token_header(ctx, conf, token) - return res - end - end - if conf.bearer_only then - -- If we get here, the token could not be validated, but we always require a valid - -- token in the request. - ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm - .. '",error="' .. err .. '"' - return ngx.HTTP_UNAUTHORIZED, err - end - end - - -- Return nil to indicate that a token could not be extracted or validated, but that we don't - -- want to fail quickly. - return nil -end - - function _M.rewrite(plugin_conf, ctx) local conf = core.table.clone(plugin_conf) @@ -240,35 +245,52 @@ function _M.rewrite(plugin_conf, ctx) local response, err if conf.introspection_endpoint or conf.public_key then - -- Try to introspect access token from request, if it is present. - -- Returns a nil response if token is not found. - response, err = introspect(ctx, conf) + -- An introspection endpoint or a public key has been configured. Try to + -- validate the access token from the request, if it is present in a + -- request header. Otherwise, return a nil response. See below for + -- handling of the case where the access token is stored in a session cookie. + response, err, access_token, userinfo = introspect(ctx, conf) if err then - -- Unable to introspect. Fail quickly. - core.log.error("failed to introspect in openidc: ", err) + -- Error while validating token or invalid token. + core.log.error("OIDC introspection failed: ", err) return response end + + if response then + if access_token then + -- Add configured access token header, maybe. + add_access_token_header(ctx, conf, token) + end + if userinfo and conf.set_userinfo_header then + -- Set X-Userinfo header to introspection endpoint response. + core.request.set_header(ctx, "X-Userinfo", + ngx_encode_base64(core.json.encode(userinfo))) + end + end end if not response then - -- Response has not yet been determined. Either no token was found in - -- the request or introspection is not set up. - - -- Authenticate the request. This will check and validate the token if - -- it is stored in the openidc module's session cookie, or divert to the - -- authorization endpoint of the ID provider. + -- Either token validation via introspection endpoint or public key is + -- not configured, and/or token could not be extracted from the request. + + -- Authenticate the request. This will validate the access token if it + -- is stored in a session cookie, and also renew the token if required. + -- If no token can be extracted, the response will redirect to the ID + -- provider's authorization endpoint to initiate the Relying Party flow. + -- This code path also handles when the ID provider then redirects to + -- the configured redirect URI after successful authentication. local response, err = openidc.authenticate(conf) if err then - core.log.error("failed to authenticate in openidc: ", err) + core.log.error("OIDC authentication failed: ", err) return 500 end if response then -- If the openidc module has returned a response, it may contain, - -- respectively, the access token, ID token, and userinfo. Add - -- respective headers to the request, if so configured. + -- respectively, the access token, the ID token, and the userinfo. + -- Add respective headers to the request, if so configured. -- Add configured access token header, maybe. if response.access_token then From ae34c7fed1f24e167d73e74bcb3f7640652a9f1d Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Sat, 26 Dec 2020 14:56:19 +0100 Subject: [PATCH 096/108] Minor fixes. --- apisix/plugins/openid-connect.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index c5b5fa1dd34a..e3633095ba52 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -169,7 +169,6 @@ local function introspect(ctx, conf) end -- If we get here, token was found in request. - local res, err if conf.public_key then -- Validate token against public key. @@ -178,7 +177,7 @@ local function introspect(ctx, conf) -- It is inefficient that we also need to extract it (just from headers) -- so we can add it in the configured header. Find a way to use openidc -- module's internal methods to extract the token. - res, err = openidc.bearer_jwt_verify(conf) + local res, err = openidc.bearer_jwt_verify(conf) if err then -- Error while validating or token invalid. @@ -192,7 +191,7 @@ local function introspect(ctx, conf) else -- Validate token against introspection endpoint. -- TODO: Same as above for public key validation. - res, err = openidc.introspect(conf) + local res, err = openidc.introspect(conf) if err then ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm .. @@ -249,7 +248,7 @@ function _M.rewrite(plugin_conf, ctx) -- validate the access token from the request, if it is present in a -- request header. Otherwise, return a nil response. See below for -- handling of the case where the access token is stored in a session cookie. - response, err, access_token, userinfo = introspect(ctx, conf) + local response, err, access_token, userinfo = introspect(ctx, conf) if err then -- Error while validating token or invalid token. @@ -260,7 +259,7 @@ function _M.rewrite(plugin_conf, ctx) if response then if access_token then -- Add configured access token header, maybe. - add_access_token_header(ctx, conf, token) + add_access_token_header(ctx, conf, access_token) end if userinfo and conf.set_userinfo_header then -- Set X-Userinfo header to introspection endpoint response. From d9ab5baa0d1bba64c0b18b96b8923fdff24404b4 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Sat, 26 Dec 2020 15:04:35 +0100 Subject: [PATCH 097/108] Minor fix. --- apisix/plugins/openid-connect.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index e3633095ba52..ccfa5e623a1a 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -243,12 +243,14 @@ function _M.rewrite(plugin_conf, ctx) end local response, err + if conf.introspection_endpoint or conf.public_key then -- An introspection endpoint or a public key has been configured. Try to -- validate the access token from the request, if it is present in a -- request header. Otherwise, return a nil response. See below for -- handling of the case where the access token is stored in a session cookie. - local response, err, access_token, userinfo = introspect(ctx, conf) + local access_token, userinfo + response, err, access_token, userinfo = introspect(ctx, conf) if err then -- Error while validating token or invalid token. @@ -279,7 +281,7 @@ function _M.rewrite(plugin_conf, ctx) -- provider's authorization endpoint to initiate the Relying Party flow. -- This code path also handles when the ID provider then redirects to -- the configured redirect URI after successful authentication. - local response, err = openidc.authenticate(conf) + response, err = openidc.authenticate(conf) if err then core.log.error("OIDC authentication failed: ", err) From 8ca9c8eedb1ec48dd273e0e9137748eb53568ada Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Sat, 26 Dec 2020 15:42:28 +0100 Subject: [PATCH 098/108] Fix test. --- t/plugin/openid-connect.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index edb5ef506b61..afa650fece97 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -1528,7 +1528,7 @@ GET /t --- response_body false --- error_log -failed to introspect in openidc: invalid token +OIDC introspection failed: invalid token From 0e924d70e0b6955b9f2966e885684b9684b4afea Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 28 Dec 2020 10:53:16 +0100 Subject: [PATCH 099/108] Remove duplicate test case. --- t/plugin/openid-connect.t | 81 --------------------------------------- 1 file changed, 81 deletions(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index afa650fece97..01a184637d04 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -973,87 +973,6 @@ true -=== TEST 14: Update route URI to '/uri' where upstream endpoint returns request headers in response body. ---- config - location /t { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/uri" - }]], - [[{ "node": { - "value": { - "plugins": { - "openid-connect": { - "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH", - "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa", - "discovery": "https://samples.auth0.com/.well-known/openid-configuration", - "redirect_uri": "https://iresty.com", - "ssl_verify": false, - "timeout": 10, - "bearer_only": true, - "scope": "apisix", - "public_key": "-----BEGIN PUBLIC KEY-----\n]] .. - [[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] .. - [[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] .. - [[-----END PUBLIC KEY-----", - "token_signing_alg_values_expected": "RS256" - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/uri" - }, - "key": "/apisix/routes/1" - }, - "action": "set" - }]] - ) - - if code >= 300 then - ngx.status = code - end - ngx.say(body) - } - } ---- request -GET /t ---- response_body -passed ---- no_error_log -[error] - - - - === TEST 14: Update route URI to '/uri' where upstream endpoint returns request headers in response body. --- config location /t { From 18a188bf38e5c1f9217246aa3014095a1a9b431a Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 28 Dec 2020 11:15:43 +0100 Subject: [PATCH 100/108] Check Authorization heade parse result. --- apisix/plugins/openid-connect.lua | 13 ++++++++-- t/plugin/openid-connect.t | 41 +++++++++++++++++++------------ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index ccfa5e623a1a..840c6f1b87fd 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -137,8 +137,13 @@ local function get_bearer_access_token(ctx) -- Check format of Authorization header. local res, err = ngx_re.split(auth_header, " ", nil, nil, 2) + if not res then + -- No result was returned. return false, nil, err + elseif #res < 2 then + -- Header doesn't split into enough tokens. + return false, nil, "Invalid Authorization header format." end if string.lower(res[1]) == "bearer" then @@ -146,14 +151,18 @@ local function get_bearer_access_token(ctx) return true, res[2], nil end - return false + return false, nil, nil end local function introspect(ctx, conf) -- Extract token, maybe. - local has_token, token, _ = get_bearer_access_token(ctx) + local has_token, token, status, err = get_bearer_access_token(ctx) + + if err then + return ngx.HTTP_BAD_REQUEST, err, nil, nil + end if not has_token then -- Could not find token. diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 01a184637d04..bccfc570eed6 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -848,20 +848,29 @@ passed -=== TEST 11: Access route w/o bearer token. Should return 401. +=== TEST 11: Access route w/o bearer token. Should return 401 (Unauthorized). --- timeout: 10s --- request GET /hello --- error_code: 401 --- response_headers_like WWW-Authenticate: Bearer realm=apisix ---- no_error_log -[error] ---- SKIP +--- error_log +OIDC introspection failed: No bearer token found in request. + + + +=== TEST 12: Access route with invalid Authorization header value. Should return 400 (Bad Request). +--- timeout: 10s +--- request +GET /hello +--- error_code: 401 +--- error_log +OIDC introspection failed: Invalid Authorization header format. -=== TEST 12: Update plugin with ID provider public key, so tokens can be validated locally. +=== TEST 13: Update plugin with ID provider public key, so tokens can be validated locally. --- config location /t { content_by_lua_block { @@ -941,7 +950,7 @@ passed -=== TEST 13: Access route with valid token. +=== TEST 14: Access route with valid token. --- config location /t { content_by_lua_block { @@ -973,7 +982,7 @@ true -=== TEST 14: Update route URI to '/uri' where upstream endpoint returns request headers in response body. +=== TEST 15: Update route URI to '/uri' where upstream endpoint returns request headers in response body. --- config location /t { content_by_lua_block { @@ -1053,7 +1062,7 @@ passed -=== TEST 15: Access route with valid token in `Authorization` header. Upstream should additionally get the token in the `X-Access-Token` header. +=== TEST 16: Access route with valid token in `Authorization` header. Upstream should additionally get the token in the `X-Access-Token` header. --- request GET /uri HTTP/1.1 --- more_headers @@ -1070,7 +1079,7 @@ x-real-ip: 127.0.0.1 -=== TEST 16: Update plugin to only use `Authorization` header. +=== TEST 17: Update plugin to only use `Authorization` header. --- config location /t { content_by_lua_block { @@ -1157,7 +1166,7 @@ passed -=== TEST 17: Access route with valid token in `Authorization` header. Upstream should not get the additional `X-Access-Token` header. +=== TEST 18: Access route with valid token in `Authorization` header. Upstream should not get the additional `X-Access-Token` header. --- request GET /uri HTTP/1.1 --- more_headers @@ -1173,7 +1182,7 @@ x-real-ip: 127.0.0.1 -=== TEST 18: Switch route URI back to `/hello`. +=== TEST 19: Switch route URI back to `/hello`. --- config location /t { content_by_lua_block { @@ -1253,7 +1262,7 @@ passed -=== TEST 19: Access route with invalid token. Should return 401. +=== TEST 20: Access route with invalid token. Should return 401. --- config location /t { content_by_lua_block { @@ -1284,7 +1293,7 @@ jwt signature verification failed -=== TEST 20: Update route with Keycloak introspection endpoint and public key removed. Should now invoke introspection endpoint to validate tokens. +=== TEST 21: Update route with Keycloak introspection endpoint and public key removed. Should now invoke introspection endpoint to validate tokens. --- config location /t { content_by_lua_block { @@ -1360,7 +1369,7 @@ passed -=== TEST 21: Obtain valid token and access route with it. +=== TEST 22: Obtain valid token and access route with it. --- config location /t { content_by_lua_block { @@ -1420,7 +1429,7 @@ true -=== TEST 22: Access route with an invalid token. +=== TEST 23: Access route with an invalid token. --- config location /t { content_by_lua_block { @@ -1451,7 +1460,7 @@ OIDC introspection failed: invalid token -=== TEST 23: Check defaults. +=== TEST 24: Check defaults. --- config location /t { content_by_lua_block { From 5354eca132c37a2e711903cf0dd7077ab17f7016 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 28 Dec 2020 11:21:28 +0100 Subject: [PATCH 101/108] Fix stray return value. --- apisix/plugins/openid-connect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index 840c6f1b87fd..e41ef3303943 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -158,7 +158,7 @@ end local function introspect(ctx, conf) -- Extract token, maybe. - local has_token, token, status, err = get_bearer_access_token(ctx) + local has_token, token, err = get_bearer_access_token(ctx) if err then return ngx.HTTP_BAD_REQUEST, err, nil, nil From 207cfa1c662b012aa30c0269e4ae1fe1531667d8 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 28 Dec 2020 13:41:22 +0100 Subject: [PATCH 102/108] Always try to extract access token from header if bearer_only flag is set. --- apisix/plugins/openid-connect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index e41ef3303943..cd6c30a2c97a 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -253,7 +253,7 @@ function _M.rewrite(plugin_conf, ctx) local response, err - if conf.introspection_endpoint or conf.public_key then + if conf.bearer_only or conf.introspection_endpoint or conf.public_key then -- An introspection endpoint or a public key has been configured. Try to -- validate the access token from the request, if it is present in a -- request header. Otherwise, return a nil response. See below for From 61ae9f381f54c2ac6bd6f917363a31b6669288e7 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 28 Dec 2020 14:11:48 +0100 Subject: [PATCH 103/108] Fix test cases. --- t/plugin/openid-connect.t | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index bccfc570eed6..47afa78f2790 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -864,7 +864,9 @@ OIDC introspection failed: No bearer token found in request. --- timeout: 10s --- request GET /hello ---- error_code: 401 +--- more_headers +Authorization: foo +--- error_code: 400 --- error_log OIDC introspection failed: Invalid Authorization header format. From d3b046bba6169f2cdd32632b77eeb56c6c9a9052 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 28 Dec 2020 15:14:21 +0100 Subject: [PATCH 104/108] Fix expected response header. --- t/plugin/openid-connect.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t index 47afa78f2790..7222b0034533 100644 --- a/t/plugin/openid-connect.t +++ b/t/plugin/openid-connect.t @@ -854,7 +854,7 @@ passed GET /hello --- error_code: 401 --- response_headers_like -WWW-Authenticate: Bearer realm=apisix +WWW-Authenticate: Bearer realm="apisix" --- error_log OIDC introspection failed: No bearer token found in request. From 29a4904208e6642e83f2b2a76ce542fd63b3da8f Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 28 Dec 2020 17:11:31 +0100 Subject: [PATCH 105/108] Trivial change to re-start failed checks on GitHub. --- apisix/plugins/openid-connect.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index cd6c30a2c97a..2eec4eeee316 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -105,6 +105,7 @@ local _M = { schema = schema, } + function _M.check_schema(conf) if conf.ssl_verify == "no" then -- we used to set 'ssl_verify' to "no" @@ -155,7 +156,6 @@ local function get_bearer_access_token(ctx) end - local function introspect(ctx, conf) -- Extract token, maybe. local has_token, token, err = get_bearer_access_token(ctx) From 307e07b8562fe44a76b195a8abe941f90ca1983e Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Tue, 29 Dec 2020 15:21:09 +0100 Subject: [PATCH 106/108] Move check into auxilliary function. --- apisix/plugins/openid-connect.lua | 35 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/apisix/plugins/openid-connect.lua b/apisix/plugins/openid-connect.lua index 2eec4eeee316..1c8d78c1c3ba 100644 --- a/apisix/plugins/openid-connect.lua +++ b/apisix/plugins/openid-connect.lua @@ -216,17 +216,19 @@ end local function add_access_token_header(ctx, conf, token) - -- Add Authorization or X-Access-Token header, respectively, if not already set. - if conf.set_access_token_header then - if conf.access_token_in_authorization_header then - if not core.request.header(ctx, "Authorization") then - -- Add Authorization header. - core.request.set_header(ctx, "Authorization", "Bearer " .. token) - end - else - if not core.request.header(ctx, "X-Access-Token") then - -- Add X-Access-Token header. - core.request.set_header(ctx, "X-Access-Token", token) + if token then + -- Add Authorization or X-Access-Token header, respectively, if not already set. + if conf.set_access_token_header then + if conf.access_token_in_authorization_header then + if not core.request.header(ctx, "Authorization") then + -- Add Authorization header. + core.request.set_header(ctx, "Authorization", "Bearer " .. token) + end + else + if not core.request.header(ctx, "X-Access-Token") then + -- Add X-Access-Token header. + core.request.set_header(ctx, "X-Access-Token", token) + end end end end @@ -268,10 +270,9 @@ function _M.rewrite(plugin_conf, ctx) end if response then - if access_token then - -- Add configured access token header, maybe. - add_access_token_header(ctx, conf, access_token) - end + -- Add configured access token header, maybe. + add_access_token_header(ctx, conf, access_token) + if userinfo and conf.set_userinfo_header then -- Set X-Userinfo header to introspection endpoint response. core.request.set_header(ctx, "X-Userinfo", @@ -303,9 +304,7 @@ function _M.rewrite(plugin_conf, ctx) -- Add respective headers to the request, if so configured. -- Add configured access token header, maybe. - if response.access_token then - add_access_token_header(ctx, conf, response.access_token) - end + add_access_token_header(ctx, conf, response.access_token) -- Add X-ID-Token header, maybe. if response.id_token and conf.set_id_token_header then From f51baf74c13800e195bf73c8100197c23224a97f Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 4 Jan 2021 11:33:13 +0100 Subject: [PATCH 107/108] Update plugin documentation. --- doc/plugins/openid-connect.md | 108 +++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 16 deletions(-) diff --git a/doc/plugins/openid-connect.md b/doc/plugins/openid-connect.md index ddfffa739ba5..d261254fbed2 100644 --- a/doc/plugins/openid-connect.md +++ b/doc/plugins/openid-connect.md @@ -31,22 +31,55 @@ The OAuth 2 / Open ID Connect(OIDC) plugin provides authentication and introspec ## Attributes -| Name | Type | Requirement | Default | Valid | Description | -| ---------------------------------- | ------- | ----------- | --------------------- | ------- | ---------------------------------------------------------------------------------------------- | -| client_id | string | required | | | OAuth client ID | -| client_secret | string | required | | | OAuth client secret | -| discovery | string | required | | | URL of the discovery endpoint of the identity server | -| scope | string | optional | "openid" | | Scope used for the authentication | -| realm | string | optional | "apisix" | | Realm used for the authentication | -| bearer_only | boolean | optional | false | | Setting this `true` will check for the authorization header in the request with a bearer token | -| logout_path | string | optional | "/logout" | | | -| redirect_uri | string | optional | "ngx.var.request_uri" | | | -| timeout | integer | optional | 3 | [1,...] | Timeout in seconds | -| ssl_verify | boolean | optional | false | | | -| introspection_endpoint | string | optional | | | URL of the token verification endpoint of the identity server | -| introspection_endpoint_auth_method | string | optional | "client_secret_basic" | | Authentication method name for token introspection | -| public_key | string | optional | | | The public key to verify the token | -| token_signing_alg_values_expected | string | optional | | | Algorithm used to sign the token | +| Name | Type | Requirement | Default | Valid | Description | +| ------------------------------------ | ------- | ----------- | --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- | +| client_id | string | required | | | OAuth client ID | +| client_secret | string | required | | | OAuth client secret | +| discovery | string | required | | | URL of the discovery endpoint of the identity server | +| scope | string | optional | "openid" | | Scope used for the authentication | +| realm | string | optional | "apisix" | | Realm used for the authentication | +| bearer_only | boolean | optional | false | | Setting this `true` will check for the authorization header in the request with a bearer token | +| logout_path | string | optional | "/logout" | | | +| redirect_uri | string | optional | "ngx.var.request_uri" | | | +| timeout | integer | optional | 3 | [1,...] | Timeout in seconds | +| ssl_verify | boolean | optional | false | | | +| introspection_endpoint | string | optional | | | URL of the token verification endpoint of the identity server | +| introspection_endpoint_auth_method | string | optional | "client_secret_basic" | | Authentication method name for token introspection | +| public_key | string | optional | | | The public key to verify the token | +| token_signing_alg_values_expected | string | optional | | | Algorithm used to sign the token | +| set_access_token_header | boolean | optional | true | | Whether to ensure the access token is set in a request header. | +| access_token_in_authorization_header | boolean | optional | false | | If set to `true`, ensure that the access token is set in the `Authorization` header, otherwise use the `X-Access-Token` header. | +| set_id_token_header | boolean | optional | true | | Whether to ensure the ID token, if available, is set in the `X-ID-Token` request header. | +| set_userinfo_header | boolean | optional | true | | Whether to ensure the UserInfo object, if available, is set in the `X-Userinfo` request header. | + +### Modes of operation +The plugin supports different modes of operation. + +1) It can be configured to just validate an access token that is expected to be present in a request header. +In this case, requests without a token or where the token is invalid are always rejected. This requires +`bearer_only` be set to `true` and that either an introspection endpoint has been configured through +`introspection_endpoint`, or that a public key has been configured through `public_key`. See the relevant +sections below. + +2) Alternatively, the plugin can also be configured to authenticate a request without a valid token against +an identity provider by going through the OIDC Authorization Code flow. The plugin then acts as an OIDC Relying Party. +In this scenario, when the requesting user has authenticated successfully, the plugin will obtain and manage +an access token and further user claims on behalf of the user in a session cookie. Subsequent requests that +contain the cookie will use the access token stored in the cookie. In this case, `bearer_only` must be set to `false`. + +The first option is typically appropriate for service-to-service communication where the requesting side can +be reasonably expected to obtain and manage a valid access token by itself. The second option is convenient +to support web browser interaction with endpoints through a human user that may still need to be authenticated +when accessing for the first time. + +The plugin can also be configured to support both scenarios by setting `bearer_only` to false, but still configuring +either an introspection endpoint or a public key. In this case, introspection of an existing token from a request +header takes precedence over the Relying Party flow. That is, if a request contains an invalid token, the request +will be rejected without redirecting to the ID provider to obtain a valid token. + +The method used to authenticate a request also affects the headers that can be enforced on the request before +sending it to upstream. The headers that can be enforced are mentioned below in each relevant section. + ### Token Introspection @@ -92,6 +125,8 @@ The following command can be used to access the new route. curl -i -X GET http://127.0.0.1:9080/get -H "Host: httpbin.org" -H "Authorization: Bearer {replace_jwt_token}" ``` +In this case, the plugin can enforce that the access token and the UserInfo object get set in respective configured request headers. + #### Introspecting with public key You can also provide the public key of the JWT token to verify the token. In case if you have provided a public key and @@ -129,6 +164,47 @@ curl http://127.0.0.1:9080/apisix/admin/routes/5 -H 'X-API-KEY: edd1c9f034335f13 }' ``` +In this case, the plugin can only enforce that the access token gets set in the configured request headers. + +#### Authentication through OIDC Relying Party flow + +When an incoming request does not contain an access token in a header, nor in an appropriate session cookie, +the plugin can act as an OIDC Relying Party and redirect to the authorization endpoint of the identity provider +to go through the OIDC Authorization Code flow; see https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. +Once the user has authenticated against the identity provider, the plugin will obtain and manage an access token +and further information from the identity provider on behalf of the user. The information is currently stored +in a session cookie that the user agent can submit on subsequent requests. The plugin will recognize the cookie +and use the information therein to avoid having to go through the flow again. + +The following command adds this mode of operation to a route. + +```bash +curl http://127.0.0.1:9080/apisix/admin/routes/5 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/get", + "plugins": { + "proxy-rewrite": { + "scheme": "https" + }, + "openid-connect": { + "client_id": "api_six_client_id", + "client_secret": "client_secret_code", + "discovery": "full_URL_of_the_discovery_endpoint", + "bearer_only": false, + "realm": "master" +} + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "httpbin.org:443": 1 + } + } +}' +``` + +In this case, the plugin can enforce that the access token, the ID token, and the UserInfo object get set in respective configured request headers. + ## Troubleshooting Check/modify the DNS settings (`conf/config.yaml) if APISIX cannot resolve/connect to the identity provider. From 4ec11c6e55ef8eefc77181b088fcf7d40964b9f0 Mon Sep 17 00:00:00 2001 From: Jens Keiner Date: Mon, 4 Jan 2021 11:34:42 +0100 Subject: [PATCH 108/108] =?UTF-8?q?Fix=20stray=20quotation=20mark.=20=20Bi?= =?UTF-8?q?tte=20geben=20Sie=20eine=20Commit-Beschreibung=20f=C3=BCr=20Ihr?= =?UTF-8?q?e=20=C3=84nderungen=20ein.=20Zeilen,?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/plugins/openid-connect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/plugins/openid-connect.md b/doc/plugins/openid-connect.md index d261254fbed2..11983161670c 100644 --- a/doc/plugins/openid-connect.md +++ b/doc/plugins/openid-connect.md @@ -207,4 +207,4 @@ In this case, the plugin can enforce that the access token, the ID token, and th ## Troubleshooting -Check/modify the DNS settings (`conf/config.yaml) if APISIX cannot resolve/connect to the identity provider. +Check/modify the DNS settings (`conf/config.yaml`) if APISIX cannot resolve/connect to the identity provider.