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/10/06 10:18:32 UTC

[apisix] branch master updated: feature: support `consumer_name` as key for `limit-req` plugin. (#2270)

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 c3de84e  feature: support `consumer_name` as key for `limit-req` plugin.  (#2270)
c3de84e is described below

commit c3de84e28519e74f3b07d25d06a6cab0cad4bdc4
Author: Firstsawyou <52...@users.noreply.github.com>
AuthorDate: Tue Oct 6 18:18:22 2020 +0800

    feature: support `consumer_name` as key for `limit-req` plugin.  (#2270)
    
    fix #2267
---
 apisix/plugins/limit-req.lua   |  14 +-
 doc/plugins/limit-req.md       | 112 +++++++++++++--
 doc/zh-cn/plugins/limit-req.md | 110 +++++++++++++--
 t/admin/plugins.t              |   2 +-
 t/plugin/limit-req.t           | 313 ++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 524 insertions(+), 27 deletions(-)

diff --git a/apisix/plugins/limit-req.lua b/apisix/plugins/limit-req.lua
index 1caadce..7602e9b 100644
--- a/apisix/plugins/limit-req.lua
+++ b/apisix/plugins/limit-req.lua
@@ -27,7 +27,7 @@ local schema = {
         burst = {type = "number",  minimum = 0},
         key = {type = "string",
             enum = {"remote_addr", "server_addr", "http_x_real_ip",
-                    "http_x_forwarded_for"},
+                    "http_x_forwarded_for", "consumer_name"},
         },
         rejected_code = {type = "integer", minimum = 200, default = 503},
     },
@@ -67,7 +67,17 @@ function _M.access(conf, ctx)
         return 500
     end
 
-    local key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
+    local key
+    if conf.key == "consumer_name" then
+        if not ctx.consumer_id then
+            core.log.error("consumer not found.")
+            return 500, { message = "Consumer not found."}
+        end
+        key = ctx.consumer_id .. ctx.conf_type .. ctx.conf_version
+
+    else
+        key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
+    end
     core.log.info("limit key: ", key)
 
     local delay, err = lim:incoming(key, true)
diff --git a/doc/plugins/limit-req.md b/doc/plugins/limit-req.md
index ca090d9..c3d983e 100644
--- a/doc/plugins/limit-req.md
+++ b/doc/plugins/limit-req.md
@@ -20,14 +20,14 @@
 - [中文](../zh-cn/plugins/limit-req.md)
 
 # Summary
+  - [Introduction](#introduction)
+  - [Attributes](#attributes)
+  - [Example](#example)
+    - [How to enable on the `route` or `serivce`](#how-to-enable-on-the-route-or-serivce)
+    - [How to enable on the `consumer`](#how-to-enable-on-the-consumer)
+  - [Disable Plugin](#disable-plugin)
 
-- [**Name**](#name)
-- [**Attributes**](#attributes)
-- [**How To Enable**](#how-to-enable)
-- [**Test Plugin**](#test-plugin)
-- [**Disable Plugin**](#disable-plugin)
-
-## Name
+## Introduction
 
 limit request rate using the "leaky bucket" method.
 
@@ -37,14 +37,16 @@ limit request rate using the "leaky bucket" method.
 | ------------- | ------- | ----------- | ------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | rate          | integer | required    |         | [0,...]                                                                  | the specified request rate (number per second) threshold. Requests exceeding this rate (and below `burst`) will get delayed to conform to the rate.                       |
 | burst         | integer | required    |         | [0,...]                                                                  | the number of excessive requests per second allowed to be delayed. Requests exceeding this hard limit will get rejected immediately.                                      |
-| key           | string  | required    |         | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for"] | the user specified key to limit the rate, now accept those as key: "remote_addr"(client's IP), "server_addr"(server's IP), "X-Forwarded-For/X-Real-IP" in request header. |
-| rejected_code | string  | optional    | 503     | [200,...]                                                                | The HTTP status code returned when the request exceeds the threshold is rejected. The default is 503.                                                                     |
+| key           | string  | required    |         | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"] | the user specified key to limit the rate, now accept those as key: "remote_addr"(client's IP), "server_addr"(server's IP), "X-Forwarded-For/X-Real-IP" in request header, "consumer_name"(consumer's username). |
+| rejected_code | integer  | optional    | 503     | [200,...]                                                                | The HTTP status code returned when the request exceeds the threshold is rejected.                                                                      |
 
 **Key can be customized by the user, only need to modify a line of code of the plug-in to complete.  It is a security consideration that is not open in the plugin.**
 
-## How To Enable
+## Example
+
+### How to enable on the `route` or `serivce`
 
-Here's an example, enable the limit req plugin on the specified route:
+Take `route` as an example (the use of `service` is the same method), enable the `limit-req` plugin on the specified route.
 
 ```shell
 curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
@@ -76,7 +78,7 @@ Then add limit-req plugin:
 
 ![add plugin](../images/plugin/limit-req-2.png)
 
-## Test Plugin
+**Test Plugin**
 
 The above configuration limits the request rate to 1 per second. If it is greater than 1 and less than 3, the delay will be added. If the rate exceeds 3, it will be rejected:
 
@@ -104,6 +106,78 @@ Server: APISIX web server
 
 This means that the limit req plugin is in effect.
 
+### How to enable on the `consumer`
+
+To enable the `limit-req` plugin on the consumer, it needs to be used together with the authorization plugin. Here, the key-auth authorization plugin is taken as an example.
+
+1. Bind the `limit-req` plugin to the consumer
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "consumer_jack",
+    "plugins": {
+        "key-auth": {
+            "key": "auth-jack"
+        },
+        "limit-req": {
+            "rate": 1,
+            "burst": 1,
+            "rejected_code": 403,
+            "key": "consumer_name"
+        }
+    }
+}'
+```
+
+2. Create a `route` and enable the `key-auth` plugin
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/index.html",
+    "plugins": {
+        "key-auth": {
+            "key": "auth-jack"
+        }
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "127.0.0.1:1980": 1
+        }
+    }
+}'
+```
+
+**Test Plugin**
+
+The value of `rate + burst` is not exceeded.
+
+```shell
+curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
+HTTP/1.1 200 OK
+......
+```
+
+When the value of `rate + burst` is exceeded.
+
+```shell
+curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
+HTTP/1.1 403 Forbidden
+.....
+<html>
+<head><title>403 Forbidden</title></head>
+<body>
+<center><h1>403 Forbidden</h1></center>
+<hr><center>openresty</center>
+</body>
+</html>
+```
+
+Explains that the `limit-req` plugin tied to `consumer` has taken effect.
+
 ## Disable Plugin
 
 When you want to disable the limit req plugin, it is very simple,
@@ -127,4 +201,18 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
 }'
 ```
 
+Remove the `limit-req` plugin on `consumer`.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "consumer_jack",
+    "plugins": {
+        "key-auth": {
+            "key": "auth-jack"
+        }
+    }
+}'
+```
+
 The limit req plugin has been disabled now. It works for other plugins.
diff --git a/doc/zh-cn/plugins/limit-req.md b/doc/zh-cn/plugins/limit-req.md
index b6b1de1..2aacd57 100644
--- a/doc/zh-cn/plugins/limit-req.md
+++ b/doc/zh-cn/plugins/limit-req.md
@@ -19,26 +19,34 @@
 
 - [English](../../plugins/limit-req.md)
 
-# limit-req
+# 目录
+  - [简介](#简介)
+  - [属性](#属性)
+  - [示例](#示例)
+    - [如何在 `route` 或 `service` 上使用](#如何在`route`或`service`上使用)
+    - [如何在 `consumer` 上使用](#如何在`consumer`上使用)
+  - [移除插件](#移除插件)
+
+## 简介
 
 限制请求速度的插件,使用的是漏桶算法。
 
-## 参数
+## 属性
 
 | 名称          | 类型    | 必选项 | 默认值 | 有效值                                                                   | 描述                                                                                                                                              |
 | ------------- | ------- | ------ | ------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
 | rate          | integer | 必须   |        | [0,...]                                                                  | 指定的请求速率(以秒为单位),请求速率超过 `rate` 但没有超过 (`rate` + `brust`)的请求会被加上延时。                                             |
 | burst         | integer | 必须   |        | [0,...]                                                                  | t请求速率超过 (`rate` + `brust`)的请求会被直接拒绝。                                                                                            |
-| key           | string  | 必须   |        | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for"] | 用来做请求计数的依据,当前接受的 key 有:"remote_addr"(客户端IP地址), "server_addr"(服务端 IP 地址), 请求头中的"X-Forwarded-For" 或 "X-Real-IP"。 |
-| rejected_code | string  | 可选   | 503    | [200,...]                                                                | 当请求超过阈值被拒绝时,返回的 HTTP 状态码                                                                                                        |
+| key           | string  | 必须   |        | ["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"] | 用来做请求计数的依据,当前接受的 key 有:"remote_addr"(客户端IP地址), "server_addr"(服务端 IP 地址), 请求头中的"X-Forwarded-For" 或 "X-Real-IP","consumer_name"(consumer 的 username)。 |
+| rejected_code | integer  | 可选   | 503    | [200,...]                                                                | 当请求超过阈值被拒绝时,返回的 HTTP 状态码。                                                                                                        |
 
 **key 是可以被用户自定义的,只需要修改插件的一行代码即可完成。并没有在插件中放开是处于安全的考虑。**
 
 ## 示例
 
-### 开启插件
+### 如何在`route`或`service`上使用
 
-下面是一个示例,在指定的 route 上开启了 limit req 插件:
+这里以`route`为例(`service`的使用是同样的方法),在指定的 `route` 上启用 `limit-req` 插件。
 
 ```shell
 curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
@@ -70,7 +78,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
 
 ![添加插件](../../images/plugin/limit-req-2.png)
 
-### 测试插件
+**测试插件**
 
 上述配置限制了每秒请求速率为 1,大于 1 小于 3 的会被加上延时,速率超过 3 就会被拒绝:
 
@@ -98,7 +106,79 @@ Server: APISIX web server
 
 这就表示 limit req 插件生效了。
 
-### 移除插件
+### 如何在`consumer`上使用
+
+consumer上开启`limit-req`插件,需要与授权插件一起配合使用,这里以key-auth授权插件为例。
+
+1、将`limit-req`插件绑定到consumer上
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "consumer_jack",
+    "plugins": {
+        "key-auth": {
+            "key": "auth-jack"
+        },
+        "limit-req": {
+            "rate": 1,
+            "burst": 1,
+            "rejected_code": 403,
+            "key": "consumer_name"
+        }
+    }
+}'
+```
+
+2、创建`route`并开启`key-auth`插件
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "methods": ["GET"],
+    "uri": "/index.html",
+    "plugins": {
+        "key-auth": {
+            "key": "auth-jack"
+        }
+    },
+    "upstream": {
+        "type": "roundrobin",
+        "nodes": {
+            "127.0.0.1:1980": 1
+        }
+    }
+}'
+```
+
+**测试插件**
+
+未超过`rate + burst` 的值
+
+```shell
+curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
+HTTP/1.1 200 OK
+......
+```
+
+当超过`rate + burst` 的值
+
+```shell
+curl -i http://127.0.0.1:9080/index.html -H 'apikey: auth-jack'
+HTTP/1.1 403 Forbidden
+.....
+<html>
+<head><title>403 Forbidden</title></head>
+<body>
+<center><h1>403 Forbidden</h1></center>
+<hr><center>openresty</center>
+</body>
+</html>
+```
+
+说明绑在`consumer`上的 `limit-req`插件生效了
+
+## 移除插件
 
 当你想去掉 limit req 插件的时候,很简单,在插件的配置中把对应的 json 配置删除即可,无须重启服务,即刻生效:
 
@@ -116,4 +196,18 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13
 }'
 ```
 
+移除`consumer`上的 `limit-req` 插件
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "username": "consumer_jack",
+    "plugins": {
+        "key-auth": {
+            "key": "auth-jack"
+        }
+    }
+}'
+```
+
 现在就已经移除了 limit req 插件了。其他插件的开启和移除也是同样的方法。
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index 6ca86af..6c72d25 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -51,7 +51,7 @@ GET /apisix/admin/plugins
 --- request
 GET /apisix/admin/plugins/limit-req
 --- response_body
-{"properties":{"rate":{"minimum":0,"type":"number"},"burst":{"minimum":0,"type":"number"},"key":{"enum":["remote_addr","server_addr","http_x_real_ip","http_x_forwarded_for"],"type":"string"},"rejected_code":{"type":"integer","default":503,"minimum":200}},"required":["rate","burst","key"],"type":"object"}
+{"properties":{"rate":{"minimum":0,"type":"number"},"burst":{"minimum":0,"type":"number"},"key":{"enum":["remote_addr","server_addr","http_x_real_ip","http_x_forwarded_for","consumer_name"],"type":"string"},"rejected_code":{"type":"integer","default":503,"minimum":200}},"required":["rate","burst","key"],"type":"object"}
 --- no_error_log
 [error]
 
diff --git a/t/plugin/limit-req.t b/t/plugin/limit-req.t
index 001d975..15a6016 100644
--- a/t/plugin/limit-req.t
+++ b/t/plugin/limit-req.t
@@ -102,7 +102,7 @@ done
                             },
                             "type": "roundrobin"
                         },
-                        "desc": "上游节点",
+                        "desc": "upstream_node",
                         "uri": "/hello"
                 }]],
                 [[{
@@ -122,7 +122,7 @@ done
                                 },
                                 "type": "roundrobin"
                             },
-                            "desc": "上游节点",
+                            "desc": "upstream_node",
                             "uri": "/hello"
                         },
                         "key": "/apisix/routes/1"
@@ -362,7 +362,7 @@ passed
                             },
                             "type": "roundrobin"
                         },
-                        "desc": "上游节点",
+                        "desc": "upstream_node",
                         "uri": "/hello"
                 }]]
                 )
