1- #include " ext_authz.h"
21#include " source/extensions/filters/http/ext_authz/ext_authz.h"
32
43#include < chrono>
@@ -21,6 +20,9 @@ namespace ExtAuthz {
2120
2221namespace {
2322
23+ // Default timeout for per-route gRPC client creation.
24+ constexpr uint32_t kDefaultPerRouteTimeoutMs = 200 ;
25+
2426using MetadataProto = ::envoy::config::core::v3::Metadata;
2527using Filters::Common::MutationRules::CheckOperation;
2628using Filters::Common::MutationRules::CheckResult;
@@ -172,6 +174,86 @@ void FilterConfigPerRoute::merge(const FilterConfigPerRoute& other) {
172174 }
173175}
174176
177+ // Constructor used for merging configurations from different levels (vhost, route, etc.)
178+ FilterConfigPerRoute::FilterConfigPerRoute (const FilterConfigPerRoute& less_specific,
179+ const FilterConfigPerRoute& more_specific)
180+ : context_extensions_(less_specific.context_extensions_),
181+ check_settings_(more_specific.check_settings_), disabled_(more_specific.disabled_),
182+ // Only use the most specific per-route override. Do not inherit overrides from less
183+ // specific configuration. If the more specific configuration has no override, leave both
184+ // unset so that the main filter configuration is used.
185+ grpc_service_(more_specific.grpc_service_.has_value() ? more_specific.grpc_service_
186+ : absl::nullopt),
187+ http_service_(more_specific.http_service_.has_value() ? more_specific.http_service_
188+ : absl::nullopt) {
189+ // Merge context extensions from more specific configuration, overriding less specific ones.
190+ for (const auto & extension : more_specific.context_extensions_ ) {
191+ context_extensions_[extension.first ] = extension.second ;
192+ }
193+ }
194+
195+ Filters::Common::ExtAuthz::ClientPtr
196+ Filter::createPerRouteGrpcClient (const envoy::config::core::v3::GrpcService& grpc_service) {
197+ if (server_context_ == nullptr ) {
198+ ENVOY_STREAM_LOG (
199+ debug, " ext_authz filter: server context not available for per-route gRPC client creation." ,
200+ *decoder_callbacks_);
201+ return nullptr ;
202+ }
203+
204+ // Use the timeout from the gRPC service configuration, use default if not specified.
205+ const uint32_t timeout_ms =
206+ PROTOBUF_GET_MS_OR_DEFAULT (grpc_service, timeout, kDefaultPerRouteTimeoutMs );
207+
208+ // We can skip transport version check for per-route gRPC service here.
209+ // The transport version is already validated at the main configuration level.
210+ Envoy::Grpc::GrpcServiceConfigWithHashKey config_with_hash_key =
211+ Envoy::Grpc::GrpcServiceConfigWithHashKey (grpc_service);
212+
213+ auto client_or_error = server_context_->clusterManager ()
214+ .grpcAsyncClientManager ()
215+ .getOrCreateRawAsyncClientWithHashKey (config_with_hash_key,
216+ server_context_->scope (), true );
217+ if (!client_or_error.ok ()) {
218+ ENVOY_STREAM_LOG (warn,
219+ " ext_authz filter: failed to create per-route gRPC client: {}. Falling back "
220+ " to default client." ,
221+ *decoder_callbacks_, client_or_error.status ().ToString ());
222+ return nullptr ;
223+ }
224+
225+ ENVOY_STREAM_LOG (debug, " ext_authz filter: created per-route gRPC client for cluster: {}." ,
226+ *decoder_callbacks_,
227+ grpc_service.has_envoy_grpc () ? grpc_service.envoy_grpc ().cluster_name ()
228+ : " google_grpc" );
229+
230+ return std::make_unique<Filters::Common::ExtAuthz::GrpcClientImpl>(
231+ client_or_error.value (), std::chrono::milliseconds (timeout_ms));
232+ }
233+
234+ Filters::Common::ExtAuthz::ClientPtr Filter::createPerRouteHttpClient (
235+ const envoy::extensions::filters::http::ext_authz::v3::HttpService& http_service) {
236+ if (server_context_ == nullptr ) {
237+ ENVOY_STREAM_LOG (
238+ debug, " ext_authz filter: server context not available for per-route HTTP client creation." ,
239+ *decoder_callbacks_);
240+ return nullptr ;
241+ }
242+
243+ // Use the timeout from the HTTP service configuration, use default if not specified.
244+ const uint32_t timeout_ms =
245+ PROTOBUF_GET_MS_OR_DEFAULT (http_service.server_uri (), timeout, kDefaultPerRouteTimeoutMs );
246+
247+ ENVOY_STREAM_LOG (debug, " ext_authz filter: creating per-route HTTP client for URI: {}." ,
248+ *decoder_callbacks_, http_service.server_uri ().uri ());
249+
250+ const auto client_config = std::make_shared<Extensions::Filters::Common::ExtAuthz::ClientConfig>(
251+ http_service, config_->headersAsBytes (), timeout_ms, *server_context_);
252+
253+ return std::make_unique<Extensions::Filters::Common::ExtAuthz::RawHttpClientImpl>(
254+ server_context_->clusterManager (), client_config);
255+ }
256+
175257void Filter::initiateCall (const Http::RequestHeaderMap& headers) {
176258 if (filter_return_ == FilterReturn::StopDecoding) {
177259 return ;
@@ -205,9 +287,10 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) {
205287 for (const FilterConfigPerRoute& cfg :
206288 Http::Utility::getAllPerFilterConfig<FilterConfigPerRoute>(decoder_callbacks_)) {
207289 if (maybe_merged_per_route_config.has_value ()) {
208- maybe_merged_per_route_config.value ().merge (cfg);
290+ FilterConfigPerRoute current_config = maybe_merged_per_route_config.value ();
291+ maybe_merged_per_route_config.emplace (current_config, cfg);
209292 } else {
210- maybe_merged_per_route_config = cfg;
293+ maybe_merged_per_route_config. emplace ( cfg) ;
211294 }
212295 }
213296
@@ -216,6 +299,46 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) {
216299 context_extensions = maybe_merged_per_route_config.value ().takeContextExtensions ();
217300 }
218301
302+ // Check if we need to use a per-route service override (gRPC or HTTP).
303+ Filters::Common::ExtAuthz::Client* client_to_use = client_.get ();
304+ if (maybe_merged_per_route_config) {
305+ if (maybe_merged_per_route_config->grpcService ().has_value ()) {
306+ const auto & grpc_service = maybe_merged_per_route_config->grpcService ().value ();
307+ ENVOY_STREAM_LOG (debug, " ext_authz filter: using per-route gRPC service configuration." ,
308+ *decoder_callbacks_);
309+
310+ // Create a new gRPC client for this route.
311+ per_route_client_ = createPerRouteGrpcClient (grpc_service);
312+ if (per_route_client_ != nullptr ) {
313+ client_to_use = per_route_client_.get ();
314+ ENVOY_STREAM_LOG (debug, " ext_authz filter: successfully created per-route gRPC client." ,
315+ *decoder_callbacks_);
316+ } else {
317+ ENVOY_STREAM_LOG (
318+ warn,
319+ " ext_authz filter: failed to create per-route gRPC client, falling back to default." ,
320+ *decoder_callbacks_);
321+ }
322+ } else if (maybe_merged_per_route_config->httpService ().has_value ()) {
323+ const auto & http_service = maybe_merged_per_route_config->httpService ().value ();
324+ ENVOY_STREAM_LOG (debug, " ext_authz filter: using per-route HTTP service configuration." ,
325+ *decoder_callbacks_);
326+
327+ // Create a new HTTP client for this route.
328+ per_route_client_ = createPerRouteHttpClient (http_service);
329+ if (per_route_client_ != nullptr ) {
330+ client_to_use = per_route_client_.get ();
331+ ENVOY_STREAM_LOG (debug, " ext_authz filter: successfully created per-route HTTP client." ,
332+ *decoder_callbacks_);
333+ } else {
334+ ENVOY_STREAM_LOG (
335+ warn,
336+ " ext_authz filter: failed to create per-route HTTP client, falling back to default." ,
337+ *decoder_callbacks_);
338+ }
339+ }
340+ }
341+
219342 // If metadata_context_namespaces or typed_metadata_context_namespaces is specified,
220343 // pass matching filter metadata to the ext_authz service.
221344 // If metadata key is set in both the connection and request metadata,
@@ -241,7 +364,7 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) {
241364 config_->destinationLabels (), config_->allowedHeadersMatcher (),
242365 config_->disallowedHeadersMatcher ());
243366
244- ENVOY_STREAM_LOG (trace, " ext_authz filter calling authorization server" , *decoder_callbacks_);
367+ ENVOY_STREAM_LOG (trace, " ext_authz filter calling authorization server. " , *decoder_callbacks_);
245368 // Store start time of ext_authz filter call
246369 start_time_ = decoder_callbacks_->dispatcher ().timeSource ().monotonicTime ();
247370
@@ -250,8 +373,8 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) {
250373 // going to invoke check call.
251374 cluster_ = decoder_callbacks_->clusterInfo ();
252375 initiating_call_ = true ;
253- client_ ->check (*this , check_request_, decoder_callbacks_->activeSpan (),
254- decoder_callbacks_->streamInfo ());
376+ client_to_use ->check (*this , check_request_, decoder_callbacks_->activeSpan (),
377+ decoder_callbacks_->streamInfo ());
255378 initiating_call_ = false ;
256379}
257380
0 commit comments