Skip to content

Commit a1331e7

Browse files
authored
Merge pull request #1536 from tkan145/THREESCALE-10708-jwt-parser
[THREESCALE-10708] JWT Parser Policy
2 parents e4fbfc5 + 851808d commit a1331e7

File tree

9 files changed

+392
-151
lines changed

9 files changed

+392
-151
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
4545
- Remove opentracing support [PR #1520](https://github.com/3scale/APIcast/pull/1520) [THREESCALE-11603](https://issues.redhat.com/browse/THREESCALE-11603)
4646
- JWT signature verification, support for ES256/ES512 #1533 [PR #1533](https://github.com/3scale/APIcast/pull/1533) [THREESCALE-11474](https://issues.redhat.com/browse/THREESCALE-11474)
4747
- Add `enable_extended_context` to allow JWT Claim Check access full request context [PR #1535](https://github.com/3scale/APIcast/pull/1535) [THREESCALE-9510](https://issues.redhat.com/browse/THREESCALE-9510)
48+
- JWT signature verification, support for ES256/ES512 [PR #1533](https://github.com/3scale/APIcast/pull/1533) [THREESCALE-11474](https://issues.redhat.com/browse/THREESCALE-11474)
49+
- JWT Parser policy [PR #1536](https://github.com/3scale/APIcast/pull/1536) [THREESCALE-10708](https://issues.redhat.com/browse/THREESCALE-10708)
4850

4951
## [3.15.0] 2024-04-04
5052

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# JWT Parser
2+
3+
JWT Parser is used to parse the JSON Web Token (JWT) in the `Authorization` header and stores it in the request context that can be shared with other policies.
4+
5+
If `required` flag is set to true and no JWT token is sent, APIcast will reject the request and send HTTP ``WWW-Authenticate`` response header.
6+
7+
NOTE: Not compatible with OIDC authentication mode. When this policy is added to a service configured with OIDC authentication mode, APIcast will print a warning about the incompatibility and ignore the policy.
8+
9+
## Example usage
10+
11+
With `JWT Claim Check` policy
12+
13+
```
14+
"policy_chain": [
15+
{
16+
"name": "apicast.policy.jwt_parser",
17+
"configuration": {
18+
"issuer_endpoint": "http://red_hat_single_sign-on/auth/realms/foo",
19+
}
20+
},
21+
{
22+
"name": "apicast.policy.jwt_claim_check",
23+
"configuration": {
24+
"error_message": "Invalid JWT check",
25+
"rules": [
26+
{
27+
"operations": [
28+
{"op": "==", "jwt_claim": "role", "jwt_claim_type": "plain", "value": "admin"}
29+
],
30+
"combine_op":"and",
31+
"methods": ["GET"],
32+
"resource": "/resource",
33+
"resource_type": "plain"
34+
}
35+
]
36+
}
37+
}
38+
]
39+
```
40+
41+
With `Keycloak Role Check` policy
42+
43+
```
44+
"policy_chain": [
45+
{
46+
"name": "apicast.policy.jwt_parser",
47+
"configuration": {
48+
"issuer_endpoint": "http://red_hat_single_sign-on/auth/realms/foo",
49+
"required": true
50+
}
51+
},
52+
{
53+
"name": "apicast.policy.keycloak_role_check",
54+
"configuration": {
55+
"scopes": [
56+
{
57+
"realm_roles": [ { "name": "foo" } ],
58+
"resource": "/confidential"
59+
}
60+
]
61+
}
62+
},
63+
]
64+
```
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "http://apicast.io/policy-v1/schema#manifest#",
3+
"name": "JWT Parser",
4+
"summary": "Parse JWT",
5+
"description": ["This policy parse JWT token from Authorization header"],
6+
"version": "builtin",
7+
"configuration": {
8+
"type": "object",
9+
"properties": {
10+
"issuer_endpoint": {
11+
"description": "URL of OpenID Provider. The format of this endpoint is determined on your OpenID Provider setup.",
12+
"type": "string"
13+
},
14+
"required": {
15+
"description": "when enabled, rejected request if no JWT token present in Authorization header",
16+
"type": "boolean"
17+
}
18+
}
19+
}
20+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
return require('jwt_parser')

gateway/src/apicast/policy/oidc_authentication/oidc_authentication.lua renamed to gateway/src/apicast/policy/jwt_parser/jwt_parser.lua

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- OpenID Connect Authentication policy
1+
-- JWT Parser policy
22
-- It will verify JWT signature against a list of public keys
33
-- discovered through OIDC Discovery from the IDP.
44

@@ -8,7 +8,7 @@ local oidc_discovery = require('resty.oidc.discovery')
88
local http_authorization = require('resty.http_authorization')
99
local resty_url = require('resty.url')
1010
local policy = require('apicast.policy')
11-
local _M = policy.new('oidc_authentication', 'builtin')
11+
local _M = policy.new('jwt_parser', 'builtin')
1212

1313
local tostring = tostring
1414

@@ -23,7 +23,7 @@ local function valid_issuer_endpoint(endpoint)
2323
end
2424

2525
local new = _M.new
26-
--- Initialize a oidc_authentication
26+
--- Initialize jwt_parser policy
2727
-- @tparam[opt] table config Policy configuration.
2828
function _M.new(config)
2929
local self = new(config)
@@ -42,53 +42,51 @@ local function bearer_token()
4242
return http_authorization.new(ngx.var.http_authorization).token
4343
end
4444

45-
function _M:rewrite(context)
46-
local access_token = bearer_token()
47-
48-
if access_token or self.required then
49-
local jwt, err = self.oidc:parse(access_token)
50-
51-
if jwt then
52-
context[self] = jwt
53-
context.jwt = jwt
54-
else
55-
ngx.log(ngx.WARN, 'failed to parse access token ', access_token, ' err: ', err)
56-
end
57-
end
58-
end
59-
6045
local function exit_status(status)
6146
ngx.status = status
6247
-- TODO: implement content negotiation to generate proper content with an error
6348
return ngx.exit(status)
6449
end
6550

66-
local function challenge_response()
51+
local function challenge_response(status)
6752
ngx.header.www_authenticate = 'Bearer'
6853

69-
return exit_status(ngx.HTTP_UNAUTHORIZED)
54+
return exit_status(status)
7055
end
7156

72-
function _M:access(context)
73-
local jwt = context[self]
57+
local function check_compatible(context)
58+
local service = context.service or {}
59+
local authentication = service.authentication_method or service.backend_version
60+
if authentication == "oidc" or authentication == "oauth" then
61+
ngx.log(ngx.WARN, 'jwt_parser is incompatible with OIDC authentication mode')
62+
return false
63+
end
64+
return true
65+
end
7466

75-
if not jwt or not jwt.token then
67+
function _M:rewrite(context)
68+
if not check_compatible(context) then
69+
return
70+
end
71+
72+
local access_token = bearer_token()
73+
74+
if not access_token then
7675
if self.required then
77-
return challenge_response()
78-
else
79-
return
76+
return challenge_response(context.service.auth_failed_status)
8077
end
8178
end
8279

83-
local ok, err = self.oidc:verify(jwt)
80+
if access_token then
81+
local _, _, jwt_payload, err = self.oidc:transform_credentials({access_token=access_token})
8482

85-
if not ok then
86-
ngx.log(ngx.INFO, 'JWT verification error: ', err, ' token: ', tostring(jwt))
83+
if err then
84+
ngx.log(ngx.WARN, 'failed to parse access token ', access_token, ' err: ', err)
85+
return exit_status(context.service.auth_failed_status)
86+
end
8787

88-
return exit_status(ngx.HTTP_FORBIDDEN)
88+
context.jwt = jwt_payload
8989
end
90-
91-
return ok
9290
end
9391

9492
return _M

gateway/src/apicast/policy/oidc_authentication/init.lua

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)