diff --git a/CODEOWNERS b/CODEOWNERS index 7d1e02896b5a8..7f684161d7041 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -160,3 +160,5 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp /*/extensions/filters/common/local_ratelimit @mattklein123 @rgs1 # HTTP Kill Request /*/extensions/filters/http/kill_request @qqustc @htuch +# Rate limit expression descriptor +/*/extensions/rate_limit_descriptors/expr @kyessenov @lizan diff --git a/REPO_LAYOUT.md b/REPO_LAYOUT.md index 3eae104c8c35c..fff64a494971d 100644 --- a/REPO_LAYOUT.md +++ b/REPO_LAYOUT.md @@ -111,6 +111,9 @@ code/extensions, and allows us specify extension owners in [CODEOWNERS](CODEOWNE `Envoy::Extensions::Upstreams` namespace. * [watchdog](/source/extensions/watchdog): Watchdog extensions use the `Envoy::Extensions::Watchdog` namespace. + * [descriptors](/source/extensions/rate_limit_descriptors): Rate limit + descriptor extensions use the `Envoy::Extensions::RateLimitDescriptors` + namespace. * Each extension is contained wholly in its own namespace. E.g., `Envoy::Extensions::NetworkFilters::Echo`. * Common code that is used by multiple extensions should be in a `common/` directory as close to diff --git a/api/BUILD b/api/BUILD index f7c75a2d7faea..6b8fc64edab11 100644 --- a/api/BUILD +++ b/api/BUILD @@ -239,6 +239,7 @@ proto_library( "//envoy/extensions/internal_redirect/previous_routes/v3:pkg", "//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg", "//envoy/extensions/network/socket_interface/v3:pkg", + "//envoy/extensions/rate_limit_descriptors/expr/v3:pkg", "//envoy/extensions/retry/host/omit_host_metadata/v3:pkg", "//envoy/extensions/retry/priority/previous_priorities/v3:pkg", "//envoy/extensions/stat_sinks/wasm/v3:pkg", diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index 2d85fd0dc7dbb..53b351b8d3aab 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -1544,7 +1544,7 @@ message VirtualCluster { message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit"; - // [#next-free-field: 9] + // [#next-free-field: 10] message Action { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit.Action"; @@ -1742,6 +1742,9 @@ message RateLimit { // Rate limit on metadata. MetaData metadata = 8; + + // Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + core.v3.TypedExtensionConfig extension = 9; } } diff --git a/api/envoy/config/route/v4alpha/route_components.proto b/api/envoy/config/route/v4alpha/route_components.proto index 166b196719170..577282595d84a 100644 --- a/api/envoy/config/route/v4alpha/route_components.proto +++ b/api/envoy/config/route/v4alpha/route_components.proto @@ -1493,7 +1493,7 @@ message VirtualCluster { message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit"; - // [#next-free-field: 9] + // [#next-free-field: 10] message Action { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit.Action"; @@ -1694,6 +1694,9 @@ message RateLimit { // Rate limit on metadata. MetaData metadata = 8; + + // Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + core.v4alpha.TypedExtensionConfig extension = 9; } } diff --git a/api/envoy/extensions/rate_limit_descriptors/expr/v3/BUILD b/api/envoy/extensions/rate_limit_descriptors/expr/v3/BUILD new file mode 100644 index 0000000000000..facd82ce6de26 --- /dev/null +++ b/api/envoy/extensions/rate_limit_descriptors/expr/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "@com_github_cncf_udpa//udpa/annotations:pkg", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_proto", + ], +) diff --git a/api/envoy/extensions/rate_limit_descriptors/expr/v3/expr.proto b/api/envoy/extensions/rate_limit_descriptors/expr/v3/expr.proto new file mode 100644 index 0000000000000..76d3505cba04a --- /dev/null +++ b/api/envoy/extensions/rate_limit_descriptors/expr/v3/expr.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package envoy.extensions.rate_limit_descriptors.expr.v3; + +import "google/api/expr/v1alpha1/syntax.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.rate_limit_descriptors.expr.v3"; +option java_outer_classname = "ExprProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Rate limit descriptor expression] +// [#extension: envoy.rate_limit_descriptors.expr] + +// The following descriptor entry is appended with a value computed +// from a symbolic Common Expression Language expression. +// See :ref:`attributes ` for the set of +// available attributes. +// +// .. code-block:: cpp +// +// ("", "") +message Descriptor { + // The key to use in the descriptor entry. + string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; + + // If set to true, Envoy skips the descriptor if the expression evaluates to an error. + // By default, the rate limit is not applied when an expression produces an error. + bool skip_if_error = 2; + + oneof expr_specifier { + // Expression in a text form, e.g. "connection.requested_server_name". + string text = 3 [(validate.rules).string = {min_len: 1}]; + + // Parsed expression in AST form. + google.api.expr.v1alpha1.Expr parsed = 4; + } +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 41addf767671d..6f67627221480 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -122,6 +122,7 @@ proto_library( "//envoy/extensions/internal_redirect/previous_routes/v3:pkg", "//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg", "//envoy/extensions/network/socket_interface/v3:pkg", + "//envoy/extensions/rate_limit_descriptors/expr/v3:pkg", "//envoy/extensions/retry/host/omit_host_metadata/v3:pkg", "//envoy/extensions/retry/priority/previous_priorities/v3:pkg", "//envoy/extensions/stat_sinks/wasm/v3:pkg", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 95aa885b088aa..40b7d6b4945e3 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -713,6 +713,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( extensions = [ "envoy.access_loggers.wasm", "envoy.bootstrap.wasm", + "envoy.rate_limit_descriptors.expr", "envoy.filters.http.rbac", "envoy.filters.http.wasm", "envoy.filters.network.rbac", @@ -734,6 +735,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( extensions = [ "envoy.access_loggers.wasm", "envoy.bootstrap.wasm", + "envoy.rate_limit_descriptors.expr", "envoy.filters.http.rbac", "envoy.filters.http.wasm", "envoy.filters.network.rbac", @@ -912,6 +914,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( extensions = [ "envoy.access_loggers.wasm", "envoy.bootstrap.wasm", + "envoy.rate_limit_descriptors.expr", "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", @@ -931,6 +934,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( extensions = [ "envoy.access_loggers.wasm", "envoy.bootstrap.wasm", + "envoy.rate_limit_descriptors.expr", "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", diff --git a/docs/root/api-v3/config/config.rst b/docs/root/api-v3/config/config.rst index db70d213b89f3..c1240c53af724 100644 --- a/docs/root/api-v3/config/config.rst +++ b/docs/root/api-v3/config/config.rst @@ -24,3 +24,4 @@ Extensions upstream/upstream wasm/wasm watchdog/watchdog + descriptors/descriptors diff --git a/docs/root/api-v3/config/descriptors/descriptors.rst b/docs/root/api-v3/config/descriptors/descriptors.rst new file mode 100644 index 0000000000000..69ac0d2378329 --- /dev/null +++ b/docs/root/api-v3/config/descriptors/descriptors.rst @@ -0,0 +1,8 @@ +Rate limit descriptors +====================== + +.. toctree:: + :glob: + :maxdepth: 2 + + ../../extensions/rate_limit_descriptors/expr/v3/* diff --git a/docs/root/configuration/http/http_filters/rate_limit_filter.rst b/docs/root/configuration/http/http_filters/rate_limit_filter.rst index b1d6d6b11c5ac..b1f27c63c1ec2 100644 --- a/docs/root/configuration/http/http_filters/rate_limit_filter.rst +++ b/docs/root/configuration/http/http_filters/rate_limit_filter.rst @@ -121,6 +121,23 @@ the rate limit override of 42 requests per hour will be appended to the rate lim requests_per_unit: 42 unit: HOUR +Descriptor extensions +--------------------- + +Rate limit descriptors are extensible with custom descriptors. For example, :ref:`computed descriptors +` extension allows using any of the +:ref:`request attributes ` as a descriptor value: + +.. code-block:: yaml + + actions: + - extension: + name: custom + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: my_descriptor_name + text: request.method + Statistics ---------- diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index c8bd8d2ef2668..04f788bb5a1ad 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -94,6 +94,8 @@ New Features * 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. * ratelimit: added :ref:`body ` field to support custom response bodies for non-OK responses from the external ratelimit service. +* ratelimit: added :ref:`descriptor extensions `. +* ratelimit: added :ref:`computed descriptors `. * ratelimit: added :ref:`dynamic_metadata ` field to support setting dynamic metadata from the ratelimit service. * router: added support for regex rewrites during HTTP redirects using :ref:`regex_rewrite `. * sds: improved support for atomic :ref:`key rotations ` and added configurable rotation triggers for 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 e90d3bce7e053..bfb296e478368 100644 --- a/generated_api_shadow/envoy/config/route/v3/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v3/route_components.proto @@ -1556,7 +1556,7 @@ message VirtualCluster { message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit"; - // [#next-free-field: 9] + // [#next-free-field: 10] message Action { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit.Action"; @@ -1754,6 +1754,9 @@ message RateLimit { // Rate limit on metadata. MetaData metadata = 8; + + // Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + core.v3.TypedExtensionConfig extension = 9; } } 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 935610424b1a1..586527865d5be 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto @@ -1560,7 +1560,7 @@ message VirtualCluster { message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit"; - // [#next-free-field: 9] + // [#next-free-field: 10] message Action { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RateLimit.Action"; @@ -1764,6 +1764,9 @@ message RateLimit { // Rate limit on metadata. MetaData metadata = 8; + + // Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + core.v4alpha.TypedExtensionConfig extension = 9; } } diff --git a/generated_api_shadow/envoy/extensions/rate_limit_descriptors/expr/v3/BUILD b/generated_api_shadow/envoy/extensions/rate_limit_descriptors/expr/v3/BUILD new file mode 100644 index 0000000000000..facd82ce6de26 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/rate_limit_descriptors/expr/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "@com_github_cncf_udpa//udpa/annotations:pkg", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_proto", + ], +) diff --git a/generated_api_shadow/envoy/extensions/rate_limit_descriptors/expr/v3/expr.proto b/generated_api_shadow/envoy/extensions/rate_limit_descriptors/expr/v3/expr.proto new file mode 100644 index 0000000000000..76d3505cba04a --- /dev/null +++ b/generated_api_shadow/envoy/extensions/rate_limit_descriptors/expr/v3/expr.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package envoy.extensions.rate_limit_descriptors.expr.v3; + +import "google/api/expr/v1alpha1/syntax.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.rate_limit_descriptors.expr.v3"; +option java_outer_classname = "ExprProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Rate limit descriptor expression] +// [#extension: envoy.rate_limit_descriptors.expr] + +// The following descriptor entry is appended with a value computed +// from a symbolic Common Expression Language expression. +// See :ref:`attributes ` for the set of +// available attributes. +// +// .. code-block:: cpp +// +// ("", "") +message Descriptor { + // The key to use in the descriptor entry. + string descriptor_key = 1 [(validate.rules).string = {min_len: 1}]; + + // If set to true, Envoy skips the descriptor if the expression evaluates to an error. + // By default, the rate limit is not applied when an expression produces an error. + bool skip_if_error = 2; + + oneof expr_specifier { + // Expression in a text form, e.g. "connection.requested_server_name". + string text = 3 [(validate.rules).string = {min_len: 1}]; + + // Parsed expression in AST form. + google.api.expr.v1alpha1.Expr parsed = 4; + } +} diff --git a/include/envoy/ratelimit/BUILD b/include/envoy/ratelimit/BUILD index 615b69fa31073..96c325023043c 100644 --- a/include/envoy/ratelimit/BUILD +++ b/include/envoy/ratelimit/BUILD @@ -12,6 +12,10 @@ envoy_cc_library( name = "ratelimit_interface", hdrs = ["ratelimit.h"], deps = [ + "//include/envoy/config:typed_config_interface", + "//include/envoy/http:header_map_interface", + "//include/envoy/protobuf:message_validator_interface", + "//include/envoy/stream_info:stream_info_interface", "@envoy_api//envoy/type/v3:pkg_cc_proto", ], ) diff --git a/include/envoy/ratelimit/ratelimit.h b/include/envoy/ratelimit/ratelimit.h index f23c8170ef684..e106414003100 100644 --- a/include/envoy/ratelimit/ratelimit.h +++ b/include/envoy/ratelimit/ratelimit.h @@ -3,6 +3,10 @@ #include #include +#include "envoy/config/typed_config.h" +#include "envoy/http/header_map.h" +#include "envoy/protobuf/message_validator.h" +#include "envoy/stream_info/stream_info.h" #include "envoy/type/v3/ratelimit_unit.pb.h" #include "absl/types/optional.h" @@ -34,5 +38,50 @@ struct Descriptor { absl::optional limit_ = absl::nullopt; }; +/** + * Base interface for generic rate limit descriptor producer. + */ +class DescriptorProducer { +public: + virtual ~DescriptorProducer() = default; + + /** + * Potentially append a descriptor entry to the end of descriptor. + * @param descriptor supplies the descriptor to optionally fill. + * @param local_service_cluster supplies the name of the local service cluster. + * @param headers supplies the header for the request. + * @param info stream info associated with the request + * @return true if the producer populated the descriptor. + */ + virtual bool populateDescriptor(Descriptor& descriptor, const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const PURE; +}; + +using DescriptorProducerPtr = std::unique_ptr; + +/** + * Implemented by each custom rate limit descriptor extension and registered via + * Registry::registerFactory() or the convenience class RegisterFactory. + */ +class DescriptorProducerFactory : public Config::TypedFactory { +public: + ~DescriptorProducerFactory() override = default; + + /** + * Creates a particular DescriptorProducer implementation. + * + * @param config supplies the configuration for the descriptor extension. + * @param validator configuration validation visitor. + * @return DescriptorProducerPtr the rate limit descriptor producer which will be used to populate + * rate limit descriptors. + */ + virtual DescriptorProducerPtr + createDescriptorProducerFromProto(const Protobuf::Message& config, + ProtobufMessage::ValidationVisitor& validator) PURE; + + std::string category() const override { return "envoy.rate_limit_descriptors"; } +}; + } // namespace RateLimit } // namespace Envoy diff --git a/include/envoy/router/BUILD b/include/envoy/router/BUILD index ed2a65c67c8a5..754412a7ee346 100644 --- a/include/envoy/router/BUILD +++ b/include/envoy/router/BUILD @@ -97,7 +97,6 @@ envoy_cc_library( hdrs = ["router_ratelimit.h"], deps = [ "//include/envoy/http:filter_interface", - "//include/envoy/http:header_map_interface", "//include/envoy/ratelimit:ratelimit_interface", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], diff --git a/include/envoy/router/router_ratelimit.h b/include/envoy/router/router_ratelimit.h index 1e6910c3b9ba2..8a4ef25f6d6d4 100644 --- a/include/envoy/router/router_ratelimit.h +++ b/include/envoy/router/router_ratelimit.h @@ -7,7 +7,6 @@ #include "envoy/config/core/v3/base.pb.h" #include "envoy/http/filter.h" -#include "envoy/http/header_map.h" #include "envoy/ratelimit/ratelimit.h" namespace Envoy { @@ -32,32 +31,6 @@ class RateLimitOverrideAction { using RateLimitOverrideActionPtr = std::unique_ptr; -/** - * Base interface for generic rate limit action. - */ -class RateLimitAction { -public: - virtual ~RateLimitAction() = default; - - /** - * Potentially append a descriptor entry to the end of descriptor. - * @param route supplies the target route for the request. - * @param descriptor supplies the descriptor to optionally fill. - * @param local_service_cluster supplies the name of the local service cluster. - * @param headers supplies the header for the request. - * @param remote_address supplies the trusted downstream address for the connection. - * @param dynamic_metadata supplies the dynamic metadata for the request - * @return true if the RateLimitAction populated the descriptor. - */ - virtual bool - populateDescriptor(const RouteEntry& route, RateLimit::Descriptor& descriptor, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const PURE; -}; - -using RateLimitActionPtr = std::unique_ptr; - /** * Rate limit configuration. */ @@ -77,18 +50,15 @@ class RateLimitPolicyEntry { /** * Potentially populate the descriptor array with new descriptors to query. - * @param route supplies the target route for the request. * @param descriptors supplies the descriptor array to optionally fill. * @param local_service_cluster supplies the name of the local service cluster. * @param headers supplies the header for the request. - * @param remote_address supplies the trusted downstream address for the connection. - * @param dynamic_metadata supplies the dynamic metadata for the request. + * @param info stream info associated with the request */ - virtual void - populateDescriptors(const RouteEntry& route, std::vector& descriptors, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const PURE; + virtual void populateDescriptors(std::vector& descriptors, + const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const PURE; }; /** diff --git a/source/common/router/BUILD b/source/common/router/BUILD index 0caa0c8641376..f4873e98aa27b 100644 --- a/source/common/router/BUILD +++ b/source/common/router/BUILD @@ -335,11 +335,13 @@ envoy_cc_library( hdrs = ["router_ratelimit.h"], deps = [ ":config_utility_lib", + "//include/envoy/ratelimit:ratelimit_interface", "//include/envoy/router:router_interface", "//include/envoy/router:router_ratelimit_interface", "//source/common/common:assert_lib", "//source/common/common:empty_string", "//source/common/config:metadata_lib", + "//source/common/config:utility_lib", "//source/common/http:header_utility_lib", "//source/common/protobuf:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index a62ea02ca9e18..469d8530a6d95 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -341,7 +341,7 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, retry_policy_(buildRetryPolicy(vhost.retryPolicy(), route.route(), validator)), internal_redirect_policy_( buildInternalRedirectPolicy(route.route(), validator, route.name())), - rate_limit_policy_(route.route().rate_limits()), + rate_limit_policy_(route.route().rate_limits(), validator), priority_(ConfigUtility::parsePriority(route.route().priority())), config_headers_(Http::HeaderUtility::buildHeaderDataVector(route.match().headers())), total_cluster_weight_( @@ -1119,7 +1119,8 @@ VirtualHostImpl::VirtualHostImpl( vcluster_scope_(Stats::Utility::scopeFromStatNames( scope, {stat_name_storage_.statName(), factory_context.routerContext().virtualClusterStatNames().vcluster_})), - rate_limit_policy_(virtual_host.rate_limits()), global_route_config_(global_route_config), + rate_limit_policy_(virtual_host.rate_limits(), validator), + global_route_config_(global_route_config), request_headers_parser_(HeaderParser::configure(virtual_host.request_headers_to_add(), virtual_host.request_headers_to_remove())), response_headers_parser_(HeaderParser::configure(virtual_host.response_headers_to_add(), diff --git a/source/common/router/router_ratelimit.cc b/source/common/router/router_ratelimit.cc index 0774f8340be5f..cfe882a60f42c 100644 --- a/source/common/router/router_ratelimit.cc +++ b/source/common/router/router_ratelimit.cc @@ -11,6 +11,7 @@ #include "common/common/assert.h" #include "common/common/empty_string.h" #include "common/config/metadata.h" +#include "common/config/utility.h" #include "common/protobuf/utility.h" namespace Envoy { @@ -43,30 +44,24 @@ bool DynamicMetadataRateLimitOverride::populateOverride( return false; } -bool SourceClusterAction::populateDescriptor(const Router::RouteEntry&, - RateLimit::Descriptor& descriptor, +bool SourceClusterAction::populateDescriptor(RateLimit::Descriptor& descriptor, const std::string& local_service_cluster, - const Http::HeaderMap&, - const Network::Address::Instance&, - const envoy::config::core::v3::Metadata*) const { + const Http::RequestHeaderMap&, + const StreamInfo::StreamInfo&) const { descriptor.entries_.push_back({"source_cluster", local_service_cluster}); return true; } -bool DestinationClusterAction::populateDescriptor(const Router::RouteEntry& route, - RateLimit::Descriptor& descriptor, - const std::string&, const Http::HeaderMap&, - const Network::Address::Instance&, - const envoy::config::core::v3::Metadata*) const { - descriptor.entries_.push_back({"destination_cluster", route.clusterName()}); +bool DestinationClusterAction::populateDescriptor(RateLimit::Descriptor& descriptor, + const std::string&, const Http::RequestHeaderMap&, + const StreamInfo::StreamInfo& info) const { + descriptor.entries_.push_back({"destination_cluster", info.routeEntry()->clusterName()}); return true; } -bool RequestHeadersAction::populateDescriptor(const Router::RouteEntry&, - RateLimit::Descriptor& descriptor, const std::string&, - const Http::HeaderMap& headers, - const Network::Address::Instance&, - const envoy::config::core::v3::Metadata*) const { +bool RequestHeadersAction::populateDescriptor(RateLimit::Descriptor& descriptor, const std::string&, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo&) const { const auto header_value = headers.get(header_name_); // If header is not present in the request and if skip_if_absent is true skip this descriptor, @@ -81,23 +76,22 @@ bool RequestHeadersAction::populateDescriptor(const Router::RouteEntry&, return true; } -bool RemoteAddressAction::populateDescriptor(const Router::RouteEntry&, - RateLimit::Descriptor& descriptor, const std::string&, - const Http::HeaderMap&, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata*) const { - if (remote_address.type() != Network::Address::Type::Ip) { +bool RemoteAddressAction::populateDescriptor(RateLimit::Descriptor& descriptor, const std::string&, + const Http::RequestHeaderMap&, + const StreamInfo::StreamInfo& info) const { + const Network::Address::InstanceConstSharedPtr& remote_address = + info.downstreamAddressProvider().remoteAddress(); + if (remote_address->type() != Network::Address::Type::Ip) { return false; } - descriptor.entries_.push_back({"remote_address", remote_address.ip()->addressAsString()}); + descriptor.entries_.push_back({"remote_address", remote_address->ip()->addressAsString()}); return true; } -bool GenericKeyAction::populateDescriptor(const Router::RouteEntry&, - RateLimit::Descriptor& descriptor, const std::string&, - const Http::HeaderMap&, const Network::Address::Instance&, - const envoy::config::core::v3::Metadata*) const { +bool GenericKeyAction::populateDescriptor(RateLimit::Descriptor& descriptor, const std::string&, + const Http::RequestHeaderMap&, + const StreamInfo::StreamInfo&) const { descriptor.entries_.push_back({descriptor_key_, descriptor_value_}); return true; } @@ -112,18 +106,17 @@ MetaDataAction::MetaDataAction( default_value_(action.default_value()), source_(envoy::config::route::v3::RateLimit::Action::MetaData::DYNAMIC) {} -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 { +bool MetaDataAction::populateDescriptor(RateLimit::Descriptor& descriptor, const std::string&, + const Http::RequestHeaderMap&, + const StreamInfo::StreamInfo& info) const { const envoy::config::core::v3::Metadata* metadata_source; switch (source_) { case envoy::config::route::v3::RateLimit::Action::MetaData::DYNAMIC: - metadata_source = dynamic_metadata; + metadata_source = &info.dynamicMetadata(); break; case envoy::config::route::v3::RateLimit::Action::MetaData::ROUTE_ENTRY: - metadata_source = &route.metadata(); + metadata_source = &info.routeEntry()->metadata(); break; default: NOT_REACHED_GCOVR_EXCL_LINE; @@ -149,11 +142,10 @@ HeaderValueMatchAction::HeaderValueMatchAction( expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)), action_headers_(Http::HeaderUtility::buildHeaderDataVector(action.headers())) {} -bool HeaderValueMatchAction::populateDescriptor(const Router::RouteEntry&, - RateLimit::Descriptor& descriptor, - const std::string&, const Http::HeaderMap& headers, - const Network::Address::Instance&, - const envoy::config::core::v3::Metadata*) const { +bool HeaderValueMatchAction::populateDescriptor(RateLimit::Descriptor& descriptor, + const std::string&, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo&) const { if (expect_match_ == Http::HeaderUtility::matchHeaders(headers, action_headers_)) { descriptor.entries_.push_back({"header_match", descriptor_value_}); return true; @@ -163,7 +155,8 @@ bool HeaderValueMatchAction::populateDescriptor(const Router::RouteEntry&, } RateLimitPolicyEntryImpl::RateLimitPolicyEntryImpl( - const envoy::config::route::v3::RateLimit& config) + const envoy::config::route::v3::RateLimit& config, + ProtobufMessage::ValidationVisitor& validator) : disable_key_(config.disable_key()), stage_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, stage, 0))) { for (const auto& action : config.actions()) { @@ -192,6 +185,25 @@ RateLimitPolicyEntryImpl::RateLimitPolicyEntryImpl( case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kHeaderValueMatch: actions_.emplace_back(new HeaderValueMatchAction(action.header_value_match())); break; + case envoy::config::route::v3::RateLimit::Action::ActionSpecifierCase::kExtension: { + auto* factory = Envoy::Config::Utility::getFactory( + action.extension()); + if (!factory) { + throw EnvoyException( + absl::StrCat("Rate limit descriptor extension not found: ", action.extension().name())); + } + auto message = Envoy::Config::Utility::translateAnyToFactoryConfig( + action.extension().typed_config(), validator, *factory); + RateLimit::DescriptorProducerPtr producer = + factory->createDescriptorProducerFromProto(*message, validator); + if (producer) { + actions_.emplace_back(std::move(producer)); + } else { + throw EnvoyException( + absl::StrCat("Rate limit descriptor extension failed: ", action.extension().name())); + } + break; + } default: NOT_REACHED_GCOVR_EXCL_LINE; } @@ -208,23 +220,21 @@ RateLimitPolicyEntryImpl::RateLimitPolicyEntryImpl( } } -void RateLimitPolicyEntryImpl::populateDescriptors( - const Router::RouteEntry& route, std::vector& descriptors, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const { +void RateLimitPolicyEntryImpl::populateDescriptors(std::vector& descriptors, + const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const { RateLimit::Descriptor descriptor; bool result = true; - for (const RateLimitActionPtr& action : actions_) { - result = result && action->populateDescriptor(route, descriptor, local_service_cluster, headers, - remote_address, dynamic_metadata); + for (const RateLimit::DescriptorProducerPtr& action : actions_) { + result = result && action->populateDescriptor(descriptor, local_service_cluster, headers, info); if (!result) { break; } } if (limit_override_) { - limit_override_.value()->populateOverride(descriptor, dynamic_metadata); + limit_override_.value()->populateOverride(descriptor, &info.dynamicMetadata()); } if (result) { @@ -233,11 +243,12 @@ void RateLimitPolicyEntryImpl::populateDescriptors( } RateLimitPolicyImpl::RateLimitPolicyImpl( - const Protobuf::RepeatedPtrField& rate_limits) + const Protobuf::RepeatedPtrField& rate_limits, + ProtobufMessage::ValidationVisitor& validator) : rate_limit_entries_reference_(RateLimitPolicyImpl::MAX_STAGE_NUMBER + 1) { for (const auto& rate_limit : rate_limits) { std::unique_ptr rate_limit_policy_entry( - new RateLimitPolicyEntryImpl(rate_limit)); + new RateLimitPolicyEntryImpl(rate_limit, validator)); uint64_t stage = rate_limit_policy_entry->stage(); ASSERT(stage < rate_limit_entries_reference_.size()); rate_limit_entries_reference_[stage].emplace_back(*rate_limit_policy_entry); diff --git a/source/common/router/router_ratelimit.h b/source/common/router/router_ratelimit.h index 912606fc0da85..7aa0d6ca0401f 100644 --- a/source/common/router/router_ratelimit.h +++ b/source/common/router/router_ratelimit.h @@ -7,6 +7,7 @@ #include "envoy/config/core/v3/base.pb.h" #include "envoy/config/route/v3/route_components.pb.h" +#include "envoy/ratelimit/ratelimit.h" #include "envoy/router/router.h" #include "envoy/router/router_ratelimit.h" @@ -38,41 +39,41 @@ class DynamicMetadataRateLimitOverride : public RateLimitOverrideAction { /** * Action for source cluster rate limiting. */ -class SourceClusterAction : public RateLimitAction { +class SourceClusterAction : public RateLimit::DescriptorProducer { public: - // Router::RateLimitAction - bool populateDescriptor(const Router::RouteEntry& route, RateLimit::Descriptor& descriptor, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const override; + // Ratelimit::DescriptorProducer + bool populateDescriptor(RateLimit::Descriptor& descriptor, + const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const override; }; /** * Action for destination cluster rate limiting. */ -class DestinationClusterAction : public RateLimitAction { +class DestinationClusterAction : public RateLimit::DescriptorProducer { public: - // Router::RateLimitAction - bool populateDescriptor(const Router::RouteEntry& route, RateLimit::Descriptor& descriptor, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const override; + // Ratelimit::DescriptorProducer + bool populateDescriptor(RateLimit::Descriptor& descriptor, + const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const override; }; /** * Action for request headers rate limiting. */ -class RequestHeadersAction : public RateLimitAction { +class RequestHeadersAction : public RateLimit::DescriptorProducer { public: RequestHeadersAction(const envoy::config::route::v3::RateLimit::Action::RequestHeaders& action) : header_name_(action.header_name()), descriptor_key_(action.descriptor_key()), skip_if_absent_(action.skip_if_absent()) {} - // Router::RateLimitAction - bool populateDescriptor(const Router::RouteEntry& route, RateLimit::Descriptor& descriptor, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const override; + // Ratelimit::DescriptorProducer + bool populateDescriptor(RateLimit::Descriptor& descriptor, + const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const override; private: const Http::LowerCaseString header_name_; @@ -83,30 +84,30 @@ class RequestHeadersAction : public RateLimitAction { /** * Action for remote address rate limiting. */ -class RemoteAddressAction : public RateLimitAction { +class RemoteAddressAction : public RateLimit::DescriptorProducer { public: - // Router::RateLimitAction - bool populateDescriptor(const Router::RouteEntry& route, RateLimit::Descriptor& descriptor, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const override; + // Ratelimit::DescriptorProducer + bool populateDescriptor(RateLimit::Descriptor& descriptor, + const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const override; }; /** * Action for generic key rate limiting. */ -class GenericKeyAction : public RateLimitAction { +class GenericKeyAction : public RateLimit::DescriptorProducer { public: GenericKeyAction(const envoy::config::route::v3::RateLimit::Action::GenericKey& action) : descriptor_value_(action.descriptor_value()), descriptor_key_(!action.descriptor_key().empty() ? action.descriptor_key() : "generic_key") {} - // Router::RateLimitAction - bool populateDescriptor(const Router::RouteEntry& route, RateLimit::Descriptor& descriptor, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const override; + // Ratelimit::DescriptorProducer + bool populateDescriptor(RateLimit::Descriptor& descriptor, + const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const override; private: const std::string descriptor_value_; @@ -116,16 +117,16 @@ class GenericKeyAction : public RateLimitAction { /** * Action for metadata rate limiting. */ -class MetaDataAction : public RateLimitAction { +class MetaDataAction : public RateLimit::DescriptorProducer { public: 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, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const override; + // Ratelimit::DescriptorProducer + bool populateDescriptor(RateLimit::Descriptor& descriptor, + const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const override; private: const Envoy::Config::MetadataKey metadata_key_; @@ -137,16 +138,16 @@ class MetaDataAction : public RateLimitAction { /** * Action for header value match rate limiting. */ -class HeaderValueMatchAction : public RateLimitAction { +class HeaderValueMatchAction : public RateLimit::DescriptorProducer { public: HeaderValueMatchAction( const envoy::config::route::v3::RateLimit::Action::HeaderValueMatch& action); - // Router::RateLimitAction - bool populateDescriptor(const Router::RouteEntry& route, RateLimit::Descriptor& descriptor, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const override; + // Ratelimit::DescriptorProducer + bool populateDescriptor(RateLimit::Descriptor& descriptor, + const std::string& local_service_cluster, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const override; private: const std::string descriptor_value_; @@ -159,22 +160,20 @@ class HeaderValueMatchAction : public RateLimitAction { */ class RateLimitPolicyEntryImpl : public RateLimitPolicyEntry { public: - RateLimitPolicyEntryImpl(const envoy::config::route::v3::RateLimit& config); + RateLimitPolicyEntryImpl(const envoy::config::route::v3::RateLimit& config, + ProtobufMessage::ValidationVisitor& validator); // Router::RateLimitPolicyEntry uint64_t stage() const override { return stage_; } const std::string& disableKey() const override { return disable_key_; } - void - populateDescriptors(const Router::RouteEntry& route, - std::vector& descriptors, - const std::string& local_service_cluster, const Http::HeaderMap&, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata) const override; + void populateDescriptors(std::vector& descriptors, + const std::string& local_service_cluster, const Http::RequestHeaderMap&, + const StreamInfo::StreamInfo& info) const override; private: const std::string disable_key_; uint64_t stage_; - std::vector actions_; + std::vector actions_; absl::optional limit_override_ = absl::nullopt; }; @@ -184,7 +183,8 @@ class RateLimitPolicyEntryImpl : public RateLimitPolicyEntry { class RateLimitPolicyImpl : public RateLimitPolicy { public: RateLimitPolicyImpl( - const Protobuf::RepeatedPtrField& rate_limits); + const Protobuf::RepeatedPtrField& rate_limits, + ProtobufMessage::ValidationVisitor& validator); // Router::RateLimitPolicy const std::vector>& diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 52f127950c640..1fa9f6293ea79 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -229,6 +229,12 @@ EXTENSIONS = { "envoy.wasm.runtime.v8": "//source/extensions/wasm_runtime/v8:config", "envoy.wasm.runtime.wavm": "//source/extensions/wasm_runtime/wavm:config", "envoy.wasm.runtime.wasmtime": "//source/extensions/wasm_runtime/wasmtime:config", + + # + # Rate limit descriptors + # + + "envoy.rate_limit_descriptors.expr": "//source/extensions/rate_limit_descriptors/expr:config", } # These can be changed to ["//visibility:public"], for downstream builds which diff --git a/source/extensions/filters/common/expr/evaluator.cc b/source/extensions/filters/common/expr/evaluator.cc index d2c376684f72a..4a7ed3c9fc4cd 100644 --- a/source/extensions/filters/common/expr/evaluator.cc +++ b/source/extensions/filters/common/expr/evaluator.cc @@ -94,6 +94,31 @@ bool matches(const Expression& expr, const StreamInfo::StreamInfo& info, return result.IsBool() ? result.BoolOrDie() : false; } +std::string print(CelValue value) { + switch (value.type()) { + case CelValue::Type::kBool: + return value.BoolOrDie() ? "true" : "false"; + case CelValue::Type::kInt64: + return absl::StrCat(value.Int64OrDie()); + case CelValue::Type::kUint64: + return absl::StrCat(value.Uint64OrDie()); + case CelValue::Type::kDouble: + return absl::StrCat(value.DoubleOrDie()); + case CelValue::Type::kString: + return std::string(value.StringOrDie().value()); + case CelValue::Type::kBytes: + return std::string(value.BytesOrDie().value()); + case CelValue::Type::kMessage: + return value.IsNull() ? "NULL" : value.MessageOrDie()->ShortDebugString(); + case CelValue::Type::kDuration: + return absl::FormatDuration(value.DurationOrDie()); + case CelValue::Type::kTimestamp: + return absl::FormatTime(value.TimestampOrDie(), absl::UTCTimeZone()); + default: + return absl::StrCat(CelValue::TypeName(value.type()), " value"); + } +} + } // namespace Expr } // namespace Common } // namespace Filters diff --git a/source/extensions/filters/common/expr/evaluator.h b/source/extensions/filters/common/expr/evaluator.h index 37fdf63ab1bfe..32ee98572f354 100644 --- a/source/extensions/filters/common/expr/evaluator.h +++ b/source/extensions/filters/common/expr/evaluator.h @@ -52,6 +52,9 @@ absl::optional evaluate(const Expression& expr, Protobuf::Arena& arena bool matches(const Expression& expr, const StreamInfo::StreamInfo& info, const Http::RequestHeaderMap& headers); +// Returns a string for a CelValue. +std::string print(CelValue value); + // Thrown when there is an CEL library error. class CelException : public EnvoyException { public: diff --git a/source/extensions/filters/http/ratelimit/ratelimit.cc b/source/extensions/filters/http/ratelimit/ratelimit.cc index 2d8b6ab6b37af..07c11b088e2d0 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit.cc +++ b/source/extensions/filters/http/ratelimit/ratelimit.cc @@ -49,7 +49,7 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { const Router::RouteEntry* route_entry = route->routeEntry(); // Get all applicable rate limit policy entries for the route. - populateRateLimitDescriptors(route_entry->rateLimitPolicy(), descriptors, route_entry, headers); + populateRateLimitDescriptors(route_entry->rateLimitPolicy(), descriptors, headers); VhRateLimitOptions vh_rate_limit_option = getVirtualHostRateLimitOption(route); @@ -58,12 +58,12 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { break; case VhRateLimitOptions::Include: populateRateLimitDescriptors(route_entry->virtualHost().rateLimitPolicy(), descriptors, - route_entry, headers); + headers); break; case VhRateLimitOptions::Override: if (route_entry->rateLimitPolicy().empty()) { populateRateLimitDescriptors(route_entry->virtualHost().rateLimitPolicy(), descriptors, - route_entry, headers); + headers); } break; default: @@ -229,8 +229,7 @@ void Filter::complete(Filters::Common::RateLimit::LimitStatus status, void Filter::populateRateLimitDescriptors(const Router::RateLimitPolicy& rate_limit_policy, std::vector& descriptors, - const Router::RouteEntry* route_entry, - const Http::HeaderMap& headers) const { + const Http::RequestHeaderMap& headers) const { for (const Router::RateLimitPolicyEntry& rate_limit : rate_limit_policy.getApplicableRateLimit(config_->stage())) { const std::string& disable_key = rate_limit.disableKey(); @@ -239,10 +238,8 @@ void Filter::populateRateLimitDescriptors(const Router::RateLimitPolicy& rate_li fmt::format("ratelimit.{}.http_filter_enabled", disable_key), 100)) { continue; } - rate_limit.populateDescriptors( - *route_entry, descriptors, config_->localInfo().clusterName(), headers, - *callbacks_->streamInfo().downstreamAddressProvider().remoteAddress(), - &callbacks_->streamInfo().dynamicMetadata()); + rate_limit.populateDescriptors(descriptors, config_->localInfo().clusterName(), headers, + callbacks_->streamInfo()); } } diff --git a/source/extensions/filters/http/ratelimit/ratelimit.h b/source/extensions/filters/http/ratelimit/ratelimit.h index e555f566bec14..fc0341e050e39 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit.h +++ b/source/extensions/filters/http/ratelimit/ratelimit.h @@ -156,8 +156,7 @@ class Filter : public Http::StreamFilter, public Filters::Common::RateLimit::Req void initiateCall(const Http::RequestHeaderMap& headers); void populateRateLimitDescriptors(const Router::RateLimitPolicy& rate_limit_policy, std::vector& descriptors, - const Router::RouteEntry* route_entry, - const Http::HeaderMap& headers) const; + const Http::RequestHeaderMap& headers) const; void populateResponseHeaders(Http::HeaderMap& response_headers, bool from_local_reply); void appendRequestHeaders(Http::HeaderMapPtr& request_headers_to_add); VhRateLimitOptions getVirtualHostRateLimitOption(const Router::RouteConstSharedPtr& route); diff --git a/source/extensions/rate_limit_descriptors/expr/BUILD b/source/extensions/rate_limit_descriptors/expr/BUILD new file mode 100644 index 0000000000000..720c3bf5293bf --- /dev/null +++ b/source/extensions/rate_limit_descriptors/expr/BUILD @@ -0,0 +1,36 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + copts = select({ + "//bazel:windows_x86_64": [], # TODO: fix the windows ANTLR build + "//conditions:default": [ + "-DUSE_CEL_PARSER", + ], + }), + security_posture = "unknown", + deps = [ + "//include/envoy/ratelimit:ratelimit_interface", + "//include/envoy/registry", + "//source/common/protobuf:utility_lib", + "//source/extensions/filters/common/expr:evaluator_lib", + "@envoy_api//envoy/extensions/rate_limit_descriptors/expr/v3:pkg_cc_proto", + ] + select( + { + "//bazel:windows_x86_64": [], + "//conditions:default": [ + "@com_google_cel_cpp//parser", + ], + }, + ), +) diff --git a/source/extensions/rate_limit_descriptors/expr/config.cc b/source/extensions/rate_limit_descriptors/expr/config.cc new file mode 100644 index 0000000000000..aeebb5f96e238 --- /dev/null +++ b/source/extensions/rate_limit_descriptors/expr/config.cc @@ -0,0 +1,99 @@ +#include "extensions/rate_limit_descriptors/expr/config.h" + +#include "envoy/extensions/rate_limit_descriptors/expr/v3/expr.pb.h" +#include "envoy/extensions/rate_limit_descriptors/expr/v3/expr.pb.validate.h" + +#include "common/protobuf/utility.h" + +#if defined(USE_CEL_PARSER) +#include "parser/parser.h" +#endif + +namespace Envoy { +namespace Extensions { +namespace RateLimitDescriptors { +namespace Expr { + +namespace { + +/** + * Descriptor producer for a symbolic expression descriptor. + */ +class ExpressionDescriptor : public RateLimit::DescriptorProducer { +public: + ExpressionDescriptor( + const envoy::extensions::rate_limit_descriptors::expr::v3::Descriptor& config, + Filters::Common::Expr::Builder& builder, const google::api::expr::v1alpha1::Expr& input_expr) + : input_expr_(input_expr), descriptor_key_(config.descriptor_key()), + skip_if_error_(config.skip_if_error()) { + compiled_expr_ = Extensions::Filters::Common::Expr::createExpression(builder, input_expr_); + } + + // Ratelimit::DescriptorProducer + bool populateDescriptor(RateLimit::Descriptor& descriptor, const std::string&, + const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const override { + ProtobufWkt::Arena arena; + const auto result = Filters::Common::Expr::evaluate(*compiled_expr_.get(), arena, info, + &headers, nullptr, nullptr); + if (!result.has_value() || result.value().IsError()) { + // If result is an error and if skip_if_error is true skip this descriptor, + // while calling rate limiting service. If skip_if_error is false, do not call rate limiting + // service. + return skip_if_error_; + } + descriptor.entries_.push_back({descriptor_key_, Filters::Common::Expr::print(result.value())}); + return true; + } + +private: + const google::api::expr::v1alpha1::Expr input_expr_; + const std::string descriptor_key_; + const bool skip_if_error_; + Extensions::Filters::Common::Expr::ExpressionPtr compiled_expr_; +}; + +} // namespace + +std::string ExprDescriptorFactory::name() const { return "envoy.rate_limit_descriptors.expr"; } + +ProtobufTypes::MessagePtr ExprDescriptorFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +RateLimit::DescriptorProducerPtr ExprDescriptorFactory::createDescriptorProducerFromProto( + const Protobuf::Message& message, ProtobufMessage::ValidationVisitor& validator) { + const auto& config = MessageUtil::downcastAndValidate< + const envoy::extensions::rate_limit_descriptors::expr::v3::Descriptor&>(message, validator); + switch (config.expr_specifier_case()) { +#if defined(USE_CEL_PARSER) + case envoy::extensions::rate_limit_descriptors::expr::v3::Descriptor::kText: { + auto parse_status = google::api::expr::parser::Parse(config.text()); + if (!parse_status.ok()) { + throw EnvoyException("Unable to parse descriptor expression: " + + parse_status.status().ToString()); + } + return std::make_unique(config, getOrCreateBuilder(), + parse_status.value().expr()); + } +#endif + case envoy::extensions::rate_limit_descriptors::expr::v3::Descriptor::kParsed: + return std::make_unique(config, getOrCreateBuilder(), config.parsed()); + default: + return nullptr; + } +} + +Filters::Common::Expr::Builder& ExprDescriptorFactory::getOrCreateBuilder() { + if (expr_builder_ == nullptr) { + expr_builder_ = Filters::Common::Expr::createBuilder(nullptr); + } + return *expr_builder_; +} + +REGISTER_FACTORY(ExprDescriptorFactory, RateLimit::DescriptorProducerFactory); + +} // namespace Expr +} // namespace RateLimitDescriptors +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/rate_limit_descriptors/expr/config.h b/source/extensions/rate_limit_descriptors/expr/config.h new file mode 100644 index 0000000000000..6d3967bb82856 --- /dev/null +++ b/source/extensions/rate_limit_descriptors/expr/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include "envoy/ratelimit/ratelimit.h" +#include "envoy/registry/registry.h" + +#include "extensions/filters/common/expr/evaluator.h" + +namespace Envoy { +namespace Extensions { +namespace RateLimitDescriptors { +namespace Expr { + +/** + * Config registration for the computed rate limit descriptor. + * @see DescriptorProducerFactory. + */ +class ExprDescriptorFactory : public RateLimit::DescriptorProducerFactory { +public: + std::string name() const override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + RateLimit::DescriptorProducerPtr + createDescriptorProducerFromProto(const Protobuf::Message& message, + ProtobufMessage::ValidationVisitor& validator) override; + +private: + Filters::Common::Expr::Builder& getOrCreateBuilder(); + Filters::Common::Expr::BuilderPtr expr_builder_; +}; + +} // namespace Expr +} // namespace RateLimitDescriptors +} // namespace Extensions +} // namespace Envoy diff --git a/test/common/router/router_ratelimit_test.cc b/test/common/router/router_ratelimit_test.cc index d8ab967f72de5..4fab81660417c 100644 --- a/test/common/router/router_ratelimit_test.cc +++ b/test/common/router/router_ratelimit_test.cc @@ -82,15 +82,16 @@ class RateLimitConfiguration : public testing::Test { TestUtility::loadFromYaml(yaml, route_config); config_ = std::make_unique(route_config, factory_context_, any_validation_visitor_, true); + stream_info_.downstream_address_provider_->setRemoteAddress(default_remote_address_); } NiceMock factory_context_; ProtobufMessage::NullValidationVisitorImpl any_validation_visitor_; std::unique_ptr config_; Http::TestRequestHeaderMapImpl header_; - const RouteEntry* route_; - Network::Address::Ipv4Instance default_remote_address_{"10.0.0.1"}; - const envoy::config::core::v3::Metadata* dynamic_metadata_; + Network::Address::InstanceConstSharedPtr default_remote_address_{ + new Network::Address::Ipv4Instance("10.0.0.1")}; + NiceMock stream_info_; }; TEST_F(RateLimitConfiguration, NoApplicableRateLimit) { @@ -116,8 +117,7 @@ TEST_F(RateLimitConfiguration, NoApplicableRateLimit) { factory_context_.cluster_manager_.initializeClusters({"www2"}, {}); setupTest(yaml); - NiceMock stream_info; - EXPECT_EQ(0U, config_->route(genHeaders("www.lyft.com", "/bar", "GET"), stream_info, 0) + EXPECT_EQ(0U, config_->route(genHeaders("www.lyft.com", "/bar", "GET"), stream_info_, 0) ->routeEntry() ->rateLimitPolicy() .getApplicableRateLimit(0) @@ -139,11 +139,12 @@ TEST_F(RateLimitConfiguration, NoRateLimitPolicy) { factory_context_.cluster_manager_.initializeClusters({"www2"}, {}); setupTest(yaml); + auto* route = + config_->route(genHeaders("www.lyft.com", "/bar", "GET"), stream_info_, 0)->routeEntry(); + ON_CALL(Const(stream_info_), routeEntry()).WillByDefault(testing::Return(route)); - NiceMock stream_info; - route_ = config_->route(genHeaders("www.lyft.com", "/bar", "GET"), stream_info, 0)->routeEntry(); - EXPECT_EQ(0U, route_->rateLimitPolicy().getApplicableRateLimit(0).size()); - EXPECT_TRUE(route_->rateLimitPolicy().empty()); + EXPECT_EQ(0U, route->rateLimitPolicy().getApplicableRateLimit(0).size()); + EXPECT_TRUE(route->rateLimitPolicy().empty()); } TEST_F(RateLimitConfiguration, TestGetApplicationRateLimit) { @@ -164,18 +165,18 @@ TEST_F(RateLimitConfiguration, TestGetApplicationRateLimit) { factory_context_.cluster_manager_.initializeClusters({"www2"}, {}); setupTest(yaml); + auto* route = + config_->route(genHeaders("www.lyft.com", "/foo", "GET"), stream_info_, 0)->routeEntry(); + ON_CALL(Const(stream_info_), routeEntry()).WillByDefault(testing::Return(route)); - NiceMock stream_info; - route_ = config_->route(genHeaders("www.lyft.com", "/foo", "GET"), stream_info, 0)->routeEntry(); - EXPECT_FALSE(route_->rateLimitPolicy().empty()); + EXPECT_FALSE(route->rateLimitPolicy().empty()); std::vector> rate_limits = - route_->rateLimitPolicy().getApplicableRateLimit(0); + route->rateLimitPolicy().getApplicableRateLimit(0); EXPECT_EQ(1U, rate_limits.size()); std::vector descriptors; for (const RateLimitPolicyEntry& rate_limit : rate_limits) { - rate_limit.populateDescriptors(*route_, descriptors, "", header_, default_remote_address_, - dynamic_metadata_); + rate_limit.populateDescriptors(descriptors, "", header_, stream_info_); } EXPECT_THAT(std::vector({{{{"remote_address", "10.0.0.1"}}}}), testing::ContainerEq(descriptors)); @@ -199,17 +200,17 @@ TEST_F(RateLimitConfiguration, TestVirtualHost) { factory_context_.cluster_manager_.initializeClusters({"www2test"}, {}); setupTest(yaml); + auto* route = + config_->route(genHeaders("www.lyft.com", "/bar", "GET"), stream_info_, 0)->routeEntry(); + ON_CALL(Const(stream_info_), routeEntry()).WillByDefault(testing::Return(route)); - NiceMock stream_info; - route_ = config_->route(genHeaders("www.lyft.com", "/bar", "GET"), stream_info, 0)->routeEntry(); std::vector> rate_limits = - route_->virtualHost().rateLimitPolicy().getApplicableRateLimit(0); + route->virtualHost().rateLimitPolicy().getApplicableRateLimit(0); EXPECT_EQ(1U, rate_limits.size()); std::vector descriptors; for (const RateLimitPolicyEntry& rate_limit : rate_limits) { - rate_limit.populateDescriptors(*route_, descriptors, "service_cluster", header_, - default_remote_address_, dynamic_metadata_); + rate_limit.populateDescriptors(descriptors, "service_cluster", header_, stream_info_); } EXPECT_THAT(std::vector({{{{"destination_cluster", "www2test"}}}}), testing::ContainerEq(descriptors)); @@ -239,17 +240,17 @@ TEST_F(RateLimitConfiguration, Stages) { factory_context_.cluster_manager_.initializeClusters({"www2test"}, {}); setupTest(yaml); + auto* route = + config_->route(genHeaders("www.lyft.com", "/foo", "GET"), stream_info_, 0)->routeEntry(); + ON_CALL(Const(stream_info_), routeEntry()).WillByDefault(testing::Return(route)); - NiceMock stream_info; - route_ = config_->route(genHeaders("www.lyft.com", "/foo", "GET"), stream_info, 0)->routeEntry(); std::vector> rate_limits = - route_->rateLimitPolicy().getApplicableRateLimit(0); + route->rateLimitPolicy().getApplicableRateLimit(0); EXPECT_EQ(2U, rate_limits.size()); std::vector descriptors; for (const RateLimitPolicyEntry& rate_limit : rate_limits) { - rate_limit.populateDescriptors(*route_, descriptors, "service_cluster", header_, - default_remote_address_, dynamic_metadata_); + rate_limit.populateDescriptors(descriptors, "service_cluster", header_, stream_info_); } EXPECT_THAT(std::vector( {{{{"destination_cluster", "www2test"}}}, @@ -257,33 +258,36 @@ TEST_F(RateLimitConfiguration, Stages) { testing::ContainerEq(descriptors)); descriptors.clear(); - rate_limits = route_->rateLimitPolicy().getApplicableRateLimit(1UL); + rate_limits = route->rateLimitPolicy().getApplicableRateLimit(1UL); EXPECT_EQ(1U, rate_limits.size()); for (const RateLimitPolicyEntry& rate_limit : rate_limits) { - rate_limit.populateDescriptors(*route_, descriptors, "service_cluster", header_, - default_remote_address_, dynamic_metadata_); + rate_limit.populateDescriptors(descriptors, "service_cluster", header_, stream_info_); } EXPECT_THAT(std::vector({{{{"remote_address", "10.0.0.1"}}}}), testing::ContainerEq(descriptors)); - rate_limits = route_->rateLimitPolicy().getApplicableRateLimit(10UL); + rate_limits = route->rateLimitPolicy().getApplicableRateLimit(10UL); EXPECT_TRUE(rate_limits.empty()); } class RateLimitPolicyEntryTest : public testing::Test { public: void setupTest(const std::string& yaml) { - rate_limit_entry_ = std::make_unique(parseRateLimitFromV3Yaml(yaml)); + rate_limit_entry_ = std::make_unique( + parseRateLimitFromV3Yaml(yaml), ProtobufMessage::getStrictValidationVisitor()); descriptors_.clear(); + stream_info_.downstream_address_provider_->setRemoteAddress(default_remote_address_); + ON_CALL(Const(stream_info_), routeEntry()).WillByDefault(testing::Return(&route_)); } std::unique_ptr rate_limit_entry_; Http::TestRequestHeaderMapImpl header_; NiceMock route_; std::vector descriptors_; - Network::Address::Ipv4Instance default_remote_address_{"10.0.0.1"}; - const envoy::config::core::v3::Metadata* dynamic_metadata_; + Network::Address::InstanceConstSharedPtr default_remote_address_{ + new Network::Address::Ipv4Instance("10.0.0.1")}; + NiceMock stream_info_; }; TEST_F(RateLimitPolicyEntryTest, RateLimitPolicyEntryMembers) { @@ -308,8 +312,7 @@ TEST_F(RateLimitPolicyEntryTest, RemoteAddress) { setupTest(yaml); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"remote_address", "10.0.0.1"}}}}), testing::ContainerEq(descriptors_)); } @@ -323,9 +326,9 @@ TEST_F(RateLimitPolicyEntryTest, PipeAddress) { setupTest(yaml); - Network::Address::PipeInstance pipe_address("/hello"); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, pipe_address, - dynamic_metadata_); + stream_info_.downstream_address_provider_->setRemoteAddress( + std::make_shared("/hello")); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -337,8 +340,7 @@ TEST_F(RateLimitPolicyEntryTest, SourceService) { setupTest(yaml); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "service_cluster", header_, - default_remote_address_, dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header_, stream_info_); EXPECT_THAT( std::vector({{{{"source_cluster", "service_cluster"}}}}), testing::ContainerEq(descriptors_)); @@ -352,8 +354,7 @@ TEST_F(RateLimitPolicyEntryTest, DestinationService) { setupTest(yaml); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "service_cluster", header_, - default_remote_address_, dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header_, stream_info_); EXPECT_THAT( std::vector({{{{"destination_cluster", "fake_cluster"}}}}), testing::ContainerEq(descriptors_)); @@ -370,8 +371,7 @@ TEST_F(RateLimitPolicyEntryTest, RequestHeaders) { setupTest(yaml); Http::TestRequestHeaderMapImpl header{{"x-header-name", "test_value"}}; - rate_limit_entry_->populateDescriptors(route_, descriptors_, "service_cluster", header, - default_remote_address_, dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header, stream_info_); EXPECT_THAT(std::vector({{{{"my_header_name", "test_value"}}}}), testing::ContainerEq(descriptors_)); } @@ -394,8 +394,7 @@ TEST_F(RateLimitPolicyEntryTest, RequestHeadersWithSkipIfAbsent) { setupTest(yaml); Http::TestRequestHeaderMapImpl header{{"x-header-name", "test_value"}}; - rate_limit_entry_->populateDescriptors(route_, descriptors_, "service_cluster", header, - default_remote_address_, dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header, stream_info_); EXPECT_THAT(std::vector({{{{"my_header_name", "test_value"}}}}), testing::ContainerEq(descriptors_)); } @@ -418,8 +417,7 @@ TEST_F(RateLimitPolicyEntryTest, RequestHeadersWithDefaultSkipIfAbsent) { setupTest(yaml); Http::TestRequestHeaderMapImpl header{{"x-header-test", "test_value"}}; - rate_limit_entry_->populateDescriptors(route_, descriptors_, "service_cluster", header, - default_remote_address_, dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -434,8 +432,7 @@ TEST_F(RateLimitPolicyEntryTest, RequestHeadersNoMatch) { setupTest(yaml); Http::TestRequestHeaderMapImpl header{{"x-header-name", "test_value"}}; - rate_limit_entry_->populateDescriptors(route_, descriptors_, "service_cluster", header, - default_remote_address_, dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -448,8 +445,7 @@ TEST_F(RateLimitPolicyEntryTest, RateLimitKey) { setupTest(yaml); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"generic_key", "fake_key"}}}}), testing::ContainerEq(descriptors_)); } @@ -464,8 +460,7 @@ TEST_F(RateLimitPolicyEntryTest, GenericKeyWithSetDescriptorKey) { setupTest(yaml); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"fake_key", "fake_value"}}}}), testing::ContainerEq(descriptors_)); } @@ -480,8 +475,7 @@ TEST_F(RateLimitPolicyEntryTest, GenericKeyWithEmptyDescriptorKey) { setupTest(yaml); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"generic_key", "fake_value"}}}}), testing::ContainerEq(descriptors_)); } @@ -508,11 +502,8 @@ TEST_F(RateLimitPolicyEntryTest, DEPRECATED_FEATURE_TEST(DynamicMetaDataMatch)) 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); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"fake_key", "foo"}}}}), testing::ContainerEq(descriptors_)); @@ -540,11 +531,8 @@ TEST_F(RateLimitPolicyEntryTest, MetaDataMatchDynamicSourceByDefault) { 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); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"fake_key", "foo"}}}}), testing::ContainerEq(descriptors_)); @@ -573,11 +561,8 @@ TEST_F(RateLimitPolicyEntryTest, MetaDataMatchDynamicSource) { 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); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"fake_key", "foo"}}}}), testing::ContainerEq(descriptors_)); @@ -608,8 +593,7 @@ TEST_F(RateLimitPolicyEntryTest, MetaDataMatchRouteEntrySource) { TestUtility::loadFromYaml(metadata_yaml, route_.metadata_); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"fake_key", "foo"}}}}), testing::ContainerEq(descriptors_)); @@ -638,11 +622,8 @@ TEST_F(RateLimitPolicyEntryTest, MetaDataNoMatchWithDefaultValue) { 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); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"fake_key", "fake_value"}}}}), testing::ContainerEq(descriptors_)); @@ -669,11 +650,8 @@ TEST_F(RateLimitPolicyEntryTest, MetaDataNoMatch) { 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); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -699,11 +677,8 @@ TEST_F(RateLimitPolicyEntryTest, MetaDataEmptyValue) { prop: "" )EOF"; - envoy::config::core::v3::Metadata metadata; - TestUtility::loadFromYaml(metadata_yaml, metadata); - - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - &metadata); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -730,11 +705,8 @@ TEST_F(RateLimitPolicyEntryTest, MetaDataAndDefaultValueEmpty) { prop: "" )EOF"; - envoy::config::core::v3::Metadata metadata; - TestUtility::loadFromYaml(metadata_yaml, metadata); - - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - &metadata); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -761,11 +733,8 @@ TEST_F(RateLimitPolicyEntryTest, MetaDataNonStringNoMatch) { foo: bar )EOF"; - envoy::config::core::v3::Metadata metadata; - TestUtility::loadFromYaml(metadata_yaml, metadata); - - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - &metadata); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -783,8 +752,7 @@ TEST_F(RateLimitPolicyEntryTest, HeaderValueMatch) { setupTest(yaml); Http::TestRequestHeaderMapImpl header{{"x-header-name", "test_value"}}; - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header, default_remote_address_, - dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "", header, stream_info_); EXPECT_THAT(std::vector({{{{"header_match", "fake_value"}}}}), testing::ContainerEq(descriptors_)); } @@ -802,8 +770,7 @@ TEST_F(RateLimitPolicyEntryTest, HeaderValueMatchNoMatch) { setupTest(yaml); Http::TestRequestHeaderMapImpl header{{"x-header-name", "not_same_value"}}; - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header, default_remote_address_, - dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "", header, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -821,8 +788,7 @@ TEST_F(RateLimitPolicyEntryTest, HeaderValueMatchHeadersNotPresent) { setupTest(yaml); Http::TestRequestHeaderMapImpl header{{"x-header-name", "not_same_value"}}; - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header, default_remote_address_, - dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "", header, stream_info_); EXPECT_THAT(std::vector({{{{"header_match", "fake_value"}}}}), testing::ContainerEq(descriptors_)); } @@ -841,8 +807,7 @@ TEST_F(RateLimitPolicyEntryTest, HeaderValueMatchHeadersPresent) { setupTest(yaml); Http::TestRequestHeaderMapImpl header{{"x-header-name", "test_value"}}; - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header, default_remote_address_, - dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "", header, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -855,8 +820,7 @@ TEST_F(RateLimitPolicyEntryTest, CompoundActions) { setupTest(yaml); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "service_cluster", header_, - default_remote_address_, dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header_, stream_info_); EXPECT_THAT( std::vector( {{{{"destination_cluster", "fake_cluster"}, {"source_cluster", "service_cluster"}}}}), @@ -876,8 +840,7 @@ TEST_F(RateLimitPolicyEntryTest, CompoundActionsNoDescriptor) { setupTest(yaml); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "service_cluster", header_, - default_remote_address_, dynamic_metadata_); + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header_, stream_info_); EXPECT_TRUE(descriptors_.empty()); } @@ -904,10 +867,8 @@ TEST_F(RateLimitPolicyEntryTest, DynamicMetadataRateLimitOverride) { unit: HOUR )EOF"; - envoy::config::core::v3::Metadata metadata; - TestUtility::loadFromYaml(metadata_yaml, metadata); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - &metadata); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT( std::vector( {{{{"generic_key", "limited_fake_key"}}, {{42, envoy::type::v3::RateLimitUnit::HOUR}}}}), @@ -937,10 +898,8 @@ TEST_F(RateLimitPolicyEntryTest, DynamicMetadataRateLimitOverrideNotFound) { unit: HOUR )EOF"; - envoy::config::core::v3::Metadata metadata; - TestUtility::loadFromYaml(metadata_yaml, metadata); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - &metadata); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"generic_key", "limited_fake_key"}}}}), testing::ContainerEq(descriptors_)); } @@ -966,10 +925,8 @@ TEST_F(RateLimitPolicyEntryTest, DynamicMetadataRateLimitOverrideWrongType) { test: some_string )EOF"; - envoy::config::core::v3::Metadata metadata; - TestUtility::loadFromYaml(metadata_yaml, metadata); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - &metadata); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"generic_key", "limited_fake_key"}}}}), testing::ContainerEq(descriptors_)); } @@ -997,10 +954,8 @@ TEST_F(RateLimitPolicyEntryTest, DynamicMetadataRateLimitOverrideWrongUnit) { unit: NOT_A_UNIT )EOF"; - envoy::config::core::v3::Metadata metadata; - TestUtility::loadFromYaml(metadata_yaml, metadata); - rate_limit_entry_->populateDescriptors(route_, descriptors_, "", header_, default_remote_address_, - &metadata); + TestUtility::loadFromYaml(metadata_yaml, stream_info_.dynamicMetadata()); + rate_limit_entry_->populateDescriptors(descriptors_, "", header_, stream_info_); EXPECT_THAT(std::vector({{{{"generic_key", "limited_fake_key"}}}}), testing::ContainerEq(descriptors_)); } diff --git a/test/extensions/filters/common/expr/BUILD b/test/extensions/filters/common/expr/BUILD index 2ee719111d5a0..9f77992305d45 100644 --- a/test/extensions/filters/common/expr/BUILD +++ b/test/extensions/filters/common/expr/BUILD @@ -29,6 +29,17 @@ envoy_extension_cc_test( ], ) +envoy_extension_cc_test( + name = "evaluator_test", + srcs = ["evaluator_test.cc"], + extension_name = "envoy.filters.http.rbac", + deps = [ + "//source/extensions/filters/common/expr:evaluator_lib", + "//test/test_common:utility_lib", + "@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper", + ], +) + envoy_proto_library( name = "evaluator_fuzz_proto", srcs = ["evaluator_fuzz.proto"], diff --git a/test/extensions/filters/common/expr/evaluator_test.cc b/test/extensions/filters/common/expr/evaluator_test.cc new file mode 100644 index 0000000000000..ab4da05f04f7b --- /dev/null +++ b/test/extensions/filters/common/expr/evaluator_test.cc @@ -0,0 +1,48 @@ +#include "extensions/filters/common/expr/evaluator.h" + +#include "test/test_common/utility.h" + +#include "absl/time/time.h" +#include "eval/public/structs/cel_proto_wrapper.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Filters { +namespace Common { +namespace Expr { +namespace { + +TEST(Evaluator, Print) { + EXPECT_EQ(print(CelValue::CreateBool(false)), "false"); + EXPECT_EQ(print(CelValue::CreateInt64(123)), "123"); + EXPECT_EQ(print(CelValue::CreateUint64(123)), "123"); + EXPECT_EQ(print(CelValue::CreateDouble(1.23)), "1.23"); + + std::string test = "test"; + EXPECT_EQ(print(CelValue::CreateString(&test)), "test"); + EXPECT_EQ(print(CelValue::CreateBytes(&test)), "test"); + + ProtobufWkt::Arena arena; + envoy::config::core::v3::Node node; + std::string node_yaml = "id: test"; + TestUtility::loadFromYaml(node_yaml, node); + EXPECT_EQ(print(CelValue::CreateNull()), "NULL"); + EXPECT_EQ(print(google::api::expr::runtime::CelProtoWrapper::CreateMessage(&node, &arena)), + "id: \"test\""); + + EXPECT_EQ(print(CelValue::CreateDuration(absl::Minutes(1))), "1m"); + absl::Time time = TestUtility::parseTime("Dec 22 01:50:34 2020 GMT", "%b %e %H:%M:%S %Y GMT"); + EXPECT_EQ(print(CelValue::CreateTimestamp(time)), "2020-12-22T01:50:34+00:00"); + + absl::Status status = absl::UnimplementedError("unimplemented"); + EXPECT_EQ(print(CelValue::CreateError(&status)), "CelError value"); +} + +} // namespace +} // namespace Expr +} // namespace Common +} // namespace Filters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/ratelimit/ratelimit_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_test.cc index 06feeac7954d6..1dccd4f065384 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_test.cc @@ -167,8 +167,8 @@ TEST_F(HttpRateLimitFilterTest, NoApplicableRateLimit) { TEST_F(HttpRateLimitFilterTest, NoDescriptor) { SetUpTest(filter_config_); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)); - EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _, _, _)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)); + EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)); EXPECT_CALL(*client_, limit(_, _, _, _, _)).Times(0); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); @@ -201,8 +201,8 @@ TEST_F(HttpRateLimitFilterTest, OkResponse) { EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(filter_callbacks_.route_->route_entry_.virtual_host_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -246,8 +246,8 @@ TEST_F(HttpRateLimitFilterTest, OkResponseWithHeaders) { EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(filter_callbacks_.route_->route_entry_.virtual_host_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -303,8 +303,8 @@ TEST_F(HttpRateLimitFilterTest, OkResponseWithFilterHeaders) { EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(filter_callbacks_.route_->route_entry_.virtual_host_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -359,8 +359,8 @@ TEST_F(HttpRateLimitFilterTest, ImmediateOkResponse) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, "foo", testing::ContainerEq(std::vector{ @@ -390,8 +390,8 @@ TEST_F(HttpRateLimitFilterTest, ImmediateErrorResponse) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, "foo", testing::ContainerEq(std::vector{ @@ -426,8 +426,8 @@ TEST_F(HttpRateLimitFilterTest, ErrorResponse) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -460,8 +460,8 @@ TEST_F(HttpRateLimitFilterTest, ErrorResponseWithFailureModeAllowOff) { SetUpTest(fail_close_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -492,8 +492,8 @@ TEST_F(HttpRateLimitFilterTest, LimitResponse) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -532,8 +532,8 @@ TEST_F(HttpRateLimitFilterTest, LimitResponseWithDynamicMetadata) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -584,8 +584,8 @@ TEST_F(HttpRateLimitFilterTest, LimitResponseWithHeaders) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -636,8 +636,8 @@ TEST_F(HttpRateLimitFilterTest, LimitResponseWithBody) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -699,8 +699,8 @@ TEST_F(HttpRateLimitFilterTest, LimitResponseWithBodyAndContentType) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -768,8 +768,8 @@ TEST_F(HttpRateLimitFilterTest, LimitResponseWithFilterHeaders) { SetUpTest(enable_x_ratelimit_headers_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -820,8 +820,8 @@ TEST_F(HttpRateLimitFilterTest, LimitResponseWithoutEnvoyRateLimitedHeader) { SetUpTest(disable_x_envoy_ratelimited_header_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -858,8 +858,8 @@ TEST_F(HttpRateLimitFilterTest, LimitResponseRuntimeDisabled) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -900,8 +900,8 @@ TEST_F(HttpRateLimitFilterTest, ResetDuringCall) { SetUpTest(filter_config_); InSequence s; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, _, _, _, _)) .WillOnce( WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { @@ -922,7 +922,7 @@ TEST_F(HttpRateLimitFilterTest, RouteRateLimitDisabledForRouteKey) { ON_CALL(runtime_.snapshot_, featureEnabled("ratelimit.test_key.http_filter_enabled", 100)) .WillByDefault(Return(false)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)).Times(0); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)).Times(0); EXPECT_CALL(*client_, limit(_, _, _, _, _)).Times(0); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); @@ -942,7 +942,7 @@ TEST_F(HttpRateLimitFilterTest, VirtualHostRateLimitDisabledForRouteKey) { ON_CALL(runtime_.snapshot_, featureEnabled("ratelimit.test_vh_key.http_filter_enabled", 100)) .WillByDefault(Return(false)); - EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _, _, _)).Times(0); + EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)).Times(0); EXPECT_CALL(*client_, limit(_, _, _, _, _)).Times(0); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); @@ -964,8 +964,8 @@ TEST_F(HttpRateLimitFilterTest, IncorrectRequestType) { )EOF"; SetUpTest(internal_filter_config); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)).Times(0); - EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _, _, _)).Times(0); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)).Times(0); + EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)).Times(0); EXPECT_CALL(*client_, limit(_, _, _, _, _)).Times(0); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); @@ -985,8 +985,8 @@ TEST_F(HttpRateLimitFilterTest, IncorrectRequestType) { SetUpTest(external_filter_config); Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"}}; - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)).Times(0); - EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _, _, _)).Times(0); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)).Times(0); + EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)).Times(0); EXPECT_CALL(*client_, limit(_, _, _, _, _)).Times(0); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false)); @@ -1011,8 +1011,8 @@ TEST_F(HttpRateLimitFilterTest, InternalRequestType) { EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(filter_callbacks_.route_->route_entry_.virtual_host_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -1055,8 +1055,8 @@ TEST_F(HttpRateLimitFilterTest, ExternalRequestType) { EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(filter_callbacks_.route_->route_entry_.virtual_host_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -1098,8 +1098,8 @@ TEST_F(HttpRateLimitFilterTest, DEPRECATED_FEATURE_TEST(ExcludeVirtualHost)) { InSequence s; EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(filter_callbacks_.route_->route_entry_, includeVirtualHostRateLimits()) .WillOnce(Return(false)); @@ -1149,8 +1149,8 @@ TEST_F(HttpRateLimitFilterTest, OverrideVHRateLimitOptionWithRouteRateLimitSet) FilterConfigPerRoute per_route_config_(settings); EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(filter_callbacks_.route_->route_entry_, includeVirtualHostRateLimits()) .WillOnce(Return(false)); @@ -1214,8 +1214,8 @@ TEST_F(HttpRateLimitFilterTest, OverrideVHRateLimitOptionWithoutRouteRateLimit) EXPECT_CALL(filter_callbacks_.route_->route_entry_.virtual_host_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(*client_, limit(_, "foo", testing::ContainerEq(std::vector{ @@ -1262,8 +1262,8 @@ TEST_F(HttpRateLimitFilterTest, IncludeVHRateLimitOptionWithOnlyVHRateLimitSet) EXPECT_CALL(filter_callbacks_.route_->route_entry_.virtual_host_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_two_)); + EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_two_)); EXPECT_CALL(*client_, limit(_, "foo", @@ -1299,8 +1299,8 @@ TEST_F(HttpRateLimitFilterTest, IncludeVHRateLimitOptionWithRouteAndVHRateLimitS FilterConfigPerRoute per_route_config_(settings); EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(filter_callbacks_.route_->route_entry_, includeVirtualHostRateLimits()) .WillOnce(Return(false)); @@ -1312,8 +1312,8 @@ TEST_F(HttpRateLimitFilterTest, IncludeVHRateLimitOptionWithRouteAndVHRateLimitS EXPECT_CALL(filter_callbacks_.route_->route_entry_.virtual_host_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_two_)); + EXPECT_CALL(vh_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_two_)); EXPECT_CALL(*client_, limit(_, "foo", @@ -1349,8 +1349,8 @@ TEST_F(HttpRateLimitFilterTest, IgnoreVHRateLimitOptionWithRouteRateLimitSet) { FilterConfigPerRoute per_route_config_(settings); EXPECT_CALL(filter_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); - EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _, _)) - .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); EXPECT_CALL(filter_callbacks_.route_->route_entry_, includeVirtualHostRateLimits()) .WillOnce(Return(false)); diff --git a/test/extensions/rate_limit_descriptors/expr/BUILD b/test/extensions/rate_limit_descriptors/expr/BUILD new file mode 100644 index 0000000000000..857126f646754 --- /dev/null +++ b/test/extensions/rate_limit_descriptors/expr/BUILD @@ -0,0 +1,34 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + copts = select({ + "//bazel:windows_x86_64": [], # TODO: fix the windows ANTLR build + "//conditions:default": [ + "-DUSE_CEL_PARSER", + ], + }), + extension_name = "envoy.rate_limit_descriptors.expr", + deps = [ + "//source/common/protobuf:utility_lib", + "//source/common/router:router_ratelimit_lib", + "//source/extensions/rate_limit_descriptors/expr:config", + "//test/mocks/http:http_mocks", + "//test/mocks/ratelimit:ratelimit_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/route/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/rate_limit_descriptors/expr/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/rate_limit_descriptors/expr/config_test.cc b/test/extensions/rate_limit_descriptors/expr/config_test.cc new file mode 100644 index 0000000000000..d68981ede37d2 --- /dev/null +++ b/test/extensions/rate_limit_descriptors/expr/config_test.cc @@ -0,0 +1,221 @@ +#include "envoy/config/route/v3/route_components.pb.h" +#include "envoy/config/route/v3/route_components.pb.validate.h" +#include "envoy/extensions/rate_limit_descriptors/expr/v3/expr.pb.h" +#include "envoy/extensions/rate_limit_descriptors/expr/v3/expr.pb.validate.h" + +#include "common/protobuf/utility.h" +#include "common/router/router_ratelimit.h" + +#include "extensions/rate_limit_descriptors/expr/config.h" + +#include "test/mocks/http/mocks.h" +#include "test/mocks/ratelimit/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; + +namespace Envoy { +namespace Extensions { +namespace RateLimitDescriptors { +namespace Expr { +namespace { + +class RateLimitPolicyEntryTest : public testing::Test { +public: + void setupTest(const std::string& yaml) { + envoy::config::route::v3::RateLimit rate_limit; + TestUtility::loadFromYaml(yaml, rate_limit, false, true); + TestUtility::validate(rate_limit); + rate_limit_entry_ = std::make_unique( + rate_limit, ProtobufMessage::getStrictValidationVisitor()); + } + + std::unique_ptr rate_limit_entry_; + Http::TestRequestHeaderMapImpl header_; + std::vector descriptors_; + NiceMock stream_info_; +}; + +TEST_F(RateLimitPolicyEntryTest, MissingExtension) { + const std::string yaml = R"EOF( +actions: +- extension: + name: custom + typed_config: + "@type": type.googleapis.com/google.protobuf.Value + )EOF"; + + EXPECT_THROW_WITH_REGEX(setupTest(yaml), EnvoyException, + "Rate limit descriptor extension not found: .*"); +} + +TEST_F(RateLimitPolicyEntryTest, ExpressionUnset) { + const std::string yaml = R"EOF( +actions: +- extension: + name: custom_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: my_descriptor_name + )EOF"; + + EXPECT_THROW_WITH_REGEX(setupTest(yaml), EnvoyException, + "Rate limit descriptor extension failed: .*"); +} + +#if defined(USE_CEL_PARSER) +TEST_F(RateLimitPolicyEntryTest, ExpressionText) { + const std::string yaml = R"EOF( +actions: +- extension: + name: custom_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: my_descriptor_name + text: request.headers["x-header-name"] + )EOF"; + + setupTest(yaml); + Http::TestRequestHeaderMapImpl header{{"x-header-name", "test_value"}}; + + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header, stream_info_); + EXPECT_THAT(std::vector({{{{"my_descriptor_name", "test_value"}}}}), + testing::ContainerEq(descriptors_)); +} + +TEST_F(RateLimitPolicyEntryTest, ExpressionTextMalformed) { + const std::string yaml = R"EOF( +actions: +- extension: + name: custom_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: my_descriptor_name + text: undefined_ext(false) + )EOF"; + + EXPECT_THROW_WITH_REGEX(setupTest(yaml), EnvoyException, "failed to create an expression: .*"); +} + +TEST_F(RateLimitPolicyEntryTest, ExpressionUnparsable) { + const std::string yaml = R"EOF( +actions: +- extension: + name: custom_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: my_descriptor_name + text: ++ + )EOF"; + + EXPECT_THROW_WITH_REGEX(setupTest(yaml), EnvoyException, + "Unable to parse descriptor expression: .*"); +} +#endif + +TEST_F(RateLimitPolicyEntryTest, ExpressionParsed) { + const std::string yaml = R"EOF( +actions: +- extension: + name: custom_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: my_descriptor_name + parsed: + call_expr: + function: _==_ + args: + - select_expr: + operand: + ident_expr: + name: request + field: method + - const_expr: + string_value: GET + )EOF"; + + setupTest(yaml); + Http::TestRequestHeaderMapImpl header{{":method", "GET"}}; + + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header, stream_info_); + EXPECT_THAT(std::vector({{{{"my_descriptor_name", "true"}}}}), + testing::ContainerEq(descriptors_)); +} + +TEST_F(RateLimitPolicyEntryTest, ExpressionParsedMalformed) { + const std::string yaml = R"EOF( +actions: +- extension: + name: custom_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: my_descriptor_name + parsed: + call_expr: + function: undefined_extent + args: + - const_expr: + bool_value: false + )EOF"; + + EXPECT_THROW_WITH_REGEX(setupTest(yaml), EnvoyException, "failed to create an expression: .*"); +} + +#if defined(USE_CEL_PARSER) +TEST_F(RateLimitPolicyEntryTest, ExpressionTextError) { + const std::string yaml = R"EOF( +actions: +- extension: + name: first_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: test_key + text: "'a'" +- extension: + name: second_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: my_descriptor_name + text: request.headers["x-header-name"] + )EOF"; + + setupTest(yaml); + + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header_, stream_info_); + EXPECT_TRUE(descriptors_.empty()); +} + +TEST_F(RateLimitPolicyEntryTest, ExpressionTextErrorSkip) { + const std::string yaml = R"EOF( +actions: +- extension: + name: first_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: test_key + text: "'a'" +- extension: + name: second_descriptor + typed_config: + "@type": type.googleapis.com/envoy.extensions.rate_limit_descriptors.expr.v3.Descriptor + descriptor_key: my_descriptor_name + text: request.headers["x-header-name"] + skip_if_error: true + )EOF"; + + setupTest(yaml); + + rate_limit_entry_->populateDescriptors(descriptors_, "service_cluster", header_, stream_info_); + EXPECT_THAT(std::vector({{{{"test_key", "a"}}}}), + testing::ContainerEq(descriptors_)); +} +#endif + +} // namespace +} // namespace Expr +} // namespace RateLimitDescriptors +} // namespace Extensions +} // namespace Envoy diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index 39edf3a2f925d..6d665cd262527 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -190,10 +190,9 @@ class MockRateLimitPolicyEntry : public RateLimitPolicyEntry { MOCK_METHOD(uint64_t, stage, (), (const)); MOCK_METHOD(const std::string&, disableKey, (), (const)); MOCK_METHOD(void, populateDescriptors, - (const RouteEntry& route, std::vector& descriptors, - const std::string& local_service_cluster, const Http::HeaderMap& headers, - const Network::Address::Instance& remote_address, - const envoy::config::core::v3::Metadata* dynamic_metadata), + (std::vector & descriptors, + const std::string& local_service_cluster, const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info), (const)); uint64_t stage_{};