Skip to content

Commit

Permalink
ext_proc: allow override metadata without grpc_service (envoyproxy#31544
Browse files Browse the repository at this point in the history
)

* ext_proc: allow override metadata without grpc_service

allows `overrides.metadata` in `ExtProcPerRoute` that appends and
overrides parent metadata:

      - match:
          ...
        route:
          ...
        typed_per_filter_config:
          envoy.filters.http.ext_proc:
            "@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExtProcPerRoute
            overrides:
              metadata:
                - key: "x-ext-proc-override"
                  value: "route3-override"
                - key: "x-router3-metadata-append"
                  value: "route3-metadata-append"
    http_filters:
    - name: envoy.filters.http.ext_proc
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor
      grpc_service:
        envoy_grpc:
          cluster_name: ext-proc-proxy
        timeout: 60s
        initial_metadata:
          - key: "x-ext-proc-override"
            value: "root"
          - key: "x-ext-proc-original"
            value: "root"

that allows to have:

    x-ext-proc-override: route3-override
    x-router3-metadata-append: route3-metadata-append
    x-ext-proc-original: root

---------

Signed-off-by: Ivan Prisyazhnyy <john.koepi@gmail.com>
  • Loading branch information
sitano authored Feb 27, 2024
1 parent bc01901 commit 372a262
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 104 deletions.
9 changes: 8 additions & 1 deletion api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ syntax = "proto3";
package envoy.extensions.filters.http.ext_proc.v3;

import "envoy/config/common/mutation_rules/v3/mutation_rules.proto";
import "envoy/config/core/v3/base.proto";
import "envoy/config/core/v3/grpc_service.proto";
import "envoy/extensions/filters/http/ext_proc/v3/processing_mode.proto";
import "envoy/type/matcher/v3/string.proto";
Expand Down Expand Up @@ -273,7 +274,7 @@ message ExtProcPerRoute {
}

// Overrides that may be set on a per-route basis
// [#next-free-field: 7]
// [#next-free-field: 8]
message ExtProcOverrides {
// Set a different processing mode for this route than the default.
ProcessingMode processing_mode = 1;
Expand Down Expand Up @@ -301,4 +302,10 @@ message ExtProcOverrides {
// config used. It is the prerogative of the control plane to ensure this
// most-specific config contains the correct final overrides.
MetadataOptions metadata_options = 6;

// Additional metadata to include into streams initiated to the ext_proc gRPC
// service. This can be used for scenarios in which additional ad hoc
// authorization headers (e.g. ``x-foo-bar: baz-key``) are to be injected or
// when a route needs to partially override inherited metadata.
repeated config.core.v3.HeaderValue grpc_initial_metadata = 7;
}
9 changes: 9 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ removed_config_or_runtime:
removed ``envoy_reloadable_features_initialize_upstream_filters`` and legacy code paths.
new_features:
- area: ext_proc
change: |
Added
:ref:`grpc_initial_metadata <envoy_v3_api_field_extensions.filters.http.ext_proc.v3.ExtProcOverrides.grpc_initial_metadata>`
config API to allow extending inherited metadata from
:ref:`ExternalProcessor.grpc_service <envoy_v3_api_field_extensions.filters.http.ext_proc.v3.ExternalProcessor.grpc_service>`
and
:ref:`ExtProcOverrides.grpc_service <envoy_v3_api_field_extensions.filters.http.ext_proc.v3.ExtProcOverrides.grpc_service>`
with the new or updated values.
- area: aws_request_signing
change: |
Update ``aws_request_signing`` filter to support use as an upstream HTTP filter. This allows successful calculation of
Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/http/ext_proc/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ envoy_cc_library(
"//source/extensions/filters/http/common:pass_through_filter_lib",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/strings:string_view",
"@envoy_api//envoy/config/common/mutation_rules/v3:pkg_cc_proto",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
"@envoy_api//envoy/extensions/filters/http/ext_proc/v3:pkg_cc_proto",
Expand Down
218 changes: 136 additions & 82 deletions source/extensions/filters/http/ext_proc/ext_proc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
#include "source/extensions/filters/http/ext_proc/mutation_utils.h"

#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace ExternalProcessing {
namespace {

using envoy::config::common::mutation_rules::v3::HeaderMutationRules;
using envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute;
Expand All @@ -35,9 +37,114 @@ using Http::RequestTrailerMap;
using Http::ResponseHeaderMap;
using Http::ResponseTrailerMap;

static const std::string ErrorPrefix = "ext_proc_error";
static const int DefaultImmediateStatus = 200;
static const std::string FilterName = "envoy.filters.http.ext_proc";
constexpr absl::string_view ErrorPrefix = "ext_proc_error";
constexpr int DefaultImmediateStatus = 200;
constexpr absl::string_view FilterName = "envoy.filters.http.ext_proc";

absl::optional<ProcessingMode> initProcessingMode(const ExtProcPerRoute& config) {
if (!config.disabled() && config.has_overrides() && config.overrides().has_processing_mode()) {
return config.overrides().processing_mode();
}
return absl::nullopt;
}

absl::optional<envoy::config::core::v3::GrpcService>
initGrpcService(const ExtProcPerRoute& config) {
if (config.has_overrides() && config.overrides().has_grpc_service()) {
return config.overrides().grpc_service();
}
return absl::nullopt;
}

std::vector<std::string> initNamespaces(const Protobuf::RepeatedPtrField<std::string>& ns) {
std::vector<std::string> namespaces;
for (const auto& single_ns : ns) {
namespaces.emplace_back(single_ns);
}
return namespaces;
}

absl::optional<std::vector<std::string>>
initUntypedForwardingNamespaces(const ExtProcPerRoute& config) {
if (!config.has_overrides() || !config.overrides().has_metadata_options() ||
!config.overrides().metadata_options().has_forwarding_namespaces()) {
return absl::nullopt;
}

return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().untyped())};
}

absl::optional<std::vector<std::string>>
initTypedForwardingNamespaces(const ExtProcPerRoute& config) {
if (!config.has_overrides() || !config.overrides().has_metadata_options() ||
!config.overrides().metadata_options().has_forwarding_namespaces()) {
return absl::nullopt;
}

return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().typed())};
}

