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/06/22 06:46:01 UTC

[incubator-apisix] branch master updated: feature: implemented plugin `uri-blocklist` . (#1727)

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/incubator-apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 6a43a8c  feature: implemented plugin `uri-blocklist` . (#1727)
6a43a8c is described below

commit 6a43a8c735cc984df1d23ce1b6c4bc834c628d18
Author: YuanSheng Wang <me...@gmail.com>
AuthorDate: Mon Jun 22 14:45:55 2020 +0800

    feature: implemented plugin `uri-blocklist` . (#1727)
    
    first step: #1617
---
 README.md                      |   3 +-
 README_CN.md                   |   3 +-
 apisix/plugins/uri-blocker.lua |  86 +++++++++++++
 conf/config.yaml               |   3 +-
 doc/README.md                  |   4 +-
 doc/plugins/uri-blocker.md     |  96 +++++++++++++++
 doc/zh-cn/README.md            |   1 +
 t/admin/plugins.t              |   2 +-
 t/debug/debug-mode.t           |   1 +
 t/plugin/uri-blocker.t         | 266 +++++++++++++++++++++++++++++++++++++++++
 10 files changed, 460 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index da9ccf1..3572484 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,8 @@ 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)
+    - [CORS](doc/plugins/cors.md) Enable CORS(Cross-origin resource sharing) for your API.
+    - [uri-blocker](plugins/uri-blocker.md): Block client request by URI.
 
 - **OPS friendly**
     - OpenTracing: support [Apache Skywalking](doc/plugins/skywalking.md) and [Zipkin](doc/plugins/zipkin.md)
diff --git a/README_CN.md b/README_CN.md
index 1fbd998..408fc22 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -90,7 +90,8 @@ A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、限流限速、抵
     - [限制请求数](doc/zh-cn/plugins/limit-count.md)
     - [限制并发](doc/zh-cn/plugins/limit-conn.md)
     - 防御 ReDoS(正则表达式拒绝服务):内置策略,无需配置即可抵御 ReDoS。
-    - [CORS](doc/zh-cn/plugins/cors.md)
+    - [CORS](doc/zh-cn/plugins/cors.md):为你的API启用 CORS。
+    - [uri-blocker](plugins/uri-blocker.md):根据 URI 拦截用户请求。
 
 - **运维友好**
     - OpenTracing 可观测性: 支持 [Apache Skywalking](doc/zh-cn/plugins/skywalking.md) 和 [Zipkin](doc/zh-cn/plugins/zipkin.md)。
diff --git a/apisix/plugins/uri-blocker.lua b/apisix/plugins/uri-blocker.lua
new file mode 100644
index 0000000..ab5b682
--- /dev/null
+++ b/apisix/plugins/uri-blocker.lua
@@ -0,0 +1,86 @@
+--
+-- 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 re_compile = require("resty.core.regex").re_match_compile
+local re_find = ngx.re.find
+local ipairs = ipairs
+
+local schema = {
+    type = "object",
+    properties = {
+        block_rules = {
+            type = "array",
+            items = {
+                type = "string",
+                minLength = 1,
+                maxLength = 4096,
+            },
+            uniqueItems = true
+        },
+        rejected_code = {
+            type = "integer",
+            minimum = 200,
+            default = 403
+        },
+    },
+    required = {"block_rules"},
+}
+
+
+local plugin_name = "uri-blocker"
+
+local _M = {
+    version = 0.1,
+    priority = 2900,
+    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
+
+    local block_rules = {}
+    for i, re_rule in ipairs(conf.block_rules) do
+        local ok, err = re_compile(re_rule, "j")
+        -- core.log.warn("ok: ", tostring(ok), " err: ", tostring(err), " re_rule: ", re_rule)
+        if not ok then
+            return false, err
+        end
+        block_rules[i] = re_rule
+    end
+
+    conf.block_rules_concat = core.table.concat(block_rules, "|")
+    core.log.info("concat block_rules: ", conf.block_rules_concat)
+    return true
+end
+
+
+function _M.rewrite(conf, ctx)
+    core.log.info("uri: ", ctx.var.request_uri)
+    core.log.info("block uri rules: ", conf.block_rules_concat)
+    local from = re_find(ctx.var.request_uri, conf.block_rules_concat, "jo")
+    if from then
+        core.response.exit(conf.rejected_code)
+    end
+end
+
+
+return _M
diff --git a/conf/config.yaml b/conf/config.yaml
index d640ef7..cee95a6 100644
--- a/conf/config.yaml
+++ b/conf/config.yaml
@@ -95,7 +95,7 @@ apisix:
     ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES2 [...]
     key_encrypt_salt: "edd1c9f0985e76a2"    #  If not set, will save origin ssl key into etcd.
                                             #  If set this, must be a string of length 16. And it will encrypt ssl key with AES-128-CBC
-                                            #  !!! So do not change it after saving your ssl, it can't decrypt the ssl keys have be saved if you change !! 
+                                            #  !!! So do not change it after saving your ssl, it can't decrypt the ssl keys have be saved if you change !!
 #  discovery: eureka               # service discovery center
 nginx_config:                     # config for render the template to genarate nginx.conf
   error_log: "logs/error.log"
@@ -168,6 +168,7 @@ plugins:                          # plugin list
   - skywalking
   - echo
   - authz-keycloak
+  - uri-blocker
 
 stream_plugins:
   - mqtt-proxy
diff --git a/doc/README.md b/doc/README.md
index 042012d..3f9142a 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -65,10 +65,12 @@ Plugins
 * [kafka-logger](plugins/kafka-logger.md): Log requests to External Kafka servers.
 * [cors](plugins/cors.md): Enable CORS(Cross-origin resource sharing) for your API.
 * [batch-requests](plugins/batch-requests.md): Allow you send mutiple http api via **http pipeline**.
-* [authz-keycloak](plugins/authz-keycloak.md): Authorization with Keycloak Identity Server
+* [authz-keycloak](plugins/authz-keycloak.md): Authorization with Keycloak Identity Server.
+* [uri-blocker](plugins/uri-blocker.md): Block client request by URI.
 
 Deploy to the Cloud
 =======
+
 ### AWS
 
 The recommended approach is to deploy APISIX with [AWS CDK](https://aws.amazon.com/cdk/) on [AWS Fargate](https://aws.amazon.com/fargate/) which helps you decouple the APISIX layer and the upstream layer on top of a fully-managed and secure serverless container compute environment with autoscaling capabilities.
diff --git a/doc/plugins/uri-blocker.md b/doc/plugins/uri-blocker.md
new file mode 100644
index 0000000..270f303
--- /dev/null
+++ b/doc/plugins/uri-blocker.md
@@ -0,0 +1,96 @@
+<!--
+#
+# 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](uri-blocker.md)
+
+# Summary
+
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+
+## Name
+
+The plugin helps we intercept user requests, we only need to indicate the `block_rules`.
+
+## Attributes
+
+|Name          |Requirement  |Description|
+|---------     |--------|-----------|
+|block_rules  |required|Regular filter rule array. Each of these items is a regular rule. If the current request URI hits any one of them, set the response code to rejected_code to exit the current user request. Example: `["root.exe", "root.m+"]`.|
+|rejected_code |optional|The HTTP status code returned when the request URI hit any of `filter_rule`, default `403`.|
+
+## How To Enable
+
+Here's an example, enable the `uri blocker` plugin on the specified route:
+
+```shell
+curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/*",
+    "plugins": {
+        "uri-blocker": {
+            "block_rules": ["root.exe", "root.m+"]
+        }
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "127.0.0.1:1980": 1
+        }
+    }
+}'
+```
+
+## Test Plugin
+
+```shell
+$ curl -i http://127.0.0.1:9080/root.exe?a=a
+HTTP/1.1 403 Forbidden
+Date: Wed, 17 Jun 2020 13:55:41 GMT
+Content-Type: text/html; charset=utf-8
+Content-Length: 150
+Connection: keep-alive
+Server: APISIX web server
+
+... ...
+```
+
+## Disable Plugin
+
+When you want to disable the `uri blocker` plugin, it is very simple,
+ you can delete the corresponding json configuration in the plugin configuration,
+  no need to restart the service, it will take effect immediately:
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/*",
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "127.0.0.1:1980": 1
+        }
+    }
+}'
+```
+
+The `uri blocker` plugin has been disabled now. It works for other plugins.
diff --git a/doc/zh-cn/README.md b/doc/zh-cn/README.md
index 2d9965c..9b2c6c5 100644
--- a/doc/zh-cn/README.md
+++ b/doc/zh-cn/README.md
@@ -67,3 +67,4 @@ Reference document
 * [cors](plugins/cors.md): 为你的API启用 CORS
 * [batch-requests](plugins/batch-requests.md): 以 **http pipeline** 的方式在网关一次性发起多个 `http` 请求。
 * [authz-keycloak](plugins/authz-keycloak-cn.md): 支持 Keycloak 身份认证服务器
+* [uri-blocker](plugins/uri-blocker.md): 根据 URI 拦截用户请求。
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index 1a5f06e..b134b97 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -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","cors","consumer-restriction","syslog","batch-requests","http-logger","skywalking","echo","authz-keycloak"\]/
+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","consumer-restriction","syslog","batch-requests","http-logger","skywalking","echo","authz-keycloak","uri-blocker"\]/
 --- no_error_log
 [error]
 
diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t
index 2924cdc..05534e1 100644
--- a/t/debug/debug-mode.t
+++ b/t/debug/debug-mode.t
@@ -60,6 +60,7 @@ loaded plugin and sort by priority: 10000 name: serverless-pre-function
 loaded plugin and sort by priority: 4010 name: batch-requests
 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: 2900 name: uri-blocker
 loaded plugin and sort by priority: 2599 name: openid-connect
 loaded plugin and sort by priority: 2555 name: wolf-rbac
 loaded plugin and sort by priority: 2520 name: basic-auth
diff --git a/t/plugin/uri-blocker.t b/t/plugin/uri-blocker.t
new file mode 100644
index 0000000..5f10640
--- /dev/null
+++ b/t/plugin/uri-blocker.t
@@ -0,0 +1,266 @@
+#
+# 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.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(2);
+no_long_string();
+no_root_location();
+no_shuffle();
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: invalid regular expression
+--- 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,
+            [[{
+                "plugins": {
+                    "uri-blocker": {
+                        "block_rules": [".+("]
+                    }
+                },
+                "uri": "/hello"
+            }]])
+
+        if code >= 300 then
+            ngx.status = code
+        end
+        ngx.print(body)
+    }
+}
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"failed to check the configuration of plugin uri-blocker err: pcre_compile() failed: missing ) in \".+(\""}
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: multiple valid rules
+--- 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,
+            [[{
+                "plugins": {
+                    "uri-blocker": {
+                        "block_rules": ["^a", "^b"]
+                    }
+                },
+                "uri": "/hello"
+            }]]
+            )
+
+        if code >= 300 then
+            ngx.status = code
+        end
+        ngx.say(body)
+    }
+}
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+--- error_log
+concat block_rules: ^a|^b,
+
+
+
+=== TEST 3: multiple rules(include one invalid rule)
+--- 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,
+            [[{
+                "plugins": {
+                    "uri-blocker": {
+                        "block_rules": ["^a", "^b("]
+                    }
+                },
+                "uri": "/hello"
+            }]]
+            )
+
+        if code >= 300 then
+            ngx.status = code
+        end
+        ngx.print(body)
+    }
+}
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"failed to check the configuration of plugin uri-blocker err: pcre_compile() failed: missing ) in \"^b(\""}
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: sanity
+--- 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,
+            [[{
+                "plugins": {
+                    "uri-blocker": {
+                        "block_rules": ["aa"]
+                    }
+                },
+                "upstream": {
+                    "nodes": {
+                        "127.0.0.1:1980": 1
+                    },
+                    "type": "roundrobin"
+                },
+                "uri": "/hello"
+            }]],
+            [[{
+                "node": {
+                    "value": {
+                        "plugins": {
+                            "uri-blocker": {
+                                "block_rules": ["aa"]
+                            }
+                        }
+                    }
+                }
+            }]]
+            )
+
+        if code >= 300 then
+            ngx.status = code
+        end
+        ngx.say(body)
+    }
+}
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+--- error_log
+concat block_rules: aa,
+
+
+
+=== TEST 5: hit block rule
+--- request
+GET /hello?aa=1
+--- error_code: 403
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: miss block rule
+--- request
+GET /hello?bb=2
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: multiple block rules
+--- 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,
+            [[{
+                "plugins": {
+                    "uri-blocker": {
+                        "block_rules": ["aa", "bb", "c\\d+"]
+                    }
+                },
+                "upstream": {
+                    "nodes": {
+                        "127.0.0.1:1980": 1
+                    },
+                    "type": "roundrobin"
+                },
+                "uri": "/hello"
+            }]]
+        )
+
+        if code >= 300 then
+            ngx.status = code
+        end
+        ngx.say(body)
+    }
+}
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+--- error_log
+concat block_rules: aa|bb|c\d+,
+
+
+
+=== TEST 8: hit block rule
+--- request
+GET /hello?x=bb
+--- error_code: 403
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: hit block rule
+--- request
+GET /hello?bb=2
+--- error_code: 403
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: hit block rule
+--- request
+GET /hello?c1=2
+--- error_code: 403
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: not hit block rule
+--- request
+GET /hello?cc=2
+--- no_error_log
+[error]