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

http: allow runtime override of default for max response headers kb #36439

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
3 changes: 3 additions & 0 deletions api/envoy/config/core/v3/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ message HttpProtocolOptions {
// The maximum number of headers (request headers if configured on HttpConnectionManager,
// response headers when configured on a cluster).
// If unconfigured, the default maximum number of headers allowed is 100.
// The default value for requests can be overridden by setting runtime key ``envoy.reloadable_features.max_request_headers_count``.
// The default value for responses can be overridden by setting runtime key ``envoy.reloadable_features.max_response_headers_count``.
// Downstream requests that exceed this limit will receive a 431 response for HTTP/1.x and cause a stream
// reset for HTTP/2.
// Upstream responses that exceed this limit will result in a 503 response.
Expand All @@ -270,6 +272,7 @@ message HttpProtocolOptions {
// The maximum size of response headers.
// If unconfigured, the default is 60 KiB, except for HTTP/1 response headers which have a default
// of 80KiB.
// The default value can be overridden by setting runtime key ``envoy.reloadable_features.max_response_headers_size_kb``.
// Responses that exceed this limit will result in a 503 response.
// In Envoy, this setting is only valid when configured on an upstream cluster, not on the
// :ref:`HTTP Connection Manager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ message HttpConnectionManager {

// The maximum request headers size for incoming connections.
// If unconfigured, the default max request headers allowed is 60 KiB.
// The default value can be overridden by setting runtime key ``envoy.reloadable_features.max_request_headers_size_kb``.
// Requests that exceed this limit will receive a 431 response.
//
// Note: currently some protocol codecs impose limits on the maximum size of a single header:
Expand Down
3 changes: 2 additions & 1 deletion changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ new_features:
- area: http
change: |
Added configuration setting for the :ref:`maximum size of response headers
<envoy_v3_api_field_config.core.v3.HttpProtocolOptions.max_response_headers_kb>` in responses.
<envoy_v3_api_field_config.core.v3.HttpProtocolOptions.max_response_headers_kb>` in responses. The default can
be overridden with runtime key ``envoy.reloadable_features.max_response_headers_size_kb``.
- area: http_11_proxy
change: |
Added the option to configure the transport socket via locality or endpoint metadata.
Expand Down
2 changes: 2 additions & 0 deletions envoy/http/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const char MaxResponseHeadersCountOverrideKey[] =
"envoy.reloadable_features.max_response_headers_count";
const char MaxRequestHeadersSizeOverrideKey[] =
"envoy.reloadable_features.max_request_headers_size_kb";
const char MaxResponseHeadersSizeOverrideKey[] =
"envoy.reloadable_features.max_response_headers_size_kb";

class Stream;
class RequestDecoder;
Expand Down
13 changes: 11 additions & 2 deletions source/common/upstream/upstream_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1229,8 +1229,17 @@ ClusterInfoImpl::ClusterInfoImpl(
http_protocol_options_->common_http_protocol_options_, max_headers_count,
runtime_.snapshot().getInteger(Http::MaxResponseHeadersCountOverrideKey,
Http::DEFAULT_MAX_HEADERS_COUNT))),
max_response_headers_kb_(PROTOBUF_GET_OPTIONAL_WRAPPED(
http_protocol_options_->common_http_protocol_options_, max_response_headers_kb)),
max_response_headers_kb_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(
http_protocol_options_->common_http_protocol_options_, max_response_headers_kb,
[&]() -> absl::optional<uint16_t> {
constexpr uint64_t unspecified = 0;
uint64_t runtime_val = runtime_.snapshot().getInteger(
Http::MaxResponseHeadersSizeOverrideKey, unspecified);
if (runtime_val == unspecified) {
return absl::nullopt;
}
return runtime_val;
}())),
type_(config.type()),
drain_connections_on_host_removal_(config.ignore_health_on_host_removal()),
connection_pool_per_downstream_connection_(
Expand Down
63 changes: 63 additions & 0 deletions test/common/upstream/upstream_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5594,6 +5594,69 @@ TEST_F(ClusterInfoImplTest, Http2AutoWithNonAlpnMatcherAndValidationOff) {
EXPECT_NO_THROW(makeCluster(yaml + auto_http2));
}

TEST_F(ClusterInfoImplTest, MaxResponseHeadersDefault) {
const std::string yaml = TestEnvironment::substitute(R"EOF(
name: name
connect_timeout: 0.25s
type: STRICT_DNS
)EOF",
Network::Address::IpVersion::v4);

auto cluster = makeCluster(yaml);
EXPECT_FALSE(cluster->info()->maxResponseHeadersKb().has_value());
EXPECT_EQ(100, cluster->info()->maxResponseHeadersCount());
}

// Test that the runtime override for the defaults is used when specified.
TEST_F(ClusterInfoImplTest, MaxResponseHeadersRuntimeOverride) {
const std::string yaml = TestEnvironment::substitute(R"EOF(
name: name
connect_timeout: 0.25s
type: STRICT_DNS
)EOF",
Network::Address::IpVersion::v4);

EXPECT_CALL(runtime_.snapshot_,
getInteger("envoy.reloadable_features.max_response_headers_size_kb", _))
.WillRepeatedly(Return(123));
EXPECT_CALL(runtime_.snapshot_,
getInteger("envoy.reloadable_features.max_response_headers_count", _))
.WillRepeatedly(Return(456));

auto cluster = makeCluster(yaml);
EXPECT_EQ(absl::make_optional(uint16_t(123)), cluster->info()->maxResponseHeadersKb());
EXPECT_EQ(456, cluster->info()->maxResponseHeadersCount());
}

// Test that the runtime override is ignored if there is a configured value.
TEST_F(ClusterInfoImplTest, MaxResponseHeadersRuntimeOverrideIgnored) {
const std::string yaml = TestEnvironment::substitute(R"EOF(
name: name
connect_timeout: 0.25s
type: STRICT_DNS
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
common_http_protocol_options:
max_response_headers_kb: 1
max_headers_count: 2
explicit_http_config:
http2_protocol_options: {}
)EOF",
Network::Address::IpVersion::v4);

EXPECT_CALL(runtime_.snapshot_,
getDouble("envoy.reloadable_features.max_response_headers_size_kb", _))
.WillRepeatedly(Return(123));
EXPECT_CALL(runtime_.snapshot_,
getDouble("envoy.reloadable_features.max_response_headers_count", _))
.WillRepeatedly(Return(456));

auto cluster = makeCluster(yaml);
EXPECT_EQ(absl::make_optional(uint16_t(1)), cluster->info()->maxResponseHeadersKb());
EXPECT_EQ(2, cluster->info()->maxResponseHeadersCount());
}

TEST_F(ClusterInfoImplTest, UpstreamFilterTypedAndDynamicConfigThrows) {
const std::string yaml = R"EOF(
name: name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,29 @@ TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersKbMaxConfiguredViaRunti
EXPECT_EQ(9000, config.maxRequestHeadersKb());
}

TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersCountMaxConfiguredViaRuntime) {
const std::string yaml_string = R"EOF(
stat_prefix: ingress_http
route_config:
name: local_route
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
)EOF";

ON_CALL(context_.server_factory_context_.runtime_loader_.snapshot_,
getInteger("envoy.reloadable_features.max_request_headers_count", _))
.WillByDefault(Return(42));

HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_,
date_provider_, route_config_provider_manager_,
&scoped_routes_config_provider_manager_, tracer_manager_,
filter_config_provider_manager_, creation_status_);
ASSERT_TRUE(creation_status_.ok());
EXPECT_EQ(42, config.maxRequestHeadersCount());
}

// Validated that an explicit zero stream idle timeout disables.
TEST_F(HttpConnectionManagerConfigTest, DisabledStreamIdleTimeout) {
const std::string yaml_string = R"EOF(
Expand Down