absl::optional<std::vector<std::string>>
initUntypedReceivingNamespaces(const ExtProcPerRoute& config) {
if (!config.has_overrides() || !config.overrides().has_metadata_options() ||
!config.overrides().metadata_options().has_receiving_namespaces()) {
return absl::nullopt;
}

return {initNamespaces(config.overrides().metadata_options().receiving_namespaces().untyped())};
}

absl::optional<ProcessingMode> mergeProcessingMode(const FilterConfigPerRoute& less_specific,
const FilterConfigPerRoute& more_specific) {
if (more_specific.disabled()) {
return absl::nullopt;
}
return more_specific.processingMode().has_value() ? more_specific.processingMode()
: less_specific.processingMode();
}

// Replaces all entries with the same name or append one.
void mergeHeaderValues(std::vector<envoy::config::core::v3::HeaderValue>& metadata,
const envoy::config::core::v3::HeaderValue& header) {
bool has_key = false;
for (auto& dest : metadata) {
if (dest.key() == header.key()) {
dest.CopyFrom(header);
has_key = true;
}
}
if (!has_key) {
metadata.emplace_back(header);
}
}

std::vector<envoy::config::core::v3::HeaderValue>
mergeGrpcInitialMetadata(const FilterConfigPerRoute& less_specific,
const FilterConfigPerRoute& more_specific) {
std::vector<envoy::config::core::v3::HeaderValue> metadata(less_specific.grpcInitialMetadata());

for (const auto& header : more_specific.grpcInitialMetadata()) {
mergeHeaderValues(metadata, header);
}

return metadata;
}

// Replaces all entries with the same name or append one.
void mergeHeaderValuesField(
Protobuf::RepeatedPtrField<::envoy::config::core::v3::HeaderValue>& metadata,
const envoy::config::core::v3::HeaderValue& header) {
bool has_key = false;
for (auto& dest : metadata) {
if (dest.key() == header.key()) {
dest.CopyFrom(header);
has_key = true;
}
}
if (!has_key) {
metadata.Add()->CopyFrom(header);
}
}

// Changes to headers are normally tested against the MutationRules supplied
// with configuration. When writing an immediate response message, however,
Expand All @@ -60,6 +167,19 @@ class ImmediateMutationChecker {
std::unique_ptr<Checker> rule_checker_;
};

const ImmediateMutationChecker& immediateResponseChecker() {
CONSTRUCT_ON_FIRST_USE(ImmediateMutationChecker);
}

