From b24190c7b9ad865521957e18730b0a421e6f7dfe Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Sat, 10 Feb 2018 20:44:56 -0800 Subject: [PATCH 1/4] listener: add support for multiple filter chains. *Risk Level*: Medium *Testing*: bazel test //test/... *Docs Changes*: Added *Release Notes*: Added Fixes #1843. Signed-off-by: Piotr Sikora --- api/envoy/api/v2/lds.proto | 11 +- api/envoy/api/v2/listener/listener.proto | 39 +- docs/root/configuration/listeners/stats.rst | 2 +- docs/root/faq/sni.rst | 7 - docs/root/intro/version_history.rst | 1 + include/envoy/network/BUILD | 3 +- include/envoy/network/filter.h | 43 +- include/envoy/network/listener.h | 11 +- include/envoy/network/transport_socket.h | 4 +- .../envoy/server/transport_socket_config.h | 13 +- include/envoy/ssl/context_manager.h | 17 +- source/common/network/BUILD | 1 + source/common/network/raw_buffer_socket.h | 1 + source/common/protobuf/utility.h | 8 + source/common/ssl/BUILD | 2 +- source/common/ssl/context_impl.cc | 98 +-- source/common/ssl/context_impl.h | 18 +- source/common/ssl/context_manager_impl.cc | 108 +-- source/common/ssl/context_manager_impl.h | 19 +- source/common/ssl/ssl_socket.cc | 9 +- source/common/ssl/ssl_socket.h | 6 +- source/common/upstream/upstream_impl.cc | 2 +- .../listener/tls_inspector/tls_inspector.cc | 2 +- .../transport_sockets/capture/config.cc | 7 +- .../transport_sockets/capture/config.h | 8 +- .../transport_sockets/raw_buffer/config.cc | 4 +- .../transport_sockets/raw_buffer/config.h | 8 +- .../transport_sockets/ssl/config.cc | 8 +- .../extensions/transport_sockets/ssl/config.h | 12 +- .../transport_sockets/well_known_names.h | 2 +- source/server/BUILD | 2 + source/server/connection_handler_impl.cc | 40 +- source/server/connection_handler_impl.h | 3 +- source/server/http/BUILD | 1 + source/server/http/admin.cc | 6 +- source/server/http/admin.h | 35 +- source/server/listener_manager_impl.cc | 164 +++-- source/server/listener_manager_impl.h | 55 +- .../grpc/grpc_client_integration_test.cc | 5 +- test/common/ssl/context_impl_test.cc | 5 +- test/common/ssl/ssl_socket_test.cc | 664 +++++------------- .../proxy_protocol/proxy_protocol_test.cc | 40 +- .../tls_inspector/tls_inspector_test.cc | 6 +- test/integration/BUILD | 2 + test/integration/ads_integration_test.cc | 5 +- test/integration/autonomous_upstream.cc | 3 +- test/integration/autonomous_upstream.h | 4 +- test/integration/fake_upstream.cc | 11 +- test/integration/fake_upstream.h | 19 +- test/integration/ssl_integration_test.cc | 2 +- test/integration/xfcc_integration_test.cc | 5 +- test/mocks/network/mocks.cc | 6 + test/mocks/network/mocks.h | 25 +- test/mocks/ssl/mocks.h | 17 +- test/server/BUILD | 3 + test/server/connection_handler_test.cc | 49 +- test/server/listener_manager_impl_test.cc | 363 ++++++---- test/test_common/BUILD | 1 + test/test_common/network_utility.cc | 12 +- test/test_common/network_utility.h | 38 + 60 files changed, 983 insertions(+), 1082 deletions(-) diff --git a/api/envoy/api/v2/lds.proto b/api/envoy/api/v2/lds.proto index 7780fea23ed78..e3a5c8a49ee44 100644 --- a/api/envoy/api/v2/lds.proto +++ b/api/envoy/api/v2/lds.proto @@ -55,15 +55,8 @@ message Listener { // :ref:`FilterChainMatch ` criteria is used on a // connection. // - // .. attention:: - // - // In the current version, multiple filter chains are supported **only** so that SNI can be - // configured. See the :ref:`FAQ entry ` on how to configure SNI for more - // information. When multiple filter chains are configured, each filter chain must have an - // **identical** set of :ref:`filters `. If the - // filters differ, the configuration will fail to load. In the future, this limitation will be - // relaxed such that different filters can be used depending on which filter chain matches - // (based on SNI or some other parameter). + // Example using SNI for filter chain selection can be found in the + // :ref:`FAQ entry `. repeated listener.FilterChain filter_chains = 3 [(validate.rules).repeated .min_items = 1, (gogoproto.nullable) = false]; diff --git a/api/envoy/api/v2/listener/listener.proto b/api/envoy/api/v2/listener/listener.proto index 6889b467051cf..d459f201ce03d 100644 --- a/api/envoy/api/v2/listener/listener.proto +++ b/api/envoy/api/v2/listener/listener.proto @@ -47,12 +47,32 @@ message Filter { // Specifies the match criteria for selecting a specific filter chain for a // listener. +// +// In order for a filter chain to be selected, *ALL* of its criteria must be +// fulfilled by the incoming connection, properties of which are set by the +// networking stack and/or listener filters. +// +// The following order applies: +// +// [#comment:TODO(PiotrSikora): destination IP / ranges are going to be 1.] +// 1. Server name (e.g. SNI for TLS protocol), +// 2. Transport protocol. +// [#comment:TODO(PiotrSikora): application protocols are going to be 4.] +// +// For criterias that allow ranges or wildcards, the most specific value in any +// of the configured filter chains that matches the incoming connection is going +// to be used (e.g. for SNI ``www.example.com`` the most specific match would be +// ``www.example.com``, then ``*.example.com``, then any filter chain without +// ``sni_domains`` requirements). +// +// [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] message FilterChainMatch { // If non-empty, the SNI domain names to consider. May contain a wildcard prefix for - // the bottom-level domain of a domain name, e.g. ``*.example.com``. Note that - // ``foo.example.com`` will be matched by ``foo.example.com`` and ``*.example.com`` - // SNI domain names, but **not** by ``*foo.example.com``, ``*oo.example.com``, - // ``*example.com``, ``*.com`` or ``*``. + // the bottom-level domain of a domain name, e.g. ``*.example.com``. + // + // Note that ``foo.example.com`` will be matched by ``foo.example.com`` + // and ``*.example.com`` SNI domain names, but **not** by ``*foo.example.com``, + // ``*oo.example.com``, ``*example.com``, ``*.com`` or ``*``. // // .. attention:: // @@ -90,6 +110,17 @@ message FilterChainMatch { // listener in determining a filter chain match. // [#not-implemented-hide:] google.protobuf.UInt32Value destination_port = 8; + + // If non-empty, a transport protocol to consider when determining a filter chain match. + // This value will be compared against the transport protocol of a new connection, when + // it's detected by one of the listener filters (this needs to be configured separately). + // + // Valid values include: + // + // * ``raw_buffer`` - default, used when no transport protocol is detected, + // * ``tls`` - set by :ref:`envoy.listener.tls_inspector ` + // when TLS protocol is detected. + string transport_protocol = 9; } // A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and diff --git a/docs/root/configuration/listeners/stats.rst b/docs/root/configuration/listeners/stats.rst index d0c30c5e7696f..049ec04ebe757 100644 --- a/docs/root/configuration/listeners/stats.rst +++ b/docs/root/configuration/listeners/stats.rst @@ -16,11 +16,11 @@ Every listener has a statistics tree rooted at *listener.
.* with the fo downstream_cx_destroy, Counter, Total destroyed connections downstream_cx_active, Gauge, Total active connections downstream_cx_length_ms, Histogram, Connection length milliseconds + no_filter_chain_match, Counter, Total connections that didn't match any filter chain ssl.connection_error, Counter, Total TLS connection errors not including failed certificate verifications ssl.handshake, Counter, Total successful TLS connection handshakes ssl.session_reused, Counter, Total successful TLS session resumptions ssl.no_certificate, Counter, Total successul TLS connections with no client certificate - ssl.fail_no_sni_match, Counter, Total TLS connections that were rejected because of missing SNI match ssl.fail_verify_no_cert, Counter, Total TLS connections that failed because of missing client certificate ssl.fail_verify_error, Counter, Total TLS connections that failed CA verification ssl.fail_verify_san, Counter, Total TLS connections that failed SAN verification diff --git a/docs/root/faq/sni.rst b/docs/root/faq/sni.rst index 21e1e500aa173..b518c4f693c5e 100644 --- a/docs/root/faq/sni.rst +++ b/docs/root/faq/sni.rst @@ -6,13 +6,6 @@ How do I setup SNI? `SNI `_ is only supported in the :ref:`v2 configuration/API `. -The current implementation has the requirement that the :ref:`filters -` in every :ref:`FilterChain ` must -be identical. In a future release, this requirement will be relaxed so that SNI can be used to -choose between completely different filter chains. :ref:`Domain name matching -` can still be used within the HTTP connection manager to -choose different routes. This is by far the most common use case for SNI. - The following is a YAML example of the above requirement. .. code-block:: yaml diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 9969a846ab830..777447115e667 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -53,6 +53,7 @@ Version history Notably the HTTP response code is always "200" in this case, and the gRPC error code is carried in "grpc-status" header, optionally accompanied with a text message in "grpc-message" header. * listeners: added :ref:`tcp_fast_open_queue_length ` option. +* listeners: removed restriction on all filter chains having identical filters. * load balancing: added :ref:`weighted round robin ` support. The round robin scheduler now respects endpoint weights and also has improved fidelity across diff --git a/include/envoy/network/BUILD b/include/envoy/network/BUILD index d1be2bcb65b97..f8cf005e53c49 100644 --- a/include/envoy/network/BUILD +++ b/include/envoy/network/BUILD @@ -52,6 +52,8 @@ envoy_cc_library( name = "filter_interface", hdrs = ["filter.h"], deps = [ + ":listen_socket_interface", + ":transport_socket_interface", "//include/envoy/buffer:buffer_interface", "//include/envoy/upstream:host_description_interface", ], @@ -67,7 +69,6 @@ envoy_cc_library( name = "transport_socket_interface", hdrs = ["transport_socket.h"], deps = [ - ":connection_interface", "//include/envoy/ssl:connection_interface", ], ) diff --git a/include/envoy/network/filter.h b/include/envoy/network/filter.h index ec94e4b7cd994..1079b76b0f255 100644 --- a/include/envoy/network/filter.h +++ b/include/envoy/network/filter.h @@ -3,6 +3,8 @@ #include #include "envoy/buffer/buffer.h" +#include "envoy/network/listen_socket.h" +#include "envoy/network/transport_socket.h" #include "envoy/upstream/host_description.h" namespace Envoy { @@ -232,6 +234,43 @@ class ListenerFilterManager { */ typedef std::function ListenerFilterFactoryCb; +/** + * Interface representing a single filter chain. + */ +class FilterChain { +public: + virtual ~FilterChain() {} + + /** + * @return const TransportSocketFactory& a transport socket factory to be used by the new + * connection. + */ + virtual const TransportSocketFactory& transportSocketFactory() const PURE; + + /** + * const std::vector& a list of filters to be used by the new connection. + */ + virtual const std::vector& networkFilterFactories() const PURE; +}; + +typedef std::shared_ptr FilterChainSharedPtr; + +/** + * Interface for searching through configured filter chains. + */ +class FilterChainManager { +public: + virtual ~FilterChainManager() {} + + /** + * Find filter chain that's matching metadata from the new connection. + * @param socket supplies connection metadata that's going to be used for the filter chain lookup. + * @return const FilterChain* filter chain to be used by the new connection, + * nullptr if no matching filter chain was found. + */ + virtual const FilterChain* findFilterChain(const ConnectionSocket& socket) const PURE; +}; + /** * Creates a chain of network filters for a new connection. */ @@ -242,10 +281,12 @@ class FilterChainFactory { /** * Called to create the network filter chain. * @param connection supplies the connection to create the chain on. + * @param filter_factories supplies a list of filter factories to create the chain from. * @return true if filter chain was created successfully. Otherwise * false, e.g. filter chain is empty. */ - virtual bool createNetworkFilterChain(Connection& connection) PURE; + virtual bool createNetworkFilterChain(Connection& connection, + const std::vector& filter_factories) PURE; /** * Called to create the listener filter chain. diff --git a/include/envoy/network/listener.h b/include/envoy/network/listener.h index 16cf4e5d481a3..74ca9fadf7498 100644 --- a/include/envoy/network/listener.h +++ b/include/envoy/network/listener.h @@ -20,6 +20,12 @@ class ListenerConfig { public: virtual ~ListenerConfig() {} + /** + * @return FilterChainManager& the factory for adding and searching through configured + * filter chains. + */ + virtual FilterChainManager& filterChainManager() PURE; + /** * @return FilterChainFactory& the factory for setting up the filter chain on a new * connection. @@ -32,11 +38,6 @@ class ListenerConfig { */ virtual Socket& socket() PURE; - /** - * @return TransportSocketFactory& the transport socket factory. - */ - virtual TransportSocketFactory& transportSocketFactory() PURE; - /** * @return bool specifies whether the listener should actually listen on the port. * A listener that doesn't listen on a port can only receive connections diff --git a/include/envoy/network/transport_socket.h b/include/envoy/network/transport_socket.h index b4938bb07fd55..a5390c29853ad 100644 --- a/include/envoy/network/transport_socket.h +++ b/include/envoy/network/transport_socket.h @@ -2,12 +2,14 @@ #include "envoy/buffer/buffer.h" #include "envoy/common/pure.h" -#include "envoy/network/connection.h" #include "envoy/ssl/connection.h" namespace Envoy { namespace Network { +class Connection; +enum class ConnectionEvent; + /** * Action that should occur on a connection after I/O. */ diff --git a/include/envoy/server/transport_socket_config.h b/include/envoy/server/transport_socket_config.h index fa6b671c7ff73..811aececd0486 100644 --- a/include/envoy/server/transport_socket_config.h +++ b/include/envoy/server/transport_socket_config.h @@ -77,14 +77,8 @@ class DownstreamTransportSocketConfigFactory : public virtual TransportSocketCon public: /** * Create a particular downstream transport socket factory implementation. - * TODO(lizan): Revisit the parameters for SNI below when TLS sniffing and filter chain match are - * implemented. - * @param listener_name const std::string& the name of the listener. * @param server_names const std::vector& the names of the server. This parameter is * currently used by SNI implementation to know the expected server names. - * @param skip_ssl_context_update bool indicates whether the ssl context update should be skipped. - * This parameter is currently used by SNI implementation to know whether it should perform - * certificate selection. * @param config const Protobuf::Message& supplies the config message for the transport socket * implementation. * @param context TransportSocketFactoryContext& supplies the transport socket's context. @@ -95,10 +89,9 @@ class DownstreamTransportSocketConfigFactory : public virtual TransportSocketCon * parameters. */ virtual Network::TransportSocketFactoryPtr - createTransportSocketFactory(const std::string& listener_name, - const std::vector& server_names, - bool skip_ssl_context_update, const Protobuf::Message& config, - TransportSocketFactoryContext& context) PURE; + createTransportSocketFactory(const Protobuf::Message& config, + TransportSocketFactoryContext& context, + const std::vector& server_names) PURE; }; } // namespace Configuration diff --git a/include/envoy/ssl/context_manager.h b/include/envoy/ssl/context_manager.h index 977f4128a156b..7489800c99caf 100644 --- a/include/envoy/ssl/context_manager.h +++ b/include/envoy/ssl/context_manager.h @@ -24,21 +24,10 @@ class ContextManager { /** * Builds a ServerContext from a ServerContextConfig. - * The skip_context_update parameter is used for fast-path (avoiding lock & context lookup) - * on listeners with a single filter chain and no SNI restrictions. */ - virtual ServerContextPtr createSslServerContext(const std::string& listener_name, - const std::vector& server_names, - Stats::Scope& scope, - const ServerContextConfig& config, - bool skip_context_update) PURE; - - /** - * Find ServerContext for a given listener and server_name. - * @return ServerContext or nullptr in case there is no match. - */ - virtual ServerContext* findSslServerContext(const std::string& listener_name, - const std::string& server_name) const PURE; + virtual ServerContextPtr + createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config, + const std::vector& server_names) PURE; /** * @return the number of days until the next certificate being managed will expire. diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 94ccfdd1b78ce..c9b268f6ab2f2 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -150,6 +150,7 @@ envoy_cc_library( hdrs = ["raw_buffer_socket.h"], deps = [ ":utility_lib", + "//include/envoy/network:connection_interface", "//include/envoy/network:transport_socket_interface", "//source/common/buffer:buffer_lib", "//source/common/common:empty_string", diff --git a/source/common/network/raw_buffer_socket.h b/source/common/network/raw_buffer_socket.h index 08434516d60b8..3ab5ac0a27254 100644 --- a/source/common/network/raw_buffer_socket.h +++ b/source/common/network/raw_buffer_socket.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/buffer/buffer.h" +#include "envoy/network/connection.h" #include "envoy/network/transport_socket.h" #include "common/common/logger.h" diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 1b98d7fa2f549..787dfed1b228e 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -122,6 +122,14 @@ class ProtoValidationException : public EnvoyException { class MessageUtil { public: + // std::hash + std::size_t operator()(const Protobuf::Message& message) const { return hash(message); } + + // std::equals_to + bool operator()(const Protobuf::Message& lhs, const Protobuf::Message& rhs) const { + return Protobuf::util::MessageDifferencer::Equivalent(lhs, rhs); + } + static std::size_t hash(const Protobuf::Message& message) { // Use Protobuf::io::CodedOutputStream to force deterministic serialization, so that the same // message doesn't hash to different values. diff --git a/source/common/ssl/BUILD b/source/common/ssl/BUILD index c433ca2db4478..d285ad4a4f748 100644 --- a/source/common/ssl/BUILD +++ b/source/common/ssl/BUILD @@ -16,6 +16,7 @@ envoy_cc_library( deps = [ ":context_config_lib", ":context_lib", + "//include/envoy/network:connection_interface", "//include/envoy/network:transport_socket_interface", "//source/common/common:assert_lib", "//source/common/common:empty_string", @@ -61,7 +62,6 @@ envoy_cc_library( "//include/envoy/stats:stats_interface", "//include/envoy/stats:stats_macros", "//source/common/common:assert_lib", - "//source/common/common:empty_string", "//source/common/common:hex_lib", ], ) diff --git a/source/common/ssl/context_impl.cc b/source/common/ssl/context_impl.cc index ab44d6e83251a..0d1bfe0f442ff 100644 --- a/source/common/ssl/context_impl.cc +++ b/source/common/ssl/context_impl.cc @@ -29,18 +29,17 @@ int ContextImpl::sslContextIndex() { ContextImpl::ContextImpl(ContextManagerImpl& parent, Stats::Scope& scope, const ContextConfig& config) - : parent_(parent), ctx_(SSL_CTX_new(TLS_method())), scope_(scope), stats_(generateStats(scope)), - min_protocol_version_(config.minProtocolVersion()), - max_protocol_version_(config.maxProtocolVersion()), ecdh_curves_(config.ecdhCurves()) { + : parent_(parent), ctx_(SSL_CTX_new(TLS_method())), scope_(scope), + stats_(generateStats(scope)) { RELEASE_ASSERT(ctx_); int rc = SSL_CTX_set_ex_data(ctx_.get(), sslContextIndex(), this); RELEASE_ASSERT(rc == 1); - rc = SSL_CTX_set_min_proto_version(ctx_.get(), min_protocol_version_); + rc = SSL_CTX_set_min_proto_version(ctx_.get(), config.minProtocolVersion()); RELEASE_ASSERT(rc == 1); - rc = SSL_CTX_set_max_proto_version(ctx_.get(), max_protocol_version_); + rc = SSL_CTX_set_max_proto_version(ctx_.get(), config.maxProtocolVersion()); RELEASE_ASSERT(rc == 1); if (!SSL_CTX_set_strict_cipher_list(ctx_.get(), config.cipherSuites().c_str())) { @@ -48,8 +47,8 @@ ContextImpl::ContextImpl(ContextManagerImpl& parent, Stats::Scope& scope, fmt::format("Failed to initialize cipher suites {}", config.cipherSuites())); } - if (!SSL_CTX_set1_curves_list(ctx_.get(), ecdh_curves_.c_str())) { - throw EnvoyException(fmt::format("Failed to initialize ECDH curves {}", ecdh_curves_)); + if (!SSL_CTX_set1_curves_list(ctx_.get(), config.ecdhCurves().c_str())) { + throw EnvoyException(fmt::format("Failed to initialize ECDH curves {}", config.ecdhCurves())); } int verify_mode = SSL_VERIFY_NONE; @@ -418,23 +417,15 @@ bssl::UniquePtr ClientContextImpl::newSsl() const { return ssl_con; } -ServerContextImpl::ServerContextImpl(ContextManagerImpl& parent, const std::string& listener_name, +ServerContextImpl::ServerContextImpl(ContextManagerImpl& parent, Stats::Scope& scope, + const ServerContextConfig& config, const std::vector& server_names, - Stats::Scope& scope, const ServerContextConfig& config, - bool skip_context_update, Runtime::Loader& runtime) - : ContextImpl(parent, scope, config), listener_name_(listener_name), - server_names_(server_names), skip_context_update_(skip_context_update), runtime_(runtime), + Runtime::Loader& runtime) + : ContextImpl(parent, scope, config), runtime_(runtime), session_ticket_keys_(config.sessionTicketKeys()) { if (config.certChain().empty()) { throw EnvoyException("Server TlsCertificates must have a certificate specified"); } - SSL_CTX_set_select_certificate_cb( - ctx_.get(), [](const SSL_CLIENT_HELLO* client_hello) -> ssl_select_cert_result_t { - ContextImpl* context_impl = static_cast( - SSL_CTX_get_ex_data(SSL_get_SSL_CTX(client_hello->ssl), sslContextIndex())); - return dynamic_cast(context_impl)->processClientHello(client_hello); - }); - if (!config.caCert().empty()) { bssl::UniquePtr bio( BIO_new_mem_buf(const_cast(config.caCert().data()), config.caCert().size())); @@ -575,7 +566,7 @@ ServerContextImpl::ServerContextImpl(ContextManagerImpl& parent, const std::stri // Hash configured SNIs for this context, so that sessions cannot be resumed across different // filter chains, even when using the same server certificate. - for (const auto& name : server_names_) { + for (const auto& name : server_names) { rc = EVP_DigestUpdate(&md, name.data(), name.size()); RELEASE_ASSERT(rc == 1); } @@ -586,73 +577,6 @@ ServerContextImpl::ServerContextImpl(ContextManagerImpl& parent, const std::stri RELEASE_ASSERT(rc == 1); } -ssl_select_cert_result_t -ServerContextImpl::processClientHello(const SSL_CLIENT_HELLO* client_hello) { - if (skip_context_update_) { - return ssl_select_cert_success; - } - - std::string server_name; - const uint8_t* data; - size_t len; - - if (SSL_early_callback_ctx_extension_get(client_hello, TLSEXT_TYPE_server_name, &data, &len)) { - // Based on BoringSSL's ext_sni_parse_clienthello(). - // Match on empty SNI instead of rejecting connection in case we cannot process the extension. - // TODO(PiotrSikora): figure out if we can upstream this to BoringSSL. - CBS extension; - CBS_init(&extension, data, len); - CBS server_name_list, host_name; - uint8_t name_type; - if (CBS_get_u16_length_prefixed(&extension, &server_name_list) && - CBS_get_u8(&server_name_list, &name_type) && - CBS_get_u16_length_prefixed(&server_name_list, &host_name) && - CBS_len(&server_name_list) == 0 && CBS_len(&extension) == 0 && - name_type == TLSEXT_NAMETYPE_host_name && CBS_len(&host_name) != 0 && - CBS_len(&host_name) <= TLSEXT_MAXLEN_host_name && !CBS_contains_zero_byte(&host_name)) { - server_name.assign(reinterpret_cast(CBS_data(&host_name)), CBS_len(&host_name)); - } - } - - ServerContext* new_ctx = parent_.findSslServerContext(listener_name_, server_name); - - // Reject connection if we didn't find a match. - if (new_ctx == nullptr) { - stats_.fail_no_sni_match_.inc(); - return ssl_select_cert_error; - } - - // Update context if it changed. - if (new_ctx != this) { - ServerContextImpl* new_impl = dynamic_cast(new_ctx); - new_impl->updateConnectionContext(client_hello->ssl); - } - - return ssl_select_cert_success; -} - -void ServerContextImpl::updateConnectionContext(SSL* ssl) { - ASSERT(ctx_); - - SSL_set_SSL_CTX(ssl, ctx_.get()); - ASSERT(SSL_CTX_get_ex_data(ctx_.get(), sslContextIndex()) == this); - - // Update SSL-level settings and parameters that are inherited from SSL_CTX during SSL_new(). - // TODO(PiotrSikora): add SSL_early_set_SSL_CTX() to BoringSSL. - - // TODO(PiotrSikora): add getters to BoringSSL. - int rc = SSL_set_min_proto_version(ssl, min_protocol_version_); - ASSERT(rc == 1); - rc = SSL_set_max_proto_version(ssl, max_protocol_version_); - ASSERT(rc == 1); - - SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ctx_.get()), SSL_CTX_get_verify_callback(ctx_.get())); - - // TODO(PiotrSikora): add getters to BoringSSL. - rc = SSL_set1_curves_list(ssl, ecdh_curves_.c_str()); - ASSERT(rc == 1); -} - int ServerContextImpl::sessionTicketProcess(SSL*, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, HMAC_CTX* hmac_ctx, int encrypt) { const EVP_MD* hmac = EVP_sha256(); diff --git a/source/common/ssl/context_impl.h b/source/common/ssl/context_impl.h index f7ac862a84b3c..aae4b9de73443 100644 --- a/source/common/ssl/context_impl.h +++ b/source/common/ssl/context_impl.h @@ -27,7 +27,6 @@ namespace Ssl { COUNTER(handshake) \ COUNTER(session_reused) \ COUNTER(no_certificate) \ - COUNTER(fail_no_sni_match) \ COUNTER(fail_verify_no_cert) \ COUNTER(fail_verify_error) \ COUNTER(fail_verify_san) \ @@ -77,6 +76,7 @@ class ContextImpl : public virtual Context { protected: ContextImpl(ContextManagerImpl& parent, Stats::Scope& scope, const ContextConfig& config); + ~ContextImpl() { parent_.releaseContext(this); } /** * The global SSL-library index used for storing a pointer to the context @@ -117,16 +117,12 @@ class ContextImpl : public virtual Context { bssl::UniquePtr cert_chain_; std::string ca_file_path_; std::string cert_chain_file_path_; - const uint16_t min_protocol_version_; - const uint16_t max_protocol_version_; - const std::string ecdh_curves_; }; class ClientContextImpl : public ContextImpl, public ClientContext { public: ClientContextImpl(ContextManagerImpl& parent, Stats::Scope& scope, const ClientContextConfig& config); - ~ClientContextImpl() { parent_.releaseClientContext(this); } bssl::UniquePtr newSsl() const override; @@ -136,24 +132,16 @@ class ClientContextImpl : public ContextImpl, public ClientContext { class ServerContextImpl : public ContextImpl, public ServerContext { public: - ServerContextImpl(ContextManagerImpl& parent, const std::string& listener_name, - const std::vector& server_names, Stats::Scope& scope, - const ServerContextConfig& config, bool skip_context_update, + ServerContextImpl(ContextManagerImpl& parent, Stats::Scope& scope, + const ServerContextConfig& config, const std::vector& server_names, Runtime::Loader& runtime); - ~ServerContextImpl() { parent_.releaseServerContext(this, listener_name_, server_names_); } private: - ssl_select_cert_result_t processClientHello(const SSL_CLIENT_HELLO* client_hello); - void updateConnectionContext(SSL* ssl); - int alpnSelectCallback(const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen); int sessionTicketProcess(SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, HMAC_CTX* hmac_ctx, int encrypt); - const std::string listener_name_; - const std::vector server_names_; - const bool skip_context_update_; Runtime::Loader& runtime_; std::vector parsed_alt_alpn_protocols_; const std::vector session_ticket_keys_; diff --git a/source/common/ssl/context_manager_impl.cc b/source/common/ssl/context_manager_impl.cc index bbf84c1a36b47..1c54c2f018656 100644 --- a/source/common/ssl/context_manager_impl.cc +++ b/source/common/ssl/context_manager_impl.cc @@ -4,7 +4,6 @@ #include #include "common/common/assert.h" -#include "common/common/empty_string.h" #include "common/ssl/context_impl.h" namespace Envoy { @@ -12,7 +11,7 @@ namespace Ssl { ContextManagerImpl::~ContextManagerImpl() { ASSERT(contexts_.empty()); } -void ContextManagerImpl::releaseClientContext(ClientContext* context) { +void ContextManagerImpl::releaseContext(Context* context) { std::unique_lock lock(contexts_lock_); // context may not be found, in the case that a subclass of Context throws @@ -21,41 +20,6 @@ void ContextManagerImpl::releaseClientContext(ClientContext* context) { contexts_.remove(context); } -void ContextManagerImpl::releaseServerContext(ServerContext* context, - const std::string& listener_name, - const std::vector& server_names) { - std::unique_lock lock(contexts_lock_); - - // Remove mappings. - auto& listener_map_exact = map_exact_[listener_name]; - if (server_names.empty()) { - const auto ctx = listener_map_exact.find(EMPTY_STRING); - if (ctx != listener_map_exact.end() && ctx->second == context) { - listener_map_exact.erase(ctx); - } - } else { - auto& listener_map_wildcard = map_wildcard_[listener_name]; - for (const auto& name : server_names) { - if (isWildcardServerName(name)) { - const auto ctx = listener_map_wildcard.find(name); - if (ctx != listener_map_wildcard.end() && ctx->second == context) { - listener_map_wildcard.erase(ctx); - } - } else { - const auto ctx = listener_map_exact.find(name); - if (ctx != listener_map_exact.end() && ctx->second == context) { - listener_map_exact.erase(ctx); - } - } - } - } - - // context may not be found, in the case that a subclass of Context throws - // in it's constructor. In that case the context did not get added, but - // the destructor of Context will run and call releaseContext(). - contexts_.remove(context); -} - ClientContextPtr ContextManagerImpl::createSslClientContext(Stats::Scope& scope, const ClientContextConfig& config) { ClientContextPtr context(new ClientContextImpl(*this, scope, config)); @@ -64,77 +28,15 @@ ClientContextPtr ContextManagerImpl::createSslClientContext(Stats::Scope& scope, return context; } -bool ContextManagerImpl::isWildcardServerName(const std::string& name) { - return name.size() > 2 && name[0] == '*' && name[1] == '.'; -} - -ServerContextPtr ContextManagerImpl::createSslServerContext( - const std::string& listener_name, const std::vector& server_names, - Stats::Scope& scope, const ServerContextConfig& config, bool skip_context_update) { - ServerContextPtr context(new ServerContextImpl(*this, listener_name, server_names, scope, config, - skip_context_update, runtime_)); +ServerContextPtr +ContextManagerImpl::createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config, + const std::vector& server_names) { + ServerContextPtr context(new ServerContextImpl(*this, scope, config, server_names, runtime_)); std::unique_lock lock(contexts_lock_); contexts_.emplace_back(context.get()); - - // Save mappings. - if (server_names.empty()) { - map_exact_[listener_name][EMPTY_STRING] = context.get(); - } else { - for (const auto& name : server_names) { - if (isWildcardServerName(name)) { - map_wildcard_[listener_name][name] = context.get(); - } else { - map_exact_[listener_name][name] = context.get(); - } - } - } - return context; } -ServerContext* ContextManagerImpl::findSslServerContext(const std::string& listener_name, - const std::string& server_name) const { - // Find Ssl::ServerContext to use. The algorithm for "www.example.com" is as follows: - // 1. Try exact match on domain, i.e. "www.example.com" - // 2. Try exact match on wildcard, i.e. "*.example.com" - // 3. Try "no SNI" match, i.e. "" - // 4. Return no context and reject connection. - - // TODO(PiotrSikora): make this lockless. - std::shared_lock lock(contexts_lock_); - - // TODO(PiotrSikora): refactor and combine code with RouteMatcher::findVirtualHost(). - const auto listener_map_exact = map_exact_.find(listener_name); - if (listener_map_exact != map_exact_.end()) { - const auto ctx = listener_map_exact->second.find(server_name); - if (ctx != listener_map_exact->second.end()) { - return ctx->second; - } - } - - // Try to construct and match wildcard domain. - const size_t pos = server_name.find('.'); - if (pos > 0 && pos < server_name.size() - 1) { - const std::string wildcard = '*' + server_name.substr(pos); - const auto listener_map_wildcard = map_wildcard_.find(listener_name); - if (listener_map_wildcard != map_wildcard_.end()) { - const auto ctx = listener_map_wildcard->second.find(wildcard); - if (ctx != listener_map_wildcard->second.end()) { - return ctx->second; - } - } - } - - if (listener_map_exact != map_exact_.end()) { - const auto ctx = listener_map_exact->second.find(EMPTY_STRING); - if (ctx != listener_map_exact->second.end()) { - return ctx->second; - } - } - - return nullptr; -} - size_t ContextManagerImpl::daysUntilFirstCertExpires() const { std::shared_lock lock(contexts_lock_); size_t ret = std::numeric_limits::max(); diff --git a/source/common/ssl/context_manager_impl.h b/source/common/ssl/context_manager_impl.h index 85e47a9ccd7c6..bd31db4f22008 100644 --- a/source/common/ssl/context_manager_impl.h +++ b/source/common/ssl/context_manager_impl.h @@ -3,7 +3,6 @@ #include #include #include -#include #include "envoy/runtime/runtime.h" #include "envoy/ssl/context_manager.h" @@ -28,31 +27,21 @@ class ContextManagerImpl final : public ContextManager { * admin purposes. When a caller frees a context it will tell us to release it also from the list * of contexts. */ - void releaseClientContext(ClientContext* context); - void releaseServerContext(ServerContext* context, const std::string& listener_name, - const std::vector& server_names); + void releaseContext(Context* context); // Ssl::ContextManager Ssl::ClientContextPtr createSslClientContext(Stats::Scope& scope, const ClientContextConfig& config) override; - Ssl::ServerContextPtr createSslServerContext(const std::string& listener_name, - const std::vector& server_names, - Stats::Scope& scope, - const ServerContextConfig& config, - bool skip_context_update) override; - Ssl::ServerContext* findSslServerContext(const std::string& listener_name, - const std::string& server_name) const override; + Ssl::ServerContextPtr + createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config, + const std::vector& server_names) override; size_t daysUntilFirstCertExpires() const override; void iterateContexts(std::function callback) override; private: - static bool isWildcardServerName(const std::string& name); - Runtime::Loader& runtime_; std::list contexts_; mutable std::shared_timed_mutex contexts_lock_; - std::unordered_map> map_exact_; - std::unordered_map> map_wildcard_; }; } // namespace Ssl diff --git a/source/common/ssl/ssl_socket.cc b/source/common/ssl/ssl_socket.cc index e0d2fec272009..7fc08421c1329 100644 --- a/source/common/ssl/ssl_socket.cc +++ b/source/common/ssl/ssl_socket.cc @@ -385,13 +385,10 @@ Network::TransportSocketPtr ClientSslSocketFactory::createTransportSocket() cons bool ClientSslSocketFactory::implementsSecureTransport() const { return true; } ServerSslSocketFactory::ServerSslSocketFactory(const ServerContextConfig& config, - const std::string& listener_name, - const std::vector& server_names, - bool skip_context_update, Ssl::ContextManager& manager, - Stats::Scope& stats_scope) - : ssl_ctx_(manager.createSslServerContext(listener_name, server_names, stats_scope, config, - skip_context_update)) {} + Stats::Scope& stats_scope, + const std::vector& server_names) + : ssl_ctx_(manager.createSslServerContext(stats_scope, config, server_names)) {} Network::TransportSocketPtr ServerSslSocketFactory::createTransportSocket() const { return std::make_unique(*ssl_ctx_, Ssl::InitialState::Server); diff --git a/source/common/ssl/ssl_socket.h b/source/common/ssl/ssl_socket.h index c87189e3300e1..d5f39a026b10c 100644 --- a/source/common/ssl/ssl_socket.h +++ b/source/common/ssl/ssl_socket.h @@ -3,6 +3,7 @@ #include #include +#include "envoy/network/connection.h" #include "envoy/network/transport_socket.h" #include "common/common/logger.h" @@ -76,9 +77,8 @@ class ClientSslSocketFactory : public Network::TransportSocketFactory { class ServerSslSocketFactory : public Network::TransportSocketFactory { public: - ServerSslSocketFactory(const ServerContextConfig& config, const std::string& listener_name, - const std::vector& server_names, bool skip_context_update, - Ssl::ContextManager& manager, Stats::Scope& stats_scope); + ServerSslSocketFactory(const ServerContextConfig& config, Ssl::ContextManager& manager, + Stats::Scope& stats_scope, const std::vector& server_names); Network::TransportSocketPtr createTransportSocket() const override; bool implementsSecureTransport() const override; diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 6922736f29f21..33abddb015535 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -278,7 +278,7 @@ ClusterInfoImpl::ClusterInfoImpl(const envoy::api::v2::Cluster& config, auto transport_socket = config.transport_socket(); if (!config.has_transport_socket()) { if (config.has_tls_context()) { - transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().SSL); + transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().TLS); MessageUtil::jsonConvert(config.tls_context(), *transport_socket.mutable_config()); } else { transport_socket.set_name( diff --git a/source/extensions/filters/listener/tls_inspector/tls_inspector.cc b/source/extensions/filters/listener/tls_inspector/tls_inspector.cc index d5bceb8fb29b9..7475e80ec5a62 100644 --- a/source/extensions/filters/listener/tls_inspector/tls_inspector.cc +++ b/source/extensions/filters/listener/tls_inspector/tls_inspector.cc @@ -173,7 +173,7 @@ void Filter::parseClientHello(const void* data, size_t len) { case SSL_ERROR_SSL: if (clienthello_success_) { config_->stats().tls_found_.inc(); - cb_->socket().setDetectedTransportProtocol(TransportSockets::TransportSocketNames::get().SSL); + cb_->socket().setDetectedTransportProtocol(TransportSockets::TransportSocketNames::get().TLS); } else { config_->stats().tls_not_found_.inc(); } diff --git a/source/extensions/transport_sockets/capture/config.cc b/source/extensions/transport_sockets/capture/config.cc index 9ed2eb759c8d9..d98fbe669d9f1 100644 --- a/source/extensions/transport_sockets/capture/config.cc +++ b/source/extensions/transport_sockets/capture/config.cc @@ -33,9 +33,8 @@ Network::TransportSocketFactoryPtr UpstreamCaptureSocketConfigFactory::createTra Network::TransportSocketFactoryPtr DownstreamCaptureSocketConfigFactory::createTransportSocketFactory( - const std::string& name, const std::vector& server_names, - bool skip_ssl_context_update, const Protobuf::Message& message, - Server::Configuration::TransportSocketFactoryContext& context) { + const Protobuf::Message& message, Server::Configuration::TransportSocketFactoryContext& context, + const std::vector& server_names) { const auto& outer_config = MessageUtil::downcastAndValidate< const envoy::config::transport_socket::capture::v2alpha::Capture&>(message); auto& inner_config_factory = Config::Utility::getAndCheckFactory< @@ -44,7 +43,7 @@ DownstreamCaptureSocketConfigFactory::createTransportSocketFactory( ProtobufTypes::MessagePtr inner_factory_config = Config::Utility::translateToFactoryConfig( outer_config.transport_socket(), inner_config_factory); auto inner_transport_factory = inner_config_factory.createTransportSocketFactory( - name, server_names, skip_ssl_context_update, *inner_factory_config, context); + *inner_factory_config, context, server_names); return std::make_unique(outer_config.file_sink().path_prefix(), outer_config.file_sink().format(), std::move(inner_transport_factory)); diff --git a/source/extensions/transport_sockets/capture/config.h b/source/extensions/transport_sockets/capture/config.h index f44db3f54e9da..c844430a3f359 100644 --- a/source/extensions/transport_sockets/capture/config.h +++ b/source/extensions/transport_sockets/capture/config.h @@ -34,10 +34,10 @@ class DownstreamCaptureSocketConfigFactory : public Server::Configuration::DownstreamTransportSocketConfigFactory, public CaptureSocketConfigFactory { public: - Network::TransportSocketFactoryPtr createTransportSocketFactory( - const std::string& listener_name, const std::vector& server_names, - bool skip_context_update, const Protobuf::Message& config, - Server::Configuration::TransportSocketFactoryContext& context) override; + Network::TransportSocketFactoryPtr + createTransportSocketFactory(const Protobuf::Message& config, + Server::Configuration::TransportSocketFactoryContext& context, + const std::vector& server_names) override; }; } // namespace Capture diff --git a/source/extensions/transport_sockets/raw_buffer/config.cc b/source/extensions/transport_sockets/raw_buffer/config.cc index c32364603781f..45d0c32768631 100644 --- a/source/extensions/transport_sockets/raw_buffer/config.cc +++ b/source/extensions/transport_sockets/raw_buffer/config.cc @@ -15,8 +15,8 @@ Network::TransportSocketFactoryPtr UpstreamRawBufferSocketFactory::createTranspo } Network::TransportSocketFactoryPtr DownstreamRawBufferSocketFactory::createTransportSocketFactory( - const std::string&, const std::vector&, bool, const Protobuf::Message&, - Server::Configuration::TransportSocketFactoryContext&) { + const Protobuf::Message&, Server::Configuration::TransportSocketFactoryContext&, + const std::vector&) { return std::make_unique(); } diff --git a/source/extensions/transport_sockets/raw_buffer/config.h b/source/extensions/transport_sockets/raw_buffer/config.h index bd548c479b132..20f5bc06f9f20 100644 --- a/source/extensions/transport_sockets/raw_buffer/config.h +++ b/source/extensions/transport_sockets/raw_buffer/config.h @@ -33,10 +33,10 @@ class DownstreamRawBufferSocketFactory : public Server::Configuration::DownstreamTransportSocketConfigFactory, public RawBufferSocketFactory { public: - Network::TransportSocketFactoryPtr createTransportSocketFactory( - const std::string& listener_name, const std::vector& server_names, - bool skip_context_update, const Protobuf::Message& config, - Server::Configuration::TransportSocketFactoryContext& context) override; + Network::TransportSocketFactoryPtr + createTransportSocketFactory(const Protobuf::Message& config, + Server::Configuration::TransportSocketFactoryContext& context, + const std::vector& server_names) override; }; } // namespace RawBuffer diff --git a/source/extensions/transport_sockets/ssl/config.cc b/source/extensions/transport_sockets/ssl/config.cc index ecc7a076984c3..354f752494469 100644 --- a/source/extensions/transport_sockets/ssl/config.cc +++ b/source/extensions/transport_sockets/ssl/config.cc @@ -32,15 +32,13 @@ static Registry::RegisterFactory& server_names, - bool skip_context_update, const Protobuf::Message& message, - Server::Configuration::TransportSocketFactoryContext& context) { + const Protobuf::Message& message, Server::Configuration::TransportSocketFactoryContext& context, + const std::vector& server_names) { return std::make_unique( Ssl::ServerContextConfigImpl( MessageUtil::downcastAndValidate( message)), - listener_name, server_names, skip_context_update, context.sslContextManager(), - context.statsScope()); + context.sslContextManager(), context.statsScope(), server_names); } ProtobufTypes::MessagePtr DownstreamSslSocketFactory::createEmptyConfigProto() { diff --git a/source/extensions/transport_sockets/ssl/config.h b/source/extensions/transport_sockets/ssl/config.h index f257fd44f0256..1b5a84dea64e4 100644 --- a/source/extensions/transport_sockets/ssl/config.h +++ b/source/extensions/transport_sockets/ssl/config.h @@ -10,13 +10,13 @@ namespace TransportSockets { namespace SslTransport { /** - * Config registration for the SSL transport socket factory. + * Config registration for the BoringSSL transport socket factory. * @see TransportSocketConfigFactory. */ class SslSocketConfigFactory : public virtual Server::Configuration::TransportSocketConfigFactory { public: virtual ~SslSocketConfigFactory() {} - std::string name() const override { return TransportSocketNames::get().SSL; } + std::string name() const override { return TransportSocketNames::get().TLS; } }; class UpstreamSslSocketFactory : public Server::Configuration::UpstreamTransportSocketConfigFactory, @@ -32,10 +32,10 @@ class DownstreamSslSocketFactory : public Server::Configuration::DownstreamTransportSocketConfigFactory, public SslSocketConfigFactory { public: - Network::TransportSocketFactoryPtr createTransportSocketFactory( - const std::string& listener_name, const std::vector& server_names, - bool skip_context_update, const Protobuf::Message& config, - Server::Configuration::TransportSocketFactoryContext& context) override; + Network::TransportSocketFactoryPtr + createTransportSocketFactory(const Protobuf::Message& config, + Server::Configuration::TransportSocketFactoryContext& context, + const std::vector& server_names) override; ProtobufTypes::MessagePtr createEmptyConfigProto() override; }; diff --git a/source/extensions/transport_sockets/well_known_names.h b/source/extensions/transport_sockets/well_known_names.h index bac2b8ead9a32..2fd92d7cea9d0 100644 --- a/source/extensions/transport_sockets/well_known_names.h +++ b/source/extensions/transport_sockets/well_known_names.h @@ -14,7 +14,7 @@ class TransportSocketNameValues { public: const std::string CAPTURE = "envoy.transport_sockets.capture"; const std::string RAW_BUFFER = "raw_buffer"; - const std::string SSL = "ssl"; + const std::string TLS = "tls"; }; typedef ConstSingleton TransportSocketNames; diff --git a/source/server/BUILD b/source/server/BUILD index 0cbda89cbb9e1..f6f66b503f2ce 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -203,6 +203,7 @@ envoy_cc_library( "//include/envoy/server:transport_socket_config_interface", "//include/envoy/server:worker_interface", "//source/common/api:os_sys_calls_lib", + "//source/common/common:empty_string", "//source/common/config:utility_lib", "//source/common/network:listen_socket_lib", "//source/common/network:resolver_lib", @@ -211,6 +212,7 @@ envoy_cc_library( "//source/common/protobuf:utility_lib", "//source/common/ssl:context_config_lib", "//source/extensions/filters/listener:well_known_names", + "//source/extensions/filters/network:well_known_names", "//source/extensions/transport_sockets:well_known_names", "@envoy_api//envoy/admin/v2alpha:config_dump_cc", "@envoy_api//envoy/api/v2:lds_cc", diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index 851ca337ad749..854180cd4914d 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -185,30 +185,42 @@ void ConnectionHandlerImpl::ActiveListener::onAccept( } void ConnectionHandlerImpl::ActiveListener::newConnection(Network::ConnectionSocketPtr&& socket) { - Network::ConnectionPtr new_connection = parent_.dispatcher_.createServerConnection( - std::move(socket), config_.transportSocketFactory().createTransportSocket()); + // Find matching filter chain. + const auto filter_chain = config_.filterChainManager().findFilterChain(*socket); + if (filter_chain == nullptr) { + ENVOY_LOG_TO_LOGGER(parent_.logger_, debug, + "closing connection: no matching filter chain found"); + stats_.no_filter_chain_match_.inc(); + socket->close(); + return; + } + + auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(); + Network::ConnectionPtr new_connection = + parent_.dispatcher_.createServerConnection(std::move(socket), std::move(transport_socket)); new_connection->setBufferLimits(config_.perConnectionBufferLimitBytes()); + + const bool empty_filter_chain = !config_.filterChainFactory().createNetworkFilterChain( + *new_connection, filter_chain->networkFilterFactories()); + if (empty_filter_chain) { + ENVOY_CONN_LOG_TO_LOGGER(parent_.logger_, debug, "closing connection: no filters", + *new_connection); + new_connection->close(Network::ConnectionCloseType::NoFlush); + return; + } + onNewConnection(std::move(new_connection)); } void ConnectionHandlerImpl::ActiveListener::onNewConnection( Network::ConnectionPtr&& new_connection) { ENVOY_CONN_LOG_TO_LOGGER(parent_.logger_, debug, "new connection", *new_connection); - bool empty_filter_chain = !config_.filterChainFactory().createNetworkFilterChain(*new_connection); // If the connection is already closed, we can just let this connection immediately die. if (new_connection->state() != Network::Connection::State::Closed) { - // Close the connection if the filter chain is empty to avoid leaving open connections - // with nothing to do. - if (empty_filter_chain) { - ENVOY_CONN_LOG_TO_LOGGER(parent_.logger_, debug, "closing connection: no filters", - *new_connection); - new_connection->close(Network::ConnectionCloseType::NoFlush); - } else { - ActiveConnectionPtr active_connection(new ActiveConnection(*this, std::move(new_connection))); - active_connection->moveIntoList(std::move(active_connection), connections_); - parent_.num_connections_++; - } + ActiveConnectionPtr active_connection(new ActiveConnection(*this, std::move(new_connection))); + active_connection->moveIntoList(std::move(active_connection), connections_); + parent_.num_connections_++; } } diff --git a/source/server/connection_handler_impl.h b/source/server/connection_handler_impl.h index 380507f1e8a12..5d2a386022a1b 100644 --- a/source/server/connection_handler_impl.h +++ b/source/server/connection_handler_impl.h @@ -28,7 +28,8 @@ namespace Server { COUNTER (downstream_cx_total) \ COUNTER (downstream_cx_destroy) \ GAUGE (downstream_cx_active) \ - HISTOGRAM(downstream_cx_length_ms) + HISTOGRAM(downstream_cx_length_ms) \ + COUNTER (no_filter_chain_match) // clang-format on /** diff --git a/source/server/http/BUILD b/source/server/http/BUILD index 21efd98ef533f..048bdf7e2f6ae 100644 --- a/source/server/http/BUILD +++ b/source/server/http/BUILD @@ -16,6 +16,7 @@ envoy_cc_library( ":config_tracker_lib", "//include/envoy/filesystem:filesystem_interface", "//include/envoy/http:filter_interface", + "//include/envoy/network:filter_interface", "//include/envoy/network:listen_socket_interface", "//include/envoy/runtime:runtime_interface", "//include/envoy/server:admin_interface", diff --git a/source/server/http/admin.cc b/source/server/http/admin.cc index 11751af27a499..af74b58ab4da1 100644 --- a/source/server/http/admin.cc +++ b/source/server/http/admin.cc @@ -804,7 +804,8 @@ AdminImpl::AdminImpl(const std::string& access_log_path, const std::string& prof {"/runtime_modify", "modify runtime values", MAKE_ADMIN_HANDLER(handlerRuntimeModify), false, true}}, // TODO(jsedgwick) add /runtime_reset endpoint that removes all admin-set values - listener_(*this, std::move(listener_scope)) { + listener_(*this, std::move(listener_scope)), + admin_filter_chain_(std::make_shared()) { if (!address_out_path.empty()) { std::ofstream address_out_file(address_out_path); @@ -830,7 +831,8 @@ Http::ServerConnectionPtr AdminImpl::createCodec(Network::Connection& connection new Http::Http1::ServerConnectionImpl(connection, callbacks, Http::Http1Settings())}; } -bool AdminImpl::createNetworkFilterChain(Network::Connection& connection) { +bool AdminImpl::createNetworkFilterChain(Network::Connection& connection, + const std::vector&) { connection.addReadFilter(Network::ReadFilterSharedPtr{new Http::ConnectionManagerImpl( *this, server_.drainManager(), server_.random(), server_.httpTracer(), server_.runtime(), server_.localInfo(), server_.clusterManager())}); diff --git a/source/server/http/admin.h b/source/server/http/admin.h index 9be6f4568f748..bff17579221e4 100644 --- a/source/server/http/admin.h +++ b/source/server/http/admin.h @@ -8,6 +8,7 @@ #include #include "envoy/http/filter.h" +#include "envoy/network/filter.h" #include "envoy/network/listen_socket.h" #include "envoy/runtime/runtime.h" #include "envoy/server/admin.h" @@ -36,6 +37,7 @@ namespace Server { * Implementation of Server::Admin. */ class AdminImpl : public Admin, + public Network::FilterChainManager, public Network::FilterChainFactory, public Http::FilterChainFactory, public Http::ConnectionManagerConfig, @@ -59,8 +61,15 @@ class AdminImpl : public Admin, bool removeHandler(const std::string& prefix) override; ConfigTracker& getConfigTracker() override; + // Network::FilterChainManager + const Network::FilterChain* findFilterChain(const Network::ConnectionSocket&) const override { + return admin_filter_chain_.get(); + } + // Network::FilterChainFactory - bool createNetworkFilterChain(Network::Connection& connection) override; + bool + createNetworkFilterChain(Network::Connection& connection, + const std::vector& filter_factories) override; bool createListenerFilterChain(Network::ListenerFilterManager&) override { return true; } // Http::FilterChainFactory @@ -191,11 +200,9 @@ class AdminImpl : public Admin, stats_(Http::ConnectionManagerImpl::generateListenerStats("http.admin.", *scope_)) {} // Network::ListenerConfig + Network::FilterChainManager& filterChainManager() override { return parent_; } Network::FilterChainFactory& filterChainFactory() override { return parent_; } Network::Socket& socket() override { return parent_.mutable_socket(); } - Network::TransportSocketFactory& transportSocketFactory() override { - return parent_.transport_socket_factory_; - } bool bindToPort() override { return true; } bool handOffRestoredDestinationConnections() const override { return false; } uint32_t perConnectionBufferLimitBytes() override { return 0; } @@ -209,11 +216,28 @@ class AdminImpl : public Admin, Http::ConnectionManagerListenerStats stats_; }; + class AdminFilterChain : public Network::FilterChain { + public: + AdminFilterChain() {} + + // Network::FilterChain + const Network::TransportSocketFactory& transportSocketFactory() const override { + return transport_socket_factory_; + } + + const std::vector& networkFilterFactories() const override { + return empty_network_filter_factory_; + } + + private: + const Network::RawBufferSocketFactory transport_socket_factory_; + const std::vector empty_network_filter_factory_; + }; + Server::Instance& server_; std::list access_logs_; const std::string profile_path_; Network::SocketPtr socket_; - Network::RawBufferSocketFactory transport_socket_factory_; Http::ConnectionManagerStats stats_; // Note: this is here to essentially blackhole the tracing stats since they aren't used in the // Admin case. @@ -228,6 +252,7 @@ class AdminImpl : public Admin, AdminListener listener_; Http::Http1Settings http1_settings_; ConfigTrackerImpl config_tracker_; + const Network::FilterChainSharedPtr admin_filter_chain_; }; /** diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index ff0eafc903db9..83d4a7a53c0aa 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -6,6 +6,7 @@ #include "common/api/os_sys_calls_impl.h" #include "common/common/assert.h" +#include "common/common/empty_string.h" #include "common/common/fmt.h" #include "common/config/utility.h" #include "common/network/listen_socket_impl.h" @@ -18,8 +19,11 @@ #include "server/drain_manager_impl.h" #include "extensions/filters/listener/well_known_names.h" +#include "extensions/filters/network/well_known_names.h" #include "extensions/transport_sockets/well_known_names.h" +#include "absl/strings/match.h" + namespace Envoy { namespace Server { @@ -126,10 +130,6 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st workers_started_(workers_started), hash_(hash), local_drain_manager_(parent.factory_.createDrainManager(config.drain_type())), config_(config), version_info_(version_info) { - // TODO(htuch): Support multiple filter chains #1280, add constraint to ensure we have at least on - // filter chain #1308. - ASSERT(config.filter_chains().size() >= 1); - if (config.has_transparent()) { addListenSocketOptions(Network::SocketOptionFactory::buildIpTransparentOptions()); } @@ -165,27 +165,21 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st factory.createFilterFactoryFromProto(Envoy::ProtobufWkt::Empty(), *this)); } - // Skip lookup and update of the SSL Context if there is only one filter chain - // and it doesn't enforce any SNI restrictions. - const bool skip_context_update = - (config.filter_chains().size() == 1 && - config.filter_chains()[0].filter_chain_match().sni_domains().empty()); + bool need_tls_inspector = false; + std::unordered_set + filter_chains; - absl::optional filters_hash; - uint32_t has_tls = 0; - uint32_t has_stk = 0; for (const auto& filter_chain : config.filter_chains()) { - std::vector sni_domains(filter_chain.filter_chain_match().sni_domains().begin(), - filter_chain.filter_chain_match().sni_domains().end()); - if (!filters_hash) { - filters_hash = RepeatedPtrUtil::hash(filter_chain.filters()); - filter_factories_ = - parent_.factory_.createNetworkFilterFactoryList(filter_chain.filters(), *this); - } else if (filters_hash.value() != RepeatedPtrUtil::hash(filter_chain.filters())) { - throw EnvoyException(fmt::format("error adding listener '{}': use of different filter chains " - "is currently not supported", + const auto& filter_chain_match = filter_chain.filter_chain_match(); + if (filter_chains.find(filter_chain_match) != filter_chains.end()) { + throw EnvoyException(fmt::format("error adding listener '{}': multiple filter chains with " + "the same matching rules are defined", address_->asString())); } + filter_chains.insert(filter_chain_match); + + std::vector server_names(filter_chain_match.sni_domains().begin(), + filter_chain_match.sni_domains().end()); // If the cluster doesn't have transport socke configured, override with default transport // socket implementation based on tls_context. We copy by value first then override if @@ -193,13 +187,8 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st auto transport_socket = filter_chain.transport_socket(); if (!filter_chain.has_transport_socket()) { if (filter_chain.has_tls_context()) { - transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().SSL); + transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().TLS); MessageUtil::jsonConvert(filter_chain.tls_context(), *transport_socket.mutable_config()); - - has_tls++; - if (filter_chain.tls_context().has_session_ticket_keys()) { - has_stk++; - } } else { transport_socket.set_name( Extensions::TransportSockets::TransportSocketNames::get().RAW_BUFFER); @@ -211,27 +200,28 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st ProtobufTypes::MessagePtr message = Config::Utility::translateToFactoryConfig(transport_socket, config_factory); - // Each transport socket factory owns one SslServerContext, we need to store them all in a - // vector since Ssl::ContextManager doesn't own SslServerContext. While transportSocketFacotry() - // always returns the first element of transport_socket_factories_, other transport socket - // factories are needed when the default Ssl::ServerContext updates SSL context based on - // ClientHello. This behavior is a workaround for initial SNI support before the full SNI based - // filter chain match is implemented. - transport_socket_factories_.emplace_back(config_factory.createTransportSocketFactory( - name_, sni_domains, skip_context_update, *message, *this)); - ASSERT(transport_socket_factories_.back() != nullptr); + addFilterChain(server_names, filter_chain_match.transport_protocol(), + config_factory.createTransportSocketFactory(*message, *this, server_names), + parent_.factory_.createNetworkFilterFactoryList(filter_chain.filters(), *this)); + + need_tls_inspector = filter_chain_match.transport_protocol() == "tls" || + (filter_chain_match.transport_protocol().empty() && !server_names.empty()); } - ASSERT(!transport_socket_factories_.empty()); - // TODO(PiotrSikora): allow filter chains with mixed use of Session Ticket Keys. - // This doesn't work right now, because BoringSSL uses "session context" (initial SSL_CTX that - // accepted connection, before SNI update) for session related stuff, including Session Ticket - // callback, which is going to be called iff it's set on the initial SSL_CTX, even if it's not - // set on the current SSL_CTX that doesn't have any Session Ticket Keys configured. - if (has_stk != 0 && has_stk != has_tls) { - throw EnvoyException(fmt::format("error adding listener '{}': filter chains with mixed use of " - "Session Ticket Keys are currently not supported", - address_->asString())); + // Automatically inject TLS Inspector if it wasn't configured explicitly and it's needed. + if (need_tls_inspector) { + for (const auto& filter : config.listener_filters()) { + if (filter.name() == Extensions::ListenerFilters::ListenerFilterNames::get().TLS_INSPECTOR) { + need_tls_inspector = false; + break; + } + } + if (need_tls_inspector) { + throw EnvoyException( + fmt::format("error adding listener '{}': filter chain match rules require TLS Inspector " + "listener filter, but it isn't configured", + address_->asString())); + } } } @@ -242,11 +232,87 @@ ListenerImpl::~ListenerImpl() { // active. This is done here explicitly by setting a boolean and then clearing the factory // vector for clarity. initialize_canceled_ = true; - filter_factories_.clear(); + filter_chains_.clear(); +} + +bool ListenerImpl::isWildcardServerName(const std::string& name) { + return absl::StartsWith(name, "*."); +} + +void ListenerImpl::addFilterChain(const std::vector& server_names, + const std::string& transport_protocol, + Network::TransportSocketFactoryPtr&& transport_socket_factory, + std::vector filters_factory) { + const auto filter_chain = std::make_shared(std::move(transport_socket_factory), + std::move(filters_factory)); + // Save mappings. + if (server_names.empty()) { + filter_chains_[EMPTY_STRING][transport_protocol] = filter_chain; + } else { + for (const auto& server_name : server_names) { + if (isWildcardServerName(server_name)) { + // Add mapping for the wildcard domain, i.e. ".example.com" for "*.example.com". + filter_chains_[server_name.substr(1)][transport_protocol] = filter_chain; + } else { + filter_chains_[server_name][transport_protocol] = filter_chain; + } + } + } +} + +const Network::FilterChain* +ListenerImpl::findFilterChain(const Network::ConnectionSocket& socket) const { + const std::string server_name(socket.requestedServerName()); + + // Match on exact server name, i.e. "www.example.com" for "www.example.com". + const auto server_name_exact_match = filter_chains_.find(server_name); + if (server_name_exact_match != filter_chains_.end()) { + return findFilterChainForServerName(server_name_exact_match->second, socket); + } + + // Match on the wildcard domain, i.e. ".example.com" for "www.example.com". + const size_t pos = server_name.find('.'); + if (pos > 0 && pos < server_name.size() - 1) { + const std::string wildcard = server_name.substr(pos); + const auto server_name_wildcard_match = filter_chains_.find(wildcard); + if (server_name_wildcard_match != filter_chains_.end()) { + return findFilterChainForServerName(server_name_wildcard_match->second, socket); + } + } + + // Match on a filter chain without server name requirements. + const auto server_name_catchall_match = filter_chains_.find(EMPTY_STRING); + if (server_name_catchall_match != filter_chains_.end()) { + return findFilterChainForServerName(server_name_catchall_match->second, socket); + } + + return nullptr; +} + +const Network::FilterChain* ListenerImpl::findFilterChainForServerName( + const std::unordered_map& server_name_match, + const Network::ConnectionSocket& socket) const { + const std::string transport_protocol(socket.detectedTransportProtocol()); + + // Match on exact transport protocol, e.g. "tls". + const auto transport_protocol_match = server_name_match.find(transport_protocol); + if (transport_protocol_match != server_name_match.end()) { + return transport_protocol_match->second.get(); + } + + // Match on a filter chain without transport protocol requirements. + const auto any_protocol_match = server_name_match.find(EMPTY_STRING); + if (any_protocol_match != server_name_match.end()) { + return any_protocol_match->second.get(); + } + + return nullptr; } -bool ListenerImpl::createNetworkFilterChain(Network::Connection& connection) { - return Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories_); +bool ListenerImpl::createNetworkFilterChain( + Network::Connection& connection, + const std::vector& filter_factories) { + return Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); } bool ListenerImpl::createListenerFilterChain(Network::ListenerFilterManager& manager) { diff --git a/source/server/listener_manager_impl.h b/source/server/listener_manager_impl.h index 92a038399a17a..517fbe9f24bb2 100644 --- a/source/server/listener_manager_impl.h +++ b/source/server/listener_manager_impl.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/api/v2/listener/listener.pb.h" +#include "envoy/network/filter.h" #include "envoy/server/filter_config.h" #include "envoy/server/instance.h" #include "envoy/server/listener_manager.h" @@ -182,6 +183,7 @@ class ListenerManagerImpl : public ListenerManager, Logger::Loggable { @@ -230,15 +232,13 @@ class ListenerImpl : public Network::ListenerConfig, const std::string& versionInfo() { return version_info_; } // Network::ListenerConfig + Network::FilterChainManager& filterChainManager() override { return *this; } Network::FilterChainFactory& filterChainFactory() override { return *this; } Network::Socket& socket() override { return *socket_; } bool bindToPort() override { return bind_to_port_; } bool handOffRestoredDestinationConnections() const override { return hand_off_restored_destination_connections_; } - Network::TransportSocketFactory& transportSocketFactory() override { - return *transport_socket_factories_[0]; - } uint32_t perConnectionBufferLimitBytes() override { return per_connection_buffer_limit_bytes_; } Stats::Scope& listenerScope() override { return *listener_scope_; } uint64_t listenerTag() const override { return listener_tag_; } @@ -286,8 +286,13 @@ class ListenerImpl : public Network::ListenerConfig, // Network::DrainDecision bool drainClose() const override; + // Network::FilterChainManager + const Network::FilterChain* + findFilterChain(const Network::ConnectionSocket& socket) const override; + // Network::FilterChainFactory - bool createNetworkFilterChain(Network::Connection& connection) override; + bool createNetworkFilterChain(Network::Connection& connection, + const std::vector& factories) override; bool createListenerFilterChain(Network::ListenerFilterManager& manager) override; // Configuration::TransportSocketFactoryContext @@ -295,13 +300,29 @@ class ListenerImpl : public Network::ListenerConfig, Stats::Scope& statsScope() const override { return *listener_scope_; } private: + void addFilterChain(const std::vector& server_names, + const std::string& transport_protocol, + Network::TransportSocketFactoryPtr&& transport_socket_factory, + std::vector filters_factory); + const Network::FilterChain* findFilterChainForServerName( + const std::unordered_map& server_name_match, + const Network::ConnectionSocket& socket) const; + static bool isWildcardServerName(const std::string& name); + + // Mapping of FilterChain's configured server name and transport protocol, i.e. + // map[server_name][transport_protocol] => FilterChain + // + // For the server_name lookups, both exact server names and wildcard domains are part of the same + // map, in which wildcard domains are prefixed with "." (i.e. ".example.com" for "*.example.com") + // to differentiate between exact and wildcard entries. + std::unordered_map> + filter_chains_; + ListenerManagerImpl& parent_; Network::Address::InstanceConstSharedPtr address_; Network::SocketSharedPtr socket_; Stats::ScopePtr global_scope_; // Stats with global named scope, but needed for LDS cleanup. Stats::ScopePtr listener_scope_; // Stats with listener named scope. - std::vector tls_contexts_; - std::vector transport_socket_factories_; const bool bind_to_port_; const bool hand_off_restored_destination_connections_; const uint32_t per_connection_buffer_limit_bytes_; @@ -312,7 +333,6 @@ class ListenerImpl : public Network::ListenerConfig, const uint64_t hash_; InitManagerImpl dynamic_init_manager_; bool initialize_canceled_{}; - std::vector filter_factories_; std::vector listener_filter_factories_; DrainManagerPtr local_drain_manager_; bool saw_listener_create_failure_{}; @@ -321,5 +341,26 @@ class ListenerImpl : public Network::ListenerConfig, Network::Socket::OptionsSharedPtr listen_socket_options_; }; +class FilterChainImpl : public Network::FilterChain { +public: + FilterChainImpl(Network::TransportSocketFactoryPtr&& transport_socket_factory, + std::vector filters_factory) + : transport_socket_factory_(std::move(transport_socket_factory)), + filters_factory_(std::move(filters_factory)) {} + + // Network::FilterChain + const Network::TransportSocketFactory& transportSocketFactory() const override { + return *transport_socket_factory_; + } + + const std::vector& networkFilterFactories() const override { + return filters_factory_; + } + +private: + const Network::TransportSocketFactoryPtr transport_socket_factory_; + const std::vector filters_factory_; +}; + } // namespace Server } // namespace Envoy diff --git a/test/common/grpc/grpc_client_integration_test.cc b/test/common/grpc/grpc_client_integration_test.cc index bfac4dd7f03e5..07e4c365b936d 100644 --- a/test/common/grpc/grpc_client_integration_test.cc +++ b/test/common/grpc/grpc_client_integration_test.cc @@ -810,9 +810,8 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { Ssl::ServerContextConfigImpl cfg(tls_context); static Stats::Scope* upstream_stats_store = new Stats::IsolatedStoreImpl(); - return std::make_unique(cfg, EMPTY_STRING, - std::vector{}, true, - context_manager_, *upstream_stats_store); + return std::make_unique( + cfg, context_manager_, *upstream_stats_store, std::vector{}); } bool use_client_cert_{}; diff --git a/test/common/ssl/context_impl_test.cc b/test/common/ssl/context_impl_test.cc index 1594aa7467b57..898ab7bbae24a 100644 --- a/test/common/ssl/context_impl_test.cc +++ b/test/common/ssl/context_impl_test.cc @@ -175,7 +175,8 @@ class SslServerContextImplTicketTest : public SslContextImplTest { Runtime::MockLoader runtime; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; - ServerContextPtr server_ctx(manager.createSslServerContext("", {}, store, cfg, true)); + ServerContextPtr server_ctx( + manager.createSslServerContext(store, cfg, std::vector{})); } static void loadConfigV2(envoy::api::v2::auth::DownstreamTlsContext& cfg) { @@ -392,7 +393,7 @@ TEST(ServerContextImplTest, TlsCertificateNonEmpty) { ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; EXPECT_THROW_WITH_MESSAGE(ServerContextPtr server_ctx(manager.createSslServerContext( - "", {}, store, client_context_config, true)), + store, client_context_config, std::vector{})), EnvoyException, "Server TlsCertificates must have a certificate specified"); } diff --git a/test/common/ssl/ssl_socket_test.cc b/test/common/ssl/ssl_socket_test.cc index 32aba00414139..df2e258610642 100644 --- a/test/common/ssl/ssl_socket_test.cc +++ b/test/common/ssl/ssl_socket_test.cc @@ -55,8 +55,8 @@ void testUtil(const std::string& client_ctx_json, const std::string& server_ctx_ Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); ServerContextConfigImpl server_ctx_config(*server_ctx_loader); ContextManagerImpl manager(runtime); - Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, "", {}, true, manager, - stats_store); + Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, manager, stats_store, + std::vector{}); Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(version), nullptr, @@ -146,14 +146,17 @@ const std::string testUtilV2(const envoy::api::v2::Listener& server_proto, ContextManagerImpl manager(runtime); std::string new_session = EMPTY_STRING; + // SNI-based selection logic isn't happening in Ssl::SslSocket anymore. + ASSERT(server_proto.filter_chains().size() == 1); + std::vector server_transport_socket_factories; for (const auto& filter_chain : server_proto.filter_chains()) { if (filter_chain.has_tls_context()) { std::vector sni_domains(filter_chain.filter_chain_match().sni_domains().begin(), filter_chain.filter_chain_match().sni_domains().end()); Ssl::ServerContextConfigImpl server_ctx_config(filter_chain.tls_context()); - server_transport_socket_factories.emplace_back(new Ssl::ServerSslSocketFactory( - server_ctx_config, "test_listener", sni_domains, false, manager, stats_store)); + server_transport_socket_factories.emplace_back( + new Ssl::ServerSslSocketFactory(server_ctx_config, manager, stats_store, sni_domains)); } } ASSERT(server_transport_socket_factories.size() >= 1); @@ -735,8 +738,8 @@ TEST_P(SslSocketTest, FlushCloseDuringHandshake) { Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); ServerContextConfigImpl server_ctx_config(*server_ctx_loader); ContextManagerImpl manager(runtime); - Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, "", {}, true, manager, - stats_store); + Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, manager, stats_store, + std::vector{}); Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, @@ -793,8 +796,8 @@ TEST_P(SslSocketTest, HalfClose) { Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); ServerContextConfigImpl server_ctx_config(*server_ctx_loader); ContextManagerImpl manager(runtime); - Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, "", {}, true, manager, - stats_store); + Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, manager, stats_store, + std::vector{}); Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, @@ -876,8 +879,8 @@ TEST_P(SslSocketTest, ClientAuthMultipleCAs) { Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); ServerContextConfigImpl server_ctx_config(*server_ctx_loader); ContextManagerImpl manager(runtime); - Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, "", {}, true, manager, - stats_store); + Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, manager, stats_store, + std::vector{}); Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, @@ -944,7 +947,9 @@ namespace { // Test connecting with a client to server1, then trying to reuse the session on server2 void testTicketSessionResumption(const std::string& server_ctx_json1, + const std::vector& server_names1, const std::string& server_ctx_json2, + const std::vector& server_names2, const std::string& client_ctx_json, bool expect_reuse, const Network::Address::IpVersion ip_version) { Stats::IsolatedStoreImpl stats_store; @@ -955,10 +960,10 @@ void testTicketSessionResumption(const std::string& server_ctx_json1, Json::ObjectSharedPtr server_ctx_loader2 = TestEnvironment::jsonLoadFromString(server_ctx_json2); ServerContextConfigImpl server_ctx_config1(*server_ctx_loader1); ServerContextConfigImpl server_ctx_config2(*server_ctx_loader2); - Ssl::ServerSslSocketFactory server_ssl_socket_factory1(server_ctx_config1, "server1", {}, false, - manager, stats_store); - Ssl::ServerSslSocketFactory server_ssl_socket_factory2(server_ctx_config2, "server2", {}, false, - manager, stats_store); + Ssl::ServerSslSocketFactory server_ssl_socket_factory1(server_ctx_config1, manager, stats_store, + server_names1); + Ssl::ServerSslSocketFactory server_ssl_socket_factory2(server_ctx_config2, manager, stats_store, + server_names2); Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket1(Network::Test::getCanonicalLoopbackAddress(ip_version), nullptr, @@ -1068,7 +1073,8 @@ TEST_P(SslSocketTest, TicketSessionResumption) { } )EOF"; - testTicketSessionResumption(server_ctx_json, server_ctx_json, client_ctx_json, true, GetParam()); + testTicketSessionResumption(server_ctx_json, {}, server_ctx_json, {}, client_ctx_json, true, + GetParam()); } TEST_P(SslSocketTest, TicketSessionResumptionWithClientCA) { @@ -1088,7 +1094,8 @@ TEST_P(SslSocketTest, TicketSessionResumptionWithClientCA) { } )EOF"; - testTicketSessionResumption(server_ctx_json, server_ctx_json, client_ctx_json, true, GetParam()); + testTicketSessionResumption(server_ctx_json, {}, server_ctx_json, {}, client_ctx_json, true, + GetParam()); } TEST_P(SslSocketTest, TicketSessionResumptionRotateKey) { @@ -1118,7 +1125,7 @@ TEST_P(SslSocketTest, TicketSessionResumptionRotateKey) { } )EOF"; - testTicketSessionResumption(server_ctx_json1, server_ctx_json2, client_ctx_json, true, + testTicketSessionResumption(server_ctx_json1, {}, server_ctx_json2, {}, client_ctx_json, true, GetParam()); } @@ -1148,10 +1155,44 @@ TEST_P(SslSocketTest, TicketSessionResumptionWrongKey) { } )EOF"; - testTicketSessionResumption(server_ctx_json1, server_ctx_json2, client_ctx_json, false, + testTicketSessionResumption(server_ctx_json1, {}, server_ctx_json2, {}, client_ctx_json, false, GetParam()); } +// Sessions cannot be resumed even though the server certificates are the same, +// because of the different SNI requirements. +TEST_P(SslSocketTest, TicketSessionResumptionDifferentServerNames) { + std::string server_ctx_json1 = R"EOF( + { + "cert_chain_file": "{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem", + "private_key_file": "{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem", + "session_ticket_key_paths": [ + "{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a" + ] + } + )EOF"; + + std::vector server_names1 = {"server1.example.com"}; + + std::string server_ctx_json2 = R"EOF( + { + "cert_chain_file": "{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem", + "private_key_file": "{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem", + "session_ticket_key_paths": [ + "{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a" + ] + } + )EOF"; + + std::string client_ctx_json = R"EOF( + { + } + )EOF"; + + testTicketSessionResumption(server_ctx_json1, server_names1, server_ctx_json2, {}, + client_ctx_json, false, GetParam()); +} + // Sessions can be resumed because the server certificates are different but the CN/SANs and // issuer are identical TEST_P(SslSocketTest, TicketSessionResumptionDifferentServerCert) { @@ -1180,7 +1221,7 @@ TEST_P(SslSocketTest, TicketSessionResumptionDifferentServerCert) { } )EOF"; - testTicketSessionResumption(server_ctx_json1, server_ctx_json2, client_ctx_json, true, + testTicketSessionResumption(server_ctx_json1, {}, server_ctx_json2, {}, client_ctx_json, true, GetParam()); } @@ -1212,7 +1253,7 @@ TEST_P(SslSocketTest, TicketSessionResumptionDifferentServerCertIntermediateCA) } )EOF"; - testTicketSessionResumption(server_ctx_json1, server_ctx_json2, client_ctx_json, false, + testTicketSessionResumption(server_ctx_json1, {}, server_ctx_json2, {}, client_ctx_json, false, GetParam()); } @@ -1244,7 +1285,7 @@ TEST_P(SslSocketTest, TicketSessionResumptionDifferentServerCertDifferentSAN) { } )EOF"; - testTicketSessionResumption(server_ctx_json1, server_ctx_json2, client_ctx_json, false, + testTicketSessionResumption(server_ctx_json1, {}, server_ctx_json2, {}, client_ctx_json, false, GetParam()); } @@ -1279,10 +1320,10 @@ TEST_P(SslSocketTest, ClientAuthCrossListenerSessionResumption) { Json::ObjectSharedPtr server2_ctx_loader = TestEnvironment::jsonLoadFromString(server2_ctx_json); ServerContextConfigImpl server2_ctx_config(*server2_ctx_loader); ContextManagerImpl manager(runtime); - Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, "server1", {}, false, - manager, stats_store); - Ssl::ServerSslSocketFactory server2_ssl_socket_factory(server2_ctx_config, "server2", {}, false, - manager, stats_store); + Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, manager, stats_store, + std::vector{}); + Ssl::ServerSslSocketFactory server2_ssl_socket_factory(server2_ctx_config, manager, stats_store, + std::vector{}); Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, @@ -1390,8 +1431,8 @@ TEST_P(SslSocketTest, SslError) { Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); ServerContextConfigImpl server_ctx_config(*server_ctx_loader); ContextManagerImpl manager(runtime); - Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, "", {}, true, manager, - stats_store); + Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, manager, stats_store, + std::vector{}); Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, @@ -1444,42 +1485,42 @@ TEST_P(SslSocketTest, ProtocolVersions) { envoy::api::v2::auth::TlsParameters* server_params = filter_chain->mutable_tls_context()->mutable_common_tls_context()->mutable_tls_params(); - envoy::api::v2::auth::UpstreamTlsContext client_ctx; + envoy::api::v2::auth::UpstreamTlsContext client; envoy::api::v2::auth::TlsParameters* client_params = - client_ctx.mutable_common_tls_context()->mutable_tls_params(); + client.mutable_common_tls_context()->mutable_tls_params(); // Connection using defaults (client & server) succeeds, negotiating TLSv1.2. - testUtilV2(listener, client_ctx, "", true, "TLSv1.2", "", "", "", "ssl.handshake", 2, GetParam()); + // ssl.handshake logged by both: client & server. + testUtilV2(listener, client, "", true, "TLSv1.2", "", "", "", "ssl.handshake", 2, GetParam()); // Connection using TLSv1.0 (client) and defaults (server) succeeds. // ssl.handshake logged by both: client & server. client_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_0); client_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_0); - testUtilV2(listener, client_ctx, "", true, "TLSv1", "", "", "", "ssl.handshake", 2, GetParam()); + testUtilV2(listener, client, "", true, "TLSv1", "", "", "", "ssl.handshake", 2, GetParam()); // Connection using TLSv1.1 (client) and defaults (server) succeeds. // ssl.handshake logged by both: client & server. client_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_1); client_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_1); - testUtilV2(listener, client_ctx, "", true, "TLSv1.1", "", "", "", "ssl.handshake", 2, GetParam()); + testUtilV2(listener, client, "", true, "TLSv1.1", "", "", "", "ssl.handshake", 2, GetParam()); // Connection using TLSv1.2 (client) and defaults (server) succeeds. // ssl.handshake logged by both: client & server. client_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_2); client_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_2); - testUtilV2(listener, client_ctx, "", true, "TLSv1.2", "", "", "", "ssl.handshake", 2, GetParam()); + testUtilV2(listener, client, "", true, "TLSv1.2", "", "", "", "ssl.handshake", 2, GetParam()); // Connection using TLSv1.3 (client) and defaults (server) fails. client_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_3); client_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_3); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.connection_error", 1, - GetParam()); + testUtilV2(listener, client, "", false, "", "", "", "", "ssl.connection_error", 1, GetParam()); // Connection using TLSv1.3 (client) and TLSv1.0-1.3 (server) succeeds. // ssl.handshake logged by both: client & server. server_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_0); server_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_3); - testUtilV2(listener, client_ctx, "", true, "TLSv1.3", "", "", "", "ssl.handshake", 2, GetParam()); + testUtilV2(listener, client, "", true, "TLSv1.3", "", "", "", "ssl.handshake", 2, GetParam()); // Connection using defaults (client) and TLSv1.0 (server) succeeds. // ssl.handshake logged by both: client & server. @@ -1487,509 +1528,154 @@ TEST_P(SslSocketTest, ProtocolVersions) { client_params->clear_tls_maximum_protocol_version(); server_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_0); server_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_0); - testUtilV2(listener, client_ctx, "", true, "TLSv1", "", "", "", "ssl.handshake", 2, GetParam()); + testUtilV2(listener, client, "", true, "TLSv1", "", "", "", "ssl.handshake", 2, GetParam()); // Connection using defaults (client) and TLSv1.1 (server) succeeds. // ssl.handshake logged by both: client & server. server_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_1); server_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_1); - testUtilV2(listener, client_ctx, "", true, "TLSv1.1", "", "", "", "ssl.handshake", 2, GetParam()); + testUtilV2(listener, client, "", true, "TLSv1.1", "", "", "", "ssl.handshake", 2, GetParam()); // Connection using defaults (client) and TLSv1.2 (server) succeeds. // ssl.handshake logged by both: client & server. server_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_2); server_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_2); - testUtilV2(listener, client_ctx, "", true, "TLSv1.2", "", "", "", "ssl.handshake", 2, GetParam()); + testUtilV2(listener, client, "", true, "TLSv1.2", "", "", "", "ssl.handshake", 2, GetParam()); - // Connection using defaults (client) and TLSv1.3 (server) succeeds. + // Connection using defaults (client) and TLSv1.3 (server) fails. server_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_3); server_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_3); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.connection_error", 1, - GetParam()); + testUtilV2(listener, client, "", false, "", "", "", "", "ssl.connection_error", 1, GetParam()); // Connection using TLSv1.0-TLSv1.3 (client) and TLSv1.3 (server) succeeds. // ssl.handshake logged by both: client & server. client_params->set_tls_minimum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_0); client_params->set_tls_maximum_protocol_version(envoy::api::v2::auth::TlsParameters::TLSv1_3); - testUtilV2(listener, client_ctx, "", true, "TLSv1.3", "", "", "", "ssl.handshake", 2, GetParam()); + testUtilV2(listener, client, "", true, "TLSv1.3", "", "", "", "ssl.handshake", 2, GetParam()); } -TEST_P(SslSocketTest, SniCertificate) { +TEST_P(SslSocketTest, ALPN) { envoy::api::v2::Listener listener; - - // san_dns_cert.pem: server1.example.com - envoy::api::v2::listener::FilterChain* filter_chain1 = listener.add_filter_chains(); - filter_chain1->mutable_filter_chain_match()->add_sni_domains("server1.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert1->mutable_certificate_chain()->set_filename( + envoy::api::v2::listener::FilterChain* filter_chain = listener.add_filter_chains(); + envoy::api::v2::auth::TlsCertificate* server_cert = + filter_chain->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); + server_cert->mutable_certificate_chain()->set_filename( TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem")); - server_cert1->mutable_private_key()->set_filename( + server_cert->mutable_private_key()->set_filename( TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem")); + envoy::api::v2::auth::CommonTlsContext* server_ctx = + filter_chain->mutable_tls_context()->mutable_common_tls_context(); - // san_multiple_dns_cert.pem: server2.example.com, *.example.com - envoy::api::v2::listener::FilterChain* filter_chain2 = listener.add_filter_chains(); - filter_chain2->mutable_filter_chain_match()->add_sni_domains("server2.example.com"); - filter_chain2->mutable_filter_chain_match()->add_sni_domains("*.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert2->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem")); - server_cert2->mutable_private_key()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem")); + envoy::api::v2::auth::UpstreamTlsContext client; + envoy::api::v2::auth::CommonTlsContext* client_ctx = client.mutable_common_tls_context(); - envoy::api::v2::auth::UpstreamTlsContext client_ctx; - - // Connection to server1.example.com succeeds, client receives san_dns_cert.pem (exact match). - // ssl.handshake logged by both: client & server. - client_ctx.set_sni("server1.example.com"); - testUtilV2(listener, client_ctx, "", true, "", - "1406294e80c818158697d65d2aaca16748ff132442ab0e2f28bc1109f1d47a2e", "", "", - "ssl.handshake", 2, GetParam()); - - // Connection to www.example.com succeeds, client receives san_multiple_dns_cert.pem - // (wildcard match). - // ssl.handshake logged by both: client & server. - client_ctx.set_sni("www.example.com"); - testUtilV2(listener, client_ctx, "", true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.handshake", 2, GetParam()); - - // Connection without SNI fails, since there is no match. - client_ctx.clear_sni(); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.fail_no_sni_match", 1, - GetParam()); - - // Connection to bar.foo.com fails, since there is no match. - client_ctx.set_sni("bar.foo.com"); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.fail_no_sni_match", 1, - GetParam()); - - // no_san_cert.pem: * (no SNI restrictions) - envoy::api::v2::listener::FilterChain* filter_chain3 = listener.add_filter_chains(); - envoy::api::v2::auth::TlsCertificate* server_cert3 = - filter_chain3->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert3->mutable_certificate_chain()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/no_san_cert.pem")); - server_cert3->mutable_private_key()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/no_san_key.pem")); - - // Connection without SNI succeeds, client receives no_san_cert.pem (exact match on ""). + // Connection using defaults (client & server) succeeds, no ALPN is negotiated. // ssl.handshake logged by both: client & server. - client_ctx.clear_sni(); - testUtilV2(listener, client_ctx, "", true, "", - "4444fbca965d916475f04fb4dd234dd556adb028ceb4300fa8ad6f2983c6aaa3", "", "", - "ssl.handshake", 2, GetParam()); - - // Connection to bar.foo.com succeeds, client receives no_san_cert.pem (exact match on ""). - // ssl.handshake logged by both: client & server. - client_ctx.set_sni("bar.foo.com"); - testUtilV2(listener, client_ctx, "", true, "", - "4444fbca965d916475f04fb4dd234dd556adb028ceb4300fa8ad6f2983c6aaa3", "", "", - "ssl.handshake", 2, GetParam()); -} - -TEST_P(SslSocketTest, SniSessionResumption) { - envoy::api::v2::Listener listener; - - // san_dns_cert.pem: server1.example.com, * - envoy::api::v2::listener::FilterChain* filter_chain1 = listener.add_filter_chains(); - filter_chain1->mutable_filter_chain_match()->add_sni_domains("server1.example.com"); - filter_chain1->mutable_filter_chain_match()->add_sni_domains(""); // Catch-all, no SNI. - envoy::api::v2::auth::TlsCertificate* server_cert1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert1->mutable_certificate_chain()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem")); - server_cert1->mutable_private_key()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem")); - filter_chain1->mutable_tls_context()->mutable_session_ticket_keys()->add_keys()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a")); - - // san_multiple_dns_cert.pem: server2.example.com, *.example.com - envoy::api::v2::listener::FilterChain* filter_chain2 = listener.add_filter_chains(); - filter_chain2->mutable_filter_chain_match()->add_sni_domains("server2.example.com"); - filter_chain2->mutable_filter_chain_match()->add_sni_domains("*.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert2->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem")); - server_cert2->mutable_private_key()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem")); - filter_chain2->mutable_tls_context()->mutable_session_ticket_keys()->add_keys()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a")); - - // san_multiple_dns_cert.pem: protected.example.com (same certificate as #2, but different SNI) - envoy::api::v2::listener::FilterChain* filter_chain3 = listener.add_filter_chains(); - filter_chain3->mutable_filter_chain_match()->add_sni_domains("protected.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert3 = - filter_chain3->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert3->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem")); - server_cert3->mutable_private_key()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem")); - filter_chain3->mutable_tls_context()->mutable_session_ticket_keys()->add_keys()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a")); - - envoy::api::v2::auth::UpstreamTlsContext client_ctx; - - // Connection to www.example.com succeeds, new session established. - client_ctx.set_sni("www.example.com"); - std::string client_session = - testUtilV2(listener, client_ctx, "", true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.session_reused", 0, GetParam()); - - // Connection to www.example.com succeeds, session resumed. - // ssl.session_reused logged by both: client & server. - testUtilV2(listener, client_ctx, client_session, true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.session_reused", 2, GetParam()); - - // Connection without SNI succeeds, but session is NOT resumed on a different filter chain. - client_ctx.clear_sni(); - testUtilV2(listener, client_ctx, client_session, true, "", - "1406294e80c818158697d65d2aaca16748ff132442ab0e2f28bc1109f1d47a2e", "", "", - "ssl.session_reused", 0, GetParam()); - - // Connection to protected.example.com succeeds, but session is NOT resumed on a different - // filter chain, even though it's serving the same certificate. - client_ctx.set_sni("protected.example.com"); - testUtilV2(listener, client_ctx, client_session, true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.session_reused", 0, GetParam()); - - // Connection to server2.example.com succeeds, session resumed on a different domain from - // the same filter chain (respecting SNI restrictions) and serving the same certificate. - // NOTE: Tested last to make sure that the session is still resumable. - // ssl.session_reused logged by both: client & server. - client_ctx.set_sni("server2.example.com"); - testUtilV2(listener, client_ctx, client_session, true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.session_reused", 2, GetParam()); -} - -TEST_P(SslSocketTest, SniClientCertificate) { - envoy::api::v2::Listener listener; + testUtilV2(listener, client, "", true, "", "", "", "", "ssl.handshake", 2, GetParam()); - // san_multiple_dns_cert.pem: *.example.com - envoy::api::v2::listener::FilterChain* filter_chain1 = listener.add_filter_chains(); - filter_chain1->mutable_filter_chain_match()->add_sni_domains("*.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert1->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem")); - server_cert1->mutable_private_key()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem")); - filter_chain1->mutable_tls_context()->mutable_session_ticket_keys()->add_keys()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a")); - - // san_multiple_dns_cert.pem: protected.example.com - // (same certificate as #1, but requires Client Certificate) - envoy::api::v2::listener::FilterChain* filter_chain2 = listener.add_filter_chains(); - filter_chain2->mutable_filter_chain_match()->add_sni_domains("protected.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert2->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem")); - server_cert2->mutable_private_key()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem")); - filter_chain2->mutable_tls_context()->mutable_session_ticket_keys()->add_keys()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a")); - filter_chain2->mutable_tls_context()->mutable_require_client_certificate()->set_value(true); - envoy::api::v2::auth::CertificateValidationContext* validation_ctx2 = - filter_chain2->mutable_tls_context() - ->mutable_common_tls_context() - ->mutable_validation_context(); - validation_ctx2->add_verify_subject_alt_name("spiffe://lyft.com/bogus-team"); - validation_ctx2->mutable_trusted_ca()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/ca_cert.pem")); + // Client connects without ALPN to a server with "test" ALPN, no ALPN is negotiated. + server_ctx->add_alpn_protocols("test"); + testUtilV2(listener, client, "", true, "", "", "", "", "ssl.handshake", 2, GetParam()); + server_ctx->clear_alpn_protocols(); - envoy::api::v2::auth::UpstreamTlsContext client_ctx; + // Client connects with "test" ALPN to a server without ALPN, no ALPN is negotiated. + client_ctx->add_alpn_protocols("test"); + testUtilV2(listener, client, "", true, "", "", "", "", "ssl.handshake", 2, GetParam()); + client_ctx->clear_alpn_protocols(); - // Connection to www.example.com succeeds. + // Client connects with "test" ALPN to a server with "test" ALPN, "test" ALPN is negotiated. // ssl.handshake logged by both: client & server. - client_ctx.set_sni("www.example.com"); - testUtilV2(listener, client_ctx, "", true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.handshake", 2, GetParam()); - - // Connection to protected.example.com fails, since there is no client certificate. - client_ctx.set_sni("protected.example.com"); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.fail_verify_no_cert", 1, - GetParam()); - - // Connection to protected.example.com with a valid client certificate fails, beacuse its SAN - // is not whitelisted. - envoy::api::v2::auth::TlsCertificate* client_cert = - client_ctx.mutable_common_tls_context()->add_tls_certificates(); - client_cert->mutable_certificate_chain()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_uri_cert.pem")); - client_cert->mutable_private_key()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_uri_key.pem")); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.fail_verify_san", 1, GetParam()); + client_ctx->add_alpn_protocols("test"); + server_ctx->add_alpn_protocols("test"); + testUtilV2(listener, client, "", true, "", "", "", "test", "ssl.handshake", 2, GetParam()); + client_ctx->clear_alpn_protocols(); + server_ctx->clear_alpn_protocols(); - // Connection to protected.example.com with a valid client certificate succeeds. + // Client connects with "test" ALPN to a server with "test2" ALPN, "test" ALPN is negotiated. // ssl.handshake logged by both: client & server. - validation_ctx2->add_verify_subject_alt_name("spiffe://lyft.com/test-team"); - testUtilV2(listener, client_ctx, "", true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", - "spiffe://lyft.com/test-team", "", "ssl.handshake", 2, GetParam()); - - // Connection to www.example.com with a valid client certificate succeeds, - // but the client certificate is not requested. - client_ctx.set_sni("www.example.com"); - testUtilV2(listener, client_ctx, "", true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.no_certificate", 1, GetParam()); + client_ctx->add_alpn_protocols("test"); + server_ctx->add_alpn_protocols("test2"); + testUtilV2(listener, client, "", true, "", "", "", "", "ssl.handshake", 2, GetParam()); + client_ctx->clear_alpn_protocols(); + server_ctx->clear_alpn_protocols(); } -TEST_P(SslSocketTest, SniALPN) { +TEST_P(SslSocketTest, CipherSuites) { envoy::api::v2::Listener listener; - - // san_dns_cert.pem: server1.example.com - envoy::api::v2::listener::FilterChain* filter_chain1 = listener.add_filter_chains(); - filter_chain1->mutable_filter_chain_match()->add_sni_domains("server1.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert1->mutable_certificate_chain()->set_filename( + envoy::api::v2::listener::FilterChain* filter_chain = listener.add_filter_chains(); + envoy::api::v2::auth::TlsCertificate* server_cert = + filter_chain->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); + server_cert->mutable_certificate_chain()->set_filename( TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem")); - server_cert1->mutable_private_key()->set_filename( + server_cert->mutable_private_key()->set_filename( TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem")); + envoy::api::v2::auth::TlsParameters* server_params = + filter_chain->mutable_tls_context()->mutable_common_tls_context()->mutable_tls_params(); - // san_multiple_dns_cert.pem: server2.example.com - envoy::api::v2::listener::FilterChain* filter_chain2 = listener.add_filter_chains(); - filter_chain2->mutable_filter_chain_match()->add_sni_domains("server2.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert2->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem")); - server_cert2->mutable_private_key()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem")); - - envoy::api::v2::auth::CommonTlsContext* server_ctx1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context(); - envoy::api::v2::auth::CommonTlsContext* server_ctx2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context(); - envoy::api::v2::auth::UpstreamTlsContext client_ctx; - - // Test ALPN. - server_ctx1->add_alpn_protocols("srv1"); - server_ctx2->add_alpn_protocols("srv2"); - - // Connection to server1.example.com succeeds, negotiated ALPN is "srv1". - // ssl.handshake logged by both: client & server. - client_ctx.set_sni("server1.example.com"); - client_ctx.mutable_common_tls_context()->add_alpn_protocols("srv1"); - client_ctx.mutable_common_tls_context()->add_alpn_protocols("srv2"); - testUtilV2(listener, client_ctx, "", true, "", - "1406294e80c818158697d65d2aaca16748ff132442ab0e2f28bc1109f1d47a2e", "", "srv1", - "ssl.handshake", 2, GetParam()); + envoy::api::v2::auth::UpstreamTlsContext client; + envoy::api::v2::auth::TlsParameters* client_params = + client.mutable_common_tls_context()->mutable_tls_params(); - // Connection to server2.example.com succeeds, negotiated ALPN is "srv2". + // Connection using defaults (client & server) succeeds. // ssl.handshake logged by both: client & server. - client_ctx.set_sni("server2.example.com"); - testUtilV2(listener, client_ctx, "", true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "srv2", - "ssl.handshake", 2, GetParam()); -} - -TEST_P(SslSocketTest, SniCipherSuites) { - envoy::api::v2::Listener listener; - - // san_dns_cert.pem: server1.example.com - envoy::api::v2::listener::FilterChain* filter_chain1 = listener.add_filter_chains(); - filter_chain1->mutable_filter_chain_match()->add_sni_domains("server1.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert1->mutable_certificate_chain()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem")); - server_cert1->mutable_private_key()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem")); + testUtilV2(listener, client, "", true, "", "", "", "", "ssl.handshake", 2, GetParam()); - // san_multiple_dns_cert.pem: server2.example.com - envoy::api::v2::listener::FilterChain* filter_chain2 = listener.add_filter_chains(); - filter_chain2->mutable_filter_chain_match()->add_sni_domains("server2.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert2->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem")); - server_cert2->mutable_private_key()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem")); - - envoy::api::v2::auth::CommonTlsContext* server_ctx1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context(); - envoy::api::v2::auth::CommonTlsContext* server_ctx2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context(); - envoy::api::v2::auth::UpstreamTlsContext client_ctx; - - // Test cipher suites. - server_ctx1->mutable_tls_params()->add_cipher_suites("ECDHE-RSA-CHACHA20-POLY1305"); - server_ctx2->mutable_tls_params()->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256"); - - // Connection to server1.example.com using ECDHE-RSA-CHACHA20-POLY1305 succeeds. - // ssl.handshake logged by both: client & server. - client_ctx.set_sni("server1.example.com"); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->add_cipher_suites( - "ECDHE-RSA-CHACHA20-POLY1305"); - testUtilV2(listener, client_ctx, "", true, "", - "1406294e80c818158697d65d2aaca16748ff132442ab0e2f28bc1109f1d47a2e", "", "", - "ssl.handshake", 2, GetParam()); - - // Connection to server2.example.com using ECDHE-RSA-CHACHA20-POLY1305 fails. - client_ctx.set_sni("server2.example.com"); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.connection_error", 1, - GetParam()); - - // Connection to server1.example.com using ECDHE-RSA-AES128-GCM-SHA256 fails. - client_ctx.set_sni("server1.example.com"); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->clear_cipher_suites(); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->add_cipher_suites( - "ECDHE-RSA-AES128-GCM-SHA256"); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.connection_error", 1, - GetParam()); - - // Connection to server2.example.com using ECDHE-RSA-AES128-GCM-SHA256 succeeds. + // Client connects with one of the supported cipher suites, connection succeeds. // ssl.handshake logged by both: client & server. - client_ctx.set_sni("server2.example.com"); - testUtilV2(listener, client_ctx, "", true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.handshake", 2, GetParam()); + client_params->add_cipher_suites("ECDHE-RSA-CHACHA20-POLY1305"); + server_params->add_cipher_suites("ECDHE-RSA-CHACHA20-POLY1305"); + server_params->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256"); + testUtilV2(listener, client, "", true, "", "", "", "", "ssl.handshake", 2, GetParam()); + client_params->clear_cipher_suites(); + server_params->clear_cipher_suites(); + + // Client connects with unsupported cipher suite, connection fails. + client_params->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256"); + server_params->add_cipher_suites("ECDHE-RSA-CHACHA20-POLY1305"); + testUtilV2(listener, client, "", false, "", "", "", "", "ssl.connection_error", 1, GetParam()); + client_params->clear_cipher_suites(); + server_params->clear_cipher_suites(); } -TEST_P(SslSocketTest, SniEcdhCurves) { +TEST_P(SslSocketTest, EcdhCurves) { envoy::api::v2::Listener listener; - - // san_dns_cert.pem: server1.example.com - envoy::api::v2::listener::FilterChain* filter_chain1 = listener.add_filter_chains(); - filter_chain1->mutable_filter_chain_match()->add_sni_domains("server1.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert1->mutable_certificate_chain()->set_filename( + envoy::api::v2::listener::FilterChain* filter_chain = listener.add_filter_chains(); + envoy::api::v2::auth::TlsCertificate* server_cert = + filter_chain->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); + server_cert->mutable_certificate_chain()->set_filename( TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem")); - server_cert1->mutable_private_key()->set_filename( + server_cert->mutable_private_key()->set_filename( TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem")); + envoy::api::v2::auth::TlsParameters* server_params = + filter_chain->mutable_tls_context()->mutable_common_tls_context()->mutable_tls_params(); - // san_multiple_dns_cert.pem: server2.example.com - envoy::api::v2::listener::FilterChain* filter_chain2 = listener.add_filter_chains(); - filter_chain2->mutable_filter_chain_match()->add_sni_domains("server2.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert2->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem")); - server_cert2->mutable_private_key()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem")); - - envoy::api::v2::auth::CommonTlsContext* server_ctx1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context(); - envoy::api::v2::auth::CommonTlsContext* server_ctx2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context(); - envoy::api::v2::auth::UpstreamTlsContext client_ctx; - - // Test ECDH curves. - server_ctx1->mutable_tls_params()->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256"); - server_ctx1->mutable_tls_params()->add_ecdh_curves("X25519"); - server_ctx2->mutable_tls_params()->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256"); - server_ctx2->mutable_tls_params()->add_ecdh_curves("P-256"); + envoy::api::v2::auth::UpstreamTlsContext client; + envoy::api::v2::auth::TlsParameters* client_params = + client.mutable_common_tls_context()->mutable_tls_params(); - // Connection to server1.example.com using X25519 succeeds. - // ssl.handshake logged by both: client & server. - client_ctx.set_sni("server1.example.com"); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->add_ecdh_curves("X25519"); - testUtilV2(listener, client_ctx, "", true, "", - "1406294e80c818158697d65d2aaca16748ff132442ab0e2f28bc1109f1d47a2e", "", "", - "ssl.handshake", 2, GetParam()); - - // Connection to server2.example.com using X25519 fails. - client_ctx.set_sni("server2.example.com"); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.connection_error", 1, - GetParam()); - - // Connection to server1.example.com using P-256 fails. - client_ctx.set_sni("server1.example.com"); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->clear_ecdh_curves(); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->add_ecdh_curves("P-256"); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.connection_error", 1, - GetParam()); - - // Connection to server2.example.com using P-256 succeeds. + // Connection using defaults (client & server) succeeds. // ssl.handshake logged by both: client & server. - client_ctx.set_sni("server2.example.com"); - testUtilV2(listener, client_ctx, "", true, "", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.handshake", 2, GetParam()); -} - -TEST_P(SslSocketTest, SniProtocolVersions) { - envoy::api::v2::Listener listener; + testUtilV2(listener, client, "", true, "", "", "", "", "ssl.handshake", 2, GetParam()); - // san_dns_cert.pem: server1.example.com - envoy::api::v2::listener::FilterChain* filter_chain1 = listener.add_filter_chains(); - filter_chain1->mutable_filter_chain_match()->add_sni_domains("server1.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert1->mutable_certificate_chain()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem")); - server_cert1->mutable_private_key()->set_filename( - TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem")); - - // san_multiple_dns_cert.pem: server2.example.com - envoy::api::v2::listener::FilterChain* filter_chain2 = listener.add_filter_chains(); - filter_chain2->mutable_filter_chain_match()->add_sni_domains("server2.example.com"); - envoy::api::v2::auth::TlsCertificate* server_cert2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates(); - server_cert2->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem")); - server_cert2->mutable_private_key()->set_filename(TestEnvironment::substitute( - "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem")); - - envoy::api::v2::auth::CommonTlsContext* server_ctx1 = - filter_chain1->mutable_tls_context()->mutable_common_tls_context(); - envoy::api::v2::auth::CommonTlsContext* server_ctx2 = - filter_chain2->mutable_tls_context()->mutable_common_tls_context(); - envoy::api::v2::auth::UpstreamTlsContext client_ctx; - - // Test protocol versions. - server_ctx1->mutable_tls_params()->set_tls_minimum_protocol_version( - envoy::api::v2::auth::TlsParameters::TLSv1_2); - server_ctx1->mutable_tls_params()->set_tls_maximum_protocol_version( - envoy::api::v2::auth::TlsParameters::TLSv1_3); - - server_ctx2->mutable_tls_params()->set_tls_minimum_protocol_version( - envoy::api::v2::auth::TlsParameters::TLSv1_0); - server_ctx2->mutable_tls_params()->set_tls_maximum_protocol_version( - envoy::api::v2::auth::TlsParameters::TLSv1_2); - - // Connection to server1.example.com using TLSv1.3 succeeds. - // ssl.handshake logged by both: client & server. - client_ctx.set_sni("server1.example.com"); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->set_tls_minimum_protocol_version( - envoy::api::v2::auth::TlsParameters::TLSv1_3); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->set_tls_maximum_protocol_version( - envoy::api::v2::auth::TlsParameters::TLSv1_3); - testUtilV2(listener, client_ctx, "", true, "TLSv1.3", - "1406294e80c818158697d65d2aaca16748ff132442ab0e2f28bc1109f1d47a2e", "", "", - "ssl.handshake", 2, GetParam()); - - // Connection to server2.example.com using TLSv1.3 fails. - client_ctx.set_sni("server2.example.com"); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.connection_error", 1, - GetParam()); - - // Connection to server1.example.com using TLSv1.0 fails. - client_ctx.set_sni("server1.example.com"); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->set_tls_minimum_protocol_version( - envoy::api::v2::auth::TlsParameters::TLSv1_0); - client_ctx.mutable_common_tls_context()->mutable_tls_params()->set_tls_maximum_protocol_version( - envoy::api::v2::auth::TlsParameters::TLSv1_0); - testUtilV2(listener, client_ctx, "", false, "", "", "", "", "ssl.connection_error", 1, - GetParam()); - - // Connection to server2.example.com using TLSv1.0 succeeds. + // Client connects with one of the supported ECDH curves, connection succeeds. // ssl.handshake logged by both: client & server. - client_ctx.set_sni("server2.example.com"); - testUtilV2(listener, client_ctx, "", true, "TLSv1", - "77b3c289abbded6ad508d9853ba0bd36a1f6a9680eaba01e0f32774c0676ebe8", "", "", - "ssl.handshake", 2, GetParam()); + client_params->add_ecdh_curves("X25519"); + server_params->add_ecdh_curves("X25519"); + server_params->add_ecdh_curves("P-256"); + server_params->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256"); + testUtilV2(listener, client, "", true, "", "", "", "", "ssl.handshake", 2, GetParam()); + client_params->clear_ecdh_curves(); + server_params->clear_ecdh_curves(); + server_params->clear_cipher_suites(); + + // Client connects with unsupported ECDH curve, connection fails. + client_params->add_ecdh_curves("X25519"); + server_params->add_ecdh_curves("P-256"); + server_params->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256"); + testUtilV2(listener, client, "", false, "", "", "", "", "ssl.connection_error", 1, GetParam()); + client_params->clear_ecdh_curves(); + server_params->clear_ecdh_curves(); + server_params->clear_cipher_suites(); } TEST_P(SslSocketTest, RevokedCertificate) { @@ -2030,8 +1716,8 @@ class SslReadBufferLimitTest : public SslCertsTest, server_ctx_loader_ = TestEnvironment::jsonLoadFromString(server_ctx_json_); server_ctx_config_.reset(new ServerContextConfigImpl(*server_ctx_loader_)); manager_.reset(new ContextManagerImpl(runtime_)); - server_ssl_socket_factory_.reset( - new ServerSslSocketFactory(*server_ctx_config_, "", {}, true, *manager_, stats_store_)); + server_ssl_socket_factory_.reset(new ServerSslSocketFactory( + *server_ctx_config_, *manager_, stats_store_, std::vector{})); listener_ = dispatcher_->createListener(socket_, listener_callbacks_, true, false); diff --git a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc index d5624762c7a30..5bc31d35fc4ef 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc @@ -40,12 +40,13 @@ namespace ProxyProtocol { class ProxyProtocolTest : public testing::TestWithParam, public Network::ListenerConfig, + public Network::FilterChainManager, protected Logger::Loggable { public: ProxyProtocolTest() : socket_(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true), connection_handler_(new Server::ConnectionHandlerImpl(ENVOY_LOGGER(), dispatcher_)), - name_("proxy") { + name_("proxy"), filter_chain_(Network::Test::createEmptyFilterChainWithRawBufferSockets()) { connection_handler_->addListener(*this); conn_ = dispatcher_.createClientConnection(socket_.localAddress(), Network::Address::InstanceConstSharedPtr(), @@ -54,11 +55,9 @@ class ProxyProtocolTest : public testing::TestWithParam bool { @@ -76,8 +80,9 @@ class ProxyProtocolTest : public testing::TestWithParamconnect(); if (read) { read_filter_.reset(new NiceMock()); - EXPECT_CALL(factory_, createNetworkFilterChain(_)) - .WillOnce(Invoke([&](Network::Connection& connection) -> bool { + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)) + .WillOnce(Invoke([&](Network::Connection& connection, + const std::vector&) -> bool { server_connection_ = &connection; connection.addConnectionCallbacks(server_callbacks_); connection.addReadFilter(read_filter_); @@ -128,7 +133,6 @@ class ProxyProtocolTest : public testing::TestWithParam read_filter_; std::string name_; + const Network::FilterChainSharedPtr filter_chain_; }; // Parameterize the listener socket address version. @@ -313,7 +318,7 @@ TEST_P(ProxyProtocolTest, Closed) { TEST_P(ProxyProtocolTest, ClosedEmpty) { // We may or may not get these, depending on the operating system timing. EXPECT_CALL(factory_, createListenerFilterChain(_)).Times(AtLeast(0)); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).Times(AtLeast(0)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).Times(AtLeast(0)); conn_->connect(); conn_->close(Network::ConnectionCloseType::NoFlush); dispatcher_.run(Event::Dispatcher::RunType::NonBlock); @@ -321,6 +326,7 @@ TEST_P(ProxyProtocolTest, ClosedEmpty) { class WildcardProxyProtocolTest : public testing::TestWithParam, public Network::ListenerConfig, + public Network::FilterChainManager, protected Logger::Loggable { public: WildcardProxyProtocolTest() @@ -329,7 +335,7 @@ class WildcardProxyProtocolTest : public testing::TestWithParamip()->port())), connection_handler_(new Server::ConnectionHandlerImpl(ENVOY_LOGGER(), dispatcher_)), - name_("proxy") { + name_("proxy"), filter_chain_(Network::Test::createEmptyFilterChainWithRawBufferSockets()) { connection_handler_->addListener(*this); conn_ = dispatcher_.createClientConnection(local_dst_address_, Network::Address::InstanceConstSharedPtr(), @@ -345,11 +351,9 @@ class WildcardProxyProtocolTest : public testing::TestWithParamconnect(); read_filter_.reset(new NiceMock()); - EXPECT_CALL(factory_, createNetworkFilterChain(_)) - .WillOnce(Invoke([&](Network::Connection& connection) -> bool { + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)) + .WillOnce(Invoke([&](Network::Connection& connection, + const std::vector&) -> bool { server_connection_ = &connection; connection.addConnectionCallbacks(server_callbacks_); connection.addReadFilter(read_filter_); @@ -401,7 +411,6 @@ class WildcardProxyProtocolTest : public testing::TestWithParam read_filter_; std::string name_; + const Network::FilterChainSharedPtr filter_chain_; }; // Parameterize the listener socket address version. diff --git a/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc b/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc index fd1dda7cec6ef..6f22c95a9f336 100644 --- a/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc +++ b/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc @@ -103,7 +103,7 @@ TEST_F(TlsInspectorTest, SniRegistered) { return client_hello.size(); })); EXPECT_CALL(socket_, setRequestedServerName(Eq(servername))); - EXPECT_CALL(socket_, setDetectedTransportProtocol(absl::string_view("ssl"))); + EXPECT_CALL(socket_, setDetectedTransportProtocol(absl::string_view("tls"))); EXPECT_CALL(cb_, continueFilterChain(true)); file_event_callback_(Event::FileReadyType::Read); EXPECT_EQ(1, cfg_->stats().tls_found_.value()); @@ -133,7 +133,7 @@ TEST_F(TlsInspectorTest, MultipleReads) { bool got_continue = false; EXPECT_CALL(socket_, setRequestedServerName(Eq(servername))); - EXPECT_CALL(socket_, setDetectedTransportProtocol(absl::string_view("ssl"))); + EXPECT_CALL(socket_, setDetectedTransportProtocol(absl::string_view("tls"))); EXPECT_CALL(cb_, continueFilterChain(true)).WillOnce(InvokeWithoutArgs([&got_continue]() { got_continue = true; })); @@ -155,7 +155,7 @@ TEST_F(TlsInspectorTest, NoSni) { return client_hello.size(); })); EXPECT_CALL(socket_, setRequestedServerName(_)).Times(0); - EXPECT_CALL(socket_, setDetectedTransportProtocol(absl::string_view("ssl"))); + EXPECT_CALL(socket_, setDetectedTransportProtocol(absl::string_view("tls"))); EXPECT_CALL(cb_, continueFilterChain(true)); file_event_callback_(Event::FileReadyType::Read); EXPECT_EQ(1, cfg_->stats().tls_found_.value()); diff --git a/test/integration/BUILD b/test/integration/BUILD index cb31c6248d009..ed33ef6e0ea66 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -398,6 +398,7 @@ envoy_cc_test( "//source/common/network:utility_lib", "//source/common/ssl:context_config_lib", "//source/common/ssl:context_lib", + "//source/extensions/filters/listener/tls_inspector:config", "//source/extensions/transport_sockets/ssl:config", "//test/mocks/runtime:runtime_mocks", "//test/test_common:utility_lib", @@ -466,6 +467,7 @@ envoy_cc_test( deps = [ ":http_integration_lib", "//source/common/http:header_map_lib", + "//source/extensions/filters/listener/tls_inspector:config", "//source/extensions/transport_sockets/ssl:config", "//test/test_common:utility_lib", ], diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 5a1f11491627e..0331966c6f46d 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -84,9 +84,8 @@ class AdsIntegrationTest : public HttpIntegrationTest, public Grpc::GrpcClientIn Ssl::ServerContextConfigImpl cfg(tls_context); static Stats::Scope* upstream_stats_store = new Stats::TestIsolatedStoreImpl(); - return std::make_unique(cfg, EMPTY_STRING, - std::vector{}, true, - context_manager_, *upstream_stats_store); + return std::make_unique( + cfg, context_manager_, *upstream_stats_store, std::vector{}); } AssertionResult diff --git a/test/integration/autonomous_upstream.cc b/test/integration/autonomous_upstream.cc index a0e4ab7c1e10c..77f5092b05d38 100644 --- a/test/integration/autonomous_upstream.cc +++ b/test/integration/autonomous_upstream.cc @@ -67,7 +67,8 @@ AutonomousUpstream::~AutonomousUpstream() { http_connections_.clear(); } -bool AutonomousUpstream::createNetworkFilterChain(Network::Connection& connection) { +bool AutonomousUpstream::createNetworkFilterChain(Network::Connection& connection, + const std::vector&) { AutonomousHttpConnectionPtr http_connection(new AutonomousHttpConnection( QueuedConnectionWrapperPtr{new QueuedConnectionWrapper(connection, true)}, stats_store_, http_type_, *this)); diff --git a/test/integration/autonomous_upstream.h b/test/integration/autonomous_upstream.h index 6ddd88c0efc3f..df10db3e2c131 100644 --- a/test/integration/autonomous_upstream.h +++ b/test/integration/autonomous_upstream.h @@ -53,7 +53,9 @@ class AutonomousUpstream : public FakeUpstream { Network::Address::IpVersion version) : FakeUpstream(port, type, version) {} ~AutonomousUpstream(); - bool createNetworkFilterChain(Network::Connection& connection) override; + bool + createNetworkFilterChain(Network::Connection& connection, + const std::vector& filter_factories) override; bool createListenerFilterChain(Network::ListenerFilterManager& listener) override; void setLastRequestHeaders(const Http::HeaderMap& headers); diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index e35bb07a1183a..0fc0432507cb2 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -295,12 +295,12 @@ FakeUpstream::FakeUpstream(Network::TransportSocketFactoryPtr&& transport_socket FakeUpstream::FakeUpstream(Network::TransportSocketFactoryPtr&& transport_socket_factory, Network::SocketPtr&& listen_socket, FakeHttpConnection::Type type, bool enable_half_close) - : http_type_(type), transport_socket_factory_(std::move(transport_socket_factory)), - socket_(std::move(listen_socket)), api_(new Api::Impl(std::chrono::milliseconds(10000))), + : http_type_(type), socket_(std::move(listen_socket)), + api_(new Api::Impl(std::chrono::milliseconds(10000))), dispatcher_(api_->allocateDispatcher()), handler_(new Server::ConnectionHandlerImpl(ENVOY_LOGGER(), *dispatcher_)), - allow_unexpected_disconnects_(false), enable_half_close_(enable_half_close), - listener_(*this) { + allow_unexpected_disconnects_(false), enable_half_close_(enable_half_close), listener_(*this), + filter_chain_(Network::Test::createEmptyFilterChain(std::move(transport_socket_factory))) { thread_.reset(new Thread::Thread([this]() -> void { threadRoutine(); })); server_initialized_.waitReady(); } @@ -315,7 +315,8 @@ void FakeUpstream::cleanUp() { } } -bool FakeUpstream::createNetworkFilterChain(Network::Connection& connection) { +bool FakeUpstream::createNetworkFilterChain(Network::Connection& connection, + const std::vector&) { std::unique_lock lock(lock_); connection.readDisable(true); new_connections_.emplace_back( diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index d38b2df665224..65bb1e5a2cf04 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -289,7 +289,9 @@ typedef std::unique_ptr FakeRawConnectionPtr; /** * Provides a fake upstream server for integration testing. */ -class FakeUpstream : Logger::Loggable, public Network::FilterChainFactory { +class FakeUpstream : Logger::Loggable, + public Network::FilterChainManager, + public Network::FilterChainFactory { public: FakeUpstream(const std::string& uds_path, FakeHttpConnection::Type type); FakeUpstream(uint32_t port, FakeHttpConnection::Type type, Network::Address::IpVersion version, @@ -309,8 +311,15 @@ class FakeUpstream : Logger::Loggable, public Network::Filt waitForHttpConnection(Event::Dispatcher& client_dispatcher, std::vector>& upstreams); + // Network::FilterChainManager + const Network::FilterChain* findFilterChain(const Network::ConnectionSocket&) const override { + return filter_chain_.get(); + } + // Network::FilterChainFactory - bool createNetworkFilterChain(Network::Connection& connection) override; + bool + createNetworkFilterChain(Network::Connection& connection, + const std::vector& filter_factories) override; bool createListenerFilterChain(Network::ListenerFilterManager& listener) override; void set_allow_unexpected_disconnects(bool value) { allow_unexpected_disconnects_ = value; } @@ -330,11 +339,9 @@ class FakeUpstream : Logger::Loggable, public Network::Filt private: // Network::ListenerConfig + Network::FilterChainManager& filterChainManager() override { return parent_; } Network::FilterChainFactory& filterChainFactory() override { return parent_; } Network::Socket& socket() override { return *parent_.socket_; } - Network::TransportSocketFactory& transportSocketFactory() override { - return *parent_.transport_socket_factory_; - } bool bindToPort() override { return true; } bool handOffRestoredDestinationConnections() const override { return false; } uint32_t perConnectionBufferLimitBytes() override { return 0; } @@ -348,7 +355,6 @@ class FakeUpstream : Logger::Loggable, public Network::Filt void threadRoutine(); - Network::TransportSocketFactoryPtr transport_socket_factory_; Network::SocketPtr socket_; ConditionalInitializer server_initialized_; // Guards any objects which can be altered both in the upstream thread and the @@ -363,5 +369,6 @@ class FakeUpstream : Logger::Loggable, public Network::Filt bool allow_unexpected_disconnects_; const bool enable_half_close_; FakeListener listener_; + const Network::FilterChainSharedPtr filter_chain_; }; } // namespace Envoy diff --git a/test/integration/ssl_integration_test.cc b/test/integration/ssl_integration_test.cc index 42d84f48f3182..ec1523ed32b99 100644 --- a/test/integration/ssl_integration_test.cc +++ b/test/integration/ssl_integration_test.cc @@ -192,7 +192,7 @@ class SslCaptureIntegrationTest : public SslIntegrationTest { bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); // Configure inner SSL transport socket based on existing config. envoy::api::v2::core::TransportSocket ssl_transport_socket; - ssl_transport_socket.set_name("ssl"); + ssl_transport_socket.set_name("tls"); MessageUtil::jsonConvert(filter_chain->tls_context(), *ssl_transport_socket.mutable_config()); // Configure outer capture transport socket. auto* transport_socket = filter_chain->mutable_transport_socket(); diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index cc7dcfcd57326..76be3b8d4ea3c 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -74,9 +74,8 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createUpstreamSslContext Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(json); Ssl::ServerContextConfigImpl cfg(*loader); static Stats::Scope* upstream_stats_store = new Stats::TestIsolatedStoreImpl(); - return std::make_unique(cfg, EMPTY_STRING, - std::vector{}, true, - *context_manager_, *upstream_stats_store); + return std::make_unique( + cfg, *context_manager_, *upstream_stats_store, std::vector{}); } Network::ClientConnectionPtr XfccIntegrationTest::makeClientConnection() { diff --git a/test/mocks/network/mocks.cc b/test/mocks/network/mocks.cc index 295c9aee8a83b..31fba73612017 100644 --- a/test/mocks/network/mocks.cc +++ b/test/mocks/network/mocks.cc @@ -166,6 +166,12 @@ MockListenerFilterCallbacks::~MockListenerFilterCallbacks() {} MockListenerFilterManager::MockListenerFilterManager() {} MockListenerFilterManager::~MockListenerFilterManager() {} +MockFilterChain::MockFilterChain() {} +MockFilterChain::~MockFilterChain() {} + +MockFilterChainManager::MockFilterChainManager() {} +MockFilterChainManager::~MockFilterChainManager() {} + MockFilterChainFactory::MockFilterChainFactory() { ON_CALL(*this, createListenerFilterChain(_)).WillByDefault(Return(true)); } diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index e4d216ab0e3b4..449623ecfe0d0 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -257,12 +257,33 @@ class MockListenerFilterManager : public ListenerFilterManager { MOCK_METHOD1(addAcceptFilter_, void(Network::ListenerFilterPtr&)); }; +class MockFilterChain : public FilterChain { +public: + MockFilterChain(); + ~MockFilterChain(); + + // Network::FilterChain + MOCK_CONST_METHOD0(transportSocketFactory, const TransportSocketFactory&()); + MOCK_CONST_METHOD0(networkFilterFactories, const std::vector&()); +}; + +class MockFilterChainManager : public FilterChainManager { +public: + MockFilterChainManager(); + ~MockFilterChainManager(); + + // Network::FilterChainManager + MOCK_CONST_METHOD1(findFilterChain, const FilterChain*(const ConnectionSocket& socket)); +}; + class MockFilterChainFactory : public FilterChainFactory { public: MockFilterChainFactory(); ~MockFilterChainFactory(); - MOCK_METHOD1(createNetworkFilterChain, bool(Connection& connection)); + MOCK_METHOD2(createNetworkFilterChain, + bool(Connection& connection, + const std::vector& filter_factories)); MOCK_METHOD1(createListenerFilterChain, bool(ListenerFilterManager& listener)); }; @@ -325,9 +346,9 @@ class MockListenerConfig : public ListenerConfig { MockListenerConfig(); ~MockListenerConfig(); + MOCK_METHOD0(filterChainManager, FilterChainManager&()); MOCK_METHOD0(filterChainFactory, FilterChainFactory&()); MOCK_METHOD0(socket, Socket&()); - MOCK_METHOD0(transportSocketFactory, TransportSocketFactory&()); MOCK_METHOD0(bindToPort, bool()); MOCK_CONST_METHOD0(handOffRestoredDestinationConnections, bool()); MOCK_METHOD0(perConnectionBufferLimitBytes, uint32_t()); diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index fba353374c2e8..c91bb9349e0b2 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -24,21 +24,16 @@ class MockContextManager : public ContextManager { return ClientContextPtr{createSslClientContext_(scope, config)}; } - ServerContextPtr createSslServerContext(const std::string& listener_name, - const std::vector& server_names, - Stats::Scope& scope, const ServerContextConfig& config, - bool skip_context_update) override { - return ServerContextPtr{ - createSslServerContext_(listener_name, server_names, scope, config, skip_context_update)}; + ServerContextPtr createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config, + const std::vector& server_names) override { + return ServerContextPtr{createSslServerContext_(scope, config, server_names)}; } MOCK_METHOD2(createSslClientContext_, ClientContext*(Stats::Scope& scope, const ClientContextConfig& config)); - MOCK_METHOD5(createSslServerContext_, - ServerContext*(const std::string& listener_name, - const std::vector& server_names, Stats::Scope& stats, - const ServerContextConfig& config, bool skip_context_update)); - MOCK_CONST_METHOD2(findSslServerContext, ServerContext*(const std::string&, const std::string&)); + MOCK_METHOD3(createSslServerContext_, + ServerContext*(Stats::Scope& stats, const ServerContextConfig& config, + const std::vector& server_names)); MOCK_CONST_METHOD0(daysUntilFirstCertExpires, size_t()); MOCK_METHOD1(iterateContexts, void(std::function callback)); }; diff --git a/test/server/BUILD b/test/server/BUILD index e4b68c1763f5b..d035d27990c3d 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -49,6 +49,7 @@ envoy_cc_test( "//source/server:connection_handler_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", + "//test/test_common:network_utility_lib", ], ) @@ -135,7 +136,9 @@ envoy_cc_test( "//source/common/network:listen_socket_lib", "//source/common/network:socket_option_lib", "//source/common/network:utility_lib", + "//source/common/ssl:ssl_socket_lib", "//source/extensions/filters/listener/original_dst:config", + "//source/extensions/filters/listener/tls_inspector:config", "//source/extensions/filters/network/http_connection_manager:config", "//source/extensions/transport_sockets/raw_buffer:config", "//source/extensions/transport_sockets/ssl:config", diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc index 448853f3ec618..0b8016fae8360 100644 --- a/test/server/connection_handler_test.cc +++ b/test/server/connection_handler_test.cc @@ -8,6 +8,7 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" +#include "test/test_common/network_utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -25,7 +26,9 @@ namespace Server { class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable { public: - ConnectionHandlerTest() : handler_(new ConnectionHandlerImpl(ENVOY_LOGGER(), dispatcher_)) {} + ConnectionHandlerTest() + : handler_(new ConnectionHandlerImpl(ENVOY_LOGGER(), dispatcher_)), + filter_chain_(Network::Test::createEmptyFilterChainWithRawBufferSockets()) {} // Listener class TestListener : public Network::ListenerConfig, public LinkedObject { @@ -36,11 +39,9 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable dispatcher_; Network::ConnectionHandlerPtr handler_; + NiceMock manager_; NiceMock factory_; std::list listeners_; + const Network::FilterChainSharedPtr filter_chain_; }; TEST_F(ConnectionHandlerTest, RemoveListener) { @@ -94,7 +96,6 @@ TEST_F(ConnectionHandlerTest, RemoveListener) { handler_->addListener(*test_listener); Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).WillOnce(Return(true)); listener_callbacks->onNewConnection(Network::ConnectionPtr{connection}); EXPECT_EQ(1UL, handler_->numConnections()); @@ -132,7 +133,6 @@ TEST_F(ConnectionHandlerTest, DestroyCloseConnections) { handler_->addListener(*test_listener); Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).WillOnce(Return(true)); listener_callbacks->onNewConnection(Network::ConnectionPtr{connection}); EXPECT_EQ(1UL, handler_->numConnections()); @@ -158,11 +158,14 @@ TEST_F(ConnectionHandlerTest, CloseDuringFilterChainCreate) { EXPECT_CALL(test_listener->socket_, localAddress()); handler_->addListener(*test_listener); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get())); Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(factory_, createNetworkFilterChain(_)); + EXPECT_CALL(dispatcher_, createServerConnection_(_, _)).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); EXPECT_CALL(*connection, state()).WillOnce(Return(Network::Connection::State::Closed)); EXPECT_CALL(*connection, addConnectionCallbacks(_)).Times(0); - listener_callbacks->onNewConnection(Network::ConnectionPtr{connection}); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + listener_callbacks->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); EXPECT_EQ(0UL, handler_->numConnections()); EXPECT_CALL(*listener, onDestroy()); @@ -184,10 +187,14 @@ TEST_F(ConnectionHandlerTest, CloseConnectionOnEmptyFilterChain) { EXPECT_CALL(test_listener->socket_, localAddress()); handler_->addListener(*test_listener); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get())); Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).WillOnce(Return(false)); + EXPECT_CALL(dispatcher_, createServerConnection_(_, _)).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(false)); EXPECT_CALL(*connection, close(Network::ConnectionCloseType::NoFlush)); - listener_callbacks->onNewConnection(Network::ConnectionPtr{connection}); + EXPECT_CALL(*connection, addConnectionCallbacks(_)).Times(0); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + listener_callbacks->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); EXPECT_EQ(0UL, handler_->numConnections()); EXPECT_CALL(*listener, onDestroy()); @@ -292,9 +299,10 @@ TEST_F(ConnectionHandlerTest, NormalRedirect) { EXPECT_CALL(*accepted_socket, setLocalAddress(alt_address, true)); EXPECT_CALL(*accepted_socket, localAddressRestored()).WillOnce(Return(true)); EXPECT_CALL(*accepted_socket, localAddress()).WillRepeatedly(ReturnRef(alt_address)); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get())); Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).WillOnce(Return(true)); EXPECT_CALL(dispatcher_, createServerConnection_(_, _)).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); EXPECT_EQ(1UL, handler_->numConnections()); @@ -353,9 +361,10 @@ TEST_F(ConnectionHandlerTest, FallbackToWildcardListener) { EXPECT_CALL(*accepted_socket, setLocalAddress(alt_address, true)); EXPECT_CALL(*accepted_socket, localAddressRestored()).WillOnce(Return(true)); EXPECT_CALL(*accepted_socket, localAddress()).WillRepeatedly(ReturnRef(alt_address)); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get())); Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).WillOnce(Return(true)); EXPECT_CALL(dispatcher_, createServerConnection_(_, _)).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); EXPECT_EQ(1UL, handler_->numConnections()); @@ -399,9 +408,10 @@ TEST_F(ConnectionHandlerTest, WildcardListenerWithOriginalDst) { EXPECT_CALL(*accepted_socket, setLocalAddress(original_dst_address, true)); EXPECT_CALL(*accepted_socket, localAddressRestored()).WillOnce(Return(true)); EXPECT_CALL(*accepted_socket, localAddress()).WillRepeatedly(ReturnRef(original_dst_address)); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get())); Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).WillOnce(Return(true)); EXPECT_CALL(dispatcher_, createServerConnection_(_, _)).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); EXPECT_EQ(1UL, handler_->numConnections()); @@ -436,9 +446,10 @@ TEST_F(ConnectionHandlerTest, WildcardListenerWithNoOriginalDst) { EXPECT_CALL(*test_filter, onAccept(_)).WillOnce(Return(Network::FilterStatus::Continue)); EXPECT_CALL(*accepted_socket, localAddressRestored()).WillOnce(Return(false)); EXPECT_CALL(*accepted_socket, localAddress()).WillRepeatedly(ReturnRef(normal_address)); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get())); Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).WillOnce(Return(true)); EXPECT_CALL(dispatcher_, createServerConnection_(_, _)).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); EXPECT_EQ(1UL, handler_->numConnections()); @@ -462,9 +473,7 @@ TEST_F(ConnectionHandlerTest, TransportProtocolDefault) { EXPECT_CALL(*accepted_socket, detectedTransportProtocol()) .WillOnce(Return(absl::string_view(""))); EXPECT_CALL(*accepted_socket, setDetectedTransportProtocol(absl::string_view("raw_buffer"))); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).WillOnce(Return(true)); - Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(dispatcher_, createServerConnection_(_, _)).WillOnce(Return(connection)); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(nullptr)); listener_callbacks->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); EXPECT_CALL(*listener, onDestroy()); @@ -498,9 +507,7 @@ TEST_F(ConnectionHandlerTest, TransportProtocolCustom) { Network::MockConnectionSocket* accepted_socket = new NiceMock(); EXPECT_CALL(*accepted_socket, setDetectedTransportProtocol(dummy)); EXPECT_CALL(*accepted_socket, detectedTransportProtocol()).WillOnce(Return(dummy)); - EXPECT_CALL(factory_, createNetworkFilterChain(_)).WillOnce(Return(true)); - Network::MockConnection* connection = new NiceMock(); - EXPECT_CALL(dispatcher_, createServerConnection_(_, _)).WillOnce(Return(connection)); + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(nullptr)); listener_callbacks->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); EXPECT_CALL(*listener, onDestroy()); diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 6ce6e7606e46e..97f885efdbc72 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -8,6 +8,7 @@ #include "common/network/listen_socket_impl.h" #include "common/network/socket_option_impl.h" #include "common/network/utility.h" +#include "common/ssl/ssl_socket.h" #include "server/configuration_impl.h" #include "server/listener_manager_impl.h" @@ -133,7 +134,24 @@ class ListenerManagerImplWithRealFiltersTest : public ListenerManagerImplTest { return ProdListenerComponentFactory::createListenerFilterFactoryList_(filters, context); })); + socket_.reset(new NiceMock()); } + + const Network::FilterChain* findFilterChain(std::string server_name, + bool expect_transport_protocol_test, + std::string transport_protocol) { + EXPECT_CALL(*socket_, requestedServerName()).WillOnce(Return(absl::string_view(server_name))); + if (expect_transport_protocol_test) { + EXPECT_CALL(*socket_, detectedTransportProtocol()) + .WillOnce(Return(absl::string_view(transport_protocol))); + } else { + EXPECT_CALL(*socket_, detectedTransportProtocol()).Times(0); + } + return manager_->listeners().back().get().filterChainManager().findFilterChain(*socket_); + } + +private: + std::unique_ptr socket_; }; class MockLdsApi : public LdsApi { @@ -201,8 +219,11 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SslContext) { EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); manager_->addOrUpdateListener(parseListenerFromJson(json), "", true); - EXPECT_TRUE( - manager_->listeners().back().get().transportSocketFactory().implementsSecureTransport()); + EXPECT_EQ(1U, manager_->listeners().size()); + + auto filter_chain = findFilterChain("", true, "tls"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); } TEST_F(ListenerManagerImplWithRealFiltersTest, BadListenerConfig) { @@ -958,29 +979,21 @@ TEST_F(ListenerManagerImplTest, EarlyShutdown) { manager_->stopWorkers(); } -TEST_F(ListenerManagerImplWithRealFiltersTest, SniWithSingleFilterChain) { +TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSniMatch) { const std::string yaml = TestEnvironment::substitute(R"EOF( address: socket_address: { address: 127.0.0.1, port_value: 1234 } + listener_filters: + - name: "envoy.listener.tls_inspector" + config: {} filter_chains: - filter_chain_match: - sni_domains: "example.com" + sni_domains: "server1.example.com" tls_context: common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem" } private_key: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem" } - filters: - - name: envoy.http_connection_manager - config: - stat_prefix: sni_test - route_config: - virtual_hosts: - - name: "some_virtual_host" - domains: ["some.domain"] - routes: - - match: { prefix: "/" } - route: { cluster: service_foo } )EOF", Network::Address::IpVersion::v4); @@ -988,36 +1001,84 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SniWithSingleFilterChain) { EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true); EXPECT_EQ(1U, manager_->listeners().size()); + + // TLS client without SNI - no match. + auto filter_chain = findFilterChain("", false, "tls"); + EXPECT_EQ(filter_chain, nullptr); + + // TLS client without matching SNI - no match. + filter_chain = findFilterChain("www.example.com", false, "tls"); + EXPECT_EQ(filter_chain, nullptr); + + // TLS client with matching SNI - using 1st filter chain. + filter_chain = findFilterChain("server1.example.com", true, "tls"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(); + auto ssl_socket = dynamic_cast(transport_socket.get()); + auto server_names = ssl_socket->dnsSansLocalCertificate(); + EXPECT_EQ(server_names.size(), 1); + EXPECT_EQ(server_names.front(), "server1.example.com"); } -TEST_F(ListenerManagerImplWithRealFiltersTest, SniWithTwoEqualFilterChains) { +TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithTransportProtocolMatch) { const std::string yaml = TestEnvironment::substitute(R"EOF( address: socket_address: { address: 127.0.0.1, port_value: 1234 } + listener_filters: + - name: "envoy.listener.tls_inspector" + config: {} filter_chains: - filter_chain_match: - sni_domains: "example.com" + transport_protocol: "tls" tls_context: common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem" } private_key: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem" } + )EOF", + Network::Address::IpVersion::v4); + + EXPECT_CALL(server_.random_, uuid()); + EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); + manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true); + EXPECT_EQ(1U, manager_->listeners().size()); + + // TCP client - no match. + auto filter_chain = findFilterChain("", true, "raw_buffer"); + EXPECT_EQ(filter_chain, nullptr); + + // TLS client - using 1st filter chain. + filter_chain = findFilterChain("", true, "tls"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(); + auto ssl_socket = dynamic_cast(transport_socket.get()); + auto server_names = ssl_socket->dnsSansLocalCertificate(); + EXPECT_EQ(server_names.size(), 1); + EXPECT_EQ(server_names.front(), "server1.example.com"); +} + +TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithSniMatch) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: 127.0.0.1, port_value: 1234 } + listener_filters: + - name: "envoy.listener.tls_inspector" + config: {} + filter_chains: + - filter_chain_match: + # empty + tls_context: + common_tls_context: + tls_certificates: + - certificate_chain: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_uri_cert.pem" } + private_key: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_uri_key.pem" } session_ticket_keys: keys: - filename: "{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a" - filters: - - name: envoy.http_connection_manager - config: - stat_prefix: sni_test - route_config: - virtual_hosts: - - name: "some_virtual_host" - domains: ["some.domain"] - routes: - - match: { prefix: "/" } - route: { cluster: service_foo } - filter_chain_match: - sni_domains: "www.example.com" + sni_domains: "server1.example.com" tls_context: common_tls_context: tls_certificates: @@ -1026,17 +1087,16 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SniWithTwoEqualFilterChains) { session_ticket_keys: keys: - filename: "{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a" - filters: - - name: envoy.http_connection_manager - config: - stat_prefix: sni_test - route_config: - virtual_hosts: - - name: "some_virtual_host" - domains: ["some.domain"] - routes: - - match: { prefix: "/" } - route: { cluster: service_foo } + - filter_chain_match: + sni_domains: "*.example.com" + tls_context: + common_tls_context: + tls_certificates: + - certificate_chain: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_cert.pem" } + private_key: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_multiple_dns_key.pem" } + session_ticket_keys: + keys: + - filename: "{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a" )EOF", Network::Address::IpVersion::v4); @@ -1044,13 +1104,85 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SniWithTwoEqualFilterChains) { EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true); EXPECT_EQ(1U, manager_->listeners().size()); + + // TLS client without SNI - using 1st filter chain. + auto filter_chain = findFilterChain("", true, "tls"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(); + auto ssl_socket = dynamic_cast(transport_socket.get()); + auto uri = ssl_socket->uriSanLocalCertificate(); + EXPECT_EQ(uri, "spiffe://lyft.com/test-team"); + + // TLS client with exact SNI match - using 2nd filter chain. + filter_chain = findFilterChain("server1.example.com", true, "tls"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + transport_socket = filter_chain->transportSocketFactory().createTransportSocket(); + ssl_socket = dynamic_cast(transport_socket.get()); + auto server_names = ssl_socket->dnsSansLocalCertificate(); + EXPECT_EQ(server_names.size(), 1); + EXPECT_EQ(server_names.front(), "server1.example.com"); + + // TLS client with wildcard SNI match - using 3nd filter chain. + filter_chain = findFilterChain("server2.example.com", true, "tls"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + transport_socket = filter_chain->transportSocketFactory().createTransportSocket(); + ssl_socket = dynamic_cast(transport_socket.get()); + server_names = ssl_socket->dnsSansLocalCertificate(); + EXPECT_EQ(server_names.size(), 2); + EXPECT_EQ(server_names.front(), "*.example.com"); } -TEST_F(ListenerManagerImplWithRealFiltersTest, - SniWithTwoEqualFilterChainsWithDifferentSessionTicketKeys) { +TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithTransportProtocolMatch) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: 127.0.0.1, port_value: 1234 } + listener_filters: + - name: "envoy.listener.tls_inspector" + config: {} + filter_chains: + - filter_chain_match: + # empty + - filter_chain_match: + transport_protocol: "tls" + tls_context: + common_tls_context: + tls_certificates: + - certificate_chain: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem" } + private_key: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem" } + )EOF", + Network::Address::IpVersion::v4); + + EXPECT_CALL(server_.random_, uuid()); + EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); + manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true); + EXPECT_EQ(1U, manager_->listeners().size()); + + // TCP client - using 1st filter chain. + auto filter_chain = findFilterChain("", true, "raw_buffer"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_FALSE(filter_chain->transportSocketFactory().implementsSecureTransport()); + + // TLS client - using 2nd filter chain. + filter_chain = findFilterChain("server1.example.com", true, "tls"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(); + auto ssl_socket = dynamic_cast(transport_socket.get()); + auto server_names = ssl_socket->dnsSansLocalCertificate(); + EXPECT_EQ(server_names.size(), 1); + EXPECT_EQ(server_names.front(), "server1.example.com"); +} + +TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDifferentSessionTicketKeys) { const std::string yaml = TestEnvironment::substitute(R"EOF( address: socket_address: { address: 127.0.0.1, port_value: 1234 } + listener_filters: + - name: "envoy.listener.tls_inspector" + config: {} filter_chains: - filter_chain_match: sni_domains: "example.com" @@ -1062,17 +1194,6 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, session_ticket_keys: keys: - filename: "{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a" - filters: - - name: envoy.http_connection_manager - config: - stat_prefix: sni_test - route_config: - virtual_hosts: - - name: "some_virtual_host" - domains: ["some.domain"] - routes: - - match: { prefix: "/" } - route: { cluster: service_foo } - filter_chain_match: sni_domains: "www.example.com" tls_context: @@ -1083,17 +1204,6 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, session_ticket_keys: keys: - filename: "{{ test_rundir }}/test/common/ssl/test_data/ticket_key_b" - filters: - - name: envoy.http_connection_manager - config: - stat_prefix: sni_test - route_config: - virtual_hosts: - - name: "some_virtual_host" - domains: ["some.domain"] - routes: - - match: { prefix: "/" } - route: { cluster: service_foo } )EOF", Network::Address::IpVersion::v4); @@ -1104,10 +1214,13 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, } TEST_F(ListenerManagerImplWithRealFiltersTest, - SniWithTwoEqualFilterChainsWithMixedUseOfSessionTicketKeys) { + MultipleFilterChainsWithMixedUseOfSessionTicketKeys) { const std::string yaml = TestEnvironment::substitute(R"EOF( address: socket_address: { address: 127.0.0.1, port_value: 1234 } + listener_filters: + - name: "envoy.listener.tls_inspector" + config: {} filter_chains: - filter_chain_match: sni_domains: "example.com" @@ -1119,17 +1232,6 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, session_ticket_keys: keys: - filename: "{{ test_rundir }}/test/common/ssl/test_data/ticket_key_a" - filters: - - name: envoy.http_connection_manager - config: - stat_prefix: sni_test - route_config: - virtual_hosts: - - name: "some_virtual_host" - domains: ["some.domain"] - routes: - - match: { prefix: "/" } - route: { cluster: service_foo } - filter_chain_match: sni_domains: "www.example.com" tls_context: @@ -1137,74 +1239,83 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem" } private_key: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem" } - filters: - - name: envoy.http_connection_manager - config: - stat_prefix: sni_test - route_config: - virtual_hosts: - - name: "some_virtual_host" - domains: ["some.domain"] - routes: - - match: { prefix: "/" } - route: { cluster: service_foo } + )EOF", + Network::Address::IpVersion::v4); + + EXPECT_CALL(server_.random_, uuid()); + EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); + manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true); + EXPECT_EQ(1U, manager_->listeners().size()); +} + +TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithSameMatch) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: 127.0.0.1, port_value: 1234 } + listener_filters: + - name: "envoy.listener.tls_inspector" + config: {} + filter_chains: + - filter_chain_match: + transport_protocol: "tls" + - filter_chain_match: + transport_protocol: "tls" )EOF", Network::Address::IpVersion::v4); EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true), EnvoyException, - "error adding listener '127.0.0.1:1234': filter chains with mixed use " - "of Session Ticket Keys are currently not supported"); + "error adding listener '127.0.0.1:1234': multiple filter chains with " + "the same matching rules are defined"); } -TEST_F(ListenerManagerImplWithRealFiltersTest, SniWithTwoDifferentFilterChains) { +TEST_F(ListenerManagerImplWithRealFiltersTest, TlsFilterChainWithoutTlsInspector) { const std::string yaml = TestEnvironment::substitute(R"EOF( address: socket_address: { address: 127.0.0.1, port_value: 1234 } filter_chains: - filter_chain_match: - sni_domains: "example.com" - tls_context: - common_tls_context: - tls_certificates: - - certificate_chain: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem" } - private_key: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem" } - filters: - - name: envoy.http_connection_manager - config: - stat_prefix: sni_test - route_config: - virtual_hosts: - - name: "some_virtual_host" - domains: ["some.domain"] - routes: - - match: { prefix: "/" } - route: { cluster: service_foo } + transport_protocol: "tls" + )EOF", + Network::Address::IpVersion::v4); + + EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true), + EnvoyException, + "error adding listener '127.0.0.1:1234': filter chain match rules " + "require TLS Inspector listener filter, but it isn't configured"); +} + +TEST_F(ListenerManagerImplWithRealFiltersTest, SniFilterChainWithoutTlsInspector) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: 127.0.0.1, port_value: 1234 } + filter_chains: - filter_chain_match: - sni_domains: "www.example.com" - tls_context: - common_tls_context: - tls_certificates: - - certificate_chain: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem" } - private_key: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem" } - filters: - - name: envoy.http_connection_manager - config: - stat_prefix: sni_test - route_config: - virtual_hosts: - - name: "some_virtual_host" - domains: ["some.domain"] - routes: - - match: { prefix: "/" } - route: { cluster: service_bar } + sni_domains: "example.com" )EOF", Network::Address::IpVersion::v4); EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true), EnvoyException, - "error adding listener '127.0.0.1:1234': use of different filter " - "chains is currently not supported"); + "error adding listener '127.0.0.1:1234': filter chain match rules " + "require TLS Inspector listener filter, but it isn't configured"); +} + +TEST_F(ListenerManagerImplWithRealFiltersTest, CustomTransportProtocolWithSniWithoutTlsInspector) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: 127.0.0.1, port_value: 1234 } + filter_chains: + - filter_chain_match: + sni_domains: "example.com" + transport_protocol: "custom" + )EOF", + Network::Address::IpVersion::v4); + + EXPECT_CALL(server_.random_, uuid()); + EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); + manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true); + EXPECT_EQ(1U, manager_->listeners().size()); } TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInline) { diff --git a/test/test_common/BUILD b/test/test_common/BUILD index 8a2bb7efc347b..3002d14a8c42e 100644 --- a/test/test_common/BUILD +++ b/test/test_common/BUILD @@ -39,6 +39,7 @@ envoy_cc_test_library( hdrs = ["network_utility.h"], deps = [ ":utility_lib", + "//include/envoy/network:filter_interface", "//source/common/common:assert_lib", "//source/common/network:address_lib", "//source/common/network:raw_buffer_socket_lib", diff --git a/test/test_common/network_utility.cc b/test/test_common/network_utility.cc index 2dbb4bef91e35..5dad1523c407e 100644 --- a/test/test_common/network_utility.cc +++ b/test/test_common/network_utility.cc @@ -187,7 +187,17 @@ TransportSocketPtr createRawBufferSocket() { return std::make_unique(); -}; +} + +const Network::FilterChainSharedPtr +createEmptyFilterChain(TransportSocketFactoryPtr&& transport_socket_factory) { + return std::make_shared(std::move(transport_socket_factory)); +} + +const Network::FilterChainSharedPtr createEmptyFilterChainWithRawBufferSockets() { + return createEmptyFilterChain(createRawBufferSocketFactory()); +} + } // namespace Test } // namespace Network } // namespace Envoy diff --git a/test/test_common/network_utility.h b/test/test_common/network_utility.h index ee6c8a8039cba..47d480cfd4792 100644 --- a/test/test_common/network_utility.h +++ b/test/test_common/network_utility.h @@ -3,6 +3,7 @@ #include #include "envoy/network/address.h" +#include "envoy/network/filter.h" #include "envoy/network/transport_socket.h" namespace Envoy { @@ -114,6 +115,43 @@ TransportSocketPtr createRawBufferSocket(); */ TransportSocketFactoryPtr createRawBufferSocketFactory(); +/** + * Implementation of Network::FilterChain with empty filter chain, but pluggable transport socket + * factory. + */ +class EmptyFilterChain : public FilterChain { +public: + EmptyFilterChain(TransportSocketFactoryPtr&& transport_socket_factory) + : transport_socket_factory_(std::move(transport_socket_factory)) {} + + // Network::FilterChain + const Network::TransportSocketFactory& transportSocketFactory() const override { + return *transport_socket_factory_; + } + + const std::vector& networkFilterFactories() const override { + return empty_network_filter_factory_; + } + +private: + const TransportSocketFactoryPtr transport_socket_factory_; + const std::vector empty_network_filter_factory_{}; +}; + +/** + * Create an empty filter chain for testing purposes. + * @param transport_socket_factory transport socket factory to use when creating transport sockets. + * @return const Network::FilterChainSharedPtr filter chain. + */ +const Network::FilterChainSharedPtr +createEmptyFilterChain(TransportSocketFactoryPtr&& transport_socket_factory); + +/** + * Create an empty filter chain creating raw buffer sockets for testing purposes. + * @return const Network::FilterChainSharedPtr filter chain. + */ +const Network::FilterChainSharedPtr createEmptyFilterChainWithRawBufferSockets(); + } // namespace Test } // namespace Network } // namespace Envoy From e9996938db52db26620cfed605a104dd389864d5 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 16 May 2018 20:39:18 -0700 Subject: [PATCH 2/4] review: remove no longer necessary loop from the test code. Signed-off-by: Piotr Sikora --- test/common/ssl/ssl_socket_test.cc | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/test/common/ssl/ssl_socket_test.cc b/test/common/ssl/ssl_socket_test.cc index df2e258610642..5dcdf999cab5c 100644 --- a/test/common/ssl/ssl_socket_test.cc +++ b/test/common/ssl/ssl_socket_test.cc @@ -148,18 +148,12 @@ const std::string testUtilV2(const envoy::api::v2::Listener& server_proto, // SNI-based selection logic isn't happening in Ssl::SslSocket anymore. ASSERT(server_proto.filter_chains().size() == 1); - - std::vector server_transport_socket_factories; - for (const auto& filter_chain : server_proto.filter_chains()) { - if (filter_chain.has_tls_context()) { - std::vector sni_domains(filter_chain.filter_chain_match().sni_domains().begin(), - filter_chain.filter_chain_match().sni_domains().end()); - Ssl::ServerContextConfigImpl server_ctx_config(filter_chain.tls_context()); - server_transport_socket_factories.emplace_back( - new Ssl::ServerSslSocketFactory(server_ctx_config, manager, stats_store, sni_domains)); - } - } - ASSERT(server_transport_socket_factories.size() >= 1); + const auto& filter_chain = server_proto.filter_chains(0); + std::vector server_names(filter_chain.filter_chain_match().sni_domains().begin(), + filter_chain.filter_chain_match().sni_domains().end()); + Ssl::ServerContextConfigImpl server_ctx_config(filter_chain.tls_context()); + Ssl::ServerSslSocketFactory server_ssl_socket_factory(server_ctx_config, manager, stats_store, + server_names); Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(version), nullptr, @@ -192,7 +186,7 @@ const std::string testUtilV2(const envoy::api::v2::Listener& server_proto, EXPECT_CALL(callbacks, onAccept_(_, _)) .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket, bool) -> void { Network::ConnectionPtr new_connection = dispatcher.createServerConnection( - std::move(socket), server_transport_socket_factories[0]->createTransportSocket()); + std::move(socket), server_ssl_socket_factory.createTransportSocket()); callbacks.onNewConnection(std::move(new_connection)); })); EXPECT_CALL(callbacks, onNewConnection_(_)) From 104338a89fd5794b54a2320bb2de9f3087800c98 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 16 May 2018 18:04:46 -0700 Subject: [PATCH 3/4] review: clarify comment about the use of default transport sockets. Signed-off-by: Piotr Sikora --- source/server/listener_manager_impl.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 83d4a7a53c0aa..3a20c7ca40107 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -181,9 +181,9 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st std::vector server_names(filter_chain_match.sni_domains().begin(), filter_chain_match.sni_domains().end()); - // If the cluster doesn't have transport socke configured, override with default transport - // socket implementation based on tls_context. We copy by value first then override if - // neccessary. + // If the cluster doesn't have transport socket configured, then use the default "raw_buffer" + // transport socket or BoringSSL-based "tls" transport socket if TLS settings are configured. + // We copy by value first then override if necessary. auto transport_socket = filter_chain.transport_socket(); if (!filter_chain.has_transport_socket()) { if (filter_chain.has_tls_context()) { From f87d907167f37852a81d5db093508b3360c852ab Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 17 May 2018 10:36:30 -0700 Subject: [PATCH 4/4] fix one more merge conflict Signed-off-by: Greg Greenway --- .../filters/listener/tls_inspector/tls_inspector_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc b/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc index eb98eb8293aa1..a6a385fcf32c6 100644 --- a/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc +++ b/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc @@ -126,7 +126,7 @@ TEST_F(TlsInspectorTest, AlpnRegistered) { })); EXPECT_CALL(socket_, setRequestedServerName(_)).Times(0); EXPECT_CALL(socket_, setRequestedApplicationProtocols(alpn_protos)); - EXPECT_CALL(socket_, setDetectedTransportProtocol(absl::string_view("ssl"))); + EXPECT_CALL(socket_, setDetectedTransportProtocol(absl::string_view("tls"))); EXPECT_CALL(cb_, continueFilterChain(true)); file_event_callback_(Event::FileReadyType::Read); EXPECT_EQ(1, cfg_->stats().tls_found_.value());