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
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// HTTP connection manager :ref:`configuration overview <config_http_conn_man>`.
// [#extension: envoy.filters.network.http_connection_manager]

// [#next-free-field: 59]
// [#next-free-field: 60]
message HttpConnectionManager {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager";
Expand Down Expand Up @@ -289,11 +289,18 @@ message HttpConnectionManager {
// HTTP connections will be used for this upgrade type.
repeated HttpFilter filters = 2;

// Determines if upgrades are enabled or disabled by default. Defaults to true.
// Determines if upgrades are enabled or disabled by default. Defaults to true, unless
// :ref:`ignore <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.UpgradeConfig.ignore>`
// is enabled. It is invalid to set both this option and ``disabled`` to true.
// This can be overridden on a per-route basis with :ref:`cluster
// <envoy_v3_api_field_config.route.v3.RouteAction.upgrade_configs>` as documented in the
// :ref:`upgrade documentation <arch_overview_upgrades>`.
google.protobuf.BoolValue enabled = 3;

// If enabled, Envoy will ignore upgrade requests of this type if the request is HTTP/1.1. It is invalid to set both this option and
// :ref:`enabled <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.UpgradeConfig.enabled>`
// to true.
google.protobuf.BoolValue ignore_on_http11 = 4;
}

// [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied
Expand Down Expand Up @@ -787,6 +794,13 @@ message HttpConnectionManager {

repeated UpgradeConfig upgrade_configs = 23;

// If enabled, if Envoy receives an Upgrade request from a client on an HTTP/1.1 connection and no configuration for
// the upgrade type is found in
// :ref:`upgrade_configs <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.upgrade_configs>`,
// Envoy will ignore the upgrade request. If disabled, Envoy will respond with an error and close the connection.
// The default is true.
google.protobuf.BoolValue ignore_unconfigured_http11_upgrades = 59;

// Should paths be normalized according to RFC 3986 before any processing of
// requests by HTTP filters or routing? This affects the upstream ``:path`` header
// as well. For paths that fail this check, Envoy will respond with 400 to
Expand Down
1 change: 1 addition & 0 deletions envoy/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ envoy_cc_library(
hdrs = ["filter_factory.h"],
deps = [
":header_map_interface",
":protocol_interface",
"//envoy/access_log:access_log_interface",
"//envoy/grpc:status",
"@com_google_absl//absl/types:optional",
Expand Down
16 changes: 12 additions & 4 deletions envoy/http/filter_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <map>

#include "envoy/common/pure.h"
#include "envoy/http/protocol.h"

#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
Expand Down Expand Up @@ -100,19 +101,26 @@ class FilterChainFactory {
createFilterChain(FilterChainManager& manager, bool only_create_if_configured = false,
const FilterChainOptions& options = EmptyFilterChainOptions{}) const PURE;

enum class UpgradeAction {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think move the comments below up here in case the enum gets used multiple places

Rejected,
Accepted,
Ignored,
};

/**
* Called when a new upgrade stream is created on the connection.
* @param upgrade supplies the upgrade header from downstream
* @param per_route_upgrade_map supplies the upgrade map, if any, for this route.
* @param manager supplies the "sink" that is used for actually creating the filter chain. @see
* FilterChainManager.
* @return true if upgrades of this type are allowed and the filter chain has been created.
* returns false if this upgrade type is not configured, and no filter chain is created.
* @return ACCEPTED if upgrades of this type are allowed and the filter chain has been created.
* returns REJECTED if this upgrade type is not allowed, and no filter chain is created.
* returns IGNORED if the upgrade type should be ignored and no upgrade will take place.
*/
using UpgradeMap = std::map<std::string, bool>;
virtual bool createUpgradeFilterChain(
virtual UpgradeAction createUpgradeFilterChain(
absl::string_view upgrade, const UpgradeMap* per_route_upgrade_map,
FilterChainManager& manager,
absl::optional<Http::Protocol> http_version, FilterChainManager& manager,
const FilterChainOptions& options = EmptyFilterChainOptions{}) const PURE;
};

Expand Down
36 changes: 28 additions & 8 deletions source/common/http/filter_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1623,6 +1623,14 @@ void FilterManager::contextOnContinue(ScopeTrackedObjectStack& tracked_object_st
tracked_object_stack.add(filter_manager_callbacks_.scope());
}

namespace {
const StringUtil::CaseUnorderedSet& caseUnorderdSetContainingUpgrade() {
CONSTRUCT_ON_FIRST_USE(StringUtil::CaseUnorderedSet,
Http::Headers::get().ConnectionValues.Upgrade);
}

} // namespace

bool FilterManager::createFilterChain() {
if (state_.created_filter_chain_) {
return false;
Expand All @@ -1647,14 +1655,26 @@ bool FilterManager::createFilterChain() {
if (upgrade != nullptr) {
const Router::RouteEntry::UpgradeMap* upgrade_map = filter_manager_callbacks_.upgradeMap();

if (filter_chain_factory_.createUpgradeFilterChain(upgrade->value().getStringView(),
upgrade_map, *this, options)) {
auto result = filter_chain_factory_.createUpgradeFilterChain(
upgrade->value().getStringView(), upgrade_map, streamInfo().protocol(), *this, options);
switch (result) {
case FilterChainFactory::UpgradeAction::Accepted:
filter_manager_callbacks_.upgradeFilterChainCreated();
return true;
} else {
case FilterChainFactory::UpgradeAction::Rejected:
upgrade_rejected = true;
// Fall through to the default filter chain. The function calling this
// will send a local reply indicating that the upgrade failed.
break;
case FilterChainFactory::UpgradeAction::Ignored:
// Upgrades can only be ignored for HTTP/1.1.
ASSERT(streamInfo().protocol().has_value() &&
*streamInfo().protocol() == Http::Protocol::Http11);

filter_manager_callbacks_.requestHeaders()->removeUpgrade();
Http::Utility::removeConnectionUpgrade(filter_manager_callbacks_.requestHeaders().value(),
caseUnorderdSetContainingUpgrade());
break;
}
}

Expand Down Expand Up @@ -1714,11 +1734,11 @@ bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers)

if (headers != nullptr) {
// The call to setResponseHeaders is needed to ensure that the headers are properly logged in
// access logs before the stream is destroyed. Since the function expects a ResponseHeaderPtr&&,
// ownership of the headers must be passed. This cannot happen earlier in the flow (such as in
// the call to setupRedirect) because at that point it is still possible for the headers to be
// used in a different logical branch. We work around this by creating a copy and passing
// ownership of the copy instead.
// access logs before the stream is destroyed. Since the function expects a
// ResponseHeaderPtr&&, ownership of the headers must be passed. This cannot happen earlier in
// the flow (such as in the call to setupRedirect) because at that point it is still possible
// for the headers to be used in a different logical branch. We work around this by creating a
// copy and passing ownership of the copy instead.
ResponseHeaderMapPtr headers_copy = createHeaderMap<ResponseHeaderMapImpl>(*headers);
parent_.filter_manager_callbacks_.setResponseHeaders(std::move(headers_copy));
parent_.filter_manager_callbacks_.chargeStats(*headers);
Expand Down
12 changes: 2 additions & 10 deletions source/common/http/http1/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -852,16 +852,8 @@ StatusOr<CallbackResult> ConnectionImpl::onHeadersCompleteImpl() {
header_values.UpgradeValues.H2c)) {
ENVOY_CONN_LOG(trace, "removing unsupported h2c upgrade headers.", connection_);
request_or_response_headers.removeUpgrade();
if (request_or_response_headers.Connection()) {
const auto& tokens_to_remove = caseUnorderdSetContainingUpgradeAndHttp2Settings();
std::string new_value = StringUtil::removeTokens(
request_or_response_headers.getConnectionValue(), ",", tokens_to_remove, ",");
if (new_value.empty()) {
request_or_response_headers.removeConnection();
} else {
request_or_response_headers.setConnection(new_value);
}
}
Utility::removeConnectionUpgrade(request_or_response_headers,
caseUnorderdSetContainingUpgradeAndHttp2Settings());
request_or_response_headers.remove(header_values.Http2Settings);
} else {
ENVOY_CONN_LOG(trace, "codec entering upgrade mode.", connection_);
Expand Down
13 changes: 13 additions & 0 deletions source/common/http/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,19 @@ bool Utility::isWebSocketUpgradeRequest(const RequestHeaderMap& headers) {
Http::Headers::get().UpgradeValues.WebSocket));
}

void Utility::removeConnectionUpgrade(RequestOrResponseHeaderMap& headers,
StringUtil::CaseUnorderedSet tokens_to_remove) {
if (headers.Connection()) {
std::string new_value =
StringUtil::removeTokens(headers.getConnectionValue(), ",", tokens_to_remove, ",");
if (new_value.empty()) {
headers.removeConnection();
} else {
headers.setConnection(new_value);
}
}
}

Utility::PreparedLocalReplyPtr Utility::prepareLocalReply(const EncodeFunctions& encode_functions,
const LocalReplyData& local_reply_data) {
Code response_code = local_reply_data.response_code_;
Expand Down
7 changes: 7 additions & 0 deletions source/common/http/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,13 @@ bool isH3UpgradeRequest(const RequestHeaderMap& headers);
*/
bool isWebSocketUpgradeRequest(const RequestHeaderMap& headers);

/**
* Removes `tokens_to_remove` from the `Connection` header, if present and part of a comma separated
* set of values. Removes the `Connection` header if it only contains `tokens_to_remove`.
*/
void removeConnectionUpgrade(RequestOrResponseHeaderMap& headers,
StringUtil::CaseUnorderedSet tokens_to_remove);

struct EncodeFunctions {
// Function to modify locally generated response headers.
std::function<void(ResponseHeaderMap& headers)> modify_headers_;
Expand Down
8 changes: 5 additions & 3 deletions source/common/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,12 @@ class FilterConfig : Http::FilterChainFactory {
return true;
}

bool createUpgradeFilterChain(absl::string_view, const UpgradeMap*, Http::FilterChainManager&,
const Http::FilterChainOptions&) const override {
Http::FilterChainFactory::UpgradeAction
createUpgradeFilterChain(absl::string_view, const UpgradeMap*, absl::optional<Http::Protocol>,
Http::FilterChainManager&,
const Http::FilterChainOptions&) const override {
// Upgrade filter chains not yet supported for upstream HTTP filters.
return false;
return FilterChainFactory::UpgradeAction::Rejected;
}

using HeaderVector = std::vector<Http::LowerCaseString>;
Expand Down
8 changes: 5 additions & 3 deletions source/common/upstream/upstream_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1029,10 +1029,12 @@ class ClusterInfoImpl : public ClusterInfo,
manager, Http::EmptyFilterChainOptions{}, http_filter_factories_);
return true;
}
bool createUpgradeFilterChain(absl::string_view, const UpgradeMap*, Http::FilterChainManager&,
const Http::FilterChainOptions&) const override {
Http::FilterChainFactory::UpgradeAction
createUpgradeFilterChain(absl::string_view, const UpgradeMap*, absl::optional<Http::Protocol>,
Http::FilterChainManager&,
const Http::FilterChainOptions&) const override {
// Upgrade filter chains not yet supported for upstream HTTP filters.
return false;
return Http::FilterChainFactory::UpgradeAction::Rejected;
}

Http::Http1::CodecStats& http1CodecStats() const override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig(
internal_address_config_(createInternalAddressConfig(config, creation_status)),
xff_num_trusted_hops_(config.xff_num_trusted_hops()),
skip_xff_append_(config.skip_xff_append()), via_(config.via()),
ignore_unconfigured_http11_upgrades_(
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, ignore_unconfigured_http11_upgrades, true)),
scoped_routes_config_provider_manager_(scoped_routes_config_provider_manager),
filter_config_provider_manager_(filter_config_provider_manager),
http3_options_(Http3::Utility::initializeAndValidateOptions(
Expand Down Expand Up @@ -729,13 +731,20 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig(

for (const auto& upgrade_config : config.upgrade_configs()) {
const std::string& name = upgrade_config.upgrade_type();
const bool enabled = upgrade_config.has_enabled() ? upgrade_config.enabled().value() : true;
const bool ignored = PROTOBUF_GET_WRAPPED_OR_DEFAULT(upgrade_config, ignore_on_http11, false);
const bool enabled = PROTOBUF_GET_WRAPPED_OR_DEFAULT(upgrade_config, enabled, !ignored);
if (findUpgradeCaseInsensitive(upgrade_filter_factories_, name) !=
upgrade_filter_factories_.end()) {
creation_status = absl::InvalidArgumentError(
fmt::format("Error: multiple upgrade configs with the same name: '{}'", name));
return;
}
if (enabled && ignored) {
creation_status = absl::InvalidArgumentError(fmt::format(
"Error: upgrade config set both `ignore_on_http11` and `enabled` for upgrade type: '{}'",
name));
return;
}
if (!upgrade_config.filters().empty()) {
std::unique_ptr<FilterFactoriesList> factories = std::make_unique<FilterFactoriesList>();
Http::DependencyManager upgrade_dependency_manager;
Expand All @@ -747,11 +756,11 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig(
SET_AND_RETURN_IF_NOT_OK(status, creation_status);

upgrade_filter_factories_.emplace(
std::make_pair(name, FilterConfig{std::move(factories), enabled}));
std::make_pair(name, FilterConfig{std::move(factories), enabled, ignored}));
} else {
std::unique_ptr<FilterFactoriesList> factories(nullptr);
upgrade_filter_factories_.emplace(
std::make_pair(name, FilterConfig{std::move(factories), enabled}));
std::make_pair(name, FilterConfig{std::move(factories), enabled, ignored}));
}
}
}
Expand Down Expand Up @@ -796,36 +805,59 @@ bool HttpConnectionManagerConfig::createFilterChain(Http::FilterChainManager& ma
return true;
}

bool HttpConnectionManagerConfig::createUpgradeFilterChain(
Http::FilterChainFactory::UpgradeAction HttpConnectionManagerConfig::createUpgradeFilterChain(
absl::string_view upgrade_type,
const Http::FilterChainFactory::UpgradeMap* per_route_upgrade_map,
Http::FilterChainManager& callbacks, const Http::FilterChainOptions& options) const {
absl::optional<Http::Protocol> http_version, Http::FilterChainManager& callbacks,
const Http::FilterChainOptions& options) const {
bool route_enabled = false;
if (per_route_upgrade_map) {
auto route_it = findUpgradeBoolCaseInsensitive(*per_route_upgrade_map, upgrade_type);
if (route_it != per_route_upgrade_map->end()) {
// Upgrades explicitly not allowed on this route.
if (route_it->second == false) {
return false;
return Http::FilterChainFactory::UpgradeAction::Rejected;
}
// Upgrades explicitly enabled on this route.
route_enabled = true;
}
}

auto it = findUpgradeCaseInsensitive(upgrade_filter_factories_, upgrade_type);
if ((it == upgrade_filter_factories_.end() || !it->second.allow_upgrade) && !route_enabled) {
// Either the HCM disables upgrades and the route-config does not override,
// or neither is configured for this upgrade.
return false;
if (route_enabled || (it != upgrade_filter_factories_.end() && it->second.allow_upgrade)) {
// Either the per-route config enables this upgrade, or the HCM config allows the upgrade.
const FilterFactoriesList* filters_to_use = &filter_factories_;
if (it != upgrade_filter_factories_.end() && it->second.filter_factories != nullptr) {
filters_to_use = it->second.filter_factories.get();
}

Http::FilterChainUtility::createFilterChainForFactories(callbacks, options, *filters_to_use);
return Http::FilterChainFactory::UpgradeAction::Accepted;
}
const FilterFactoriesList* filters_to_use = &filter_factories_;
if (it != upgrade_filter_factories_.end() && it->second.filter_factories != nullptr) {
filters_to_use = it->second.filter_factories.get();

// Upgrades are handled differently depending on the HTTP version. In version 1.1 only, upgrades
// can be ignored by the server and the request processed as if there had not been an upgrade
// request.
const bool can_ignore = (http_version.has_value() && *http_version == Http::Protocol::Http11);

const bool ignore_specific_upgrade_type =
(it != upgrade_filter_factories_.end() && it->second.ignore_on_http11);
const bool no_specific_config_and_ignore_unknown =
(it == upgrade_filter_factories_.end() && ignore_unconfigured_http11_upgrades_);

if (can_ignore && (ignore_specific_upgrade_type || no_specific_config_and_ignore_unknown)) {
// Either the HCM ignores all unconfigured upgrades, or this upgrade type is configured to be
// ignored.
ENVOY_LOG(trace, "Ignoring upgrade for requested type: {}", upgrade_type);
return Http::FilterChainFactory::UpgradeAction::Ignored;
}

Http::FilterChainUtility::createFilterChainForFactories(callbacks, options, *filters_to_use);
return true;
// Fall-through cases: the HCM does not ignore unconfigured upgrades, or this upgrade type is
// configured to be disallowed.
ASSERT(!can_ignore || !ignore_unconfigured_http11_upgrades_ ||
(it != upgrade_filter_factories_.end() && !it->second.ignore_on_http11 &&
!it->second.allow_upgrade));
return Http::FilterChainFactory::UpgradeAction::Rejected;
}

const Network::Address::Instance& HttpConnectionManagerConfig::localAddress() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,15 @@ class HttpConnectionManagerConfig : Logger::Loggable<Logger::Id::config>,
using FilterFactoriesList = Envoy::Http::FilterChainUtility::FilterFactoriesList;
struct FilterConfig {
std::unique_ptr<FilterFactoriesList> filter_factories;
bool allow_upgrade;
const bool allow_upgrade;
const bool ignore_on_http11;
};
bool createUpgradeFilterChain(absl::string_view upgrade_type,
const Http::FilterChainFactory::UpgradeMap* per_route_upgrade_map,
Http::FilterChainManager& manager,
const Http::FilterChainOptions& options) const override;
Http::FilterChainFactory::UpgradeAction
createUpgradeFilterChain(absl::string_view upgrade_type,
const Http::FilterChainFactory::UpgradeMap* per_route_upgrade_map,
absl::optional<Http::Protocol> http_version,
Http::FilterChainManager& manager,
const Http::FilterChainOptions& options) const override;

// Http::ConnectionManagerConfig
const Http::RequestIDExtensionSharedPtr& requestIDExtension() override {
Expand Down Expand Up @@ -307,6 +310,7 @@ class HttpConnectionManagerConfig : Logger::Loggable<Logger::Id::config>,
const uint32_t xff_num_trusted_hops_;
const bool skip_xff_append_;
const std::string via_;
const bool ignore_unconfigured_http11_upgrades_;
Http::ForwardClientCertType forward_client_cert_;
std::vector<Http::ClientCertDetailsType> set_current_client_cert_details_;
Config::ConfigProviderManager* scoped_routes_config_provider_manager_;
Expand Down
Loading