Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ext_authz: add logging options #35698

Merged
merged 34 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
dbb0f42
add config field unused
antoniovleonti Aug 6, 2024
4bf24a7
consume config & add unused stats fields
antoniovleonti Aug 7, 2024
57dff22
Merge branch 'main' into authzstatimpl
antoniovleonti Aug 8, 2024
3130eb0
update logging info in onComplete and test
antoniovleonti Aug 8, 2024
296060c
fix stream info source and test
antoniovleonti Aug 13, 2024
1bc48f3
simplify unit tests
antoniovleonti Aug 14, 2024
36e7ee3
fix potential null dereference and add unit test coverage
antoniovleonti Aug 20, 2024
e439481
Merge branch 'main' into authzstatimpl
antoniovleonti Aug 20, 2024
8903287
Merge branch 'main' into authzstatimpl
antoniovleonti Aug 21, 2024
0058dd2
Merge branch 'main' into authzstatimpl
antoniovleonti Aug 22, 2024
bddf78d
only add filter state for emit_filter_state_stats
antoniovleonti Aug 27, 2024
d0373ea
update unit test & add tests for error & denied resp
antoniovleonti Aug 27, 2024
6e08841
check start time has value
antoniovleonti Aug 27, 2024
2b35d1d
clean up existing integration test
antoniovleonti Aug 27, 2024
a0ca1b0
add timeout test
antoniovleonti Aug 27, 2024
af59ced
only add filter state stats if request is being made
antoniovleonti Aug 28, 2024
78b6eb4
parameterize integration test to cover more cases
antoniovleonti Aug 28, 2024
008d785
fix formatting
antoniovleonti Aug 28, 2024
5c29e12
Merge branch 'main' into authzstatimpl
antoniovleonti Aug 28, 2024
bdddd61
change API to reflect that filter_metadata is only used when stats lo…
antoniovleonti Aug 28, 2024
8346558
fix protobufwkt direct usage
antoniovleonti Aug 28, 2024
3ba5039
fix formatting
antoniovleonti Aug 28, 2024
11097ed
fix doc links
antoniovleonti Aug 28, 2024
19f7290
revert API change
antoniovleonti Sep 5, 2024
230de4e
Merge branch 'main' into authzstatimpl
antoniovleonti Sep 5, 2024
b9563f2
make latency optional to avoid initialization weirdness
antoniovleonti Sep 5, 2024
391330c
fix logging test filter
antoniovleonti Sep 6, 2024
ed80a3f
Merge branch 'main' into authzstatimpl
antoniovleonti Sep 9, 2024
01312be
add another test for coverage
antoniovleonti Sep 9, 2024
fb39ca7
Merge branch 'authzstatimpl' of https://github.com/antoniovleonti/env…
antoniovleonti Sep 9, 2024
32dd77a
remove unnecessary check and clean up unit tests
antoniovleonti Sep 10, 2024
92abf7a
Merge branch 'main' into authzstatimpl
antoniovleonti Sep 10, 2024
b6c5258
remove unnecessary shared ptr copies
antoniovleonti Sep 10, 2024
4cbfcdd
Merge branch 'main' into authzstatimpl
antoniovleonti Sep 11, 2024
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
24 changes: 22 additions & 2 deletions api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,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: 29]
// [#next-free-field: 30]
message ExtAuthz {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.http.ext_authz.v3.ExtAuthz";
Expand Down Expand Up @@ -292,10 +292,30 @@ message ExtAuthz {
// If unset, defaults to true.
google.protobuf.BoolValue enable_dynamic_metadata_ingestion = 27;

// Deprecated in favor of
// :ref:`LoggingOptions.filter_metadata<envoy_v3_api_field_extensions.filters.http.ext_authz.v3.LoggingOptions.filter_metadata>`.
google.protobuf.Struct filter_metadata = 28
antoniovleonti marked this conversation as resolved.
Show resolved Hide resolved
[deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];

// When not empty, the filter will emit per-stream stats for access logging. The filter state key
// will be the same as the filter name.
//
// If using Envoy GRPC, emits latency, bytes sent / received, upstream info, and upstream cluster
// info. If not using Envoy GRPC, emits only latency. Note that stats are ONLY added to filter
// state if a check request is actually made to an ext_authz service.
//
// If this is empty the filter will not emit any filter state.
LoggingOptions logging_options = 29;
}

message LoggingOptions {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.http.ext_authz.v3.LoggingOptions";

// 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;
google.protobuf.Struct filter_metadata = 1;
}

// Configuration for buffering the request data.
Expand Down
7 changes: 7 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ new_features:
change: |
Added :ref:`delay_deny <envoy_v3_api_msg_extensions.filters.network.rbac.v3.RBAC>` to support deny connection after
the configured duration.
- area: ext_authz
change: |
Added :ref:`logging_options <envoy_v3_api_field_extensions.filters.http.ext_authz.v3.ExtAuthz.logging_options>`
which when non-empty enables filter state stats for access logging. Deprecated
:ref:`ExtAuthz.filter_metadata <envoy_v3_api_field_extensions.filters.http.ext_authz.v3.ExtAuthz.filter_metadata>`
in favor of
:ref:`LoggingOptions.filter_metadata <envoy_v3_api_field_extensions.filters.http.ext_authz.v3.LoggingOptions.filter_metadata>`.
- area: http3
change: |
``http3_protocol_options`` in ``HttpConnectionManager`` has been upgraded to general access.
Expand Down
5 changes: 5 additions & 0 deletions envoy/grpc/async_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ class AsyncRequest {
* Signals that the request should be cancelled. No further callbacks will be invoked.
*/
virtual void cancel() PURE;

/**
* Returns the underlying stream info.
*/
virtual const StreamInfo::StreamInfo& streamInfo() const PURE;
};

/**
Expand Down
4 changes: 4 additions & 0 deletions source/common/grpc/async_client_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ void AsyncRequestImpl::cancel() {
this->resetStream();
}

const StreamInfo::StreamInfo& AsyncRequestImpl::streamInfo() const {
return AsyncStreamImpl::streamInfo();
}

void AsyncRequestImpl::onCreateInitialMetadata(Http::RequestHeaderMap& metadata) {
Tracing::HttpTraceContext trace_context(metadata);
Tracing::UpstreamContext upstream_context(nullptr, // host_
Expand Down
2 changes: 2 additions & 0 deletions source/common/grpc/async_client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,10 @@ class AsyncRequestImpl : public AsyncRequest, public AsyncStreamImpl, RawAsyncSt

// Grpc::AsyncRequest
void cancel() override;
const StreamInfo::StreamInfo& streamInfo() const override;

private:
using AsyncStreamImpl::streamInfo;
// Grpc::AsyncStreamCallbacks
void onCreateInitialMetadata(Http::RequestHeaderMap& metadata) override;
void onReceiveInitialMetadata(Http::ResponseHeaderMapPtr&&) override;
Expand Down
4 changes: 4 additions & 0 deletions source/common/grpc/google_async_client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,12 @@ class GoogleAsyncRequestImpl : public AsyncRequest,

// Grpc::AsyncRequest
void cancel() override;
const StreamInfo::StreamInfo& streamInfo() const override {
return GoogleAsyncStreamImpl::streamInfo();
}

private:
using GoogleAsyncStreamImpl::streamInfo;
// Grpc::RawAsyncStreamCallbacks
void onCreateInitialMetadata(Http::RequestHeaderMap& metadata) override;
void onReceiveInitialMetadata(Http::ResponseHeaderMapPtr&&) override;
Expand Down
5 changes: 5 additions & 0 deletions source/extensions/filters/common/ext_authz/ext_authz.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ class Client {
virtual void check(RequestCallbacks& callback,
const envoy::service::auth::v3::CheckRequest& request,
Tracing::Span& parent_span, const StreamInfo::StreamInfo& stream_info) PURE;

/**
* Returns streamInfo of the current request if possible. By default just return a nullptr.
*/
virtual StreamInfo::StreamInfo const* streamInfo() const { return nullptr; }
};

