From 2a4da3818bd5bc67f3f387a28eddaf15a427e6d6 Mon Sep 17 00:00:00 2001 From: An Tran Date: Thu, 9 Oct 2025 12:56:01 +1000 Subject: [PATCH 1/4] [fapi] use x-fapi-interaction-id header --- gateway/src/apicast/policy/fapi/fapi.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gateway/src/apicast/policy/fapi/fapi.lua b/gateway/src/apicast/policy/fapi/fapi.lua index fd597816f..52c17c778 100644 --- a/gateway/src/apicast/policy/fapi/fapi.lua +++ b/gateway/src/apicast/policy/fapi/fapi.lua @@ -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 @@ -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] + local transaction_id = ngx.req.get_headers()[X_FAPI_INTERACTION_ID_HEADER] if not transaction_id or transaction_id == "" then -- Nothing found, generate one - transaction_id = ngx.resp.get_headers()[X_FAPI_TRANSACTION_ID_HEADER] + transaction_id = ngx.resp.get_headers()[X_FAPI_INTERACTION_ID_HEADER] if not transaction_id or transaction_id == "" then transaction_id = uuid.generate_v4() end end - ngx.header[X_FAPI_TRANSACTION_ID_HEADER] = transaction_id + ngx.header[X_FAPI_INTERACTION_ID_HEADER] = transaction_id end return _M From 9c0ab40ded43dcb35a70594aca80a3f486e8308c Mon Sep 17 00:00:00 2001 From: An Tran Date: Thu, 9 Oct 2025 13:16:35 +1000 Subject: [PATCH 2/4] [fapi] improve documentation --- CHANGELOG.md | 9 +++++++ gateway/src/apicast/policy/fapi/README.md | 29 +++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 765cd61aa..9666c1b26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/gateway/src/apicast/policy/fapi/README.md b/gateway/src/apicast/policy/fapi/README.md index a2fe27807..783dd54d2 100644 --- a/gateway/src/apicast/policy/fapi/README.md +++ b/gateway/src/apicast/policy/fapi/README.md @@ -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": {} }, @@ -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": [ @@ -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 configured 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": [ { From 07fcd6de2a8c93444e6487e755e7b7d477c47ad4 Mon Sep 17 00:00:00 2001 From: An Tran Date: Thu, 9 Oct 2025 18:30:50 +1000 Subject: [PATCH 3/4] [fapi] update tests to reflect recent change --- spec/policy/fapi/fapi_spec.lua | 21 ++++++++++----------- t/apicast-policy-fapi.t | 22 +++++++++++----------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/spec/policy/fapi/fapi_spec.lua b/spec/policy/fapi/fapi_spec.lua index e65ad4bfb..124e990e3 100644 --- a/spec/policy/fapi/fapi_spec.lua +++ b/spec/policy/fapi/fapi_spec.lua @@ -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 @@ -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) diff --git a/t/apicast-policy-fapi.t b/t/apicast-policy-fapi.t index 662d9c1db..be37d282d 100644 --- a/t/apicast-policy-fapi.t +++ b/t/apicast-policy-fapi.t @@ -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": [ @@ -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 @@ -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 { @@ -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": [ @@ -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] @@ -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] From 7647f3b1df85cc41cabbabc02409f57b99ad9d9b Mon Sep 17 00:00:00 2001 From: An Tran Date: Thu, 9 Oct 2025 19:16:06 +1000 Subject: [PATCH 4/4] [fapi] address PR review feedback --- gateway/src/apicast/policy/fapi/README.md | 4 ++-- gateway/src/apicast/policy/fapi/fapi.lua | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gateway/src/apicast/policy/fapi/README.md b/gateway/src/apicast/policy/fapi/README.md index 783dd54d2..1bda34e03 100644 --- a/gateway/src/apicast/policy/fapi/README.md +++ b/gateway/src/apicast/policy/fapi/README.md @@ -54,10 +54,10 @@ Validate requests with a x-fapi-customer-ip-address header containing a valid IP ### 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. +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 configured with mTLS and X.509 client certificate authentication. +* 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. diff --git a/gateway/src/apicast/policy/fapi/fapi.lua b/gateway/src/apicast/policy/fapi/fapi.lua index 52c17c778..e6dba30df 100644 --- a/gateway/src/apicast/policy/fapi/fapi.lua +++ b/gateway/src/apicast/policy/fapi/fapi.lua @@ -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_INTERACTION_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_INTERACTION_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_INTERACTION_ID_HEADER] = transaction_id + ngx.header[X_FAPI_INTERACTION_ID_HEADER] = interaction_id end return _M