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