using ClientPtr = std::unique_ptr<Client>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class GrpcClientImpl : public Client,
void cancel() override;
void check(RequestCallbacks& callbacks, const envoy::service::auth::v3::CheckRequest& request,
Tracing::Span& parent_span, const StreamInfo::StreamInfo& stream_info) override;
StreamInfo::StreamInfo const* streamInfo() const override {
return request_ ? &request_->streamInfo() : nullptr;
}

// Grpc::AsyncRequestCallbacks
void onCreateInitialMetadata(Http::RequestHeaderMap&) override {}
Expand Down
65 changes: 55 additions & 10 deletions source/extensions/filters/http/ext_authz/ext_authz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,12 @@ 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_metadata_(config.has_logging_options() &&
config.logging_options().has_filter_metadata()
? absl::optional(config.logging_options().filter_metadata())
: absl::nullopt),
emit_filter_state_stats_(config.has_logging_options()),
client_is_envoy_grpc_(config.has_grpc_service() && config.grpc_service().has_envoy_grpc()),
filter_enabled_(config.has_filter_enabled()
? absl::optional<Runtime::FractionalPercent>(
Runtime::FractionalPercent(config.filter_enabled(), runtime_))
Expand Down Expand Up @@ -175,6 +179,20 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) {
return;
}

