Skip to content

Commit

Permalink
feat(cors) match configured origins as a regular expression
Browse files Browse the repository at this point in the history
Use the ngx.re API to match configured origins as regular expressions
against the client Origin header. In cases where a single origin is
configured for the plugin, set the ACAO header if the configuration
contains only non-PCRE metacharacters; otherwise, treat the single
configured origin as a though multiple origins were configured, by
iterating through the array and setting the ACAO header based on the
client Origin.
  • Loading branch information
p0pr0ck5 committed May 2, 2017
1 parent bb0d46d commit cd3c42e
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## [Unreleased][unreleased]

- Plugins:
- cors: `origins` can now be configured as a regular expression to match
against Origin headers.

## [0.10.2] - 2017/05/01

### Changed
Expand Down
15 changes: 10 additions & 5 deletions kong/plugins/cors/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,26 @@ local function configure_origin(ngx, conf)
if #conf.origins == 1 then
if conf.origins[1] == "*" then
ngx.ctx.cors_allow_all = true
ngx.header["Access-Control-Allow-Origin"] = "*"
return

else
ngx.header["Vary"] = "Origin"
end

ngx.header["Access-Control-Allow-Origin"] = conf.origins[1]
return
-- if this doesnt look like a regex, set the ACAO header directly
-- otherwise, we'll fall through to an iterative search and
-- set the ACAO header based on the client Origin
if re_find(conf.origins[1], "^[A-Za-z0-9.:/-]+$", "jo") then
ngx.header["Access-Control-Allow-Origin"] = conf.origins[1]
return
end
end

local req_origin = ngx.var.http_origin
if req_origin then
for _, domain in ipairs(conf.origins) do
local from, _, err = re_find(req_origin,
[[\Q]] .. domain .. [[\E$]],
"jo")
local from, _, err = re_find(req_origin, domain, "jo")
if err then
ngx.log(ngx.ERR, "[cors] could not search for domain: ", err)
end
Expand Down
16 changes: 15 additions & 1 deletion kong/plugins/cors/schema.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
local re_match = ngx.re.match

local check_regex = function(value)
if value and (#value > 1 or value[1] ~= "*") then
for _, origin in ipairs(value) do
local _, err = re_match("just a string to test", origin)
if err then
return false, "origin " .. origin .. " is not a valid regex"
end
end
end
return true
end

return {
no_consumer = true,
fields = {
origins = { type = "array" },
origins = { type = "array", func = check_regex },
headers = { type = "array" },
exposed_headers = { type = "array" },
methods = { type = "array", enum = { "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE" } },
Expand Down
34 changes: 34 additions & 0 deletions spec/03-plugins/14-cors/01-access_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ describe("Plugin: cors (access)", function()
hosts = { "cors7.com" },
upstream_url = "http://mockbin.com"
})
local api8 = assert(helpers.dao.apis:insert {
name = "api-8",
hosts = { "cors8.com" },
upstream_url = "http://mockbin.com"
})

assert(helpers.dao.plugins:insert {
name = "cors",
Expand Down Expand Up @@ -112,6 +117,14 @@ describe("Plugin: cors (access)", function()
}
})

assert(helpers.dao.plugins:insert {
name = "cors",
api_id = api8.id,
config = {
origins = { [[.*\.?example(?:-foo)?.com]] },
}
})

assert(helpers.start_kong())
client = helpers.proxy_client()
end)
Expand Down Expand Up @@ -274,6 +287,27 @@ describe("Plugin: cors (access)", function()
})
assert.res_status(200, res)
assert.equal("http://www.example.com", res.headers["Access-Control-Allow-Origin"])

local domains = {
["example.com"] = true,
["www.example.com"] = true,
["example-foo.com"] = true,
["www.example-foo.com"] = true,
["www.example-fo0.com"] = false
}

for domain, v in pairs(domains) do
local res = assert(client:send {
method = "GET",
headers = {
["Host"] = "cors8.com",
["Origin"] = domain
}
})
assert.res_status(200, res)
assert.equal(domains[domain] and domain or nil,
res.headers["Access-Control-Allow-Origin"])
end
end)

it("does not sets CORS orgin if origin host is not in origin_domains list", function()
Expand Down
35 changes: 35 additions & 0 deletions spec/03-plugins/14-cors/02-schema_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local validate_entity = require("kong.dao.schemas_validation").validate_entity
local cors_schema = require "kong.plugins.cors.schema"

describe("cors schema", function()
it("validates '*'", function()
local ok, err = validate_entity({ origins = { "*" } }, cors_schema)

assert.True(ok)
assert.is_nil(err)
end)

it("validates what looks like a domain", function()
local ok, err = validate_entity({ origins = { "example.com" } }, cors_schema)

assert.True(ok)
assert.is_nil(err)
end)

it("validates what looks like a regex", function()
local ok, err = validate_entity({ origins = { [[.*\.example(?:-foo)?\.com]] } }, cors_schema)

assert.True(ok)
assert.is_nil(err)
end)

describe("errors", function()
it("with invalid regex in origins", function()
local mock_origins = { [[.*.example.com]], [[invalid_**regex]] }
local ok, err = validate_entity({ origins = mock_origins }, cors_schema)

assert.False(ok)
assert.same(err.origins, "origin " .. mock_origins[2] .. " is not a valid regex")
end)
end)
end)

0 comments on commit cd3c42e

Please sign in to comment.