Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions source/extensions/filters/http/ext_proc/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ envoy_cc_library(
],
tags = ["skip_on_windows"],
deps = [
":allowed_override_modes_set_lib",
":client_lib",
":matching_utils_lib",
":mutation_utils_lib",
Expand All @@ -47,6 +48,16 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "allowed_override_modes_set_lib",
hdrs = ["allowed_override_modes_set.h"],
tags = ["skip_on_windows"],
deps = [
"@com_google_absl//absl/container:flat_hash_set",
"@envoy_api//envoy/extensions/filters/http/ext_proc/v3:pkg_cc_proto",
],
)

envoy_cc_library(
name = "client_lib",
srcs = ["client_impl.cc"],
Expand All @@ -66,6 +77,7 @@ envoy_cc_extension(
hdrs = ["config.h"],
tags = ["skip_on_windows"],
deps = [
":allowed_override_modes_set_lib",
":client_lib",
":ext_proc",
"//source/extensions/filters/http/common:factory_base_lib",
Expand Down
100 changes: 100 additions & 0 deletions source/extensions/filters/http/ext_proc/allowed_override_modes_set.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#pragma once

#include "envoy/extensions/filters/http/ext_proc/v3/processing_mode.pb.h"

#include "absl/container/flat_hash_set.h"

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

/**
* A data-structure that holds the Allowed Override Mode set.
* Internally it converts each allowed-override-mode enums values to a single
* list. This saves some memory, and allows an O(1) lookup.
*/
class AllowedOverrideModesSet {
public:
/**
* Constructs an AllowedOverrideModesSet from a container of ProcessingMode protos.
*
* Each ProcessingMode in the input container is converted to an integer key
* and stored in an internal hash set for efficient lookup.
* The use of a template is to simplify passing any container that has
* envoy::extensions::filters::http::ext_proc::v3::ProcessingMode elements.
*
* @param modes The collection of ProcessingMode protos to initialize the set with.
*/
template <typename Container> explicit AllowedOverrideModesSet(const Container& modes) {
for (const auto& mode : modes) {
allowed_modes_.insert(processingModeToInt(mode));
}
}

/**
* Checks if a specific ProcessingMode is supported (i.e., present in the set).
* Note that the ``request_header_mode`` in the given mode will be ignored.
*
* @param mode The ProcessingMode object to check for support.
* @return True if the mode is supported, false otherwise.
*/
bool isModeSupported(
const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode) const {
// Convert the input ProcessingMode to its integer key representation and
// check if this key exists in the internal hash set.
return allowed_modes_.contains(processingModeToInt(mode));
}

/**
* Checks if the set of allowed override modes is empty.
* @return True if the set is empty, false otherwise.
*/
bool empty() const { return allowed_modes_.empty(); }

private:
/**
* Converts a ProcessingMode proto to a unique integer key.
*
* This method maps each relevant field of the ProcessingMode (response_header_mode,
* request_body_mode, response_body_mode, request_trailer_mode, response_trailer_mode)
* to a single digit in a base-10 number, where each digit's value is the integral
* value of the corresponding enum.
*
* The `request_header_mode` field is explicitly ignored in this conversion
* (mapped to 0) to preserve existing filter behavior where this field is
* not considered during mode comparison.
*
* Example:
* response_header_mode = SEND (1)
* request_body_mode = BUFFERED (2)
* ...
* Results in a key like: ...21 (where 1 is for response_header_mode, 2 for request_body_mode)
*
* @param mode The ProcessingMode proto to convert.
* @return A uint32_t representing the unique integer key for the mode.
*/
static uint32_t
processingModeToInt(const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode) {
uint32_t key = 0;
// Field 1: request_header_mode (Index 1 -> 10^0). This is ignored!
// Field 2: response_header_mode (Index 2 -> 10^1)
key += static_cast<uint32_t>(mode.response_header_mode()) * 10;
// Field 3: request_body_mode (Index 3 -> 10^2)
key += static_cast<uint32_t>(mode.request_body_mode()) * 100;
// Field 4: response_body_mode (Index 4 -> 10^3)
key += static_cast<uint32_t>(mode.response_body_mode()) * 1000;
// Field 5: request_trailer_mode (Index 5 -> 10^4)
key += static_cast<uint32_t>(mode.request_trailer_mode()) * 10000;
// Field 6: response_trailer_mode (Index 6 -> 10^5)
key += static_cast<uint32_t>(mode.response_trailer_mode()) * 100000;
return key;
}

absl::flat_hash_set<uint32_t> allowed_modes_;
};

} // namespace ExternalProcessing
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
23 changes: 3 additions & 20 deletions source/extensions/filters/http/ext_proc/ext_proc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,7 @@ FilterConfig::FilterConfig(const ExternalProcessor& config,
typed_cluster_metadata_forwarding_namespaces_(
config.metadata_options().cluster_metadata_forwarding_namespaces().typed().begin(),
config.metadata_options().cluster_metadata_forwarding_namespaces().typed().end()),
allowed_override_modes_(config.allowed_override_modes().begin(),
config.allowed_override_modes().end()),
allowed_override_modes_(config.allowed_override_modes()),
expression_manager_(builder, context.localInfo(), config.request_attributes(),
config.response_attributes()),
processing_request_modifier_factory_cb_(
Expand Down Expand Up @@ -1722,27 +1721,11 @@ void Filter::onReceiveMessage(std::unique_ptr<ProcessingResponse>&& r) {
(config_->processingMode().request_body_mode() != ProcessingMode::FULL_DUPLEX_STREAMED) &&
(config_->processingMode().response_body_mode() != ProcessingMode::FULL_DUPLEX_STREAMED) &&
inHeaderProcessState() && response->has_mode_override()) {
bool mode_override_allowed = true;
const auto mode_override =
effectiveModeOverride(response->mode_override(), config_->processingMode());

// First, check if mode override allow-list is configured
if (!config_->allowedOverrideModes().empty()) {
// Second, check if mode override from response is allowed.
mode_override_allowed = absl::c_any_of(
config_->allowedOverrideModes(),
[&mode_override](
const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& other) {
// Ignore matching on request_header_mode as it's not applicable.
return mode_override.request_body_mode() == other.request_body_mode() &&
mode_override.request_trailer_mode() == other.request_trailer_mode() &&
mode_override.response_header_mode() == other.response_header_mode() &&
mode_override.response_body_mode() == other.response_body_mode() &&
mode_override.response_trailer_mode() == other.response_trailer_mode();
});
}

if (mode_override_allowed) {
// Check if mode override allow-list is configured.
if (config_->isAllowedOverrideMode(mode_override)) {
ENVOY_STREAM_LOG(debug, "Processing mode overridden by server for this request",
*decoder_callbacks_);
decoding_state_.setProcessingMode(mode_override);
Expand Down
19 changes: 11 additions & 8 deletions source/extensions/filters/http/ext_proc/ext_proc.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#pragma once

#include <chrono>
#include <cstdint>
#include <memory>
#include <string>

#include "envoy/config/core/v3/base.pb.h"
Expand All @@ -25,6 +22,7 @@
#include "source/extensions/filters/common/ext_proc/client_base.h"
#include "source/extensions/filters/common/mutation_rules/mutation_rules.h"
#include "source/extensions/filters/http/common/pass_through_filter.h"
#include "source/extensions/filters/http/ext_proc/allowed_override_modes_set.h"
#include "source/extensions/filters/http/ext_proc/client_impl.h"
#include "source/extensions/filters/http/ext_proc/matching_utils.h"
#include "source/extensions/filters/http/ext_proc/on_processing_response.h"
Expand Down Expand Up @@ -270,9 +268,15 @@ class FilterConfig {
return typed_cluster_metadata_forwarding_namespaces_;
}

const std::vector<envoy::extensions::filters::http::ext_proc::v3::ProcessingMode>&
allowedOverrideModes() const {
return allowed_override_modes_;
/*
* Returns true if there are no allowed_override_modes defined, or if defined
* then one of them matches the given input.
*
* @param mode the processing mode that needs to be explicitly defined.
*/
bool isAllowedOverrideMode(
const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode) const {
return allowed_override_modes_.empty() || allowed_override_modes_.isModeSupported(mode);
}

ThreadLocalStreamManager& threadLocalStreamManager() {
Expand Down Expand Up @@ -346,8 +350,7 @@ class FilterConfig {
const std::vector<std::string> untyped_receiving_namespaces_;
const std::vector<std::string> untyped_cluster_metadata_forwarding_namespaces_;
const std::vector<std::string> typed_cluster_metadata_forwarding_namespaces_;
const std::vector<envoy::extensions::filters::http::ext_proc::v3::ProcessingMode>
allowed_override_modes_;
const AllowedOverrideModesSet allowed_override_modes_;
const ExpressionManager expression_manager_;

const std::function<std::unique_ptr<ProcessingRequestModifier>()>
Expand Down
10 changes: 10 additions & 0 deletions test/extensions/filters/http/ext_proc/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -773,3 +773,13 @@ envoy_extension_cc_test_library(
"//source/extensions/tracers/common:factory_base_lib",
],
)

envoy_extension_cc_test(
name = "allowed_override_modes_set_test",
size = "small",
srcs = ["allowed_override_modes_set_test.cc"],
extension_names = ["envoy.filters.http.ext_proc"],
deps = [
"//source/extensions/filters/http/ext_proc:allowed_override_modes_set_lib",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include "source/extensions/filters/http/ext_proc/allowed_override_modes_set.h"

#include "gtest/gtest.h"

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

using envoy::extensions::filters::http::ext_proc::v3::ProcessingMode;

class AllowedOverrideModesSetTest : public testing::Test {
protected:
// Helper to create a ProcessingMode with specific settings for readability
ProcessingMode createMode(ProcessingMode::HeaderSendMode req_header,
ProcessingMode::HeaderSendMode resp_header,
ProcessingMode::BodySendMode req_body,
ProcessingMode::BodySendMode resp_body,
ProcessingMode::HeaderSendMode req_trailer,
ProcessingMode::HeaderSendMode resp_trailer) {
ProcessingMode pm;
pm.set_request_header_mode(req_header);
pm.set_response_header_mode(resp_header);
pm.set_request_body_mode(req_body);
pm.set_response_body_mode(resp_body);
pm.set_request_trailer_mode(req_trailer);
pm.set_response_trailer_mode(resp_trailer);
return pm;
}
};

// Verify exact matches work as expected.
TEST_F(AllowedOverrideModesSetTest, BasicExactMatch) {
const ProcessingMode allowed =
createMode(ProcessingMode::SEND, ProcessingMode::SKIP, ProcessingMode::BUFFERED,
ProcessingMode::NONE, ProcessingMode::SKIP, ProcessingMode::SEND);

const std::vector<ProcessingMode> config_modes = {allowed};
const AllowedOverrideModesSet set(config_modes);

EXPECT_TRUE(set.isModeSupported(allowed));
}

// Verify that 'request_header_mode' is IGNORED during comparison.
// The allowed mode has request_header_mode = SEND.
// The candidate mode has request_header_mode = SKIP.
// All other fields match. Expected result: Supported.
TEST_F(AllowedOverrideModesSetTest, IgnoresRequestHeaderMode) {
const ProcessingMode allowed =
createMode(ProcessingMode::SEND, // Value 1.
ProcessingMode::SKIP, ProcessingMode::BUFFERED, ProcessingMode::NONE,
ProcessingMode::SKIP, ProcessingMode::SEND);

const ProcessingMode candidate =
createMode(ProcessingMode::SKIP, // Value 2 (Different!).
ProcessingMode::SKIP, ProcessingMode::BUFFERED, ProcessingMode::NONE,
ProcessingMode::SKIP, ProcessingMode::SEND);

const std::vector<ProcessingMode> config_modes = {allowed};
const AllowedOverrideModesSet set(config_modes);

EXPECT_TRUE(set.isModeSupported(candidate));
}

// Verify that differences in other fields (e.g. response_body_mode) result in rejection.
TEST_F(AllowedOverrideModesSetTest, EnforcesOtherFields) {
const ProcessingMode allowed =
createMode(ProcessingMode::SEND, ProcessingMode::SKIP, ProcessingMode::BUFFERED,
ProcessingMode::NONE, ProcessingMode::SKIP, ProcessingMode::SEND);

// Candidate differs in response_body_mode (STREAMED vs NONE)
const ProcessingMode candidate =
createMode(ProcessingMode::SEND, ProcessingMode::SKIP, ProcessingMode::BUFFERED,
ProcessingMode::STREAMED, ProcessingMode::SKIP, ProcessingMode::SEND);

const std::vector<ProcessingMode> config_modes = {allowed};
const AllowedOverrideModesSet set(config_modes);

EXPECT_FALSE(set.isModeSupported(candidate));
}

// Verify behavior with multiple allowed modes.
TEST_F(AllowedOverrideModesSetTest, MultipleAllowedModes) {
const ProcessingMode mode1 =
createMode(ProcessingMode::SEND, ProcessingMode::SEND, ProcessingMode::NONE,
ProcessingMode::NONE, ProcessingMode::SKIP, ProcessingMode::SKIP);

const ProcessingMode mode2 =
createMode(ProcessingMode::SKIP, ProcessingMode::SKIP, ProcessingMode::BUFFERED,
ProcessingMode::BUFFERED, ProcessingMode::SEND, ProcessingMode::SEND);

const std::vector<ProcessingMode> config_modes = {mode1, mode2};
const AllowedOverrideModesSet set(config_modes);

EXPECT_TRUE(set.isModeSupported(mode1));
EXPECT_TRUE(set.isModeSupported(mode2));

// A mix of mode1 and mode2 should NOT be supported
const ProcessingMode mixed =
createMode(ProcessingMode::SEND, ProcessingMode::SEND, // Matches mode1
ProcessingMode::BUFFERED, ProcessingMode::BUFFERED, // Matches mode2
ProcessingMode::SKIP, ProcessingMode::SKIP);
EXPECT_FALSE(set.isModeSupported(mixed));
}

// Verify behavior with an empty set.
TEST_F(AllowedOverrideModesSetTest, EmptySetReturnsFalse) {
const std::vector<ProcessingMode> config_modes = {}; // Empty
const AllowedOverrideModesSet set(config_modes);

const ProcessingMode candidate =
createMode(ProcessingMode::SEND, ProcessingMode::SEND, ProcessingMode::NONE,
ProcessingMode::NONE, ProcessingMode::SKIP, ProcessingMode::SKIP);

EXPECT_TRUE(set.empty());
EXPECT_FALSE(set.isModeSupported(candidate));
}
} // namespace
} // namespace ExternalProcessing
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
Loading