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 732e1a57421d0..f13f43459a0fa 100644 --- a/docs/configuration/http_conn_man/route_config/rate_limits.rst +++ b/docs/configuration/http_conn_man/route_config/rate_limits.rst @@ -43,8 +43,8 @@ Actions type *(required, string)* The type of rate limit action to perform. The currently supported action - types are *source_cluster*, *destination_cluster* , *request_headers*, *remote_address* and - *generic_key*. + types are *source_cluster*, *destination_cluster* , *request_headers*, *remote_address*, + *generic_key* and *header_value_match*. Source Cluster ^^^^^^^^^^^^^^ @@ -141,6 +141,31 @@ The following descriptor entry is appended to the descriptor: * ("generic_key", "") +Header Value Match +^^^^^^^^^^^^^^^^^^ + +.. code-block:: json + + { + "type": "header_value_match", + "descriptor_value" : "...", + "headers" : [] + } + + +descriptor_value + *(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). + +The following descriptor entry is appended to the descriptor if the request matches the headers +specified in the action config: + + * ("header_match", "") .. _config_http_conn_man_route_table_rate_limit_composing_actions: diff --git a/docs/configuration/http_conn_man/route_config/route.rst b/docs/configuration/http_conn_man/route_config/route.rst index 161f418f7c0fd..415d090dfa2c9 100644 --- a/docs/configuration/http_conn_man/route_config/route.rst +++ b/docs/configuration/http_conn_man/route_config/route.rst @@ -105,7 +105,7 @@ auto_host_rewrite *(optional, boolean)* Indicates that during forwarding, the host header will be swapped with the hostname of the upstream host chosen by the cluster manager. This option is applicable only when the destination cluster for a route is of type *strict_dns* or *logical_dns*. Setting this to true - with other cluster types has no effect. *auto_host_rewrite* and *host_rewrite* are mutually exclusive + with other cluster types has no effect. *auto_host_rewrite* and *host_rewrite* are mutually exclusive options. Only one can be specified. .. _config_http_conn_man_route_table_route_case_sensitive: @@ -137,7 +137,10 @@ priority `. :ref:`headers ` - *(optional, array)* Specifies a set of headers that the route should match on. + *(optional, array)* Specifies a set of headers that the route should match on. The router will + check the request's headers against all the specified headers in the route config. A match will + happen if all the headers in the route are present in the request with the same values (or based + on presence if the ``value`` field is not in the config). .. _config_http_conn_man_route_table_route_rate_limits: @@ -238,13 +241,13 @@ runtime_key Headers ------- -The router can match a request to a route based on headers specified in the route config. - .. code-block:: json - [ - {"name": "...", "value": "...", "regex": "..."} - ] + { + "name": "...", + "value": "...", + "regex": "..." + } name *(required, string)* Specifies the name of the header in the request. @@ -258,10 +261,6 @@ regex expression or not. Defaults to false. The regex grammar used in the value field is defined `here `_. -The router will check the request's headers against all the specified -headers in the route config. A match will happen if all the headers in the route are present in -the request with the same values (or based on presence if the ``value`` field is not in the config). - .. attention:: Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 *Host* diff --git a/docs/configuration/http_filters/fault_filter.rst b/docs/configuration/http_filters/fault_filter.rst index 7e1c82af5eee8..b3364e3f587b5 100644 --- a/docs/configuration/http_filters/fault_filter.rst +++ b/docs/configuration/http_filters/fault_filter.rst @@ -57,8 +57,14 @@ upstream_cluster: cluster that the filter should match on. Fault injection will be restricted to requests bound to the specific upstream cluster. -:ref:`headers ` - *(optional, array)* Specifies a set of headers that the filter should match on. +:ref:`headers ` + *(optional, array)* Specifies a set of headers that the filter should match on. The fault + injection filter can be applied selectively to requests that match a set of headers specified in + the fault filter config. The chances of actual fault injection further depend on the values of + *abort_percent* and *fixed_delay_percent* parameters.The filter will check the request's headers + against all the specified headers in the filter 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 abort and delay blocks can be omitted. If they are not specified in the configuration file, their respective values will be obtained from the @@ -134,41 +140,6 @@ http.fault.delay.fixed_duration_ms is missing from both the runtime and the config, no delays will be injected. -.. _config_http_filters_fault_injection_headers: - -Headers -------- - -The fault injection filter can be applied selectively to requests that -match a set of headers specified in the fault filter config. The chances of -actual fault injection further depend on the values of *abort_percent* and -*fixed_delay_percent* parameters. Each element of the array in the -*headers* field should be in the following format: - -.. code-block:: json - - [ - {"name": "...", "value": "...", "regex": "..."} - ] - -name - *(required, string)* Specifies the name of the header in the request. - -value - *(optional, string)* Specifies the value of the header. If the value is - absent a request that has the *name* header will match, regardless of the - header's value. - -regex - *(optional, boolean)* Specifies whether the header value is a regular expression - or not. Defaults to false. The regex grammar used in the value field - is defined `here `_. - -The filter will check the request's headers against all the specified -headers in the filter 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). - Statistics ---------- diff --git a/source/common/CMakeLists.txt b/source/common/CMakeLists.txt index 0878ccab04daa..f99da8492cf64 100644 --- a/source/common/CMakeLists.txt +++ b/source/common/CMakeLists.txt @@ -90,6 +90,7 @@ add_library( redis/conn_pool_impl.cc redis/proxy_filter.cc router/config_impl.cc + router/config_utility.cc router/rds_impl.cc router/retry_state_impl.cc router/router.cc diff --git a/source/common/http/filter/fault_filter.cc b/source/common/http/filter/fault_filter.cc index ce5abc0d76687..61a3a97e23b32 100644 --- a/source/common/http/filter/fault_filter.cc +++ b/source/common/http/filter/fault_filter.cc @@ -67,10 +67,7 @@ FaultFilterConfig::FaultFilterConfig(const Json::Object& json_config, Runtime::L if (json_config.hasObject("headers")) { std::vector config_headers = json_config.getObjectArray("headers"); for (const Json::ObjectPtr& header_map : config_headers) { - // allow header value to be empty, allows matching to be only based on header presence. - fault_filter_headers_.emplace_back(Http::LowerCaseString(header_map->getString("name")), - header_map->getString("value", EMPTY_STRING), - header_map->getBoolean("regex", false)); + fault_filter_headers_.push_back(*header_map); } } diff --git a/source/common/json/config_schemas.cc b/source/common/json/config_schemas.cc index 85bd0ff050c91..495ca716d28e6 100644 --- a/source/common/json/config_schemas.cc +++ b/source/common/json/config_schemas.cc @@ -526,14 +526,7 @@ const std::string Json::Schema::ROUTE_ENTRY_CONFIGURATION_SCHEMA(R"EOF( "type" : "array", "minItems" : 1, "items" : { - "type" : "object", - "properties" : { - "name" : {"type" : "string"}, - "value" : {"type" : "string"}, - "regex" : {"type" : "boolean"} - }, - "required" : ["name"], - "additionalProperties" : false + "type" : "object" } }, "rate_limits" : {"type" : "array"}, @@ -550,6 +543,20 @@ const std::string Json::Schema::ROUTE_ENTRY_CONFIGURATION_SCHEMA(R"EOF( } )EOF"); +const std::string Json::Schema::HEADER_DATA_CONFIGURATION_SCHEMA(R"EOF( + { + "$schema" : "http://json-schema.org/schema#", + "type" : "object", + "properties" : { + "name" : {"type" : "string"}, + "value" : {"type" : "string"}, + "regex" : {"type" : "boolean"} + }, + "required" : ["name"], + "additionalProperties" : false + } + )EOF"); + const std::string Json::Schema::HTTP_RATE_LIMITS_CONFIGURATION_SCHEMA(R"EOF( { "$schema": "http://json-schema.org/schema#", @@ -611,6 +618,25 @@ const std::string Json::Schema::HTTP_RATE_LIMITS_CONFIGURATION_SCHEMA(R"EOF( }, "required" : ["type", "descriptor_value"], "additionalProperties" : false + }, + "header_value_match" : { + "type" : "object", + "properties" : { + "type" : { + "type" : "string", + "enum" : ["header_value_match"] + }, + "descriptor_value" : {"type" : "string"}, + "headers" : { + "type" : "array", + "minItems" : 1, + "items" : { + "type" : "object" + } + }, + "required" : ["type", "descriptor_value", "headers"], + "additionalProperties" : false + } } }, "type" : "object", @@ -626,7 +652,8 @@ const std::string Json::Schema::HTTP_RATE_LIMITS_CONFIGURATION_SCHEMA(R"EOF( {"$ref" : "#/definitions/destination_cluster"}, {"$ref" : "#/definitions/request_headers"}, {"$ref" : "#/definitions/remote_address"}, - {"$ref" : "#/definitions/generic_key"} + {"$ref" : "#/definitions/generic_key"}, + {"$ref" : "#/definitions/header_value_match"} ] } } @@ -697,14 +724,7 @@ const std::string Json::Schema::FAULT_HTTP_FILTER_SCHEMA(R"EOF( "type" : "array", "minItems" : 1, "items" : { - "type" : "object", - "properties" : { - "name" : {"type" : "string"}, - "value" : {"type" : "string"}, - "regex" : {"type" : "boolean"} - }, - "required" : ["name"], - "additionalProperties" : false + "type" : "object" } } }, diff --git a/source/common/json/config_schemas.h b/source/common/json/config_schemas.h index eb631ecef88e4..25fbefddb5659 100644 --- a/source/common/json/config_schemas.h +++ b/source/common/json/config_schemas.h @@ -24,6 +24,7 @@ class Schema { static const std::string ROUTE_ENTRY_CONFIGURATION_SCHEMA; static const std::string HTTP_RATE_LIMITS_CONFIGURATION_SCHEMA; static const std::string RDS_CONFIGURATION_SCHEMA; + static const std::string HEADER_DATA_CONFIGURATION_SCHEMA; // HTTP Filter Schemas static const std::string BUFFER_HTTP_FILTER_SCHEMA; diff --git a/source/common/json/json_validator.h b/source/common/json/json_validator.h new file mode 100644 index 0000000000000..c01c972522fde --- /dev/null +++ b/source/common/json/json_validator.h @@ -0,0 +1,17 @@ +#pragma once + +#include "envoy/json/json_object.h" + +namespace Json { + +/** + * Base class to inherit from to validate config schema before initializing member variables. + */ +class JsonValidator { +public: + JsonValidator(const Json::Object& config, const std::string& schema) { + config.validateSchema(schema); + } +}; + +} // Json diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 8504c83256575..80f780358ad5e 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -38,41 +38,6 @@ ShadowPolicyImpl::ShadowPolicyImpl(const Json::Object& config) { runtime_key_ = config.getObject("shadow")->getString("runtime_key", ""); } -Upstream::ResourcePriority ConfigUtility::parsePriority(const Json::Object& config) { - std::string priority_string = config.getString("priority", "default"); - if (priority_string == "default") { - return Upstream::ResourcePriority::Default; - } else if (priority_string == "high") { - return Upstream::ResourcePriority::High; - } else { - throw EnvoyException(fmt::format("invalid resource priority '{}'", priority_string)); - } -} - -bool ConfigUtility::matchHeaders(const Http::HeaderMap& request_headers, - const std::vector& config_headers) { - bool matches = true; - - if (!config_headers.empty()) { - for (const HeaderData& cfg_header_data : config_headers) { - const Http::HeaderEntry* header = request_headers.get(cfg_header_data.name_); - if (cfg_header_data.value_.empty()) { - matches &= (header != nullptr); - } else if (!cfg_header_data.is_regex_) { - matches &= (header != nullptr) && (header->value() == cfg_header_data.value_.c_str()); - } else { - matches &= (header != nullptr) && - std::regex_match(header->value().c_str(), cfg_header_data.regex_pattern_); - } - if (!matches) { - break; - } - } - } - - return matches; -} - HashPolicyImpl::HashPolicyImpl(const Json::Object& config) : header_name_(config.getString("header_name")) {} @@ -164,12 +129,7 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, const Json: if (route.hasObject("headers")) { std::vector config_headers = route.getObjectArray("headers"); for (const Json::ObjectPtr& header_map : config_headers) { - // allow header value to be empty, allows matching to be only based on header presence. - // Regex is an opt-in. Unless explicitly mentioned, we will use header values for exact string - // matches. - config_headers_.emplace_back(Http::LowerCaseString(header_map->getString("name")), - header_map->getString("value", EMPTY_STRING), - header_map->getBoolean("regex", false)); + config_headers_.push_back(*header_map); } } diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index 2e51dfb007586..85fd84c988d8e 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -7,6 +7,8 @@ #include "envoy/runtime/runtime.h" #include "envoy/upstream/cluster_manager.h" +#include "common/router/config_utility.h" + namespace Router { /** @@ -48,38 +50,6 @@ class SslRedirectRoute : public Route { static const SslRedirector SSL_REDIRECTOR; }; -/** - * Utility routines for loading route configuration and matching runtime request headers. - */ -class ConfigUtility { -public: - struct HeaderData { - HeaderData(const Http::LowerCaseString& name, const std::string& value, const bool is_regex) - : name_(name), value_(value), regex_pattern_(value_, std::regex::optimize), - is_regex_(is_regex) {} - - const Http::LowerCaseString name_; - const std::string value_; - const std::regex regex_pattern_; - const bool is_regex_; - }; - - /** - * @return the resource priority parsed from JSON. - */ - static Upstream::ResourcePriority parsePriority(const Json::Object& config); - - /** - * See if the specified headers are present in the request headers. - * @param headers supplies the list of headers to match - * @param request_headers supplies the list of request headers to compare against search_list - * @return true all the headers (and values) in the search_list set are found in the - * request_headers - */ - static bool matchHeaders(const Http::HeaderMap& headers, - const std::vector& request_headers); -}; - /** * Holds all routing configuration for an entire virtual host. */ diff --git a/source/common/router/config_utility.cc b/source/common/router/config_utility.cc new file mode 100644 index 0000000000000..f227a1eae54a2 --- /dev/null +++ b/source/common/router/config_utility.cc @@ -0,0 +1,40 @@ +#include "config_utility.h" + +namespace Router { + +Upstream::ResourcePriority ConfigUtility::parsePriority(const Json::Object& config) { + std::string priority_string = config.getString("priority", "default"); + if (priority_string == "default") { + return Upstream::ResourcePriority::Default; + } else if (priority_string == "high") { + return Upstream::ResourcePriority::High; + } else { + throw EnvoyException(fmt::format("invalid resource priority '{}'", priority_string)); + } +} + +bool ConfigUtility::matchHeaders(const Http::HeaderMap& request_headers, + const std::vector& config_headers) { + bool matches = true; + + if (!config_headers.empty()) { + for (const HeaderData& cfg_header_data : config_headers) { + const Http::HeaderEntry* header = request_headers.get(cfg_header_data.name_); + if (cfg_header_data.value_.empty()) { + matches &= (header != nullptr); + } else if (!cfg_header_data.is_regex_) { + matches &= (header != nullptr) && (header->value() == cfg_header_data.value_.c_str()); + } else { + matches &= (header != nullptr) && + std::regex_match(header->value().c_str(), cfg_header_data.regex_pattern_); + } + if (!matches) { + break; + } + } + } + + return matches; +} + +} // Router diff --git a/source/common/router/config_utility.h b/source/common/router/config_utility.h new file mode 100644 index 0000000000000..e60f5a6c2afaf --- /dev/null +++ b/source/common/router/config_utility.h @@ -0,0 +1,50 @@ +#pragma once + +#include "envoy/json/json_object.h" +#include "envoy/upstream/resource_manager.h" + +#include "common/common/empty_string.h" +#include "common/http/headers.h" +#include "common/json/config_schemas.h" +#include "common/json/json_validator.h" + +namespace Router { + +/** + * Utility routines for loading route configuration and matching runtime request headers. + */ +class ConfigUtility { +public: + struct HeaderData : Json::JsonValidator { + // An empty header value allows for matching to be only based on header presence. + // Regex is an opt-in. Unless explicitly mentioned, the header values will be used for + // exact string matching. + HeaderData(const Json::Object& config) + : Json::JsonValidator(config, Json::Schema::HEADER_DATA_CONFIGURATION_SCHEMA), + name_(config.getString("name")), value_(config.getString("value", EMPTY_STRING)), + regex_pattern_(value_, std::regex::optimize), + is_regex_(config.getBoolean("regex", false)) {} + + const Http::LowerCaseString name_; + const std::string value_; + const std::regex regex_pattern_; + const bool is_regex_; + }; + + /** + * @return the resource priority parsed from JSON. + */ + static Upstream::ResourcePriority parsePriority(const Json::Object& config); + + /** + * See if the specified headers are present in the request headers. + * @param headers supplies the list of headers to match + * @param request_headers supplies the list of request headers to compare against search_list + * @return true all the headers (and values) in the search_list set are found in the + * request_headers + */ + static bool matchHeaders(const Http::HeaderMap& headers, + const std::vector& request_headers); +}; + +} // Router diff --git a/source/common/router/router_ratelimit.cc b/source/common/router/router_ratelimit.cc index cebad84998616..e20e814106cb0 100644 --- a/source/common/router/router_ratelimit.cc +++ b/source/common/router/router_ratelimit.cc @@ -1,5 +1,6 @@ #include "router_ratelimit.h" +#include "common/common/empty_string.h" #include "common/json/config_schemas.h" namespace Router { @@ -50,6 +51,23 @@ void GenericKeyAction::populateDescriptor(const Router::RouteEntry&, descriptor.entries_.push_back({"generic_key", descriptor_value_}); } +HeaderValueMatchAction::HeaderValueMatchAction(const Json::Object& action) + : descriptor_value_(action.getString("descriptor_value")) { + std::vector config_headers = action.getObjectArray("headers"); + for (const Json::ObjectPtr& header_map : config_headers) { + action_headers_.push_back(*header_map); + } +} + +void 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_)) { + descriptor.entries_.push_back({"header_match", descriptor_value_}); + } +} + RateLimitPolicyEntryImpl::RateLimitPolicyEntryImpl(const Json::Object& config) : disable_key_(config.getString("disable_key", "")), stage_(config.getInteger("stage", 0)) { @@ -67,6 +85,8 @@ RateLimitPolicyEntryImpl::RateLimitPolicyEntryImpl(const Json::Object& config) actions_.emplace_back(new RemoteAddressAction()); } else if (type == "generic_key") { actions_.emplace_back(new GenericKeyAction(*action)); + } else if (type == "header_value_match") { + actions_.emplace_back(new HeaderValueMatchAction(*action)); } else { throw EnvoyException(fmt::format("unknown http rate limit filter action '{}'", type)); } diff --git a/source/common/router/router_ratelimit.h b/source/common/router/router_ratelimit.h index 60e98f3b3ac6f..1a986ea534e72 100644 --- a/source/common/router/router_ratelimit.h +++ b/source/common/router/router_ratelimit.h @@ -4,6 +4,7 @@ #include "envoy/router/router_ratelimit.h" #include "common/http/filter/ratelimit.h" +#include "common/router/config_utility.h" namespace Router { @@ -76,6 +77,23 @@ class GenericKeyAction : public RateLimitAction { const std::string descriptor_value_; }; +/** + * Action for header value match rate limiting. + */ +class HeaderValueMatchAction : public RateLimitAction { +public: + HeaderValueMatchAction(const Json::Object& action); + + // Router::RateLimitAction + void populateDescriptor(const Router::RouteEntry& route, ::RateLimit::Descriptor& descriptor, + const std::string& local_service_cluster, const Http::HeaderMap& headers, + const std::string& remote_address) const override; + +private: + const std::string descriptor_value_; + std::vector action_headers_; +}; + /* * Implementation of RateLimitPolicyEntry that holds the action for the configuration. */ diff --git a/test/common/router/router_ratelimit_test.cc b/test/common/router/router_ratelimit_test.cc index 9951e8855a9be..b8ed63019fb16 100644 --- a/test/common/router/router_ratelimit_test.cc +++ b/test/common/router/router_ratelimit_test.cc @@ -306,7 +306,7 @@ TEST_F(RateLimitPolicyEntryTest, RateLimitPolicyEntryMembers) { { "stage": 2, "disable_key": "no_ratelimit", - "actions":[ + "actions": [ { "type": "remote_address" } @@ -323,7 +323,7 @@ TEST_F(RateLimitPolicyEntryTest, RateLimitPolicyEntryMembers) { TEST_F(RateLimitPolicyEntryTest, RemoteAddress) { std::string json = R"EOF( { - "actions":[ + "actions": [ { "type": "remote_address" } @@ -342,7 +342,7 @@ TEST_F(RateLimitPolicyEntryTest, RemoteAddress) { TEST_F(RateLimitPolicyEntryTest, NoAddress) { std::string json = R"EOF( { - "actions":[ + "actions": [ { "type": "remote_address" } @@ -359,7 +359,7 @@ TEST_F(RateLimitPolicyEntryTest, NoAddress) { TEST_F(RateLimitPolicyEntryTest, SourceService) { std::string json = R"EOF( { - "actions":[ + "actions": [ { "type": "source_cluster" } @@ -377,7 +377,7 @@ TEST_F(RateLimitPolicyEntryTest, SourceService) { TEST_F(RateLimitPolicyEntryTest, DestinationService) { std::string json = R"EOF( { - "actions":[ + "actions": [ { "type": "destination_cluster" } @@ -399,7 +399,7 @@ TEST_F(RateLimitPolicyEntryTest, RequestHeaders) { { "type": "request_headers", "header_name": "x-header-name", - "descriptor_key" : "my_header_name" + "descriptor_key": "my_header_name" } ] } @@ -420,7 +420,7 @@ TEST_F(RateLimitPolicyEntryTest, RequestHeadersNoMatch) { { "type": "request_headers", "header_name": "x-header", - "descriptor_key" : "my_header_name" + "descriptor_key": "my_header_name" } ] } @@ -436,10 +436,10 @@ TEST_F(RateLimitPolicyEntryTest, RequestHeadersNoMatch) { TEST_F(RateLimitPolicyEntryTest, RateLimitKey) { std::string json = R"EOF( { - "actions":[ + "actions": [ { "type": "generic_key", - "descriptor_value" : "fake_key" + "descriptor_value": "fake_key" } ] } @@ -452,12 +452,65 @@ TEST_F(RateLimitPolicyEntryTest, RateLimitKey) { testing::ContainerEq(descriptors_)); } +TEST_F(RateLimitPolicyEntryTest, HeadeValueMatch) { + std::string json = R"EOF( + { + "actions": [ + { + "type": "header_value_match", + "descriptor_value": "fake_value", + "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_THAT(std::vector<::RateLimit::Descriptor>({{{{"header_match", "fake_value"}}}}), + testing::ContainerEq(descriptors_)); +} + +TEST_F(RateLimitPolicyEntryTest, HeadeValueMatchNoMatch) { + std::string json = R"EOF( + { + "actions": [ + { + "type": "header_value_match", + "descriptor_value": "fake_value", + "headers": [ + { + "name": "x-header-name", + "value": "test_value", + "regex": false + } + ] + } + ] + } + )EOF"; + + SetUpTest(json); + Http::TestHeaderMapImpl header{{"x-header-name", "fake_value"}}; + + rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header, ""); + EXPECT_TRUE(descriptors_.empty()); +} + TEST_F(RateLimitPolicyEntryTest, CompoundActions) { std::string json = R"EOF( { - "actions":[ + "actions": [ { - "type" : "destination_cluster" + "type": "destination_cluster" }, { "type": "source_cluster"