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

features: append cors plugin #1327

Merged
merged 14 commits into from
Mar 29, 2020
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ A/B testing, canary release, blue-green deployment, limit rate, defense against
- [Limit-count](doc/plugins/limit-count.md)
- [Limit-concurrency](doc/plugins/limit-conn.md)
- Anti-ReDoS(Regular expression Denial of Service): Built-in policies to Anti ReDoS without configuration.
- [CORS](doc/plugins/cors.md)

- **OPS friendly**
- OpenTracing: [support Apache Skywalking and Zipkin](doc/plugins/zipkin.md)
Expand Down
1 change: 1 addition & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、限流限速、抵
- [限制请求数](doc/plugins/limit-count-cn.md)
- [限制并发](doc/plugins/limit-conn-cn.md)
- 防御 ReDoS(正则表达式拒绝服务):内置策略,无需配置即可抵御 ReDoS。
- [CORS](doc/plugins/cors-cn.md)

- **运维友好**
- OpenTracing 可观测性: [支持 Apache Skywalking 和 Zipkin](doc/plugins/zipkin-cn.md)。
Expand Down
2 changes: 1 addition & 1 deletion conf/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,6 @@ plugins: # plugin list
- tcp-logger
- proxy-mirror
- kafka-logger

- cors
stream_plugins:
- mqtt-proxy
1 change: 1 addition & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Plugins
* [udp-logger](plugins/udp-logger.md): Log requests to UDP servers.
* [proxy-mirror](plugins/proxy-mirror.md): Provides the ability to mirror client requests.
* [kafka-logger](plugins/kafka-logger.md): Log requests to External Kafka servers.
* [cors](plugins/cors.md): Enbale cors for you api.

Deploy to the Cloud
=======
Expand Down
1 change: 1 addition & 0 deletions doc/README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ Reference document
* [udp-logger](plugins/udp-logger.md): 将请求记录到UDP服务器
* [tcp-logger](plugins/tcp-logger.md): 将请求记录到TCP服务器
* [kafka-logger](plugins/kafka-logger-cn.md): 将请求记录到外部Kafka服务器。
* [cors](plugins/cors-cn.md): 为你的API启用CORS.
93 changes: 93 additions & 0 deletions doc/plugins/cors-cn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
-->

# [English](cors.md)

# 目录

