Skip to content

Commit

Permalink
ext_authz: filter state access logging stats PREP (envoyproxy#34945)
Browse files Browse the repository at this point in the history
Adds a temporarily unused ExtAuthzLoggingInfo to
store stats in filter status. Also adds a field to ext_authz config
which will populate a filter_metadata field in the ExtAuthzLoggingInfo.

Signed-off-by: antoniovleonti <[email protected]>
  • Loading branch information
antoniovleonti authored Jul 23, 2024
1 parent 69a9c7c commit ff6ced1
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "envoy/type/matcher/v3/metadata.proto";
import "envoy/type/matcher/v3/string.proto";
import "envoy/type/v3/http_status.proto";

import "google/protobuf/struct.proto";
import "google/protobuf/wrappers.proto";

import "envoy/annotations/deprecation.proto";
Expand All @@ -29,7 +30,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// External Authorization :ref:`configuration overview <config_http_filters_ext_authz>`.
// [#extension: envoy.filters.http.ext_authz]

// [#next-free-field: 28]
// [#next-free-field: 29]
message ExtAuthz {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.http.ext_authz.v3.ExtAuthz";
Expand Down Expand Up @@ -291,6 +292,11 @@ message ExtAuthz {
//
// If unset, defaults to true.
google.protobuf.BoolValue enable_dynamic_metadata_ingestion = 27;

// Additional metadata to be added to the filter state for logging purposes. The metadata will be
// added to StreamInfo's filter state under the namespace corresponding to the ext_authz filter
// name.
google.protobuf.Struct filter_metadata = 28;
}

// Configuration for buffering the request data.
Expand Down
5 changes: 5 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ removed_config_or_runtime:
path.
new_features:
- area: ext_authz
change: |
Added config field
:ref:`filter_metadata <envoy_v3_api_field_extensions.filters.http.ext_authz.v3.ExtAuthz.filter_metadata>`
for injecting arbitrary data to the filter state for logging.
deprecated:
11 changes: 11 additions & 0 deletions source/extensions/filters/http/ext_authz/ext_authz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::ext_authz::v3
enable_dynamic_metadata_ingestion_(
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enable_dynamic_metadata_ingestion, true)),
runtime_(factory_context.runtime()), http_context_(factory_context.httpContext()),
filter_metadata_(config.has_filter_metadata() ? absl::optional(config.filter_metadata())
: absl::nullopt),
filter_enabled_(config.has_filter_enabled()
? absl::optional<Runtime::FractionalPercent>(
Runtime::FractionalPercent(config.filter_enabled(), runtime_))
Expand Down Expand Up @@ -376,6 +378,15 @@ Http::FilterMetadataStatus Filter::encodeMetadata(Http::MetadataMap&) {

void Filter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) {
decoder_callbacks_ = &callbacks;
const Envoy::StreamInfo::FilterStateSharedPtr& filter_state =
decoder_callbacks_->streamInfo().filterState();
if (config_->filterMetadata().has_value() &&
!filter_state->hasData<ExtAuthzLoggingInfo>(decoder_callbacks_->filterConfigName())) {
filter_state->setData(decoder_callbacks_->filterConfigName(),
std::make_shared<ExtAuthzLoggingInfo>(*config_->filterMetadata()),
Envoy::StreamInfo::FilterState::StateType::ReadOnly,
Envoy::StreamInfo::FilterState::LifeSpan::Request);
}
}

void Filter::setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks& callbacks) {
Expand Down
14 changes: 14 additions & 0 deletions source/extensions/filters/http/ext_authz/ext_authz.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ struct ExtAuthzFilterStats {
ALL_EXT_AUTHZ_FILTER_STATS(GENERATE_COUNTER_STRUCT)
};

class ExtAuthzLoggingInfo : public Envoy::StreamInfo::FilterState::Object {
public:
explicit ExtAuthzLoggingInfo(const Envoy::ProtobufWkt::Struct& filter_metadata)
: filter_metadata_(filter_metadata) {}

const ProtobufWkt::Struct& filterMetadata() const { return filter_metadata_; }

private:
const Envoy::ProtobufWkt::Struct filter_metadata_;
};

/**
* Configuration for the External Authorization (ext_authz) filter.
*/
Expand Down Expand Up @@ -139,6 +150,8 @@ class FilterConfig {
bool includeTLSSession() const { return include_tls_session_; }
const LabelsMap& destinationLabels() const { return destination_labels_; }

const absl::optional<ProtobufWkt::Struct>& filterMetadata() const { return filter_metadata_; }

bool chargeClusterResponseStats() const { return charge_cluster_response_stats_; }

const Filters::Common::ExtAuthz::MatcherSharedPtr& allowedHeadersMatcher() const {
Expand Down Expand Up @@ -189,6 +202,7 @@ class FilterConfig {
Runtime::Loader& runtime_;
Http::Context& http_context_;
LabelsMap destination_labels_;
const absl::optional<ProtobufWkt::Struct> filter_metadata_;

const absl::optional<Runtime::FractionalPercent> filter_enabled_;
const absl::optional<Matchers::MetadataMatcher> filter_enabled_metadata_;
Expand Down
68 changes: 68 additions & 0 deletions test/extensions/filters/http/ext_authz/ext_authz_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ namespace HttpFilters {
namespace ExtAuthz {
namespace {

constexpr char FilterConfigName[] = "ext_authz_filter";

template <class T> class HttpFilterTestBase : public T {
public:
HttpFilterTestBase() {}
Expand All @@ -70,6 +72,7 @@ template <class T> class HttpFilterTestBase : public T {
"ext_authz_prefix", factory_context_);
client_ = new Filters::Common::ExtAuthz::MockClient();
filter_ = std::make_unique<Filter>(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_);
addr_ = std::make_shared<Network::Address::Ipv4Instance>("1.2.3.4", 1111);
Expand Down Expand Up @@ -2926,6 +2929,71 @@ TEST_P(HttpFilterTestParam, ImmediateOkResponse) {
EXPECT_EQ(1U, config_->stats().ok_.value());
}

TEST_F(HttpFilterTest, LoggingInfoOK) {
InSequence s;

initialize(R"EOF(
grpc_service:
envoy_grpc:
cluster_name: "ext_authz_server"
filter_metadata:
foo: "bar"
)EOF");

prepareCheck();

Filters::Common::ExtAuthz::Response response{};
response.status = Filters::Common::ExtAuthz::CheckStatus::OK;

EXPECT_CALL(*client_, check(_, _, _, _))
.WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks,
const envoy::service::auth::v3::CheckRequest&, Tracing::Span&,
const StreamInfo::StreamInfo&) -> void {
callbacks.onComplete(std::make_unique<Filters::Common::ExtAuthz::Response>(response));
}));
EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()).Times(0);
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false));
EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false));
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_));

