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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Fixed
- Correct FAPI header to `x-fapi-interaction-id` [PR #1557](https://github.com/3scale/APIcast/pull/1557) [THREESCALE-11957](https://issues.redhat.com/browse/THREESCALE-11957)

### Added
- Update APIcast schema manifest [PR #1550](https://github.com/3scale/APIcast/pull/1550)
- Update luarocks to v3.12.0 [PR #1555](https://github.com/3scale/APIcast/pull/1555)

### Removed

## [3.16.0] 2025-05-19

### Fixed
Expand Down
29 changes: 27 additions & 2 deletions gateway/src/apicast/policy/fapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

The FAPI policy supports various features of the Financial-grade API (FAPI) standard.

* FAPI 1.0 Baseline Profile
* FAPI 1.0 Advance Profile
* [FAPI 1.0 Baseline Profile](https://openid.net/specs/openid-financial-api-part-1-1_0.html)
* [FAPI 1.0 Advance Profile](https://openid.net/specs/openid-financial-api-part-2-1_0.html)

## Example configuration

FAPI policy set the response header `x-fapi-interaction-id` to the value received from the corresponding FAPI client request header or to a RFC4122 UUID value if the request header was not provided.

```
"policy_chain": [
{ "name": "apicast.policy.fapi", "configuration": {} },
Expand All @@ -17,8 +19,24 @@ The FAPI policy supports various features of the Financial-grade API (FAPI) stan
}
]
```
### Log the value of x-fapi-interaction-id header

```
"policy_chain": [
{ "name": "apicast.policy.fapi", "configuration": {} },
{
"name": "apicast.policy.logging",
"configuration": {
"enable_access_logs": false,
"custom_logging": "[{{time_local}}] {{host}}:{{server_port}} {{remote_addr}}:{{remote_port}} x-fapi-interaction-id: {{resp.headers.x-fapi-interaction-id}} \"{{request}}\" {{status}} {{body_bytes_sent}} ({{request_time}}) {{post_action_impact}} ",
}
}
{ "name": "apicast.policy.apicast" }
]
```

### Validate x-fapi-customer-ip-address header
Validate requests with a x-fapi-customer-ip-address header containing a valid IPv4 or IPv6 address

```
"policy_chain": [
Expand All @@ -36,6 +54,13 @@ The FAPI policy supports various features of the Financial-grade API (FAPI) stan

### Validate certificate-bound access tokens

Certificate-bound access tokens, as defined in [RFC 8705](https://datatracker.ietf.org/doc/html/rfc8705), enhance security by linking tokens to clients, thereby verifying the sender's authorization to access protected resources.

You'll need to:
* Configure an Identity Provider (IdP) such as Keycloak with mTLS and X.509 client certificate authentication.
* Configure the gateway to handle mTLS client certificate authentication.
* Enable `validate_oauth2_certificate_bound_access_token` in the FAPI plugin.

```
"policy_chain": [
{
Expand Down
14 changes: 7 additions & 7 deletions gateway/src/apicast/policy/fapi/fapi.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ local b64 = require('ngx.base64')
local fmt = string.format

local new = _M.new
local X_FAPI_TRANSACTION_ID_HEADER = "x-fapi-transaction-id"
local X_FAPI_INTERACTION_ID_HEADER = "x-fapi-interaction-id"
local X_FAPI_CUSTOMER_IP_ADDRESS = "x-fapi-customer-ip-address"

-- The "x5t#S256" (X.509 Certificate SHA-256 Thumbprint) Header
Expand Down Expand Up @@ -98,15 +98,15 @@ end
function _M:header_filter()
--- 6.2.1.11
-- shall set the response header x-fapi-interaction-id to the value received from the corresponding FAPI client request header or to a RFC4122 UUID value if the request header was not provided to track the interaction
local transaction_id = ngx.req.get_headers()[X_FAPI_TRANSACTION_ID_HEADER]
if not transaction_id or transaction_id == "" then
local interaction_id = ngx.req.get_headers()[X_FAPI_INTERACTION_ID_HEADER]
if not interaction_id or interaction_id == "" then
-- Nothing found, generate one
transaction_id = ngx.resp.get_headers()[X_FAPI_TRANSACTION_ID_HEADER]
if not transaction_id or transaction_id == "" then
transaction_id = uuid.generate_v4()
interaction_id = ngx.resp.get_headers()[X_FAPI_INTERACTION_ID_HEADER]
if not interaction_id or interaction_id == "" then
interaction_id = uuid.generate_v4()
end
end
ngx.header[X_FAPI_TRANSACTION_ID_HEADER] = transaction_id
ngx.header[X_FAPI_INTERACTION_ID_HEADER] = interaction_id
end

return _M
21 changes: 10 additions & 11 deletions spec/policy/fapi/fapi_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ local clientCert = assert(fixture('CA', 'client.crt'))
local header_parameter = 'x5t#S256'

local function jwt_cnf()
local cnf = b64.encode_base64url(X509.new(clientCert):digest('SHA256'))
return { [header_parameter] = b64.encode_base64url(X509.new(clientCert):digest('SHA256')) }
end

Expand Down Expand Up @@ -36,31 +35,31 @@ describe('fapi_1_baseline_profile policy', function()

describe('.header_filter', function()
it('Use value from request', function()
ngx_req_headers['x-fapi-transaction-id'] = 'abc'
ngx_req_headers['x-fapi-interaction-id'] = 'abc'
local fapi_policy = FAPIPolicy.new({})
fapi_policy:header_filter()
assert.same('abc', ngx.header['x-fapi-transaction-id'])
assert.same('abc', ngx.header['x-fapi-interaction-id'])
end)

it('Only use x-fapi-transaction-id from request if the header also exist in response from upstream', function()
ngx_req_headers['x-fapi-transaction-id'] = 'abc'
ngx_resp_headers['x-fapi-transaction-id'] = 'bdf'
it('Only use x-fapi-interaction-id from request if the header also exist in response from upstream', function()
ngx_req_headers['x-fapi-interaction-id'] = 'abc'
ngx_resp_headers['x-fapi-interaction-id'] = 'bdf'
local fapi_policy = FAPIPolicy.new({})
fapi_policy:header_filter()
assert.same('abc', ngx.header['x-fapi-transaction-id'])
assert.same('abc', ngx.header['x-fapi-interaction-id'])
end)

it('Use x-fapi-transaction-id from upstream response', function()
ngx_resp_headers['x-fapi-transaction-id'] = 'abc'
it('Use x-fapi-interaction-id from upstream response', function()
ngx_resp_headers['x-fapi-interaction-id'] = 'abc'
local fapi_policy = FAPIPolicy.new({})
fapi_policy:header_filter()
assert.same('abc', ngx.header['x-fapi-transaction-id'])
assert.same('abc', ngx.header['x-fapi-interaction-id'])
end)

it('generate uuid if header does not exist in both request and response', function()
local fapi_policy = FAPIPolicy.new({})
fapi_policy:header_filter()
assert.is_true(uuid.is_valid(ngx.header['x-fapi-transaction-id']))
assert.is_true(uuid.is_valid(ngx.header['x-fapi-interaction-id']))
end)
end)

Expand Down
22 changes: 11 additions & 11 deletions t/apicast-policy-fapi.t
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ run_tests();

__DATA__

=== TEST 1: Enables fapi policy inject x-fapi-transaction-id header to the response
=== TEST 1: Enables fapi policy inject x-fapi-interaction-id header to the response
--- configuration
{
"services": [
Expand Down Expand Up @@ -62,9 +62,9 @@ __DATA__
}
}
--- more_headers
x-fapi-transaction-id: abc
x-fapi-interaction-id: abc
--- response_headers
x-fapi-transaction-id: abc
x-fapi-interaction-id: abc
--- request
GET /?user_key=value
--- error_code: 200
Expand All @@ -73,7 +73,7 @@ GET /?user_key=value



=== TEST 2: When x-fapi-transaction-id exist in both request and response headers, always use
=== TEST 2: When x-fapi-interaction-id exist in both request and response headers, always use
value from request
--- configuration
{
Expand Down Expand Up @@ -109,23 +109,23 @@ value from request
--- upstream
location / {
content_by_lua_block {
ngx.header['x-fapi-transaction-id'] = "blah"
ngx.header['x-fapi-interaction-id'] = "blah"
ngx.exit(200)
}
}
--- more_headers
x-fapi-transaction-id: abc
x-fapi-interaction-id: abc
--- request
GET /?user_key=value
--- response_headers
x-fapi-transaction-id: abc
x-fapi-interaction-id: abc
--- error_code: 200
--- no_error_log
[error]



=== TEST 3: Use x-fapi-transaction-id header from upstream response
=== TEST 3: Use x-fapi-interaction-id header from upstream response
--- configuration
{
"services": [
Expand Down Expand Up @@ -160,14 +160,14 @@ x-fapi-transaction-id: abc
--- upstream
location / {
content_by_lua_block {
ngx.header['x-fapi-transaction-id'] = "blah"
ngx.header['x-fapi-interaction-id'] = "blah"
ngx.exit(200)
}
}
--- request
GET /?user_key=value
--- response_headers
x-fapi-transaction-id: blah
x-fapi-interaction-id: blah
--- error_code: 200
--- no_error_log
[error]
Expand Down Expand Up @@ -215,7 +215,7 @@ x-fapi-transaction-id: blah
--- request
GET /?user_key=value
--- response_headers_like
x-fapi-transaction-id: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
x-fapi-interaction-id: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
--- error_code: 200
--- no_error_log
[error]
Expand Down
Loading