Skip to content
Closed
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
7 changes: 6 additions & 1 deletion changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ date: Pending

behavior_changes:
# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required*
- area: http2
change: |
Discard the ``Host`` header if the ``:authority`` header was received to bring Envoy into compliance with
https://www.rfc-editor.org/rfc/rfc9113#section-8.3.1 This behavioral change can be reverted by setting runtime flag
``envoy.reloadable_features.http2_discard_host_header`` to false.

minor_behavior_changes:
# *Changes that may cause incompatibilities for some users, but should not for most*
Expand All @@ -26,4 +31,4 @@ new_features:
``envoy.reloadable_features.google_grpc_disable_tls_13`` to disable TLSv1.3
usage by gRPC SDK for ``google_grpc`` services.

deprecated:
deprecated:
12 changes: 12 additions & 0 deletions source/common/http/http2/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2128,6 +2128,18 @@ int ServerConnectionImpl::onHeader(const nghttp2_frame* frame, HeaderString&& na
// For a server connection, we should never get push promise frames.
ASSERT(frame->hd.type == NGHTTP2_HEADERS);
ASSERT(frame->headers.cat == NGHTTP2_HCAT_REQUEST || frame->headers.cat == NGHTTP2_HCAT_HEADERS);
if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http2_discard_host_header")) {
StreamImpl* stream = getStreamUnchecked(frame->hd.stream_id);
if (stream && name == static_cast<absl::string_view>(Http::Headers::get().HostLegacy)) {
// Check if there is already the :authority header
const auto result = stream->headers().get(Http::Headers::get().Host);
if (!result.empty()) {
// Discard the host header value
return 0;
}
// Otherwise use host value as :authority
}
}
return saveHeader(frame, std::move(name), std::move(value));
}

Expand Down
3 changes: 2 additions & 1 deletion source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ RUNTIME_GUARD(envoy_reloadable_features_http1_allow_codec_error_response_after_1
RUNTIME_GUARD(envoy_reloadable_features_http1_connection_close_header_in_redirect);
RUNTIME_GUARD(envoy_reloadable_features_http1_use_balsa_parser);
RUNTIME_GUARD(envoy_reloadable_features_http2_decode_metadata_with_quiche);
RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header);
RUNTIME_GUARD(envoy_reloadable_features_http2_validate_authority_with_quiche);
RUNTIME_GUARD(envoy_reloadable_features_http_allow_partial_urls_in_referer);
RUNTIME_GUARD(envoy_reloadable_features_http_filter_avoid_reentrant_local_reply);
Expand Down Expand Up @@ -253,4 +254,4 @@ void maybeSetDeprecatedInts(absl::string_view name, uint32_t value) {
}

} // namespace Runtime
} // namespace Envoy
} // namespace Envoy
6 changes: 3 additions & 3 deletions test/common/http/http2/http2_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ class Http2Frame {
ASSERT(size() >= HeaderSize);
setPayloadSize(size() - HeaderSize);
}
// Headers are directly encoded
void appendStaticHeader(StaticHeaderIndex index);
void appendHeaderWithoutIndexing(StaticHeaderIndex index, absl::string_view value);

private:
void buildHeader(Type type, uint32_t payload_size = 0, uint8_t flags = 0, uint32_t stream_id = 0);
Expand All @@ -277,9 +280,6 @@ class Http2Frame {
std::copy(data.begin(), data.end(), data_.begin() + 9);
}

// Headers are directly encoded
void appendStaticHeader(StaticHeaderIndex index);
void appendHeaderWithoutIndexing(StaticHeaderIndex index, absl::string_view value);
void appendEmptyHeader();

DataContainer data_;
Expand Down
51 changes: 49 additions & 2 deletions test/integration/multiplexed_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2206,7 +2206,7 @@ TEST_P(Http2FrameIntegrationTest, HostDifferentFromAuthority) {
sendFrame(request);

waitForNextUpstreamRequest();
EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com,two.example.com");
EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com");
upstream_request_->encodeHeaders(default_response_headers_, true);
auto frame = readFrame();
EXPECT_EQ(Http2Frame::Type::Headers, frame.type());
Expand All @@ -2223,14 +2223,61 @@ TEST_P(Http2FrameIntegrationTest, HostSameAsAuthority) {
sendFrame(request);

waitForNextUpstreamRequest();
EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com,one.example.com");
EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com");
upstream_request_->encodeHeaders(default_response_headers_, true);
auto frame = readFrame();
EXPECT_EQ(Http2Frame::Type::Headers, frame.type());
EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus());
tcp_client_->close();
}

TEST_P(Http2FrameIntegrationTest, HostConcatenatedWithAuthorityWithOverride) {
config_helper_.addRuntimeOverride("envoy.reloadable_features.http2_discard_host_header", "false");
beginSession();

uint32_t request_idx = 0;
auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(request_idx),
"one.example.com", "/path", {{"host", "two.example.com"}});
sendFrame(request);

waitForNextUpstreamRequest();
EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com,two.example.com");
upstream_request_->encodeHeaders(default_response_headers_, true);
auto frame = readFrame();
EXPECT_EQ(Http2Frame::Type::Headers, frame.type());
EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus());
tcp_client_->close();
}

// All HTTP/2 static headers must be before non-static headers.
// Verify that codecs validate this.
TEST_P(Http2FrameIntegrationTest, HostBeforeAuthorityIsRejected) {
#ifdef ENVOY_ENABLE_UHV
// TODO(yanavlasov): fix this check for oghttp2 in UHV mode.
if (GetParam().http2_implementation == Http2Impl::Oghttp2) {
return;
}
#endif
beginSession();

Http2Frame request = Http2Frame::makeEmptyHeadersFrame(Http2Frame::makeClientStreamId(0),
Http2Frame::HeadersFlags::EndHeaders);
request.appendStaticHeader(Http2Frame::StaticHeaderIndex::MethodPost);
request.appendStaticHeader(Http2Frame::StaticHeaderIndex::SchemeHttps);
request.appendHeaderWithoutIndexing(Http2Frame::StaticHeaderIndex::Path, "/path");
// Add the `host` header before `:authority`
request.appendHeaderWithoutIndexing({"host", "two.example.com"});
request.appendHeaderWithoutIndexing(Http2Frame::StaticHeaderIndex::Authority, "one.example.com");
request.adjustPayloadSize();

sendFrame(request);

// By default codec treats stream errors as protocol errors and closes the connection.
tcp_client_->waitForDisconnect();
tcp_client_->close();
EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_cx_protocol_error")->value());
}

TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequests) {
const int kRequestsSentPerIOCycle = 20;
autonomous_upstream_ = true;
Expand Down