From 6d83d3e9507769f76078b3cebb4a1155b17d4e5a Mon Sep 17 00:00:00 2001 From: Eloy Coto Date: Thu, 25 Apr 2019 14:42:41 +0200 Subject: [PATCH] [THREESCALE-1524][config] add support to filter services by public URL. This commit adds the option to filter services based on the endpoint. This commit expose a new config flag using the env variable `APICAST_SERVICES_FILTER_BY_URL`, services filter happens on `configuration.filter_services`. Signed-off-by: Eloy Coto --- CHANGELOG.md | 1 + doc/parameters.md | 23 +++++++++++++++ gateway/src/apicast/configuration.lua | 40 +++++++++++++++++++++++---- spec/configuration_spec.lua | 39 ++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8045a642..6df0bcb60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Ability to configure client certificate chain depth [PR #1006](https://github.com/3scale/APIcast/pull/1006) +- Ability to filter services by endpoint name using Regexp [PR #]() [THREESCALE-1524](https://issues.jboss.org/browse/THREESCALE-1524) ### Fixed diff --git a/doc/parameters.md b/doc/parameters.md index f16f7cbd2..0322f7ef6 100644 --- a/doc/parameters.md +++ b/doc/parameters.md @@ -186,6 +186,29 @@ before the client is throttled by adding latency. When set to _true_, APIcast will log the response code of the response returned by the API backend in 3scale. In some plans this information can later be consulted from the 3scale admin portal. Find more information about the Response Codes feature on the [3scale support site](https://access.redhat.com/documentation/en-us/red_hat_3scale/2.saas/html/analytics/response-codes-tracking). +### `APICAST_SERVICES_FILTER_BY_URL` +**Value:** a regular expression +**Default:**: .* +**Example:** *.example.com + +Used to filter the service configured in the 3scale API Manager, the filter +matches with the public base URL. The services that don't match the filter will +be discarded. + +Note: If a service does not match, but is included in the +`APICAST_SERVICES_LIST` the service will not be discarded + +Example: + +Regexp Filter: http:\/\/^test.*.com +Service 1: backend endpoint http://test.foo.com +Service 2: backend endpoint http://prod.foo.com +Service 3: backend endpoint http://test.bar.com +Service 4: backend endpoint http://prod.bar.com + +The services that will be configured in Apicast will be 1 and 3. Services 2 and +4 will be discarded. + ### `APICAST_SERVICES_LIST` **Value:** a comma-separated list of service IDs diff --git a/gateway/src/apicast/configuration.lua b/gateway/src/apicast/configuration.lua index 90a4a3f3f..74c081f8c 100644 --- a/gateway/src/apicast/configuration.lua +++ b/gateway/src/apicast/configuration.lua @@ -12,7 +12,6 @@ local insert = table.insert local setmetatable = setmetatable local null = ngx.null -local re = require 'ngx.re' local env = require 'resty.env' local resty_url = require 'resty.url' local util = require 'apicast.util' @@ -20,6 +19,9 @@ local policy_chain = require 'apicast.policy_chain' local mapping_rule = require 'apicast.mapping_rule' local tab_new = require('resty.core.base').new_tab +local re = require 'ngx.re' +local match = ngx.re.match + local mt = { __index = _M, __tostring = function() return 'Configuration' end } local function map(func, tbl) @@ -140,20 +142,48 @@ function _M.services_limit() end function _M.filter_services(services, subset) - subset = subset and util.to_hash(subset) or _M.services_limit() - if not subset or not next(subset) then return services end - local s = {} + local serviceRegexpFilter = env.value("APICAST_SERVICES_FILTER_BY_URL") + + if serviceRegexpFilter then + -- Checking that the regexp sent is correct, if not an empty service list + -- will be returned. + local _, err = match("", serviceRegexpFilter) + if err then + -- @todo this return and empty list, Apicast will continue running maybe + -- process need to be stopped here. + ngx.log(ngx.ERR, "APICAST_SERVICES_FILTER cannot compile and all services are filtering out") + return s + end + end + + subset = subset and util.to_hash(subset) or _M.services_limit() + if (not subset or not next(subset)) and not serviceRegexpFilter then return services end + subset = subset or {} for i = 1, #services do local service = services[i] + local success = false + + if serviceRegexpFilter then + for j = 1,#service.hosts do + local val, _ = match(service.hosts[j], serviceRegexpFilter) + if val then + success = true + end + end + end + if subset[service.id] then + success = true + end + + if success then insert(s, service) else ngx.log(ngx.WARN, 'filtering out service ', service.id) end end - return s end diff --git a/spec/configuration_spec.lua b/spec/configuration_spec.lua index de8f639a6..5b84aeff3 100644 --- a/spec/configuration_spec.lua +++ b/spec/configuration_spec.lua @@ -125,6 +125,45 @@ describe('Configuration object', function() assert.same(services, filter_services(services, { '42' })) assert.same({}, filter_services(services, { '21' })) end) + + describe("with service filter", function() + local mockservices = { + {id="42", hosts={"test.foo.com"}}, + {id="12", hosts={"staging.foo.com"}}, + {id="21", hosts={"prod.foo.com"}}, + } + + it("validates default case", function() + assert.same(filter_services(mockservices, nil), mockservices) + end) + + it("reads from environment variable", function() + env.set('APICAST_SERVICES_FILTER_BY_URL', '.*.foo.com') + assert.same(filter_services(mockservices, nil), mockservices) + + env.set('APICAST_SERVICES_FILTER_BY_URL', '^test.*') + assert.same(filter_services(mockservices, nil), {mockservices[1]}) + end) + + it("validates invalid regexp", function() + env.set('APICAST_SERVICES_FILTER_BY_URL', '^]') + assert.same(filter_services(mockservices, nil), {}) + end) + + it("combination with service list", function() + env.set('APICAST_SERVICES_FILTER_BY_URL', '^test.*') + assert.same(filter_services(mockservices, {"21"}), { + mockservices[1], + mockservices[3]}) + + + env.set('APICAST_SERVICES_LIST', '42,21') + assert.same(filter_services(mockservices, nil), { + mockservices[1], + mockservices[3]}) + end) + end) + end) insulate('.services_limit', function()