diff --git a/CODEOWNERS b/CODEOWNERS index d988d86308c33..c5a719a787a8e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,7 +3,7 @@ #* @envoyproxy/maintainers # dubbo_proxy extension -/*/extensions/filters/network/dubbo_proxy @zyfjeff @lizan +/*/extensions/filters/network/dubbo_proxy @zyfjeff @lizan # thrift_proxy extension /*/extensions/filters/network/thrift_proxy @zuercher @brian-pane # jwt_authn http filter extension @@ -14,3 +14,5 @@ /*/extensions/transport_sockets/alts @htuch @yangminzhu # sni_cluster extension /*/extensions/filters/network/sni_cluster @rshriram @lizan +# extension +/*/extensions/filters/network/forward_original_sni @lizan @vadimeisenbergibm diff --git a/docs/root/configuration/network_filters/forward_original_sni.rst b/docs/root/configuration/network_filters/forward_original_sni.rst new file mode 100644 index 0000000000000..9b9e28bc631df --- /dev/null +++ b/docs/root/configuration/network_filters/forward_original_sni.rst @@ -0,0 +1,14 @@ +.. _config_network_filters_forward_original_sni: + +Forward Original SNI +========================= + +The `forward_original_sni` is a network filter that instructs other filters, +such as `tcp_proxy`, to forward the SNI value from the downstream connection +to the upstream connection. The filter will do nothing for non-TLS connections or +for TLS connections without SNI. + +This filter has no configuration. It must be installed before the +:ref:`tcp_proxy ` filter. + +* :ref:`v2 API reference ` diff --git a/docs/root/configuration/network_filters/network_filters.rst b/docs/root/configuration/network_filters/network_filters.rst index 5ef34c7dd4724..c0557a90ee845 100644 --- a/docs/root/configuration/network_filters/network_filters.rst +++ b/docs/root/configuration/network_filters/network_filters.rst @@ -20,3 +20,4 @@ filters. tcp_proxy_filter thrift_proxy_filter sni_cluster_filter + forward_original_sni diff --git a/docs/root/configuration/network_filters/tcp_proxy_filter.rst b/docs/root/configuration/network_filters/tcp_proxy_filter.rst index 91e83903ec2e2..e0e6048112f52 100644 --- a/docs/root/configuration/network_filters/tcp_proxy_filter.rst +++ b/docs/root/configuration/network_filters/tcp_proxy_filter.rst @@ -18,6 +18,14 @@ implementation for the details. .. _config_network_filters_tcp_proxy_stats: +Setting SNI +----------- + +If `forward_original_sni` filter is installed, the TCP proxy filter will +override the value in the `sni` field of +:ref:`UpstreamTlsContext ` of the +upstream cluster by the SNI value from the downstream TLS connection. + Statistics ---------- diff --git a/docs/root/intro/arch_overview/ssl.rst b/docs/root/intro/arch_overview/ssl.rst index 9d8bdad18676d..46d570a460fd9 100644 --- a/docs/root/intro/arch_overview/ssl.rst +++ b/docs/root/intro/arch_overview/ssl.rst @@ -18,7 +18,7 @@ requirements (TLS1.2, SNI, etc.). Envoy supports the following TLS features: (CRL) if one is :ref:`provided `. * **ALPN**: TLS listeners support ALPN. The HTTP connection manager uses this information (in addition to protocol inference) to determine whether a client is speaking HTTP/1.1 or HTTP/2. -* **SNI**: SNI is supported for both server (listener) and client (upstream) connections. +* **SNI**: SNI is supported for both server (listener) and client (upstream) connections, including forwarding the SNI value from the downstream connection to the upstream one. * **Session resumption**: Server connections support resuming previous sessions via TLS session tickets (see `RFC 5077 `_). Resumption can be performed across hot restarts and between parallel Envoy instances (typically useful in a front proxy diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index e5a6d9a5ff1bd..73daa2550e7f4 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -43,6 +43,8 @@ Version history * tracing: added support to the Zipkin tracer for the :ref:`b3 ` single header format. * upstream: changed how load calculation for :ref:`priority levels` and :ref:`panic thresholds` interact. As long as normalized total health is 100% panic thresholds are disregarded. * upstream: changed the default hash for :ref:`ring hash ` from std::hash to `xxHash `_. +* network: introduced :ref:`forward_original_sni ` network filter that instructs `tcp_proxy` to forward the SNI value from the downstream TLS connection to the upstream TLS connection. If `forward_original_sni` is installed, `tcp_proxy` will override the value in the `sni` field of :ref:`UpstreamTlsContext ` of + the upstream cluster by the SNI value from the downstream connection. 1.8.0 (Oct 4, 2018) =================== diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 1a2a243e5f535..d507740acb626 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -66,6 +66,7 @@ EXTENSIONS = { "envoy.filters.network.ext_authz": "//source/extensions/filters/network/ext_authz:config", "envoy.filters.network.http_connection_manager": "//source/extensions/filters/network/http_connection_manager:config", "envoy.filters.network.mongo_proxy": "//source/extensions/filters/network/mongo_proxy:config", + "envoy.filters.network.forward_original_sni": "//source/extensions/filters/network/forward_original_sni:config", "envoy.filters.network.ratelimit": "//source/extensions/filters/network/ratelimit:config", "envoy.filters.network.rbac": "//source/extensions/filters/network/rbac:config", "envoy.filters.network.redis_proxy": "//source/extensions/filters/network/redis_proxy:config", @@ -182,6 +183,7 @@ WINDOWS_EXTENSIONS = { #"envoy.filters.network.ext_authz": "//source/extensions/filters/network/ext_authz:config", #"envoy.filters.network.http_connection_manager": "//source/extensions/filters/network/http_connection_manager:config", #"envoy.filters.network.mongo_proxy": "//source/extensions/filters/network/mongo_proxy:config", + #"envoy.filters.network.original_sni": "//source/extensions/filters/network/original_sni:config", #"envoy.filters.network.redis_proxy": "//source/extensions/filters/network/redis_proxy:config", #"envoy.filters.network.ratelimit": "//source/extensions/filters/network/ratelimit:config", "envoy.filters.network.tcp_proxy": "//source/extensions/filters/network/tcp_proxy:config", diff --git a/source/extensions/filters/network/forward_original_sni/BUILD b/source/extensions/filters/network/forward_original_sni/BUILD new file mode 100644 index 0000000000000..580659df03489 --- /dev/null +++ b/source/extensions/filters/network/forward_original_sni/BUILD @@ -0,0 +1,32 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "forward_original_sni", + srcs = ["forward_original_sni.cc"], + hdrs = ["forward_original_sni.h"], + deps = [ + "//include/envoy/network:connection_interface", + "//include/envoy/network:filter_interface", + "//source/common/stream_info:forward_requested_server_name_lib", + ], +) + +envoy_cc_library( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":forward_original_sni", + "//include/envoy/registry", + "//include/envoy/server:filter_config_interface", + "//source/extensions/filters/network:well_known_names", + ], +) diff --git a/source/extensions/filters/network/forward_original_sni/config.cc b/source/extensions/filters/network/forward_original_sni/config.cc new file mode 100644 index 0000000000000..b7a311a8a23eb --- /dev/null +++ b/source/extensions/filters/network/forward_original_sni/config.cc @@ -0,0 +1,40 @@ +#include "extensions/filters/network/forward_original_sni/config.h" + +#include "envoy/registry/registry.h" +#include "envoy/server/filter_config.h" + +#include "extensions/filters/network/forward_original_sni/forward_original_sni.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ForwardOriginalSni { + +Network::FilterFactoryCb ForwardOriginalSniNetworkFilterConfigFactory::createFilterFactory( + const Json::Object&, Server::Configuration::FactoryContext&) { + // Only used in v1 filters. + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} + +Network::FilterFactoryCb ForwardOriginalSniNetworkFilterConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message&, Server::Configuration::FactoryContext&) { + return [](Network::FilterManager& filter_manager) -> void { + filter_manager.addReadFilter(std::make_shared()); + }; +} + +ProtobufTypes::MessagePtr ForwardOriginalSniNetworkFilterConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +/** + * Static registration for the forward_original_sni filter. @see RegisterFactory. + */ +static Registry::RegisterFactory + registered_; + +} // namespace ForwardOriginalSni +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/forward_original_sni/config.h b/source/extensions/filters/network/forward_original_sni/config.h new file mode 100644 index 0000000000000..213772e1f507f --- /dev/null +++ b/source/extensions/filters/network/forward_original_sni/config.h @@ -0,0 +1,31 @@ +#pragma once + +#include "envoy/server/filter_config.h" + +#include "extensions/filters/network/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ForwardOriginalSni { + +/** + * Config registration for the original_sni filter. @see NamedNetworkFilterConfigFactory. + */ +class ForwardOriginalSniNetworkFilterConfigFactory + : public Server::Configuration::NamedNetworkFilterConfigFactory { +public: + // NamedNetworkFilterConfigFactory + Network::FilterFactoryCb createFilterFactory(const Json::Object&, + Server::Configuration::FactoryContext&) override; + Network::FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message&, + Server::Configuration::FactoryContext&) override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + std::string name() override { return NetworkFilterNames::get().ForwardOriginalSni; } +}; + +} // namespace ForwardOriginalSni +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/forward_original_sni/forward_original_sni.cc b/source/extensions/filters/network/forward_original_sni/forward_original_sni.cc new file mode 100644 index 0000000000000..3910dca458da4 --- /dev/null +++ b/source/extensions/filters/network/forward_original_sni/forward_original_sni.cc @@ -0,0 +1,29 @@ +#include "extensions/filters/network/forward_original_sni/forward_original_sni.h" + +#include "envoy/network/connection.h" + +#include "common/stream_info/forward_requested_server_name.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ForwardOriginalSni { + +using ::Envoy::StreamInfo::ForwardRequestedServerName; + +Network::FilterStatus ForwardOriginalSniFilter::onNewConnection() { + absl::string_view sni = read_callbacks_->connection().requestedServerName(); + + if (!sni.empty()) { + read_callbacks_->connection().streamInfo().filterState().setData( + ForwardRequestedServerName::Key, std::make_unique(sni), + StreamInfo::FilterState::StateType::ReadOnly); + } + + return Network::FilterStatus::Continue; +} + +} // namespace ForwardOriginalSni +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/forward_original_sni/forward_original_sni.h b/source/extensions/filters/network/forward_original_sni/forward_original_sni.h new file mode 100644 index 0000000000000..5e2bf4a38bb08 --- /dev/null +++ b/source/extensions/filters/network/forward_original_sni/forward_original_sni.h @@ -0,0 +1,32 @@ +#pragma once + +#include "envoy/network/filter.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ForwardOriginalSni { + +/** + * Implementation of the original_sni filter that sets the original requested server name from + * the SNI field in the TLS connection. + */ +class ForwardOriginalSniFilter : public Network::ReadFilter { +public: + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance&, bool) override { + return Network::FilterStatus::Continue; + } + Network::FilterStatus onNewConnection() override; + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + } + +private: + Network::ReadFilterCallbacks* read_callbacks_{}; +}; + +} // namespace ForwardOriginalSni +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/forward_original_sni/BUILD b/test/extensions/filters/network/forward_original_sni/BUILD new file mode 100644 index 0000000000000..f06273d973a67 --- /dev/null +++ b/test/extensions/filters/network/forward_original_sni/BUILD @@ -0,0 +1,26 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +envoy_package() + +envoy_extension_cc_test( + name = "forward_original_sni_test", + srcs = ["forward_original_sni_test.cc"], + extension_name = "envoy.filters.network.forward_original_sni", + deps = [ + "//source/extensions/filters/network/forward_original_sni", + "//source/extensions/filters/network/forward_original_sni:config", + "//test/mocks/network:network_mocks", + "//test/mocks/server:server_mocks", + "//test/mocks/stream_info:stream_info_mocks", + ], +) diff --git a/test/extensions/filters/network/forward_original_sni/forward_original_sni_test.cc b/test/extensions/filters/network/forward_original_sni/forward_original_sni_test.cc new file mode 100644 index 0000000000000..b6b262ba108c5 --- /dev/null +++ b/test/extensions/filters/network/forward_original_sni/forward_original_sni_test.cc @@ -0,0 +1,78 @@ +#include "common/stream_info/forward_requested_server_name.h" + +#include "extensions/filters/network/forward_original_sni/config.h" +#include "extensions/filters/network/forward_original_sni/forward_original_sni.h" + +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/mocks/stream_info/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Matcher; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ForwardOriginalSni { + +using ::Envoy::StreamInfo::ForwardRequestedServerName; + +// Test that a ForwardOriginalSni filter config works. +TEST(ForwardOriginalSni, ConfigTest) { + NiceMock context; + ForwardOriginalSniNetworkFilterConfigFactory factory; + + Network::FilterFactoryCb cb = + factory.createFilterFactoryFromProto(*factory.createEmptyConfigProto(), context); + Network::MockConnection connection; + EXPECT_CALL(connection, addReadFilter(_)); + cb(connection); +} + +// Test that forward requested server name is set if SNI is available +TEST(ForwardOriginalSni, SetForwardRequestedServerNameOnlyIfSniIsPresent) { + NiceMock filter_callbacks; + + NiceMock stream_info; + ON_CALL(filter_callbacks.connection_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + ON_CALL(Const(filter_callbacks.connection_), streamInfo()).WillByDefault(ReturnRef(stream_info)); + + ForwardOriginalSniFilter filter; + filter.initializeReadFilterCallbacks(filter_callbacks); + + // no sni + { + ON_CALL(filter_callbacks.connection_, requestedServerName()) + .WillByDefault(Return(EMPTY_STRING)); + filter.onNewConnection(); + + EXPECT_FALSE(stream_info.filterState().hasData( + ForwardRequestedServerName::Key)); + } + + // with sni + { + ON_CALL(filter_callbacks.connection_, requestedServerName()) + .WillByDefault(Return("www.example.com")); + filter.onNewConnection(); + + EXPECT_TRUE(stream_info.filterState().hasData( + ForwardRequestedServerName::Key)); + + auto forward_requested_server_name = + stream_info.filterState().getDataReadOnly( + ForwardRequestedServerName::Key); + EXPECT_EQ(forward_requested_server_name.value(), "www.example.com"); + } +} + +} // namespace ForwardOriginalSni +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy