Skip to content

Commit 399ec7d

Browse files
tedgeatphlax
authored andcommitted
http: configurable ignore of HTTP/1.1 upgrades (#37642)
Fixes #36305 Add configuration to ignore HTTP/1.1 Upgrade headers . See https://datatracker.ietf.org/doc/html/rfc7230#section-6.7: Signed-off-by: Greg Greenway <ggreenway@apple.com> (cherry picked from commit ad40097) fix stringmatcherimpl template version difference Signed-off-by: Greg Greenway <ggreenway@apple.com> Signed-off-by: Ryan Northey <ryan@synca.io>
1 parent 85d313e commit 399ec7d

File tree

19 files changed

+437
-50
lines changed

19 files changed

+437
-50
lines changed

β€Žapi/envoy/config/core/v3/protocol.proto

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ syntax = "proto3";
33
package envoy.config.core.v3;
44

55
import "envoy/config/core/v3/extension.proto";
6+
import "envoy/type/matcher/v3/string.proto";
67
import "envoy/type/v3/percent.proto";
78

89
import "google/protobuf/duration.proto";
@@ -305,7 +306,7 @@ message HttpProtocolOptions {
305306
google.protobuf.UInt32Value max_requests_per_connection = 6;
306307
}
307308

308-
// [#next-free-field: 11]
309+
// [#next-free-field: 12]
309310
message Http1ProtocolOptions {
310311
option (udpa.annotations.versioning).previous_message_type =
311312
"envoy.api.v2.core.Http1ProtocolOptions";
@@ -417,6 +418,14 @@ message Http1ProtocolOptions {
417418
// <envoy_v3_api_field_extensions.http.header_validators.envoy_default.v3.HeaderValidatorConfig.restrict_http_methods>`
418419
// to reject custom methods.
419420
bool allow_custom_methods = 10 [(xds.annotations.v3.field_status).work_in_progress = true];
421+
422+
// Ignore HTTP/1.1 upgrade values matching any of the supplied matchers.
423+
//
424+
// .. note::
425+
//
426+
// ``h2c`` upgrades are always removed for backwards compatibility, regardless of the
427+
// value in this setting.
428+
repeated type.matcher.v3.StringMatcher ignore_http_11_upgrade = 11;
420429
}
421430

422431
message KeepaliveSettings {

β€Žchangelogs/current.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,10 @@ removed_config_or_runtime:
1919
# *Normally occurs at the end of the* :ref:`deprecation period <deprecated>`
2020

2121
new_features:
22+
- area: http
23+
change: |
24+
Added :ref:`ignore_http_11_upgrade
25+
<envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.ignore_http_11_upgrade>`
26+
to ignore HTTP/1.1 Upgrade values matching any of the supplied matchers.
2227
2328
deprecated:

β€Ženvoy/http/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ envoy_cc_library(
5050
":stream_reset_handler_interface",
5151
"//envoy/access_log:access_log_interface",
5252
"//envoy/buffer:buffer_interface",
53+
"//envoy/common:matchers_interface",
5354
"//envoy/grpc:status",
5455
"//envoy/network:address_interface",
5556
"//envoy/stream_info:stream_info_interface",

β€Ženvoy/http/codec.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "envoy/access_log/access_log.h"
88
#include "envoy/buffer/buffer.h"
9+
#include "envoy/common/matchers.h"
910
#include "envoy/common/pure.h"
1011
#include "envoy/grpc/status.h"
1112
#include "envoy/http/header_formatter.h"
@@ -497,6 +498,9 @@ struct Http1Settings {
497498
// headers set. By default such messages are rejected, but if option is enabled - Envoy will
498499
// remove Content-Length header and process message.
499500
bool allow_chunked_length_{false};
501+
// Remove HTTP/1.1 Upgrade header tokens matching any provided matcher. By default such
502+
// messages are rejected
503+
std::shared_ptr<const std::vector<Matchers::StringMatcherPtr>> ignore_upgrade_matchers_;
500504

501505
enum class HeaderKeyFormat {
502506
// By default no formatting is performed, presenting all headers in lowercase (as Envoy

β€Žsource/common/http/http1/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ envoy_cc_library(
9494
deps = [
9595
"//envoy/http:codec_interface",
9696
"//envoy/protobuf:message_validator_interface",
97+
"//source/common/common:matchers_lib",
9798
"//source/common/config:utility_lib",
9899
"//source/common/runtime:runtime_features_lib",
99100
"@com_google_absl//absl/types:optional",

β€Žsource/common/http/http1/codec_impl.cc

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,12 @@ static constexpr uint32_t kMaxOutboundResponses = 2;
6565
using Http1ResponseCodeDetails = ConstSingleton<Http1ResponseCodeDetailValues>;
6666
using Http1HeaderTypes = ConstSingleton<Http1HeaderTypesValues>;
6767

68-
const StringUtil::CaseUnorderedSet& caseUnorderdSetContainingUpgradeAndHttp2Settings() {
68+
const StringUtil::CaseUnorderedSet& caseUnorderedSetContainingUpgrade() {
69+
CONSTRUCT_ON_FIRST_USE(StringUtil::CaseUnorderedSet,
70+
Http::Headers::get().ConnectionValues.Upgrade);
71+
}
72+
73+
const StringUtil::CaseUnorderedSet& caseUnorderedSetContainingUpgradeAndHttp2Settings() {
6974
CONSTRUCT_ON_FIRST_USE(StringUtil::CaseUnorderedSet,
7075
Http::Headers::get().ConnectionValues.Upgrade,
7176
Http::Headers::get().ConnectionValues.Http2Settings);
@@ -846,24 +851,33 @@ StatusOr<CallbackResult> ConnectionImpl::onHeadersCompleteImpl() {
846851
RequestOrResponseHeaderMap& request_or_response_headers = requestOrResponseHeaders();
847852
const Http::HeaderValues& header_values = Http::Headers::get();
848853
if (Utility::isUpgrade(request_or_response_headers) && upgradeAllowed()) {
854+
auto upgrade_value = request_or_response_headers.getUpgradeValue();
855+
const bool is_h2c = absl::EqualsIgnoreCase(upgrade_value, header_values.UpgradeValues.H2c);
856+
849857
// Ignore h2c upgrade requests until we support them.
850858
// See https://github.com/envoyproxy/envoy/issues/7161 for details.
851-
if (absl::EqualsIgnoreCase(request_or_response_headers.getUpgradeValue(),
852-
header_values.UpgradeValues.H2c)) {
859+
// Upgrades are rejected unless ignore_http_11_upgrade is configured.
860+
// See https://github.com/envoyproxy/envoy/issues/36305 for details.
861+
if (is_h2c) {
853862
ENVOY_CONN_LOG(trace, "removing unsupported h2c upgrade headers.", connection_);
854863
request_or_response_headers.removeUpgrade();
855-
if (request_or_response_headers.Connection()) {
856-
const auto& tokens_to_remove = caseUnorderdSetContainingUpgradeAndHttp2Settings();
857-
std::string new_value = StringUtil::removeTokens(
858-
request_or_response_headers.getConnectionValue(), ",", tokens_to_remove, ",");
859-
if (new_value.empty()) {
860-
request_or_response_headers.removeConnection();
861-
} else {
862-
request_or_response_headers.setConnection(new_value);
863-
}
864-
}
864+
Utility::removeConnectionUpgrade(request_or_response_headers,
865+
caseUnorderedSetContainingUpgradeAndHttp2Settings());
865866
request_or_response_headers.remove(header_values.Http2Settings);
866-
} else {
867+
} else if (codec_settings_.ignore_upgrade_matchers_ != nullptr &&
868+
!codec_settings_.ignore_upgrade_matchers_->empty()) {
869+
ENVOY_CONN_LOG(trace, "removing ignored upgrade headers.", connection_);
870+
871+
Utility::removeUpgrade(request_or_response_headers,
872+
*codec_settings_.ignore_upgrade_matchers_);
873+
874+
if (!request_or_response_headers.Upgrade()) {
875+
Utility::removeConnectionUpgrade(request_or_response_headers,
876+
caseUnorderedSetContainingUpgrade());
877+
}
878+
}
879+
880+
if (Utility::isUpgrade(request_or_response_headers)) {
867881
ENVOY_CONN_LOG(trace, "codec entering upgrade mode.", connection_);
868882
handling_upgrade_ = true;
869883
}

β€Žsource/common/http/http1/settings.cc

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "envoy/http/header_formatter.h"
44

5+
#include "source/common/common/matchers.h"
56
#include "source/common/config/utility.h"
67
#include "source/common/runtime/runtime_features.h"
78

@@ -10,6 +11,7 @@ namespace Http {
1011
namespace Http1 {
1112

1213
Http1Settings parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOptions& config,
14+
Server::Configuration::CommonFactoryContext& context,
1315
ProtobufMessage::ValidationVisitor& validation_visitor) {
1416
Http1Settings ret;
1517
ret.allow_absolute_url_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, allow_absolute_url, true);
@@ -19,6 +21,18 @@ Http1Settings parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOpt
1921
ret.enable_trailers_ = config.enable_trailers();
2022
ret.allow_chunked_length_ = config.allow_chunked_length();
2123

24+
if (!config.ignore_http_11_upgrade().empty()) {
25+
std::vector<Matchers::StringMatcherPtr> matchers;
26+
for (const auto& matcher : config.ignore_http_11_upgrade()) {
27+
matchers.emplace_back(
28+
std::make_unique<
29+
Envoy::Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>(
30+
matcher, context));
31+
}
32+
ret.ignore_upgrade_matchers_ =
33+
std::make_shared<const std::vector<Matchers::StringMatcherPtr>>(std::move(matchers));
34+
}
35+
2236
if (config.header_key_format().has_proper_case_words()) {
2337
ret.header_key_format_ = Http1Settings::HeaderKeyFormat::ProperCase;
2438
} else if (config.header_key_format().has_stateful_formatter()) {
@@ -45,10 +59,11 @@ Http1Settings parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOpt
4559
}
4660

4761
Http1Settings parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOptions& config,
62+
Server::Configuration::CommonFactoryContext& context,
4863
ProtobufMessage::ValidationVisitor& validation_visitor,
4964
const ProtobufWkt::BoolValue& hcm_stream_error,
5065
bool validate_scheme) {
51-
Http1Settings ret = parseHttp1Settings(config, validation_visitor);
66+
Http1Settings ret = parseHttp1Settings(config, context, validation_visitor);
5267
ret.validate_scheme_ = validate_scheme;
5368

5469
if (config.has_override_stream_error_on_invalid_http_message()) {

β€Žsource/common/http/http1/settings.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "envoy/config/core/v3/protocol.pb.h"
44
#include "envoy/http/codec.h"
55
#include "envoy/protobuf/message_validator.h"
6+
#include "envoy/server/factory_context.h"
67

78
namespace Envoy {
89
namespace Http {
@@ -13,9 +14,11 @@ namespace Http1 {
1314
* envoy::config::core::v3::Http1ProtocolOptions config.
1415
*/
1516
Http1Settings parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOptions& config,
17+
Server::Configuration::CommonFactoryContext& context,
1618
ProtobufMessage::ValidationVisitor& validation_visitor);
1719

1820
Http1Settings parseHttp1Settings(const envoy::config::core::v3::Http1ProtocolOptions& config,
21+
Server::Configuration::CommonFactoryContext& context,
1922
ProtobufMessage::ValidationVisitor& validation_visitor,
2023
const ProtobufWkt::BoolValue& hcm_stream_error,
2124
bool validate_scheme);

β€Žsource/common/http/utility.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,41 @@ bool Utility::isWebSocketUpgradeRequest(const RequestHeaderMap& headers) {
649649
Http::Headers::get().UpgradeValues.WebSocket));
650650
}
651651

652+
void Utility::removeUpgrade(RequestOrResponseHeaderMap& headers,
653+
const std::vector<Matchers::StringMatcherPtr>& matchers) {
654+
if (headers.Upgrade()) {
655+
std::vector<absl::string_view> tokens =
656+
Envoy::StringUtil::splitToken(headers.getUpgradeValue(), ",", false, true);
657+
658+
auto end = std::remove_if(tokens.begin(), tokens.end(), [&](absl::string_view token) {
659+
return std::any_of(
660+
matchers.begin(), matchers.end(),
661+
[&token](const Matchers::StringMatcherPtr& matcher) { return matcher->match(token); });
662+
});
663+
664+
const std::string new_value = absl::StrJoin(tokens.begin(), end, ",");
665+
666+
if (new_value.empty()) {
667+
headers.removeUpgrade();
668+
} else {
669+
headers.setUpgrade(new_value);
670+
}
671+
}
672+
}
673+
674+
void Utility::removeConnectionUpgrade(RequestOrResponseHeaderMap& headers,
675+
StringUtil::CaseUnorderedSet tokens_to_remove) {
676+
if (headers.Connection()) {
677+
const std::string new_value =
678+
StringUtil::removeTokens(headers.getConnectionValue(), ",", tokens_to_remove, ",");
679+
if (new_value.empty()) {
680+
headers.removeConnection();
681+
} else {
682+
headers.setConnection(new_value);
683+
}
684+
}
685+
}
686+
652687
Utility::PreparedLocalReplyPtr Utility::prepareLocalReply(const EncodeFunctions& encode_functions,
653688
const LocalReplyData& local_reply_data) {
654689
Code response_code = local_reply_data.response_code_;

β€Žsource/common/http/utility.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,20 @@ bool isH3UpgradeRequest(const RequestHeaderMap& headers);
315315
*/
316316
bool isWebSocketUpgradeRequest(const RequestHeaderMap& headers);
317317

318+
/**
319+
* Removes tokens from `Upgrade` header matching one of the matchers. Removes the `Upgrade`
320+
* header if result is empty.
321+
*/
322+
void removeUpgrade(RequestOrResponseHeaderMap& headers,
323+
const std::vector<Matchers::StringMatcherPtr>& matchers);
324+
325+
/**
326+
* Removes `tokens_to_remove` from the `Connection` header, if present and part of a comma separated
327+
* set of values. Removes the `Connection` header if it only contains `tokens_to_remove`.
328+
*/
329+
void removeConnectionUpgrade(RequestOrResponseHeaderMap& headers,
330+
StringUtil::CaseUnorderedSet tokens_to_remove);
331+
318332
struct EncodeFunctions {
319333
// Function to modify locally generated response headers.
320334
std::function<void(ResponseHeaderMap& headers)> modify_headers_;

0 commit comments

Comments
 (0)