auto filter_state = decoder_filter_callbacks_.streamInfo().filterState();
ASSERT_TRUE(filter_state->hasData<ExtAuthzLoggingInfo>(FilterConfigName));

auto logging_info = filter_state->getDataReadOnly<ExtAuthzLoggingInfo>(FilterConfigName);
ASSERT_TRUE(logging_info->filterMetadata().fields().contains("foo"));
EXPECT_EQ(logging_info->filterMetadata().fields().at("foo").string_value(), "bar");
}

// Test that if no filter metadata is configured, filter state is not added to stream info.
TEST_F(HttpFilterTest, LoggingInfoEmpty) {
InSequence s;

initialize(R"EOF(
grpc_service:
envoy_grpc:
cluster_name: "ext_authz_server"
)EOF");

prepareCheck();

Filters::Common::ExtAuthz::Response response{};
response.status = Filters::Common::ExtAuthz::CheckStatus::OK;

EXPECT_CALL(*client_, check(_, _, _, _))
.WillOnce(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks,
const envoy::service::auth::v3::CheckRequest&, Tracing::Span&,
const StreamInfo::StreamInfo&) -> void {
callbacks.onComplete(std::make_unique<Filters::Common::ExtAuthz::Response>(response));
}));
EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()).Times(0);
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, false));
EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false));
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_));

auto filter_state = decoder_filter_callbacks_.streamInfo().filterState();
EXPECT_FALSE(filter_state->hasData<ExtAuthzLoggingInfo>(FilterConfigName));
}

// Test that an synchronous denied response from the authorization service passing additional HTTP
// attributes to the downstream.
TEST_P(HttpFilterTestParam, ImmediateDeniedResponseWithHttpAttributes) {
Expand Down

0 comments on commit ff6ced1

Please sign in to comment.