You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by mo...@apache.org on 2023/04/20 06:22:32 UTC

[apisix] branch master updated: feat: limit-count plugin supports username and ssl for redis policy (#9185)

This is an automated email from the ASF dual-hosted git repository.

monkeydluffy 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 41dc764f8 feat: limit-count plugin supports username and ssl for redis policy (#9185)
41dc764f8 is described below

commit 41dc764f82607fdcfff28075476d337050c4288c
Author: Simon FLURY <18...@users.noreply.github.com>
AuthorDate: Thu Apr 20 08:22:26 2023 +0200

    feat: limit-count plugin supports username and ssl for redis policy (#9185)
---
 apisix/plugins/limit-count/init.lua              |   9 ++
 apisix/plugins/limit-count/limit-count-redis.lua |  14 +-
 ci/pod/docker-compose.plugin.yml                 |   9 ++
 docs/en/latest/plugins/limit-count.md            |   3 +
 docs/zh/latest/plugins/limit-count.md            |   3 +
 t/plugin/limit-count-redis.t                     | 170 ++++++++++++++++++++++-
 t/plugin/limit-count-redis3.t                    | 115 +++++++++++++++
 7 files changed, 320 insertions(+), 3 deletions(-)

diff --git a/apisix/plugins/limit-count/init.lua b/apisix/plugins/limit-count/init.lua
index 58fbf7969..40b18c17a 100644
--- a/apisix/plugins/limit-count/init.lua
+++ b/apisix/plugins/limit-count/init.lua
@@ -50,6 +50,9 @@ local policy_to_additional_properties = {
             redis_port = {
                 type = "integer", minimum = 1, default = 6379,
             },
+            redis_username = {
+                type = "string", minLength = 1,
+            },
             redis_password = {
                 type = "string", minLength = 0,
             },
@@ -59,6 +62,12 @@ local policy_to_additional_properties = {
             redis_timeout = {
                 type = "integer", minimum = 1, default = 1000,
             },
+            redis_ssl = {
+                type = "boolean", default = false,
+            },
+            redis_ssl_verify = {
+                type = "boolean", default = false,
+            },
         },
         required = {"redis_host"},
     },
diff --git a/apisix/plugins/limit-count/limit-count-redis.lua b/apisix/plugins/limit-count/limit-count-redis.lua
index bed920229..f2e441d61 100644
--- a/apisix/plugins/limit-count/limit-count-redis.lua
+++ b/apisix/plugins/limit-count/limit-count-redis.lua
@@ -44,7 +44,12 @@ local function redis_cli(conf)
 
     red:set_timeouts(timeout, timeout, timeout)
 
-    local ok, err = red:connect(conf.redis_host, conf.redis_port or 6379)
+    local sock_opts = {
+        ssl = conf.redis_ssl,
+        ssl_verify = conf.redis_ssl_verify
+    }
+
+    local ok, err = red:connect(conf.redis_host, conf.redis_port or 6379, sock_opts)
     if not ok then
         return false, err
     end
@@ -53,7 +58,12 @@ local function redis_cli(conf)
     count, err = red:get_reused_times()
     if 0 == count then
         if conf.redis_password and conf.redis_password ~= '' then
-            local ok, err = red:auth(conf.redis_password)
+            local ok, err
+            if conf.redis_username then
+                ok, err = red:auth(conf.redis_username, conf.redis_password)
+            else
+                ok, err = red:auth(conf.redis_password)
+            end
             if not ok then
                 return nil, err
             end
diff --git a/ci/pod/docker-compose.plugin.yml b/ci/pod/docker-compose.plugin.yml
index e7878ab61..c0f102f88 100644
--- a/ci/pod/docker-compose.plugin.yml
+++ b/ci/pod/docker-compose.plugin.yml
@@ -23,8 +23,17 @@ services:
     # The latest image is the latest stable version
     image: redis:latest
     restart: unless-stopped
+    volumes:
+      - ./t/certs:/certs
+    command: "--tls-port 6380 \
+            --tls-cert-file /certs/mtls_server.crt \
+            --tls-key-file /certs/mtls_server.key \
+            --tls-ca-cert-file /certs/mtls_ca.crt \
+            --tls-auth-clients no \
+            --user alice on +@all ~* \\&* \\>somepassword"
     ports:
       - "6379:6379"
+      - "6380:6380"
     networks:
       apisix_net:
 
diff --git a/docs/en/latest/plugins/limit-count.md b/docs/en/latest/plugins/limit-count.md
index b32cad88f..c67b0832d 100644
--- a/docs/en/latest/plugins/limit-count.md
+++ b/docs/en/latest/plugins/limit-count.md
@@ -46,7 +46,10 @@ The `limit-count` Plugin limits the number of requests to your service by a give
 | group                   | string  | False                                     |               | non-empty                              | Group to share the counter with. Routes configured with the same group will share the counter.                                                                                                                                                                                                                                                                    [...]
 | redis_host              | string  | required when `policy` is `redis`         |               |                                        | Address of the Redis server. Used when the `policy` attribute is set to `redis`.                                                                                                                                                                                                                                                                                  [...]
 | redis_port              | integer | False                                     | 6379          | [1,...]                                | Port of the Redis server. Used when the `policy` attribute is set to `redis`.                                                                                                                                                                                                                                                                                     [...]
+| redis_username          | string  | False                                     |               |                                        | Username use to authenticate to the Redis server for version >= 6.0, use only `redis_password` for the versions prior. Used when the `policy` is set to `redis`.                                                                                                                                                                                                  [...]
 | redis_password          | string  | False                                     |               |                                        | Password of the Redis server. Used when the `policy` is set to `redis` or `redis-cluster`.                                                                                                                                                                                                                                                                        [...]
+| redis_ssl               | boolean | False                                     | false         |                                        | If set to `true`, then uses SSL to connect to redis instance. Used when the `policy` attribute is set to `redis`.                                                                                                                                                                                                                                                 [...]
+| redis_ssl_verify        | boolean | False                                     | false         |                                        | If set to `true`, then verifies the validity of the server SSL certificate. Used when the `policy` attribute is set to `redis`. See [tcpsock:sslhandshake](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake).                                                                                                                                    [...]
 | redis_database          | integer | False                                     | 0             | redis_database >= 0                    | Selected database of the Redis server (for single instance operation or when using Redis cloud with a single entrypoint). Used when the `policy` attribute is set to `redis`.                                                                                                                                                                                     [...]
 | redis_timeout           | integer | False                                     | 1000          | [1,...]                                | Timeout in milliseconds for any command submitted to the Redis server. Used when the `policy` attribute is set to `redis` or `redis-cluster`.                                                                                                                                                                                                                     [...]
 | redis_cluster_nodes     | array   | required when `policy` is `redis-cluster` |               |                                        | Addresses of Redis cluster nodes. Used when the `policy` attribute is set to `redis-cluster`.                                                                                                                                                                                                                                                                     [...]
diff --git a/docs/zh/latest/plugins/limit-count.md b/docs/zh/latest/plugins/limit-count.md
index b70f4631a..c52b6ea65 100644
--- a/docs/zh/latest/plugins/limit-count.md
+++ b/docs/zh/latest/plugins/limit-count.md
@@ -47,7 +47,10 @@ description: 本文介绍了 Apache APISIX limit-count 插件的相关操作,
 | group               | string | 否         |               | 非空                                    | 配置相同 group 的路由将共享相同的限流计数器。 |
 | redis_host          | string  | 否        |               |                                         | 当使用 `redis` 限速策略时,Redis 服务节点的地址。**当 `policy` 属性设置为 `redis` 时必选。**|
 | redis_port          | integer | 否        | 6379          | [1,...]                                 | 当使用 `redis` 限速策略时,Redis 服务节点的端口。|
+| redis_username      | string  | 否        |               |                                         | Redis 服务器的用户名。当 `policy` 设置为 `redis` 时使用。|
 | redis_password      | string  | 否        |               |                                         | 当使用 `redis`  或者 `redis-cluster`  限速策略时,Redis 服务节点的密码。|
+| redis_ssl           | boolean | 否        | false         |                                         | 当使用 `redis` 限速策略时,如果设置为 true,则使用 SSL 连接到 `redis` |
+| redis_ssl_verify    | boolean | 否        | false         |                                         | 当使用 `redis` 限速策略时,如果设置为 true,则验证服务器 SSL 证书的有效性, 具体请参考 [tcpsock:sslhandshake](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake). |
 | redis_database      | integer | 否        | 0             | redis_database >= 0                     | 当使用 `redis` 限速策略时,Redis 服务节点中使用的 `database`,并且只针对非 Redis 集群模式(单实例模式或者提供单入口的 Redis 公有云服务)生效。|
 | redis_timeout       | integer | 否        | 1000          | [1,...]                                 | 当 `policy` 设置为 `redis` 或 `redis-cluster` 时,Redis 服务节点的超时时间(以毫秒为单位)。|
 | redis_cluster_nodes | array   | 否        |               |                                         | 当使用 `redis-cluster` 限速策略时,Redis 集群服务节点的地址列表(至少需要两个地址)。**当 `policy` 属性设置为 `redis-cluster` 时必选。**|
diff --git a/t/plugin/limit-count-redis.t b/t/plugin/limit-count-redis.t
index a777a3871..d06188050 100644
--- a/t/plugin/limit-count-redis.t
+++ b/t/plugin/limit-count-redis.t
@@ -344,7 +344,175 @@ failed to limit count: WRONGPASS invalid username-password pair or user is disab
 
 
 
-=== TEST 13: restore redis password to ''
+=== TEST 13: set route, with redis host, port and bad username and good password
+--- 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": "/hello",
+                    "plugins": {
+                        "limit-count": {
+                            "count": 2,
+                            "time_window": 60,
+                            "rejected_code": 503,
+                            "key": "remote_addr",
+                            "policy": "redis",
+                            "redis_host": "127.0.0.1",
+                            "redis_port": 6379,
+                            "redis_timeout": 1001,
+                            "redis_username": "bob",
+                            "redis_password": "somepassword"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 14: request for TEST 13
+--- request
+GET /hello
+--- error_code eval
+500
+--- error_log
+failed to limit count: WRONGPASS invalid username-password pair or user is disabled
+
+
+
+=== TEST 15: set route, with redis host, port and good username and bad password
+--- 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": "/hello",
+                    "plugins": {
+                        "limit-count": {
+                            "count": 2,
+                            "time_window": 60,
+                            "rejected_code": 503,
+                            "key": "remote_addr",
+                            "policy": "redis",
+                            "redis_host": "127.0.0.1",
+                            "redis_port": 6379,
+                            "redis_timeout": 1001,
+                            "redis_username": "alice",
+                            "redis_password": "badpassword"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 16: request for TEST 15
+--- request
+GET /hello
+--- error_code eval
+500
+--- error_log
+failed to limit count: WRONGPASS invalid username-password pair or user is disabled
+
+
+
+=== TEST 17: set route, with redis host, port and right username and password
+--- 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": "/hello",
+                    "plugins": {
+                        "limit-count": {
+                            "count": 2,
+                            "time_window": 60,
+                            "rejected_code": 503,
+                            "key": "remote_addr",
+                            "policy": "redis",
+                            "redis_host": "127.0.0.1",
+                            "redis_port": 6379,
+                            "redis_timeout": 1001,
+                            "redis_username": "alice",
+                            "redis_password": "somepassword"
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 18: up the limit
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200, 503, 503]
+
+
+
+=== TEST 19: up the limit
+--- pipelined_requests eval
+["GET /hello1", "GET /hello", "GET /hello2", "GET /hello", "GET /hello"]
+--- error_code eval
+[404, 503, 404, 503, 503]
+
+
+
+=== TEST 20: restore redis password to ''
 --- config
     location /t {
         content_by_lua_block {
diff --git a/t/plugin/limit-count-redis3.t b/t/plugin/limit-count-redis3.t
index 781f52720..a7694f63b 100644
--- a/t/plugin/limit-count-redis3.t
+++ b/t/plugin/limit-count-redis3.t
@@ -186,3 +186,118 @@ passed
     }
 --- response_body
 ["1","0","0"]
+
+
+
+=== TEST 5: set route, with redis host, port and SSL
+--- 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": "/hello",
+                    "plugins": {
+                        "limit-count": {
+                            "count": 2,
+                            "time_window": 60,
+                            "rejected_code": 503,
+                            "key": "remote_addr",
+                            "policy": "redis",
+                            "redis_host": "127.0.0.1",
+                            "redis_port": 6380,
+                            "redis_timeout": 1001,
+                            "redis_ssl": true,
+                            "redis_ssl_verify": false
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 6: up the limit
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200, 503, 503]
+
+
+
+=== TEST 7: up the limit
+--- pipelined_requests eval
+["GET /hello1", "GET /hello", "GET /hello2", "GET /hello", "GET /hello"]
+--- error_code eval
+[404, 503, 404, 503, 503]
+
+
+
+=== TEST 8: set route, with redis host, port, SSL and SSL verify is true(will cause ssl handshake err), with enable degradation switch
+--- 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": "/hello",
+                    "plugins": {
+                        "limit-count": {
+                            "count": 2,
+                            "time_window": 60,
+                            "rejected_code": 503,
+                            "key": "remote_addr",
+                            "allow_degradation": true,
+                            "policy": "redis",
+                            "redis_host": "127.0.0.1",
+                            "redis_port": 6380,
+                            "redis_timeout": 1001,
+                            "redis_ssl": true,
+                            "redis_ssl_verify": true
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 9: enable degradation switch for TEST 8
+--- request
+GET /hello
+--- response_body
+hello world
+--- error_log
+failed to do ssl handshake