diff --git a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto index bc2e42fced1b..59c61d281aa0 100644 --- a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto +++ b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Local Rate limit :ref:`configuration overview `. // [#extension: envoy.filters.http.local_ratelimit] -// [#next-free-field: 10] +// [#next-free-field: 11] message LocalRateLimit { // The human readable prefix to use when emitting stats. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -63,8 +63,14 @@ message LocalRateLimit { // Defaults to 0% of requests for safety. config.core.v3.RuntimeFractionalPercent filter_enforced = 5; + // Specifies a list of HTTP headers that should be added to each request that + // has been rate limited and is also forwarded upstream. This can only occur when the + // filter is enabled but not enforced. + repeated config.core.v3.HeaderValueOption request_headers_to_add_when_not_enforced = 10 + [(validate.rules).repeated = {max_items: 10}]; + // Specifies a list of HTTP headers that should be added to each response for requests that - // have been rate limited. + // have been rate limited. This occurs when the filter is either enabled or fully enforced. repeated config.core.v3.HeaderValueOption response_headers_to_add = 6 [(validate.rules).repeated = {max_items: 10}]; diff --git a/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst b/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst index 7933b04a320e..4467ba080a41 100644 --- a/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst +++ b/docs/root/configuration/http/http_filters/local_rate_limit_filter.rst @@ -13,9 +13,14 @@ limit when the request's route or virtual host has a per filter :ref:`local rate limit configuration `. If the local rate limit token bucket is checked, and there are no tokens available, a 429 response is returned -(the response is configurable). The local rate limit filter also sets the -:ref:`x-envoy-ratelimited` header. Additional response -headers may be configured. +(the response is configurable). The local rate limit filter then sets the +:ref:`x-envoy-ratelimited` response header. :ref:`Additional response headers +` can be +configured to be returned. + +:ref:`Request headers +` can be +configured to be added to forwarded requests to the upstream when the local rate limit filter is enabled but not enforced. .. note:: The token bucket is shared across all workers, thus the rate limits are applied per Envoy process. diff --git a/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto b/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto index bc2e42fced1b..59c61d281aa0 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Local Rate limit :ref:`configuration overview `. // [#extension: envoy.filters.http.local_ratelimit] -// [#next-free-field: 10] +// [#next-free-field: 11] message LocalRateLimit { // The human readable prefix to use when emitting stats. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -63,8 +63,14 @@ message LocalRateLimit { // Defaults to 0% of requests for safety. config.core.v3.RuntimeFractionalPercent filter_enforced = 5; + // Specifies a list of HTTP headers that should be added to each request that + // has been rate limited and is also forwarded upstream. This can only occur when the + // filter is enabled but not enforced. + repeated config.core.v3.HeaderValueOption request_headers_to_add_when_not_enforced = 10 + [(validate.rules).repeated = {max_items: 10}]; + // Specifies a list of HTTP headers that should be added to each response for requests that - // have been rate limited. + // have been rate limited. This occurs when the filter is either enabled or fully enforced. repeated config.core.v3.HeaderValueOption response_headers_to_add = 6 [(validate.rules).repeated = {max_items: 10}]; diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc index 263d77849dc2..30ed854930e5 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc @@ -38,6 +38,8 @@ FilterConfig::FilterConfig( : absl::nullopt), response_headers_parser_( Envoy::Router::HeaderParser::configure(config.response_headers_to_add())), + request_headers_parser_(Envoy::Router::HeaderParser::configure( + config.request_headers_to_add_when_not_enforced())), stage_(static_cast(config.stage())), has_descriptors_(!config.descriptors().empty()) { // Note: no token bucket is fine for the global config, which would be the case for enabling @@ -90,6 +92,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, config->stats().rate_limited_.inc(); if (!config->enforced()) { + config->requestHeadersParser().evaluateHeaders(headers, decoder_callbacks_->streamInfo()); return Http::FilterHeadersStatus::Continue; } diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h index cffbc399c05d..af27e23efe4e 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h @@ -58,6 +58,7 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { bool enforced() const; LocalRateLimitStats& stats() const { return stats_; } const Router::HeaderParser& responseHeadersParser() const { return *response_headers_parser_; } + const Router::HeaderParser& requestHeadersParser() const { return *request_headers_parser_; } Http::Code status() const { return status_; } uint64_t stage() const { return stage_; } bool hasDescriptors() const { return has_descriptors_; } @@ -83,6 +84,7 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { const absl::optional filter_enabled_; const absl::optional filter_enforced_; Router::HeaderParserPtr response_headers_parser_; + Router::HeaderParserPtr request_headers_parser_; const uint64_t stage_; const bool has_descriptors_; }; diff --git a/test/extensions/filters/http/local_ratelimit/filter_test.cc b/test/extensions/filters/http/local_ratelimit/filter_test.cc index 0c9f2507e016..cd9b6ec84f7b 100644 --- a/test/extensions/filters/http/local_ratelimit/filter_test.cc +++ b/test/extensions/filters/http/local_ratelimit/filter_test.cc @@ -34,6 +34,11 @@ stat_prefix: test header: key: x-test-rate-limit value: 'true' +request_headers_to_add_when_not_enforced: + - append: false + header: + key: x-local-ratelimited + value: 'true' )"; class FilterTest : public testing::Test { @@ -128,8 +133,12 @@ TEST_F(FilterTest, RequestRateLimited) { EXPECT_EQ(details, "local_rate_limited"); })); - auto headers = Http::TestRequestHeaderMapImpl(); - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + auto request_headers = Http::TestRequestHeaderMapImpl(); + auto expected_headers = Http::TestRequestHeaderMapImpl(); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(request_headers, expected_headers); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enabled")); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enforced")); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); @@ -140,8 +149,12 @@ TEST_F(FilterTest, RequestRateLimitedButNotEnforced) { EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::TooManyRequests, _, _, _, _)).Times(0); - auto headers = Http::TestRequestHeaderMapImpl(); - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + auto request_headers = Http::TestRequestHeaderMapImpl(); + Http::TestRequestHeaderMapImpl expected_headers{ + {"x-local-ratelimited", "true"}, + }; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(request_headers, expected_headers); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enabled")); EXPECT_EQ(0U, findCounter("test.http_local_rate_limit.enforced")); EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited"));