diff --git a/CHANGELOG.md b/CHANGELOG.md index 284daa221..65a4d7b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `THREESCALE_PORTAL_ENDPOINT` and `THREESCALE_CONFIG_FILE` are not required anymore [PR #702](https://github.com/3scale/apicast/pull/702) - The `scope` of the Rate Limit policy is `service` by default [PR #704](https://github.com/3scale/apicast/pull/704) +- Decoded JWTs are now exposed in the policies context by the APIcast policy [PR #718](https://github.com/3scale/apicast/pull/718) ## [3.2.0-rc2] - 2018-05-11 diff --git a/gateway/src/apicast/oauth/oidc.lua b/gateway/src/apicast/oauth/oidc.lua index 6f8242115..2f8f37695 100644 --- a/gateway/src/apicast/oauth/oidc.lua +++ b/gateway/src/apicast/oauth/oidc.lua @@ -128,7 +128,7 @@ function _M:transform_credentials(credentials) if ngx.config.debug then ngx.log(ngx.DEBUG, 'JWT object: ', require('inspect')(jwt_obj)) end - return nil, nil, jwt_obj and jwt_obj.reason or err + return nil, nil, nil, jwt_obj and jwt_obj.reason or err end local payload = jwt_obj.payload @@ -149,7 +149,7 @@ function _M:transform_credentials(credentials) -- OAuth2 credentials for OIDC -- @field app_id Client id -- @table credentials_oauth - return { app_id = app_id }, ttl + return { app_id = app_id }, ttl, payload end diff --git a/gateway/src/apicast/proxy.lua b/gateway/src/apicast/proxy.lua index 5813751ec..f52096513 100644 --- a/gateway/src/apicast/proxy.lua +++ b/gateway/src/apicast/proxy.lua @@ -267,7 +267,8 @@ function _M:rewrite(service, context) local ttl if self.oauth then - credentials, ttl, err = self.oauth:transform_credentials(credentials) + local jwt_payload + credentials, ttl, jwt_payload, err = self.oauth:transform_credentials(credentials) if err then ngx.log(ngx.DEBUG, 'oauth failed with ', err) @@ -275,6 +276,7 @@ function _M:rewrite(service, context) end ctx.credentials = credentials ctx.ttl = ttl + context.jwt = jwt_payload end context.credentials = ctx.credentials diff --git a/spec/oauth/oidc_spec.lua b/spec/oauth/oidc_spec.lua index 8162a6ebb..f420907df 100644 --- a/spec/oauth/oidc_spec.lua +++ b/spec/oauth/oidc_spec.lua @@ -31,7 +31,7 @@ describe('OIDC', function() }, }) - local credentials, ttl, err = oidc:transform_credentials({ access_token = access_token }) + local credentials, ttl, _, err = oidc:transform_credentials({ access_token = access_token }) assert(credentials, err) @@ -53,7 +53,7 @@ describe('OIDC', function() local stubbed for _=1, 10 do - local credentials, _, err = oidc:transform_credentials({ access_token = access_token }) + local credentials, _, _, err = oidc:transform_credentials({ access_token = access_token }) if not stubbed then stubbed = stub(jwt, 'verify_jwt_obj', function(_, jwt_obj, _) return jwt_obj end) end @@ -75,7 +75,7 @@ describe('OIDC', function() }, }) - local credentials, _, err = oidc:transform_credentials({ access_token = access_token }) + local credentials, _, _, err = oidc:transform_credentials({ access_token = access_token }) assert(credentials, err) end) @@ -93,7 +93,7 @@ describe('OIDC', function() }, }) - local credentials, _, err = oidc:transform_credentials({ access_token = access_token }) + local credentials, _, _, err = oidc:transform_credentials({ access_token = access_token }) assert.falsy(credentials) assert.truthy(err) @@ -113,7 +113,7 @@ describe('OIDC', function() jwt_validators.set_system_clock(function() return 0 end) - local credentials, _, err = oidc:transform_credentials({ access_token = access_token }) + local credentials, _, _, err = oidc:transform_credentials({ access_token = access_token }) assert(credentials, err) jwt_validators.set_system_clock(function() return 1 end) @@ -130,7 +130,7 @@ describe('OIDC', function() payload = { }, }) - local credentials, _, err = oidc:transform_credentials({ access_token = access_token }) + local credentials, _, _, err = oidc:transform_credentials({ access_token = access_token }) assert.match('invalid alg', err, nil, true) assert.falsy(credentials, err) @@ -150,7 +150,7 @@ describe('OIDC', function() }, }) - local credentials, _, err = oidc:transform_credentials({ access_token = access_token }) + local credentials, _, _, err = oidc:transform_credentials({ access_token = access_token }) assert.same("Claim 'typ' ('Not-Bearer') returned failure", err) assert.falsy(credentials, err) end) @@ -169,7 +169,7 @@ describe('OIDC', function() }, }) - local credentials, _, err = oidc:transform_credentials({ access_token = access_token }) + local credentials, _, _, err = oidc:transform_credentials({ access_token = access_token }) assert(credentials, err) end) end) diff --git a/t/apicast-policy-headers.t b/t/apicast-policy-headers.t index 634417a3f..8c4983c9c 100644 --- a/t/apicast-policy-headers.t +++ b/t/apicast-policy-headers.t @@ -1,6 +1,10 @@ use lib 't'; use Test::APIcast::Blackbox 'no_plan'; +use Cwd qw(abs_path); + +our $rsa = `cat t/fixtures/rsa.pem`; + run_tests(); __DATA__ @@ -518,3 +522,86 @@ yay, api backend --- error_code: 200 --- no_error_log [error] + +=== TEST 9: templating with jwt token information +This tests that the headers policy can send headers with jwt information. +The APIcast policy stores the jwt in the policies context, so the headers +policy has access to it. +Notice that in the configuration, oidc.config.public_key is the one in +"/fixtures/rsa.pub". +--- backend + location /transactions/oauth_authrep.xml { + content_by_lua_block { + ngx.exit(200) + } + } +--- configuration +{ + "oidc": [ + { + "issuer": "https://example.com/auth/realms/apicast", + "config": { + "public_key": "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALClz96cDQ965ENYMfZzG+Acu25lpx2KNpAALBQ+catCA59us7+uLY5rjQR6SOgZpCz5PJiKNAdRPDJMXSmXqM0CAwEAAQ==", + "openid": { "id_token_signing_alg_values_supported": [ "RS256" ] } + } + } + ], + "services": [ + { + "id": 42, + "backend_version": "oauth", + "backend_authentication_type": "service_token", + "backend_authentication_value": "token-value", + "proxy": { + "authentication_method": "oidc", + "oidc_issuer_endpoint": "https://example.com/auth/realms/apicast", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ], + "policy_chain": [ + { "name": "apicast.policy.apicast" }, + { + "name": "apicast.policy.headers", + "configuration": + { + "request": + [ + { + "op": "set", + "header": "Token-aud", + "value": "{{ jwt.aud }}", + "value_type": "liquid" + } + ] + } + } + ] + } + } + ] +} +--- upstream + location / { + content_by_lua_block { + local assert = require('luassert') + assert.same("the_token_audience", ngx.req.get_headers()['Token-aud']) + ngx.say('yay, api backend'); + } + } +--- request +GET / +--- more_headers eval +use Crypt::JWT qw(encode_jwt); +my $jwt = encode_jwt(payload => { + aud => 'the_token_audience', + nbf => 0, + iss => 'https://example.com/auth/realms/apicast', + exp => time + 3600 }, key => \$::rsa, alg => 'RS256'); +"Authorization: Bearer $jwt" +--- error_code: 200 +--- response_body +yay, api backend +--- no_error_log +[error] +oauth failed with