diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index 690a9956454df..0a2492b2ff5f5 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -476,7 +476,6 @@ message ExtAuthzPerRoute { } // Extra settings for the check request. -// [#next-free-field: 6] message CheckSettings { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ext_authz.v2.CheckSettings"; @@ -514,16 +513,4 @@ message CheckSettings { // :ref:`disable_request_body_buffering ` // may be specified. BufferSettings with_request_body = 3; - - // Override the external authorization service for this route. - // This allows different routes to use different external authorization service backends - // and service types (gRPC or HTTP). If specified, this overrides the filter-level service - // configuration regardless of the original service type. - oneof service_override { - // Override with a gRPC service configuration. - config.core.v3.GrpcService grpc_service = 4; - - // Override with an HTTP service configuration. - HttpService http_service = 5; - } } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 4495eeb525b54..076cf1a618dbf 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -203,8 +203,9 @@ new_features: for more details. - area: socket change: | - Added ``network_namespace_filepath`` to :ref:`SocketAddress `. This field allows - specifying a Linux network namespace filepath for socket creation, enabling network isolation in containerized environments. + Added :ref:``network_namespace_filepath `` to + :ref:`SocketAddress `. This field allows specifying a Linux network namespace filepath + for socket creation, enabling network isolation in containerized environments. - area: ratelimit change: | Add the :ref:`rate_limits @@ -255,13 +256,6 @@ new_features: Added ``virtualHost()`` to the Stream handle API, allowing Lua scripts to retrieve virtual host information. So far, the only method implemented is ``metadata()``, allowing Lua scripts to access virtual host metadata scoped to the specific filter name. See :ref:`Virtual host object API ` for more details. -- area: ext_authz - change: | - Added support for per-route gRPC service override in the ``ext_authz`` HTTP filter. This allows different routes - to use different external authorization backends by configuring a - :ref:`grpc_service ` - in the per-route ``check_settings``. Routes without this configuration continue to use the default - authorization service. - area: tracing change: | Added :ref:`trace_context_option ` enum diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc index 170ee16ef84a8..070636df941af 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc @@ -133,30 +133,6 @@ ClientConfig::ClientConfig(const envoy::extensions::filters::http::ext_authz::v3 Router::HeaderParserPtr)), encode_raw_headers_(config.encode_raw_headers()) {} -ClientConfig::ClientConfig( - const envoy::extensions::filters::http::ext_authz::v3::HttpService& http_service, - bool encode_raw_headers, uint32_t timeout, Server::Configuration::CommonFactoryContext& context) - : client_header_matchers_(toClientMatchers( - http_service.authorization_response().allowed_client_headers(), context)), - client_header_on_success_matchers_(toClientMatchersOnSuccess( - http_service.authorization_response().allowed_client_headers_on_success(), context)), - to_dynamic_metadata_matchers_(toDynamicMetadataMatchers( - http_service.authorization_response().dynamic_metadata_from_headers(), context)), - upstream_header_matchers_(toUpstreamMatchers( - http_service.authorization_response().allowed_upstream_headers(), context)), - upstream_header_to_append_matchers_(toUpstreamMatchers( - http_service.authorization_response().allowed_upstream_headers_to_append(), context)), - cluster_name_(http_service.server_uri().cluster()), timeout_(timeout), - path_prefix_( - THROW_OR_RETURN_VALUE(validatePathPrefix(http_service.path_prefix()), std::string)), - tracing_name_(fmt::format("async {} egress", http_service.server_uri().cluster())), - request_headers_parser_(THROW_OR_RETURN_VALUE( - Router::HeaderParser::configure( - http_service.authorization_request().headers_to_add(), - envoy::config::core::v3::HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD), - Router::HeaderParserPtr)), - encode_raw_headers_(encode_raw_headers) {} - MatcherSharedPtr ClientConfig::toClientMatchersOnSuccess(const envoy::type::matcher::v3::ListStringMatcher& list, Server::Configuration::CommonFactoryContext& context) { diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h index 0847afe6c5a14..d79b34876548d 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h @@ -28,11 +28,6 @@ class ClientConfig { uint32_t timeout, absl::string_view path_prefix, Server::Configuration::CommonFactoryContext& context); - // Build config directly from HttpService without constructing a temporary ExtAuthz. - ClientConfig(const envoy::extensions::filters::http::ext_authz::v3::HttpService& http_service, - bool encode_raw_headers, uint32_t timeout, - Server::Configuration::CommonFactoryContext& context); - /** * Returns the name of the authorization cluster. */ diff --git a/source/extensions/filters/http/ext_authz/config.cc b/source/extensions/filters/http/ext_authz/config.cc index 8f7febefa8ccc..83a3c60287ae6 100644 --- a/source/extensions/filters/http/ext_authz/config.cc +++ b/source/extensions/filters/http/ext_authz/config.cc @@ -39,8 +39,7 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoWithServ &server_context](Http::FilterChainFactoryCallbacks& callbacks) { auto client = std::make_unique( server_context.clusterManager(), client_config); - callbacks.addStreamFilter( - std::make_shared(filter_config, std::move(client), server_context)); + callbacks.addStreamFilter(std::make_shared(filter_config, std::move(client))); }; } else { // gRPC client. @@ -58,8 +57,7 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoWithServ THROW_IF_NOT_OK_REF(client_or_error.status()); auto client = std::make_unique( client_or_error.value(), std::chrono::milliseconds(timeout_ms)); - callbacks.addStreamFilter( - std::make_shared(filter_config, std::move(client), server_context)); + callbacks.addStreamFilter(std::make_shared(filter_config, std::move(client))); }; } return callback; diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index 7fd7abf65f365..9efd5b6fc36f6 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -1,3 +1,4 @@ +#include "ext_authz.h" #include "source/extensions/filters/http/ext_authz/ext_authz.h" #include @@ -20,9 +21,6 @@ namespace ExtAuthz { namespace { -// Default timeout for per-route gRPC client creation. -constexpr uint32_t kDefaultPerRouteTimeoutMs = 200; - using MetadataProto = ::envoy::config::core::v3::Metadata; using Filters::Common::MutationRules::CheckOperation; using Filters::Common::MutationRules::CheckResult; @@ -174,86 +172,6 @@ void FilterConfigPerRoute::merge(const FilterConfigPerRoute& other) { } } -// Constructor used for merging configurations from different levels (vhost, route, etc.) -FilterConfigPerRoute::FilterConfigPerRoute(const FilterConfigPerRoute& less_specific, - const FilterConfigPerRoute& more_specific) - : context_extensions_(less_specific.context_extensions_), - check_settings_(more_specific.check_settings_), disabled_(more_specific.disabled_), - // Only use the most specific per-route override. Do not inherit overrides from less - // specific configuration. If the more specific configuration has no override, leave both - // unset so that the main filter configuration is used. - grpc_service_(more_specific.grpc_service_.has_value() ? more_specific.grpc_service_ - : absl::nullopt), - http_service_(more_specific.http_service_.has_value() ? more_specific.http_service_ - : absl::nullopt) { - // Merge context extensions from more specific configuration, overriding less specific ones. - for (const auto& extension : more_specific.context_extensions_) { - context_extensions_[extension.first] = extension.second; - } -} - -Filters::Common::ExtAuthz::ClientPtr -Filter::createPerRouteGrpcClient(const envoy::config::core::v3::GrpcService& grpc_service) { - if (server_context_ == nullptr) { - ENVOY_STREAM_LOG( - debug, "ext_authz filter: server context not available for per-route gRPC client creation.", - *decoder_callbacks_); - return nullptr; - } - - // Use the timeout from the gRPC service configuration, use default if not specified. - const uint32_t timeout_ms = - PROTOBUF_GET_MS_OR_DEFAULT(grpc_service, timeout, kDefaultPerRouteTimeoutMs); - - // We can skip transport version check for per-route gRPC service here. - // The transport version is already validated at the main configuration level. - Envoy::Grpc::GrpcServiceConfigWithHashKey config_with_hash_key = - Envoy::Grpc::GrpcServiceConfigWithHashKey(grpc_service); - - auto client_or_error = server_context_->clusterManager() - .grpcAsyncClientManager() - .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, - server_context_->scope(), true); - if (!client_or_error.ok()) { - ENVOY_STREAM_LOG(warn, - "ext_authz filter: failed to create per-route gRPC client: {}. Falling back " - "to default client.", - *decoder_callbacks_, client_or_error.status().ToString()); - return nullptr; - } - - ENVOY_STREAM_LOG(debug, "ext_authz filter: created per-route gRPC client for cluster: {}.", - *decoder_callbacks_, - grpc_service.has_envoy_grpc() ? grpc_service.envoy_grpc().cluster_name() - : "google_grpc"); - - return std::make_unique( - client_or_error.value(), std::chrono::milliseconds(timeout_ms)); -} - -Filters::Common::ExtAuthz::ClientPtr Filter::createPerRouteHttpClient( - const envoy::extensions::filters::http::ext_authz::v3::HttpService& http_service) { - if (server_context_ == nullptr) { - ENVOY_STREAM_LOG( - debug, "ext_authz filter: server context not available for per-route HTTP client creation.", - *decoder_callbacks_); - return nullptr; - } - - // Use the timeout from the HTTP service configuration, use default if not specified. - const uint32_t timeout_ms = - PROTOBUF_GET_MS_OR_DEFAULT(http_service.server_uri(), timeout, kDefaultPerRouteTimeoutMs); - - ENVOY_STREAM_LOG(debug, "ext_authz filter: creating per-route HTTP client for URI: {}.", - *decoder_callbacks_, http_service.server_uri().uri()); - - const auto client_config = std::make_shared( - http_service, config_->headersAsBytes(), timeout_ms, *server_context_); - - return std::make_unique( - server_context_->clusterManager(), client_config); -} - void Filter::initiateCall(const Http::RequestHeaderMap& headers) { if (filter_return_ == FilterReturn::StopDecoding) { return; @@ -287,10 +205,9 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { for (const FilterConfigPerRoute& cfg : Http::Utility::getAllPerFilterConfig(decoder_callbacks_)) { if (maybe_merged_per_route_config.has_value()) { - FilterConfigPerRoute current_config = maybe_merged_per_route_config.value(); - maybe_merged_per_route_config.emplace(current_config, cfg); + maybe_merged_per_route_config.value().merge(cfg); } else { - maybe_merged_per_route_config.emplace(cfg); + maybe_merged_per_route_config = cfg; } } @@ -299,46 +216,6 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { context_extensions = maybe_merged_per_route_config.value().takeContextExtensions(); } - // Check if we need to use a per-route service override (gRPC or HTTP). - Filters::Common::ExtAuthz::Client* client_to_use = client_.get(); - if (maybe_merged_per_route_config) { - if (maybe_merged_per_route_config->grpcService().has_value()) { - const auto& grpc_service = maybe_merged_per_route_config->grpcService().value(); - ENVOY_STREAM_LOG(debug, "ext_authz filter: using per-route gRPC service configuration.", - *decoder_callbacks_); - - // Create a new gRPC client for this route. - per_route_client_ = createPerRouteGrpcClient(grpc_service); - if (per_route_client_ != nullptr) { - client_to_use = per_route_client_.get(); - ENVOY_STREAM_LOG(debug, "ext_authz filter: successfully created per-route gRPC client.", - *decoder_callbacks_); - } else { - ENVOY_STREAM_LOG( - warn, - "ext_authz filter: failed to create per-route gRPC client, falling back to default.", - *decoder_callbacks_); - } - } else if (maybe_merged_per_route_config->httpService().has_value()) { - const auto& http_service = maybe_merged_per_route_config->httpService().value(); - ENVOY_STREAM_LOG(debug, "ext_authz filter: using per-route HTTP service configuration.", - *decoder_callbacks_); - - // Create a new HTTP client for this route. - per_route_client_ = createPerRouteHttpClient(http_service); - if (per_route_client_ != nullptr) { - client_to_use = per_route_client_.get(); - ENVOY_STREAM_LOG(debug, "ext_authz filter: successfully created per-route HTTP client.", - *decoder_callbacks_); - } else { - ENVOY_STREAM_LOG( - warn, - "ext_authz filter: failed to create per-route HTTP client, falling back to default.", - *decoder_callbacks_); - } - } - } - // If metadata_context_namespaces or typed_metadata_context_namespaces is specified, // pass matching filter metadata to the ext_authz service. // If metadata key is set in both the connection and request metadata, @@ -364,7 +241,7 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { config_->destinationLabels(), config_->allowedHeadersMatcher(), config_->disallowedHeadersMatcher()); - ENVOY_STREAM_LOG(trace, "ext_authz filter calling authorization server.", *decoder_callbacks_); + ENVOY_STREAM_LOG(trace, "ext_authz filter calling authorization server", *decoder_callbacks_); // Store start time of ext_authz filter call start_time_ = decoder_callbacks_->dispatcher().timeSource().monotonicTime(); @@ -373,8 +250,8 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { // going to invoke check call. cluster_ = decoder_callbacks_->clusterInfo(); initiating_call_ = true; - client_to_use->check(*this, check_request_, decoder_callbacks_->activeSpan(), - decoder_callbacks_->streamInfo()); + client_->check(*this, check_request_, decoder_callbacks_->activeSpan(), + decoder_callbacks_->streamInfo()); initiating_call_ = false; } diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 123daee1a6154..3f309b97c6d2e 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -6,10 +6,8 @@ #include #include "envoy/extensions/filters/http/ext_authz/v3/ext_authz.pb.h" -#include "envoy/grpc/async_client_manager.h" #include "envoy/http/filter.h" #include "envoy/runtime/runtime.h" -#include "envoy/server/factory_context.h" #include "envoy/service/auth/v3/external_auth.pb.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" @@ -19,7 +17,6 @@ #include "source/common/common/logger.h" #include "source/common/common/matchers.h" #include "source/common/common/utility.h" -#include "source/common/grpc/typed_async_client.h" #include "source/common/http/codes.h" #include "source/common/http/header_map_impl.h" #include "source/common/runtime/runtime_protos.h" @@ -308,13 +305,7 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig { check_settings_(config.has_check_settings() ? config.check_settings() : envoy::extensions::filters::http::ext_authz::v3::CheckSettings()), - disabled_(config.disabled()), - grpc_service_(config.has_check_settings() && config.check_settings().has_grpc_service() - ? absl::make_optional(config.check_settings().grpc_service()) - : absl::nullopt), - http_service_(config.has_check_settings() && config.check_settings().has_http_service() - ? absl::make_optional(config.check_settings().http_service()) - : absl::nullopt) { + disabled_(config.disabled()) { if (config.has_check_settings() && config.check_settings().disable_request_body_buffering() && config.check_settings().has_with_request_body()) { ExceptionUtil::throwEnvoyException( @@ -323,12 +314,6 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig { } } - // This constructor is used as a way to merge more-specific config into less-specific config in a - // clearly defined way (e.g. route config into VH config). All fields on this class must be const - // and thus must be initialized in the constructor initialization list. - FilterConfigPerRoute(const FilterConfigPerRoute& less_specific, - const FilterConfigPerRoute& more_specific); - void merge(const FilterConfigPerRoute& other); /** @@ -344,30 +329,12 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig { return check_settings_; } - /** - * @return The gRPC service override for this route, if any. - */ - const absl::optional& grpcService() const { - return grpc_service_; - } - - /** - * @return The HTTP service override for this route, if any. - */ - const absl::optional& - httpService() const { - return http_service_; - } - private: // We save the context extensions as a protobuf map instead of a std::map as this allows us to // move it to the CheckRequest, thus avoiding a copy that would incur by converting it. ContextExtensionsMap context_extensions_; envoy::extensions::filters::http::ext_authz::v3::CheckSettings check_settings_; - const bool disabled_; - const absl::optional grpc_service_; - const absl::optional - http_service_; + bool disabled_; }; /** @@ -381,12 +348,6 @@ class Filter : public Logger::Loggable, Filter(const FilterConfigSharedPtr& config, Filters::Common::ExtAuthz::ClientPtr&& client) : config_(config), client_(std::move(client)), stats_(config->stats()) {} - // Constructor that includes server context for per-route service support. - Filter(const FilterConfigSharedPtr& config, Filters::Common::ExtAuthz::ClientPtr&& client, - Server::Configuration::ServerFactoryContext& server_context) - : config_(config), client_(std::move(client)), server_context_(&server_context), - stats_(config->stats()) {} - // Http::StreamFilterBase void onDestroy() override; @@ -422,14 +383,6 @@ class Filter : public Logger::Loggable, // code. void rejectResponse(); - // Create a new gRPC client for per-route gRPC service configuration. - Filters::Common::ExtAuthz::ClientPtr - createPerRouteGrpcClient(const envoy::config::core::v3::GrpcService& grpc_service); - - // Create a new HTTP client for per-route HTTP service configuration. - Filters::Common::ExtAuthz::ClientPtr createPerRouteHttpClient( - const envoy::extensions::filters::http::ext_authz::v3::HttpService& http_service); - absl::optional start_time_; void addResponseHeaders(Http::HeaderMap& header_map, const Http::HeaderVector& headers); void initiateCall(const Http::RequestHeaderMap& headers); @@ -457,10 +410,6 @@ class Filter : public Logger::Loggable, Http::HeaderMapPtr getHeaderMap(const Filters::Common::ExtAuthz::ResponsePtr& response); FilterConfigSharedPtr config_; Filters::Common::ExtAuthz::ClientPtr client_; - // Per-route gRPC client that overrides the default client when specified. - Filters::Common::ExtAuthz::ClientPtr per_route_client_; - // Server context for creating per-route clients. - Server::Configuration::ServerFactoryContext* server_context_{nullptr}; Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; Http::RequestHeaderMap* request_headers_; diff --git a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc index 92cc6cddc5e62..298abb58a10e0 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc @@ -209,27 +209,6 @@ class ExtAuthzHttpClientTest : public testing::Test { NiceMock stream_info_; }; -// Verify ClientConfig could be built directly from HttpService and that the -// fields get wired correctly. -TEST_F(ExtAuthzHttpClientTest, ClientConfigFromHttpService) { - envoy::extensions::filters::http::ext_authz::v3::HttpService http_service; - http_service.mutable_server_uri()->set_uri("ext_authz:9000"); - http_service.mutable_server_uri()->set_cluster("ext_authz"); - http_service.mutable_server_uri()->mutable_timeout()->set_seconds(0); - http_service.set_path_prefix("/prefix"); - // Add one header to add to request to exercise header parser creation. - auto* add = http_service.mutable_authorization_request()->add_headers_to_add(); - add->set_key("x-added"); - add->set_value("v"); - - auto cfg = std::make_shared(http_service, /*encode_raw_headers=*/true, - /*timeout_ms=*/123, factory_context_); - EXPECT_EQ(cfg->cluster(), "ext_authz"); - EXPECT_EQ(cfg->pathPrefix(), "/prefix"); - EXPECT_EQ(cfg->timeout(), std::chrono::milliseconds{123}); - EXPECT_TRUE(cfg->encodeRawHeaders()); -} - TEST_F(ExtAuthzHttpClientTest, StreamInfo) { envoy::service::auth::v3::CheckRequest request; client_->check(request_callbacks_, request, parent_span_, stream_info_); diff --git a/test/extensions/filters/http/ext_authz/config_test.cc b/test/extensions/filters/http/ext_authz/config_test.cc index 9a84ee2e73deb..bfb2a593b4114 100644 --- a/test/extensions/filters/http/ext_authz/config_test.cc +++ b/test/extensions/filters/http/ext_authz/config_test.cc @@ -8,7 +8,6 @@ #include "source/common/network/address_impl.h" #include "source/common/thread_local/thread_local_impl.h" #include "source/extensions/filters/http/ext_authz/config.h" -#include "source/extensions/filters/http/ext_authz/ext_authz.h" #include "test/mocks/server/factory_context.h" #include "test/test_common/real_threads_test_helper.h" @@ -29,10 +28,6 @@ namespace Extensions { namespace HttpFilters { namespace ExtAuthz { -using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; - class TestAsyncClientManagerImpl : public Grpc::AsyncClientManagerImpl { public: TestAsyncClientManagerImpl(Upstream::ClusterManager& cm, ThreadLocal::Instance& tls, @@ -222,412 +217,6 @@ TEST_F(ExtAuthzFilterHttpTest, FilterWithServerContext) { cb(filter_callback); } -TEST_F(ExtAuthzFilterHttpTest, PerRouteGrpcServiceConfiguration) { - const std::string per_route_config_yaml = R"EOF( - check_settings: - context_extensions: - virtual_host: "my_virtual_host" - route_type: "high_qps" - grpc_service: - envoy_grpc: - cluster_name: "ext_authz_high_qps" - )EOF"; - - ExtAuthzFilterConfig factory; - ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(per_route_config_yaml, *proto_config); - - testing::NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); - auto route_config = factory.createRouteSpecificFilterConfig(*proto_config, context, - context.messageValidationVisitor()); - EXPECT_TRUE(route_config.ok()); - - const auto& typed_config = dynamic_cast(*route_config.value()); - EXPECT_FALSE(typed_config.disabled()); - EXPECT_TRUE(typed_config.grpcService().has_value()); - - const auto& grpc_service = typed_config.grpcService().value(); - EXPECT_TRUE(grpc_service.has_envoy_grpc()); - EXPECT_EQ(grpc_service.envoy_grpc().cluster_name(), "ext_authz_high_qps"); - - const auto& context_extensions = typed_config.contextExtensions(); - EXPECT_EQ(context_extensions.at("virtual_host"), "my_virtual_host"); - EXPECT_EQ(context_extensions.at("route_type"), "high_qps"); -} - -TEST_F(ExtAuthzFilterHttpTest, PerRouteHttpServiceConfiguration) { - const std::string per_route_config_yaml = R"EOF( - check_settings: - context_extensions: - virtual_host: "my_virtual_host" - route_type: "high_qps" - http_service: - server_uri: - uri: "https://ext-authz-http.example.com" - cluster: "ext_authz_http_cluster" - timeout: 2s - path_prefix: "/api/auth" - )EOF"; - - ExtAuthzFilterConfig factory; - ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(per_route_config_yaml, *proto_config); - - testing::NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); - auto route_config = factory.createRouteSpecificFilterConfig(*proto_config, context, - context.messageValidationVisitor()); - EXPECT_TRUE(route_config.ok()); - - const auto& typed_config = dynamic_cast(*route_config.value()); - EXPECT_FALSE(typed_config.disabled()); - EXPECT_TRUE(typed_config.httpService().has_value()); - EXPECT_FALSE(typed_config.grpcService().has_value()); - - const auto& http_service = typed_config.httpService().value(); - EXPECT_EQ(http_service.server_uri().uri(), "https://ext-authz-http.example.com"); - EXPECT_EQ(http_service.server_uri().cluster(), "ext_authz_http_cluster"); - EXPECT_EQ(http_service.server_uri().timeout().seconds(), 2); - EXPECT_EQ(http_service.path_prefix(), "/api/auth"); - - const auto& context_extensions = typed_config.contextExtensions(); - EXPECT_EQ(context_extensions.at("virtual_host"), "my_virtual_host"); - EXPECT_EQ(context_extensions.at("route_type"), "high_qps"); -} - -TEST_F(ExtAuthzFilterHttpTest, PerRouteServiceTypeSwitching) { - // Test that we can switch service types - e.g., have gRPC in less specific and HTTP in more - // specific - const std::string less_specific_config_yaml = R"EOF( - check_settings: - context_extensions: - base_setting: "from_base" - grpc_service: - envoy_grpc: - cluster_name: "ext_authz_grpc_cluster" - )EOF"; - - const std::string more_specific_config_yaml = R"EOF( - check_settings: - context_extensions: - override_setting: "from_override" - http_service: - server_uri: - uri: "https://ext-authz-http.example.com" - cluster: "ext_authz_http_cluster" - timeout: 3s - path_prefix: "/auth/check" - )EOF"; - - ExtAuthzFilterConfig factory; - - // Create less specific configuration with gRPC service - ProtobufTypes::MessagePtr less_specific_proto = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(less_specific_config_yaml, *less_specific_proto); - FilterConfigPerRoute less_specific_config( - *dynamic_cast( - less_specific_proto.get())); - - // Create more specific configuration with HTTP service - ProtobufTypes::MessagePtr more_specific_proto = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(more_specific_config_yaml, *more_specific_proto); - FilterConfigPerRoute more_specific_config( - *dynamic_cast( - more_specific_proto.get())); - - // Merge configurations - should use HTTP service from more specific config - FilterConfigPerRoute merged_config(less_specific_config, more_specific_config); - - // Verify that HTTP service from more specific config is used (service type switching) - EXPECT_TRUE(merged_config.httpService().has_value()); - EXPECT_FALSE(merged_config.grpcService().has_value()); - - const auto& http_service = merged_config.httpService().value(); - EXPECT_EQ(http_service.server_uri().uri(), "https://ext-authz-http.example.com"); - EXPECT_EQ(http_service.server_uri().cluster(), "ext_authz_http_cluster"); - EXPECT_EQ(http_service.path_prefix(), "/auth/check"); - - // Verify context extensions are properly merged (less specific preserved, more specific - // overrides) - const auto& context_extensions = merged_config.contextExtensions(); - EXPECT_EQ(context_extensions.size(), 2); - EXPECT_EQ(context_extensions.at("base_setting"), "from_base"); - EXPECT_EQ(context_extensions.at("override_setting"), "from_override"); -} - -TEST_F(ExtAuthzFilterHttpTest, PerRouteServiceTypeSwitchingHttpToGrpc) { - // Test that we can switch from HTTP service to gRPC service (reverse of the other test) - const std::string less_specific_config_yaml = R"EOF( - check_settings: - context_extensions: - base_setting: "from_base" - http_service: - server_uri: - uri: "https://ext-authz-http.example.com" - cluster: "ext_authz_http_cluster" - timeout: 1s - path_prefix: "/auth" - )EOF"; - - const std::string more_specific_config_yaml = R"EOF( - check_settings: - context_extensions: - override_setting: "from_override" - grpc_service: - envoy_grpc: - cluster_name: "ext_authz_grpc_cluster" - authority: "ext-authz.example.com" - timeout: 5s - )EOF"; - - ExtAuthzFilterConfig factory; - - // Create less specific configuration with HTTP service - ProtobufTypes::MessagePtr less_specific_proto = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(less_specific_config_yaml, *less_specific_proto); - FilterConfigPerRoute less_specific_config( - *dynamic_cast( - less_specific_proto.get())); - - // Create more specific configuration with gRPC service - ProtobufTypes::MessagePtr more_specific_proto = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(more_specific_config_yaml, *more_specific_proto); - FilterConfigPerRoute more_specific_config( - *dynamic_cast( - more_specific_proto.get())); - - // Merge configurations - should use gRPC service from more specific config - FilterConfigPerRoute merged_config(less_specific_config, more_specific_config); - - // Verify that gRPC service from more specific config is used (service type switching) - EXPECT_TRUE(merged_config.grpcService().has_value()); - EXPECT_FALSE(merged_config.httpService().has_value()); - - const auto& grpc_service = merged_config.grpcService().value(); - EXPECT_TRUE(grpc_service.has_envoy_grpc()); - EXPECT_EQ(grpc_service.envoy_grpc().cluster_name(), "ext_authz_grpc_cluster"); - EXPECT_EQ(grpc_service.envoy_grpc().authority(), "ext-authz.example.com"); - EXPECT_EQ(grpc_service.timeout().seconds(), 5); - - // Verify context extensions are properly merged - const auto& context_extensions = merged_config.contextExtensions(); - EXPECT_EQ(context_extensions.size(), 2); - EXPECT_EQ(context_extensions.at("base_setting"), "from_base"); - EXPECT_EQ(context_extensions.at("override_setting"), "from_override"); -} - -TEST_F(ExtAuthzFilterHttpTest, PerRouteHttpServiceWithTimeout) { - // Test HTTP service configuration with custom timeout - const std::string per_route_config_yaml = R"EOF( - check_settings: - http_service: - server_uri: - uri: "https://ext-authz-custom.example.com" - cluster: "ext_authz_custom_cluster" - timeout: 10s - path_prefix: "/custom/auth" - )EOF"; - - ExtAuthzFilterConfig factory; - ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(per_route_config_yaml, *proto_config); - - testing::NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); - auto route_config = factory.createRouteSpecificFilterConfig(*proto_config, context, - context.messageValidationVisitor()); - EXPECT_TRUE(route_config.ok()); - - const auto& typed_config = dynamic_cast(*route_config.value()); - EXPECT_TRUE(typed_config.httpService().has_value()); - EXPECT_FALSE(typed_config.grpcService().has_value()); - - const auto& http_service = typed_config.httpService().value(); - EXPECT_EQ(http_service.server_uri().uri(), "https://ext-authz-custom.example.com"); - EXPECT_EQ(http_service.server_uri().cluster(), "ext_authz_custom_cluster"); - EXPECT_EQ(http_service.server_uri().timeout().seconds(), 10); - EXPECT_EQ(http_service.path_prefix(), "/custom/auth"); -} - -TEST_F(ExtAuthzFilterHttpTest, PerRouteGrpcServiceWithTimeout) { - // Test gRPC service configuration with custom timeout - const std::string per_route_config_yaml = R"EOF( - check_settings: - grpc_service: - envoy_grpc: - cluster_name: "ext_authz_custom_grpc" - authority: "custom-ext-authz.example.com" - timeout: 15s - )EOF"; - - ExtAuthzFilterConfig factory; - ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(per_route_config_yaml, *proto_config); - - testing::NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); - auto route_config = factory.createRouteSpecificFilterConfig(*proto_config, context, - context.messageValidationVisitor()); - EXPECT_TRUE(route_config.ok()); - - const auto& typed_config = dynamic_cast(*route_config.value()); - EXPECT_TRUE(typed_config.grpcService().has_value()); - EXPECT_FALSE(typed_config.httpService().has_value()); - - const auto& grpc_service = typed_config.grpcService().value(); - EXPECT_TRUE(grpc_service.has_envoy_grpc()); - EXPECT_EQ(grpc_service.envoy_grpc().cluster_name(), "ext_authz_custom_grpc"); - EXPECT_EQ(grpc_service.envoy_grpc().authority(), "custom-ext-authz.example.com"); - EXPECT_EQ(grpc_service.timeout().seconds(), 15); -} - -TEST_F(ExtAuthzFilterHttpTest, PerRouteEmptyContextExtensionsMerging) { - // Test merging when one config has empty context extensions - const std::string less_specific_config_yaml = R"EOF( - check_settings: - context_extensions: - base_key: "base_value" - shared_key: "base_shared" - grpc_service: - envoy_grpc: - cluster_name: "base_cluster" - )EOF"; - - const std::string more_specific_config_yaml = R"EOF( - check_settings: - grpc_service: - envoy_grpc: - cluster_name: "specific_cluster" - )EOF"; - - ExtAuthzFilterConfig factory; - - ProtobufTypes::MessagePtr less_specific_proto = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(less_specific_config_yaml, *less_specific_proto); - FilterConfigPerRoute less_specific_config( - *dynamic_cast( - less_specific_proto.get())); - - ProtobufTypes::MessagePtr more_specific_proto = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(more_specific_config_yaml, *more_specific_proto); - FilterConfigPerRoute more_specific_config( - *dynamic_cast( - more_specific_proto.get())); - - FilterConfigPerRoute merged_config(less_specific_config, more_specific_config); - - // Should use gRPC service from more specific - EXPECT_TRUE(merged_config.grpcService().has_value()); - EXPECT_EQ(merged_config.grpcService().value().envoy_grpc().cluster_name(), "specific_cluster"); - - // Should preserve context extensions from less specific since more specific has none - const auto& context_extensions = merged_config.contextExtensions(); - EXPECT_EQ(context_extensions.size(), 2); - EXPECT_EQ(context_extensions.at("base_key"), "base_value"); - EXPECT_EQ(context_extensions.at("shared_key"), "base_shared"); -} - -TEST_F(ExtAuthzFilterHttpTest, PerRouteGrpcServiceConfigurationMerging) { - // Test merging of per-route configurations - const std::string less_specific_config_yaml = R"EOF( - check_settings: - context_extensions: - virtual_host: "my_virtual_host" - shared_setting: "from_less_specific" - grpc_service: - envoy_grpc: - cluster_name: "ext_authz_default" - )EOF"; - - const std::string more_specific_config_yaml = R"EOF( - check_settings: - context_extensions: - route_type: "high_qps" - shared_setting: "from_more_specific" - grpc_service: - envoy_grpc: - cluster_name: "ext_authz_high_qps" - )EOF"; - - ExtAuthzFilterConfig factory; - - // Create less specific configuration - ProtobufTypes::MessagePtr less_specific_proto = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(less_specific_config_yaml, *less_specific_proto); - FilterConfigPerRoute less_specific_config( - *dynamic_cast( - less_specific_proto.get())); - - // Create more specific configuration - ProtobufTypes::MessagePtr more_specific_proto = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(more_specific_config_yaml, *more_specific_proto); - FilterConfigPerRoute more_specific_config( - *dynamic_cast( - more_specific_proto.get())); - - // Merge configurations - FilterConfigPerRoute merged_config(less_specific_config, more_specific_config); - - // Check that more specific gRPC service is used - EXPECT_TRUE(merged_config.grpcService().has_value()); - const auto& grpc_service = merged_config.grpcService().value(); - EXPECT_TRUE(grpc_service.has_envoy_grpc()); - EXPECT_EQ(grpc_service.envoy_grpc().cluster_name(), "ext_authz_high_qps"); - - // Check that context extensions are properly merged - const auto& context_extensions = merged_config.contextExtensions(); - EXPECT_EQ(context_extensions.at("virtual_host"), "my_virtual_host"); - EXPECT_EQ(context_extensions.at("route_type"), "high_qps"); - EXPECT_EQ(context_extensions.at("shared_setting"), "from_more_specific"); -} - -TEST_F(ExtAuthzFilterHttpTest, PerRouteGrpcServiceConfigurationWithoutGrpcService) { - const std::string per_route_config_yaml = R"EOF( - check_settings: - context_extensions: - virtual_host: "my_virtual_host" - disable_request_body_buffering: true - )EOF"; - - ExtAuthzFilterConfig factory; - ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(per_route_config_yaml, *proto_config); - - testing::NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); - auto route_config = factory.createRouteSpecificFilterConfig(*proto_config, context, - context.messageValidationVisitor()); - EXPECT_TRUE(route_config.ok()); - - const auto& typed_config = dynamic_cast(*route_config.value()); - EXPECT_FALSE(typed_config.disabled()); - EXPECT_FALSE(typed_config.grpcService().has_value()); - - const auto& context_extensions = typed_config.contextExtensions(); - EXPECT_EQ(context_extensions.at("virtual_host"), "my_virtual_host"); -} - -TEST_F(ExtAuthzFilterHttpTest, PerRouteGrpcServiceConfigurationDisabled) { - const std::string per_route_config_yaml = R"EOF( - disabled: true - )EOF"; - - ExtAuthzFilterConfig factory; - ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); - TestUtility::loadFromYaml(per_route_config_yaml, *proto_config); - - testing::NiceMock context; - EXPECT_CALL(context, messageValidationVisitor()); - auto route_config = factory.createRouteSpecificFilterConfig(*proto_config, context, - context.messageValidationVisitor()); - EXPECT_TRUE(route_config.ok()); - - const auto& typed_config = dynamic_cast(*route_config.value()); - EXPECT_TRUE(typed_config.disabled()); - EXPECT_FALSE(typed_config.grpcService().has_value()); -} - class ExtAuthzFilterGrpcTest : public ExtAuthzFilterTest { public: void testFilterFactoryAndFilterWithGrpcClient(const std::string& ext_authz_config_yaml) { diff --git a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc index 9eb2348bdbafd..e072262e484b5 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc @@ -5,7 +5,6 @@ #include "source/common/common/macros.h" #include "source/extensions/filters/common/ext_authz/ext_authz.h" -#include "source/extensions/filters/http/ext_authz/ext_authz.h" #include "source/server/config_validation/server.h" #include "test/common/grpc/grpc_client_integration.h" @@ -984,32 +983,6 @@ INSTANTIATE_TEST_SUITE_P(IpVersionsCientType, ExtAuthzGrpcIntegrationTest, testing::Bool()), ExtAuthzGrpcIntegrationTest::testParamsToString); -// Test per-route gRPC service configuration parsing -TEST_P(ExtAuthzGrpcIntegrationTest, PerRouteGrpcServiceConfigurationParsing) { - // Create a simple per-route configuration with gRPC service - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - per_route_config.mutable_check_settings() - ->mutable_grpc_service() - ->mutable_envoy_grpc() - ->set_cluster_name("per_route_cluster"); - (*per_route_config.mutable_check_settings()->mutable_context_extensions())["route_type"] = - "special"; - - // Test configuration parsing and validation - Envoy::Extensions::HttpFilters::ExtAuthz::FilterConfigPerRoute config_per_route(per_route_config); - - // Verify the configuration was parsed correctly - ASSERT_TRUE(config_per_route.grpcService().has_value()); - EXPECT_TRUE(config_per_route.grpcService().value().has_envoy_grpc()); - EXPECT_EQ(config_per_route.grpcService().value().envoy_grpc().cluster_name(), - "per_route_cluster"); - - // Verify context extensions are present - const auto& check_settings = config_per_route.checkSettings(); - ASSERT_TRUE(check_settings.context_extensions().contains("route_type")); - EXPECT_EQ(check_settings.context_extensions().at("route_type"), "special"); -} - // Verifies that the request body is included in the CheckRequest when the downstream protocol is // HTTP/1.1. TEST_P(ExtAuthzGrpcIntegrationTest, HTTP1DownstreamRequestWithBody) { diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index 9d5fa891fb18c..129798b8cefb5 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -71,8 +71,7 @@ template class HttpFilterTestBase : public T { config_ = std::make_shared(proto_config, *stats_store_.rootScope(), "ext_authz_prefix", factory_context_); client_ = new NiceMock(); - filter_ = std::make_unique(config_, Filters::Common::ExtAuthz::ClientPtr{client_}, - factory_context_); + filter_ = std::make_unique(config_, Filters::Common::ExtAuthz::ClientPtr{client_}); ON_CALL(decoder_filter_callbacks_, filterConfigName()).WillByDefault(Return(FilterConfigName)); filter_->setDecoderFilterCallbacks(decoder_filter_callbacks_); filter_->setEncoderFilterCallbacks(encoder_filter_callbacks_); @@ -440,13 +439,9 @@ class InvalidMutationTest : public HttpFilterTestBase { EXPECT_EQ(1U, config_->stats().invalid_.value()); } - static constexpr const char* invalid_key_ = "invalid-\nkey"; - static constexpr uint8_t invalid_value_bytes_[3]{0x7f, 0x7f, 0}; + const std::string invalid_key_ = "invalid-\nkey"; + const uint8_t invalid_value_bytes_[3]{0x7f, 0x7f, 0}; const std::string invalid_value_; - - static std::string getInvalidValue() { - return std::string(reinterpret_cast(invalid_value_bytes_)); - } }; TEST_F(HttpFilterTest, DisableDynamicMetadataIngestion) { @@ -489,152 +484,142 @@ TEST_F(HttpFilterTest, DisableDynamicMetadataIngestion) { // Tests that the filter rejects authz responses with mutations with an invalid key when // validate_authz_response is set to true in config. -// Parameterized test for invalid mutation scenarios to reduce redundancy. -class InvalidMutationParamTest - : public InvalidMutationTest, - public testing::WithParamInterface< - std::tuple, // setup func - Filters::Common::ExtAuthz::CheckStatus // status - >> {}; - -TEST_P(InvalidMutationParamTest, InvalidMutationFields) { - const auto& [test_name, setup_func, status] = GetParam(); - +TEST_F(InvalidMutationTest, HeadersToSetKey) { Filters::Common::ExtAuthz::Response response; - response.status = status; - setup_func(response); + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.headers_to_set = {{invalid_key_, "bar"}}; testResponse(response); } -INSTANTIATE_TEST_SUITE_P( - InvalidMutationScenarios, InvalidMutationParamTest, - testing::Values( - // Invalid key tests - std::make_tuple( - "HeadersToSetKey", - [](Filters::Common::ExtAuthz::Response& r) { - r.headers_to_set = {{InvalidMutationTest::invalid_key_, "bar"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "HeadersToAddKey", - [](Filters::Common::ExtAuthz::Response& r) { - r.headers_to_add = {{InvalidMutationTest::invalid_key_, "bar"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "HeadersToSetKeyDenied", - [](Filters::Common::ExtAuthz::Response& r) { - r.headers_to_set = {{InvalidMutationTest::invalid_key_, "bar"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::Denied), - std::make_tuple( - "HeadersToAppendKey", - [](Filters::Common::ExtAuthz::Response& r) { - r.headers_to_append = {{InvalidMutationTest::invalid_key_, "bar"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "ResponseHeadersToAddKey", - [](Filters::Common::ExtAuthz::Response& r) { - r.response_headers_to_set = {{InvalidMutationTest::invalid_key_, "bar"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "ResponseHeadersToSetKey", - [](Filters::Common::ExtAuthz::Response& r) { - r.response_headers_to_set = {{InvalidMutationTest::invalid_key_, "bar"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "ResponseHeadersToAddIfAbsentKey", - [](Filters::Common::ExtAuthz::Response& r) { - r.response_headers_to_add_if_absent = {{InvalidMutationTest::invalid_key_, "bar"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "ResponseHeadersToOverwriteIfExistsKey", - [](Filters::Common::ExtAuthz::Response& r) { - r.response_headers_to_overwrite_if_exists = { - {InvalidMutationTest::invalid_key_, "bar"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "QueryParametersToSetKey", - [](Filters::Common::ExtAuthz::Response& r) { - r.query_parameters_to_set = {{"f o o", "bar"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - // Invalid value tests - std::make_tuple( - "HeadersToSetValue", - [](Filters::Common::ExtAuthz::Response& r) { - r.headers_to_set = {{"foo", InvalidMutationTest::getInvalidValue()}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "HeadersToAddValue", - [](Filters::Common::ExtAuthz::Response& r) { - r.headers_to_add = {{"foo", InvalidMutationTest::getInvalidValue()}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "HeadersToSetValueDenied", - [](Filters::Common::ExtAuthz::Response& r) { - r.headers_to_set = {{"foo", InvalidMutationTest::getInvalidValue()}}; - }, - Filters::Common::ExtAuthz::CheckStatus::Denied), - std::make_tuple( - "HeadersToAppendValue", - [](Filters::Common::ExtAuthz::Response& r) { - r.headers_to_append = {{"foo", InvalidMutationTest::getInvalidValue()}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "ResponseHeadersToAddValue", - [](Filters::Common::ExtAuthz::Response& r) { - r.response_headers_to_set = {{"foo", InvalidMutationTest::getInvalidValue()}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "ResponseHeadersToSetValue", - [](Filters::Common::ExtAuthz::Response& r) { - r.response_headers_to_set = {{"foo", InvalidMutationTest::getInvalidValue()}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "ResponseHeadersToAddIfAbsentValue", - [](Filters::Common::ExtAuthz::Response& r) { - r.response_headers_to_add_if_absent = { - {"foo", InvalidMutationTest::getInvalidValue()}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "ResponseHeadersToOverwriteIfExistsValue", - [](Filters::Common::ExtAuthz::Response& r) { - r.response_headers_to_overwrite_if_exists = { - {"foo", InvalidMutationTest::getInvalidValue()}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK), - std::make_tuple( - "QueryParametersToSetValue", - [](Filters::Common::ExtAuthz::Response& r) { - r.query_parameters_to_set = {{"foo", "b a r"}}; - }, - Filters::Common::ExtAuthz::CheckStatus::OK)), - [](const testing::TestParamInfo& info) { - return std::get<0>(info.param); - }); - -// Keep one simple focused test to ensure backward compatibility. -TEST_F(InvalidMutationTest, BasicInvalidKey) { +// Same as above, setting a different field... +TEST_F(InvalidMutationTest, HeadersToAddKey) { Filters::Common::ExtAuthz::Response response; response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.headers_to_add = {{invalid_key_, "bar"}}; + testResponse(response); +} + +// headers_to_set is also used when the authz response has status denied. +TEST_F(InvalidMutationTest, HeadersToSetKeyDenied) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::Denied; response.headers_to_set = {{invalid_key_, "bar"}}; testResponse(response); } +TEST_F(InvalidMutationTest, HeadersToAppendKey) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.headers_to_append = {{invalid_key_, "bar"}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, ResponseHeadersToAddKey) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.response_headers_to_set = {{invalid_key_, "bar"}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, ResponseHeadersToSetKey) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.response_headers_to_set = {{invalid_key_, "bar"}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, ResponseHeadersToAddIfAbsentKey) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.response_headers_to_add_if_absent = {{invalid_key_, "bar"}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, ResponseHeadersToOverwriteIfExistsKey) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.response_headers_to_overwrite_if_exists = {{invalid_key_, "bar"}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, QueryParametersToSetKey) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.query_parameters_to_set = {{"f o o", "bar"}}; + testResponse(response); +} + +// Test that the filter rejects mutations with an invalid value +TEST_F(InvalidMutationTest, HeadersToSetValueOk) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.headers_to_set = {{"foo", invalid_value_}}; + testResponse(response); +} + +// Same as above, setting a different field... +TEST_F(InvalidMutationTest, HeadersToAddValue) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.headers_to_add = {{"foo", invalid_value_}}; + testResponse(response); +} + +// headers_to_set is also used when the authz response has status denied. +TEST_F(InvalidMutationTest, HeadersToSetValueDenied) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::Denied; + response.headers_to_set = {{"foo", invalid_value_}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, HeadersToAppendValue) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + response.headers_to_append = {{"foo", invalid_value_}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, ResponseHeadersToAddValue) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + // Add a valid header to see if it gets added to the downstream response. + response.response_headers_to_set = {{"foo", invalid_value_}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, ResponseHeadersToSetValue) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + // Add a valid header to see if it gets added to the downstream response. + response.response_headers_to_set = {{"foo", invalid_value_}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, ResponseHeadersToAddIfAbsentValue) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + // Add a valid header to see if it gets added to the downstream response. + response.response_headers_to_add_if_absent = {{"foo", invalid_value_}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, ResponseHeadersToOverwriteIfExistsValue) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + // Add a valid header to see if it gets added to the downstream response. + response.response_headers_to_overwrite_if_exists = {{"foo", invalid_value_}}; + testResponse(response); +} + +TEST_F(InvalidMutationTest, QueryParametersToSetValue) { + Filters::Common::ExtAuthz::Response response; + response.status = Filters::Common::ExtAuthz::CheckStatus::OK; + // Add a valid header to see if it gets added to the downstream response. + response.query_parameters_to_set = {{"foo", "b a r"}}; + testResponse(response); +} + struct DecoderHeaderMutationRulesTestOpts { absl::optional rules; bool expect_reject_response = false; @@ -839,55 +824,68 @@ TEST_F(DecoderHeaderMutationRulesTest, DisallowAll) { runTest(opts); } -// Consolidated rejection test that covers all the scenarios previously tested individually. -TEST_F(DecoderHeaderMutationRulesTest, RejectResponseOperations) { - // Test data structure for all rejection scenarios - struct TestCase { - std::string name; - bool use_disallow_all; - std::function setup_func; - }; +TEST_F(DecoderHeaderMutationRulesTest, RejectResponseAdd) { + DecoderHeaderMutationRulesTestOpts opts; + opts.rules = envoy::config::common::mutation_rules::v3::HeaderMutationRules(); + opts.rules->mutable_disallow_all()->set_value(true); + opts.rules->mutable_disallow_is_error()->set_value(true); + opts.expect_reject_response = true; - std::vector test_cases = { - {"RejectResponseAdd", true, - [](DecoderHeaderMutationRulesTestOpts& opts) { - opts.disallowed_headers_to_add = {{"cant-add-me", "sad"}}; - }}, - {"RejectResponseAppend", true, - [](DecoderHeaderMutationRulesTestOpts& opts) { - opts.disallowed_headers_to_append = {{"cant-append-to-me", "fail"}}; - }}, - {"RejectResponseAppendPseudoheader", false, - [](DecoderHeaderMutationRulesTestOpts& opts) { - opts.disallowed_headers_to_append = {{":fake-pseudo-header", "fail"}}; - }}, - {"RejectResponseSet", true, - [](DecoderHeaderMutationRulesTestOpts& opts) { - opts.disallowed_headers_to_set = {{"cant-override-me", "nope"}}; - }}, - {"RejectResponseRemove", true, - [](DecoderHeaderMutationRulesTestOpts& opts) { - opts.disallowed_headers_to_remove = {"cant-delete-me"}; - }}, - {"RejectResponseRemovePseudoHeader", false, [](DecoderHeaderMutationRulesTestOpts& opts) { - opts.disallowed_headers_to_remove = {":fake-pseudo-header"}; - }}}; - - // Run all test cases - for (const auto& test_case : test_cases) { - SCOPED_TRACE(test_case.name); - - DecoderHeaderMutationRulesTestOpts opts; - opts.rules = envoy::config::common::mutation_rules::v3::HeaderMutationRules(); - if (test_case.use_disallow_all) { - opts.rules->mutable_disallow_all()->set_value(true); - } - opts.rules->mutable_disallow_is_error()->set_value(true); - opts.expect_reject_response = true; + opts.disallowed_headers_to_add = {{"cant-add-me", "sad"}}; + runTest(opts); +} - test_case.setup_func(opts); - runTest(opts); - } +TEST_F(DecoderHeaderMutationRulesTest, RejectResponseAppend) { + DecoderHeaderMutationRulesTestOpts opts; + opts.rules = envoy::config::common::mutation_rules::v3::HeaderMutationRules(); + opts.rules->mutable_disallow_all()->set_value(true); + opts.rules->mutable_disallow_is_error()->set_value(true); + opts.expect_reject_response = true; + + opts.disallowed_headers_to_append = {{"cant-append-to-me", "fail"}}; + runTest(opts); +} + +TEST_F(DecoderHeaderMutationRulesTest, RejectResponseAppendPseudoheader) { + DecoderHeaderMutationRulesTestOpts opts; + opts.rules = envoy::config::common::mutation_rules::v3::HeaderMutationRules(); + opts.rules->mutable_disallow_is_error()->set_value(true); + opts.expect_reject_response = true; + + opts.disallowed_headers_to_append = {{":fake-pseudo-header", "fail"}}; + runTest(opts); +} + +TEST_F(DecoderHeaderMutationRulesTest, RejectResponseSet) { + DecoderHeaderMutationRulesTestOpts opts; + opts.rules = envoy::config::common::mutation_rules::v3::HeaderMutationRules(); + opts.rules->mutable_disallow_all()->set_value(true); + opts.rules->mutable_disallow_is_error()->set_value(true); + opts.expect_reject_response = true; + + opts.disallowed_headers_to_set = {{"cant-override-me", "nope"}}; + runTest(opts); +} + +TEST_F(DecoderHeaderMutationRulesTest, RejectResponseRemove) { + DecoderHeaderMutationRulesTestOpts opts; + opts.rules = envoy::config::common::mutation_rules::v3::HeaderMutationRules(); + opts.rules->mutable_disallow_all()->set_value(true); + opts.rules->mutable_disallow_is_error()->set_value(true); + opts.expect_reject_response = true; + + opts.disallowed_headers_to_remove = {"cant-delete-me"}; + runTest(opts); +} + +TEST_F(DecoderHeaderMutationRulesTest, RejectResponseRemovePseudoHeader) { + DecoderHeaderMutationRulesTestOpts opts; + opts.rules = envoy::config::common::mutation_rules::v3::HeaderMutationRules(); + opts.rules->mutable_disallow_is_error()->set_value(true); + opts.expect_reject_response = true; + + opts.disallowed_headers_to_remove = {":fake-pseudo-header"}; + runTest(opts); } TEST_F(DecoderHeaderMutationRulesTest, DisallowExpression) { @@ -2830,20 +2828,19 @@ TEST_P(HttpFilterTestParam, ContextExtensions) { // Test that filter can be disabled with route config. TEST_P(HttpFilterTestParam, DisabledOnRoute) { envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute settings; - std::unique_ptr auth_per_route = - std::make_unique(settings); + FilterConfigPerRoute auth_per_route(settings); prepareCheck(); + ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) + .WillByDefault(Return(&auth_per_route)); + auto test_disable = [&](bool disabled) { initialize(""); // Set disabled settings.set_disabled(disabled); // Initialize the route's per filter config. - auth_per_route = std::make_unique(settings); - // Update the mock to return the new pointer - ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) - .WillByDefault(Return(auth_per_route.get())); + auth_per_route = FilterConfigPerRoute(settings); }; // baseline: make sure that when not disabled, check is called @@ -2864,8 +2861,10 @@ TEST_P(HttpFilterTestParam, DisabledOnRoute) { // Test that filter can be disabled with route config. TEST_P(HttpFilterTestParam, DisabledOnRouteWithRequestBody) { envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute settings; - std::unique_ptr auth_per_route = - std::make_unique(settings); + FilterConfigPerRoute auth_per_route(settings); + + ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) + .WillByDefault(Return(&auth_per_route)); auto test_disable = [&](bool disabled) { initialize(R"EOF( @@ -2881,10 +2880,7 @@ TEST_P(HttpFilterTestParam, DisabledOnRouteWithRequestBody) { // Set the filter disabled setting. settings.set_disabled(disabled); // Initialize the route's per filter config. - auth_per_route = std::make_unique(settings); - // Update the mock to return the new pointer. - ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) - .WillByDefault(Return(auth_per_route.get())); + auth_per_route = FilterConfigPerRoute(settings); }; test_disable(false); @@ -3918,31 +3914,6 @@ TEST_F(HttpFilterTest, PerRouteCheckSettingsWorks) { } // Checks that the per-route filter can override the check_settings set on the main filter. -TEST_F(HttpFilterTest, NullRouteSkipsCheck) { - initialize(R"EOF( - grpc_service: - envoy_grpc: - cluster_name: "ext_authz_server" - failure_mode_allow: false - stat_prefix: "ext_authz" - )EOF"); - - prepareCheck(); - - // Set up a null route return value. - ON_CALL(decoder_filter_callbacks_, route()).WillByDefault(Return(nullptr)); - - // With null route, no authorization check should be performed. - EXPECT_CALL(*client_, check(_, _, _, _)).Times(0); - - // Call the filter directly. - Http::TestRequestHeaderMapImpl request_headers{ - {":method", "GET"}, {":path", "/test"}, {":scheme", "http"}, {"host", "example.com"}}; - - // With null route, the filter should continue without an auth check. - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); -} - TEST_F(HttpFilterTest, PerRouteCheckSettingsOverrideWorks) { InSequence s; @@ -4004,8 +3975,10 @@ TEST_F(HttpFilterTest, PerRouteCheckSettingsOverrideWorks) { // Verify that request body buffering can be skipped per route. TEST_P(HttpFilterTestParam, DisableRequestBodyBufferingOnRoute) { envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute settings; - std::unique_ptr auth_per_route = - std::make_unique(settings); + FilterConfigPerRoute auth_per_route(settings); + + ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) + .WillByDefault(Return(&auth_per_route)); auto test_disable_request_body_buffering = [&](bool bypass) { initialize(R"EOF( @@ -4021,10 +3994,7 @@ TEST_P(HttpFilterTestParam, DisableRequestBodyBufferingOnRoute) { // Set bypass request body buffering for this route. settings.mutable_check_settings()->set_disable_request_body_buffering(bypass); // Initialize the route's per filter config. - auth_per_route = std::make_unique(settings); - // Update the mock to return the new pointer. - ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) - .WillByDefault(Return(auth_per_route.get())); + auth_per_route = FilterConfigPerRoute(settings); }; test_disable_request_body_buffering(false); @@ -4189,801 +4159,6 @@ TEST_P(EmitFilterStateTest, PreexistingFilterStateSameTypeMutable) { TEST_P(ExtAuthzLoggingInfoTest, FieldTest) { test(); } -// Test per-route gRPC service override with null server context (fallback to default client) -TEST_P(HttpFilterTestParam, PerRouteGrpcServiceOverrideWithNullServerContext) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test - per-route gRPC service only applies to gRPC clients - return; - } - - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - per_route_config.mutable_check_settings() - ->mutable_grpc_service() - ->mutable_envoy_grpc() - ->set_cluster_name("per_route_ext_authz_cluster"); - - std::unique_ptr per_route_filter_config = - std::make_unique(per_route_config); - - // Set up route to return per-route config - ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) - .WillByDefault(Return(per_route_filter_config.get())); - - prepareCheck(); - - // Mock the default client check call (should fall back to default since server context is null) - EXPECT_CALL(*client_, check(_, _, _, _)) - .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, - const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, - const StreamInfo::StreamInfo&) -> void { - Filters::Common::ExtAuthz::Response response{}; - response.status = Filters::Common::ExtAuthz::CheckStatus::OK; - callbacks.onComplete(std::make_unique(response)); - })); - - EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()).Times(0); - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); -} - -// Test per-route configuration merging with context extensions -TEST_P(HttpFilterTestParam, PerRouteConfigurationMergingWithContextExtensions) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test - configuration merging applies to gRPC clients - return; - } - - // Create base configuration with context extensions - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute base_config; - base_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"base_key", "base_value"}); - base_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"shared_key", "base_shared_value"}); - - // Create more specific configuration with context extensions - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute specific_config; - specific_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"specific_key", "specific_value"}); - specific_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"shared_key", "specific_shared_value"}); - - // Test merging using the merge constructor - FilterConfigPerRoute base_filter_config(base_config); - FilterConfigPerRoute specific_filter_config(specific_config); - FilterConfigPerRoute merged_config(base_filter_config, specific_filter_config); - - // Verify merged context extensions - const auto& merged_extensions = merged_config.contextExtensions(); - EXPECT_EQ(merged_extensions.size(), 3); - EXPECT_EQ(merged_extensions.at("base_key"), "base_value"); - EXPECT_EQ(merged_extensions.at("specific_key"), "specific_value"); - EXPECT_EQ(merged_extensions.at("shared_key"), "specific_shared_value"); // More specific wins -} - -// Test per-route configuration merging with gRPC service override -TEST_P(HttpFilterTestParam, PerRouteConfigurationMergingWithGrpcServiceOverride) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test - gRPC service override applies to gRPC clients - return; - } - - // Create base configuration without gRPC service - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute base_config; - base_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"base_key", "base_value"}); - - // Create more specific configuration with gRPC service - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute specific_config; - specific_config.mutable_check_settings() - ->mutable_grpc_service() - ->mutable_envoy_grpc() - ->set_cluster_name("specific_cluster"); - specific_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"specific_key", "specific_value"}); - - // Test merging using the merge constructor - FilterConfigPerRoute base_filter_config(base_config); - FilterConfigPerRoute specific_filter_config(specific_config); - FilterConfigPerRoute merged_config(base_filter_config, specific_filter_config); - - // Verify gRPC service override is from more specific config - EXPECT_TRUE(merged_config.grpcService().has_value()); - EXPECT_EQ(merged_config.grpcService().value().envoy_grpc().cluster_name(), "specific_cluster"); - - // Verify context extensions are merged - const auto& merged_extensions = merged_config.contextExtensions(); - EXPECT_EQ(merged_extensions.size(), 2); - EXPECT_EQ(merged_extensions.at("base_key"), "base_value"); - EXPECT_EQ(merged_extensions.at("specific_key"), "specific_value"); -} - -// Test per-route configuration merging with request body settings -TEST_P(HttpFilterTestParam, PerRouteConfigurationMergingWithRequestBodySettings) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test - request body settings apply to gRPC clients - return; - } - - // Create base configuration with request body settings - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute base_config; - base_config.mutable_check_settings()->mutable_with_request_body()->set_max_request_bytes(1000); - base_config.mutable_check_settings()->mutable_with_request_body()->set_allow_partial_message( - true); - - // Create more specific configuration with different request body settings - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute specific_config; - specific_config.mutable_check_settings()->mutable_with_request_body()->set_max_request_bytes( - 2000); - specific_config.mutable_check_settings()->mutable_with_request_body()->set_allow_partial_message( - false); - - // Test merging using the merge constructor - FilterConfigPerRoute base_filter_config(base_config); - FilterConfigPerRoute specific_filter_config(specific_config); - FilterConfigPerRoute merged_config(base_filter_config, specific_filter_config); - - // Verify request body settings are from more specific config - const auto& merged_check_settings = merged_config.checkSettings(); - EXPECT_TRUE(merged_check_settings.has_with_request_body()); - EXPECT_EQ(merged_check_settings.with_request_body().max_request_bytes(), 2000); - EXPECT_EQ(merged_check_settings.with_request_body().allow_partial_message(), false); -} - -// Test per-route configuration merging with disable_request_body_buffering -TEST_P(HttpFilterTestParam, PerRouteConfigurationMergingWithDisableRequestBodyBuffering) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test - disable request body buffering applies to gRPC clients - return; - } - - // Create base configuration without disable_request_body_buffering - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute base_config; - base_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"base_key", "base_value"}); - - // Create more specific configuration with disable_request_body_buffering - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute specific_config; - specific_config.mutable_check_settings()->set_disable_request_body_buffering(true); - - // Test merging using the merge constructor - FilterConfigPerRoute base_filter_config(base_config); - FilterConfigPerRoute specific_filter_config(specific_config); - FilterConfigPerRoute merged_config(base_filter_config, specific_filter_config); - - // Verify disable_request_body_buffering is from more specific config - const auto& merged_check_settings = merged_config.checkSettings(); - EXPECT_TRUE(merged_check_settings.disable_request_body_buffering()); -} - -// Test per-route configuration merging with multiple levels -TEST_P(HttpFilterTestParam, PerRouteConfigurationMergingMultipleLevels) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test - configuration merging applies to gRPC clients - return; - } - - // Create virtual host level configuration - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute vh_config; - vh_config.mutable_check_settings()->mutable_context_extensions()->insert({"vh_key", "vh_value"}); - vh_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"shared_key", "vh_shared_value"}); - - // Create route level configuration - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute route_config; - route_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"route_key", "route_value"}); - route_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"shared_key", "route_shared_value"}); - route_config.mutable_check_settings() - ->mutable_grpc_service() - ->mutable_envoy_grpc() - ->set_cluster_name("route_cluster"); - - // Create weighted cluster level configuration - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute wc_config; - wc_config.mutable_check_settings()->mutable_context_extensions()->insert({"wc_key", "wc_value"}); - wc_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"shared_key", "wc_shared_value"}); - - // Test merging from least specific to most specific - FilterConfigPerRoute vh_filter_config(vh_config); - FilterConfigPerRoute route_filter_config(route_config); - FilterConfigPerRoute wc_filter_config(wc_config); - - // First merge: vh + route - FilterConfigPerRoute vh_route_merged(vh_filter_config, route_filter_config); - - // Second merge: (vh + route) + weighted cluster - FilterConfigPerRoute final_merged(vh_route_merged, wc_filter_config); - - // Verify final merged context extensions - const auto& merged_extensions = final_merged.contextExtensions(); - EXPECT_EQ(merged_extensions.size(), 4); - EXPECT_EQ(merged_extensions.at("vh_key"), "vh_value"); - EXPECT_EQ(merged_extensions.at("route_key"), "route_value"); - EXPECT_EQ(merged_extensions.at("wc_key"), "wc_value"); - EXPECT_EQ(merged_extensions.at("shared_key"), "wc_shared_value"); // Most specific wins - - // Verify gRPC service override is NOT inherited from less specific levels. - EXPECT_FALSE(final_merged.grpcService().has_value()); -} - -// Test per-route context extensions take precedence over check_settings context extensions. -TEST_P(HttpFilterTestParam, PerRouteContextExtensionsPrecedence) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test as context extensions apply to gRPC clients. - return; - } - - // Create configuration with context extensions in both places. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute base_config; - base_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"check_key", "check_value"}); - base_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"shared_key", "check_shared_value"}); - - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute specific_config; - specific_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"specific_check_key", "specific_check_value"}); - specific_config.mutable_check_settings()->mutable_context_extensions()->insert( - {"shared_key", "specific_check_shared_value"}); - - // Test merging using the merge constructor. - FilterConfigPerRoute base_filter_config(base_config); - FilterConfigPerRoute specific_filter_config(specific_config); - FilterConfigPerRoute merged_config(base_filter_config, specific_filter_config); - - // Verify context extensions are properly merged. - const auto& merged_extensions = merged_config.contextExtensions(); - EXPECT_EQ(merged_extensions.size(), 3); - EXPECT_EQ(merged_extensions.at("check_key"), "check_value"); - EXPECT_EQ(merged_extensions.at("specific_check_key"), "specific_check_value"); - EXPECT_EQ(merged_extensions.at("shared_key"), - "specific_check_shared_value"); // More specific wins -} - -// Test per-route Google gRPC service configuration. -TEST_P(HttpFilterTestParam, PerRouteGoogleGrpcServiceConfiguration) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test as per-route gRPC service only applies to gRPC clients. - return; - } - - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - per_route_config.mutable_check_settings() - ->mutable_grpc_service() - ->mutable_google_grpc() - ->set_target_uri("https://ext-authz.googleapis.com"); - - std::unique_ptr per_route_filter_config = - std::make_unique(per_route_config); - - // Verify Google gRPC service is properly configured - EXPECT_TRUE(per_route_filter_config->grpcService().has_value()); - EXPECT_TRUE(per_route_filter_config->grpcService().value().has_google_grpc()); - EXPECT_EQ(per_route_filter_config->grpcService().value().google_grpc().target_uri(), - "https://ext-authz.googleapis.com"); -} - -// Test existing functionality still works with new logic. -TEST_P(HttpFilterTestParam, ExistingFunctionalityWithNewLogic) { - // Test that the existing functionality still works with our new per-route merging logic. - prepareCheck(); - - // Mock the default client check call (no per-route config). - EXPECT_CALL(*client_, check(_, _, _, _)) - .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, - const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, - const StreamInfo::StreamInfo&) -> void { - Filters::Common::ExtAuthz::Response response{}; - response.status = Filters::Common::ExtAuthz::CheckStatus::OK; - callbacks.onComplete(std::make_unique(response)); - })); - - EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()).Times(0); - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false)); -} - -// Test per-route configuration merging with empty configurations. -TEST_P(HttpFilterTestParam, PerRouteConfigurationMergingWithEmptyConfigurations) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test as configuration merging applies to gRPC clients. - return; - } - - // Create empty base configuration. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute base_config; - - // Create empty specific configuration. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute specific_config; - - // Test merging using the merge constructor. - FilterConfigPerRoute base_filter_config(base_config); - FilterConfigPerRoute specific_filter_config(specific_config); - FilterConfigPerRoute merged_config(base_filter_config, specific_filter_config); - - // Verify merged configuration has empty context extensions. - const auto& merged_extensions = merged_config.contextExtensions(); - EXPECT_EQ(merged_extensions.size(), 0); - - // Verify no gRPC service override - EXPECT_FALSE(merged_config.grpcService().has_value()); -} - -// Test per-route gRPC service configuration merging functionality. -TEST_P(HttpFilterTestParam, PerRouteGrpcServiceMergingWithBaseConfiguration) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test as per-route gRPC service only applies to gRPC clients. - return; - } - - // Create base per-route configuration. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute base_config; - (*base_config.mutable_check_settings()->mutable_context_extensions())["base"] = "value"; - FilterConfigPerRoute base_filter_config(base_config); - - // Create per-route configuration with gRPC service. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - per_route_config.mutable_check_settings() - ->mutable_grpc_service() - ->mutable_envoy_grpc() - ->set_cluster_name("per_route_cluster"); - (*per_route_config.mutable_check_settings()->mutable_context_extensions())["route"] = "override"; - - // Test merging constructor. - FilterConfigPerRoute merged_config(base_filter_config, per_route_config); - - // Verify the merged configuration has the gRPC service from the per-route config. - EXPECT_TRUE(merged_config.grpcService().has_value()); - EXPECT_TRUE(merged_config.grpcService().value().has_envoy_grpc()); - EXPECT_EQ(merged_config.grpcService().value().envoy_grpc().cluster_name(), "per_route_cluster"); - - // Verify that context extensions are properly merged. - const auto& merged_settings = merged_config.checkSettings(); - EXPECT_TRUE(merged_settings.context_extensions().contains("route")); - EXPECT_EQ(merged_settings.context_extensions().at("route"), "override"); -} - -// Test focused integration test to verify per-route configuration is processed correctly. -TEST_P(HttpFilterTestParam, PerRouteConfigurationIntegrationTest) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test - per-route gRPC service only applies to gRPC clients. - return; - } - - // This test covers the per-route configuration processing in initiateCall - // which exercises the lines where getAllPerFilterConfig is called and processed. - - // Set up per-route configuration with gRPC service override - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - per_route_config.mutable_check_settings() - ->mutable_grpc_service() - ->mutable_envoy_grpc() - ->set_cluster_name("per_route_cluster"); - - // Add context extensions to test that path too. - (*per_route_config.mutable_check_settings()->mutable_context_extensions())["test_key"] = - "test_value"; - - std::unique_ptr per_route_filter_config = - std::make_unique(per_route_config); - - // Mock decoder callbacks to return per-route config. - ON_CALL(decoder_filter_callbacks_, mostSpecificPerFilterConfig()) - .WillByDefault(Return(per_route_filter_config.get())); - - // Mock perFilterConfigs to return the per-route config vector. - Router::RouteSpecificFilterConfigs per_route_configs; - per_route_configs.push_back(per_route_filter_config.get()); - ON_CALL(decoder_filter_callbacks_, perFilterConfigs()).WillByDefault(Return(per_route_configs)); - - // Set up basic request headers. - Http::TestRequestHeaderMapImpl headers{ - {":method", "GET"}, {":path", "/test"}, {":scheme", "https"}, {"host", "example.com"}}; - - prepareCheck(); - - // Mock client check to capture and verify the check request has proper context extensions. - EXPECT_CALL(*client_, check(_, _, _, _)) - .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, - const envoy::service::auth::v3::CheckRequest& check_request, - Tracing::Span&, const StreamInfo::StreamInfo&) -> void { - // Verify that per-route context extensions were merged correctly - auto context_extensions = check_request.attributes().context_extensions(); - EXPECT_TRUE(context_extensions.contains("test_key")); - EXPECT_EQ(context_extensions.at("test_key"), "test_value"); - - // Return OK to complete the test - auto response = std::make_unique(); - response->status = Filters::Common::ExtAuthz::CheckStatus::OK; - callbacks.onComplete(std::move(response)); - })); - - // This exercises the per-route configuration processing logic which includes - // the getAllPerFilterConfig call and per-route gRPC service detection. - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); -} - -// Test per-route gRPC client creation and usage. -TEST_P(HttpFilterTestParam, PerRouteGrpcClientCreationAndUsage) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test as per-route gRPC service only applies to gRPC clients. - return; - } - - // Create per-route configuration with valid gRPC service. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - per_route_config.mutable_check_settings() - ->mutable_grpc_service() - ->mutable_envoy_grpc() - ->set_cluster_name("per_route_ext_authz_cluster"); - - // Add context extensions to test merging. - (*per_route_config.mutable_check_settings()->mutable_context_extensions())["test_key"] = - "test_value"; - - std::unique_ptr per_route_filter_config = - std::make_unique(per_route_config); - - // Set up route to return per-route config. - ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) - .WillByDefault(Return(per_route_filter_config.get())); - - // Mock perFilterConfigs to return the per-route config vector which exercises - // getAllPerFilterConfig. - Router::RouteSpecificFilterConfigs per_route_configs; - per_route_configs.push_back(per_route_filter_config.get()); - ON_CALL(decoder_filter_callbacks_, perFilterConfigs()).WillByDefault(Return(per_route_configs)); - - prepareCheck(); - - // Create a filter with server context for per-route gRPC client creation. - auto new_client = std::make_unique(); - auto* new_client_ptr = new_client.get(); - auto new_filter = std::make_unique(config_, std::move(new_client), factory_context_); - new_filter->setDecoderFilterCallbacks(decoder_filter_callbacks_); - - // Mock successful gRPC async client manager access. - auto mock_grpc_client_manager = std::make_shared(); - ON_CALL(factory_context_, clusterManager()).WillByDefault(ReturnRef(cm_)); - ON_CALL(cm_, grpcAsyncClientManager()).WillByDefault(ReturnRef(*mock_grpc_client_manager)); - - // Mock successful raw gRPC client creation which exercises createPerRouteGrpcClient. - auto mock_raw_grpc_client = std::make_shared(); - EXPECT_CALL(*mock_grpc_client_manager, getOrCreateRawAsyncClientWithHashKey(_, _, true)) - .WillOnce(Return(absl::StatusOr(mock_raw_grpc_client))); - - // Set up expectations for the sendRaw call that will be made by the GrpcClientImpl. - EXPECT_CALL(*mock_raw_grpc_client, sendRaw(_, _, _, _, _, _)) - .WillOnce( - Invoke([](absl::string_view /*service_full_name*/, absl::string_view /*method_name*/, - Buffer::InstancePtr&& /*request*/, Grpc::RawAsyncRequestCallbacks& callbacks, - Tracing::Span& parent_span, - const Http::AsyncClient::RequestOptions& /*options*/) -> Grpc::AsyncRequest* { - envoy::service::auth::v3::CheckResponse check_response; - check_response.mutable_status()->set_code(Grpc::Status::WellKnownGrpcStatus::Ok); - check_response.mutable_ok_response(); - - // Serialize the response to a buffer. - std::string serialized_response; - check_response.SerializeToString(&serialized_response); - auto response = std::make_unique(serialized_response); - - callbacks.onSuccessRaw(std::move(response), parent_span); - return nullptr; // No async request handle needed for immediate response. - })); - - // Since per-route gRPC client creation succeeds, the per-route client should be used - // instead of the default client. We won't see a call to new_client_ptr. - EXPECT_CALL(*new_client_ptr, check(_, _, _, _)).Times(0); - - EXPECT_EQ(Http::FilterHeadersStatus::Continue, - new_filter->decodeHeaders(request_headers_, false)); -} - -// Test per-route HTTP service configuration parsing. -TEST_P(HttpFilterTestParam, PerRouteHttpServiceConfigurationParsing) { - if (!std::get<1>(GetParam())) { - // Skip gRPC client test as per-route HTTP service only applies to HTTP clients. - return; - } - - // Create per-route configuration with valid HTTP service. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - per_route_config.mutable_check_settings()->mutable_http_service()->mutable_server_uri()->set_uri( - "https://per-route-ext-authz.example.com"); - per_route_config.mutable_check_settings() - ->mutable_http_service() - ->mutable_server_uri() - ->set_cluster("per_route_http_cluster"); - per_route_config.mutable_check_settings()->mutable_http_service()->set_path_prefix( - "/api/v2/auth"); - - std::unique_ptr per_route_filter_config = - std::make_unique(per_route_config); - - // Verify the per-route HTTP service configuration is correctly parsed - EXPECT_TRUE(per_route_filter_config->httpService().has_value()); - EXPECT_FALSE(per_route_filter_config->grpcService().has_value()); - - const auto& http_service = per_route_filter_config->httpService().value(); - EXPECT_EQ(http_service.server_uri().uri(), "https://per-route-ext-authz.example.com"); - EXPECT_EQ(http_service.server_uri().cluster(), "per_route_http_cluster"); - EXPECT_EQ(http_service.path_prefix(), "/api/v2/auth"); -} - -// Test error handling when server context is not available for per-route gRPC client. -TEST_P(HttpFilterTestParam, PerRouteGrpcClientCreationNoServerContext) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test - per-route gRPC service only applies to gRPC clients. - return; - } - - // Create per-route configuration with gRPC service. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - per_route_config.mutable_check_settings() - ->mutable_grpc_service() - ->mutable_envoy_grpc() - ->set_cluster_name("per_route_grpc_cluster"); - - std::unique_ptr per_route_filter_config = - std::make_unique(per_route_config); - - ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) - .WillByDefault(Return(per_route_filter_config.get())); - - Router::RouteSpecificFilterConfigs per_route_configs; - per_route_configs.push_back(per_route_filter_config.get()); - ON_CALL(decoder_filter_callbacks_, perFilterConfigs()).WillByDefault(Return(per_route_configs)); - - prepareCheck(); - - // Create filter without server context. This should cause per-route client creation to fail. - auto new_client = std::make_unique(); - auto* new_client_ptr = new_client.get(); - auto new_filter = std::make_unique(config_, std::move(new_client)); // No server context - new_filter->setDecoderFilterCallbacks(decoder_filter_callbacks_); - - // Since per-route client creation fails (no server context), should fall back to default client. - EXPECT_CALL(*new_client_ptr, check(_, _, _, _)) - .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, - const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, - const StreamInfo::StreamInfo&) -> void { - // Verify this is using the default client. - auto response = std::make_unique(); - response->status = Filters::Common::ExtAuthz::CheckStatus::OK; - callbacks.onComplete(std::move(response)); - })); - - Http::TestRequestHeaderMapImpl request_headers_{ - {":method", "GET"}, {":path", "/test"}, {":scheme", "http"}, {"host", "example.com"}}; - - EXPECT_EQ(Http::FilterHeadersStatus::Continue, - new_filter->decodeHeaders(request_headers_, false)); -} - -// Test error handling when server context is not available for per-route HTTP client. -TEST_P(HttpFilterTestParam, PerRouteHttpClientCreationNoServerContext) { - if (!std::get<1>(GetParam())) { - // Skip gRPC client test as per-route HTTP service only applies to HTTP clients. - return; - } - - // Create per-route configuration with HTTP service. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - per_route_config.mutable_check_settings()->mutable_http_service()->mutable_server_uri()->set_uri( - "https://per-route-ext-authz.example.com"); - per_route_config.mutable_check_settings() - ->mutable_http_service() - ->mutable_server_uri() - ->set_cluster("per_route_http_cluster"); - - std::unique_ptr per_route_filter_config = - std::make_unique(per_route_config); - - ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) - .WillByDefault(Return(per_route_filter_config.get())); - - Router::RouteSpecificFilterConfigs per_route_configs; - per_route_configs.push_back(per_route_filter_config.get()); - ON_CALL(decoder_filter_callbacks_, perFilterConfigs()).WillByDefault(Return(per_route_configs)); - - prepareCheck(); - - // Create filter without server context. - auto new_client = std::make_unique(); - auto* new_client_ptr = new_client.get(); - auto new_filter = std::make_unique(config_, std::move(new_client)); // No server context - new_filter->setDecoderFilterCallbacks(decoder_filter_callbacks_); - - // Since per-route client creation fails, should fall back to default client. - EXPECT_CALL(*new_client_ptr, check(_, _, _, _)) - .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, - const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, - const StreamInfo::StreamInfo&) -> void { - auto response = std::make_unique(); - response->status = Filters::Common::ExtAuthz::CheckStatus::OK; - callbacks.onComplete(std::move(response)); - })); - - Http::TestRequestHeaderMapImpl request_headers_{ - {":method", "GET"}, {":path", "/test"}, {":scheme", "http"}, {"host", "example.com"}}; - - EXPECT_EQ(Http::FilterHeadersStatus::Continue, - new_filter->decodeHeaders(request_headers_, false)); -} - -// Test gRPC client error handling for per-route config. -TEST_F(HttpFilterTest, GrpcClientPerRouteError) { - // Initialize with gRPC client configuration. - initialize(R"EOF( - grpc_service: - envoy_grpc: - cluster_name: "ext_authz_server" - failure_mode_allow: false - stat_prefix: "ext_authz" - )EOF"); - - prepareCheck(); - - // Create per-route configuration with gRPC service override. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - auto* grpc_service = per_route_config.mutable_check_settings()->mutable_grpc_service(); - grpc_service->mutable_envoy_grpc()->set_cluster_name("nonexistent_cluster"); - - FilterConfigPerRoute per_route_filter_config(per_route_config); - - // Set up route config to use the per-route configuration. - ON_CALL(decoder_filter_callbacks_, mostSpecificPerFilterConfig()) - .WillByDefault(Return(&per_route_filter_config)); - - // Since cluster doesn't exist, per-route client creation should fail - // and we'll use the default client instead. - EXPECT_CALL(*client_, check(_, _, _, _)) - .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, - const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, - const StreamInfo::StreamInfo&) -> void { - Filters::Common::ExtAuthz::Response response{}; - response.status = Filters::Common::ExtAuthz::CheckStatus::OK; - callbacks.onComplete(std::make_unique(response)); - })); - - // Verify filter processes the request with the default client. - Http::TestRequestHeaderMapImpl request_headers{ - {":method", "GET"}, {":path", "/test"}, {":scheme", "http"}, {"host", "example.com"}}; - - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); -} - -// Test HTTP client with per-route configuration. -TEST_F(HttpFilterTest, HttpClientPerRouteOverride) { - // Initialize with HTTP client configuration. - initialize(R"EOF( - http_service: - server_uri: - uri: "https://ext-authz.example.com" - cluster: "ext_authz_server" - path_prefix: "/api/v1/auth" - failure_mode_allow: false - stat_prefix: "ext_authz" - )EOF"); - - prepareCheck(); - - // Create per-route configuration with HTTP service override. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - auto* http_service = per_route_config.mutable_check_settings()->mutable_http_service(); - http_service->mutable_server_uri()->set_uri("https://per-route-ext-authz.example.com"); - http_service->mutable_server_uri()->set_cluster("per_route_http_cluster"); - http_service->set_path_prefix("/api/v2/auth"); - - FilterConfigPerRoute per_route_filter_config(per_route_config); - - // Set up route config to use the per-route configuration. - ON_CALL(decoder_filter_callbacks_, mostSpecificPerFilterConfig()) - .WillByDefault(Return(&per_route_filter_config)); - - // Set up a check expectation that will be satisfied by the default client. - EXPECT_CALL(*client_, check(_, _, _, _)) - .WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, - const envoy::service::auth::v3::CheckRequest&, Tracing::Span&, - const StreamInfo::StreamInfo&) -> void { - Filters::Common::ExtAuthz::Response response{}; - response.status = Filters::Common::ExtAuthz::CheckStatus::OK; - callbacks.onComplete(std::make_unique(response)); - })); - - // Verify filter processes the request. - Http::TestRequestHeaderMapImpl request_headers{ - {":method", "GET"}, {":path", "/test"}, {":scheme", "http"}, {"host", "example.com"}}; - - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); -} - -// Test invalid response header validation via response_headers_to_add. -TEST_F(InvalidMutationTest, InvalidResponseHeadersToAddName) { - Filters::Common::ExtAuthz::Response r; - r.status = Filters::Common::ExtAuthz::CheckStatus::OK; - r.response_headers_to_add = {{"invalid header name", "value"}}; - testResponse(r); -} - -// Test invalid response header validation via response_headers_to_add value. -TEST_F(InvalidMutationTest, InvalidResponseHeadersToAddValue) { - Filters::Common::ExtAuthz::Response r; - r.status = Filters::Common::ExtAuthz::CheckStatus::OK; - r.response_headers_to_add = {{"valid-name", getInvalidValue()}}; - testResponse(r); -} - -// Test per-route timeout configuration is correctly used in gRPC client creation. -TEST_P(HttpFilterTestParam, PerRouteGrpcClientTimeoutConfiguration) { - if (std::get<1>(GetParam())) { - // Skip HTTP client test as per-route gRPC service only applies to gRPC clients. - return; - } - - // Create per-route configuration with custom timeout. - envoy::extensions::filters::http::ext_authz::v3::ExtAuthzPerRoute per_route_config; - auto* grpc_service = per_route_config.mutable_check_settings()->mutable_grpc_service(); - grpc_service->mutable_envoy_grpc()->set_cluster_name("per_route_grpc_cluster"); - grpc_service->mutable_timeout()->set_seconds(30); // Custom 30s timeout - - std::unique_ptr per_route_filter_config = - std::make_unique(per_route_config); - - ON_CALL(*decoder_filter_callbacks_.route_, mostSpecificPerFilterConfig(_)) - .WillByDefault(Return(per_route_filter_config.get())); - - Router::RouteSpecificFilterConfigs per_route_configs; - per_route_configs.push_back(per_route_filter_config.get()); - ON_CALL(decoder_filter_callbacks_, perFilterConfigs()).WillByDefault(Return(per_route_configs)); - - prepareCheck(); - - auto new_client = std::make_unique(); - auto* new_client_ptr = new_client.get(); - auto new_filter = std::make_unique(config_, std::move(new_client), factory_context_); - new_filter->setDecoderFilterCallbacks(decoder_filter_callbacks_); - - // Mock gRPC client manager. - auto mock_grpc_client_manager = std::make_shared(); - ON_CALL(factory_context_, clusterManager()).WillByDefault(ReturnRef(cm_)); - ON_CALL(cm_, grpcAsyncClientManager()).WillByDefault(ReturnRef(*mock_grpc_client_manager)); - - auto mock_raw_grpc_client = std::make_shared(); - EXPECT_CALL(*mock_grpc_client_manager, getOrCreateRawAsyncClientWithHashKey(_, _, true)) - .WillOnce(Return(absl::StatusOr(mock_raw_grpc_client))); - - // Mock the sendRaw call and verify the timeout is used correctly. - EXPECT_CALL(*mock_raw_grpc_client, sendRaw(_, _, _, _, _, _)) - .WillOnce(Invoke([](absl::string_view, absl::string_view, Buffer::InstancePtr&&, - Grpc::RawAsyncRequestCallbacks& callbacks, Tracing::Span& parent_span, - const Http::AsyncClient::RequestOptions& options) -> Grpc::AsyncRequest* { - // Verify that the timeout from the per-route config is used (30s = 30000ms) - EXPECT_TRUE(options.timeout.has_value()); - EXPECT_EQ(options.timeout->count(), 30000); - - envoy::service::auth::v3::CheckResponse check_response; - check_response.mutable_status()->set_code(Grpc::Status::WellKnownGrpcStatus::Ok); - check_response.mutable_ok_response(); - - std::string serialized_response; - check_response.SerializeToString(&serialized_response); - auto response = std::make_unique(serialized_response); - - callbacks.onSuccessRaw(std::move(response), parent_span); - return nullptr; - })); - - EXPECT_CALL(*new_client_ptr, check(_, _, _, _)).Times(0); - - Http::TestRequestHeaderMapImpl request_headers_{ - {":method", "GET"}, {":path", "/test"}, {":scheme", "http"}, {"host", "example.com"}}; - - EXPECT_EQ(Http::FilterHeadersStatus::Continue, - new_filter->decodeHeaders(request_headers_, false)); -} - } // namespace } // namespace ExtAuthz } // namespace HttpFilters