Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow to use environment variables for limit-count plugin settings #10607

Merged
10 changes: 6 additions & 4 deletions apisix/plugins/limit-count.lua
Expand Up @@ -14,11 +14,12 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local limit_count = require("apisix.plugins.limit-count.init")
local fetch_secrets = require("apisix.secret").fetch_secrets
local limit_count = require("apisix.plugins.limit-count.init")

local plugin_name = "limit-count"
local _M = {
version = 0.4,
local plugin_name = "limit-count"
local _M = {
monkeyDluffy6017 marked this conversation as resolved.
Show resolved Hide resolved
version = 0.5,
priority = 1002,
name = plugin_name,
schema = limit_count.schema,
Expand All @@ -31,6 +32,7 @@ end


function _M.access(conf, ctx)
conf = fetch_secrets(conf)
return limit_count.rate_limit(conf, ctx, plugin_name, 1)
end

Expand Down
2 changes: 2 additions & 0 deletions apisix/plugins/limit-count/init.lua
Expand Up @@ -68,6 +68,7 @@ local policy_to_additional_properties = {
type = "boolean", default = false,
},
},
encrypt_fields = {"redis_username", "redis_password"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the redis_username need to be encrypted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, not really, will fix that

required = {"redis_host"},
},
["redis-cluster"] = {
Expand Down Expand Up @@ -95,6 +96,7 @@ local policy_to_additional_properties = {
type = "boolean", default = false,
},
},
encrypt_fields = {"redis_password"},
required = {"redis_cluster_nodes", "redis_cluster_name"},
},
}
Expand Down
32 changes: 32 additions & 0 deletions docs/en/latest/plugins/limit-count.md
Expand Up @@ -56,6 +56,7 @@ The `limit-count` Plugin limits the number of requests to your service by a give
| redis_cluster_name | string | required when `policy` is `redis-cluster` | | | Name of the Redis cluster service nodes. Used when the `policy` attribute is set to `redis-cluster`. |
| redis_cluster_ssl | boolean | False | false | | If set to `true`, then uses SSL to connect to redis-cluster. Used when the `policy` attribute is set to `redis-cluster`. |
| redis_cluster_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-cluster`. |
NOTE: `encrypt_fields = {"redis_password", "redis_username}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).

## Enable Plugin

Expand Down Expand Up @@ -257,6 +258,37 @@ curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \
}'
```

In addition, you can use APISIX secret to store and reference plugin attributes. APISIX currently supports storing secrets in two ways - [Environment Variables and HashiCorp Vault](../terminology/secret.md). For example, in
case you have environment variables `REDIS_HOST` and `REDIS_PASSWORD` set, you can use them in the plugin configuration as shown below:

```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/index.html",
"plugins": {
"limit-count": {
"count": 2,
"time_window": 60,
"rejected_code": 503,
"key": "remote_addr",
"policy": "redis",
"redis_host": "$ENV://REDIS_HOST",
"redis_port": 6379,
"redis_password": "$ENV://REDIS_PASSWORD",
"redis_database": 1,
"redis_timeout": 1001
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

## Example usage

The above configuration limits to 2 requests in 60 seconds. The first two requests will work and the response headers will contain the headers `X-RateLimit-Limit` and `X-RateLimit-Remaining` and `X-RateLimit-Reset`, represents the total number of requests that are limited, the number of requests that can still be sent, and the number of seconds left for the counter to reset:
Expand Down
56 changes: 56 additions & 0 deletions t/plugin/limit-count-redis-cluster3.t
Expand Up @@ -14,6 +14,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
BEGIN {
$ENV{REDIS_NODE_0} = "127.0.0.1:5000";
$ENV{REDIS_NODE_1} = "127.0.0.1:5001";
}

use t::APISIX;

Expand Down Expand Up @@ -77,3 +81,55 @@ __DATA__
}
--- response_body
remaining: 1



=== TEST 2: set route, with redis_cluster_nodes as environment variables and redis_cluster_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,
[[{
"uri": "/hello",
"plugins": {
"limit-count": {
"count": 2,
"time_window": 60,
"rejected_code": 503,
"key": "remote_addr",
"policy": "redis-cluster",
"redis_timeout": 1001,
"redis_cluster_nodes": [
"$ENV://REDIS_NODE_0",
"$ENV://REDIS_NODE_1"
monkeyDluffy6017 marked this conversation as resolved.
Show resolved Hide resolved
],
"redis_cluster_name": "redis-cluster-1"
}
},
"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 3: up the limit with environment variables for redis_cluster_nodes
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello"]
--- error_code eval
[200, 200, 503]
49 changes: 49 additions & 0 deletions t/plugin/limit-count-redis4.t
Expand Up @@ -22,6 +22,8 @@ BEGIN {
$ENV{TEST_NGINX_USE_HUP} = 1;
undef $ENV{TEST_NGINX_USE_STAP};
}

$ENV{REDIS_HOST} = "127.0.0.1";
}

use t::APISIX;
Expand Down Expand Up @@ -85,3 +87,50 @@ __DATA__
}
--- response_body
remaining: 1



=== TEST 2: set route, with redis host as environment variable
--- 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": "$ENV://REDIS_HOST"
}
},
"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 3: up the limit with host environment variable
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello"]
--- error_code eval
[200, 200, 503]
48 changes: 48 additions & 0 deletions t/plugin/limit-count5.t
Expand Up @@ -22,6 +22,8 @@ BEGIN {
$ENV{TEST_NGINX_USE_HUP} = 1;
undef $ENV{TEST_NGINX_USE_STAP};
}

$ENV{LIMIT_COUNT_KEY} = "remote_addr";
}

use t::APISIX;
Expand Down Expand Up @@ -89,3 +91,49 @@ remaining: 2
remaining: 0
rejected
rejected



=== TEST 2: set route(id: 1) using environment variable for key
--- 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"],
"plugins": {
"limit-count": {
"count": 2,
"time_window": 60,
"rejected_code": 503,
"key": "$ENV://LIMIT_COUNT_KEY"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same reasoning as above

}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)

if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed



=== TEST 3: up the limit with environment variable for key
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- error_code eval
[200, 200, 503, 503]