Skip to content

Commit

Permalink
feat(cors): support for the Timing-Allow-Origin header (apache#9365)
Browse files Browse the repository at this point in the history
  • Loading branch information
skimdz86 authored Dec 5, 2023
1 parent a0fe930 commit b40264f
Show file tree
Hide file tree
Showing 3 changed files with 748 additions and 15 deletions.
102 changes: 87 additions & 15 deletions apisix/plugins/cors.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ local re_find = ngx.re.find
local ipairs = ipairs
local origins_pattern = [[^(\*|\*\*|null|\w+://[^,]+(,\w+://[^,]+)*)$]]

local TYPE_ACCESS_CONTROL_ALLOW_ORIGIN = "ACAO"
local TYPE_TIMING_ALLOW_ORIGIN = "TAO"

local lrucache = core.lrucache.new({
type = "plugin",
Expand Down Expand Up @@ -119,6 +121,28 @@ local schema = {
minItems = 1,
uniqueItems = true,
},
timing_allow_origins = {
description =
"you can use '*' to allow all origins which can view timing information " ..
"when no credentials," ..
"'**' to allow forcefully (it will bring some security risks, be careful)," ..
"multiple origin use ',' to split. default: nil",
type = "string",
pattern = origins_pattern
},
timing_allow_origins_by_regex = {
type = "array",
description =
"you can use regex to allow specific origins which can view timing information," ..
"for example use [.*\\.test.com] to allow a.test.com and b.test.com",
items = {
type = "string",
minLength = 1,
maxLength = 4096,
},
minItems = 1,
uniqueItems = true,
},
}
}

Expand Down Expand Up @@ -166,7 +190,8 @@ function _M.check_schema(conf, schema_type)
end
if conf.allow_credential then
if conf.allow_origins == "*" or conf.allow_methods == "*" or
conf.allow_headers == "*" or conf.expose_headers == "*" then
conf.allow_headers == "*" or conf.expose_headers == "*" or
conf.timing_allow_origins == "*" then
return false, "you can not set '*' for other option when 'allow_credential' is true"
end
end
Expand All @@ -179,6 +204,15 @@ function _M.check_schema(conf, schema_type)
end
end

if conf.timing_allow_origins_by_regex then
for i, re_rule in ipairs(conf.timing_allow_origins_by_regex) do
local ok, err = re_compile(re_rule, "j")
if not ok then
return false, err
end
end
end

return true
end

Expand All @@ -204,7 +238,14 @@ local function set_cors_headers(conf, ctx)
end
end

local function process_with_allow_origins(allow_origins, ctx, req_origin,
local function set_timing_headers(conf, ctx)
if ctx.timing_allow_origin then
core.response.set_header("Timing-Allow-Origin", ctx.timing_allow_origin)
end
end


local function process_with_allow_origins(allow_origin_type, allow_origins, ctx, req_origin,
cache_key, cache_version)
if allow_origins == "**" then
allow_origins = req_origin or '*'
Expand All @@ -217,7 +258,7 @@ local function process_with_allow_origins(allow_origins, ctx, req_origin,
)
else
multiple_origin, err = core.lrucache.plugin_ctx(
lrucache, ctx, nil, create_multiple_origin_cache, allow_origins
lrucache, ctx, allow_origin_type, create_multiple_origin_cache, allow_origins
)
end

Expand All @@ -236,18 +277,23 @@ local function process_with_allow_origins(allow_origins, ctx, req_origin,
return allow_origins
end

local function process_with_allow_origins_by_regex(conf, ctx, req_origin)
if not conf.allow_origins_by_regex_rules_concat then
local function process_with_allow_origins_by_regex(allow_origin_type,
allow_origins_by_regex, conf, ctx, req_origin)

local allow_origins_by_regex_rules_concat_conf_key =
"allow_origins_by_regex_rules_concat_" .. allow_origin_type

if not conf[allow_origins_by_regex_rules_concat_conf_key] then
local allow_origins_by_regex_rules = {}
for i, re_rule in ipairs(conf.allow_origins_by_regex) do
for i, re_rule in ipairs(allow_origins_by_regex) do
allow_origins_by_regex_rules[i] = re_rule
end
conf.allow_origins_by_regex_rules_concat = core.table.concat(
conf[allow_origins_by_regex_rules_concat_conf_key] = core.table.concat(
allow_origins_by_regex_rules, "|")
end

-- core.log.warn("regex: ", conf.allow_origins_by_regex_rules_concat, "\n ")
local matched = re_find(req_origin, conf.allow_origins_by_regex_rules_concat, "jo")
-- core.log.warn("regex: ", conf[allow_origins_by_regex_rules_concat_conf_key], "\n ")
local matched = re_find(req_origin, conf[allow_origins_by_regex_rules_concat_conf_key], "jo")
if matched then
return req_origin
end
Expand All @@ -258,7 +304,9 @@ local function match_origins(req_origin, allow_origins)
return req_origin == allow_origins or allow_origins == '*'
end

local function process_with_allow_origins_by_metadata(allow_origins_by_metadata, ctx, req_origin)
local function process_with_allow_origins_by_metadata(allow_origin_type, allow_origins_by_metadata,
ctx, req_origin)

if allow_origins_by_metadata == nil then
return
end
Expand All @@ -268,8 +316,10 @@ local function process_with_allow_origins_by_metadata(allow_origins_by_metadata,
local allow_origins_map = metadata.value.allow_origins
for _, key in ipairs(allow_origins_by_metadata) do
local allow_origins_conf = allow_origins_map[key]
local allow_origins = process_with_allow_origins(allow_origins_conf, ctx, req_origin,
plugin_name .. "#" .. key, metadata.modifiedIndex)
local allow_origins = process_with_allow_origins(
allow_origin_type, allow_origins_conf, ctx, req_origin,
plugin_name .. "#" .. key, metadata.modifiedIndex
)
if match_origins(req_origin, allow_origins) then
return req_origin
end
Expand All @@ -292,13 +342,18 @@ function _M.header_filter(conf, ctx)
-- If allow_origins_by_regex is not nil, should be matched to it only
local allow_origins
if conf.allow_origins_by_regex == nil then
allow_origins = process_with_allow_origins(conf.allow_origins, ctx, req_origin)
allow_origins = process_with_allow_origins(
TYPE_ACCESS_CONTROL_ALLOW_ORIGIN, conf.allow_origins, ctx, req_origin
)
else
allow_origins = process_with_allow_origins_by_regex(conf, ctx, req_origin)
allow_origins = process_with_allow_origins_by_regex(
TYPE_ACCESS_CONTROL_ALLOW_ORIGIN, conf.allow_origins_by_regex,
conf, ctx, req_origin
)
end
if not match_origins(req_origin, allow_origins) then
allow_origins = process_with_allow_origins_by_metadata(
conf.allow_origins_by_metadata, ctx, req_origin
TYPE_ACCESS_CONTROL_ALLOW_ORIGIN, conf.allow_origins_by_metadata, ctx, req_origin
)
end
if conf.allow_origins ~= "*" then
Expand All @@ -308,6 +363,23 @@ function _M.header_filter(conf, ctx)
ctx.cors_allow_origins = allow_origins
set_cors_headers(conf, ctx)
end

local timing_allow_origins
if conf.timing_allow_origins_by_regex == nil and conf.timing_allow_origins then
timing_allow_origins = process_with_allow_origins(
TYPE_TIMING_ALLOW_ORIGIN, conf.timing_allow_origins, ctx, req_origin
)
elseif conf.timing_allow_origins_by_regex then
timing_allow_origins = process_with_allow_origins_by_regex(
TYPE_TIMING_ALLOW_ORIGIN, conf.timing_allow_origins_by_regex,
conf, ctx, req_origin
)
end
if timing_allow_origins and match_origins(req_origin, timing_allow_origins) then
ctx.timing_allow_origin = timing_allow_origins
set_timing_headers(conf, ctx)
end

end

return _M
20 changes: 20 additions & 0 deletions docs/en/latest/plugins/cors.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ The `cors` Plugins lets you enable [CORS](https://developer.mozilla.org/en-US/do

## Attributes

### CORS attributes

| Name | Type | Required | Default | Description |
|---------------------------|---------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| allow_origins | string | False | "*" | Origins to allow CORS. Use the `scheme://host:port` format. For example, `https://somedomain.com:8081`. If you have multiple origins, use a `,` to list them. If `allow_credential` is set to `false`, you can enable CORS for all origins by using `*`. If `allow_credential` is set to `true`, you can forcefully allow CORS on all origins by using `**` but it will pose some security issues. |
Expand All @@ -50,6 +52,24 @@ The `cors` Plugins lets you enable [CORS](https://developer.mozilla.org/en-US/do

:::

### Resource Timing attributes

| Name | Type | Required | Default | Description |
|---------------------------|---------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| timing_allow_origins | string | False | nil | Origin to allow to access the resource timing information. See [Timing-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin). Use the `scheme://host:port` format. For example, `https://somedomain.com:8081`. If you have multiple origins, use a `,` to list them. |
| timing_allow_origins_by_regex | array | False | nil | Regex to match with origin for enabling access to the resource timing information. For example, `[".*\.test.com"]` can match all subdomain of `test.com`. When set to specified range, only domains in this range will be allowed, no matter what `timing_allow_origins` is. |

:::note

The Timing-Allow-Origin header is defined in the Resource Timing API, but it is related to the CORS concept.

Suppose you have 2 domains, `domain-A.com` and `domain-B.com`.
You are on a page on `domain-A.com`, you have an XHR call to a resource on `domain-B.com` and you need its timing information.
You can allow the browser to show this timing information only if you have cross-origin permissions on `domain-B.com`.
So, you have to set the CORS headers first, then access the `domain-B.com` URL, and if you set [Timing-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin), the browser will show the requested timing information.

:::

## Metadata

| Name | Type | Required | Description |
Expand Down
Loading

0 comments on commit b40264f

Please sign in to comment.