From fe54d07d0db082989dd80c5105e69e5acbd9bdac Mon Sep 17 00:00:00 2001 From: spacewander Date: Wed, 14 Oct 2020 15:36:45 +0800 Subject: [PATCH 1/4] feat: use interceptors to protect plugin's route --- apisix/admin/plugin_metadata.lua | 22 +++- apisix/api_router.lua | 72 +++++++++++- t/admin/plugin-metadata.t | 190 +++++++++++++++++++++++++++---- 3 files changed, 255 insertions(+), 29 deletions(-) diff --git a/apisix/admin/plugin_metadata.lua b/apisix/admin/plugin_metadata.lua index 82f18101a78d..7b72ff1e107d 100644 --- a/apisix/admin/plugin_metadata.lua +++ b/apisix/admin/plugin_metadata.lua @@ -17,6 +17,7 @@ local pcall = pcall local require = require local core = require("apisix.core") +local api_router = require("apisix.api_router") local _M = { } @@ -44,15 +45,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) diff --git a/apisix/api_router.lua b/apisix/api_router.lua index b18e823ad956..55c13be20aa5 100644 --- a/apisix/api_router.lua +++ b/apisix/api_router.lua @@ -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 diff --git a/t/admin/plugin-metadata.t b/t/admin/plugin-metadata.t index 66d6822cbc4c..d583d43291fd 100644 --- a/t/admin/plugin-metadata.t +++ b/t/admin/plugin-metadata.t @@ -237,58 +237,177 @@ GET /t -=== TEST 8: no plugin metadata schema +=== TEST 8: verify metadata schema fail --- 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"}]], + local code, body = t('/apisix/admin/plugin_metadata/example-plugin', + ngx.HTTP_PUT, + [[{ + "skey": "val" + }]], [[{ "node": { - "value": "sdf" + "value": { + "skey": "val", + "ikey": 1 + } }, "action": "set" }]] ) ngx.status = code - ngx.print(body) + ngx.say(body) } } --- request GET /t --- error_code: 400 +--- response_body eval +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 -{"error_msg":"no metadata schema for plugin echo"} +passed --- no_error_log [error] -=== TEST 9: verify metadata schema fail +=== 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/example-plugin', + local code, body = t('/apisix/admin/plugin_metadata/prometheus', ngx.HTTP_PUT, [[{ - "skey": "val" - }]], + "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, [[{ - "node": { - "value": { - "skey": "val", - "ikey": 1 + "interceptors": [ + { + "name": "unknown", + "conf": { + "whitelist": ["127.0.0.1"] } - }, - "action": "set" + } + ] }]] ) - ngx.status = code + 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) } } @@ -296,6 +415,39 @@ GET /t GET /t --- error_code: 400 --- response_body eval -qr/\{"error_msg":"invalid configuration: property \\"ikey\\" is required"\}/ +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] From 15536524f1ce62d941c103d369798658db494ab8 Mon Sep 17 00:00:00 2001 From: spacewander Date: Wed, 14 Oct 2020 15:44:21 +0800 Subject: [PATCH 2/4] localize --- apisix/admin/plugin_metadata.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/apisix/admin/plugin_metadata.lua b/apisix/admin/plugin_metadata.lua index 7b72ff1e107d..d138115cab6e 100644 --- a/apisix/admin/plugin_metadata.lua +++ b/apisix/admin/plugin_metadata.lua @@ -14,6 +14,7 @@ -- 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") From 80396ede440ba396a6b025f73aeaeb21e8578b6c Mon Sep 17 00:00:00 2001 From: spacewander Date: Wed, 14 Oct 2020 17:03:29 +0800 Subject: [PATCH 3/4] tweak --- t/admin/plugin-metadata.t | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/t/admin/plugin-metadata.t b/t/admin/plugin-metadata.t index d583d43291fd..379cdb22ead2 100644 --- a/t/admin/plugin-metadata.t +++ b/t/admin/plugin-metadata.t @@ -282,10 +282,10 @@ qr/\{"error_msg":"invalid configuration: property \\"ikey\\" is required"\}/ [[{ "interceptors": [ { - "name": "ip-restriction", - "conf": { - "whitelist": ["192.168.1.0/24"] - } + "name": "ip-restriction", + "conf": { + "whitelist": ["192.168.1.0/24"] + } } ] }]] @@ -323,10 +323,10 @@ GET /apisix/prometheus/metrics [[{ "interceptors": [ { - "name": "ip-restriction", - "conf": { - "whitelist": ["127.0.0.1"] - } + "name": "ip-restriction", + "conf": { + "whitelist": ["127.0.0.1"] + } } ] }]] @@ -364,10 +364,10 @@ GET /apisix/prometheus/metrics [[{ "interceptors": [ { - "name": "unknown", - "conf": { - "whitelist": ["127.0.0.1"] - } + "name": "unknown", + "conf": { + "whitelist": ["127.0.0.1"] + } } ] }]] @@ -399,7 +399,7 @@ qr/\{"error_msg":"invalid configuration: property \\"interceptors\\" validation [[{ "interceptors": [ { - "name": "ip-restriction" + "name": "ip-restriction" } ] }]] @@ -431,8 +431,8 @@ qr/\{"error_msg":"invalid configuration: property \\"interceptors\\" validation [[{ "interceptors": [ { - "name": "ip-restriction", - "conf": {"aa": "b"} + "name": "ip-restriction", + "conf": {"aa": "b"} } ] }]] From f6e27021ded200e50f22224c4c4fb81c91b32c4c Mon Sep 17 00:00:00 2001 From: spacewander Date: Fri, 16 Oct 2020 11:04:59 +0800 Subject: [PATCH 4/4] doc --- doc/plugin-interceptors.md | 55 +++++++++++++++++++++++++++++ doc/plugins/batch-requests.md | 5 +++ doc/plugins/jwt-auth.md | 5 +++ doc/plugins/prometheus.md | 5 +++ doc/plugins/wolf-rbac.md | 9 +++++ doc/zh-cn/plugin-interceptors.md | 50 ++++++++++++++++++++++++++ doc/zh-cn/plugins/batch-requests.md | 5 +++ doc/zh-cn/plugins/jwt-auth.md | 5 +++ doc/zh-cn/plugins/prometheus.md | 5 +++ doc/zh-cn/plugins/wolf-rbac.md | 10 ++++++ 10 files changed, 154 insertions(+) create mode 100644 doc/plugin-interceptors.md create mode 100644 doc/zh-cn/plugin-interceptors.md diff --git a/doc/plugin-interceptors.md b/doc/plugin-interceptors.md new file mode 100644 index 000000000000..1a30a08f630b --- /dev/null +++ b/doc/plugin-interceptors.md @@ -0,0 +1,55 @@ + + +[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) diff --git a/doc/plugins/batch-requests.md b/doc/plugins/batch-requests.md index 16b621ca815e..480d60ac680d 100644 --- a/doc/plugins/batch-requests.md +++ b/doc/plugins/batch-requests.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 diff --git a/doc/plugins/jwt-auth.md b/doc/plugins/jwt-auth.md index 1f5aa1e1cfc0..cf004af99f95 100644 --- a/doc/plugins/jwt-auth.md +++ b/doc/plugins/jwt-auth.md @@ -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 diff --git a/doc/plugins/prometheus.md b/doc/plugins/prometheus.md index bca881302381..b07656a28430 100644 --- a/doc/plugins/prometheus.md +++ b/doc/plugins/prometheus.md @@ -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 diff --git a/doc/plugins/wolf-rbac.md b/doc/plugins/wolf-rbac.md index 70a4fec6466d..ec3207da99a5 100644 --- a/doc/plugins/wolf-rbac.md +++ b/doc/plugins/wolf-rbac.md @@ -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 diff --git a/doc/zh-cn/plugin-interceptors.md b/doc/zh-cn/plugin-interceptors.md new file mode 100644 index 000000000000..42b611b2709f --- /dev/null +++ b/doc/zh-cn/plugin-interceptors.md @@ -0,0 +1,50 @@ + + +[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) diff --git a/doc/zh-cn/plugins/batch-requests.md b/doc/zh-cn/plugins/batch-requests.md index f4927a4f7d34..cdf3cef8c233 100644 --- a/doc/zh-cn/plugins/batch-requests.md +++ b/doc/zh-cn/plugins/batch-requests.md @@ -40,6 +40,11 @@ 无 +## 接口 + +插件会增加 `/apisix/batch-requests` 这个接口,你可能需要通过 [interceptors](plugin-interceptors.md) +来保护它。 + ## 如何启用 本插件默认启用。 diff --git a/doc/zh-cn/plugins/jwt-auth.md b/doc/zh-cn/plugins/jwt-auth.md index 2d431e421815..8047a700e17f 100644 --- a/doc/zh-cn/plugins/jwt-auth.md +++ b/doc/zh-cn/plugins/jwt-auth.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` 的值。 diff --git a/doc/zh-cn/plugins/prometheus.md b/doc/zh-cn/plugins/prometheus.md index 768f6363c65b..651ca14369e9 100644 --- a/doc/zh-cn/plugins/prometheus.md +++ b/doc/zh-cn/plugins/prometheus.md @@ -27,6 +27,11 @@ 无 +## 接口 + +插件会增加 `/apisix/prometheus/metrics` 这个接口,你可能需要通过 [interceptors](plugin-interceptors.md) +来保护它。 + ## 如何开启插件 `prometheus` 插件用空{}就可以开启了,他没有任何的选项。 diff --git a/doc/zh-cn/plugins/wolf-rbac.md b/doc/zh-cn/plugins/wolf-rbac.md index d7a2b8baa561..a0a80303b088 100644 --- a/doc/zh-cn/plugins/wolf-rbac.md +++ b/doc/zh-cn/plugins/wolf-rbac.md @@ -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, 并启动服务