Skip to content

Commit

Permalink
feature: add cors plugin (#1327)
Browse files Browse the repository at this point in the history
  • Loading branch information
ShiningRush authored Mar 29, 2020
1 parent 7ff43d5 commit c363ea4
Show file tree
Hide file tree
Showing 11 changed files with 759 additions and 2 deletions.
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` 插件。

```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

0 comments on commit c363ea4

Please sign in to comment.