mirror of
https://gitee.com/iresty/apisix.git
synced 2024-12-05 05:27:35 +08:00
feat: use interceptors to protect plugin's route (#2416)
This commit is contained in:
parent
90109ca678
commit
6a543a98a2
@ -14,9 +14,11 @@
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
--
|
||||
local error = error
|
||||
local pcall = pcall
|
||||
local require = require
|
||||
local core = require("apisix.core")
|
||||
local api_router = require("apisix.api_router")
|
||||
|
||||
local _M = {
|
||||
}
|
||||
@ -44,15 +46,26 @@ local function check_conf(plugin_name, conf)
|
||||
return nil, {error_msg = "invalid plugin name"}
|
||||
end
|
||||
|
||||
local schema = plugin_object.metadata_schema
|
||||
if not schema then
|
||||
return nil, {error_msg = "no metadata schema for plugin " .. plugin_name}
|
||||
end
|
||||
|
||||
if not conf then
|
||||
return nil, {error_msg = "missing configurations"}
|
||||
end
|
||||
|
||||
local schema = plugin_object.metadata_schema or {
|
||||
type = "object",
|
||||
properties = {},
|
||||
}
|
||||
if not schema.properties then
|
||||
schema.properties = {
|
||||
additionalProperties = false,
|
||||
}
|
||||
end
|
||||
|
||||
-- inject interceptors schema to each plugins
|
||||
if schema.properties.interceptors then
|
||||
error("'interceptors' can not be used as the name of metadata schema's field")
|
||||
end
|
||||
schema.properties.interceptors = api_router.interceptors_schema
|
||||
|
||||
core.log.info("schema: ", core.json.delay_encode(schema))
|
||||
core.log.info("conf : ", core.json.delay_encode(conf))
|
||||
local ok, err = core.schema.check(schema, conf)
|
||||
|
@ -16,13 +16,57 @@
|
||||
--
|
||||
local require = require
|
||||
local router = require("resty.radixtree")
|
||||
local plugin = require("apisix.plugin")
|
||||
local plugin_mod = require("apisix.plugin")
|
||||
local ip_restriction = require("apisix.plugins.ip-restriction")
|
||||
local core = require("apisix.core")
|
||||
local ipairs = ipairs
|
||||
|
||||
|
||||
local _M = {}
|
||||
local match_opts = {}
|
||||
local interceptors = {
|
||||
["ip-restriction"] = {
|
||||
run = function (conf, ctx)
|
||||
return ip_restriction.access(conf, ctx)
|
||||
end,
|
||||
schema = ip_restriction.schema,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_M.interceptors_schema = {
|
||||
type = "array",
|
||||
items = {
|
||||
type = "object",
|
||||
minItems = 1,
|
||||
properties = {
|
||||
name = {
|
||||
type = "string",
|
||||
enum = {"ip-restriction"},
|
||||
},
|
||||
conf = {
|
||||
type = "object",
|
||||
}
|
||||
},
|
||||
required = {"name", "conf"},
|
||||
dependencies = {
|
||||
name = {
|
||||
oneOf = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for name, attrs in pairs(interceptors) do
|
||||
core.table.insert(_M.interceptors_schema.items.properties.name.enum, name)
|
||||
core.table.insert(_M.interceptors_schema.items.dependencies.name.oneOf, {
|
||||
properties = {
|
||||
name = {
|
||||
enum = {name},
|
||||
},
|
||||
conf = attrs.schema,
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
local fetch_api_router
|
||||
@ -31,9 +75,10 @@ do
|
||||
function fetch_api_router()
|
||||
core.table.clear(routes)
|
||||
|
||||
for _, plugin in ipairs(plugin.plugins) do
|
||||
for _, plugin in ipairs(plugin_mod.plugins) do
|
||||
local api_fun = plugin.api
|
||||
if api_fun then
|
||||
local name = plugin.name
|
||||
local api_routes = api_fun()
|
||||
core.log.debug("fetched api routes: ",
|
||||
core.json.delay_encode(api_routes, true))
|
||||
@ -41,8 +86,25 @@ function fetch_api_router()
|
||||
core.table.insert(routes, {
|
||||
methods = route.methods,
|
||||
paths = route.uri,
|
||||
handler = function (...)
|
||||
local code, body = route.handler(...)
|
||||
handler = function (api_ctx)
|
||||
local code, body
|
||||
|
||||
local metadata = plugin_mod.plugin_metadata(name)
|
||||
if metadata and metadata.interceptors then
|
||||
for _, rule in ipairs(metadata.interceptors) do
|
||||
local f = interceptors[rule.name]
|
||||
if f == nil then
|
||||
core.log.error("unknown interceptor: ", rule.name)
|
||||
else
|
||||
code, body = f.run(rule.conf, api_ctx)
|
||||
if code or body then
|
||||
return core.response.exit(code, body)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
code, body = route.handler(api_ctx)
|
||||
if code or body then
|
||||
core.response.exit(code, body)
|
||||
end
|
||||
@ -59,7 +121,7 @@ end -- do
|
||||
|
||||
|
||||
function _M.match(api_ctx)
|
||||
local api_router = core.lrucache.global("api_router", plugin.load_times, fetch_api_router)
|
||||
local api_router = core.lrucache.global("api_router", plugin_mod.load_times, fetch_api_router)
|
||||
if not api_router then
|
||||
core.log.error("failed to fetch valid api router")
|
||||
return false
|
||||
|
55
doc/plugin-interceptors.md
Normal file
55
doc/plugin-interceptors.md
Normal file
@ -0,0 +1,55 @@
|
||||
<!--
|
||||
#
|
||||
# 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](zh-cn/plugin-interceptors.md)
|
||||
|
||||
## Plugin interceptors
|
||||
|
||||
Some plugins will register API to serve their purposes.
|
||||
|
||||
Since these API are not added as regular [Route](admin-api.md), we can't add
|
||||
plugins to protect them. To solve the problem, we add a new concept called 'interceptors'
|
||||
to run rules to protect them.
|
||||
|
||||
Here is an example to limit the access of `/apisix/prometheus/metrics` (a route introduced via plugin prometheus)
|
||||
to clients in `10.0.0.0/24`:
|
||||
|
||||
```shell
|
||||
$ curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/prometheus -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -i -X PUT -d '
|
||||
{
|
||||
"interceptors": [
|
||||
{
|
||||
"name": "ip-restriction",
|
||||
"conf": {
|
||||
"whitelist": ["10.0.0.0/24"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can see that the interceptors are configured like the plugins. The `name` is
|
||||
the name of plugin which you want to run and the `conf` is the configuration of the
|
||||
plugin.
|
||||
|
||||
Currently we only support a subset of plugins which can be run as interceptors.
|
||||
|
||||
Supported interceptors:
|
||||
|
||||
* [ip-restriction](./plugins/ip-restriction.md)
|
@ -40,6 +40,11 @@
|
||||
|
||||
None
|
||||
|
||||
## API
|
||||
|
||||
This plugin will add `/apisix/batch-requests` as the endpoint.
|
||||
You may need to use [interceptors](plugin-interceptors.md) to protect it.
|
||||
|
||||
## How To Enable
|
||||
|
||||
Default enabled
|
||||
|
@ -45,6 +45,11 @@ For more information on JWT, refer to [JWT](https://jwt.io/) for more informatio
|
||||
| exp | integer | optional | 86400 | [1,...] | token's expire time, in seconds |
|
||||
| base64_secret | boolean | optional | false | | whether secret is base64 encoded |
|
||||
|
||||
## API
|
||||
|
||||
This plugin will add `/apisix/plugin/jwt/sign` to sign.
|
||||
You may need to use [interceptors](plugin-interceptors.md) to protect it.
|
||||
|
||||
## How To Enable
|
||||
|
||||
1. set a consumer and config the value of the `jwt-auth` option
|
||||
|
@ -27,6 +27,11 @@ This plugin exposes metrics in Prometheus Exposition format.
|
||||
|
||||
none.
|
||||
|
||||
## API
|
||||
|
||||
This plugin will add `/apisix/prometheus/metrics` to expose the metrics.
|
||||
You may need to use [interceptors](plugin-interceptors.md) to protect it.
|
||||
|
||||
## How to enable it
|
||||
|
||||
`prometheus` plugin can be enable with empty table, because it doesn't have
|
||||
|
@ -42,6 +42,15 @@ The rbac feature is provided by [wolf](https://github.com/iGeeky/wolf). For more
|
||||
| appid | string | optional | "unset" | | Set the app id. The app id must be added in wolf-console. |
|
||||
| header_prefix | string | optional | "X-" | | prefix of custom HTTP header. After authentication is successful, three headers will be added to the request header (for backend) and response header (for frontend): `X-UserId`, `X-Username`, `X-Nickname`. |
|
||||
|
||||
## API
|
||||
|
||||
This plugin will add several API:
|
||||
|
||||
* /apisix/plugin/wolf-rbac/login
|
||||
* /apisix/plugin/wolf-rbac/change_pwd
|
||||
* /apisix/plugin/wolf-rbac/user_info
|
||||
|
||||
You may need to use [interceptors](plugin-interceptors.md) to protect it.
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
50
doc/zh-cn/plugin-interceptors.md
Normal file
50
doc/zh-cn/plugin-interceptors.md
Normal file
@ -0,0 +1,50 @@
|
||||
<!--
|
||||
#
|
||||
# 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](../plugin-interceptors.md)
|
||||
|
||||
## Plugin interceptors
|
||||
|
||||
有些插件为实现它的功能会注册额外的接口。
|
||||
|
||||
由于这些接口不是通过 admin API 添加的,所以没办法像管理 Route 那样管理它们。为了能够保护这些接口不被利用,我们引入了 interceptors 的概念。
|
||||
|
||||
下面是通过 interceptors 来保护由 prometheus 插件引入的 `/apisix/prometheus/metrics` 接口,限定只能由 `10.0.0.0/24` 网段的用户访问:
|
||||
|
||||
```shell
|
||||
$ curl http://127.0.0.1:9080/apisix/admin/plugin_metadata/prometheus -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -i -X PUT -d '
|
||||
{
|
||||
"interceptors": [
|
||||
{
|
||||
"name": "ip-restriction",
|
||||
"conf": {
|
||||
"whitelist": ["10.0.0.0/24"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
我们能看到配置 interceptors 就像配置 plugin 一样:name 是 interceptor 的名称,而 conf 是它的配置。
|
||||
|
||||
当前我们只支持一部分插件作为 interceptor 运行。
|
||||
|
||||
支持的 interceptor:
|
||||
|
||||
* [ip-restriction](./plugins/ip-restriction.md)
|
@ -40,6 +40,11 @@
|
||||
|
||||
无
|
||||
|
||||
## 接口
|
||||
|
||||
插件会增加 `/apisix/batch-requests` 这个接口,你可能需要通过 [interceptors](plugin-interceptors.md)
|
||||
来保护它。
|
||||
|
||||
## 如何启用
|
||||
|
||||
本插件默认启用。
|
||||
|
@ -46,6 +46,11 @@
|
||||
| exp | integer | 可选 | 86400 | [1,...] | token 的超时时间 |
|
||||
| base64_secret | boolean | 可选 | false | | 密钥是否为 base64 编码 |
|
||||
|
||||
## 接口
|
||||
|
||||
插件会增加 `/apisix/plugin/jwt/sign` 这个接口,你可能需要通过 [interceptors](plugin-interceptors.md)
|
||||
来保护它。
|
||||
|
||||
## 如何启用
|
||||
|
||||
1. 创建一个 consumer 对象,并设置插件 `jwt-auth` 的值。
|
||||
|
@ -27,6 +27,11 @@
|
||||
|
||||
无
|
||||
|
||||
## 接口
|
||||
|
||||
插件会增加 `/apisix/prometheus/metrics` 这个接口,你可能需要通过 [interceptors](plugin-interceptors.md)
|
||||
来保护它。
|
||||
|
||||
## 如何开启插件
|
||||
|
||||
`prometheus` 插件用空{}就可以开启了,他没有任何的选项。
|
||||
|
@ -42,6 +42,16 @@ rbac功能由[wolf](https://github.com/iGeeky/wolf)提供, 有关 `wolf` 的更
|
||||
| appid | string | 可选 | "unset" | | 设置应用id, 该应用id, 需要是在 `wolf-console` 中已经添加的应用id |
|
||||
| header_prefix | string | 可选 | "X-" | | 自定义http头的前缀。`wolf-rbac`在鉴权成功后, 会在请求头(用于传给后端)及响应头(用于传给前端)中添加3个头: `X-UserId`, `X-Username`, `X-Nickname` |
|
||||
|
||||
## 接口
|
||||
|
||||
插件会增加这些接口:
|
||||
|
||||
* /apisix/plugin/wolf-rbac/login
|
||||
* /apisix/plugin/wolf-rbac/change_pwd
|
||||
* /apisix/plugin/wolf-rbac/user_info
|
||||
|
||||
你可能需要通过 [interceptors](plugin-interceptors.md) 来保护它们。
|
||||
|
||||
## 依赖项
|
||||
|
||||
### 安装 wolf, 并启动服务
|
||||
|
@ -237,37 +237,7 @@ GET /t
|
||||
|
||||
|
||||
|
||||
=== TEST 8: no plugin metadata schema
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local t = require("lib.test_admin").test
|
||||
local code, body = t('/apisix/admin/plugin_metadata/echo',
|
||||
ngx.HTTP_PUT,
|
||||
[[{"k": "v"}]],
|
||||
[[{
|
||||
"node": {
|
||||
"value": "sdf"
|
||||
},
|
||||
"action": "set"
|
||||
}]]
|
||||
)
|
||||
|
||||
ngx.status = code
|
||||
ngx.print(body)
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- error_code: 400
|
||||
--- response_body
|
||||
{"error_msg":"no metadata schema for plugin echo"}
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 9: verify metadata schema fail
|
||||
=== TEST 8: verify metadata schema fail
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
@ -299,3 +269,185 @@ GET /t
|
||||
qr/\{"error_msg":"invalid configuration: property \\"ikey\\" is required"\}/
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 9: set plugin interceptors
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local t = require("lib.test_admin").test
|
||||
local code, body = t('/apisix/admin/plugin_metadata/prometheus',
|
||||
ngx.HTTP_PUT,
|
||||
[[{
|
||||
"interceptors": [
|
||||
{
|
||||
"name": "ip-restriction",
|
||||
"conf": {
|
||||
"whitelist": ["192.168.1.0/24"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}]]
|
||||
)
|
||||
|
||||
if code >= 300 then
|
||||
ngx.status = code
|
||||
end
|
||||
ngx.say(body)
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body
|
||||
passed
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 10: hit prometheus route
|
||||
--- request
|
||||
GET /apisix/prometheus/metrics
|
||||
-- error_code: 403
|
||||
|
||||
|
||||
|
||||
=== TEST 11: set plugin interceptors (allow ip access)
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local t = require("lib.test_admin").test
|
||||
local code, body = t('/apisix/admin/plugin_metadata/prometheus',
|
||||
ngx.HTTP_PUT,
|
||||
[[{
|
||||
"interceptors": [
|
||||
{
|
||||
"name": "ip-restriction",
|
||||
"conf": {
|
||||
"whitelist": ["127.0.0.1"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}]]
|
||||
)
|
||||
|
||||
if code >= 300 then
|
||||
ngx.status = code
|
||||
end
|
||||
ngx.say(body)
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body
|
||||
passed
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 12: hit prometheus route again
|
||||
--- request
|
||||
GET /apisix/prometheus/metrics
|
||||
-- error_code: 200
|
||||
|
||||
|
||||
|
||||
=== TEST 13: invalid interceptors configure (unknown interceptor)
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local t = require("lib.test_admin").test
|
||||
local code, body = t('/apisix/admin/plugin_metadata/prometheus',
|
||||
ngx.HTTP_PUT,
|
||||
[[{
|
||||
"interceptors": [
|
||||
{
|
||||
"name": "unknown",
|
||||
"conf": {
|
||||
"whitelist": ["127.0.0.1"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}]]
|
||||
)
|
||||
|
||||
if code >= 300 then
|
||||
ngx.status = code
|
||||
end
|
||||
ngx.say(body)
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- response_body eval
|
||||
qr/\{"error_msg":"invalid configuration: property \\"interceptors\\" validation failed: failed to validate item 1: property \\"name\\" validation failed: matches non of the enum values"\}/
|
||||
--- error_code: 400
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 14: invalid interceptors configure (missing conf)
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local t = require("lib.test_admin").test
|
||||
local code, body = t('/apisix/admin/plugin_metadata/prometheus',
|
||||
ngx.HTTP_PUT,
|
||||
[[{
|
||||
"interceptors": [
|
||||
{
|
||||
"name": "ip-restriction"
|
||||
}
|
||||
]
|
||||
}]]
|
||||
)
|
||||
|
||||
if code >= 300 then
|
||||
ngx.status = code
|
||||
end
|
||||
ngx.say(body)
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- error_code: 400
|
||||
--- response_body eval
|
||||
qr/\{"error_msg":"invalid configuration: property \\"interceptors\\" validation failed: failed to validate item 1: property \\"conf\\" is required"\}/
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
||||
|
||||
|
||||
=== TEST 15: invalid interceptors configure (invalid interceptor configure)
|
||||
--- config
|
||||
location /t {
|
||||
content_by_lua_block {
|
||||
local t = require("lib.test_admin").test
|
||||
local code, body = t('/apisix/admin/plugin_metadata/prometheus',
|
||||
ngx.HTTP_PUT,
|
||||
[[{
|
||||
"interceptors": [
|
||||
{
|
||||
"name": "ip-restriction",
|
||||
"conf": {"aa": "b"}
|
||||
}
|
||||
]
|
||||
}]]
|
||||
)
|
||||
|
||||
if code >= 300 then
|
||||
ngx.status = code
|
||||
end
|
||||
ngx.say(body)
|
||||
}
|
||||
}
|
||||
--- request
|
||||
GET /t
|
||||
--- error_code: 400
|
||||
--- response_body eval
|
||||
qr/\{"error_msg":"invalid configuration: property \\"interceptors\\" validation failed: failed to validate item 1: failed to validate dependent schema for \\"name\\": value should match only one schema, but matches none"\}/
|
||||
--- no_error_log
|
||||
[error]
|
||||
|
Loading…
Reference in New Issue
Block a user