diff --git a/api/envoy/config/listener/v3/listener_components.proto b/api/envoy/config/listener/v3/listener_components.proto index 6cbb1368b62d8..93acc4c94a666 100644 --- a/api/envoy/config/listener/v3/listener_components.proto +++ b/api/envoy/config/listener/v3/listener_components.proto @@ -64,9 +64,12 @@ message Filter { // 3. Server name (e.g. SNI for TLS protocol), // 4. Transport protocol. // 5. Application protocols (e.g. ALPN for TLS protocol). -// 6. Source type (e.g. any, local or external network). -// 7. Source IP address. -// 8. Source port. +// 6. Directly connected source IP address (this will only be different from the source IP address +// when using a listener filter that overrides the source address, such as the :ref:`Proxy Protocol +// listener filter `). +// 7. Source type (e.g. any, local or external network). +// 8. Source IP address. +// 9. Source port. // // For criteria that allow ranges or wildcards, the most specific value in any // of the configured filter chains that matches the incoming connection is going @@ -90,7 +93,7 @@ message Filter { // listed at the end, because that's how we want to list them in the docs. // // [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] -// [#next-free-field: 13] +// [#next-free-field: 14] message FilterChainMatch { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.listener.FilterChainMatch"; @@ -124,6 +127,11 @@ message FilterChainMatch { // [#not-implemented-hide:] google.protobuf.UInt32Value suffix_len = 5; + // The criteria is satisfied if the directly connected source IP address of the downstream + // connection is contained in at least one of the specified subnets. If the parameter is not + // specified or the list is empty, the directly connected source IP address is ignored. + repeated core.v3.CidrRange direct_source_prefix_ranges = 13; + // Specifies the connection source IP match type. Can be any, local or external network. ConnectionSourceType source_type = 12 [(validate.rules).enum = {defined_only: true}]; diff --git a/api/envoy/config/listener/v4alpha/listener_components.proto b/api/envoy/config/listener/v4alpha/listener_components.proto index 178faff7b2658..103fa23484f81 100644 --- a/api/envoy/config/listener/v4alpha/listener_components.proto +++ b/api/envoy/config/listener/v4alpha/listener_components.proto @@ -63,9 +63,12 @@ message Filter { // 3. Server name (e.g. SNI for TLS protocol), // 4. Transport protocol. // 5. Application protocols (e.g. ALPN for TLS protocol). -// 6. Source type (e.g. any, local or external network). -// 7. Source IP address. -// 8. Source port. +// 6. Directly connected source IP address (this will only be different from the source IP address +// when using a listener filter that overrides the source address, such as the :ref:`Proxy Protocol +// listener filter `). +// 7. Source type (e.g. any, local or external network). +// 8. Source IP address. +// 9. Source port. // // For criteria that allow ranges or wildcards, the most specific value in any // of the configured filter chains that matches the incoming connection is going @@ -89,7 +92,7 @@ message Filter { // listed at the end, because that's how we want to list them in the docs. // // [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] -// [#next-free-field: 13] +// [#next-free-field: 14] message FilterChainMatch { option (udpa.annotations.versioning).previous_message_type = "envoy.config.listener.v3.FilterChainMatch"; @@ -123,6 +126,11 @@ message FilterChainMatch { // [#not-implemented-hide:] google.protobuf.UInt32Value suffix_len = 5; + // The criteria is satisfied if the directly connected source IP address of the downstream + // connection is contained in at least one of the specified subnets. If the parameter is not + // specified or the list is empty, the directly connected source IP address is ignored. + repeated core.v4alpha.CidrRange direct_source_prefix_ranges = 13; + // Specifies the connection source IP match type. Can be any, local or external network. ConnectionSourceType source_type = 12 [(validate.rules).enum = {defined_only: true}]; diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 099b8b61817fb..a62f14ef5d7aa 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -107,6 +107,7 @@ New Features * input matcher: a new input matcher that :ref:`matches an IP address against a list of CIDR ranges `. * jwt_authn: added support to fetch remote jwks asynchronously specified by :ref:`async_fetch `. * listener: added ability to change an existing listener's address. +* listener: added filter chain match support for :ref:`direct source address `. * local_rate_limit_filter: added suppoort for locally rate limiting http requests on a per connection basis. This can be enabled by setting the :ref:`local_rate_limit_per_downstream_connection ` field to true. * metric service: added support for sending metric tags as labels. This can be enabled by setting the :ref:`emit_tags_as_labels ` field to true. * proxy protocol: added support for generating the header while using the :ref:`HTTP connection manager `. This is done using the using the :ref:`Proxy Protocol Transport Socket ` on upstream clusters. diff --git a/generated_api_shadow/envoy/config/listener/v3/listener_components.proto b/generated_api_shadow/envoy/config/listener/v3/listener_components.proto index ffb27810a97ad..5de8b265d8806 100644 --- a/generated_api_shadow/envoy/config/listener/v3/listener_components.proto +++ b/generated_api_shadow/envoy/config/listener/v3/listener_components.proto @@ -67,9 +67,12 @@ message Filter { // 3. Server name (e.g. SNI for TLS protocol), // 4. Transport protocol. // 5. Application protocols (e.g. ALPN for TLS protocol). -// 6. Source type (e.g. any, local or external network). -// 7. Source IP address. -// 8. Source port. +// 6. Directly connected source IP address (this will only be different from the source IP address +// when using a listener filter that overrides the source address, such as the :ref:`Proxy Protocol +// listener filter `). +// 7. Source type (e.g. any, local or external network). +// 8. Source IP address. +// 9. Source port. // // For criteria that allow ranges or wildcards, the most specific value in any // of the configured filter chains that matches the incoming connection is going @@ -93,7 +96,7 @@ message Filter { // listed at the end, because that's how we want to list them in the docs. // // [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] -// [#next-free-field: 13] +// [#next-free-field: 14] message FilterChainMatch { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.listener.FilterChainMatch"; @@ -127,6 +130,11 @@ message FilterChainMatch { // [#not-implemented-hide:] google.protobuf.UInt32Value suffix_len = 5; + // The criteria is satisfied if the directly connected source IP address of the downstream + // connection is contained in at least one of the specified subnets. If the parameter is not + // specified or the list is empty, the directly connected source IP address is ignored. + repeated core.v3.CidrRange direct_source_prefix_ranges = 13; + // Specifies the connection source IP match type. Can be any, local or external network. ConnectionSourceType source_type = 12 [(validate.rules).enum = {defined_only: true}]; diff --git a/generated_api_shadow/envoy/config/listener/v4alpha/listener_components.proto b/generated_api_shadow/envoy/config/listener/v4alpha/listener_components.proto index 752035d6feeeb..e4db4367d1c6a 100644 --- a/generated_api_shadow/envoy/config/listener/v4alpha/listener_components.proto +++ b/generated_api_shadow/envoy/config/listener/v4alpha/listener_components.proto @@ -64,9 +64,12 @@ message Filter { // 3. Server name (e.g. SNI for TLS protocol), // 4. Transport protocol. // 5. Application protocols (e.g. ALPN for TLS protocol). -// 6. Source type (e.g. any, local or external network). -// 7. Source IP address. -// 8. Source port. +// 6. Directly connected source IP address (this will only be different from the source IP address +// when using a listener filter that overrides the source address, such as the :ref:`Proxy Protocol +// listener filter `). +// 7. Source type (e.g. any, local or external network). +// 8. Source IP address. +// 9. Source port. // // For criteria that allow ranges or wildcards, the most specific value in any // of the configured filter chains that matches the incoming connection is going @@ -90,7 +93,7 @@ message Filter { // listed at the end, because that's how we want to list them in the docs. // // [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] -// [#next-free-field: 13] +// [#next-free-field: 14] message FilterChainMatch { option (udpa.annotations.versioning).previous_message_type = "envoy.config.listener.v3.FilterChainMatch"; @@ -124,6 +127,11 @@ message FilterChainMatch { // [#not-implemented-hide:] google.protobuf.UInt32Value suffix_len = 5; + // The criteria is satisfied if the directly connected source IP address of the downstream + // connection is contained in at least one of the specified subnets. If the parameter is not + // specified or the list is empty, the directly connected source IP address is ignored. + repeated core.v4alpha.CidrRange direct_source_prefix_ranges = 13; + // Specifies the connection source IP match type. Can be any, local or external network. ConnectionSourceType source_type = 12 [(validate.rules).enum = {defined_only: true}]; diff --git a/source/server/filter_chain_manager_impl.cc b/source/server/filter_chain_manager_impl.cc index 427b3cb0eeef8..095d16eb6b505 100644 --- a/source/server/filter_chain_manager_impl.cc +++ b/source/server/filter_chain_manager_impl.cc @@ -19,7 +19,9 @@ namespace Server { namespace { -// Return a fake address for use when either the source or destination is UDS. +// Return a fake address for use when either the source or destination is unix domain socket. +// This address will only match the fallback matcher of 0.0.0.0/0, which is the default +// when no IP matcher is configured. Network::Address::InstanceConstSharedPtr fakeAddress() { CONSTRUCT_ON_FIRST_USE(Network::Address::InstanceConstSharedPtr, Network::Utility::parseInternetAddress("255.255.255.255")); @@ -174,20 +176,23 @@ void FilterChainManagerImpl::addFilterChains( } filter_chains.insert({filter_chain_match, filter_chain->name()}); - // Validate IP addresses. - std::vector destination_ips; - destination_ips.reserve(filter_chain_match.prefix_ranges().size()); - for (const auto& destination_ip : filter_chain_match.prefix_ranges()) { - const auto& cidr_range = Network::Address::CidrRange::create(destination_ip); - destination_ips.push_back(cidr_range.asString()); - } + auto createAddressVector = [](const auto& prefix_ranges) -> std::vector { + std::vector ips; + ips.reserve(prefix_ranges.size()); + for (const auto& ip : prefix_ranges) { + const auto& cidr_range = Network::Address::CidrRange::create(ip); + ips.push_back(cidr_range.asString()); + } + return ips; + }; - std::vector source_ips; - source_ips.reserve(filter_chain_match.source_prefix_ranges().size()); - for (const auto& source_ip : filter_chain_match.source_prefix_ranges()) { - const auto& cidr_range = Network::Address::CidrRange::create(source_ip); - source_ips.push_back(cidr_range.asString()); - } + // Validate IP addresses. + std::vector destination_ips = + createAddressVector(filter_chain_match.prefix_ranges()); + std::vector source_ips = + createAddressVector(filter_chain_match.source_prefix_ranges()); + std::vector direct_source_ips = + createAddressVector(filter_chain_match.direct_source_prefix_ranges()); std::vector server_names; // Reject partial wildcards, we don't match on them. @@ -215,8 +220,9 @@ void FilterChainManagerImpl::addFilterChains( destination_ports_map_, PROTOBUF_GET_WRAPPED_OR_DEFAULT(filter_chain_match, destination_port, 0), destination_ips, server_names, filter_chain_match.transport_protocol(), - filter_chain_match.application_protocols(), filter_chain_match.source_type(), source_ips, - filter_chain_match.source_ports(), filter_chain_impl); + filter_chain_match.application_protocols(), direct_source_ips, + filter_chain_match.source_type(), source_ips, filter_chain_match.source_ports(), + filter_chain_impl); fc_contexts_[*filter_chain] = filter_chain_impl; } @@ -265,6 +271,7 @@ void FilterChainManagerImpl::addFilterChainForDestinationPorts( const std::vector& destination_ips, const absl::Span server_names, const std::string& transport_protocol, const absl::Span application_protocols, + const std::vector& direct_source_ips, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, @@ -275,26 +282,28 @@ void FilterChainManagerImpl::addFilterChainForDestinationPorts( } addFilterChainForDestinationIPs(destination_ports_map[destination_port].first, destination_ips, server_names, transport_protocol, application_protocols, - source_type, source_ips, source_ports, filter_chain); + direct_source_ips, source_type, source_ips, source_ports, + filter_chain); } void FilterChainManagerImpl::addFilterChainForDestinationIPs( DestinationIPsMap& destination_ips_map, const std::vector& destination_ips, const absl::Span server_names, const std::string& transport_protocol, const absl::Span application_protocols, + const std::vector& direct_source_ips, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, const Network::FilterChainSharedPtr& filter_chain) { if (destination_ips.empty()) { addFilterChainForServerNames(destination_ips_map[EMPTY_STRING], server_names, - transport_protocol, application_protocols, source_type, source_ips, - source_ports, filter_chain); + transport_protocol, application_protocols, direct_source_ips, + source_type, source_ips, source_ports, filter_chain); } else { for (const auto& destination_ip : destination_ips) { addFilterChainForServerNames(destination_ips_map[destination_ip], server_names, - transport_protocol, application_protocols, source_type, - source_ips, source_ports, filter_chain); + transport_protocol, application_protocols, direct_source_ips, + source_type, source_ips, source_ports, filter_chain); } } } @@ -303,6 +312,7 @@ void FilterChainManagerImpl::addFilterChainForServerNames( ServerNamesMapSharedPtr& server_names_map_ptr, const absl::Span server_names, const std::string& transport_protocol, const absl::Span application_protocols, + const std::vector& direct_source_ips, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, @@ -314,19 +324,19 @@ void FilterChainManagerImpl::addFilterChainForServerNames( if (server_names.empty()) { addFilterChainForApplicationProtocols(server_names_map[EMPTY_STRING][transport_protocol], - application_protocols, source_type, source_ips, - source_ports, filter_chain); + application_protocols, direct_source_ips, source_type, + source_ips, source_ports, 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". addFilterChainForApplicationProtocols( server_names_map[server_name.substr(1)][transport_protocol], application_protocols, - source_type, source_ips, source_ports, filter_chain); + direct_source_ips, source_type, source_ips, source_ports, filter_chain); } else { addFilterChainForApplicationProtocols(server_names_map[server_name][transport_protocol], - application_protocols, source_type, source_ips, - source_ports, filter_chain); + application_protocols, direct_source_ips, source_type, + source_ips, source_ports, filter_chain); } } } @@ -335,27 +345,52 @@ void FilterChainManagerImpl::addFilterChainForServerNames( void FilterChainManagerImpl::addFilterChainForApplicationProtocols( ApplicationProtocolsMap& application_protocols_map, const absl::Span application_protocols, + const std::vector& direct_source_ips, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, const Network::FilterChainSharedPtr& filter_chain) { if (application_protocols.empty()) { - addFilterChainForSourceTypes(application_protocols_map[EMPTY_STRING], source_type, source_ips, - source_ports, filter_chain); + addFilterChainForDirectSourceIPs(application_protocols_map[EMPTY_STRING].first, + direct_source_ips, source_type, source_ips, source_ports, + filter_chain); } else { for (const auto& application_protocol_ptr : application_protocols) { - addFilterChainForSourceTypes(application_protocols_map[*application_protocol_ptr], - source_type, source_ips, source_ports, filter_chain); + addFilterChainForDirectSourceIPs(application_protocols_map[*application_protocol_ptr].first, + direct_source_ips, source_type, source_ips, source_ports, + filter_chain); + } + } +} + +void FilterChainManagerImpl::addFilterChainForDirectSourceIPs( + DirectSourceIPsMap& direct_source_ips_map, const std::vector& direct_source_ips, + const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, + const std::vector& source_ips, + const absl::Span source_ports, + const Network::FilterChainSharedPtr& filter_chain) { + if (direct_source_ips.empty()) { + addFilterChainForSourceTypes(direct_source_ips_map[EMPTY_STRING], source_type, source_ips, + source_ports, filter_chain); + } else { + for (const auto& direct_source_ip : direct_source_ips) { + addFilterChainForSourceTypes(direct_source_ips_map[direct_source_ip], source_type, source_ips, + source_ports, filter_chain); } } } void FilterChainManagerImpl::addFilterChainForSourceTypes( - SourceTypesArray& source_types_array, + SourceTypesArraySharedPtr& source_types_array_ptr, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, const Network::FilterChainSharedPtr& filter_chain) { + if (source_types_array_ptr == nullptr) { + source_types_array_ptr = std::make_shared(); + } + + SourceTypesArray& source_types_array = *source_types_array_ptr; if (source_ips.empty()) { addFilterChainForSourceIPs(source_types_array[source_type].first, EMPTY_STRING, source_ports, filter_chain); @@ -527,14 +562,31 @@ const Network::FilterChain* FilterChainManagerImpl::findFilterChainForApplicatio for (const auto& application_protocol : socket.requestedApplicationProtocols()) { const auto application_protocol_match = application_protocols_map.find(application_protocol); if (application_protocol_match != application_protocols_map.end()) { - return findFilterChainForSourceTypes(application_protocol_match->second, socket); + return findFilterChainForDirectSourceIP(*application_protocol_match->second.second, socket); } } // Match on a filter chain without application protocol requirements. const auto any_protocol_match = application_protocols_map.find(EMPTY_STRING); if (any_protocol_match != application_protocols_map.end()) { - return findFilterChainForSourceTypes(any_protocol_match->second, socket); + return findFilterChainForDirectSourceIP(*any_protocol_match->second.second, socket); + } + + return nullptr; +} + +const Network::FilterChain* FilterChainManagerImpl::findFilterChainForDirectSourceIP( + const DirectSourceIPsTrie& direct_source_ips_trie, + const Network::ConnectionSocket& socket) const { + auto address = socket.addressProvider().directRemoteAddress(); + if (address->type() != Network::Address::Type::Ip) { + address = fakeAddress(); + } + + const auto& data = direct_source_ips_trie.getData(address); + if (!data.empty()) { + ASSERT(data.size() == 1); + return findFilterChainForSourceTypes(*data.back(), socket); } return nullptr; @@ -627,20 +679,34 @@ void FilterChainManagerImpl::convertIPsToTries() { UNREFERENCED_PARAMETER(server_name); for (auto& [transport_protocol, application_protocols_map] : transport_protocols_map) { UNREFERENCED_PARAMETER(transport_protocol); - for (auto& [application_protocol, source_arrays] : application_protocols_map) { + for (auto& [application_protocol, direct_source_ips_pair] : application_protocols_map) { UNREFERENCED_PARAMETER(application_protocol); - for (auto& [source_ips_map, source_ips_trie] : source_arrays) { - std::vector< - std::pair>> - source_ips_list; - source_ips_list.reserve(source_ips_map.size()); - - for (auto& [source_ip, source_port_map_ptr] : source_ips_map) { - source_ips_list.push_back(makeCidrListEntry(source_ip, source_port_map_ptr)); - } + auto& [direct_source_ips_map, direct_source_ips_trie] = direct_source_ips_pair; + + std::vector< + std::pair>> + direct_source_ips_list; + direct_source_ips_list.reserve(direct_source_ips_map.size()); - source_ips_trie = std::make_unique(source_ips_list, true); + for (auto& [direct_source_ip, source_arrays_ptr] : direct_source_ips_map) { + direct_source_ips_list.push_back( + makeCidrListEntry(direct_source_ip, source_arrays_ptr)); + + for (auto& [source_ips_map, source_ips_trie] : *source_arrays_ptr) { + std::vector< + std::pair>> + source_ips_list; + source_ips_list.reserve(source_ips_map.size()); + + for (auto& [source_ip, source_port_map_ptr] : source_ips_map) { + source_ips_list.push_back(makeCidrListEntry(source_ip, source_port_map_ptr)); + } + + source_ips_trie = std::make_unique(source_ips_list, true); + } } + direct_source_ips_trie = + std::make_unique(direct_source_ips_list, true); } } } diff --git a/source/server/filter_chain_manager_impl.h b/source/server/filter_chain_manager_impl.h index 92b429473bb3e..4e6caf625dbf9 100644 --- a/source/server/filter_chain_manager_impl.h +++ b/source/server/filter_chain_manager_impl.h @@ -240,7 +240,20 @@ class FilterChainManagerImpl : public Network::FilterChainManager, using SourceIPsTrie = Network::LcTrie::LcTrie; using SourceIPsTriePtr = std::unique_ptr; using SourceTypesArray = std::array, 3>; - using ApplicationProtocolsMap = absl::flat_hash_map; + using SourceTypesArraySharedPtr = std::shared_ptr; + using DirectSourceIPsMap = absl::flat_hash_map; + using DirectSourceIPsTrie = Network::LcTrie::LcTrie; + using DirectSourceIPsTriePtr = std::unique_ptr; + + // This would nominally be a `std::pair`, but that version crashes the Windows clang_cl compiler + // for unknown reasons. This variation, which is equivalent, does not crash the compiler. + // The `std::pair` version was confirmed to crash both clang 11 and clang 12. + struct DirectSourceIPsPair { + DirectSourceIPsMap first; + DirectSourceIPsTriePtr second; + }; + + using ApplicationProtocolsMap = absl::flat_hash_map; using TransportProtocolsMap = absl::flat_hash_map; // 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 @@ -258,6 +271,7 @@ class FilterChainManagerImpl : public Network::FilterChainManager, const std::vector& destination_ips, const absl::Span server_names, const std::string& transport_protocol, const absl::Span application_protocols, + const std::vector& direct_source_ips, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, @@ -266,6 +280,7 @@ class FilterChainManagerImpl : public Network::FilterChainManager, DestinationIPsMap& destination_ips_map, const std::vector& destination_ips, const absl::Span server_names, const std::string& transport_protocol, const absl::Span application_protocols, + const std::vector& direct_source_ips, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, @@ -274,6 +289,7 @@ class FilterChainManagerImpl : public Network::FilterChainManager, ServerNamesMapSharedPtr& server_names_map_ptr, const absl::Span server_names, const std::string& transport_protocol, const absl::Span application_protocols, + const std::vector& direct_source_ips, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, @@ -281,12 +297,19 @@ class FilterChainManagerImpl : public Network::FilterChainManager, void addFilterChainForApplicationProtocols( ApplicationProtocolsMap& application_protocol_map, const absl::Span application_protocols, + const std::vector& direct_source_ips, + const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, + const std::vector& source_ips, + const absl::Span source_ports, + const Network::FilterChainSharedPtr& filter_chain); + void addFilterChainForDirectSourceIPs( + DirectSourceIPsMap& direct_source_ips_map, const std::vector& direct_source_ips, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, const Network::FilterChainSharedPtr& filter_chain); void addFilterChainForSourceTypes( - SourceTypesArray& source_types_array, + SourceTypesArraySharedPtr& source_types_array_ptr, const envoy::config::listener::v3::FilterChainMatch::ConnectionSourceType source_type, const std::vector& source_ips, const absl::Span source_ports, @@ -311,6 +334,9 @@ class FilterChainManagerImpl : public Network::FilterChainManager, findFilterChainForApplicationProtocols(const ApplicationProtocolsMap& application_protocols_map, const Network::ConnectionSocket& socket) const; const Network::FilterChain* + findFilterChainForDirectSourceIP(const DirectSourceIPsTrie& direct_source_ips_trie, + const Network::ConnectionSocket& socket) const; + const Network::FilterChain* findFilterChainForSourceTypes(const SourceTypesArray& source_types, const Network::ConnectionSocket& socket) const; diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 43fa0b6d9add1..9421ea9f1901d 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -428,15 +428,16 @@ size_t entryIndex(const std::string& file, uint32_t entry) { std::string BaseIntegrationTest::waitForAccessLog(const std::string& filename, uint32_t entry) { // Wait a max of 1s for logs to flush to disk. + std::string contents; for (int i = 0; i < 1000; ++i) { - std::string contents = TestEnvironment::readFileToStringForTest(filename); + contents = TestEnvironment::readFileToStringForTest(filename); size_t index = entryIndex(contents, entry); if (contents.length() > index) { return contents.substr(index); } absl::SleepFor(absl::Milliseconds(1)); } - RELEASE_ASSERT(0, "Timed out waiting for access log"); + RELEASE_ASSERT(0, absl::StrCat("Timed out waiting for access log. Found: ", contents)); return ""; } diff --git a/test/integration/proxy_proto_integration_test.cc b/test/integration/proxy_proto_integration_test.cc index dc683f6772050..83d983edbc069 100644 --- a/test/integration/proxy_proto_integration_test.cc +++ b/test/integration/proxy_proto_integration_test.cc @@ -275,4 +275,117 @@ TEST_P(ProxyProtoTcpIntegrationTest, AccessLog) { EXPECT_EQ(log_result, "remote=1.2.3.4:12345 local=254.254.254.254:1234"); } +ProxyProtoFilterChainMatchIntegrationTest::ProxyProtoFilterChainMatchIntegrationTest() { + useListenerAccessLog("%FILTER_CHAIN_NAME% %RESPONSE_CODE_DETAILS%"); + config_helper_.skipPortUsageValidation(); + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + // This test doesn't need to deal with upstream connections at all, so make sure none occur. + bootstrap.mutable_static_resources()->mutable_clusters(0)->clear_load_assignment(); + + auto* orig_filter_chain = + bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); + for (unsigned i = 0; i < 3; i++) { + *bootstrap.mutable_static_resources()->mutable_listeners(0)->add_filter_chains() = + *orig_filter_chain; + } + + auto setPrefix = [](auto* prefix, const std::string& ip, uint32_t length) { + prefix->set_address_prefix(ip); + prefix->mutable_prefix_len()->set_value(length); + }; + + { + auto* filter_chain = + bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); + filter_chain->set_name("directsource_localhost_and_source_1.2.3.0/24"); + + setPrefix(filter_chain->mutable_filter_chain_match()->add_direct_source_prefix_ranges(), + "127.0.0.1", 8); + setPrefix(filter_chain->mutable_filter_chain_match()->add_direct_source_prefix_ranges(), + "::1", 128); + + setPrefix(filter_chain->mutable_filter_chain_match()->add_source_prefix_ranges(), "1.2.3.0", + 24); + } + + { + auto* filter_chain = + bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(1); + filter_chain->set_name("wrong_directsource_and_source_1.2.3.0/24"); + setPrefix(filter_chain->mutable_filter_chain_match()->add_direct_source_prefix_ranges(), + "1.1.1.1", 32); + setPrefix(filter_chain->mutable_filter_chain_match()->add_direct_source_prefix_ranges(), + "eeee::1", 128); + + setPrefix(filter_chain->mutable_filter_chain_match()->add_source_prefix_ranges(), "1.2.3.0", + 24); + } + + { + auto* filter_chain = + bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(2); + filter_chain->set_name("wrong_directsource_and_source_5.5.5.5.32"); + setPrefix(filter_chain->mutable_filter_chain_match()->add_direct_source_prefix_ranges(), + "1.1.1.1", 32); + setPrefix(filter_chain->mutable_filter_chain_match()->add_direct_source_prefix_ranges(), + "eeee::1", 128); + + setPrefix(filter_chain->mutable_filter_chain_match()->add_source_prefix_ranges(), "5.5.5.5", + 32); + } + + { + auto* filter_chain = + bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(3); + filter_chain->set_name("no_direct_source_and_source_6.6.6.6/32"); + + setPrefix(filter_chain->mutable_filter_chain_match()->add_source_prefix_ranges(), "6.6.6.6", + 32); + } + }); +} + +INSTANTIATE_TEST_SUITE_P(IpVersions, ProxyProtoFilterChainMatchIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +// Validate that source IP and direct source IP match correctly. +TEST_P(ProxyProtoFilterChainMatchIntegrationTest, MatchDirectSourceAndSource) { + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(tcp_client->write("PROXY TCP4 1.2.3.4 254.254.254.254 12345 1234\r\nhello", false)); + tcp_client->waitForDisconnect(); + + EXPECT_THAT(waitForAccessLog(listener_access_log_name_), + testing::HasSubstr("directsource_localhost_and_source_1.2.3.0/24 -")); +} + +// Test that a mismatched direct source prevents matching a filter chain with a matching source. +TEST_P(ProxyProtoFilterChainMatchIntegrationTest, MismatchDirectSourceButMatchSource) { + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(tcp_client->write("PROXY TCP4 5.5.5.5 254.254.254.254 12345 1234\r\nhello", false)); + tcp_client->waitForDisconnect(); + + EXPECT_THAT(waitForAccessLog(listener_access_log_name_), + testing::HasSubstr( + absl::StrCat("- ", StreamInfo::ResponseCodeDetails::get().FilterChainNotFound))); +} + +// Test that a more specific direct source match prevents matching a filter chain with a less +// specific direct source match but matching source. +TEST_P(ProxyProtoFilterChainMatchIntegrationTest, MoreSpecificDirectSource) { + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(tcp_client->write("PROXY TCP4 6.6.6.6 254.254.254.254 12345 1234\r\nhello", false)); + tcp_client->waitForDisconnect(); + + EXPECT_THAT(waitForAccessLog(listener_access_log_name_), + testing::HasSubstr( + absl::StrCat("- ", StreamInfo::ResponseCodeDetails::get().FilterChainNotFound))); +} + } // namespace Envoy diff --git a/test/integration/proxy_proto_integration_test.h b/test/integration/proxy_proto_integration_test.h index cfce0d5534616..224b6b168eb82 100644 --- a/test/integration/proxy_proto_integration_test.h +++ b/test/integration/proxy_proto_integration_test.h @@ -24,4 +24,9 @@ class ProxyProtoTcpIntegrationTest : public testing::TestWithParam address_provider_; bool is_closed_; }; diff --git a/test/server/filter_chain_benchmark_test.cc b/test/server/filter_chain_benchmark_test.cc index 8ff59db408b2a..89569264633b0 100644 --- a/test/server/filter_chain_benchmark_test.cc +++ b/test/server/filter_chain_benchmark_test.cc @@ -61,6 +61,8 @@ class MockConnectionSocket : public Network::ConnectionSocket { } else { res->address_provider_->setRemoteAddress( Network::Utility::parseInternetAddress(source_address, source_port)); + res->address_provider_->setDirectRemoteAddressForTest( + Network::Utility::parseInternetAddress(source_address, source_port)); } res->server_name_ = server_name; res->transport_protocol_ = transport_protocol; @@ -124,7 +126,7 @@ class MockConnectionSocket : public Network::ConnectionSocket { private: Network::IoHandlePtr io_handle_; OptionsSharedPtr options_; - Network::SocketAddressSetterSharedPtr address_provider_; + std::shared_ptr address_provider_; std::string server_name_; std::string transport_protocol_; std::vector application_protocols_; diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 489bca559ace7..45c503f846f73 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -2407,6 +2407,52 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDestinationP EXPECT_EQ(filter_chain, nullptr); } +TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDirectSourceIPMatch) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: 127.0.0.1, port_value: 1234 } + listener_filters: + - name: "envoy.filters.listener.tls_inspector" + typed_config: {} + filter_chains: + - filter_chain_match: + direct_source_prefix_ranges: { address_prefix: 127.0.0.0, prefix_len: 8 } + transport_socket: + name: tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } + private_key: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_key.pem" } + )EOF", + Network::Address::IpVersion::v4); + + EXPECT_CALL(server_.api_.random_, uuid()); + EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, {true})); + manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true); + EXPECT_EQ(1U, manager_->listeners().size()); + + // IPv4 client connects to unknown IP - no match. + auto filter_chain = findFilterChain(1234, "1.2.3.4", "", "tls", {}, "8.8.8.8", 111, "1.2.3.4"); + EXPECT_EQ(filter_chain, nullptr); + + // IPv4 client connects to valid IP - using 1st filter chain. + filter_chain = findFilterChain(1234, "1.2.3.4", "", "tls", {}, "8.8.8.8", 111, "127.0.0.1"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); + auto ssl_socket = + dynamic_cast(transport_socket.get()); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); + EXPECT_EQ(server_names.size(), 1); + EXPECT_EQ(server_names.front(), "server1.example.com"); + + // UDS client - no match. + filter_chain = findFilterChain(0, "/tmp/test.sock", "", "tls", {}, "/tmp/test.sock", 111); + EXPECT_EQ(filter_chain, nullptr); +} + TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDestinationIPMatch) { const std::string yaml = TestEnvironment::substitute(R"EOF( address: @@ -3004,7 +3050,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true); EXPECT_EQ(1U, manager_->listeners().size()); - // IPv4 client connects to default IP - using 1st filter chain. + // UDS client connects - using 1st filter chain with no IP match auto filter_chain = findFilterChain(1234, "127.0.0.1", "", "tls", {}, "127.0.0.1", 111); ASSERT_NE(filter_chain, nullptr); EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); @@ -3014,6 +3060,15 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati auto uri = ssl_socket->ssl()->uriSanLocalCertificate(); EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); + // IPv4 client connects to default IP - using 1st filter chain. + filter_chain = findFilterChain(1234, "127.0.0.1", "", "tls", {}, "127.0.0.1", 111); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); + ssl_socket = dynamic_cast(transport_socket.get()); + uri = ssl_socket->ssl()->uriSanLocalCertificate(); + EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); + // IPv4 client connects to exact IP match - using 2nd filter chain. filter_chain = findFilterChain(1234, "192.168.0.1", "", "tls", {}, "127.0.0.1", 111); ASSERT_NE(filter_chain, nullptr); @@ -3044,6 +3099,92 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); } +TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDirectSourceIPMatch) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: 127.0.0.1, port_value: 1234 } + listener_filters: + - name: "envoy.filters.listener.tls_inspector" + typed_config: {} + filter_chains: + - filter_chain_match: + # empty + transport_socket: + name: tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" } + private_key: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem" } + - filter_chain_match: + direct_source_prefix_ranges: { address_prefix: 192.168.0.1, prefix_len: 32 } + transport_socket: + name: tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } + private_key: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_key.pem" } + - filter_chain_match: + direct_source_prefix_ranges: { address_prefix: 192.168.0.0, prefix_len: 16 } + transport_socket: + name: tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } + private_key: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_key.pem" } + )EOF", + Network::Address::IpVersion::v4); + + EXPECT_CALL(server_.api_.random_, uuid()); + EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, {true})); + manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true); + EXPECT_EQ(1U, manager_->listeners().size()); + + // UDS client connects - using 1st filter chain with no IP match + auto filter_chain = findFilterChain(1234, "/uds_1", "", "tls", {}, "/uds_2", 111, "/uds_3"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); + auto ssl_socket = + dynamic_cast(transport_socket.get()); + auto uri = ssl_socket->ssl()->uriSanLocalCertificate(); + EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); + + // IPv4 client connects to default IP - using 1st filter chain. + filter_chain = findFilterChain(1234, "127.0.0.1", "", "tls", {}, "127.0.0.1", 111, "127.0.0.1"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); + ssl_socket = dynamic_cast(transport_socket.get()); + uri = ssl_socket->ssl()->uriSanLocalCertificate(); + EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); + + // IPv4 client connects to exact IP match - using 2nd filter chain. + filter_chain = findFilterChain(1234, "127.0.0.1", "", "tls", {}, "127.0.0.1", 111, "192.168.0.1"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); + ssl_socket = dynamic_cast(transport_socket.get()); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); + EXPECT_EQ(server_names.size(), 1); + EXPECT_EQ(server_names.front(), "server1.example.com"); + + // IPv4 client connects to wildcard IP match - using 3rd filter chain. + filter_chain = findFilterChain(1234, "127.0.0.1", "", "tls", {}, "127.0.0.1", 111, "192.168.1.1"); + ASSERT_NE(filter_chain, nullptr); + EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); + transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); + ssl_socket = dynamic_cast(transport_socket.get()); + server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); + EXPECT_EQ(server_names.size(), 2); + EXPECT_EQ(server_names.front(), "*.example.com"); +} + TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNamesMatch) { const std::string yaml = TestEnvironment::substitute(R"EOF( address: diff --git a/test/server/listener_manager_impl_test.h b/test/server/listener_manager_impl_test.h index 26b19f70e672d..48d1610a05658 100644 --- a/test/server/listener_manager_impl_test.h +++ b/test/server/listener_manager_impl_test.h @@ -175,7 +175,8 @@ class ListenerManagerImplTest : public testing::Test { findFilterChain(uint16_t destination_port, const std::string& destination_address, const std::string& server_name, const std::string& transport_protocol, const std::vector& application_protocols, - const std::string& source_address, uint16_t source_port) { + const std::string& source_address, uint16_t source_port, + std::string direct_source_address = "") { if (absl::StartsWith(destination_address, "/")) { local_address_ = std::make_shared(destination_address); } else { @@ -197,6 +198,18 @@ class ListenerManagerImplTest : public testing::Test { } socket_->address_provider_->setRemoteAddress(remote_address_); + if (direct_source_address.empty()) { + direct_source_address = source_address; + } + if (absl::StartsWith(direct_source_address, "/")) { + direct_remote_address_ = + std::make_shared(direct_source_address); + } else { + direct_remote_address_ = + Network::Utility::parseInternetAddress(direct_source_address, source_port); + } + socket_->address_provider_->setDirectRemoteAddressForTest(direct_remote_address_); + return manager_->listeners().back().get().filterChainManager().findFilterChain(*socket_); } @@ -294,6 +307,7 @@ class ListenerManagerImplTest : public testing::Test { Api::ApiPtr api_; Network::Address::InstanceConstSharedPtr local_address_; Network::Address::InstanceConstSharedPtr remote_address_; + Network::Address::InstanceConstSharedPtr direct_remote_address_; std::unique_ptr socket_; uint64_t listener_tag_{1}; bool enable_dispatcher_stats_{false};