diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index 407f285310a5..595fde141e6c 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -1510,7 +1510,7 @@ message VirtualCluster { message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit"; - // [#next-free-field: 8] + // [#next-free-field: 9] message Action { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit.Action"; @@ -1627,11 +1627,15 @@ message RateLimit { repeated HeaderMatcher headers = 3 [(validate.rules).repeated = {min_items: 1}]; } - // The following descriptor entry is appended when the dynamic metadata contains a key value: + // The following descriptor entry is appended when the + // :ref:`dynamic metadata ` contains a key value: // // .. code-block:: cpp // - // ("", "") + // ("", "") + // + // .. attention:: + // This action has been deprecated in favor of the :ref:`metadata ` action message DynamicMetaData { // The key to use in the descriptor entry. string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; @@ -1645,6 +1649,35 @@ message RateLimit { string default_value = 3; } + // The following descriptor entry is appended when the metadata contains a key value: + // + // .. code-block:: cpp + // + // ("", "") + message MetaData { + enum Source { + // Query :ref:`dynamic metadata ` + DYNAMIC = 0; + + // Query :ref:`route entry metadata ` + ROUTE_ENTRY = 1; + } + + // The key to use in the descriptor entry. + string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; + + // Metadata struct that defines the key and path to retrieve the string value. A match will + // only happen if the value in the metadata is of type string. + type.metadata.v3.MetadataKey metadata_key = 2 [(validate.rules).message = {required: true}]; + + // An optional value to use if *metadata_key* is empty. If not set and + // no value is present under the metadata_key then no descriptor is generated. + string default_value = 3; + + // Source of metadata + Source source = 4 [(validate.rules).enum = {defined_only: true}]; + } + oneof action_specifier { option (validate.required) = true; @@ -1667,7 +1700,14 @@ message RateLimit { HeaderValueMatch header_value_match = 6; // Rate limit on dynamic metadata. - DynamicMetaData dynamic_metadata = 7; + // + // .. attention:: + // This field has been deprecated in favor of the :ref:`metadata ` field + DynamicMetaData dynamic_metadata = 7 + [deprecated = true, (envoy.annotations.disallowed_by_default) = true]; + + // Rate limit on metadata. + MetaData metadata = 8; } } diff --git a/api/envoy/config/route/v4alpha/route_components.proto b/api/envoy/config/route/v4alpha/route_components.proto index 766d64ebedee..0bf0b493e956 100644 --- a/api/envoy/config/route/v4alpha/route_components.proto +++ b/api/envoy/config/route/v4alpha/route_components.proto @@ -1459,7 +1459,7 @@ message VirtualCluster { message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit"; - // [#next-free-field: 8] + // [#next-free-field: 9] message Action { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit.Action"; @@ -1576,11 +1576,15 @@ message RateLimit { repeated HeaderMatcher headers = 3 [(validate.rules).repeated = {min_items: 1}]; } - // The following descriptor entry is appended when the dynamic metadata contains a key value: + // The following descriptor entry is appended when the + // :ref:`dynamic metadata ` contains a key value: // // .. code-block:: cpp // - // ("", "") + // ("", "") + // + // .. attention:: + // This action has been deprecated in favor of the :ref:`metadata ` action message DynamicMetaData { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit.Action.DynamicMetaData"; @@ -1597,6 +1601,42 @@ message RateLimit { string default_value = 3; } + // The following descriptor entry is appended when the metadata contains a key value: + // + // .. code-block:: cpp + // + // ("", "") + message MetaData { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.route.v3.RateLimit.Action.MetaData"; + + enum Source { + // Query :ref:`dynamic metadata ` + DYNAMIC = 0; + + // Query :ref:`route entry metadata ` + ROUTE_ENTRY = 1; + } + + // The key to use in the descriptor entry. + string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; + + // Metadata struct that defines the key and path to retrieve the string value. A match will + // only happen if the value in the metadata is of type string. + type.metadata.v3.MetadataKey metadata_key = 2 [(validate.rules).message = {required: true}]; + + // An optional value to use if *metadata_key* is empty. If not set and + // no value is present under the metadata_key then no descriptor is generated. + string default_value = 3; + + // Source of metadata + Source source = 4 [(validate.rules).enum = {defined_only: true}]; + } + + reserved 7; + + reserved "dynamic_metadata"; + oneof action_specifier { option (validate.required) = true; @@ -1618,8 +1658,8 @@ message RateLimit { // Rate limit on the existence of request headers. HeaderValueMatch header_value_match = 6; - // Rate limit on dynamic metadata. - DynamicMetaData dynamic_metadata = 7; + // Rate limit on metadata. + MetaData metadata = 8; } } diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 74060d4e5c5f..fa38b7b297aa 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -31,8 +31,10 @@ New Features * hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. * health_check: added option to use :ref:`no_traffic_healthy_interval ` which allows a different no traffic interval when the host is healthy. * mongo_proxy: the list of commands to produce metrics for is now :ref:`configurable `. +* ratelimit: added support for use of various :ref:`metadata ` as a ratelimit action. * ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. * tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. Deprecated ---------- +* ratelimit: the :ref:`dynamic metadata ` action is deprecated in favor of the more generic :ref:`metadata ` action. diff --git a/generated_api_shadow/envoy/config/route/v3/route_components.proto b/generated_api_shadow/envoy/config/route/v3/route_components.proto index a3a823cafe44..e203cdcf4e84 100644 --- a/generated_api_shadow/envoy/config/route/v3/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v3/route_components.proto @@ -1522,7 +1522,7 @@ message VirtualCluster { message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit"; - // [#next-free-field: 8] + // [#next-free-field: 9] message Action { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit.Action"; @@ -1639,11 +1639,15 @@ message RateLimit { repeated HeaderMatcher headers = 3 [(validate.rules).repeated = {min_items: 1}]; } - // The following descriptor entry is appended when the dynamic metadata contains a key value: + // The following descriptor entry is appended when the + // :ref:`dynamic metadata ` contains a key value: // // .. code-block:: cpp // - // ("", "") + // ("", "") + // + // .. attention:: + // This action has been deprecated in favor of the :ref:`metadata ` action message DynamicMetaData { // The key to use in the descriptor entry. string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; @@ -1657,6 +1661,35 @@ message RateLimit { string default_value = 3; } + // The following descriptor entry is appended when the metadata contains a key value: + // + // .. code-block:: cpp + // + // ("", "") + message MetaData { + enum Source { + // Query :ref:`dynamic metadata ` + DYNAMIC = 0; + + // Query :ref:`route entry metadata ` + ROUTE_ENTRY = 1; + } + + // The key to use in the descriptor entry. + string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; + + // Metadata struct that defines the key and path to retrieve the string value. A match will + // only happen if the value in the metadata is of type string. + type.metadata.v3.MetadataKey metadata_key = 2 [(validate.rules).message = {required: true}]; + + // An optional value to use if *metadata_key* is empty. If not set and + // no value is present under the metadata_key then no descriptor is generated. + string default_value = 3; + + // Source of metadata + Source source = 4 [(validate.rules).enum = {defined_only: true}]; + } + oneof action_specifier { option (validate.required) = true; @@ -1679,7 +1712,14 @@ message RateLimit { HeaderValueMatch header_value_match = 6; // Rate limit on dynamic metadata. - DynamicMetaData dynamic_metadata = 7; + // + // .. attention:: + // This field has been deprecated in favor of the :ref:`metadata ` field + DynamicMetaData dynamic_metadata = 7 + [deprecated = true, (envoy.annotations.disallowed_by_default) = true]; + + // Rate limit on metadata. + MetaData metadata = 8; } } diff --git a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto index 4d4a93eebe60..12c56dd834a4 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto @@ -1526,7 +1526,7 @@ message VirtualCluster { message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit"; - // [#next-free-field: 8] + // [#next-free-field: 9] message Action { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit.Action"; @@ -1643,11 +1643,15 @@ message RateLimit { repeated HeaderMatcher headers = 3 [(validate.rules).repeated = {min_items: 1}]; } - // The following descriptor entry is appended when the dynamic metadata contains a key value: + // The following descriptor entry is appended when the + // :ref:`dynamic metadata ` contains a key value: // // .. code-block:: cpp // - // ("", "") + // ("", "") + // + // .. attention:: + // This action has been deprecated in favor of the :ref:`metadata ` action message DynamicMetaData { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit.Action.DynamicMetaData"; @@ -1664,6 +1668,38 @@ message RateLimit { string default_value = 3; } + // The following descriptor entry is appended when the metadata contains a key value: + // + // .. code-block:: cpp + // + // ("", "") + message MetaData { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.route.v3.RateLimit.Action.MetaData"; + + enum Source { + // Query :ref:`dynamic metadata ` + DYNAMIC = 0; + + // Query :ref:`route entry metadata ` + ROUTE_ENTRY = 1; + } + + // The key to use in the descriptor entry. + string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; + + // Metadata struct that defines the key and path to retrieve the string value. A match will + // only happen if the value in the metadata is of type string. + type.metadata.v3.MetadataKey metadata_key = 2 [(validate.rules).message = {required: true}]; + + // An optional value to use if *metadata_key* is empty. If not set and + // no value is present under the metadata_key then no descriptor is generated. + string default_value = 3; + + // Source of metadata + Source source = 4 [(validate.rules).enum = {defined_only: true}]; + } + oneof action_specifier { option (validate.required) = true; @@ -1686,7 +1722,14 @@ message RateLimit { HeaderValueMatch header_value_match = 6; // Rate limit on dynamic metadata. - DynamicMetaData dynamic_metadata = 7; + // + // .. attention:: + // This field has been deprecated in favor of the :ref:`metadata ` field + DynamicMetaData hidden_envoy_deprecated_dynamic_metadata = 7 + [deprecated = true, (envoy.annotations.disallowed_by_default) = true]; + + // Rate limit on metadata. + MetaData metadata = 8; } } diff --git a/source/common/router/router_ratelimit.cc b/source/common/router/router_ratelimit.cc index c75d58b50d9b..0774f8340be5 100644 --- a/source/common/router/router_ratelimit.cc +++ b/source/common/router/router_ratelimit.cc @@ -102,22 +102,40 @@ bool GenericKeyAction::populateDescriptor(const Router::RouteEntry&, return true; } -DynamicMetaDataAction::DynamicMetaDataAction( +MetaDataAction::MetaDataAction(const envoy::config::route::v3::RateLimit::Action::MetaData& action) + : metadata_key_(action.metadata_key()), descriptor_key_(action.descriptor_key()), + default_value_(action.default_value()), source_(action.source()) {} + +MetaDataAction::MetaDataAction( const envoy::config::route::v3::RateLimit::Action::DynamicMetaData& action) : metadata_key_(action.metadata_key()), descriptor_key_(action.descriptor_key()), - default_value_(action.default_value()) {} + default_value_(action.default_value()), + source_(envoy::config::route::v3::RateLimit::Action::MetaData::DYNAMIC) {} -bool DynamicMetaDataAction::populateDescriptor( - const Router::RouteEntry&, RateLimit::Descriptor& descriptor, const std::string&, +bool MetaDataAction::populateDescriptor( + const Router::RouteEntry& route, RateLimit::Descriptor& descriptor, const std::string&, const Http::HeaderMap&, const Network::Address::Instance&, const envoy::config::core::v3::Metadata* dynamic_metadata) const { - const ProtobufWkt::Value& metadata_value = - Envoy::Config::Metadata::metadataValue(dynamic_metadata, metadata_key_); + const envoy::config::core::v3::Metadata* metadata_source; + + switch (source_) { + case envoy::config::route::v3::RateLimit::Action::MetaData::DYNAMIC: + metadata_source = dynamic_metadata; + break; + case envoy::config::route::v3::RateLimit::Action::MetaData::ROUTE_ENTRY: + metadata_source = &route.metadata(); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + + const std::string metadata_string_value = + Envoy::Config::Metadata::metadataValue(metadata_source, metadata_key_).string_value(); - if (!metadata_value.string_value().empty()) { - descriptor.entries_.push_back({descriptor_key_, metadata_value.string_value()}); + if (!metadata_string_value.empty()) { + descriptor.entries_.push_back({descriptor_key_, metadata_string_value}); return true; - } else if (metadata_value.string_value().empty() && !default_value_.empty()) { + } else if (metadata_string_value.empty() && !default_value_.empty()) { descriptor.entries_.push_back({descriptor_key_, default_value_}); return true; } @@ -166,7 +184,10 @@ RateLimitPolicyEntryImpl::RateLimitPolicyEntryImpl( actions_.emplace_back(new GenericKeyAction(action.generic_key())); break; case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kDynamicMetadata: - actions_.emplace_back(new DynamicMetaDataAction(action.dynamic_metadata())); + actions_.emplace_back(new MetaDataAction(action.dynamic_metadata())); + break; + case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kMetadata: + actions_.emplace_back(new MetaDataAction(action.metadata())); break; case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kHeaderValueMatch: actions_.emplace_back(new HeaderValueMatchAction(action.header_value_match())); diff --git a/source/common/router/router_ratelimit.h b/source/common/router/router_ratelimit.h index 9ea90a5d46b0..912606fc0da8 100644 --- a/source/common/router/router_ratelimit.h +++ b/source/common/router/router_ratelimit.h @@ -114,11 +114,13 @@ class GenericKeyAction : public RateLimitAction { }; /** - * Action for dynamic metadata rate limiting. + * Action for metadata rate limiting. */ -class DynamicMetaDataAction : public RateLimitAction { +class MetaDataAction : public RateLimitAction { public: - DynamicMetaDataAction(const envoy::config::route::v3::RateLimit::Action::DynamicMetaData& action); + MetaDataAction(const envoy::config::route::v3::RateLimit::Action::MetaData& action); + // for maintaining backward compatibility with the deprecated DynamicMetaData action + MetaDataAction(const envoy::config::route::v3::RateLimit::Action::DynamicMetaData& action); // Router::RateLimitAction bool populateDescriptor(const Router::RouteEntry& route, RateLimit::Descriptor& descriptor, const std::string& local_service_cluster, const Http::HeaderMap& headers, @@ -129,6 +131,7 @@ class DynamicMetaDataAction : public RateLimitAction { const Envoy::Config::MetadataKey metadata_key_; const std::string descriptor_key_; const std::string default_value_; + const envoy::config::route::v3::RateLimit::Action::MetaData::Source source_; }; /** diff --git a/test/common/router/router_ratelimit_test.cc b/test/common/router/router_ratelimit_test.cc index 7d5fba68835f..bad535cbf100 100644 --- a/test/common/router/router_ratelimit_test.cc +++ b/test/common/router/router_ratelimit_test.cc @@ -481,7 +481,7 @@ TEST_F(RateLimitPolicyEntryTest, GenericKeyWithEmptyDescriptorKey) { testing::ContainerEq(descriptors_)); } -TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataMatch) { +TEST_F(RateLimitPolicyEntryTest, DEPRECATED_FEATURE_TEST(DynamicMetaDataMatch)) { const std::string yaml = R"EOF( actions: - dynamic_metadata: @@ -513,11 +513,108 @@ TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataMatch) { testing::ContainerEq(descriptors_)); } +TEST_F(RateLimitPolicyEntryTest, MetaDataMatchDynamicSourceByDefault) { + const std::string yaml = R"EOF( +actions: +- metadata: + descriptor_key: fake_key + default_value: fake_value + metadata_key: + key: 'envoy.xxx' + path: + - key: test + - key: prop + )EOF"; + + setupTest(yaml); + + std::string metadata_yaml = R"EOF( +filter_metadata: + envoy.xxx: + test: + prop: foo + )EOF"; + + envoy::config::core::v3::Metadata metadata; + TestUtility::loadFromYaml(metadata_yaml, metadata); + + rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, + &metadata); + + EXPECT_THAT(std::vector({{{{"fake_key", "foo"}}}}), + testing::ContainerEq(descriptors_)); +} + +TEST_F(RateLimitPolicyEntryTest, MetaDataMatchDynamicSource) { + const std::string yaml = R"EOF( +actions: +- metadata: + descriptor_key: fake_key + default_value: fake_value + metadata_key: + key: 'envoy.xxx' + path: + - key: test + - key: prop + source: DYNAMIC + )EOF"; + + setupTest(yaml); + + std::string metadata_yaml = R"EOF( +filter_metadata: + envoy.xxx: + test: + prop: foo + )EOF"; + + envoy::config::core::v3::Metadata metadata; + TestUtility::loadFromYaml(metadata_yaml, metadata); + + rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, + &metadata); + + EXPECT_THAT(std::vector({{{{"fake_key", "foo"}}}}), + testing::ContainerEq(descriptors_)); +} + +TEST_F(RateLimitPolicyEntryTest, MetaDataMatchRouteEntrySource) { + const std::string yaml = R"EOF( +actions: +- metadata: + descriptor_key: fake_key + default_value: fake_value + metadata_key: + key: 'envoy.xxx' + path: + - key: test + - key: prop + source: ROUTE_ENTRY + )EOF"; + + setupTest(yaml); + + std::string metadata_yaml = R"EOF( +filter_metadata: + envoy.xxx: + test: + prop: foo + )EOF"; + + TestUtility::loadFromYaml(metadata_yaml, route_.metadata_); + + rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, + dynamic_metadata_); + + EXPECT_THAT(std::vector({{{{"fake_key", "foo"}}}}), + testing::ContainerEq(descriptors_)); +} + // Tests that the default_value is used in the descriptor when the metadata_key is empty. -TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataNoMatchWithDefaultValue) { +TEST_F(RateLimitPolicyEntryTest, MetaDataNoMatchWithDefaultValue) { const std::string yaml = R"EOF( actions: -- dynamic_metadata: +- metadata: descriptor_key: fake_key default_value: fake_value metadata_key: @@ -546,10 +643,10 @@ TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataNoMatchWithDefaultValue) { testing::ContainerEq(descriptors_)); } -TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataNoMatch) { +TEST_F(RateLimitPolicyEntryTest, MetaDataNoMatch) { const std::string yaml = R"EOF( actions: -- dynamic_metadata: +- metadata: descriptor_key: fake_key metadata_key: key: 'envoy.xxx' @@ -576,10 +673,10 @@ TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataNoMatch) { EXPECT_TRUE(descriptors_.empty()); } -TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataEmptyValue) { +TEST_F(RateLimitPolicyEntryTest, MetaDataEmptyValue) { const std::string yaml = R"EOF( actions: -- dynamic_metadata: +- metadata: descriptor_key: fake_key metadata_key: key: 'envoy.xxx' @@ -606,10 +703,10 @@ TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataEmptyValue) { EXPECT_TRUE(descriptors_.empty()); } // Tests that no descriptor is generated when both the metadata_key and default_value are empty. -TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataAndDefaultValueEmpty) { +TEST_F(RateLimitPolicyEntryTest, MetaDataAndDefaultValueEmpty) { const std::string yaml = R"EOF( actions: -- dynamic_metadata: +- metadata: descriptor_key: fake_key default_value: "" metadata_key: @@ -637,10 +734,10 @@ TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataAndDefaultValueEmpty) { EXPECT_TRUE(descriptors_.empty()); } -TEST_F(RateLimitPolicyEntryTest, DynamicMetaDataNonStringMatch) { +TEST_F(RateLimitPolicyEntryTest, MetaDataNonStringNoMatch) { const std::string yaml = R"EOF( actions: -- dynamic_metadata: +- metadata: descriptor_key: fake_key metadata_key: key: 'envoy.xxx'