@@ -403,7 +403,7 @@ passed
                             },
                             "type": "roundrobin"
                         },
-                        "desc": "上游节点",
+                        "desc": "upstream_node",
                         "uri": "/hello"
                 }]],
                 [[{
@@ -432,3 +432,308 @@ GET /t
 passed
 --- no_error_log
 [error]
+
+
+
+=== TEST 12: consumer binds the limit-req plugin and `key` is `consumer_name`
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "new_consumer",
+                    "plugins": {
+                        "key-auth": {
+                            "key": "auth-jack"
+                        },
+                        "limit-req": {
+                            "rate": 3,
+                            "burst": 2,
+                            "rejected_code": 403,
+                            "key": "consumer_name"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "new_consumer",
+                            "plugins": {
+                               "key-auth": {
+                                    "key": "auth-jack"
+                                },
+                                "limit-req": {
+                                    "rate": 3,
+                                    "burst": 2,
+                                    "rejected_code": 403,
+                                    "key": "consumer_name"
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: route add "key-auth" plugin
+--- 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": {
+                        "key-auth": {} 
+                    },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "desc": "upstream_node",
+                        "uri": "/hello"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                                "key-auth": {}
+                            },
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:1980": 1
+                                },
+                                "type": "roundrobin"
+                            },
+                            "desc": "upstream_node",
+                            "uri": "/hello"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 14: not exceeding the burst
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- more_headers
+apikey: auth-jack
+--- error_code eval
+[200, 200, 200]
+--- no_error_log
+[error]
+
+
+
+=== TEST 15: update the limit-req plugin
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "new_consumer",
+                    "plugins": {
+                        "key-auth": {
+                            "key": "auth-jack"
+                        },
+                        "limit-req": {
+                            "rate": 0.1,
+                            "burst": 0.1,
+                            "rejected_code": 403,
+                            "key": "consumer_name"
+                        }
+                    }
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "username": "new_consumer",
+                            "plugins": {
+                               "key-auth": {
+                                    "key": "auth-jack"
+                                },
+                                "limit-req": {
+                                    "rate": 0.1,
+                                    "burst": 0.1,
+                                    "rejected_code": 403,
+                                    "key": "consumer_name"
+                                }
+                            }
+                        }
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: exceeding the burst
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
+--- more_headers
+apikey: auth-jack
+--- error_code eval
+[403, 403, 403, 403]
+--- no_error_log
+[error]
+
+
+
+=== TEST 17: key is consumer_name
+--- 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": {
+                        "limit-req": {
+                            "rate": 2,
+                            "burst": 1,
+                            "key": "consumer_name"
+                        }
+                    },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "desc": "upstream_node",
+                        "uri": "/hello"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                                "limit-req": {
+                                    "rate": 2,
+                                    "burst": 1,
+                                    "key": "consumer_name"
+                                }
+                            },
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:1980": 1
+                                },
+                                "type": "roundrobin"
+                            },
+                            "desc": "upstream_node",
+                            "uri": "/hello"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 18: get "consumer_name" is empty
+--- request
+GET /hello
+--- error_code: 500
+--- response_body
+{"message":"Consumer not found."}
+--- error_log
+[error]
+
+
+
+=== TEST 19: delete consumer
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/consumers/new_consumer', ngx.HTTP_DELETE)
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 20: delete route
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1', ngx.HTTP_DELETE)
+
+            ngx.status =code
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]