Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cors): avoid overwriting Access-Control-Expose-Headers response header #11136

Merged
merged 8 commits into from
Apr 23, 2024
12 changes: 6 additions & 6 deletions apisix/plugins/cors.lua
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,9 @@ local schema = {
},
expose_headers = {
description =
"you can use '*' to expose all header when no credentials," ..
"'**' to allow forcefully(it will bring some security risks, be carefully)," ..
"multiple header use ',' to split. default: *.",
type = "string",
default = "*"
"multiple header use ',' to split." ..
"If not specified, no custom headers are exposed.",
type = "string"
},
max_age = {
description =
Expand Down Expand Up @@ -226,7 +224,9 @@ local function set_cors_headers(conf, ctx)
core.response.set_header("Access-Control-Allow-Origin", ctx.cors_allow_origins)
core.response.set_header("Access-Control-Allow-Methods", allow_methods)
core.response.set_header("Access-Control-Max-Age", conf.max_age)
core.response.set_header("Access-Control-Expose-Headers", conf.expose_headers)
if conf.expose_headers ~= nil and conf.expose_headers ~= "" then
core.response.set_header("Access-Control-Expose-Headers", conf.expose_headers)
end
if conf.allow_headers == "**" then
core.response.set_header("Access-Control-Allow-Headers",
core.request.header(ctx, "Access-Control-Request-Headers"))
Expand Down
3 changes: 1 addition & 2 deletions docs/en/latest/plugins/cors.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The `cors` Plugins lets you enable [CORS](https://developer.mozilla.org/en-US/do
| 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. |
| allow_methods | string | False | "*" | Request methods to enable CORS on. For example `GET`, `POST`. Use `,` to add multiple methods. If `allow_credential` is set to `false`, you can enable CORS for all methods by using `*`. If `allow_credential` is set to `true`, you can forcefully allow CORS on all methods by using `**` but it will pose some security issues. |
| allow_headers | string | False | "*" | Headers in the request allowed when accessing a cross-origin resource. Use `,` to add multiple headers. If `allow_credential` is set to `false`, you can enable CORS for all request headers by using `*`. If `allow_credential` is set to `true`, you can forcefully allow CORS on all request headers by using `**` but it will pose some security issues. |
| expose_headers | string | False | "*" | Headers in the response allowed when accessing a cross-origin resource. Use `,` to add multiple headers. If `allow_credential` is set to `false`, you can enable CORS for all response headers by using `*`. If `allow_credential` is set to `true`, you can forcefully allow CORS on all response headers by using `**` but it will pose some security issues. |
| expose_headers | string | False | | Headers in the response allowed when accessing a cross-origin resource. Use `,` to add multiple headers. If `allow_credential` is set to `false`, you can enable CORS for all response headers by using `*`. If not specified, the plugin will not modify the `Access-Control-Expose-Headers header`. See [Access-Control-Expose-Headers - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) for more details. |
| max_age | integer | False | 5 | Maximum time in seconds the result is cached. If the time is within this limit, the browser will check the cached result. Set to `-1` to disable caching. Note that the maximum value is browser dependent. See [Access-Control-Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age#Directives) for more details. |
| allow_credential | boolean | False | false | When set to `true`, allows requests to include credentials like cookies. According to CORS specification, if you set this to `true`, you cannot use '*' to allow all for the other attributes. |
| allow_origins_by_regex | array | False | nil | Regex to match origins that allow CORS. For example, `[".*\.test.com$"]` can match all subdomains of `test.com`. When set to specified range, only domains in this range will be allowed, no matter what `allow_origins` is. |
Expand Down Expand Up @@ -110,7 +110,6 @@ curl http://127.0.0.1:9080/hello -v
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: *
< Access-Control-Allow-Headers: *
< Access-Control-Expose-Headers: *
< Access-Control-Max-Age: 5
...
```
Expand Down
3 changes: 1 addition & 2 deletions docs/zh/latest/plugins/cors.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ description: 本文介绍了 Apache APISIX cors 插件的基本信息及使用
| allow_origins | string | 否 | "*" | 允许跨域访问的 Origin,格式为 `scheme://host:port`,示例如 `https://somedomain.com:8081`。如果你有多个 Origin,请使用 `,` 分隔。当 `allow_credential` 为 `false` 时,可以使用 `*` 来表示允许所有 Origin 通过。你也可以在启用了 `allow_credential` 后使用 `**` 强制允许所有 Origin 均通过,但请注意这样存在安全隐患。 |
| allow_methods | string | 否 | "*" | 允许跨域访问的 Method,比如:`GET`,`POST` 等。如果你有多个 Method,请使用 `,` 分割。当 `allow_credential` 为 `false` 时,可以使用 `*` 来表示允许所有 Method 通过。你也可以在启用了 `allow_credential` 后使用 `**` 强制允许所有 Method 都通过,但请注意这样存在安全隐患。 |
| allow_headers | string | 否 | "*" | 允许跨域访问时请求方携带哪些非 `CORS 规范` 以外的 Header。如果你有多个 Header,请使用 `,` 分割。当 `allow_credential` 为 `false` 时,可以使用 `*` 来表示允许所有 Header 通过。你也可以在启用了 `allow_credential` 后使用 `**` 强制允许所有 Header 都通过,但请注意这样存在安全隐患。 |
| expose_headers | string | 否 | "*" | 允许跨域访问时响应方携带哪些非 `CORS 规范` 以外的 Header。如果你有多个 Header,请使用 `,` 分割。当 `allow_credential``false` 时,可以使用 `*` 来表示允许任意 Header。你也可以在启用了 `allow_credential` 后使用 `**` 强制允许任意 Header,但请注意这样存在安全隐患。 |
| expose_headers | string | 否 | | 允许跨域访问时响应方携带哪些非 CORS 规范 以外的 Header。如果你有多个 Header,请使用 , 分割。当 allow_credential 为 false 时,可以使用 * 来表示允许任意 Header。如果不设置,插件不会修改 `Access-Control-Expose-Headers` 头,详情请参考 [Access-Control-Expose-Headers - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers)。 |
| max_age | integer | 否 | 5 | 浏览器缓存 CORS 结果的最大时间,单位为秒。在这个时间范围内,浏览器会复用上一次的检查结果,`-1` 表示不缓存。请注意各个浏览器允许的最大时间不同,详情请参考 [Access-Control-Max-Age - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age#directives)。 |
| allow_credential | boolean | 否 | false | 是否允许跨域访问的请求方携带凭据(如 Cookie 等)。根据 CORS 规范,如果设置该选项为 `true`,那么将不能在其他属性中使用 `*`。 |
| allow_origins_by_regex | array | 否 | nil | 使用正则表达式数组来匹配允许跨域访问的 Origin,如 `[".*\.test.com$"]` 可以匹配任何 `test.com` 的子域名。如果 `allow_origins_by_regex` 属性已经指定,则会忽略 `allow_origins` 属性。 |
Expand Down Expand Up @@ -94,7 +94,6 @@ curl http://127.0.0.1:9080/hello -v
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: *
< Access-Control-Allow-Headers: *
< Access-Control-Expose-Headers: *
< Access-Control-Max-Age: 5
...
```
Expand Down
2 changes: 1 addition & 1 deletion t/plugin/cors.t
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ Access-Control-Allow-Origin: *
Vary:
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:

Expand Down
12 changes: 6 additions & 6 deletions t/plugin/cors3.t
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ Access-Control-Allow-Origin: https://test.com
Vary: Via, Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:

Expand All @@ -204,7 +204,7 @@ Access-Control-Allow-Origin: https://domain.com
Vary: Via, Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:

Expand Down Expand Up @@ -276,7 +276,7 @@ Access-Control-Allow-Origin: https://domain.com
Vary: Via, Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:

Expand All @@ -295,7 +295,7 @@ Access-Control-Allow-Origin: https://sub.domain.com
Vary: Via, Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:

Expand Down Expand Up @@ -348,7 +348,7 @@ Access-Control-Allow-Origin: http://foo.example.org
Vary: Origin
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:

Expand Down Expand Up @@ -417,6 +417,6 @@ hello world
Access-Control-Allow-Origin: https://domain.com
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers: *
Access-Control-Expose-Headers: *
Access-Control-Expose-Headers:
Access-Control-Max-Age: 5
Access-Control-Allow-Credentials:
110 changes: 110 additions & 0 deletions t/plugin/cors4.t
Original file line number Diff line number Diff line change
Expand Up @@ -639,3 +639,113 @@ Access-Control-Allow-Headers: request-h
Access-Control-Expose-Headers: expose-h
Access-Control-Max-Age: 10
Timing-Allow-Origin: http://testurl.domain.com



=== TEST 26: set route ( expose_headers not specified )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_credential": true,
"allow_headers": "**",
"allow_methods": "**",
"allow_origins": "**",
"expose_headers": "",
"max_age": 3500
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed



=== TEST 27: remove Access-Control-Expose-Headers match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.domain.com
--- response_headers
Access-Control-Allow-Origin: http://sub.domain.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
Access-Control-Expose-Headers:
Access-Control-Allow-Headers:
Access-Control-Max-Age: 3500
Access-Control-Allow-Credentials: true



=== TEST 28: set route ( expose_headers set value )
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"cors": {
"allow_credential": true,
"allow_headers": "**",
"allow_methods": "**",
"allow_origins": "**",
"expose_headers": "ex-headr1,ex-headr2",
"max_age": 3500
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed



=== TEST 29: Access-Control-Expose-Headers match
--- request
GET /hello HTTP/1.1
--- more_headers
Origin: http://sub.domain.com
--- response_headers
Access-Control-Allow-Origin: http://sub.domain.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
Access-Control-Expose-Headers: ex-headr1,ex-headr2
Access-Control-Allow-Headers:
Access-Control-Max-Age: 3500
Access-Control-Allow-Credentials: true
Loading