diff --git a/CHANGELOG.md b/CHANGELOG.md index 6527cdde2..e87a0be45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Extended variables in Liquid template operations [PR #1081](https://github.com/3scale/APIcast/pull/1081) +- Introduce possibility of specifying policy order restrictions in their schemas. APIcast now shows a warning when those restrictions are not respected [#1088](https://github.com/3scale/APIcast/pull/1088), [THREESCALE-2896](https://issues.jboss.org/browse/THREESCALE-2896) ## [3.6.0-beta1] - 2019-06-18 diff --git a/gateway/src/apicast/configuration.lua b/gateway/src/apicast/configuration.lua index af4eb0898..5f399bf09 100644 --- a/gateway/src/apicast/configuration.lua +++ b/gateway/src/apicast/configuration.lua @@ -77,7 +77,9 @@ local function build_policy_chain(policies) end end - return policy_chain.new(chain) + local built_chain = policy_chain.new(chain) + built_chain:check_order() + return built_chain end function _M.parse_service(service) diff --git a/gateway/src/apicast/policy/3scale_batcher/3scale_batcher.lua b/gateway/src/apicast/policy/3scale_batcher/3scale_batcher.lua index d69ffe233..0c34390c6 100644 --- a/gateway/src/apicast/policy/3scale_batcher/3scale_batcher.lua +++ b/gateway/src/apicast/policy/3scale_batcher/3scale_batcher.lua @@ -17,7 +17,7 @@ local ipairs = ipairs local default_auths_ttl = 10 local default_batch_reports_seconds = 10 -local _M, mt = policy.new('3scale Batcher policy') +local _M, mt = policy.new('3scale Batcher policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/3scale_batcher/apicast-policy.json b/gateway/src/apicast/policy/3scale_batcher/apicast-policy.json index 28f7a8d3d..48baf0488 100644 --- a/gateway/src/apicast/policy/3scale_batcher/apicast-policy.json +++ b/gateway/src/apicast/policy/3scale_batcher/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "3scale batcher", "summary": "Caches auths from 3scale backend and batches reports.", "description": diff --git a/gateway/src/apicast/policy/3scale_referrer/3scale_referrer.lua b/gateway/src/apicast/policy/3scale_referrer/3scale_referrer.lua index af26c9b12..29c242568 100644 --- a/gateway/src/apicast/policy/3scale_referrer/3scale_referrer.lua +++ b/gateway/src/apicast/policy/3scale_referrer/3scale_referrer.lua @@ -1,5 +1,5 @@ local policy = require('apicast.policy') -local _M = policy.new('3scale Referrer policy') +local _M = policy.new('3scale Referrer policy', 'builtin') function _M.rewrite(_, context) local referrer = ngx.var.http_referer diff --git a/gateway/src/apicast/policy/3scale_referrer/apicast-policy.json b/gateway/src/apicast/policy/3scale_referrer/apicast-policy.json index 0f70775f3..ca5288176 100644 --- a/gateway/src/apicast/policy/3scale_referrer/apicast-policy.json +++ b/gateway/src/apicast/policy/3scale_referrer/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "3scale Referrer", "summary": "Sends the 'Referer' to 3scale backend so it can be validated.", "description": "Sends the 'Referer' to 3scale backend for validation.", diff --git a/gateway/src/apicast/policy/apicast/apicast-policy.json b/gateway/src/apicast/policy/apicast/apicast-policy.json index e95f68ff7..fa6e76f43 100644 --- a/gateway/src/apicast/policy/apicast/apicast-policy.json +++ b/gateway/src/apicast/policy/apicast/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "3scale APIcast", "summary": "Main functionality of APIcast to work with the 3scale API manager.", "description": diff --git a/gateway/src/apicast/policy/apicast/apicast.lua b/gateway/src/apicast/policy/apicast/apicast.lua index 88cb19815..da48e6e5c 100644 --- a/gateway/src/apicast/policy/apicast/apicast.lua +++ b/gateway/src/apicast/policy/apicast/apicast.lua @@ -6,7 +6,7 @@ local assert = assert local user_agent = require('apicast.user_agent') -local _M = require('apicast.policy').new('APIcast', require('apicast.version')) +local _M = require('apicast.policy').new('APIcast', 'builtin') local mt = { __index = _M diff --git a/gateway/src/apicast/policy/caching/apicast-policy.json b/gateway/src/apicast/policy/caching/apicast-policy.json index c44bbef1a..4a2790969 100644 --- a/gateway/src/apicast/policy/caching/apicast-policy.json +++ b/gateway/src/apicast/policy/caching/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "3scale auth caching", "summary": "Controls how to cache authorizations returned by the 3scale backend.", "description": diff --git a/gateway/src/apicast/policy/caching/caching.lua b/gateway/src/apicast/policy/caching/caching.lua index 92c9a7f2f..564428b05 100644 --- a/gateway/src/apicast/policy/caching/caching.lua +++ b/gateway/src/apicast/policy/caching/caching.lua @@ -18,7 +18,7 @@ -- - None: disables caching. local policy = require('apicast.policy') -local _M = policy.new('Caching policy') +local _M = policy.new('Caching policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/conditional/apicast-policy.json b/gateway/src/apicast/policy/conditional/apicast-policy.json index 453cea12c..a5ffd0cb0 100644 --- a/gateway/src/apicast/policy/conditional/apicast-policy.json +++ b/gateway/src/apicast/policy/conditional/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Conditional policy [Tech preview]", "summary": "Executes a policy chain conditionally.", "description": [ diff --git a/gateway/src/apicast/policy/conditional/conditional.lua b/gateway/src/apicast/policy/conditional/conditional.lua index f17f11798..731b88247 100644 --- a/gateway/src/apicast/policy/conditional/conditional.lua +++ b/gateway/src/apicast/policy/conditional/conditional.lua @@ -8,7 +8,7 @@ local Condition = require('apicast.conditions.condition') local Operation = require('apicast.conditions.operation') local ngx_variable = require('apicast.policy.ngx_variable') -local _M = policy.new('Conditional policy') +local _M = policy.new('Conditional policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/cors/apicast-policy.json b/gateway/src/apicast/policy/cors/apicast-policy.json index 7083fa11f..0a360ba48 100644 --- a/gateway/src/apicast/policy/cors/apicast-policy.json +++ b/gateway/src/apicast/policy/cors/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "CORS", "summary": "Enables CORS (Cross Origin Resource Sharing) request handling.", "description": @@ -9,6 +9,14 @@ "When combined with the APIcast policy, the CORS policy should be ", "placed before it in the chain."], "version": "builtin", + "order": { + "before": [ + { + "name": "apicast", + "version": "builtin" + } + ] + }, "configuration": { "type": "object", "properties": { diff --git a/gateway/src/apicast/policy/cors/cors.lua b/gateway/src/apicast/policy/cors/cors.lua index b12ccf018..b183c9591 100644 --- a/gateway/src/apicast/policy/cors/cors.lua +++ b/gateway/src/apicast/policy/cors/cors.lua @@ -14,7 +14,7 @@ -- 'example.com' too. local policy = require('apicast.policy') -local _M = policy.new('CORS Policy') +local _M = policy.new('CORS Policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/default_credentials/apicast-policy.json b/gateway/src/apicast/policy/default_credentials/apicast-policy.json index 4fce3b164..baaa06cc7 100644 --- a/gateway/src/apicast/policy/default_credentials/apicast-policy.json +++ b/gateway/src/apicast/policy/default_credentials/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Anonymous access", "summary": "Provides default credentials for unauthenticated requests.", "description": @@ -11,6 +11,14 @@ "You need to configure a user_key; or, the combination of app_id + app_key. \n", "Note: this policy should be placed before the APIcast policy in the chain."], "version": "builtin", + "order": { + "before": [ + { + "name": "apicast", + "version": "builtin" + } + ] + }, "configuration": { "type":"object", "properties":{ diff --git a/gateway/src/apicast/policy/default_credentials/default_credentials.lua b/gateway/src/apicast/policy/default_credentials/default_credentials.lua index 745dbb3c2..d72f977f9 100644 --- a/gateway/src/apicast/policy/default_credentials/default_credentials.lua +++ b/gateway/src/apicast/policy/default_credentials/default_credentials.lua @@ -3,7 +3,7 @@ local tostring = tostring local policy = require('apicast.policy') -local _M = policy.new('Default credentials policy') +local _M = policy.new('Default credentials policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/echo/apicast-policy.json b/gateway/src/apicast/policy/echo/apicast-policy.json index ccb0385e4..411d17f8c 100644 --- a/gateway/src/apicast/policy/echo/apicast-policy.json +++ b/gateway/src/apicast/policy/echo/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Echo", "summary": "Prints the request back to the client and optionally sets a status code.", "description": diff --git a/gateway/src/apicast/policy/echo/echo.lua b/gateway/src/apicast/policy/echo/echo.lua index 6bb6b0d29..4ac820387 100644 --- a/gateway/src/apicast/policy/echo/echo.lua +++ b/gateway/src/apicast/policy/echo/echo.lua @@ -3,7 +3,7 @@ -- Also can interrupt the execution and skip the current phase or -- the whole processing of the request. -local _M = require('apicast.policy').new('Echo Policy') +local _M = require('apicast.policy').new('Echo Policy', 'builtin') local cjson = require('cjson') local tonumber = tonumber diff --git a/gateway/src/apicast/policy/headers/apicast-policy.json b/gateway/src/apicast/policy/headers/apicast-policy.json index 7c07fa925..edcb1a30e 100644 --- a/gateway/src/apicast/policy/headers/apicast-policy.json +++ b/gateway/src/apicast/policy/headers/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Header modification", "summary": "Allows to include custom headers.", "description": diff --git a/gateway/src/apicast/policy/headers/headers.lua b/gateway/src/apicast/policy/headers/headers.lua index e22b20687..f5553c223 100644 --- a/gateway/src/apicast/policy/headers/headers.lua +++ b/gateway/src/apicast/policy/headers/headers.lua @@ -14,7 +14,7 @@ local TemplateString = require 'apicast.template_string' local default_value_type = 'plain' local policy = require('apicast.policy') -local _M = policy.new('Headers policy') +local _M = policy.new('Headers policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/ip_check/apicast-policy.json b/gateway/src/apicast/policy/ip_check/apicast-policy.json index d7a1d9832..f060a187b 100644 --- a/gateway/src/apicast/policy/ip_check/apicast-policy.json +++ b/gateway/src/apicast/policy/ip_check/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "IP check", "summary": "Accepts or denies a request based on the IP.", "description": [ diff --git a/gateway/src/apicast/policy/ip_check/ip_check.lua b/gateway/src/apicast/policy/ip_check/ip_check.lua index 8bcfa4e5b..41ba50e97 100644 --- a/gateway/src/apicast/policy/ip_check/ip_check.lua +++ b/gateway/src/apicast/policy/ip_check/ip_check.lua @@ -2,7 +2,7 @@ local iputils = require("resty.iputils") local ClientIP = require('apicast.policy.ip_check.client_ip') local policy = require('apicast.policy') -local _M = policy.new('IP check policy') +local _M = policy.new('IP check policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/jwt_claim_check/apicast-policy.json b/gateway/src/apicast/policy/jwt_claim_check/apicast-policy.json index 4775cc38a..f63c89716 100644 --- a/gateway/src/apicast/policy/jwt_claim_check/apicast-policy.json +++ b/gateway/src/apicast/policy/jwt_claim_check/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "JWT Claim Check", "summary": "Allow or deny traffic based on a JWT claim", "description": [ diff --git a/gateway/src/apicast/policy/jwt_claim_check/jwt_claim_check.lua b/gateway/src/apicast/policy/jwt_claim_check/jwt_claim_check.lua index ecbc945a9..683585197 100644 --- a/gateway/src/apicast/policy/jwt_claim_check/jwt_claim_check.lua +++ b/gateway/src/apicast/policy/jwt_claim_check/jwt_claim_check.lua @@ -1,5 +1,5 @@ local policy = require('apicast.policy') -local _M = policy.new('JWT check policy') +local _M = policy.new('JWT check policy', 'builtin') local Condition = require('apicast.conditions.condition') local MappingRule = require('apicast.mapping_rule') diff --git a/gateway/src/apicast/policy/keycloak_role_check/apicast-policy.json b/gateway/src/apicast/policy/keycloak_role_check/apicast-policy.json index 780b49d21..ec3840c23 100644 --- a/gateway/src/apicast/policy/keycloak_role_check/apicast-policy.json +++ b/gateway/src/apicast/policy/keycloak_role_check/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "RH-SSO/Keycloak role check", "summary": "Adds role check with Keycloak.", "description": [ diff --git a/gateway/src/apicast/policy/keycloak_role_check/keycloak_role_check.lua b/gateway/src/apicast/policy/keycloak_role_check/keycloak_role_check.lua index 946ae1cc5..681126b1b 100644 --- a/gateway/src/apicast/policy/keycloak_role_check/keycloak_role_check.lua +++ b/gateway/src/apicast/policy/keycloak_role_check/keycloak_role_check.lua @@ -50,7 +50,7 @@ -- ] local policy = require('apicast.policy') -local _M = policy.new('Keycloak Role Check Policy') +local _M = policy.new('Keycloak Role Check Policy', 'builtin') local ipairs = ipairs local MappingRule = require('apicast.mapping_rule') diff --git a/gateway/src/apicast/policy/liquid_context_debug/apicast-policy.json b/gateway/src/apicast/policy/liquid_context_debug/apicast-policy.json index ecbe8603e..f3a7c3dfe 100644 --- a/gateway/src/apicast/policy/liquid_context_debug/apicast-policy.json +++ b/gateway/src/apicast/policy/liquid_context_debug/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Liquid context debug", "summary": "Inspects the available liquid context.", "description": [ @@ -16,6 +16,22 @@ "references." ], "version": "builtin", + "order": { + "before": [ + { + "name": "apicast", + "version": "builtin" + }, + { + "name": "upstream", + "version": "builtin" + }, + { + "name": "routing", + "version": "builtin" + } + ] + }, "configuration": { "type": "object", "properties": { diff --git a/gateway/src/apicast/policy/liquid_context_debug/liquid_context_debug.lua b/gateway/src/apicast/policy/liquid_context_debug/liquid_context_debug.lua index 07cbc23aa..a2f6e42f3 100644 --- a/gateway/src/apicast/policy/liquid_context_debug/liquid_context_debug.lua +++ b/gateway/src/apicast/policy/liquid_context_debug/liquid_context_debug.lua @@ -2,7 +2,7 @@ local context_content = require('context_content') local cjson = require('cjson') local policy = require('apicast.policy') local ngx_variable = require('apicast.policy.ngx_variable') -local _M = policy.new('Liquid context debug') +local _M = policy.new('Liquid context debug', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/load_configuration/load_configuration.lua b/gateway/src/apicast/policy/load_configuration/load_configuration.lua index 0d5dbefd7..16e52e4bb 100644 --- a/gateway/src/apicast/policy/load_configuration/load_configuration.lua +++ b/gateway/src/apicast/policy/load_configuration/load_configuration.lua @@ -2,7 +2,7 @@ local _M = require('apicast.policy').new('Load Configuration') local ssl = require('ngx.ssl') local configuration_loader = require('apicast.configuration_loader').new() -local configuration_store = require('apicast.configuration_store') +local configuration_store = require('apicast.configuration_store', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/logging/apicast-policy.json b/gateway/src/apicast/policy/logging/apicast-policy.json index ccd56a87a..4ec568bb5 100644 --- a/gateway/src/apicast/policy/logging/apicast-policy.json +++ b/gateway/src/apicast/policy/logging/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Logging", "summary": "Controls logging.", "description": [ diff --git a/gateway/src/apicast/policy/logging/logging.lua b/gateway/src/apicast/policy/logging/logging.lua index 1feb7a55b..d9395169d 100644 --- a/gateway/src/apicast/policy/logging/logging.lua +++ b/gateway/src/apicast/policy/logging/logging.lua @@ -1,6 +1,6 @@ --- Logging policy -local _M = require('apicast.policy').new('Logging Policy') +local _M = require('apicast.policy').new('Logging Policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/manifest-schema.json b/gateway/src/apicast/policy/manifest-schema.json index 20a88ce5a..d99e02d40 100644 --- a/gateway/src/apicast/policy/manifest-schema.json +++ b/gateway/src/apicast/policy/manifest-schema.json @@ -1,5 +1,5 @@ { - "$id": "http://apicast.io/policy-v1/schema#manifest", + "$id": "http://apicast.io/policy-v1.1/schema#manifest", "type": "object", "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { @@ -57,6 +57,52 @@ [ "Redirect request to different upstream: ", " - based on path", "- set different Host header"] ] }, + "order": { + "$id": "/properties/order", + "type": "object", + "title": "Order restrictions of the policy", + "description": "Specifies before or after which policies the policy should be placed in the chain.", + "properties": { + "before": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ] + }, + "description": "The policy should be placed before these ones in the chain." + }, + "after": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ] + }, + "description": "The policy should be placed after these ones in the chain." + } + } + }, "version": { "$ref": "#/definitions/version" }, diff --git a/gateway/src/apicast/policy/nginx_metrics/nginx_metrics.lua b/gateway/src/apicast/policy/nginx_metrics/nginx_metrics.lua index d8d9f7bf7..c25d16b24 100644 --- a/gateway/src/apicast/policy/nginx_metrics/nginx_metrics.lua +++ b/gateway/src/apicast/policy/nginx_metrics/nginx_metrics.lua @@ -1,4 +1,4 @@ -local _M = require('apicast.policy').new('Metrics') +local _M = require('apicast.policy').new('Metrics', 'builtin') local resty_env = require('resty.env') local errlog = require('ngx.errlog') diff --git a/gateway/src/apicast/policy/oidc_authentication/oidc_authentication.lua b/gateway/src/apicast/policy/oidc_authentication/oidc_authentication.lua index 46d58aaa2..42e168e6f 100644 --- a/gateway/src/apicast/policy/oidc_authentication/oidc_authentication.lua +++ b/gateway/src/apicast/policy/oidc_authentication/oidc_authentication.lua @@ -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') +local _M = policy.new('oidc_authentication', 'builtin') local tostring = tostring diff --git a/gateway/src/apicast/policy/rate_limit/apicast-policy.json b/gateway/src/apicast/policy/rate_limit/apicast-policy.json index 17bd383ff..82d50680e 100644 --- a/gateway/src/apicast/policy/rate_limit/apicast-policy.json +++ b/gateway/src/apicast/policy/rate_limit/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Edge limiting", "summary": "Adds rate limit.", "description": ["This policy adds rate limit."], diff --git a/gateway/src/apicast/policy/retry/apicast-policy.json b/gateway/src/apicast/policy/retry/apicast-policy.json index d9a1e3469..98579c060 100644 --- a/gateway/src/apicast/policy/retry/apicast-policy.json +++ b/gateway/src/apicast/policy/retry/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Retry", "summary": "Allows to retry requests to the upstream", "description": "Allows to retry requests to the upstream", diff --git a/gateway/src/apicast/policy/retry/retry.lua b/gateway/src/apicast/policy/retry/retry.lua index 0c91651ef..031c280ce 100644 --- a/gateway/src/apicast/policy/retry/retry.lua +++ b/gateway/src/apicast/policy/retry/retry.lua @@ -2,7 +2,7 @@ local tonumber = tonumber -local _M = require('apicast.policy').new('Retry Policy') +local _M = require('apicast.policy').new('Retry Policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/rewrite_url_captures/apicast-policy.json b/gateway/src/apicast/policy/rewrite_url_captures/apicast-policy.json index 7d3e3dfa6..fdbb985d4 100644 --- a/gateway/src/apicast/policy/rewrite_url_captures/apicast-policy.json +++ b/gateway/src/apicast/policy/rewrite_url_captures/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "URL rewriting with captures", "summary": "Captures arguments in a URL and rewrites the URL using them.", "description": diff --git a/gateway/src/apicast/policy/routing/apicast-policy.json b/gateway/src/apicast/policy/routing/apicast-policy.json index acb6a77b0..89d79bf43 100644 --- a/gateway/src/apicast/policy/routing/apicast-policy.json +++ b/gateway/src/apicast/policy/routing/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Routing", "summary": "Allows to modify the upstream URL of the request.", "description": [ @@ -10,6 +10,14 @@ "placed before it in the policy chain." ], "version": "builtin", + "order": { + "before": [ + { + "name": "apicast", + "version": "builtin" + } + ] + }, "configuration": { "type": "object", "definitions": { diff --git a/gateway/src/apicast/policy/routing/routing.lua b/gateway/src/apicast/policy/routing/routing.lua index 5c5740aa4..0bb6b7341 100644 --- a/gateway/src/apicast/policy/routing/routing.lua +++ b/gateway/src/apicast/policy/routing/routing.lua @@ -7,7 +7,7 @@ local UpstreamSelector = require('upstream_selector') local Request = require('request') local Rule = require('rule') -local _M = require('apicast.policy').new('Routing policy') +local _M = require('apicast.policy').new('Routing policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/soap/apicast-policy.json b/gateway/src/apicast/policy/soap/apicast-policy.json index 02c899c45..e0d201276 100644 --- a/gateway/src/apicast/policy/soap/apicast-policy.json +++ b/gateway/src/apicast/policy/soap/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "SOAP", "summary": "Adds support for a small subset of SOAP.", "description": diff --git a/gateway/src/apicast/policy/tls_validation/apicast-policy.json b/gateway/src/apicast/policy/tls_validation/apicast-policy.json index 32b85cf91..7705ec752 100644 --- a/gateway/src/apicast/policy/tls_validation/apicast-policy.json +++ b/gateway/src/apicast/policy/tls_validation/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "TLS Client Certificate Validation", "summary": "Validate certificates provided by the client during TLS handshake (HTTPS).", "description": [ diff --git a/gateway/src/apicast/policy/upstream/apicast-policy.json b/gateway/src/apicast/policy/upstream/apicast-policy.json index 17812c5a4..986d00f32 100644 --- a/gateway/src/apicast/policy/upstream/apicast-policy.json +++ b/gateway/src/apicast/policy/upstream/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Upstream", "summary": "Allows to modify the upstream URL of the request based on its path.", "description": @@ -9,6 +9,14 @@ "When combined with the APIcast policy, the upstream policy should be ", "placed before it in the policy chain."], "version": "builtin", + "order": { + "before": [ + { + "name": "apicast", + "version": "builtin" + } + ] + }, "configuration": { "type": "object", "properties": { diff --git a/gateway/src/apicast/policy/upstream/upstream.lua b/gateway/src/apicast/policy/upstream/upstream.lua index 5a7dc1842..4dfa8d58f 100644 --- a/gateway/src/apicast/policy/upstream/upstream.lua +++ b/gateway/src/apicast/policy/upstream/upstream.lua @@ -8,7 +8,7 @@ local tab_insert = table.insert local tab_new = require('resty.core.base').new_tab local balancer = require('apicast.balancer') -local _M = require('apicast.policy').new('Upstream policy') +local _M = require('apicast.policy').new('Upstream policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/upstream_connection/apicast-policy.json b/gateway/src/apicast/policy/upstream_connection/apicast-policy.json index bc524bb50..d3320ca0b 100644 --- a/gateway/src/apicast/policy/upstream_connection/apicast-policy.json +++ b/gateway/src/apicast/policy/upstream_connection/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "Upstream connection", "summary": "Allows to configure several options for the connections to the upstream", "description": "Allows to configure several options for the connections to the upstream", diff --git a/gateway/src/apicast/policy/upstream_connection/upstream_connection.lua b/gateway/src/apicast/policy/upstream_connection/upstream_connection.lua index f40f207db..369c54539 100644 --- a/gateway/src/apicast/policy/upstream_connection/upstream_connection.lua +++ b/gateway/src/apicast/policy/upstream_connection/upstream_connection.lua @@ -4,7 +4,7 @@ local tonumber = tonumber -local _M = require('apicast.policy').new('Upstream connection policy') +local _M = require('apicast.policy').new('Upstream connection policy', 'builtin') local new = _M.new diff --git a/gateway/src/apicast/policy/url_rewriting/apicast-policy.json b/gateway/src/apicast/policy/url_rewriting/apicast-policy.json index 9573ad5fd..d1a854a47 100644 --- a/gateway/src/apicast/policy/url_rewriting/apicast-policy.json +++ b/gateway/src/apicast/policy/url_rewriting/apicast-policy.json @@ -1,5 +1,5 @@ { - "$schema": "http://apicast.io/policy-v1/schema#manifest#", + "$schema": "http://apicast.io/policy-v1.1/schema#manifest#", "name": "URL rewriting", "summary": "Allows to modify the path of a request.", "description": diff --git a/gateway/src/apicast/policy_chain.lua b/gateway/src/apicast/policy_chain.lua index d70929365..ffa9851b1 100644 --- a/gateway/src/apicast/policy_chain.lua +++ b/gateway/src/apicast/policy_chain.lua @@ -22,6 +22,8 @@ require('apicast.loader') local linked_list = require('apicast.linked_list') local policy_phases = require('apicast.policy').phases local policy_loader = require('apicast.policy_loader') +local PolicyOrderChecker = require('apicast.policy_order_checker') +local policy_manifests_loader = require('apicast.policy_manifests_loader') local _M = { @@ -183,6 +185,14 @@ function _M:add_policy(name, version, ...) end end +-- Checks if there are any policies placed in the wrong place in the chain. +-- It doesn't return anything, it prints error messages when there's a problem. +function _M:check_order(manifests) + PolicyOrderChecker.new( + manifests or policy_manifests_loader.get_all() + ):check(self) +end + local function call_chain(phase_name) return function(self, context) for i=1, #self do diff --git a/gateway/src/apicast/policy_order_checker.lua b/gateway/src/apicast/policy_order_checker.lua new file mode 100644 index 000000000..a5336344b --- /dev/null +++ b/gateway/src/apicast/policy_order_checker.lua @@ -0,0 +1,153 @@ +local setmetatable = setmetatable +local pairs = pairs +local ipairs = ipairs +local policy_loader = require 'apicast.policy_loader' +local insert = table.insert +local format = string.format + +local _M = {} + +local mt = { __index = _M } + +-- The name that appears in the schema is different from the name in an +-- instance of a policy. The name of the schema corresponds to the name of the +-- folder that contains the policy ("headers", "routing"), whereas the name in +-- an instantiated policy is "prettified" ("Headers policy", "Routing policy"). +-- +-- In a policy chain, we only have access to the "prettified" names, so in +-- order to compare them, we'll need to convert them using this function. +local function prettified_name(name_in_schema) + local policy_mod = policy_loader:pcall(name_in_schema) + return policy_mod and policy_mod._NAME +end + +local OrderRestrictions = {} + +function OrderRestrictions.new() + local self = setmetatable({}, { __index = OrderRestrictions }) + self.restrictions = {} + return self +end + +function OrderRestrictions.new_from_policy_manifests(policy_manifests) + local self = OrderRestrictions.new() + + -- Notice that the value is an array because there might be several versions + -- of a policy. + for policy_name, manifests in pairs(policy_manifests) do + for _, manifest in ipairs(manifests) do + if manifest.order then + local policy_in_manifest = { + name = prettified_name(policy_name), + version = manifest.version + } + + for _, policy_in_restriction in ipairs(manifest.order.before or {}) do + self:insert( + policy_in_manifest, + { + name = prettified_name(policy_in_restriction.name), + version = policy_in_restriction.version + } + ) + end + + for _, policy_in_restriction in ipairs(manifest.order.after or {}) do + self:insert( + { + name = prettified_name(policy_in_restriction.name), + version = policy_in_restriction.version + }, + policy_in_manifest + ) + end + end + end + end + + return self +end + +function OrderRestrictions:insert(policy_before, policy_after) + self.restrictions[policy_before.name] = self.restrictions[policy_before.name] or {} + self.restrictions[policy_before.name][policy_before.version] = + self.restrictions[policy_before.name][policy_before.version] or {} + + insert( + self.restrictions[policy_before.name][policy_before.version], + policy_after + ) +end + +function OrderRestrictions:policies_to_be_placed_after(policy_before) + return (self.restrictions[policy_before.name] and + self.restrictions[policy_before.name][policy_before.version]) or {} +end + +function _M.new(policy_manifests) + local self = setmetatable({}, mt) + self.restrictions = OrderRestrictions.new_from_policy_manifests(policy_manifests) + return self +end + +-- Returns a table with policies and their positions in the chain. +-- Example of a chain with 4 policies: +-- ['some_policy']['1.0.0'] = { 1, 2 } +-- ['some_policy']['2.0.0'] = 3 +-- ['another_policy']['builtin'] = 4 +local function positions_in_the_chain(policy_chain) + local res = {} + + for index, policy in ipairs(policy_chain) do + local policy_name = policy._NAME + local policy_version = policy._VERSION + + res[policy_name] = res[policy_name] or {} + res[policy_name][policy_version] = res[policy_name][policy_version] or {} + + insert(res[policy_name][policy_version], index) + end + + return res +end + +local function error_msg(policy_before, policy_after) + return format( + "%s (version: %s) should be placed before %s (version: %s)", + policy_before.name, policy_before.version, + policy_after.name, policy_after.version + ) +end + +-- Logs warnings when it detects that an order restriction has been violated in +-- the given policy chain. +function _M:check(policy_chain) + if not policy_chain then return end + + local positions = positions_in_the_chain(policy_chain) + + for index, policy in ipairs(policy_chain) do + local policy_name = policy._NAME + local policy_version = policy._VERSION + + local policies_to_be_placed_after = self.restrictions:policies_to_be_placed_after( + { name = policy_name, version = policy_version } + ) + + for _, policy_after in ipairs(policies_to_be_placed_after) do + local indexes_should_be_after = (positions[policy_after.name] and + positions[policy_after.name][policy_after.version]) or {} + + for _, index_after in ipairs(indexes_should_be_after) do + if index > index_after then + ngx.log( + ngx.WARN, + error_msg({ name = policy_name, version = policy_version }, policy_after) + ) + end + end + end + end +end + +return _M diff --git a/spec/policy_order_checker_spec.lua b/spec/policy_order_checker_spec.lua new file mode 100644 index 000000000..c0aff609f --- /dev/null +++ b/spec/policy_order_checker_spec.lua @@ -0,0 +1,123 @@ +local policy_manifests_loader = require 'apicast.policy_manifests_loader' +local PolicyOrderChecker = require 'apicast.policy_order_checker' +local IpCheckPolicy = require 'apicast.policy.ip_check' +local HeadersPolicy = require'apicast.policy.headers' +local CORSPolicy = require 'apicast.policy.cors' +local DefaultCredentialsPolicy = require 'apicast.policy.default_credentials' +local APIcastPolicy = require 'apicast.policy.apicast' + +describe('Policy Order Checker', function() + describe('.check', function() + -- Use the real manifests of the built-in policies + local manifests = policy_manifests_loader.get_all() + + local order_checker = PolicyOrderChecker.new(manifests) + + before_each(function() + stub(ngx, 'log') + end) + + it('does not show errors when there are no policies in the chain', function() + order_checker:check({}) + + assert.stub(ngx.log).was_not_called() + end) + + it('does not show errors when the chain is nil', function() + order_checker:check() + + assert.stub(ngx.log).was_not_called() + end) + + it('does not show errors when there are no order violations', function() + -- There are no order restrictions between the headers policy and the IP + -- check one. + local headers_policy_instance = HeadersPolicy.new({}) + local ip_check_policy_instance = IpCheckPolicy.new( + { ips = { '1.2.3.4' }, check_type = 'whitelist' } + ) + local chain = { headers_policy_instance, ip_check_policy_instance } + + order_checker:check(chain) + + assert.stub(ngx.log).was_not_called() + end) + + it('shows an error when there is a "before" order violation', function() + -- The CORS policy needs to be placed before the apicast one. + local apicast_policy_instance = APIcastPolicy.new({}) + local cors_policy_instance = CORSPolicy.new({}) + local chain = { apicast_policy_instance, cors_policy_instance } + + order_checker:check(chain) + + assert.stub(ngx.log).was_called_with( + ngx.WARN, + 'CORS Policy (version: builtin) should be placed before APIcast (version: builtin)' + ) + end) + + it('shows an error for each order violation when there are several of them', function() + -- Both the CORS policy and the default credentials one need to be placed + -- before the apicast one. + local apicast_policy_instance = APIcastPolicy.new({}) + local cors_policy_instance = CORSPolicy.new({}) + local default_creds_policy_instance = DefaultCredentialsPolicy.new( + { auth_type = 'user_key', user_key = 'uk' } + ) + local chain = { + apicast_policy_instance, + cors_policy_instance, + default_creds_policy_instance + } + + order_checker:check(chain) + + assert.stub(ngx.log).was_called(2) + + assert.stub(ngx.log).was_called_with( + ngx.WARN, + 'CORS Policy (version: builtin) should be placed before APIcast (version: builtin)' + ) + + assert.stub(ngx.log).was_called_with( + ngx.WARN, + 'Default credentials policy (version: builtin) should be placed before APIcast (version: builtin)' + ) + end) + + it('shows errors when there are several instances of a policy and one of them violates the rules', function() + -- The CORS policy needs to be placed before the apicast one. + local cors_policy_instance_1 = CORSPolicy.new({}) + local apicast_policy_instance = APIcastPolicy.new({}) + local cors_policy_instance_2 = CORSPolicy.new({}) + local chain = { + cors_policy_instance_1, + apicast_policy_instance, + cors_policy_instance_2 + } + + order_checker:check(chain) + + assert.stub(ngx.log).was_called(1) + assert.stub(ngx.log).was_called_with( + ngx.WARN, + 'CORS Policy (version: builtin) should be placed before APIcast (version: builtin)' + ) + end) + + it('does no show an error when the version does not match the one in the restriction', function() + -- The CORS policy needs to be placed before the apicast one (version + -- 'builtin'). This test instantiates it with a different version to + -- check that no errors are logged. + local apicast_policy_instance = APIcastPolicy.new({}) + apicast_policy_instance._VERSION = "0.1" + local cors_policy_instance = CORSPolicy.new({}) + local chain = { apicast_policy_instance, cors_policy_instance } + + order_checker:check(chain) + + assert.stub(ngx.log).was_not_called() + end) + end) +end) diff --git a/t/apicast-policies-order.t b/t/apicast-policies-order.t new file mode 100644 index 000000000..76544d208 --- /dev/null +++ b/t/apicast-policies-order.t @@ -0,0 +1,114 @@ +use lib 't'; +use Test::APIcast::Blackbox 'no_plan'; + +# Load the config on every request, because errors related with the order of +# policies in the chain only appear when loading the config. If we used a cache, +# the second request would fail because the error would not be there. +env_to_apicast( + 'APICAST_CONFIGURATION_LOADER' => 'lazy', + 'APICAST_CONFIGURATION_CACHE' => 0 +); + +run_tests(); + +__DATA__ + +=== TEST 1: shows an error when policies are placed in an incorrect order +The default credentials policy should be placed after the apicast policy in the +chain. In this test, we are going to place them in the reverse order and check +that APIcast logs an error. +The request returns a 401 status code (credentials missing) because APIcast +cannot set the default credentials. +--- configuration +{ + "services": [ + { + "id": 42, + "backend_version": 1, + "backend_authentication_type": "service_token", + "backend_authentication_value": "token-value", + "proxy": { + "policy_chain": [ + { + "name": "apicast.policy.apicast" + }, + { + "name": "apicast.policy.default_credentials", + "configuration": { + "auth_type": "user_key", + "user_key": "uk" + } + } + ], + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_token=token-value&service_id=42&usage%5Bhits%5D=2&user_key=uk" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + echo 'yay, api backend'; + } +--- request +GET / +--- error_code: 401 +--- error_log +Default credentials policy (version: builtin) should be placed before APIcast (version: builtin) + + +=== TEST 2: does not show any error when policies are placed in the correct order +--- configuration +{ + "services": [ + { + "id": 42, + "backend_version": 1, + "backend_authentication_type": "service_token", + "backend_authentication_value": "token-value", + "proxy": { + "policy_chain": [ + { + "name": "apicast.policy.default_credentials", + "configuration": { + "auth_type": "user_key", + "user_key": "uk" + } + }, + { + "name": "apicast.policy.apicast" + } + ], + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + local expected = "service_token=token-value&service_id=42&usage%5Bhits%5D=2&user_key=uk" + require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) + } + } +--- upstream + location / { + echo 'yay, api backend'; + } +--- request +GET / +--- error_code: 200 +--- no_error_log +should be placed