// Now that we'll definitely be making the request, add filter state stats if configured to do so.
const Envoy::StreamInfo::FilterStateSharedPtr& filter_state =
decoder_callbacks_->streamInfo().filterState();
if (config_->emitFilterStateStats() &&
!filter_state->hasData<ExtAuthzLoggingInfo>(decoder_callbacks_->filterConfigName())) {
filter_state->setData(decoder_callbacks_->filterConfigName(),
std::make_shared<ExtAuthzLoggingInfo>(config_->filterMetadata()),
Envoy::StreamInfo::FilterState::StateType::Mutable,
Envoy::StreamInfo::FilterState::LifeSpan::Request);

logging_info_ =
filter_state->getDataMutable<ExtAuthzLoggingInfo>(decoder_callbacks_->filterConfigName());
}

auto&& maybe_merged_per_route_config =
Http::Utility::getMergedPerFilterConfig<FilterConfigPerRoute>(
decoder_callbacks_, [](FilterConfigPerRoute& cfg_base, const FilterConfigPerRoute& cfg) {
Expand Down Expand Up @@ -378,14 +396,39 @@ 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::updateLoggingInfo() {
if (!config_->emitFilterStateStats()) {
return;
}

// Latency is the only stat available if we aren't using envoy grpc.
if (start_time_.has_value()) {
logging_info_->setLatency(std::chrono::duration_cast<std::chrono::microseconds>(
decoder_callbacks_->dispatcher().timeSource().monotonicTime() - start_time_.value()));
}

if (!config_->clientIsEnvoyGrpc()) {
return;
}
antoniovleonti marked this conversation as resolved.
Show resolved Hide resolved
auto const* stream_info = client_->streamInfo();
if (stream_info == nullptr) {
return;
}

const auto& bytes_meter = stream_info->getUpstreamBytesMeter();
if (bytes_meter != nullptr) {
logging_info_->setBytesSent(bytes_meter->wireBytesSent());
logging_info_->setBytesReceived(bytes_meter->wireBytesReceived());
}
if (stream_info->upstreamInfo().has_value()) {
logging_info_->setUpstreamHost(stream_info->upstreamInfo()->upstreamHost());
}
absl::optional<Upstream::ClusterInfoConstSharedPtr> cluster_info =
stream_info->upstreamClusterInfo();
if (cluster_info) {
logging_info_->setClusterInfo(std::move(*cluster_info));
}
}

Expand Down Expand Up @@ -416,6 +459,8 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) {
using Filters::Common::ExtAuthz::CheckStatus;
Stats::StatName empty_stat_name;

updateLoggingInfo();
antoniovleonti marked this conversation as resolved.
Show resolved Hide resolved

if (!response->dynamic_metadata.fields().empty()) {
if (!config_->enableDynamicMetadataIngestion()) {
ENVOY_STREAM_LOG(trace,
Expand Down
34 changes: 31 additions & 3 deletions source/extensions/filters/http/ext_authz/ext_authz.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,34 @@ struct ExtAuthzFilterStats {

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

const ProtobufWkt::Struct& filterMetadata() const { return filter_metadata_; }
const absl::optional<ProtobufWkt::Struct>& filterMetadata() const { return filter_metadata_; }
std::chrono::microseconds latency() const { return latency_; };
absl::optional<uint64_t> bytesSent() const { return bytes_sent_; }
absl::optional<uint64_t> bytesReceived() const { return bytes_received_; }
Upstream::ClusterInfoConstSharedPtr clusterInfo() const { return cluster_info_; }
Upstream::HostDescriptionConstSharedPtr upstreamHost() const { return upstream_host_; }

void setLatency(std::chrono::microseconds ms) { latency_ = ms; };
void setBytesSent(uint64_t bytes_sent) { bytes_sent_ = bytes_sent; }
void setBytesReceived(uint64_t bytes_received) { bytes_received_ = bytes_received; }
void setClusterInfo(Upstream::ClusterInfoConstSharedPtr cluster_info) {
cluster_info_ = cluster_info;
}
void setUpstreamHost(Upstream::HostDescriptionConstSharedPtr upstream_host) {
upstream_host_ = upstream_host;
antoniovleonti marked this conversation as resolved.
Show resolved Hide resolved
}

private:
const Envoy::ProtobufWkt::Struct filter_metadata_;
const absl::optional<Envoy::ProtobufWkt::Struct> filter_metadata_;
std::chrono::microseconds latency_;
// The following stats are populated for ext_authz filters using Envoy gRPC only.
absl::optional<uint64_t> bytes_sent_;
absl::optional<uint64_t> bytes_received_;
Upstream::ClusterInfoConstSharedPtr cluster_info_;
Upstream::HostDescriptionConstSharedPtr upstream_host_;
};

/**
Expand Down Expand Up @@ -152,6 +173,9 @@ class FilterConfig {

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

bool emitFilterStateStats() const { return emit_filter_state_stats_; }
bool clientIsEnvoyGrpc() const { return client_is_envoy_grpc_; }

bool chargeClusterResponseStats() const { return charge_cluster_response_stats_; }

const Filters::Common::ExtAuthz::MatcherSharedPtr& allowedHeadersMatcher() const {
Expand Down Expand Up @@ -203,6 +227,8 @@ class FilterConfig {
Http::Context& http_context_;
LabelsMap destination_labels_;
const absl::optional<ProtobufWkt::Struct> filter_metadata_;
const bool emit_filter_state_stats_;
const bool client_is_envoy_grpc_;

const absl::optional<Runtime::FractionalPercent> filter_enabled_;
const absl::optional<Matchers::MetadataMatcher> filter_enabled_metadata_;
Expand Down Expand Up @@ -337,6 +363,7 @@ class Filter : public Logger::Loggable<Logger::Id::ext_authz>,
void initiateCall(const Http::RequestHeaderMap& headers);
void continueDecoding();
bool isBufferFull(uint64_t num_bytes_processing) const;
void updateLoggingInfo();

// This holds a set of flags defined in per-route configuration.
struct PerRouteFlags {
Expand Down Expand Up @@ -370,6 +397,7 @@ class Filter : public Logger::Loggable<Logger::Id::ext_authz>,
Upstream::ClusterInfoConstSharedPtr cluster_;
// The stats for the filter.
ExtAuthzFilterStats stats_;
ExtAuthzLoggingInfo* logging_info_;

// This is used to hold the final configs after we merge them with per-route configs.
bool allow_partial_message_{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ TEST_F(ExtAuthzGrpcClientTest, AuthorizationOk) {
client_->onSuccess(std::move(check_response), span_);
}

TEST_F(ExtAuthzGrpcClientTest, StreamInfo) {
initialize();

envoy::service::auth::v3::CheckRequest request;
EXPECT_CALL(*async_client_, sendRaw(_, _, _, _, _, _)).WillOnce(Return(&async_request_));
client_->check(request_callbacks_, request, Tracing::NullSpan::instance(), stream_info_);

NiceMock<StreamInfo::MockStreamInfo> ext_authz_stream_info;
EXPECT_CALL(async_request_, streamInfo()).WillOnce(ReturnRef(ext_authz_stream_info));
EXPECT_NE(client_->streamInfo(), nullptr);

EXPECT_CALL(async_request_, cancel());
client_->cancel();
}

// Test the client when an ok response is received.
TEST_F(ExtAuthzGrpcClientTest, AuthorizationOkWithAllAtributes) {
initialize();
Expand Down
1 change: 1 addition & 0 deletions test/extensions/filters/common/ext_authz/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class MockClient : public Client {
MOCK_METHOD(void, check,
(RequestCallbacks & callbacks, const envoy::service::auth::v3::CheckRequest& request,
Tracing::Span& parent_span, const StreamInfo::StreamInfo& stream_info));
MOCK_METHOD(StreamInfo::StreamInfo const*, streamInfo, (), (const));
};

class MockRequestCallbacks : public RequestCallbacks {
Expand Down
27 changes: 27 additions & 0 deletions test/extensions/filters/http/ext_authz/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ load(
load(
"//test/extensions:extensions_build_system.bzl",
"envoy_extension_cc_test",
"envoy_extension_cc_test_library",
)

licenses(["notice"]) # Apache 2
Expand Down Expand Up @@ -73,6 +74,8 @@ envoy_extension_cc_test(
],
extension_names = ["envoy.filters.http.ext_authz"],
deps = [
":ext_authz_fuzz_proto_cc_proto",
":logging_test_filter_lib",
"//source/extensions/clusters/strict_dns:strict_dns_cluster_lib",
"//source/extensions/filters/http/ext_authz:config",
"//source/extensions/listener_managers/validation_listener_manager:validation_listener_manager_lib",
Expand Down Expand Up @@ -149,3 +152,27 @@ envoy_cc_test_library(
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
],
)

envoy_proto_library(
name = "logging_test_filter_proto",
srcs = ["logging_test_filter.proto"],
)

envoy_extension_cc_test_library(
name = "logging_test_filter_lib",
srcs = [
"logging_test_filter.cc",
],
extension_names = ["envoy.filters.http.ext_authz"],
deps = [
":logging_test_filter_proto_cc_proto",
"//envoy/http:filter_interface",
"//envoy/registry",
"//envoy/server:filter_config_interface",
"//source/common/router:string_accessor_lib",
"//source/extensions/filters/http/common:factory_base_lib",
"//source/extensions/filters/http/common:pass_through_filter_lib",
"//source/extensions/filters/http/ext_authz",
"//test/test_common:utility_lib",
],
)
Loading
Loading