You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by me...@apache.org on 2020/12/10 14:54:01 UTC
[apisix] branch master updated: feat: improve jsonschema validation
on route resource (#3008)
This is an automated email from the ASF dual-hosted git repository.
membphis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 0eacf3a feat: improve jsonschema validation on route resource (#3008)
0eacf3a is described below
commit 0eacf3af7dd12d5cd7ba1e2d8e9b4ba1833218b1
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Thu Dec 10 22:53:53 2020 +0800
feat: improve jsonschema validation on route resource (#3008)
fix #2671
fix #2991
---
apisix/admin/routes.lua | 9 +--
apisix/schema_def.lua | 35 ++++++++++
doc/admin-api.md | 11 ++--
doc/zh-cn/admin-api.md | 6 +-
t/admin/routes2.t | 169 ++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 218 insertions(+), 12 deletions(-)
diff --git a/apisix/admin/routes.lua b/apisix/admin/routes.lua
index f54a090..e5b2cb1 100644
--- a/apisix/admin/routes.lua
+++ b/apisix/admin/routes.lua
@@ -51,10 +51,6 @@ local function check_conf(id, conf, need_id)
core.log.info("schema: ", core.json.delay_encode(core.schema.route))
core.log.info("conf : ", core.json.delay_encode(conf))
- local ok, err = core.schema.check(core.schema.route, conf)
- if not ok then
- return nil, {error_msg = "invalid configuration: " .. err}
- end
if conf.host and conf.hosts then
return nil, {error_msg = "only one of host or hosts is allowed"}
@@ -65,6 +61,11 @@ local function check_conf(id, conf, need_id)
.. "allowed"}
end
+ local ok, err = core.schema.check(core.schema.route, conf)
+ if not ok then
+ return nil, {error_msg = "invalid configuration: " .. err}
+ end
+
local upstream_conf = conf.upstream
if upstream_conf then
local ok, err = upstreams.check_upstream_conf(upstream_conf)
diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index 57f0202..aaa9959 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -421,6 +421,7 @@ _M.route = {
description = "HTTP uri",
type = "string",
},
+ minItems = 1,
uniqueItems = true,
},
name = rule_name_def,
@@ -441,12 +442,14 @@ _M.route = {
hosts = {
type = "array",
items = host_def,
+ minItems = 1,
uniqueItems = true,
},
remote_addr = remote_addr_def,
remote_addrs = {
type = "array",
items = remote_addr_def,
+ minItems = 1,
uniqueItems = true,
},
vars = {
@@ -504,6 +507,38 @@ _M.route = {
default = 1
},
},
+ allOf = {
+ {
+ oneOf = {
+ {required = {"uri"}},
+ {required = {"uris"}},
+ },
+ },
+ {
+ oneOf = {
+ {["not"] = {
+ anyOf = {
+ {required = {"host"}},
+ {required = {"hosts"}},
+ }
+ }},
+ {required = {"host"}},
+ {required = {"hosts"}}
+ },
+ },
+ {
+ oneOf = {
+ {["not"] = {
+ anyOf = {
+ {required = {"remote_addr"}},
+ {required = {"remote_addrs"}},
+ }
+ }},
+ {required = {"remote_addr"}},
+ {required = {"remote_addrs"}}
+ },
+ },
+ },
anyOf = {
{required = {"plugins", "uri"}},
{required = {"upstream", "uri"}},
diff --git a/doc/admin-api.md b/doc/admin-api.md
index ba9f5df..7085b2d 100644
--- a/doc/admin-api.md
+++ b/doc/admin-api.md
@@ -55,11 +55,12 @@
|---------|---------|----|-----------|----|
|name |False |Auxiliary |Identifies route names.|customer-xxxx|
|desc |False |Auxiliary |route description, usage scenarios, and more.|customer xxxx|
-|uri |True |Match Rules|In addition to full matching such as `/foo/bar`、`/foo/gloo`, using different [Router](architecture-design.md#router) allows more advanced matching, see [Router](architecture-design.md#router) for more.|"/hello"|
-|host |False |Match Rules|Currently requesting a domain name, such as `foo.com`; pan-domain names such as `*.foo.com` are also supported.|"foo.com"|
-|hosts |False |Match Rules|The `host` in the form of a list means that multiple different hosts are allowed, and match any one of them.|{"foo.com", "*.bar.com"}|
-|remote_addr|False |Match Rules|The client requests an IP address: `192.168.1.101`, `192.168.1.102`, and CIDR format support `192.168.1.0/24`. In particular, APISIX also fully supports IPv6 address matching: `::1`, `fe80::1`, `fe80::1/64`, etc.|"192.168.1.0/24"|
-|remote_addrs|False |Match Rules|The `remote_addr` in the form of a list indicates that multiple different IP addresses are allowed, and match any one of them.|{"127.0.0.1", "192.0.0.0/8", "::1"}|
+|uri |True, can't be used with `uris` |Match Rules|In addition to full matching such as `/foo/bar`、`/foo/gloo`, using different [Router](architecture-design.md#router) allows more advanced matching, see [Router](architecture-design.md#router) for more.|"/hello"|
+|uris |True, can't be used with `uri`|Match Rules|The `uri` in the form of a non-empty list means that multiple different uris are allowed, and match any one of them.|["/hello", "/word"]|
+|host |False, can't be used with `hosts` |Match Rules|Currently requesting a domain name, such as `foo.com`; PAN domain names such as `*.foo.com` are also supported.|"foo.com"|
+|hosts |False, can't be used with `host` |Match Rules|The `host` in the form of a non-empty list means that multiple different hosts are allowed, and match any one of them.|{"foo.com", "*.bar.com"}|
+|remote_addr|False, can't be used with `remote_addrs` |Match Rules|The client requests an IP address: `192.168.1.101`, `192.168.1.102`, and CIDR format support `192.168.1.0/24`. In particular, APISIX also fully supports IPv6 address matching: `::1`, `fe80::1`, `fe80::1/64`, etc.|"192.168.1.0/24"|
+|remote_addrs|False, can't be used with `remote_addr` |Match Rules|The `remote_addr` in the form of a non-empty list indicates that multiple different IP addresses are allowed, and match any one of them.|{"127.0.0.1", "192.0.0.0/8", "::1"}|
|methods |False |Match Rules|If empty or without this option, there are no `method` restrictions, and it can be a combination of one or more: `GET`,`POST`,`PUT`,`DELETE`,`PATCH`, `HEAD`,`OPTIONS`,`CONNECT`,`TRACE`.|{"GET", "POST"}|
|priority |False |Match Rules|If different routes contain the same `uri`, determine which route is matched first based on the attribute` priority`. Larger value means higher priority. The default value is 0.|priority = 10|
|vars |False |Match Rules |A list of one or more `{var, operator, val}` elements, like this: `{{var, operator, val}, {var, operator, val}, ...}}`. For example: `{"arg_name", "==", "json"}` means that the current request parameter `name` is `json`. The `var` here is consistent with the internal variable name of Nginx, so you can also use `request_uri`, `host`, etc. For the operator part, the currently supported operators are `==`, `~=`,`>`, `<`, and `~~`. For the `>` and `<` operat [...]
diff --git a/doc/zh-cn/admin-api.md b/doc/zh-cn/admin-api.md
index 0710345..de519b2 100644
--- a/doc/zh-cn/admin-api.md
+++ b/doc/zh-cn/admin-api.md
@@ -57,7 +57,7 @@
|名字 |可选项 |类型 |说明 |示例|
|---------|---------|----|-----------|----|
|uri |与 `uris` 二选一 |匹配规则|除了如 `/foo/bar`、`/foo/gloo` 这种全量匹配外,使用不同 [Router](architecture-design.md#router) 还允许更高级匹配,更多见 [Router](architecture-design.md#router)。|"/hello"|
-|uris |与 `uri` 二选一 |匹配规则|数组形式,可以匹配多个 `uri`|["/hello", "/world"]|
+|uris |与 `uri` 二选一 |匹配规则|非空数组形式,可以匹配多个 `uri`|["/hello", "/world"]|
|plugins |`plugins`、`script`、`upstream`/`upstream_id`、`service_id`至少选择一个 |Plugin|详见 [Plugin](architecture-design.md#plugin) ||
|script |`plugins`、`script`、`upstream`/`upstream_id`、`service_id`至少选择一个 |Script|详见 [Script](architecture-design.md#script) ||
|upstream |`plugins`、`script`、`upstream`/`upstream_id`、`service_id`至少选择一个 |Upstream|启用的 Upstream 配置,详见 [Upstream](architecture-design.md#upstream)||
@@ -67,9 +67,9 @@
|name |可选 |辅助 |标识路由名称|route-xxxx|
|desc |可选 |辅助 |标识描述、使用场景等。|客户 xxxx|
|host |可选 |匹配规则|当前请求域名,比如 `foo.com`;也支持泛域名,比如 `*.foo.com`。|"foo.com"|
-|hosts |可选 |匹配规则|列表形态的 `host`,表示允许有多个不同 `host`,匹配其中任意一个即可。|{"foo.com", "*.bar.com"}|
+|hosts |可选 |匹配规则|非空列表形态的 `host`,表示允许有多个不同 `host`,匹配其中任意一个即可。|{"foo.com", "*.bar.com"}|
|remote_addr|可选 |匹配规则|客户端请求 IP 地址: `192.168.1.101`、`192.168.1.102` 以及 CIDR 格式的支持 `192.168.1.0/24`。特别的,APISIX 也完整支持 IPv6 地址匹配:`::1`,`fe80::1`, `fe80::1/64` 等。|"192.168.1.0/24"|
-|remote_addrs|可选 |匹配规则|列表形态的 `remote_addr`,表示允许有多个不同 IP 地址,符合其中任意一个即可。|{"127.0.0.1", "192.0.0.0/8", "::1"}|
+|remote_addrs|可选 |匹配规则|非空列表形态的 `remote_addr`,表示允许有多个不同 IP 地址,符合其中任意一个即可。|{"127.0.0.1", "192.0.0.0/8", "::1"}|
|methods |可选 |匹配规则|如果为空或没有该选项,代表没有任何 `method` 限制,也可以是一个或多个的组合:`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`,`CONNECT`,`TRACE`。|{"GET", "POST"}|
|priority |可选 |匹配规则|如果不同路由包含相同 `uri`,根据属性 `priority` 确定哪个 `route` 被优先匹配,值越大优先级越高,默认值为 0。|priority = 10|
|vars |可选 |匹配规则|由一个或多个`{var, operator, val}`元素组成的列表,类似这样:`{{var, operator, val}, {var, operator, val}, ...}}`。例如:`{"arg_name", "==", "json"}`,表示当前请求参数 `name` 是 `json`。这里的 `var` 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 `request_uri`、`host` 等;对于 `operator` 部分,目前已支持的运算符有 `==`、`~=`、`>`、`<` 和 `~~`。对于`>`和`<`两个运算符,会把结果先转换成 number 然后再做比较。查看支持的[运算符列表](#运算符列表)|{{"arg_name", "==", "json"}, {"arg_age", ">", 18}}|
diff --git a/t/admin/routes2.t b/t/admin/routes2.t
index d4b77f6..7357250 100644
--- a/t/admin/routes2.t
+++ b/t/admin/routes2.t
@@ -349,3 +349,172 @@ GET /t
{"action":"delete","deleted":"1","key":"/apisix/routes/1","node":{}}
--- no_error_log
[error]
+
+
+
+=== TEST 10: invalid route: empty remote_addrs
+--- 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,
+ [[{
+ "methods": ["GET"],
+ "remote_addrs": [],
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:8080": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/index.html"
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body_like eval
+qr/property \\"remote_addrs\\" validation failed:/
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: invalid route: empty uris
+--- 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,
+ [[{
+ "methods": ["GET"],
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:8080": 1
+ },
+ "type": "roundrobin"
+ },
+ "uris": []
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body_like eval
+qr/property \\"uris\\" validation failed:/
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: invalid route: empty hosts
+--- 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,
+ [[{
+ "methods": ["GET"],
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:8080": 1
+ },
+ "type": "roundrobin"
+ },
+ "hosts": [],
+ "uri": "/"
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body_like eval
+qr/property \\"hosts\\" validation failed:/
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: invalid route: uris & uri
+--- 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,
+ [[{
+ "methods": ["GET"],
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:8080": 1
+ },
+ "type": "roundrobin"
+ },
+ "uris": ["/"],
+ "uri": "/"
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body_like eval
+qr/value should match only one schema/
+--- no_error_log
+[error]
+
+
+
+=== TEST 14: enable remote_addrs and remote_addr together
+--- 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,
+ [[{
+ "uri": "/index.html",
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:8080": 1
+ },
+ "type": "roundrobin"
+ },
+ "remote_addr": "127.0.0.1",
+ "remote_addrs": ["127.0.0.1"]
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"only one of remote_addr or remote_addrs is allowed"}
+--- no_error_log
+[error]