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]