ProcessingMode allDisabledMode() {
ProcessingMode pm;
pm.set_request_header_mode(ProcessingMode::SKIP);
pm.set_response_header_mode(ProcessingMode::SKIP);
return pm;
}

} // namespace

void ExtProcLoggingInfo::recordGrpcCall(
std::chrono::microseconds latency, Grpc::Status::GrpcStatus call_status,
ProcessorState::CallbackState callback_state,
Expand Down Expand Up @@ -117,77 +237,11 @@ ExtProcLoggingInfo::grpcCalls(envoy::config::core::v3::TrafficDirection traffic_
: encoding_processor_grpc_calls_;
}

std::vector<std::string>
FilterConfigPerRoute::initNamespaces(const Protobuf::RepeatedPtrField<std::string>& ns) {
if (ns.empty()) {
return {};
}

std::vector<std::string> namespaces;
for (const auto& single_ns : ns) {
namespaces.emplace_back(single_ns);
}
return namespaces;
}

absl::optional<std::vector<std::string>>
FilterConfigPerRoute::initUntypedForwardingNamespaces(const ExtProcPerRoute& config) {
if (!config.has_overrides() || !config.overrides().has_metadata_options() ||
!config.overrides().metadata_options().has_forwarding_namespaces()) {
return absl::nullopt;
}

return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().untyped())};
}

absl::optional<std::vector<std::string>>
FilterConfigPerRoute::initTypedForwardingNamespaces(const ExtProcPerRoute& config) {
if (!config.has_overrides() || !config.overrides().has_metadata_options() ||
!config.overrides().metadata_options().has_forwarding_namespaces()) {
return absl::nullopt;
}

return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().typed())};
}

absl::optional<std::vector<std::string>>
FilterConfigPerRoute::initUntypedReceivingNamespaces(const ExtProcPerRoute& config) {
if (!config.has_overrides() || !config.overrides().has_metadata_options() ||
!config.overrides().metadata_options().has_receiving_namespaces()) {
return absl::nullopt;
}

return {initNamespaces(config.overrides().metadata_options().receiving_namespaces().untyped())};
}

absl::optional<ProcessingMode>
FilterConfigPerRoute::initProcessingMode(const ExtProcPerRoute& config) {
if (!config.disabled() && config.has_overrides() && config.overrides().has_processing_mode()) {
return config.overrides().processing_mode();
}
return absl::nullopt;
}
absl::optional<envoy::config::core::v3::GrpcService>
FilterConfigPerRoute::initGrpcService(const ExtProcPerRoute& config) {
if (config.has_overrides() && config.overrides().has_grpc_service()) {
return config.overrides().grpc_service();
}
return absl::nullopt;
}

absl::optional<ProcessingMode>
FilterConfigPerRoute::mergeProcessingMode(const FilterConfigPerRoute& less_specific,
const FilterConfigPerRoute& more_specific) {
if (more_specific.disabled()) {
return absl::nullopt;
}
return more_specific.processingMode().has_value() ? more_specific.processingMode()
: less_specific.processingMode();
}

