From 8c559990106d9509626365566fcc7b7f15db88c8 Mon Sep 17 00:00:00 2001 From: yizhibian <1119155090@qq.com> Date: Fri, 10 Jan 2025 15:38:38 +0800 Subject: [PATCH 1/3] feat: use enable_limit_quota_headers as ratelimited option --- plugins/plugins/limitcountredis/filter.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/plugins/limitcountredis/filter.go b/plugins/plugins/limitcountredis/filter.go index e1a5c0ae..8a10b717 100644 --- a/plugins/plugins/limitcountredis/filter.go +++ b/plugins/plugins/limitcountredis/filter.go @@ -161,8 +161,9 @@ func (f *filter) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) api remain := ress[2*i].(int64) if remain < 0 { hdr := http.Header{} - // TODO: add option to disable x-envoy-ratelimited - hdr.Set("x-envoy-ratelimited", "true") + if config.EnableLimitQuotaHeaders { + hdr.Set("x-envoy-ratelimited", "true") + } status := 429 if config.RateLimitedStatus >= 400 { // follow the behavior of Envoy status = int(config.RateLimitedStatus) From c57842fbac4d33e740735fae1e44214497c14e93 Mon Sep 17 00:00:00 2001 From: yizhibian <1119155090@qq.com> Date: Sat, 11 Jan 2025 14:49:10 +0800 Subject: [PATCH 2/3] feat: add disable_x_envoy_ratelimited_header option --- .../plugins/limitcountredis/config_test.go | 4 +++ plugins/plugins/limitcountredis/filter.go | 2 +- types/plugins/limitcountredis/config.pb.go | 27 ++++++++++++++----- .../limitcountredis/config.pb.validate.go | 2 ++ types/plugins/limitcountredis/config.proto | 2 ++ 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/plugins/plugins/limitcountredis/config_test.go b/plugins/plugins/limitcountredis/config_test.go index f01de771..6e40ad50 100644 --- a/plugins/plugins/limitcountredis/config_test.go +++ b/plugins/plugins/limitcountredis/config_test.go @@ -66,6 +66,10 @@ func TestConfig(t *testing.T) { name: "pass", input: `{"address":"127.0.0.1:6479", "rules":[{"count":1,"timeWindow":"1s"}], "prefix":"test"}`, }, + { + name: "disable x-envoy-ratelimited header", + input: `{"address":"127.0.0.1:6479", "rules":[{"count":1,"timeWindow":"1s"}], "prefix":"test", "disable_x_envoy_ratelimited_header": true}`, + }, } for _, tt := range tests { diff --git a/plugins/plugins/limitcountredis/filter.go b/plugins/plugins/limitcountredis/filter.go index 8a10b717..dd86d094 100644 --- a/plugins/plugins/limitcountredis/filter.go +++ b/plugins/plugins/limitcountredis/filter.go @@ -161,7 +161,7 @@ func (f *filter) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) api remain := ress[2*i].(int64) if remain < 0 { hdr := http.Header{} - if config.EnableLimitQuotaHeaders { + if !config.DisableXEnvoyRatelimitedHeader { hdr.Set("x-envoy-ratelimited", "true") } status := 429 diff --git a/types/plugins/limitcountredis/config.pb.go b/types/plugins/limitcountredis/config.pb.go index 4dfb1d59..f36d228c 100644 --- a/types/plugins/limitcountredis/config.pb.go +++ b/types/plugins/limitcountredis/config.pb.go @@ -170,7 +170,8 @@ type Config struct { StatusOnError v1.StatusCode `protobuf:"varint,9,opt,name=status_on_error,json=statusOnError,proto3,enum=types.plugins.api.v1.StatusCode" json:"status_on_error,omitempty"` RateLimitedStatus v1.StatusCode `protobuf:"varint,10,opt,name=rate_limited_status,json=rateLimitedStatus,proto3,enum=types.plugins.api.v1.StatusCode" json:"rate_limited_status,omitempty"` // There is no special reason to limit the length <=128, just to avoid too long string - Prefix string `protobuf:"bytes,12,opt,name=prefix,proto3" json:"prefix,omitempty"` + Prefix string `protobuf:"bytes,12,opt,name=prefix,proto3" json:"prefix,omitempty"` + DisableXEnvoyRatelimitedHeader bool `protobuf:"varint,13,opt,name=disable_x_envoy_ratelimited_header,json=disableXEnvoyRatelimitedHeader,proto3" json:"disable_x_envoy_ratelimited_header,omitempty"` } func (x *Config) Reset() { @@ -296,6 +297,13 @@ func (x *Config) GetPrefix() string { return "" } +func (x *Config) GetDisableXEnvoyRatelimitedHeader() bool { + if x != nil { + return x.DisableXEnvoyRatelimitedHeader + } + return false +} + type isConfig_Source interface { isConfig_Source() } @@ -337,7 +345,7 @@ var file_types_plugins_limitcountredis_config_proto_rawDesc = []byte{ 0x22, 0x31, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x65, 0x73, 0x22, 0xd9, 0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, + 0x73, 0x65, 0x73, 0x22, 0xa5, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x42, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x79, @@ -374,11 +382,16 @@ var file_types_plugins_limitcountredis_config_proto_rawDesc = []byte{ 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x01, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, - 0x42, 0x0d, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x03, 0xf8, 0x42, 0x01, 0x42, - 0x2c, 0x5a, 0x2a, 0x6d, 0x6f, 0x73, 0x6e, 0x2e, 0x69, 0x6f, 0x2f, 0x68, 0x74, 0x6e, 0x6e, 0x2f, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x65, 0x64, 0x69, 0x73, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x4a, 0x0a, 0x22, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x78, 0x5f, 0x65, 0x6e, + 0x76, 0x6f, 0x79, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x5f, + 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x58, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x52, 0x61, 0x74, 0x65, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x03, 0xf8, 0x42, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x6d, + 0x6f, 0x73, 0x6e, 0x2e, 0x69, 0x6f, 0x2f, 0x68, 0x74, 0x6e, 0x6e, 0x2f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x65, 0x64, 0x69, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/types/plugins/limitcountredis/config.pb.validate.go b/types/plugins/limitcountredis/config.pb.validate.go index 517437cc..85af4aca 100644 --- a/types/plugins/limitcountredis/config.pb.validate.go +++ b/types/plugins/limitcountredis/config.pb.validate.go @@ -393,6 +393,8 @@ func (m *Config) validate(all bool) error { errors = append(errors, err) } + // no validation rules for DisableXEnvoyRatelimitedHeader + oneofSourcePresent := false switch v := m.Source.(type) { case *Config_Address: diff --git a/types/plugins/limitcountredis/config.proto b/types/plugins/limitcountredis/config.proto index 8b8f9345..023cfc2f 100644 --- a/types/plugins/limitcountredis/config.proto +++ b/types/plugins/limitcountredis/config.proto @@ -58,4 +58,6 @@ message Config { // There is no special reason to limit the length <=128, just to avoid too long string string prefix = 12 [(validate.rules).string = {min_len: 1, max_len: 128}]; + + bool disable_x_envoy_ratelimited_header = 13; } From 19531ea87daf3395b6c524a4adfa82ad3cbf3a0b Mon Sep 17 00:00:00 2001 From: yizhibian <1119155090@qq.com> Date: Mon, 13 Jan 2025 15:47:45 +0800 Subject: [PATCH 3/3] feat: update doc --- .../reference/plugins/limit_count_redis.md | 34 ++++++++++--------- .../reference/plugins/limit_count_redis.md | 33 +++++++++--------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/site/content/en/docs/reference/plugins/limit_count_redis.md b/site/content/en/docs/reference/plugins/limit_count_redis.md index 6f478a1a..739ab73e 100644 --- a/site/content/en/docs/reference/plugins/limit_count_redis.md +++ b/site/content/en/docs/reference/plugins/limit_count_redis.md @@ -16,22 +16,24 @@ The `limitCountRedis` plugin implements a global fixed window rate-limiting by s ## Configuration -| Name | Type | Required | Validation | Description | -|-------------------------|-------------------------------------|----------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| address | string | False | | Redis address. Only one of `address` and `cluster` can be configured. | -| cluster | Cluster | False | | Redis cluster configuration. Only one of `address` and `cluster` can be configured. | -| prefix | string | True | min_len: 1, max_len: 128 | The prefix will be used as the prefix of Redis key. This field is introduced so that the recreation of the route won't reset the counter as the new limiter will use the same key as the previous one. Normally, put a random string in it is enough. To share the limit counters across multiple routes, we can use the same prefix. In this case, ensure the configurations of `limitCountRedis` plugin in these routes are the same. | -| rules | Rule | True | min_items: 1, max_items: 8 | Rules | -| failureModeDeny | boolean | False | | By default, if access to Redis fails, the request is allowed through. When true, it denies the request. | -| enableLimitQuotaHeaders | boolean | False | | Whether to set response headers related to rate-limiting quotas | -| username | string | False | | Username for accessing Redis | -| password | string | False | | Password for accessing Redis | -| tls | boolean | False | | Whether to access Redis over TLS | -| tlsSkipVerify | boolean | False | | Whether to skip verification when accessing Redis over TLS | -| statusOnError | [StatusCode](../type.md#statuscode) | False | | The status code used to deny requests when Redis is inaccessible and `failureModeDeny` is true. Defaults to 500. | -| rateLimitedStatus | [StatusCode](../type.md#statuscode) | False | | The status code for responses denied due to rate-limiting. Defaults to 429. This setting only takes effect when it's 400 or above. | - -Each rule's count is independent. Rate-limiting action is triggered once any rule's quota is exhausted. Responses that are denied due to rate-limiting will include the header `x-envoy-ratelimited: true`. If `enableLimitQuotaHeaders` is set to `true` and accessing to redis succeed, all responses will include the following three headers: +| Name | Type | Required | Validation | Description | +|--------------------------------|-------------------------------------|----------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| address | string | False | | Redis address. Only one of `address` and `cluster` can be configured. | +| cluster | Cluster | False | | Redis cluster configuration. Only one of `address` and `cluster` can be configured. | +| prefix | string | True | min_len: 1, max_len: 128 | The prefix will be used as the prefix of Redis key. This field is introduced so that the recreation of the route won't reset the counter as the new limiter will use the same key as the previous one. Normally, put a random string in it is enough. To share the limit counters across multiple routes, we can use the same prefix. In this case, ensure the configurations of `limitCountRedis` plugin in these routes are the same. | +| rules | Rule | True | min_items: 1, max_items: 8 | Rules | +| failureModeDeny | boolean | False | | By default, if access to Redis fails, the request is allowed through. When true, it denies the request. | +| enableLimitQuotaHeaders | boolean | False | | Whether to set response headers related to rate-limiting quotas | +| username | string | False | | Username for accessing Redis | +| password | string | False | | Password for accessing Redis | +| tls | boolean | False | | Whether to access Redis over TLS | +| tlsSkipVerify | boolean | False | | Whether to skip verification when accessing Redis over TLS | +| statusOnError | [StatusCode](../type.md#statuscode) | False | | The status code used to deny requests when Redis is inaccessible and `failureModeDeny` is true. Defaults to 500. | +| rateLimitedStatus | [StatusCode](../type.md#statuscode) | False | | The status code for responses denied due to rate-limiting. Defaults to 429. This setting only takes effect when it's 400 or above. | +| disableXEnvoyRatelimitedHeader | bool | False | | Whether to disable the `x-envoy-ratelimited` response header when rate limiting is triggered | | + + +Each rule's count is independent. Rate-limiting action is triggered once any rule's quota is exhausted. Responses that are denied due to rate-limiting will include the header `x-envoy-ratelimited: true`(which can be disabled by config). If `enableLimitQuotaHeaders` is set to `true` and accessing to redis succeed, all responses will include the following three headers: * `x-ratelimit-limit`: Represents the applied rate-limiting rule. The format is "the rule with the least remaining quota, (rule quota;w=time window){one or more rules}", e.g., `2, 2;w=60`. * `x-ratelimit-remaining`: Represents the remaining quota of the rule with the least remaining quota, with a minimum value of `0`. diff --git a/site/content/zh-hans/docs/reference/plugins/limit_count_redis.md b/site/content/zh-hans/docs/reference/plugins/limit_count_redis.md index bfad5fb3..193d1724 100644 --- a/site/content/zh-hans/docs/reference/plugins/limit_count_redis.md +++ b/site/content/zh-hans/docs/reference/plugins/limit_count_redis.md @@ -16,22 +16,23 @@ title: Limit Count Redis ## 配置 -| 名称 | 类型 | 必选 | 校验规则 | 说明 | -|-------------------------|-------------------------------------|------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| address | string | 否 | | Redis 地址。`address` 和`cluster` 只能配置一个。 | -| cluster | Cluster | 否 | | Redis cluster 配置。`address` 和`cluster` 只能配置一个。 | -| prefix | string | 是 | min_len: 1, max_len: 128 | 该字段将用作 Redis key 的前缀。引入这个字段是为了在重新创建路由时不会重置计数器,因为新的限制统计将使用与前一个相同的 key。通常,用一个随机字符串作为它的值就够了。要在多条路由中共享计数器,我们可以使用相同的前缀。在这种情况下,请确保这些路由的 `limitCountRedis` 插件配置相同。 | -| rules | Rule | 是 | min_items: 1, max_items: 8 | 规则 | -| failureModeDeny | bool | 否 | | 默认情况下,如果访问 Redis 失败,会放行请求。该值为 true 时,会拒绝请求。 | -| enableLimitQuotaHeaders | bool | 否 | | 是否设置限流额度相关的响应头 | -| username | string | 否 | | 用于访问 Redis 的用户名 | -| password | string | 否 | | 用于访问 Redis 的密码 | -| tls | bool | 否 | | 是否通过 TLS 访问 Redis | -| tlsSkipVerify | bool | 否 | | 通过 TLS 访问 Redis 时是否跳过验证 | -| statusOnError | [StatusCode](../type.md#statuscode) | 否 | | 当无法访问 Redis 且 `failureModeDeny` 为 true 时,拒绝请求使用的状态码。默认为 500. | -| rateLimitedStatus | [StatusCode](../type.md#statuscode) | 否 | | 因限流产生的拒绝响应的状态码。默认为 429. 该配置仅在不小于 400 时生效。 | - -每个规则的统计是独立的。当任一规则的额度用完后,就会触发限流操作。因限流产生的拒绝的响应中会包含 header `x-envoy-ratelimited: true`。如果配置了 `enableLimitQuotaHeaders` 为 `true` 且访问 Redis 成功,所有响应中都会包括下面三个头: +| 名称 | 类型 | 必选 | 校验规则 | 说明 | +|--------------------------------|-------------------------------------|-----|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| +| address | string | 否 | | Redis 地址。`address` 和`cluster` 只能配置一个。 | +| cluster | Cluster | 否 | | Redis cluster 配置。`address` 和`cluster` 只能配置一个。 | +| prefix | string | 是 | min_len: 1, max_len: 128 | 该字段将用作 Redis key 的前缀。引入这个字段是为了在重新创建路由时不会重置计数器,因为新的限制统计将使用与前一个相同的 key。通常,用一个随机字符串作为它的值就够了。要在多条路由中共享计数器,我们可以使用相同的前缀。在这种情况下,请确保这些路由的 `limitCountRedis` 插件配置相同。 | +| rules | Rule | 是 | min_items: 1, max_items: 8 | 规则 | +| failureModeDeny | bool | 否 | | 默认情况下,如果访问 Redis 失败,会放行请求。该值为 true 时,会拒绝请求。 | +| enableLimitQuotaHeaders | bool | 否 | | 是否设置限流额度相关的响应头 | +| username | string | 否 | | 用于访问 Redis 的用户名 | +| password | string | 否 | | 用于访问 Redis 的密码 | +| tls | bool | 否 | | 是否通过 TLS 访问 Redis | +| tlsSkipVerify | bool | 否 | | 通过 TLS 访问 Redis 时是否跳过验证 | +| statusOnError | [StatusCode](../type.md#statuscode) | 否 | | 当无法访问 Redis 且 `failureModeDeny` 为 true 时,拒绝请求使用的状态码。默认为 500. | +| rateLimitedStatus | [StatusCode](../type.md#statuscode) | 否 | | 因限流产生的拒绝响应的状态码。默认为 429. 该配置仅在不小于 400 时生效。 | +| disableXEnvoyRatelimitedHeader | bool | 否 | | 触发限流时是否关闭`x-envoy-ratelimited`的响应头 | + +每个规则的统计是独立的。当任一规则的额度用完后,就会触发限流操作。因限流产生的拒绝的响应中会包含 header `x-envoy-ratelimited: true`(可配置关闭)。如果配置了 `enableLimitQuotaHeaders` 为 `true` 且访问 Redis 成功,所有响应中都会包括下面三个头: * `x-ratelimit-limit`:表示当前应用的限流规则。格式为“当前剩余额度最少的规则,(规则额度;w=时间窗口){1 个或多个规则}”,例如 `2, 2;w=60`。 * `x-ratelimit-remaining`:表示当前剩余额度最少的规则的剩余额度,最小值为 `0`。