Skip to content

Commit

Permalink
filter: add capability to selectively dump request/response state on …
Browse files Browse the repository at this point in the history
…crash

Signed-off-by: Rohit Agrawal <[email protected]>
  • Loading branch information
agrawroh committed Dec 26, 2024
1 parent f122866 commit 44c74e9
Show file tree
Hide file tree
Showing 12 changed files with 428 additions and 10 deletions.
62 changes: 61 additions & 1 deletion api/envoy/config/bootstrap/v3/bootstrap.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// <config_overview_bootstrap>` for more detail.

// Bootstrap :ref:`configuration overview <config_overview_bootstrap>`.
// [#next-free-field: 42]
// [#next-free-field: 43]
message Bootstrap {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.bootstrap.v2.Bootstrap";
Expand Down Expand Up @@ -415,6 +415,66 @@ message Bootstrap {
// Optional configuration for memory allocation manager.
// Memory releasing is only supported for `tcmalloc allocator <https://github.com/google/tcmalloc>`_.
MemoryAllocatorManager memory_allocator_manager = 41;

// Optional configuration for controlling what state gets included in the state dumps after unexpected termination.
// This allows operators to selectively enable/disable certain types of information from being included in state
// dumps for security or performance reasons.
//
// If not specified, all state will be dumped (default behavior).
//
// This can be used to:
// * Completely disable dumping of certain types of request or response data.
// * Specify an allow list of specific headers that should be included.
// * Control dumping of stream info state.
//
DumpStateConfig dump_state_config = 42;
}

message DumpStateConfig {
// Configuration for controlling what state gets dumped when the ``dumpState()`` function is called. This allows
// selectively enabling/disabling certain types of information from being included in state dumps, which can be useful
// for security or performance reasons.
message DumpConfig {
// Configures how this type of state should be dumped.
oneof config {
option (validate.required) = true;

// When set to ``true``, completely disables dumping of this type of state information. This provides a simple way
// to completely exclude certain data from dumps.
bool disabled = 1;

// Provides an allow-list of specific headers that should be included in dumps. Only headers in this list will be
// dumped and the remaining will be excluded. This provides granular control over exactly which headers appear in
// the dumps.
HeaderList allowed_headers = 2;
}

// List of specific headers that should be included in dumps when using an allow-list.
message HeaderList {
// The list of header names that should be included in the dumps. Any headers not in this list will be excluded
// from dumps. Header names should match the exact header key.
repeated string headers = 1;
}
}

// Controls dumping of HTTP request headers. When not specified, all request headers will be dumped.
// Can be disabled entirely or limited to specific headers via allow-list.
DumpConfig request_headers = 1;

// Controls dumping of HTTP request trailers. When not specified, all request trailers will be dumped.
// Can be disabled entirely or limited to specific trailers via allow-list.
DumpConfig request_trailers = 2;

// Controls dumping of HTTP response headers. When not specified, all response headers will be dumped.
// Can be disabled entirely or limited to specific headers via allow-list.
DumpConfig response_headers = 3;

// Controls dumping of HTTP response trailers. When not specified, all response trailers will be dumped.
// Can be disabled entirely or limited to specific trailers via allow-list.
DumpConfig response_trailers = 4;

// Controls dumping of stream info state. When not specified, all stream info will be dumped.
DumpConfig stream_info = 5;
}

// Administration interface :ref:`operations documentation
Expand Down
6 changes: 6 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,12 @@ new_features:
change: |
Add the option to reduce the rate limit budget based on request/response contexts on stream done.
See :ref:`apply_on_stream_done <envoy_v3_api_field_config.route.v3.RateLimit.apply_on_stream_done>` for more details.
- area: filter_manager
change: |
Added new configuration options in :ref:`bootstrap dump_state_config<envoy_v3_api_field_config.bootstrap.v3.Bootstrap.dump_state_config>`
to redact sensitive headers in the state dump. This provide users more control over sensitive data exposure during
fatal error dumps. Users can choose to disable dumping the entire request or response headers or selectively pass a
list of allowed headers.
deprecated:
- area: rbac
Expand Down
5 changes: 5 additions & 0 deletions envoy/router/router_filter_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ class RouterFilterInterface {
* @returns the number of attempts (e.g. retries) performed for this stream.
*/
virtual uint32_t attemptCount() const PURE;

/*
* @returns the dump state config from bootstrap.
*/
virtual const envoy::config::bootstrap::v3::DumpStateConfig* dumpStateConfig() const PURE;
};

} // namespace Router
Expand Down
2 changes: 1 addition & 1 deletion source/common/http/async_client_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ AsyncClientImpl::AsyncClientImpl(Upstream::ClusterInfoConstSharedPtr cluster,
*stats_store.rootScope(), cm, factory_context.runtime(),
factory_context.api().randomGenerator(), std::move(shadow_writer), true, false, false,
false, false, false, Protobuf::RepeatedPtrField<std::string>{}, dispatcher.timeSource(),
http_context, router_context)),
http_context, router_context, nullptr)),
dispatcher_(dispatcher), runtime_(factory_context.runtime()),
local_reply_(LocalReply::Factory::createDefault()) {}

