diff --git a/docs/configuration/http_conn_man/route_config/rate_limits.rst b/docs/configuration/http_conn_man/route_config/rate_limits.rst index 5729e2e844c8..936b312d97e0 100644 --- a/docs/configuration/http_conn_man/route_config/rate_limits.rst +++ b/docs/configuration/http_conn_man/route_config/rate_limits.rst @@ -141,7 +141,7 @@ Generic Key descriptor_value - *(required, string)* The value to use in the descriptor entry. + *(required, string)* The value to use in the descriptor entry. The following descriptor entry is appended to the descriptor: @@ -157,22 +157,27 @@ Header Value Match { "type": "header_value_match", "descriptor_value" : "...", + "expect_match" : "...", "headers" : [] } descriptor_value - *(required, string)* The value to use in the descriptor entry. + *(required, string)* The value to use in the descriptor entry. -:ref:`headers` - *(required, array)* Specifies a set of headers that the rate limit action should match on. The - action will check the request's headers against all the specified headers in the config. A match - will happen if all the headers in the config are present in the request with the same values (or - based on presence if the ``value`` field is not in the config). +expect_match + *(optional, boolean)* If set to true, the action will append a descriptor entry when the request + matches the :ref:`headers`. If set to false, + the action will append a descriptor entry when the request does not match the + :ref:`headers`. The default value is true. -The following descriptor entry is appended to the descriptor if the request matches the headers -specified in the action config: +:ref:`headers` + *(required, array)* Specifies a set of headers that the rate limit action should match on. The + action will check the request's headers against all the specified headers in the config. A match + will happen if all the headers in the config are present in the request with the same values (or + based on presence if the ``value`` field is not in the config). +The following descriptor entry is appended to the descriptor: .. code-block:: cpp ("header_match", "") diff --git a/source/common/json/config_schemas.cc b/source/common/json/config_schemas.cc index 63e22e0b06c0..28709c1ee2d2 100644 --- a/source/common/json/config_schemas.cc +++ b/source/common/json/config_schemas.cc @@ -721,6 +721,7 @@ const std::string Json::Schema::HTTP_RATE_LIMITS_CONFIGURATION_SCHEMA(R"EOF( "enum" : ["header_value_match"] }, "descriptor_value" : {"type" : "string"}, + "expect_match" : {"type" : "boolean"}, "headers" : { "type" : "array", "minItems" : 1, diff --git a/source/common/router/router_ratelimit.cc b/source/common/router/router_ratelimit.cc index ef4d61527e6a..6e4cc82fe967 100644 --- a/source/common/router/router_ratelimit.cc +++ b/source/common/router/router_ratelimit.cc @@ -62,7 +62,8 @@ bool GenericKeyAction::populateDescriptor(const Router::RouteEntry&, } HeaderValueMatchAction::HeaderValueMatchAction(const Json::Object& action) - : descriptor_value_(action.getString("descriptor_value")) { + : descriptor_value_(action.getString("descriptor_value")), + expect_match_(action.getBoolean("expect_match", true)) { std::vector config_headers = action.getObjectArray("headers"); for (const Json::ObjectPtr& header_map : config_headers) { action_headers_.push_back(*header_map); @@ -73,7 +74,7 @@ bool HeaderValueMatchAction::populateDescriptor(const Router::RouteEntry&, ::RateLimit::Descriptor& descriptor, const std::string&, const Http::HeaderMap& headers, const std::string&) const { - if (ConfigUtility::matchHeaders(headers, action_headers_)) { + if (expect_match_ == ConfigUtility::matchHeaders(headers, action_headers_)) { descriptor.entries_.push_back({"header_match", descriptor_value_}); return true; } else { diff --git a/source/common/router/router_ratelimit.h b/source/common/router/router_ratelimit.h index 9fe5ff266218..65d7620539a2 100644 --- a/source/common/router/router_ratelimit.h +++ b/source/common/router/router_ratelimit.h @@ -97,6 +97,7 @@ class HeaderValueMatchAction : public RateLimitAction { private: const std::string descriptor_value_; + const bool expect_match_; std::vector action_headers_; }; diff --git a/test/common/router/router_ratelimit_test.cc b/test/common/router/router_ratelimit_test.cc index 9082a80e1cae..34ee86250aa3 100644 --- a/test/common/router/router_ratelimit_test.cc +++ b/test/common/router/router_ratelimit_test.cc @@ -546,7 +546,62 @@ TEST_F(RateLimitPolicyEntryTest, HeaderValueMatchNoMatch) { )EOF"; SetUpTest(json); - Http::TestHeaderMapImpl header{{"x-header-name", "fake_value"}}; + Http::TestHeaderMapImpl header{{"x-header-name", "not_same_value"}}; + + rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header, ""); + EXPECT_TRUE(descriptors_.empty()); +} + +TEST_F(RateLimitPolicyEntryTest, HeaderValueMatchHeadersNotPresent) { + std::string json = R"EOF( + { + "actions": [ + { + "type": "header_value_match", + "descriptor_value": "fake_value", + "expect_match": false, + "headers": [ + { + "name": "x-header-name", + "value": "test_value", + "regex": false + } + ] + } + ] + } + )EOF"; + + SetUpTest(json); + Http::TestHeaderMapImpl header{{"x-header-name", "not_same_value"}}; + + rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header, ""); + EXPECT_THAT(std::vector<::RateLimit::Descriptor>({{{{"header_match", "fake_value"}}}}), + testing::ContainerEq(descriptors_)); +} + +TEST_F(RateLimitPolicyEntryTest, HeaderValueMatchHeadersPresent) { + std::string json = R"EOF( + { + "actions": [ + { + "type": "header_value_match", + "descriptor_value": "fake_value", + "expect_match": false, + "headers": [ + { + "name": "x-header-name", + "value": "test_value", + "regex": false + } + ] + } + ] + } + )EOF"; + + SetUpTest(json); + Http::TestHeaderMapImpl header{{"x-header-name", "test_value"}}; rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header, ""); EXPECT_TRUE(descriptors_.empty());