Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
3 changes: 2 additions & 1 deletion api/envoy/config/filter/accesslog/v2/accesslog.proto
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ message ResponseFlagFilter {
"RLSE",
"DC",
"URX",
"SI"
"SI",
"IH"
]
}];
}
Expand Down
2 changes: 1 addition & 1 deletion api/envoy/config/filter/http/router/v2/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal")
load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this because we want to validate values set for strict_check_headers in the config in api/envoy/config/filter/http/router/v2/router.proto#L14, which requires inclusion of com_envoyproxy_protoc_gen_validate/validate/validate.proto.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm no bazel expert but I thought this was only necessary if you directly referenced the api_go_proto_library rule in this BUILD file which it doesn't seem like you're doing? I could be totally wrong here, so feel free to leave it if it's necessary for the build

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will repush with this removed and add it back if it turns out to actually result in failed builds.

Hmm.. I rebuilt //source/exe:envoy-static (not from a clean build though) with this change removed and things seemed to work. There are other places in api/envoy/config/filter/http/** that reference proto validation without this declaration in their BUILD files.


licenses(["notice"]) # Apache 2

Expand Down
28 changes: 28 additions & 0 deletions api/envoy/config/filter/http/router/v2/router.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import "envoy/config/filter/accesslog/v2/accesslog.proto";

import "google/protobuf/wrappers.proto";

import "validate/validate.proto";

// [#protodoc-title: Router]
// Router :ref:`configuration overview <config_http_filters_router>`.

Expand All @@ -36,4 +38,30 @@ message Router {
// <config_http_filters_router_headers_set>`, other Envoy filters and the HTTP
// connection manager may continue to set *x-envoy-* headers.
bool suppress_envoy_headers = 4;

// Specifies a list of HTTP headers to strictly validate. Envoy will reject a
// request and respond with HTTP status 400 if the request contains an invalid
// value for any of the headers listed in this field. Strict header checking
// is only supported for the following headers:
//
// Value must be a ','-delimited list (i.e. no spaces) of supported retry
// policy values:
//
// * :ref:`config_http_filters_router_x-envoy-retry-grpc-on`
// * :ref:`config_http_filters_router_x-envoy-retry-on`
//
// Value must be an integer:
//
// * :ref:`config_http_filters_router_x-envoy-max-retries`
// * :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`
// * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`
repeated string strict_check_headers = 5 [(validate.rules).repeated .items.string = {
Comment thread
xyu-stripe marked this conversation as resolved.
in: [
"x-envoy-upstream-rq-timeout-ms",
"x-envoy-upstream-rq-per-try-timeout-ms",
"x-envoy-max-retries",
"x-envoy-retry-grpc-on",
"x-envoy-retry-on"
]
}];
}
3 changes: 3 additions & 0 deletions api/envoy/data/accesslog/v2/accesslog.proto
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ message ResponseFlags {
REASON_UNSPECIFIED = 0;
// The request was denied by the external authorization service.
EXTERNAL_SERVICE = 1;
// The request was denied because a header value failed a :ref:`strict header check
// <envoy_api_field_config.filter.http.router.v2.Router.strict_check_headers>` failed.
STRICT_HEADER_CHECK_FAILED = 2;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this belongs here, this seems to be dealing with authorization of an endpoint, not about whether the request is valid. If there isn't a similar flag for "invalid request reason" we can probably just drop this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right. It looks like I should instead move this to be a bool field to match treatment of the other response flags in accesslog.proto

}

Reason reason = 1;
Expand Down
2 changes: 2 additions & 0 deletions docs/root/configuration/access_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ The following command operators are supported:
* **RL**: The request was ratelimited locally by the :ref:`HTTP rate limit filter <config_http_filters_rate_limit>` in addition to 429 response code.
* **UAEX**: The request was denied by the external authorization service.
* **RLSE**: The request was rejected because there was an error in rate limit service.
* **IH**: The request was rejected because it set an invalid value for a
:ref:`strictly-checked header <envoy_api_field_config.filter.http.router.v2.Router.strict_check_headers>` in addition to 400 response code.
* **SI**: Stream idle timeout in addition to 408 response code.

