From 204211a07a734d3b9e7c6f6d855b56cd893c41ad Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Fri, 8 Nov 2024 11:26:47 +0000 Subject: [PATCH] tls_wrapper: Use raw socket with non-TLS policy [ upstream commit c35cf767a838c41c0363279d5939593e4fd1fc4e ] Cilium tls_wrapper should allow raw socket to be used if policy allows without TLS context. Also check for SNI when getting TLS context so that the one matching the SNI is used if multiple are available. Add policy tests validating the policy functionality. Signed-off-by: Jarno Rajahalme Signed-off-by: Tam Mach --- cilium/bpf_metadata.cc | 12 +- cilium/network_filter.cc | 15 +- cilium/network_policy.cc | 65 +++-- cilium/network_policy.h | 21 +- cilium/socket_option.h | 10 +- cilium/tls_wrapper.cc | 32 ++- cilium/tls_wrapper.h | 16 -- tests/bpf_metadata.cc | 2 +- tests/cilium_network_policy_test.cc | 367 +++++++++++++++++++++++++++- 9 files changed, 462 insertions(+), 78 deletions(-) diff --git a/cilium/bpf_metadata.cc b/cilium/bpf_metadata.cc index b3063c84b..1e2f1cfd5 100644 --- a/cilium/bpf_metadata.cc +++ b/cilium/bpf_metadata.cc @@ -281,6 +281,7 @@ Cilium::SocketOptionSharedPtr Config::getMetadata(Network::ConnectionSocket& soc const auto sip = src_address->ip(); const auto dst_address = socket.ioHandle().localAddress(); const auto dip = dst_address->ip(); + auto sni = socket.requestedServerName(); if (!sip || !dip) { ENVOY_LOG_MISC(debug, "Non-IP addresses: src: {} dst: {}", src_address->asString(), @@ -296,11 +297,12 @@ Cilium::SocketOptionSharedPtr Config::getMetadata(Network::ConnectionSocket& soc if (is_ingress_) { pod_ip = dip->addressAsString(); other_ip = sip->addressAsString(); - ENVOY_LOG_MISC(debug, "INGRESS POD IP: {}, source IP: {}", pod_ip, other_ip); + ENVOY_LOG_MISC(debug, "INGRESS POD IP: {}, source IP: {}, sni: \"{}\"", pod_ip, other_ip, sni); } else { pod_ip = sip->addressAsString(); other_ip = dip->addressAsString(); - ENVOY_LOG_MISC(debug, "EGRESS POD IP: {}, destination IP: {}", pod_ip, other_ip); + ENVOY_LOG_MISC(debug, "EGRESS POD IP: {}, destination IP: {} sni: \"{}\"", pod_ip, other_ip, + sni); } // Load the policy for the Pod that sends or receives traffic. @@ -402,8 +404,8 @@ Cilium::SocketOptionSharedPtr Config::getMetadata(Network::ConnectionSocket& soc // policy must exist at this point if (policy == nullptr) { - ENVOY_LOG(warn, "cilium.bpf_metadata ({}): No policy found for {}", - is_ingress_ ? "ingress" : "egress", pod_ip); + ENVOY_LOG(warn, "cilium.bpf_metadata ({}): No policy found for {} sni: \"{}\"", + is_ingress_ ? "ingress" : "egress", pod_ip, sni); return nullptr; } @@ -443,7 +445,7 @@ Cilium::SocketOptionSharedPtr Config::getMetadata(Network::ConnectionSocket& soc return std::make_shared( policy, mark, ingress_source_identity, source_identity, is_ingress_, is_l7lb_, dip->port(), std::move(pod_ip), std::move(src_address), std::move(source_addresses.ipv4_), - std::move(source_addresses.ipv6_), shared_from_this(), proxy_id_); + std::move(source_addresses.ipv6_), shared_from_this(), proxy_id_, sni); } Network::FilterStatus Instance::onAccept(Network::ListenerFilterCallbacks& cb) { diff --git a/cilium/network_filter.cc b/cilium/network_filter.cc index 8dc8262a2..e27b5f3f9 100644 --- a/cilium/network_filter.cc +++ b/cilium/network_filter.cc @@ -147,9 +147,10 @@ Network::FilterStatus Instance::onNewConnection() { if (option->ingress_source_identity_ != 0) { auto ingress_port_policy = option->initial_policy_->findPortPolicy(true, destination_port_); if (!ingress_port_policy.allowed(option->ingress_source_identity_, sni)) { - ENVOY_CONN_LOG(debug, - "cilium.network: ingress policy drop for source identity: {} port: {}", - conn, option->ingress_source_identity_, destination_port_); + ENVOY_CONN_LOG( + debug, + "cilium.network: ingress policy DROP for source identity: {} port: {} sni: \"{}\"", + conn, option->ingress_source_identity_, destination_port_, sni); return false; } } @@ -160,9 +161,13 @@ Network::FilterStatus Instance::onNewConnection() { remote_id_ = option->ingress_ ? option->identity_ : destination_identity; if (!port_policy.allowed(remote_id_, sni)) { // Connection not allowed by policy - ENVOY_CONN_LOG(warn, "cilium.network: Policy DENY on id: {} port: {}", conn, remote_id_, - destination_port_); + ENVOY_CONN_LOG(debug, "cilium.network: Policy DENY on id: {} port: {} sni: \"{}\"", conn, + remote_id_, destination_port_, sni); return false; + } else { + // Connection allowed by policy + ENVOY_CONN_LOG(debug, "cilium.network: Policy ALLOW on id: {} port: {} sni: \"{}\"", conn, + remote_id_, destination_port_, sni); } const std::string& policy_name = option->pod_ip_; diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index 781642298..4512c6c5d 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -486,22 +486,30 @@ class PortNetworkPolicyRule : public Logger::Loggable { return true; // allowed by default } - Ssl::ContextSharedPtr getServerTlsContext(uint32_t remote_id, - const Ssl::ContextConfig** config) const { + Ssl::ContextSharedPtr getServerTlsContext(uint32_t remote_id, absl::string_view sni, + const Ssl::ContextConfig** config, + bool& raw_socket_allowed) const { bool denied = false; - if (server_context_ && allowed(remote_id, denied)) { - *config = &server_context_->getTlsContextConfig(); - return server_context_->getTlsContext(); + if (allowed(remote_id, sni, denied)) { + if (server_context_) { + *config = &server_context_->getTlsContextConfig(); + return server_context_->getTlsContext(); + } + raw_socket_allowed = true; } return nullptr; } - Ssl::ContextSharedPtr getClientTlsContext(uint32_t remote_id, - const Ssl::ContextConfig** config) const { + Ssl::ContextSharedPtr getClientTlsContext(uint32_t remote_id, absl::string_view sni, + const Ssl::ContextConfig** config, + bool& raw_socket_allowed) const { bool denied = false; - if (client_context_ && allowed(remote_id, denied)) { - *config = &client_context_->getTlsContextConfig(); - return client_context_->getTlsContext(); + if (allowed(remote_id, sni, denied)) { + if (client_context_) { + *config = &client_context_->getTlsContextConfig(); + return client_context_->getTlsContext(); + } + raw_socket_allowed = true; } return nullptr; } @@ -668,20 +676,24 @@ class PortNetworkPolicyRules : public Logger::Loggable { return allowed && !denied; } - Ssl::ContextSharedPtr getServerTlsContext(uint32_t remote_id, - const Ssl::ContextConfig** config) const { + Ssl::ContextSharedPtr getServerTlsContext(uint32_t remote_id, absl::string_view sni, + const Ssl::ContextConfig** config, + bool& raw_socket_allowed) const { for (const auto& rule : rules_) { - Ssl::ContextSharedPtr server_context = rule->getServerTlsContext(remote_id, config); + Ssl::ContextSharedPtr server_context = + rule->getServerTlsContext(remote_id, sni, config, raw_socket_allowed); if (server_context) return server_context; } return nullptr; } - Ssl::ContextSharedPtr getClientTlsContext(uint32_t remote_id, - const Ssl::ContextConfig** config) const { + Ssl::ContextSharedPtr getClientTlsContext(uint32_t remote_id, absl::string_view sni, + const Ssl::ContextConfig** config, + bool& raw_socket_allowed) const { for (const auto& rule : rules_) { - Ssl::ContextSharedPtr client_context = rule->getClientTlsContext(remote_id, config); + Ssl::ContextSharedPtr client_context = + rule->getClientTlsContext(remote_id, sni, config, raw_socket_allowed); if (client_context) return client_context; } @@ -784,21 +796,23 @@ bool PortPolicy::allowed(uint32_t remote_id, }); } -Ssl::ContextSharedPtr PortPolicy::getServerTlsContext(uint32_t remote_id, - const Ssl::ContextConfig** config) const { +Ssl::ContextSharedPtr PortPolicy::getServerTlsContext(uint32_t remote_id, absl::string_view sni, + const Ssl::ContextConfig** config, + bool& raw_socket_allowed) const { Ssl::ContextSharedPtr ret; for_first_range([&](const PortNetworkPolicyRules& rules) -> bool { - ret = rules.getServerTlsContext(remote_id, config); + ret = rules.getServerTlsContext(remote_id, sni, config, raw_socket_allowed); return ret != nullptr; }); return ret; } -Ssl::ContextSharedPtr PortPolicy::getClientTlsContext(uint32_t remote_id, - const Ssl::ContextConfig** config) const { +Ssl::ContextSharedPtr PortPolicy::getClientTlsContext(uint32_t remote_id, absl::string_view sni, + const Ssl::ContextConfig** config, + bool& raw_socket_allowed) const { Ssl::ContextSharedPtr ret; for_first_range([&](const PortNetworkPolicyRules& rules) -> bool { - ret = rules.getClientTlsContext(remote_id, config); + ret = rules.getClientTlsContext(remote_id, sni, config, raw_socket_allowed); return ret != nullptr; }); return ret; @@ -1017,7 +1031,7 @@ class PolicyInstanceImpl : public PolicyInstance { PolicyInstanceImpl(const NetworkPolicyMap& parent, uint64_t hash, const cilium::NetworkPolicy& proto) : conntrack_map_name_(proto.conntrack_map_name()), endpoint_id_(proto.endpoint_id()), - hash_(hash), policy_proto_(proto), endpoint_ips_(proto), + hash_(hash), policy_proto_(proto), endpoint_ips_(proto), parent_(parent), ingress_(parent, policy_proto_.ingress_per_port_policies()), egress_(parent, policy_proto_.egress_per_port_policies()) {} @@ -1059,6 +1073,8 @@ class PolicyInstanceImpl : public PolicyInstance { return res; } + void tlsWrapperMissingPolicyInc() const override { parent_.tlsWrapperMissingPolicyInc(); } + public: std::string conntrack_map_name_; uint32_t endpoint_id_; @@ -1067,6 +1083,7 @@ class PolicyInstanceImpl : public PolicyInstance { const IPAddressPair endpoint_ips_; private: + const NetworkPolicyMap& parent_; const PortNetworkPolicy ingress_; const PortNetworkPolicy egress_; }; @@ -1442,6 +1459,8 @@ class AllowAllEgressPolicyInstanceImpl : public PolicyInstance { std::string String() const override { return "AllowAllEgressPolicyInstanceImpl"; } + void tlsWrapperMissingPolicyInc() const override {} + private: PolicyMap empty_map_; static const std::string empty_string; diff --git a/cilium/network_policy.h b/cilium/network_policy.h index ce1ffa490..46d48d3d4 100644 --- a/cilium/network_policy.h +++ b/cilium/network_policy.h @@ -73,12 +73,18 @@ class PortPolicy : public Logger::Loggable { bool allowed(uint32_t remote_id, const envoy::config::core::v3::Metadata& metadata) const; // getServerTlsContext returns the server TLS context, if any. If a non-null pointer is returned, // then also the config pointer '*config' is set. - Ssl::ContextSharedPtr getServerTlsContext(uint32_t remote_id, - const Ssl::ContextConfig** config) const; + // If '*config' is nullptr and 'raw_socket_allowed' is 'true' on return then the policy + // allows the connection without TLS and a raw socket should be used. + Ssl::ContextSharedPtr getServerTlsContext(uint32_t remote_id, absl::string_view sni, + const Ssl::ContextConfig** config, + bool& raw_socket_allowed) const; // getClientTlsContext returns the client TLS context, if any. If a non-null pointer is returned, // then also the config pointer '*config' is set. - Ssl::ContextSharedPtr getClientTlsContext(uint32_t remote_id, - const Ssl::ContextConfig** config) const; + // If '*config' is nullptr and 'raw_socket_allowed' is 'true' on return then the policy + // allows the connection without TLS and a raw socket should be used. + Ssl::ContextSharedPtr getClientTlsContext(uint32_t remote_id, absl::string_view sni, + const Ssl::ContextConfig** config, + bool& raw_socket_allowed) const; private: bool for_range(std::function allowed) const; @@ -127,6 +133,8 @@ class PolicyInstance { virtual const IPAddressPair& getEndpointIPs() const PURE; virtual std::string String() const PURE; + + virtual void tlsWrapperMissingPolicyInc() const PURE; }; using PolicyInstanceConstSharedPtr = std::shared_ptr; @@ -173,7 +181,8 @@ class NetworkPolicyDecoder : public Envoy::Config::OpaqueResourceDecoder { COUNTER(updates) \ COUNTER(updates_rejected) \ COUNTER(updates_timed_out) \ - HISTOGRAM(update_latency_ms, Milliseconds) + HISTOGRAM(update_latency_ms, Milliseconds) \ + COUNTER(tls_wrapper_missing_policy) // clang-format on /** @@ -237,6 +246,8 @@ class NetworkPolicyMap : public Singleton::Instance, return *transport_factory_context_; } + void tlsWrapperMissingPolicyInc() const { stats_.tls_wrapper_missing_policy_.inc(); } + private: const std::shared_ptr& GetPolicyInstanceImpl(const std::string& endpoint_policy_name) const; diff --git a/cilium/socket_option.h b/cilium/socket_option.h index 2314920b1..f05862b00 100644 --- a/cilium/socket_option.h +++ b/cilium/socket_option.h @@ -228,21 +228,22 @@ class SocketOption : public SocketMarkOption { Network::Address::InstanceConstSharedPtr original_source_address, Network::Address::InstanceConstSharedPtr ipv4_source_address, Network::Address::InstanceConstSharedPtr ipv6_source_address, - const std::shared_ptr& policy_id_resolver, uint32_t proxy_id) + const std::shared_ptr& policy_id_resolver, uint32_t proxy_id, + absl::string_view sni) : SocketMarkOption(mark, source_identity, original_source_address, ipv4_source_address, ipv6_source_address), ingress_source_identity_(ingress_source_identity), initial_policy_(policy), ingress_(ingress), is_l7lb_(l7lb), port_(port), pod_ip_(std::move(pod_ip)), - proxy_id_(proxy_id), policy_id_resolver_(policy_id_resolver) { + proxy_id_(proxy_id), sni_(sni), policy_id_resolver_(policy_id_resolver) { ENVOY_LOG(debug, "Cilium SocketOption(): source_identity: {}, " "ingress: {}, port: {}, pod_ip: {}, source_addresses: {}/{}/{}, mark: {:x} (magic " - "mark: {:x}, cluster: {}, ID: {}), proxy_id: {}", + "mark: {:x}, cluster: {}, ID: {}), proxy_id: {}, sni: \"{}\"", identity_, ingress_, port_, pod_ip_, original_source_address_ ? original_source_address_->asString() : "", ipv4_source_address_ ? ipv4_source_address_->asString() : "", ipv6_source_address_ ? ipv6_source_address_->asString() : "", mark_, mark & 0xff00, - mark & 0xff, mark >> 16, proxy_id_); + mark & 0xff, mark >> 16, proxy_id_, sni_); ASSERT(initial_policy_ != nullptr); } @@ -266,6 +267,7 @@ class SocketOption : public SocketMarkOption { uint16_t port_; std::string pod_ip_; uint32_t proxy_id_; + std::string sni_; private: const std::shared_ptr policy_id_resolver_; diff --git a/cilium/tls_wrapper.cc b/cilium/tls_wrapper.cc index 9ad50f3bd..3aae2d01f 100644 --- a/cilium/tls_wrapper.cc +++ b/cilium/tls_wrapper.cc @@ -34,6 +34,9 @@ class SslSocketWrapper : public Network::TransportSocket { void setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) override { // Get the Cilium socket option from the callbacks in order to get the TLS // configuration + // Cilium socket option is only created if the (intial) policy for the local pod exists. + // If the policy requires TLS then a TLS socket is used, but if the policy does not require + // TLS a raw socket is used instead, const auto option = Cilium::GetSocketOption(callbacks.connection().socketOptions()); if (option) { // Resolve the destination security ID and port @@ -60,13 +63,17 @@ class SslSocketWrapper : public Network::TransportSocket { } } + // get the requested server name from the connection, if any + const auto& sni = option->sni_; + auto remote_id = option->ingress_ ? option->identity_ : destination_identity; auto port_policy = option->initial_policy_->findPortPolicy(option->ingress_, destination_port); - const Envoy::Ssl::ContextConfig* config; - Envoy::Ssl::ContextSharedPtr ctx = is_client - ? port_policy.getClientTlsContext(remote_id, &config) - : port_policy.getServerTlsContext(remote_id, &config); + const Envoy::Ssl::ContextConfig* config = nullptr; + bool raw_socket_allowed = false; + Envoy::Ssl::ContextSharedPtr ctx = + is_client ? port_policy.getClientTlsContext(remote_id, sni, &config, raw_socket_allowed) + : port_policy.getServerTlsContext(remote_id, sni, &config, raw_socket_allowed); if (ctx) { // create the underlying SslSocket auto status_or_socket = Extensions::TransportSockets::Tls::SslSocket::create( @@ -79,7 +86,15 @@ class SslSocketWrapper : public Network::TransportSocket { ENVOY_LOG_MISC(error, "Unable to create ssl socket {}", status_or_socket.status().message()); } + } else if (config == nullptr && raw_socket_allowed) { + // Use RawBufferSocket when policy allows without TLS. + // If policy has TLS context config then a raw socket must NOT be used. + socket_ = std::make_unique(); + // Set the callbacks + socket_->setTransportSocketCallbacks(callbacks); } else { + option->initial_policy_->tlsWrapperMissingPolicyInc(); + std::string ipStr(""); if (option->ingress_) { Network::Address::InstanceConstSharedPtr src_address = @@ -96,10 +111,11 @@ class SslSocketWrapper : public Network::TransportSocket { ipStr = dip->addressAsString(); } } - ENVOY_LOG_MISC( - warn, "cilium.tls_wrapper: Could not get {} TLS context for {} IP {} (id {}) port {}", - is_client ? "client" : "server", option->ingress_ ? "source" : "destination", ipStr, - remote_id, destination_port); + ENVOY_LOG_MISC(warn, + "cilium.tls_wrapper: Could not get {} TLS context for {} IP {} (id {}) port " + "{} sni \"{}\" and raw socket is not allowed", + is_client ? "client" : "server", option->ingress_ ? "source" : "destination", + ipStr, remote_id, destination_port, sni); } } else { ENVOY_LOG_MISC(warn, "cilium.tls_wrapper: Can not correlate connection with Cilium Network " diff --git a/cilium/tls_wrapper.h b/cilium/tls_wrapper.h index eee789991..4340321c9 100644 --- a/cilium/tls_wrapper.h +++ b/cilium/tls_wrapper.h @@ -2,26 +2,10 @@ #include "envoy/registry/registry.h" #include "envoy/server/transport_socket_config.h" -#include "envoy/stats/scope.h" -#include "envoy/stats/stats_macros.h" namespace Envoy { namespace Cilium { -// clang-format off -#define ALL_SSL_SOCKET_FACTORY_STATS(COUNTER) \ - COUNTER(ssl_context_update_by_sds) \ - COUNTER(upstream_context_secrets_not_ready) \ - COUNTER(downstream_context_secrets_not_ready) -// clang-format on - -/** - * Wrapper struct for SSL socket factory stats. @see stats_macros.h - */ -struct SslSocketFactoryStats { - ALL_SSL_SOCKET_FACTORY_STATS(GENERATE_COUNTER_STRUCT) -}; - /** * Config registration for the Cilium TLS wrapper transport socket factory. * @see TransportSocketConfigFactory. diff --git a/tests/bpf_metadata.cc b/tests/bpf_metadata.cc index 855a56625..1d868f576 100644 --- a/tests/bpf_metadata.cc +++ b/tests/bpf_metadata.cc @@ -183,7 +183,7 @@ Cilium::SocketOptionSharedPtr TestConfig::getMetadata(Network::ConnectionSocket& return std::make_shared(policy, 0, 0, source_identity, is_ingress_, is_l7lb_, port, std::move(pod_ip), nullptr, nullptr, - nullptr, shared_from_this(), 0); + nullptr, shared_from_this(), 0, ""); } } // namespace BpfMetadata diff --git a/tests/cilium_network_policy_test.cc b/tests/cilium_network_policy_test.cc index 07e2f7df2..dd78dd4c8 100644 --- a/tests/cilium_network_policy_test.cc +++ b/tests/cilium_network_policy_test.cc @@ -12,6 +12,19 @@ namespace Envoy { namespace Cilium { +#define ON_CALL_SDS_SECRET_PROVIDER(SECRET_MANAGER, PROVIDER_TYPE, API_TYPE) \ + ON_CALL(SECRET_MANAGER, findOrCreate##PROVIDER_TYPE##Provider(_, _, _, _)) \ + .WillByDefault( \ + Invoke([](const envoy::config::core::v3::ConfigSource& sds_config_source, \ + const std::string& config_name, \ + Server::Configuration::TransportSocketFactoryContext& secret_provider_context, \ + Init::Manager& init_manager) { \ + auto secret_provider = Secret::API_TYPE##SdsApi::create( \ + secret_provider_context, sds_config_source, config_name, []() {}); \ + init_manager.add(*secret_provider->initTarget()); \ + return secret_provider; \ + })) + class CiliumNetworkPolicyTest : public ::testing::Test { protected: CiliumNetworkPolicyTest() { @@ -26,17 +39,11 @@ class CiliumNetworkPolicyTest : public ::testing::Test { // SDS server. This is only useful for testing functionality with a missing secret. auto& secret_manager = factory_context_.server_factory_context_.cluster_manager_ .cluster_manager_factory_.secretManager(); - ON_CALL(secret_manager, findOrCreateGenericSecretProvider(_, _, _, _)) - .WillByDefault( - Invoke([](const envoy::config::core::v3::ConfigSource& sds_config_source, - const std::string& config_name, - Server::Configuration::TransportSocketFactoryContext& secret_provider_context, - Init::Manager& init_manager) { - auto secret_provider = Secret::GenericSecretSdsApi::create( - secret_provider_context, sds_config_source, config_name, []() {}); - init_manager.add(*secret_provider->initTarget()); - return secret_provider; - })); + ON_CALL_SDS_SECRET_PROVIDER(secret_manager, TlsCertificate, TlsCertificate); + ON_CALL_SDS_SECRET_PROVIDER(secret_manager, CertificateValidationContext, + CertificateValidationContext); + ON_CALL_SDS_SECRET_PROVIDER(secret_manager, TlsSessionTicketKeysContext, TlsSessionTicketKeys); + ON_CALL_SDS_SECRET_PROVIDER(secret_manager, GenericSecret, GenericSecret); policy_map_ = std::make_shared(factory_context_); } @@ -91,6 +98,82 @@ class CiliumNetworkPolicyTest : public ::testing::Test { return Allowed(false, pod_ip, remote_id, port, std::move(headers)); } + testing::AssertionResult TlsAllowed(bool ingress, const std::string& pod_ip, uint64_t remote_id, + uint16_t port, absl::string_view sni, + bool& tls_socket_required, bool& raw_socket_allowed) { + auto policy = policy_map_->GetPolicyInstance(pod_ip); + if (policy == nullptr) + return testing::AssertionFailure() << "Policy not found for " << pod_ip; + + auto port_policy = policy->findPortPolicy(ingress, port); + const Envoy::Ssl::ContextConfig* config = nullptr; + + // TLS context lookup does not check SNI + tls_socket_required = false; + raw_socket_allowed = false; + Envoy::Ssl::ContextSharedPtr ctx = + !ingress ? port_policy.getClientTlsContext(remote_id, sni, &config, raw_socket_allowed) + : port_policy.getServerTlsContext(remote_id, sni, &config, raw_socket_allowed); + + // separate policy lookup for validation + bool allowed = policy->allowed(ingress, remote_id, sni, port); + + // if connection is allowed without TLS socket then TLS context is not required + if (raw_socket_allowed) { + EXPECT_TRUE(ctx == nullptr && config == nullptr); + tls_socket_required = false; + } + + // if TLS config or context is returned then connection is not allowed without TLS socket + if (ctx != nullptr || config != nullptr) { + EXPECT_FALSE(raw_socket_allowed); + tls_socket_required = true; + } + + // config must exist if ctx is returned + if (ctx != nullptr) + EXPECT_TRUE(config != nullptr); + + EXPECT_TRUE(allowed == (tls_socket_required || raw_socket_allowed)); + + if (!allowed) + return testing::AssertionFailure() << pod_ip << " policy not allowing id " << remote_id + << " on port " << port << " with SNI \"" << sni << "\""; + + // sanity check + EXPECT_TRUE(!(tls_socket_required && raw_socket_allowed) && tls_socket_required || + raw_socket_allowed); + + if (raw_socket_allowed) + return testing::AssertionSuccess() + << pod_ip << " policy allows id " << remote_id << " on port " << port << " with SNI \"" + << sni << "\" without TLS socket"; + + if (tls_socket_required && ctx != nullptr) + return testing::AssertionSuccess() + << pod_ip << " policy allows id " << remote_id << " on port " << port << " with SNI \"" + << sni << "\" with TLS socket"; + + if (tls_socket_required && ctx == nullptr) + return testing::AssertionSuccess() + << pod_ip << " policy allows id " << remote_id << " on port " << port << " with SNI \"" + << sni << "\" but missing TLS context"; + + return testing::AssertionFailure(); + } + + testing::AssertionResult TlsIngressAllowed(const std::string& pod_ip, uint64_t remote_id, + uint16_t port, absl::string_view sni, + bool& tls_socket_required, bool& raw_socket_allowed) { + return TlsAllowed(true, pod_ip, remote_id, port, sni, tls_socket_required, raw_socket_allowed); + } + + testing::AssertionResult TlsEgressAllowed(const std::string& pod_ip, uint64_t remote_id, + uint16_t port, absl::string_view sni, + bool& tls_socket_required, bool& raw_socket_allowed) { + return TlsAllowed(false, pod_ip, remote_id, port, sni, tls_socket_required, raw_socket_allowed); + } + NiceMock factory_context_; std::shared_ptr policy_map_; NiceMock store_; @@ -1107,5 +1190,267 @@ TEST_F(CiliumNetworkPolicyTest, HttpPolicyUpdateToMissingSDS) { EXPECT_FALSE(IngressAllowed("10.1.2.3", 43, 80, {{":path", "/notallowed"}})); } +TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { + bool tls_socket_required; + bool raw_socket_allowed; + + std::string version; + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "0" +)EOF")); + EXPECT_EQ(version, "0"); + EXPECT_FALSE(policy_map_->exists("10.1.2.3")); + // No policy for the pod + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + // SNI does not make a difference + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 80, "example.com", tls_socket_required, + raw_socket_allowed)); + + // 1st update without TLS requirements + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "1" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 80 + rules: + - remote_policies: [ 43 ] +)EOF")); + EXPECT_EQ(version, "1"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + // Allowed remote ID & port: + EXPECT_TRUE(TlsIngressAllowed("10.1.2.3", 43, 80, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_TRUE(raw_socket_allowed); + // SNI does not matter: + EXPECT_TRUE(TlsIngressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_TRUE(raw_socket_allowed); + // Wrong remote ID: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 40, 80, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Wrong port: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 8080, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + + // No egress is allowed: + EXPECT_FALSE(TlsEgressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + + // TLS SNI update + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 80 + rules: + - remote_policies: [ 43 ] + server_names: [ "cilium.io", "example.com" ] +)EOF")); + EXPECT_EQ(version, "2"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + // Allowed remote ID, port, SNI: + EXPECT_TRUE(TlsIngressAllowed("10.1.2.3", 43, 80, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_TRUE(raw_socket_allowed); + // Allowed remote ID, port, incorrect SNI: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 80, "www.example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Allowed remote ID, port, SNI: + EXPECT_TRUE( + TlsIngressAllowed("10.1.2.3", 43, 80, "cilium.io", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_TRUE(raw_socket_allowed); + // Missing SNI: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Wrong remote ID: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 40, 80, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Wrong port: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 8080, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + + // No egress is allowed: + EXPECT_FALSE(TlsEgressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + + // TLS Interception update + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + egress_per_port_policies: + - port: 80 + rules: + - remote_policies: [ 43 ] + server_names: [ "cilium.io", "example.com" ] + downstream_tls_context: + tls_sds_secret: "secret1" + upstream_tls_context: + validation_context_sds_secret: "cacerts" +)EOF")); + EXPECT_EQ(version, "2"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + // Allowed remote ID, port, SNI: + EXPECT_TRUE( + TlsEgressAllowed("10.1.2.3", 43, 80, "example.com", tls_socket_required, raw_socket_allowed)); + EXPECT_TRUE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Allowed remote ID, port, incorrect SNI: + EXPECT_FALSE(TlsEgressAllowed("10.1.2.3", 43, 80, "www.example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Allowed remote ID, port, SNI: + EXPECT_TRUE( + TlsEgressAllowed("10.1.2.3", 43, 80, "cilium.io", tls_socket_required, raw_socket_allowed)); + EXPECT_TRUE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Missing SNI: + EXPECT_FALSE(TlsEgressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Wrong remote ID: + EXPECT_FALSE( + TlsEgressAllowed("10.1.2.3", 40, 80, "example.com", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Wrong port: + EXPECT_FALSE(TlsEgressAllowed("10.1.2.3", 43, 8080, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + + // No igress is allowed: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + + // TLS Termination update + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 80 + rules: + - remote_policies: [ 43 ] + server_names: [ "cilium.io", "example.com" ] + downstream_tls_context: + tls_sds_secret: "secret1" +)EOF")); + EXPECT_EQ(version, "2"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + // Allowed remote ID, port, SNI: + EXPECT_TRUE(TlsIngressAllowed("10.1.2.3", 43, 80, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_TRUE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Allowed remote ID, port, incorrect SNI: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 80, "www.example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Allowed remote ID, port, SNI: + EXPECT_TRUE( + TlsIngressAllowed("10.1.2.3", 43, 80, "cilium.io", tls_socket_required, raw_socket_allowed)); + EXPECT_TRUE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Missing SNI: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Wrong remote ID: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 40, 80, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Wrong port: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 8080, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + + // No egress is allowed: + EXPECT_FALSE(TlsEgressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + + // TLS Origination update + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + egress_per_port_policies: + - port: 80 + rules: + - remote_policies: [ 43 ] + upstream_tls_context: + validation_context_sds_secret: "cacerts" +)EOF")); + EXPECT_EQ(version, "2"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + // Allowed remote ID, port, SNI: + EXPECT_TRUE( + TlsEgressAllowed("10.1.2.3", 43, 80, "example.com", tls_socket_required, raw_socket_allowed)); + EXPECT_TRUE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Allowed remote ID, port, SNI: + EXPECT_TRUE(TlsEgressAllowed("10.1.2.3", 43, 80, "www.example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_TRUE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Allowed remote ID, port, SNI: + EXPECT_TRUE( + TlsEgressAllowed("10.1.2.3", 43, 80, "cilium.io", tls_socket_required, raw_socket_allowed)); + EXPECT_TRUE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Empty SNI: + EXPECT_TRUE(TlsEgressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_TRUE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Wrong remote ID: + EXPECT_FALSE( + TlsEgressAllowed("10.1.2.3", 40, 80, "example.com", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // Wrong port: + EXPECT_FALSE(TlsEgressAllowed("10.1.2.3", 43, 8080, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + + // No igress is allowed: + EXPECT_FALSE(TlsIngressAllowed("10.1.2.3", 43, 80, "", tls_socket_required, raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); +} + } // namespace Cilium } // namespace Envoy