Expand Down
4 changes: 4 additions & 0 deletions source/common/http/conn_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
still_alive_.reset();
}

const envoy::config::bootstrap::v3::DumpStateConfig* dumpStateConfig() const override {
return filter_manager_.dumpStateConfig();
}

void log(AccessLog::AccessLogType type);
void completeRequest();

Expand Down
103 changes: 98 additions & 5 deletions source/common/http/filter_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "source/common/common/linked_object.h"
#include "source/common/common/logger.h"
#include "source/common/grpc/common.h"
#include "source/common/http/header_map_impl.h"
#include "source/common/http/header_utility.h"
#include "source/common/http/headers.h"
#include "source/common/http/matching/data_impl.h"
Expand Down Expand Up @@ -393,6 +394,11 @@ class FilterManagerCallbacks {
public:
virtual ~FilterManagerCallbacks() = default;

/**
* Returns the current dump state configuration if one exists.
*/
virtual const envoy::config::bootstrap::v3::DumpStateConfig* dumpStateConfig() const PURE;

/**
* Called when the provided headers have been encoded by all the filters in the chain.
* @param response_headers the encoded headers.
Expand Down Expand Up @@ -705,11 +711,47 @@ class FilterManager : public ScopeTrackedObject,
<< DUMP_MEMBER(state_.observed_decode_end_stream_)
<< DUMP_MEMBER(state_.observed_encode_end_stream_) << "\n";

DUMP_DETAILS(filter_manager_callbacks_.requestHeaders());
DUMP_DETAILS(filter_manager_callbacks_.requestTrailers());
DUMP_DETAILS(filter_manager_callbacks_.responseHeaders());
DUMP_DETAILS(filter_manager_callbacks_.responseTrailers());
DUMP_DETAILS(&streamInfo());
const auto* dump_config = filter_manager_callbacks_.dumpStateConfig();

const auto& headers = filter_manager_callbacks_.requestHeaders();
if (headers) {
if (!dump_config) {
DUMP_DETAILS(headers);
} else {
dumpHeadersIfEnabled(os, *headers, dump_config->request_headers(), indent_level);
}
}

const auto& req_trailers = filter_manager_callbacks_.requestTrailers();
if (req_trailers) {
if (!dump_config) {
DUMP_DETAILS(headers);
} else {
dumpHeadersIfEnabled(os, *req_trailers, dump_config->request_trailers(), indent_level);
}
}

const auto& resp_headers = filter_manager_callbacks_.responseHeaders();
if (resp_headers) {
if (!dump_config) {
DUMP_DETAILS(headers);
} else {
dumpHeadersIfEnabled(os, *resp_headers, dump_config->response_headers(), indent_level);
}
}

const auto& resp_trailers = filter_manager_callbacks_.responseTrailers();
if (resp_trailers) {
if (!dump_config) {
DUMP_DETAILS(headers);
} else {
dumpHeadersIfEnabled(os, *resp_trailers, dump_config->response_trailers(), indent_level);
}
}

if (!dump_config || shouldDumpStreamInfo(dump_config->stream_info())) {
DUMP_DETAILS(&streamInfo());
}
}

// FilterChainManager
Expand Down Expand Up @@ -979,6 +1021,47 @@ class FilterManager : public ScopeTrackedObject,
State& state() { return state_; }

private:
bool shouldDumpStreamInfo(
const envoy::config::bootstrap::v3::DumpStateConfig::DumpConfig& config) const {
return !(config.has_disabled() && config.disabled());
}

void dumpHeadersIfEnabled(std::ostream& os, const HeaderMap& headers,
const envoy::config::bootstrap::v3::DumpStateConfig::DumpConfig& config,
int indent_level) const {
const char* spaces = spacesForLevel(indent_level);

// Don't dump anything if explicitly disabled
if (config.has_disabled() && config.disabled()) {
return;
}

if (!config.has_allowed_headers()) {
// Dump all headers
DUMP_DETAILS(&headers);
} else {
// Dump only allowed headers
auto filtered = filterHeaders(headers, config);
DUMP_DETAILS(filtered.get());
}
}

Http::HeaderMapPtr
filterHeaders(const Http::HeaderMap& headers,
const envoy::config::bootstrap::v3::DumpStateConfig::DumpConfig& config) const {
auto filtered_headers = Http::ResponseHeaderMapImpl::create();
const auto& allowed = config.allowed_headers().headers();

for (const auto& header_name : allowed) {
auto values = headers.get(Http::LowerCaseString(header_name));
if (!values.empty()) {
filtered_headers->addCopy(Http::LowerCaseString(header_name),
values[0]->value().getStringView());
}
}
return filtered_headers;
}

friend class DownstreamFilterManager;
class FilterChainFactoryCallbacksImpl : public Http::FilterChainFactoryCallbacks {
public:
Expand Down Expand Up @@ -1240,11 +1323,21 @@ class DownstreamFilterManager : public FilterManager {
streamInfo().downstreamTiming()->lastDownstreamRxByteReceived().has_value();
}

/**
* Returns true if the downstream filter load shed point is configured and load shedding is needed.
*/
bool shouldLoadShed() override {
return downstream_filter_load_shed_point_ != nullptr &&
downstream_filter_load_shed_point_->shouldShedLoad();
}

/**
* Returns the dump state config from the filter manager callbacks.
*/
const envoy::config::bootstrap::v3::DumpStateConfig* dumpStateConfig() const {
return filter_manager_callbacks_.dumpStateConfig();
}

private:
/**
* Sends a local reply by constructing a response and passing it through all the encoder
Expand Down
9 changes: 8 additions & 1 deletion source/common/router/router.cc
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,14 @@ FilterConfig::FilterConfig(Stats::StatName stat_prefix,
: false,
config.strict_check_headers(), context.serverFactoryContext().api().timeSource(),
context.serverFactoryContext().httpContext(),
context.serverFactoryContext().routerContext()) {
context.serverFactoryContext().routerContext(),
[&context]() -> const envoy::config::bootstrap::v3::DumpStateConfig* {
const auto& bootstrap = context.serverFactoryContext().bootstrap();
if (bootstrap.has_dump_state_config()) {
return &bootstrap.dump_state_config();
}
return nullptr;
}()) {
for (const auto& upstream_log : config.upstream_log()) {
upstream_logs_.push_back(AccessLog::AccessLogFactory::fromProto(upstream_log, context));
}
Expand Down
14 changes: 12 additions & 2 deletions source/common/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ class FilterConfig : public Http::FilterChainFactory {
bool flush_upstream_log_on_upstream_stream,
const Protobuf::RepeatedPtrField<std::string>& strict_check_headers,
TimeSource& time_source, Http::Context& http_context,
Router::Context& router_context)
Router::Context& router_context,
const envoy::config::bootstrap::v3::DumpStateConfig* dump_state_config)
: factory_context_(factory_context), router_context_(router_context), scope_(scope),
local_info_(local_info), cm_(cm), runtime_(runtime),
default_stats_(router_context_.statNames(), scope_, stat_prefix),
Expand All @@ -221,7 +222,8 @@ class FilterConfig : public Http::FilterChainFactory {
suppress_grpc_request_failure_code_stats_(suppress_grpc_request_failure_code_stats),
flush_upstream_log_on_upstream_stream_(flush_upstream_log_on_upstream_stream),
http_context_(http_context), zone_name_(local_info_.zoneStatName()),
shadow_writer_(std::move(shadow_writer)), time_source_(time_source) {
shadow_writer_(std::move(shadow_writer)), time_source_(time_source),
dump_state_config_(dump_state_config) {
if (!strict_check_headers.empty()) {
strict_check_headers_ = std::make_unique<HeaderVector>();
for (const auto& header : strict_check_headers) {
Expand Down Expand Up @@ -261,6 +263,10 @@ class FilterConfig : public Http::FilterChainFactory {
return false;
}

const envoy::config::bootstrap::v3::DumpStateConfig* dumpStateConfig() const {
return dump_state_config_;
}

using HeaderVector = std::vector<Http::LowerCaseString>;
using HeaderVectorPtr = std::unique_ptr<HeaderVector>;

Expand Down Expand Up @@ -295,6 +301,7 @@ class FilterConfig : public Http::FilterChainFactory {
private:
ShadowWriterPtr shadow_writer_;
TimeSource& time_source_;
const envoy::config::bootstrap::v3::DumpStateConfig* dump_state_config_;
};

using FilterConfigSharedPtr = std::shared_ptr<FilterConfig>;
Expand Down Expand Up @@ -484,6 +491,9 @@ class Filter : Logger::Loggable<Logger::Id::router>,
absl::optional<std::chrono::milliseconds> dynamicMaxStreamDuration() const override {
return dynamic_max_stream_duration_;
}
const envoy::config::bootstrap::v3::DumpStateConfig* dumpStateConfig() const override {
return config_->dumpStateConfig();
}
Http::RequestHeaderMap* downstreamHeaders() override { return downstream_headers_; }
Http::RequestTrailerMap* downstreamTrailers() override { return downstream_trailers_; }
bool downstreamResponseStarted() const override { return downstream_response_started_; }
Expand Down
4 changes: 4 additions & 0 deletions source/common/router/upstream_request.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "envoy/http/codes.h"
#include "envoy/http/conn_pool.h"
#include "envoy/http/filter.h"
#include "envoy/router/router_filter_interface.h"
#include "envoy/stats/scope.h"
#include "envoy/tcp/conn_pool.h"

Expand Down Expand Up @@ -305,6 +306,9 @@ class UpstreamRequestFilterManagerCallbacks : public Http::FilterManagerCallback
}
return {};
}
const envoy::config::bootstrap::v3::DumpStateConfig* dumpStateConfig() const override {
return upstream_request_.parent_.dumpStateConfig();
}
Http::ResponseTrailerMapOptRef responseTrailers() override {
if (response_trailers_) {
return {*response_trailers_};
Expand Down
3 changes: 3 additions & 0 deletions source/common/tcp_proxy/upstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@ class CombinedUpstream : public GenericUpstream, public Envoy::Router::RouterFil
bool downstreamResponseStarted() const override { return false; }
bool downstreamEndStream() const override { return false; }
uint32_t attemptCount() const override { return 0; }
const envoy::config::bootstrap::v3::DumpStateConfig* dumpStateConfig() const override {
return nullptr;
}

protected:
void onResetEncoder(Network::ConnectionEvent event, bool inform_downstream = true);
Expand Down
Loading

0 comments on commit 44c74e9

Please sign in to comment.