- [**简介**](#简介)
- [**属性**](#属性)
- [**如何启用**](#如何启用)
- [**测试插件**](#测试插件)
- [**禁用插件**](#禁用插件)

## 简介

`cors` 插件可以让你轻易地为服务端启用 [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 的返回头。

## 属性

- `allow_origins`: `可选`,允许跨域访问的 Origin,格式如:`scheme`://`host`:`port`,比如: https://somehost.com:8081。多个值使用 `,` 分割,`allow_credential` 为 `false` 时可以使用 `*` 来表示所有 Origin 均允许通过。你也可以在启用了 `allow_credential` 后使用 `**` 强制允许所有 Origin 都通过,但请注意这样存在安全隐患。默认值为 `*`。
- `allow_methods`: `可选`,允许跨域访问的 Method,比如: `GET`,`POST`等。多个值使用 `,` 分割,`allow_credential` 为 `false` 时可以使用 `*` 来表示所有 Origin 均允许通过。你也可以在启用了 `allow_credential` 后使用 `**` 强制允许所有 Method 都通过,但请注意这样存在安全隐患。默认值为 `*`。
- `allow_headers`: `可选`,允许跨域访问时请求方携带哪些非 `CORS规范` 以外的 Header, 多个值使用 `,` 分割。默认值为 `*`。
- `expose_headers`: `可选`,允许跨域访问时响应方携带哪些非 `CORS规范` 以外的 Header, 多个值使用 `,` 分割。默认值为 `*`。
- `max_age`: `可选`,浏览器缓存 CORS 结果的最大时间,单位为秒,在这个时间范围内浏览器会复用上一次的检查结果,`-1` 表示不缓存。请注意各个浏览器允许的的最大时间不用,详情请参考 [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age#Directives). 默认值为 `5`。
- `allow_credential`: 是否允许跨域访问的请求方携带凭据(如 Cookie 等),默认值为: `false`。

## 如何启用

创建 `Route` 或 `Service` 对象,并配置 `cors` 插件。
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a blank line


```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/hello",
"plugins": {
"cors": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
}
}'
```

## 测试插件

请求下接口,发现接口已经返回了`CORS`相关的header,代表插件生效
```shell
curl http://127.0.0.1:9080/hello -v
...
< Server: APISIX web server
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: *
< Access-Control-Allow-Headers: *
< Access-Control-Expose-Headers: *
< Access-Control-Max-Age: 5
...
```

## 禁用插件

从配置中移除`cors`插件即可。
```shell
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
}
}'
```
95 changes: 95 additions & 0 deletions doc/plugins/cors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
-->

# [Chinese](cors-cn.md)

# Summary

- [**Description**](#Description)
- [**Attributes**](#Attributes)
- [**How To Enable**](#how-to-Enable)
- [**Test Plugin**](#test-plugin)
- [**Disable Plugin**](#disable-plugin)

## Description

`cors` plugin can help you enable [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) easily.

## Attributes

- `allow_origins`: `optional`, Which Origins is allowed to enable CORS, format as:`scheme`://`host`:`port`, for example: https://somehost.com:8081. Multiple origin use `,` to split. When `allow_credential` is `false`, you can use `*` to indicate allow all any origin. you alse can allow all any origins forcefully using `**` even already enable `allow_credential`, but it will bring some securiy risks. Default value: `*`.
- `allow_methods`: `optional`, Which Method is allowed to enable CORS, such as: `GET`, `POST` etc. Multiple method use `,` to split. When `allow_credential` is `false`, you can use `*` to indicate allow all any method. You alse can allow all any method forcefully using `**` even already enable `allow_credential`, but it will bring some securiy risks. Default value: `*`.
- `allow_headers`: `optional`, Which headers are allowed to set in requst when access cross-origin resource. Multiple value use `,` to split. Default value: `*`.
- `expose_headers`: `optional`, Which headers are allowed to set in response when access cross-origin resource. Multiple value use `,` to split. Default value: `*`.
- `max_age`: `optional`, Maximum number of seconds the results can be cached.. Within this time range, the browser will reuse the last check result. `-1` means no cache. Please note that the maximum value is depended on browser, please refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age#Directives) for details.Default value: `5`.
- `allow_credential`: Enable request include credentia (such as Cookie etc.), Default avlue: `false`.

## How To Enable

Create a `Route` or `Service` object and configure `cors` plugin.

```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/hello",
"plugins": {
"cors": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
}
}'
```

## Test Plugin

curl to server, you will find the headers about `CORS` is be returned, it means plugin is working fine.

```shell
curl http://127.0.0.1:9080/hello -v
...
< Server: APISIX web server
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: *
< Access-Control-Allow-Headers: *
< Access-Control-Expose-Headers: *
< Access-Control-Max-Age: 5
...
```

## Disable Plugin

Remove plugin from configuraion.

```shell
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
}
}'
```
136 changes: 136 additions & 0 deletions lua/apisix/plugins/cors.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local core = require("apisix.core")
local ngx = ngx
local plugin_name = "cors"
local str_find = string.find
local re_gmatch = ngx.re.gmatch

local schema = {
type = "object",
properties = {
allow_origins = {
description =
"you can use '*' to allow all origins when no credentials," ..
"'**' to allow forcefully(it will bring some security risks, be carefully)," ..
"multiple origin use ',' to split. default: *.",
type = "string",
default = "*"
},
allow_methods = {
description =
"you can use '*' to allow all methods when no credentials and '**'," ..
"'**' to allow forcefully(it will bring some security risks, be carefully)," ..
"multiple method use ',' to split. default: *.",
type = "string",
default = "*"
},
allow_headers = {
description =
"you can use '*' to allow all header when no credentials," ..
"multiple header use ',' to split. default: *.",
type = "string",
default = "*"
},
expose_headers = {
description =
"you can use '*' to expose all header when no credentials," ..
"multiple header use ',' to split. default: *.",
type = "string",
default = "*"
},
max_age = {
description =
"maximum number of seconds the results can be cached." ..
"-1 mean no cached,the max value is depend on browser," ..
"more detail plz check MDN. default: 5.",
type = "integer",
default = 5
},
allow_credential = {
type = "boolean",
default = false
},
}
}

local _M = {
version = 0.1,
priority = 4000,
type = 'auth',
name = plugin_name,
schema = schema,
}

function _M.check_schema(conf)
local ok, err = core.schema.check(schema, conf)
if not ok then
return false, err
end

return true
end

function _M.header_filter(conf, ctx)
if conf.allow_origins == "**" then
conf.allow_origins = ngx.var.http_origin or '*'
end
if str_find(conf.allow_origins, ",", 1, true) then
local finded = false
local iterator, err = re_gmatch(conf.allow_origins, "([^,]+)", "jiox")
if not iterator then
return 500, {message = "match origins failed", error = err}
end
while true do
local origin, err = iterator()
if err then
return 500, {message = "iterate origins failed", error = err}
end
if not origin then
break
end

if origin[0] == ngx.var.http_origin then
conf.allow_origins = origin[0]
finded = true
break
end
end
if not finded then
return
end
end

if conf.allow_methods == "**" then
conf.allow_methods = "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE"
end

ngx.header["Access-Control-Allow-Origin"] = conf.allow_origins
ngx.header["Access-Control-Allow-Methods"] = conf.allow_methods
ngx.header["Access-Control-Allow-Headers"] = conf.allow_headers
ngx.header["Access-Control-Expose-Headers"] = conf.expose_headers
ngx.header["Access-Control-Max-Age"] = conf.max_age
if conf.allow_credential then
ngx.header["Access-Control-Allow-Credentials"] = true
end

if ctx.var.request_method == "OPTIONS" then
return 200
end
end

return _M
2 changes: 1 addition & 1 deletion t/admin/plugins.t
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ __DATA__
--- request
GET /apisix/admin/plugins/list
--- response_body_like eval
qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection","udp-logger","wolf-rbac","proxy-cache","tcp-logger","proxy-mirror","kafka-logger"\]/
qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection","udp-logger","wolf-rbac","proxy-cache","tcp-logger","proxy-mirror","kafka-logger","cors"\]/
--- no_error_log
[error]

Expand Down
1 change: 1 addition & 0 deletions t/debug/debug-mode.t
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ qr/loaded plugin and sort by priority: [-\d]+ name: [\w-]+/
--- grep_error_log_out
loaded plugin and sort by priority: 11000 name: fault-injection
loaded plugin and sort by priority: 10000 name: serverless-pre-function
loaded plugin and sort by priority: 4000 name: cors
loaded plugin and sort by priority: 3000 name: ip-restriction
loaded plugin and sort by priority: 2599 name: openid-connect
loaded plugin and sort by priority: 2555 name: wolf-rbac
Expand Down
Loading