%RESPONSE_TX_DURATION%
Expand Down
3 changes: 3 additions & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Version history
* access log: added a new field for route name to file and gRPC access logger.
* access log: added a new field for response code details in :ref:`file access logger<config_access_log_format_response_code_details>` and :ref:`gRPC access logger<envoy_api_field_data.accesslog.v2.HTTPResponseProperties.response_code_details>`.
* access log: added several new variables for exposing information about the downstream TLS connection to :ref:`file access logger<config_access_log_format_response_code_details>` and :ref:`gRPC access logger<envoy_api_field_data.accesslog.v2.AccessLogCommon.tls_properties>`.
* access log: added a new flag for request rejected due to failed strict header check

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: end with period

* admin: the administration interface now includes a :ref:`/ready endpoint <operations_admin_interface>` for easier readiness checks.
* admin: extend :ref:`/runtime_modify endpoint <operations_admin_interface_runtime_modify>` to support parameters within the request body.
* admin: the :ref:`/listener endpoint <operations_admin_interface_listeners>` now returns :ref:`listeners.proto<envoy_api_msg_admin.v2alpha.Listeners>` which includes listener names and ports.
Expand Down Expand Up @@ -59,6 +60,8 @@ Version history
* router: added :ref:`RouteAction's auto_host_rewrite_header <envoy_api_field_route.RouteAction.auto_host_rewrite_header>` to allow upstream host header substitution with some other header's value
* router: added support for UPSTREAM_REMOTE_ADDRESS :ref:`header formatter
<config_http_conn_man_headers_custom_request_headers>`.
* router: add ability to reject a request that includes invalid values for
headers configured in :ref:`strict_check_headers <envoy_api_field_config.filter.http.router.v2.Router.strict_check_headers>`
* runtime: added support for :ref:`flexible layering configuration
<envoy_api_field_config.bootstrap.v2.Bootstrap.layered_runtime>`.
* runtime: added support for statically :ref:`specifying the runtime in the bootstrap configuration
Expand Down
1 change: 1 addition & 0 deletions include/envoy/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class RetryPolicy {
static const uint32_t RETRY_ON_GRPC_UNAVAILABLE = 0x100;
static const uint32_t RETRY_ON_GRPC_INTERNAL = 0x200;
static const uint32_t RETRY_ON_RETRIABLE_STATUS_CODES = 0x400;
static const uint32_t RETRY_UNKNOWN_FIELD_ERROR = 0x800;
// clang-format on

virtual ~RetryPolicy() = default;
Expand Down
6 changes: 5 additions & 1 deletion include/envoy/stream_info/stream_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ enum ResponseFlag {
UpstreamRetryLimitExceeded = 0x8000,
// Request hit the stream idle timeout, triggering a 408.
StreamIdleTimeout = 0x10000,
// Request specified x-envoy-* header values that failed strict header checks.
InvalidEnvoyRequestHeaders = 0x20000,
// ATTENTION: MAKE SURE THIS REMAINS EQUAL TO THE LAST FLAG.
LastFlag = StreamIdleTimeout
LastFlag = InvalidEnvoyRequestHeaders
};

/**
Expand Down Expand Up @@ -95,6 +97,8 @@ struct ResponseCodeDetailValues {
const std::string MissingHost = "missing_host_header";
// The request was rejected due to the request headers being larger than the configured limit.
const std::string RequestHeadersTooLarge = "request_headers_too_large";
// The request was rejected due to x-envoy-* headers failing strict header validation.
const std::string InvalidEnvoyRequestHeaders = "request_headers_failed_strict_check";
// The request was rejected due to the Path or :path header field missing.
const std::string MissingPath = "missing_path_rejected";
// The request was rejected due to using an absolute path on a route not supporting them.
Expand Down
6 changes: 3 additions & 3 deletions source/common/http/async_client_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ AsyncClientImpl::AsyncClientImpl(Upstream::ClusterInfoConstSharedPtr cluster,
Runtime::RandomGenerator& random,
Router::ShadowWriterPtr&& shadow_writer,
Http::Context& http_context)
: cluster_(cluster),
config_("http.async-client.", local_info, stats_store, cm, runtime, random,
std::move(shadow_writer), true, false, false, dispatcher.timeSource(), http_context),
: cluster_(cluster), config_("http.async-client.", local_info, stats_store, cm, runtime, random,
std::move(shadow_writer), true, false, false, {},
dispatcher.timeSource(), http_context),
dispatcher_(dispatcher) {}

AsyncClientImpl::~AsyncClientImpl() {
Expand Down
6 changes: 6 additions & 0 deletions source/common/router/config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ RetryPolicyImpl::RetryPolicyImpl(const envoy::api::v2::route::RetryPolicy& retry
num_retries_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT(retry_policy, num_retries, 1);
retry_on_ = RetryStateImpl::parseRetryOn(retry_policy.retry_on());
retry_on_ |= RetryStateImpl::parseRetryGrpcOn(retry_policy.retry_on());
// NB: parseRetryOn and parseRetryGrpcOn will return a value with
// RetryPolicy::RETRY_UNKNOWN_FIELD_ERROR set unless the input only contains
// supported values for `x-envoy-retry-on` and `x-envoy-retry-grpc-on`,
// respectively. Clear this flag here, since the `retry_on` config option is
// intended to contain retry policies from both.
retry_on_ &= ~RetryPolicy::RETRY_UNKNOWN_FIELD_ERROR;

for (const auto& host_predicate : retry_policy.retry_host_predicate()) {
auto& factory = Envoy::Config::Utility::getAndCheckFactory<Upstream::RetryHostPredicateFactory>(
Expand Down
4 changes: 4 additions & 0 deletions source/common/router/retry_state_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ uint32_t RetryStateImpl::parseRetryOn(absl::string_view config) {
ret |= RetryPolicy::RETRY_ON_REFUSED_STREAM;
} else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RetriableStatusCodes) {
ret |= RetryPolicy::RETRY_ON_RETRIABLE_STATUS_CODES;
} else {
ret |= RetryPolicy::RETRY_UNKNOWN_FIELD_ERROR;
}
}

Expand All @@ -147,6 +149,8 @@ uint32_t RetryStateImpl::parseRetryGrpcOn(absl::string_view retry_grpc_on_header
ret |= RetryPolicy::RETRY_ON_GRPC_UNAVAILABLE;
} else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Internal) {
ret |= RetryPolicy::RETRY_ON_GRPC_INTERNAL;
} else {
ret |= RetryPolicy::RETRY_UNKNOWN_FIELD_ERROR;
Comment thread
xyu-stripe marked this conversation as resolved.
Outdated
}
}

Expand Down
3 changes: 3 additions & 0 deletions source/common/router/retry_state_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class RetryStateImpl : public RetryState {
bool wouldRetryFromReset(const Http::StreamResetReason reset_reason);
RetryStatus shouldRetry(bool would_retry, DoRetryCallback callback);

static uint32_t parseRetryOn_(absl::string_view config, bool flag_parse_failures);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these used for anything anymore?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops good catch. Removed.

static uint32_t parseRetryGrpcOn_(absl::string_view config, bool flag_parse_failures);

const Upstream::ClusterInfo& cluster_;
Runtime::Loader& runtime_;
Runtime::RandomGenerator& random_;
Expand Down
41 changes: 41 additions & 0 deletions source/common/router/router.cc
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,29 @@ Filter::~Filter() {
ASSERT(!retry_state_);
}

const FilterUtility::StrictHeaderChecker::HeaderCheckResult
FilterUtility::StrictHeaderChecker::test(Http::HeaderMap& headers,
const Http::LowerCaseString& target_header) {
HeaderCheckResult r;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be used besides returning? Might be easier to just inline it e.g. return HeaderCheckResult();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; this is was from a time when we always returned "valid" to ignore a target_header didn't have an associated validator. This in combination with the assert + NOT_REACHED_GCOVR_EXCL_LINE below means we ought to be able to remove both this declaration and its return statement.

if (target_header == Http::Headers::get().EnvoyUpstreamRequestTimeoutMs) {
return isInteger(headers.EnvoyUpstreamRequestTimeoutMs());
} else if (target_header == Http::Headers::get().EnvoyUpstreamRequestPerTryTimeoutMs) {
return isInteger(headers.EnvoyUpstreamRequestPerTryTimeoutMs());
} else if (target_header == Http::Headers::get().EnvoyMaxRetries) {
return isInteger(headers.EnvoyMaxRetries());
} else if (target_header == Http::Headers::get().EnvoyRetryOn) {
return hasValidRetryFields(headers.EnvoyRetryOn(), &Router::RetryStateImpl::parseRetryOn);
} else if (target_header == Http::Headers::get().EnvoyRetryGrpcOn) {
return hasValidRetryFields(headers.EnvoyRetryGrpcOn(),
&Router::RetryStateImpl::parseRetryGrpcOn);
} else {
// Should only validate headers for which we have implemented a validator
ASSERT(false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use NOT_REACHED_GCOVR_EXCL_LINE to indicate that we'll never hit this and exclude it from coverage

}
Comment thread
xyu-stripe marked this conversation as resolved.

return r;
}

Stats::StatName Filter::upstreamZone(Upstream::HostDescriptionConstSharedPtr upstream_host) {
return upstream_host ? upstream_host->localityZoneStatName() : config_.empty_stat_name_;
}
Expand Down Expand Up @@ -381,6 +404,24 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::HeaderMap& headers, bool e
ENVOY_STREAM_LOG(debug, "cluster '{}' match for URL '{}'", *callbacks_,
route_entry_->clusterName(), headers.Path()->value().getStringView());

if (config_.strict_check_headers_ != nullptr && !config_.strict_check_headers_->empty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can omit the empty check since the for loop will just noop in that case

for (const auto& header : *config_.strict_check_headers_) {
const auto res = FilterUtility::StrictHeaderChecker::test(headers, header);
if (!res.valid_) {
callbacks_->streamInfo().setResponseFlag(
StreamInfo::ResponseFlag::InvalidEnvoyRequestHeaders);
const std::string body = fmt::format("invalid header '{}' with value '{}'",
std::string(res.entry_->key().getStringView()),
std::string(res.entry_->value().getStringView()));
const std::string details =
absl::StrCat(StreamInfo::ResponseCodeDetails::get().InvalidEnvoyRequestHeaders, "{",
res.entry_->key().getStringView(), "}");
callbacks_->sendLocalReply(Http::Code::BadRequest, body, nullptr, absl::nullopt, details);
return Http::FilterHeadersStatus::StopIteration;
}
}
}

const Http::HeaderEntry* request_alt_name = headers.EnvoyUpstreamAltStatName();
if (request_alt_name) {
// TODO(#7003): converting this header value into a StatName requires
Expand Down
52 changes: 50 additions & 2 deletions source/common/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,41 @@ class FilterUtility {
bool hedge_on_per_try_timeout_;
};

class StrictHeaderChecker {
public:
struct HeaderCheckResult {
bool valid_ = true;
const Http::HeaderEntry* entry_;
};

static const HeaderCheckResult test(Http::HeaderMap& headers,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind adding a comment to this explaining what it does? A more descriptive name might be useful too, maybe checkHeader?

const Http::LowerCaseString& target_header);

using ParseRetryFlagsFunc = std::function<uint32_t(absl::string_view)>;

private:
static HeaderCheckResult hasValidRetryFields(Http::HeaderEntry* header_entry,
const ParseRetryFlagsFunc& parseFn) {
HeaderCheckResult r;
if (header_entry) {
const auto flags = parseFn(header_entry->value().getStringView());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this some more, maybe it would make more sense to have the parseFn return std::pair<uint32_t, bool> (or something to that effect, potentially a named struct), containing both the retry flags and whether there were any unknown fields? That way we avoid having to clear the bit in the other case and the interface might be a bit self explanatory?

Sorry for the churn here, hopefully we can get this to a place where we're all happy with it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries re: churn. I think a named struct would be clearer at calling sites, but going to repush with std::pair and see how it reads during review. I agree that the clearing of the UNKNOWN flag is an implementation detail callers shouldn't need to remember.

r.valid_ = (flags & RetryPolicy::RETRY_UNKNOWN_FIELD_ERROR) == 0;
r.entry_ = header_entry;
}
return r;
}

static HeaderCheckResult isInteger(Http::HeaderEntry* header_entry) {
HeaderCheckResult r;
if (header_entry) {
uint64_t out;
r.valid_ = absl::SimpleAtoi(header_entry->value().getStringView(), &out);
r.entry_ = header_entry;
}
return r;
}
};

/**
* Set the :scheme header based on the properties of the upstream cluster.
*/
Expand Down Expand Up @@ -115,6 +150,7 @@ class FilterConfig {
Stats::Scope& scope, Upstream::ClusterManager& cm, Runtime::Loader& runtime,
Runtime::RandomGenerator& random, ShadowWriterPtr&& shadow_writer,
bool emit_dynamic_stats, bool start_child_span, bool suppress_envoy_headers,
const Protobuf::RepeatedPtrField<std::string>& strict_check_headers,
TimeSource& time_source, Http::Context& http_context)
: scope_(scope), local_info_(local_info), cm_(cm), runtime_(runtime),
random_(random), stats_{ALL_ROUTER_STATS(POOL_COUNTER_PREFIX(scope, stat_prefix))},
Expand All @@ -123,7 +159,14 @@ class FilterConfig {
stat_name_pool_(scope_.symbolTable()), retry_(stat_name_pool_.add("retry")),
zone_name_(stat_name_pool_.add(local_info_.zoneName())),
empty_stat_name_(stat_name_pool_.add("")), shadow_writer_(std::move(shadow_writer)),
time_source_(time_source) {}
time_source_(time_source) {
if (!strict_check_headers.empty()) {
strict_check_headers_ = std::make_unique<HeaderVector>();
for (const auto& header : strict_check_headers) {
strict_check_headers_->emplace_back(Http::LowerCaseString(header));
}
}
}

FilterConfig(const std::string& stat_prefix, Server::Configuration::FactoryContext& context,
ShadowWriterPtr&& shadow_writer,
Expand All @@ -132,11 +175,14 @@ class FilterConfig {
context.runtime(), context.random(), std::move(shadow_writer),
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, dynamic_stats, true),
config.start_child_span(), config.suppress_envoy_headers(),
context.api().timeSource(), context.httpContext()) {
config.strict_check_headers(), context.api().timeSource(),
context.httpContext()) {
for (const auto& upstream_log : config.upstream_log()) {
upstream_logs_.push_back(AccessLog::AccessLogFactory::fromProto(upstream_log, context));
}
}
using HeaderVector = std::vector<Http::LowerCaseString>;
using StrictCheckHeadersPtr = std::unique_ptr<HeaderVector>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is just a unique ptr to HeaderVector, i'd probably call it just HeaderVectorPtr


ShadowWriter& shadowWriter() { return *shadow_writer_; }
TimeSource& timeSource() { return time_source_; }
Expand All @@ -150,6 +196,8 @@ class FilterConfig {
const bool emit_dynamic_stats_;
const bool start_child_span_;
const bool suppress_envoy_headers_;
// TODO(xyu-stripe): Make this a bitset to keep cluster memory footprint down
StrictCheckHeadersPtr strict_check_headers_;
std::list<AccessLog::InstanceSharedPtr> upstream_logs_;
Http::Context& http_context_;
Stats::StatNamePool stat_name_pool_;
Expand Down
8 changes: 7 additions & 1 deletion source/common/stream_info/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const std::string ResponseFlagUtils::RATE_LIMITED = "RL";
const std::string ResponseFlagUtils::UNAUTHORIZED_EXTERNAL_SERVICE = "UAEX";
const std::string ResponseFlagUtils::RATELIMIT_SERVICE_ERROR = "RLSE";
const std::string ResponseFlagUtils::STREAM_IDLE_TIMEOUT = "SI";
const std::string ResponseFlagUtils::INVALID_ENVOY_REQUEST_HEADERS = "IH";

void ResponseFlagUtils::appendString(std::string& result, const std::string& append) {
if (result.empty()) {
Expand All @@ -35,7 +36,7 @@ void ResponseFlagUtils::appendString(std::string& result, const std::string& app
const std::string ResponseFlagUtils::toShortString(const StreamInfo& stream_info) {
std::string result;

static_assert(ResponseFlag::LastFlag == 0x10000, "A flag has been added. Fix this code.");
static_assert(ResponseFlag::LastFlag == 0x20000, "A flag has been added. Fix this code.");

if (stream_info.hasResponseFlag(ResponseFlag::FailedLocalHealthCheck)) {
appendString(result, FAILED_LOCAL_HEALTH_CHECK);
Expand Down Expand Up @@ -105,6 +106,10 @@ const std::string ResponseFlagUtils::toShortString(const StreamInfo& stream_info
appendString(result, STREAM_IDLE_TIMEOUT);
}

if (stream_info.hasResponseFlag(ResponseFlag::InvalidEnvoyRequestHeaders)) {
appendString(result, INVALID_ENVOY_REQUEST_HEADERS);
}

return result.empty() ? NONE : result;
}

Expand All @@ -129,6 +134,7 @@ absl::optional<ResponseFlag> ResponseFlagUtils::toResponseFlag(const std::string
ResponseFlag::DownstreamConnectionTermination},
{ResponseFlagUtils::UPSTREAM_RETRY_LIMIT_EXCEEDED, ResponseFlag::UpstreamRetryLimitExceeded},
{ResponseFlagUtils::STREAM_IDLE_TIMEOUT, ResponseFlag::StreamIdleTimeout},
{ResponseFlagUtils::INVALID_ENVOY_REQUEST_HEADERS, ResponseFlag::InvalidEnvoyRequestHeaders},
};
const auto& it = map.find(flag);
if (it != map.end()) {
Expand Down
1 change: 1 addition & 0 deletions source/common/stream_info/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ResponseFlagUtils {
const static std::string UNAUTHORIZED_EXTERNAL_SERVICE;
const static std::string RATELIMIT_SERVICE_ERROR;
const static std::string STREAM_IDLE_TIMEOUT;
const static std::string INVALID_ENVOY_REQUEST_HEADERS;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ void HttpGrpcAccessLog::responseFlagsToAccessLogResponseFlags(
envoy::data::accesslog::v2::AccessLogCommon& common_access_log,
const StreamInfo::StreamInfo& stream_info) {

static_assert(StreamInfo::ResponseFlag::LastFlag == 0x10000,
static_assert(StreamInfo::ResponseFlag::LastFlag == 0x20000,
"A flag has been added. Fix this code.");

if (stream_info.hasResponseFlag(StreamInfo::ResponseFlag::FailedLocalHealthCheck)) {
Expand Down Expand Up @@ -165,6 +165,12 @@ void HttpGrpcAccessLog::responseFlagsToAccessLogResponseFlags(
ResponseFlags_Unauthorized_Reason_EXTERNAL_SERVICE);
}

if (stream_info.hasResponseFlag(StreamInfo::ResponseFlag::InvalidEnvoyRequestHeaders)) {
common_access_log.mutable_response_flags()->mutable_unauthorized_details()->set_reason(
envoy::data::accesslog::v2::ResponseFlags_Unauthorized_Reason::
ResponseFlags_Unauthorized_Reason_STRICT_HEADER_CHECK_FAILED);
}

if (stream_info.hasResponseFlag(StreamInfo::ResponseFlag::RateLimitServiceError)) {
common_access_log.mutable_response_flags()->set_rate_limit_service_error(true);
}
Expand Down
Loading