Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Remove opentracing support [PR #1520](https://github.com/3scale/APIcast/pull/1520) [THREESCALE-11603](https://issues.redhat.com/browse/THREESCALE-11603)
- 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)
- 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)
- 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)
- JWT Parser policy [PR #1536](https://github.com/3scale/APIcast/pull/1536) [THREESCALE-10708](https://issues.redhat.com/browse/THREESCALE-10708)

## [3.15.0] 2024-04-04

Expand Down
64 changes: 64 additions & 0 deletions gateway/src/apicast/policy/jwt_parser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# JWT Parser

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.

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.

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.

## Example usage

With `JWT Claim Check` policy

```
"policy_chain": [
{
"name": "apicast.policy.jwt_parser",
"configuration": {
"issuer_endpoint": "http://red_hat_single_sign-on/auth/realms/foo",
}
},
{
"name": "apicast.policy.jwt_claim_check",
"configuration": {
"error_message": "Invalid JWT check",
"rules": [
{
"operations": [
{"op": "==", "jwt_claim": "role", "jwt_claim_type": "plain", "value": "admin"}
],
"combine_op":"and",
"methods": ["GET"],
"resource": "/resource",
"resource_type": "plain"
}
]
}
}
]
```

With `Keycloak Role Check` policy

```
"policy_chain": [
{
"name": "apicast.policy.jwt_parser",
"configuration": {
"issuer_endpoint": "http://red_hat_single_sign-on/auth/realms/foo",
"required": true
}
},
{
"name": "apicast.policy.keycloak_role_check",
"configuration": {
"scopes": [
{
"realm_roles": [ { "name": "foo" } ],
"resource": "/confidential"
}
]
}
},
]
```
20 changes: 20 additions & 0 deletions gateway/src/apicast/policy/jwt_parser/apicast-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "http://apicast.io/policy-v1/schema#manifest#",
"name": "JWT Parser",
"summary": "Parse JWT",
"description": ["This policy parse JWT token from Authorization header"],
"version": "builtin",
"configuration": {
"type": "object",
"properties": {
"issuer_endpoint": {
"description": "URL of OpenID Provider. The format of this endpoint is determined on your OpenID Provider setup.",
"type": "string"
},
"required": {
"description": "when enabled, rejected request if no JWT token present in Authorization header",
"type": "boolean"
}
}
}
}
1 change: 1 addition & 0 deletions gateway/src/apicast/policy/jwt_parser/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return require('jwt_parser')
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- OpenID Connect Authentication policy
-- JWT Parser policy
-- It will verify JWT signature against a list of public keys
-- discovered through OIDC Discovery from the IDP.

Expand All @@ -8,7 +8,7 @@ local oidc_discovery = require('resty.oidc.discovery')
local http_authorization = require('resty.http_authorization')
local resty_url = require('resty.url')
local policy = require('apicast.policy')
local _M = policy.new('oidc_authentication', 'builtin')
local _M = policy.new('jwt_parser', 'builtin')

local tostring = tostring

Expand All @@ -23,7 +23,7 @@ local function valid_issuer_endpoint(endpoint)
end

local new = _M.new
--- Initialize a oidc_authentication
--- Initialize jwt_parser policy
-- @tparam[opt] table config Policy configuration.
function _M.new(config)
local self = new(config)
Expand All @@ -42,53 +42,51 @@ local function bearer_token()
return http_authorization.new(ngx.var.http_authorization).token
end

function _M:rewrite(context)
local access_token = bearer_token()

if access_token or self.required then
local jwt, err = self.oidc:parse(access_token)

if jwt then
context[self] = jwt
context.jwt = jwt
else
ngx.log(ngx.WARN, 'failed to parse access token ', access_token, ' err: ', err)
end
end
end

local function exit_status(status)
ngx.status = status
-- TODO: implement content negotiation to generate proper content with an error
return ngx.exit(status)
end

local function challenge_response()
local function challenge_response(status)
ngx.header.www_authenticate = 'Bearer'

return exit_status(ngx.HTTP_UNAUTHORIZED)
return exit_status(status)
end

function _M:access(context)
local jwt = context[self]
local function check_compatible(context)
local service = context.service or {}
local authentication = service.authentication_method or service.backend_version
if authentication == "oidc" or authentication == "oauth" then
ngx.log(ngx.WARN, 'jwt_parser is incompatible with OIDC authentication mode')
return false
end
return true
end

if not jwt or not jwt.token then
function _M:rewrite(context)
if not check_compatible(context) then
return
end

local access_token = bearer_token()

if not access_token then
if self.required then
return challenge_response()
else
return
return challenge_response(context.service.auth_failed_status)
end
end

local ok, err = self.oidc:verify(jwt)
if access_token then
local _, _, jwt_payload, err = self.oidc:transform_credentials({access_token=access_token})

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

return exit_status(ngx.HTTP_FORBIDDEN)
context.jwt = jwt_payload
end

return ok
end

return _M
1 change: 0 additions & 1 deletion gateway/src/apicast/policy/oidc_authentication/init.lua

This file was deleted.

Loading
Loading