-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1465 from tkan145/THREESCALE-10973-fapi-baseline
THREESCALE-10973 - Support Financial-grade API (FAPI) - Baseline profile
- Loading branch information
Showing
10 changed files
with
521 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# FAPI Policy | ||
|
||
## Description | ||
|
||
The FAPI policy supports various features of the Financial-grade API (FAPI) standard. | ||
|
||
## Example configuration | ||
|
||
``` | ||
"policy_chain": [ | ||
{ "name": "apicast.policy.fapi", "configuration": {} }, | ||
{ | ||
"name": "apicast.policy.apicast" | ||
} | ||
] | ||
``` | ||
|
||
### Validate x-fapi-customer-ip-address header | ||
|
||
``` | ||
"policy_chain": [ | ||
{ | ||
"name": "apicast.policy.fapi", | ||
"configuration": { | ||
"validate_x_fapi_customer_ip_address": true | ||
} | ||
}, | ||
{ | ||
"name": "apicast.policy.apicast" | ||
} | ||
] | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"$schema": "http://apicast.io/policy-v1/schema#manifest#", | ||
"name": "The Financial-grade API (FAPI)", | ||
"summary": "Support FAPI profiles", | ||
"description": ["This policy adding support for Financial-grade API (API) profiles" | ||
], | ||
"version": "builtin", | ||
"configuration": { | ||
"type": "object", | ||
"properties": { | ||
"validate_x_fapi_customer_ip_address": { | ||
"description": "Validate x-fapi-customer-ip-address header", | ||
"type": "boolean", | ||
"default": "false" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
--- Financial-grade API (FAPI) policy | ||
|
||
local policy = require('apicast.policy') | ||
local _M = policy.new('Financial-grade API (FAPI) Policy', 'builtin') | ||
|
||
local uuid = require 'resty.jit-uuid' | ||
local ipmatcher = require "resty.ipmatcher" | ||
local fmt = string.format | ||
|
||
local new = _M.new | ||
local X_FAPI_TRANSACTION_ID_HEADER = "x-fapi-transaction-id" | ||
local X_FAPI_CUSTOMER_IP_ADDRESS = "x-fapi-customer-ip-address" | ||
|
||
local function is_valid_ip(ip) | ||
if type(ip) ~= "string" then | ||
return false | ||
end | ||
if ipmatcher.parse_ipv4(ip) then | ||
return true | ||
end | ||
|
||
return ipmatcher.parse_ipv6(ip) | ||
end | ||
|
||
local function error(status_code, msg) | ||
ngx.status = status_code | ||
ngx.header.content_type = 'application/json; charset=utf-8' | ||
ngx.print(fmt('{"error": "%s"}', msg)) | ||
ngx.exit(ngx.status) | ||
end | ||
|
||
--- Initialize FAPI policy | ||
-- @tparam[config] table config | ||
-- @field[config] validate_x_fapi_customer_ip_address Boolean | ||
function _M.new(config) | ||
local self = new(config) | ||
self.validate_customer_ip_address = config and config.validate_x_fapi_customer_ip_address | ||
return self | ||
end | ||
|
||
function _M:access() | ||
--- 6.2.1.13 | ||
-- shall not reject requests with a x-fapi-customer-ip-address header containing a valid IPv4 or IPv6 address. | ||
if self.validate_customer_ip_address then | ||
local customer_ip = ngx.req.get_headers()[X_FAPI_CUSTOMER_IP_ADDRESS] | ||
|
||
if customer_ip then | ||
-- The standard does not mention the case of having multiple IPs, but the | ||
-- x-fapi-customer-ip-address can contain multiple IPs, however I think it doesn't | ||
-- make much sense for this header to have more than one IP, so we reject the request | ||
-- if the header is a table. | ||
if not is_valid_ip(customer_ip) then | ||
ngx.log(ngx.WARN, "invalid x-fapi-customer-ip-address") | ||
return error(ngx.HTTP_FORBIDDEN, "invalid_request") | ||
end | ||
end | ||
end | ||
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 | ||
-- 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() | ||
end | ||
end | ||
ngx.header[X_FAPI_TRANSACTION_ID_HEADER] = transaction_id | ||
end | ||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
return require('fapi') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
local FAPIPolicy = require('apicast.policy.fapi') | ||
local uuid = require('resty.jit-uuid') | ||
|
||
describe('fapi_1_baseline_profile policy', function() | ||
local ngx_req_headers = {} | ||
local ngx_resp_headers = {} | ||
local context = {} | ||
before_each(function() | ||
ngx.header = {} | ||
ngx_req_headers = {} | ||
ngx_resp_headers = {} | ||
context = {} | ||
stub(ngx.req, 'get_headers', function() return ngx_req_headers end) | ||
stub(ngx.req, 'set_header', function(name, value) ngx_req_headers[name] = value end) | ||
stub(ngx.resp, 'get_headers', function() return ngx_resp_headers end) | ||
stub(ngx.resp, 'set_header', function(name, value) ngx_resp_headers[name] = value end) | ||
stub(ngx, 'print') | ||
stub(ngx, 'exit') | ||
end) | ||
|
||
describe('.new', function() | ||
it('works without configuration', function() | ||
assert(FAPIPolicy.new({})) | ||
end) | ||
end) | ||
|
||
describe('.header_filter', function() | ||
it('Use value from request', function() | ||
ngx_req_headers['x-fapi-transaction-id'] = 'abc' | ||
local transaction_id_policy = FAPIPolicy.new({}) | ||
transaction_id_policy:header_filter() | ||
assert.same('abc', ngx.header['x-fapi-transaction-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' | ||
local transaction_id_policy = FAPIPolicy.new({}) | ||
transaction_id_policy:header_filter() | ||
assert.same('abc', ngx.header['x-fapi-transaction-id']) | ||
end) | ||
|
||
it('Use x-fapi-transaction-id from upstream response', function() | ||
ngx_resp_headers['x-fapi-transaction-id'] = 'abc' | ||
local transaction_id_policy = FAPIPolicy.new({}) | ||
transaction_id_policy:header_filter() | ||
assert.same('abc', ngx.header['x-fapi-transaction-id']) | ||
end) | ||
|
||
it('generate uuid if header does not exist in both request and response', function() | ||
local transaction_id_policy = FAPIPolicy.new({}) | ||
transaction_id_policy:header_filter() | ||
assert.is_true(uuid.is_valid(ngx.header['x-fapi-transaction-id'])) | ||
end) | ||
end) | ||
|
||
describe('x-fapi-customer-ip-address', function() | ||
it('Allow request with valid IPv4', function() | ||
ngx_req_headers['x-fapi-customer-ip-address'] = '127.0.0.1' | ||
local transaction_id_policy = FAPIPolicy.new({validate_x_fapi_customer_ip_address=true}) | ||
transaction_id_policy:access() | ||
assert.stub(ngx.exit).was_not.called_with(403) | ||
end) | ||
|
||
it('Allow request with valid IPv6', function() | ||
ngx_req_headers['x-fapi-customer-ip-address'] = '2001:db8::123:12:1' | ||
local transaction_id_policy = FAPIPolicy.new({validate_x_fapi_customer_ip_address=true}) | ||
transaction_id_policy:access() | ||
assert.stub(ngx.exit).was_not.called_with(403) | ||
end) | ||
|
||
it('Reject request if header contains more than 1 IP', function() | ||
ngx_req_headers['x-fapi-customer-ip-address'] = {"2001:db8::123:12:1", "127.0.0.1"} | ||
local transaction_id_policy = FAPIPolicy.new({validate_x_fapi_customer_ip_address=true}) | ||
transaction_id_policy:access() | ||
assert.same(ngx.status, 403) | ||
assert.stub(ngx.print).was.called_with('{"error": "invalid_request"}') | ||
assert.stub(ngx.exit).was.called_with(403) | ||
end) | ||
end) | ||
end) |
Oops, something went wrong.