FilterConfigPerRoute::FilterConfigPerRoute(const ExtProcPerRoute& config)
: disabled_(config.disabled()), processing_mode_(initProcessingMode(config)),
grpc_service_(initGrpcService(config)),
grpc_initial_metadata_(config.overrides().grpc_initial_metadata().begin(),
config.overrides().grpc_initial_metadata().end()),
untyped_forwarding_namespaces_(initUntypedForwardingNamespaces(config)),
typed_forwarding_namespaces_(initTypedForwardingNamespaces(config)),
untyped_receiving_namespaces_(initUntypedReceivingNamespaces(config)) {}
Expand All @@ -198,6 +252,7 @@ FilterConfigPerRoute::FilterConfigPerRoute(const FilterConfigPerRoute& less_spec
processing_mode_(mergeProcessingMode(less_specific, more_specific)),
grpc_service_(more_specific.grpcService().has_value() ? more_specific.grpcService()
: less_specific.grpcService()),
grpc_initial_metadata_(mergeGrpcInitialMetadata(less_specific, more_specific)),
untyped_forwarding_namespaces_(more_specific.untypedForwardingMetadataNamespaces().has_value()
? more_specific.untypedForwardingMetadataNamespaces()
: less_specific.untypedForwardingMetadataNamespaces()),
Expand Down Expand Up @@ -995,10 +1050,6 @@ void Filter::onFinishProcessorCalls(Grpc::Status::GrpcStatus call_status) {
encoding_state_.onFinishProcessorCall(call_status);
}

static const ImmediateMutationChecker& immediateResponseChecker() {
CONSTRUCT_ON_FIRST_USE(ImmediateMutationChecker);
}

void Filter::sendImmediateResponse(const ImmediateResponse& response) {
auto status_code = response.has_status() ? response.status().code() : DefaultImmediateStatus;
if (!MutationUtils::isValidHttpStatus(status_code)) {
Expand Down Expand Up @@ -1036,13 +1087,6 @@ void Filter::sendImmediateResponse(const ImmediateResponse& response) {
mutate_headers, grpc_status, details);
}

static ProcessingMode allDisabledMode() {
ProcessingMode pm;
pm.set_request_header_mode(ProcessingMode::SKIP);
pm.set_response_header_mode(ProcessingMode::SKIP);
return pm;
}

void Filter::mergePerRouteConfig() {
if (route_config_merged_) {
return;
Expand Down Expand Up @@ -1089,6 +1133,16 @@ void Filter::mergePerRouteConfig() {
grpc_service_ = *merged_config->grpcService();
config_with_hash_key_.setConfig(*merged_config->grpcService());
}
if (!merged_config->grpcInitialMetadata().empty()) {
ENVOY_LOG(trace, "Overriding grpc initial metadata from per-route configuration");
envoy::config::core::v3::GrpcService config = config_with_hash_key_.config();
auto ptr = config.mutable_initial_metadata();
for (const auto& header : merged_config->grpcInitialMetadata()) {
ENVOY_LOG(trace, "Setting grpc initial metadata {} = {}", header.key(), header.value());
mergeHeaderValuesField(*ptr, header);
}
config_with_hash_key_.setConfig(config);
}

// For metadata namespaces, we only override the existing value if we have a
// value from our merged config. We indicate a lack of value from the merged
Expand Down
28 changes: 7 additions & 21 deletions source/extensions/filters/http/ext_proc/ext_proc.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig {
const absl::optional<const envoy::config::core::v3::GrpcService>& grpcService() const {
return grpc_service_;
}
const std::vector<envoy::config::core::v3::HeaderValue>& grpcInitialMetadata() const {
return grpc_initial_metadata_;
}

const absl::optional<const std::vector<std::string>>&
untypedForwardingMetadataNamespaces() const {
Expand All @@ -260,31 +263,11 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig {
}

private:
absl::optional<envoy::extensions::filters::http::ext_proc::v3::ProcessingMode>
initProcessingMode(const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config);

absl::optional<envoy::config::core::v3::GrpcService>
initGrpcService(const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config);

std::vector<std::string> initNamespaces(const Protobuf::RepeatedPtrField<std::string>& ns);

absl::optional<std::vector<std::string>> initUntypedForwardingNamespaces(
const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config);

absl::optional<std::vector<std::string>> initTypedForwardingNamespaces(
const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config);

absl::optional<std::vector<std::string>> initUntypedReceivingNamespaces(
const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config);

absl::optional<envoy::extensions::filters::http::ext_proc::v3::ProcessingMode>
mergeProcessingMode(const FilterConfigPerRoute& less_specific,
const FilterConfigPerRoute& more_specific);

const bool disabled_;
const absl::optional<const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode>
processing_mode_;
const absl::optional<const envoy::config::core::v3::GrpcService> grpc_service_;
std::vector<envoy::config::core::v3::HeaderValue> grpc_initial_metadata_;

const absl::optional<const std::vector<std::string>> untyped_forwarding_namespaces_;
const absl::optional<const std::vector<std::string>> typed_forwarding_namespaces_;
Expand Down Expand Up @@ -321,6 +304,9 @@ class Filter : public Logger::Loggable<Logger::Id::ext_proc>,
config->untypedReceivingMetadataNamespaces()) {}

const FilterConfig& config() const { return *config_; }
const envoy::config::core::v3::GrpcService& grpc_service_config() const {
return config_with_hash_key_.config();
}

ExtProcFilterStats& stats() { return stats_; }
ExtProcLoggingInfo* loggingInfo() { return logging_info_; }
Expand Down
Loading

0 comments on commit 372a262

Please sign in to comment.