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 <52862365+firstsaw...@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]