Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ 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";
Expand Down Expand Up @@ -513,4 +514,16 @@ message CheckSettings {
// :ref:`disable_request_body_buffering <envoy_v3_api_field_extensions.filters.http.ext_authz.v3.CheckSettings.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;
}
}
13 changes: 9 additions & 4 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,8 @@ new_features:
for more details.
- area: socket
change: |
Added :ref:``network_namespace_filepath <envoy_v3_api_msg_config.core.v3.SocketAddress.network_namespace_filepath>`` to
:ref:`SocketAddress <envoy_v3_api_msg_config.core.v3.SocketAddress>`. This field allows specifying a Linux network namespace filepath
for socket creation, enabling network isolation in containerized environments.
Added ``network_namespace_filepath`` to :ref:`SocketAddress <envoy_v3_api_msg_config.core.v3.SocketAddress>`. This field allows
specifying a Linux network namespace filepath for socket creation, enabling network isolation in containerized environments.
Comment thread
wbpcode marked this conversation as resolved.
- area: ratelimit
change: |
Add the :ref:`rate_limits
Expand Down Expand Up @@ -147,7 +146,6 @@ new_features:
that sends ``GOAWAY`` AND closes connections for HTTP2 server processing of requests. When
a ``GOAWAY`` frame is submitted by this the counter ``http2.goaway_sent`` will be
incremented.

- area: otlp_stat_sink
change: |
Added support for resource attributes. The stat sink will use the resource attributes configured for the OpenTelemetry tracer via
Expand All @@ -157,6 +155,13 @@ 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 <config_http_filters_lua_virtual_host_wrapper>` 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 <envoy_v3_api_field_extensions.filters.http.ext_authz.v3.CheckSettings.grpc_service>`
in the per-route ``check_settings``. Routes without this configuration continue to use the default
authorization service.
- area: rbac
change: |
Switch the IP matcher to use LC-Trie for performance improvements.
Expand Down
3 changes: 2 additions & 1 deletion source/extensions/filters/http/ext_authz/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoWithServ
THROW_IF_NOT_OK_REF(client_or_error.status());
auto client = std::make_unique<Filters::Common::ExtAuthz::GrpcClientImpl>(
client_or_error.value(), std::chrono::milliseconds(timeout_ms));
callbacks.addStreamFilter(std::make_shared<Filter>(filter_config, std::move(client)));
callbacks.addStreamFilter(
std::make_shared<Filter>(filter_config, std::move(client), server_context));
};
}
return callback;
Expand Down
140 changes: 134 additions & 6 deletions source/extensions/filters/http/ext_authz/ext_authz.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#include "ext_authz.h"
#include "source/extensions/filters/http/ext_authz/ext_authz.h"

#include <chrono>
Expand All @@ -21,6 +20,9 @@ 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;
Expand Down Expand Up @@ -172,6 +174,90 @@ 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;
}
}
Comment thread
wbpcode marked this conversation as resolved.

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()
Comment thread
agrawroh marked this conversation as resolved.
: "google_grpc");

return std::make_unique<Filters::Common::ExtAuthz::GrpcClientImpl>(
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());

// Create a temporary ExtAuthz config with the HTTP service for the ClientConfig constructor.
envoy::extensions::filters::http::ext_authz::v3::ExtAuthz temp_config;
temp_config.mutable_http_service()->CopyFrom(http_service);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maybe we should update the constructor of ClientConfig to accept HttpService only to avoid the this copy. But it's not in a hurry. The auth is slow anyway.


const auto client_config = std::make_shared<Extensions::Filters::Common::ExtAuthz::ClientConfig>(
temp_config, timeout_ms, http_service.path_prefix(), *server_context_);

return std::make_unique<Extensions::Filters::Common::ExtAuthz::RawHttpClientImpl>(
server_context_->clusterManager(), client_config);
}

void Filter::initiateCall(const Http::RequestHeaderMap& headers) {
if (filter_return_ == FilterReturn::StopDecoding) {
return;
Expand Down Expand Up @@ -205,9 +291,10 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) {
for (const FilterConfigPerRoute& cfg :
Http::Utility::getAllPerFilterConfig<FilterConfigPerRoute>(decoder_callbacks_)) {
if (maybe_merged_per_route_config.has_value()) {
maybe_merged_per_route_config.value().merge(cfg);
FilterConfigPerRoute current_config = maybe_merged_per_route_config.value();
maybe_merged_per_route_config.emplace(current_config, cfg);
} else {
maybe_merged_per_route_config = cfg;
maybe_merged_per_route_config.emplace(cfg);
}
}

Expand All @@ -216,6 +303,46 @@ 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::ClientPtr* client_to_use = &client_;
Comment thread
agrawroh marked this conversation as resolved.
Outdated
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_;
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_;
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,
Expand All @@ -241,7 +368,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();

Expand All @@ -250,8 +377,9 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) {
// going to invoke check call.
cluster_ = decoder_callbacks_->clusterInfo();
initiating_call_ = true;
client_->check(*this, check_request_, decoder_callbacks_->activeSpan(),
decoder_callbacks_->streamInfo());
(*client_to_use)
->check(*this, check_request_, decoder_callbacks_->activeSpan(),
decoder_callbacks_->streamInfo());
initiating_call_ = false;
}

Expand Down
55 changes: 53 additions & 2 deletions source/extensions/filters/http/ext_authz/ext_authz.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
#include <vector>

#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"
Expand All @@ -17,6 +19,7 @@
#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"
Expand Down Expand Up @@ -305,7 +308,13 @@ 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()) {
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) {
if (config.has_check_settings() && config.check_settings().disable_request_body_buffering() &&
config.check_settings().has_with_request_body()) {
ExceptionUtil::throwEnvoyException(
Expand All @@ -314,6 +323,12 @@ 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);

/**
Expand All @@ -329,12 +344,30 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig {
return check_settings_;
}

/**
* @return The gRPC service override for this route, if any.
*/
const absl::optional<const envoy::config::core::v3::GrpcService>& grpcService() const {
return grpc_service_;
}

/**
* @return The HTTP service override for this route, if any.
*/
const absl::optional<const envoy::extensions::filters::http::ext_authz::v3::HttpService>&
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_;
bool disabled_;
const bool disabled_;
const absl::optional<const envoy::config::core::v3::GrpcService> grpc_service_;
const absl::optional<const envoy::extensions::filters::http::ext_authz::v3::HttpService>
http_service_;
};

/**
Expand All @@ -348,6 +381,12 @@ class Filter : public Logger::Loggable<Logger::Id::ext_authz>,
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 gRPC 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;

Expand Down Expand Up @@ -383,6 +422,14 @@ class Filter : public Logger::Loggable<Logger::Id::ext_authz>,
// 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<MonotonicTime> start_time_;
void addResponseHeaders(Http::HeaderMap& header_map, const Http::HeaderVector& headers);
void initiateCall(const Http::RequestHeaderMap& headers);
Expand Down Expand Up @@ -410,6 +457,10 @@ class Filter : public Logger::Loggable<Logger::Id::ext_authz>,
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 gRPC clients.
Server::Configuration::ServerFactoryContext* server_context_{nullptr};
Comment thread
agrawroh marked this conversation as resolved.
Http::StreamDecoderFilterCallbacks* decoder_callbacks_{};
Http::StreamEncoderFilterCallbacks* encoder_callbacks_{};
Http::RequestHeaderMap* request_headers_;
Expand Down
Loading