From bc4e07c86fca1bee1b70acba5b9159357f8e902e Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Thu, 8 Jan 2026 17:20:04 -0800 Subject: [PATCH 1/8] policy: Limit proxy_id to 16 bits Proxy ID is really a port number, so we can safely limit it to 16 bits. Signed-off-by: Jarno Rajahalme --- cilium/accesslog.cc | 4 +- cilium/accesslog.h | 4 +- cilium/api/bpf_metadata.proto | 3 +- cilium/api/npds.proto | 2 +- cilium/bpf_metadata.cc | 2 +- cilium/bpf_metadata.h | 6 +-- cilium/filter_state_cilium_policy.h | 4 +- cilium/network_policy.cc | 62 +++++++++++------------ cilium/network_policy.h | 18 +++---- cilium/websocket.cc | 2 +- go/cilium/api/bpf_metadata.pb.go | 7 +-- go/cilium/api/bpf_metadata.pb.validate.go | 11 +++- go/cilium/api/npds.pb.go | 6 +-- go/cilium/api/npds.pb.validate.go | 11 +++- tests/cilium_network_policy_test.cc | 2 +- 15 files changed, 82 insertions(+), 62 deletions(-) diff --git a/cilium/accesslog.cc b/cilium/accesslog.cc index 39075dc0d..704f5a68d 100644 --- a/cilium/accesslog.cc +++ b/cilium/accesslog.cc @@ -80,7 +80,7 @@ CONST_STRING_VIEW(xRequestIdSV, "x-request-id"); CONST_STRING_VIEW(statusSV, ":status"); void AccessLog::Entry::initFromConnection( - const std::string& policy_name, uint32_t proxy_id, bool ingress, uint32_t source_identity, + const std::string& policy_name, uint16_t proxy_id, bool ingress, uint32_t source_identity, const Network::Address::InstanceConstSharedPtr& source_address, uint32_t destination_identity, const Network::Address::InstanceConstSharedPtr& destination_address, TimeSource* time_source) { request_logged_ = false; @@ -143,7 +143,7 @@ bool AccessLog::Entry::updateFromMetadata(const std::string& l7proto, return changed; } -void AccessLog::Entry::initFromRequest(const std::string& policy_name, uint32_t proxy_id, +void AccessLog::Entry::initFromRequest(const std::string& policy_name, uint16_t proxy_id, bool ingress, uint32_t source_identity, const Network::Address::InstanceConstSharedPtr& src_address, uint32_t destination_identity, diff --git a/cilium/accesslog.h b/cilium/accesslog.h index 54acaf28f..0ad874d67 100644 --- a/cilium/accesslog.h +++ b/cilium/accesslog.h @@ -32,7 +32,7 @@ class AccessLog : public UDSClient { // wrapper for protobuf class Entry : public StreamInfo::FilterState::Object { public: - void initFromRequest(const std::string& policy_name, uint32_t proxy_id, bool ingress, + void initFromRequest(const std::string& policy_name, uint16_t proxy_id, bool ingress, uint32_t source_identity, const Network::Address::InstanceConstSharedPtr& source_address, uint32_t destination_identity, @@ -43,7 +43,7 @@ class AccessLog : public UDSClient { const Http::RequestHeaderMap&); void updateFromResponse(const Http::ResponseHeaderMap&, TimeSource&); - void initFromConnection(const std::string& policy_name, uint32_t proxy_id, bool ingress, + void initFromConnection(const std::string& policy_name, uint16_t proxy_id, bool ingress, uint32_t source_identity, const Network::Address::InstanceConstSharedPtr& source_address, uint32_t destination_identity, diff --git a/cilium/api/bpf_metadata.proto b/cilium/api/bpf_metadata.proto index 1e8e5cd61..b28d58c18 100644 --- a/cilium/api/bpf_metadata.proto +++ b/cilium/api/bpf_metadata.proto @@ -5,6 +5,7 @@ option go_package = "github.com/cilium/proxy/go/cilium/api;cilium"; package cilium; import "google/protobuf/duration.proto"; +import "validate/validate.proto"; message BpfMetadata { // File system root for bpf. Bpf will not be used if left empty. @@ -45,7 +46,7 @@ message BpfMetadata { // proxy_id is passed to access log messages and allows relating access log messages to // listeners. - uint32 proxy_id = 8; + uint32 proxy_id = 8 [(validate.rules).uint32.lte = 65535]; // policy_update_warning_limit is the time in milliseconds after which a warning is logged if // network policy update took longer diff --git a/cilium/api/npds.proto b/cilium/api/npds.proto index 154a3c4be..7ab44ba04 100644 --- a/cilium/api/npds.proto +++ b/cilium/api/npds.proto @@ -141,7 +141,7 @@ message PortNetworkPolicyRule { // The given value corresponds to the 'proxy_id' value in the BpfMetadata listener filter // configuration. // This rule should be ignored if not executing in the referred listener. - uint32 proxy_id = 9; + uint32 proxy_id = 9 [(validate.rules).uint32.lte = 65535]; // Optional name for the rule, can be used in logging and error messages. string name = 5; diff --git a/cilium/bpf_metadata.cc b/cilium/bpf_metadata.cc index 00423b278..a6eca8743 100644 --- a/cilium/bpf_metadata.cc +++ b/cilium/bpf_metadata.cc @@ -202,7 +202,7 @@ Config::Config(const ::cilium::BpfMetadata& config, : so_linger_(config.has_original_source_so_linger_time() ? config.original_source_so_linger_time() : -1), - proxy_id_(config.proxy_id()), is_ingress_(config.is_ingress()), + proxy_id_(uint16_t(config.proxy_id())), is_ingress_(config.is_ingress()), use_original_source_address_(config.use_original_source_address()), is_l7lb_(config.is_l7lb()), ipv4_source_address_( diff --git a/cilium/bpf_metadata.h b/cilium/bpf_metadata.h index b69738e08..56b352429 100644 --- a/cilium/bpf_metadata.h +++ b/cilium/bpf_metadata.h @@ -42,7 +42,7 @@ struct SocketMetadata : public Logger::Loggable { Network::Address::InstanceConstSharedPtr source_address_ipv4, Network::Address::InstanceConstSharedPtr source_address_ipv6, Network::Address::InstanceConstSharedPtr original_dest_address, - const PolicyResolverSharedPtr& policy_resolver, uint32_t proxy_id, + const PolicyResolverSharedPtr& policy_resolver, uint16_t proxy_id, std::string&& proxylib_l7_proto, absl::string_view sni) : ingress_source_identity_(ingress_source_identity), source_identity_(source_identity), ingress_(ingress), is_l7lb_(l7lb), port_(port), pod_ip_(std::move(pod_ip)), @@ -120,7 +120,7 @@ struct SocketMetadata : public Logger::Loggable { std::string pod_ip_; // pod policy to enforce, if any; empty only when there is no local pod (i.e. // north/south l7lb) std::string ingress_policy_name_; // Ingress policy to enforce, if any - uint32_t proxy_id_; + uint16_t proxy_id_; std::string proxylib_l7_proto_; std::string sni_; const PolicyResolverSharedPtr policy_resolver_; @@ -158,7 +158,7 @@ class Config : public Cilium::PolicyResolver, virtual bool addPrivilegedSocketOptions() { return true; }; int so_linger_; // negative if disabled - uint32_t proxy_id_; + uint16_t proxy_id_; bool is_ingress_; bool use_original_source_address_; bool is_l7lb_; diff --git a/cilium/filter_state_cilium_policy.h b/cilium/filter_state_cilium_policy.h index 34f4d9375..09c65784f 100644 --- a/cilium/filter_state_cilium_policy.h +++ b/cilium/filter_state_cilium_policy.h @@ -40,7 +40,7 @@ class CiliumPolicyFilterState : public StreamInfo::FilterState::Object, CiliumPolicyFilterState(uint32_t ingress_source_identity, uint32_t source_identity, bool ingress, bool l7lb, uint16_t port, std::string&& pod_ip, std::string&& ingress_policy_name, - const PolicyResolverSharedPtr& policy_resolver, uint32_t proxy_id, + const PolicyResolverSharedPtr& policy_resolver, uint16_t proxy_id, absl::string_view sni) : ingress_source_identity_(ingress_source_identity), source_identity_(source_identity), ingress_(ingress), is_l7lb_(l7lb), port_(port), pod_ip_(std::move(pod_ip)), @@ -91,7 +91,7 @@ class CiliumPolicyFilterState : public StreamInfo::FilterState::Object, uint16_t port_; std::string pod_ip_; std::string ingress_policy_name_; - uint32_t proxy_id_; + uint16_t proxy_id_; std::string sni_; private: diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index 817a4740b..e8b635b3e 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -420,8 +420,8 @@ class PortNetworkPolicyRule : public Logger::Loggable { public: PortNetworkPolicyRule(const NetworkPolicyMapImpl& parent, const cilium::PortNetworkPolicyRule& rule) - : name_(rule.name()), deny_(rule.deny()), precedence_(rule.precedence()), - proxy_id_(rule.proxy_id()), l7_proto_(rule.l7_proto()) { + : name_(rule.name()), deny_(rule.deny()), proxy_id_(uint16_t(rule.proxy_id())), + precedence_(rule.precedence()), l7_proto_(rule.l7_proto()) { for (const auto& remote : rule.remote_policies()) { ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule(): {} remote {} by rule: {}", deny_ ? "Denying" : "Allowing", remote, name_); @@ -458,7 +458,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { } } - bool allowed(uint32_t proxy_id, uint32_t remote_id, bool& denied) const { + bool allowed(uint16_t proxy_id, uint32_t remote_id, bool& denied) const { // proxy_id must match if we have any. if (proxy_id_ != 0 && proxy_id != proxy_id_) { return false; @@ -487,7 +487,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { return true; } - bool allowed(uint32_t proxy_id, uint32_t remote_id, absl::string_view sni, bool& denied) const { + bool allowed(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, bool& denied) const { // sni must match if we have any if (!allowed_snis_.empty()) { if (sni.length() == 0) { @@ -507,7 +507,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { return allowed(proxy_id, remote_id, denied); } - bool allowed(uint32_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers, + bool allowed(uint16_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers, Cilium::AccessLog::Entry& log_entry, bool& denied) const { if (!allowed(proxy_id, remote_id, denied)) { return false; @@ -534,7 +534,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { return true; } - bool useProxylib(uint32_t proxy_id, uint32_t remote_id, std::string& l7_proto, + bool useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto, bool& denied) const { if (!allowed(proxy_id, remote_id, denied)) { return false; @@ -548,7 +548,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { } // Envoy Metadata matcher, called after deny has already been checked for - bool allowed(uint32_t proxy_id, uint32_t remote_id, + bool allowed(uint16_t proxy_id, uint32_t remote_id, const envoy::config::core::v3::Metadata& metadata, bool& denied) const { if (!allowed(proxy_id, remote_id, denied)) { return false; @@ -583,7 +583,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { return true; // allowed by default } - Ssl::ContextSharedPtr getServerTlsContext(uint32_t proxy_id, uint32_t remote_id, + Ssl::ContextSharedPtr getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, const Ssl::ContextConfig** config, bool& raw_socket_allowed, bool& denied) const { @@ -597,7 +597,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { return nullptr; } - Ssl::ContextSharedPtr getClientTlsContext(uint32_t proxy_id, uint32_t remote_id, + Ssl::ContextSharedPtr getClientTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, const Ssl::ContextConfig** config, bool& raw_socket_allowed, bool& denied) const { @@ -678,8 +678,8 @@ class PortNetworkPolicyRule : public Logger::Loggable { UpstreamTLSContextPtr client_context_; bool has_headermatches_{false}; bool deny_; + uint16_t proxy_id_; uint32_t precedence_; - uint32_t proxy_id_; absl::btree_set remotes_; std::vector allowed_snis_; // All SNIs allowed if empty. @@ -761,7 +761,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { } } - bool allowed(uint32_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers, + bool allowed(uint16_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers, Cilium::AccessLog::Entry& log_entry, bool& denied) const { // Empty set matches any payload from anyone if (rules_.empty()) { @@ -788,7 +788,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { return allowed && !denied; } - bool allowed(uint32_t proxy_id, uint32_t remote_id, absl::string_view sni, bool& denied) const { + bool allowed(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, bool& denied) const { // Empty set matches any payload from anyone if (rules_.empty()) { return true; @@ -814,7 +814,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { return allowed && !denied; } - bool useProxylib(uint32_t proxy_id, uint32_t remote_id, std::string& l7_proto) const { + bool useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto) const { bool denied = false; bool use_proxylib = false; forEachRule([&](const auto& rule) { @@ -827,7 +827,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { return use_proxylib && !denied; } - bool allowed(uint32_t proxy_id, uint32_t remote_id, + bool allowed(uint16_t proxy_id, uint32_t remote_id, const envoy::config::core::v3::Metadata& metadata, bool& denied) const { // Empty set matches any payload from anyone if (rules_.empty()) { @@ -855,7 +855,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { return allowed && !denied; } - Ssl::ContextSharedPtr getServerTlsContext(uint32_t proxy_id, uint32_t remote_id, + Ssl::ContextSharedPtr getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, const Ssl::ContextConfig** config, bool& raw_socket_allowed) const { @@ -873,7 +873,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { return denied ? nullptr : tls_ctx; } - Ssl::ContextSharedPtr getClientTlsContext(uint32_t proxy_id, uint32_t remote_id, + Ssl::ContextSharedPtr getClientTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, const Ssl::ContextConfig** config, bool& raw_socket_allowed) const { @@ -970,13 +970,13 @@ bool PortPolicy::forFirstRange(std::function bool { return rules.useProxylib(proxy_id, remote_id, l7_proto); }); } -bool PortPolicy::allowed(uint32_t proxy_id, uint32_t remote_id, +bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers, Cilium::AccessLog::Entry& log_entry) const { // Network layer policy has already been enforced. If there are no http rules, then there is @@ -989,20 +989,20 @@ bool PortPolicy::allowed(uint32_t proxy_id, uint32_t remote_id, }); } -bool PortPolicy::allowed(uint32_t proxy_id, uint32_t remote_id, absl::string_view sni) const { +bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni) const { return forRange([&](const PortNetworkPolicyRules& rules, bool& denied) -> bool { return rules.allowed(proxy_id, remote_id, sni, denied); }); } -bool PortPolicy::allowed(uint32_t proxy_id, uint32_t remote_id, +bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, const envoy::config::core::v3::Metadata& metadata) const { return forRange([&](const PortNetworkPolicyRules& rules, bool& denied) -> bool { return rules.allowed(proxy_id, remote_id, metadata, denied); }); } -Ssl::ContextSharedPtr PortPolicy::getServerTlsContext(uint32_t proxy_id, uint32_t remote_id, +Ssl::ContextSharedPtr PortPolicy::getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, const Ssl::ContextConfig** config, bool& raw_socket_allowed) const { @@ -1014,7 +1014,7 @@ Ssl::ContextSharedPtr PortPolicy::getServerTlsContext(uint32_t proxy_id, uint32_ return ret; } -Ssl::ContextSharedPtr PortPolicy::getClientTlsContext(uint32_t proxy_id, uint32_t remote_id, +Ssl::ContextSharedPtr PortPolicy::getClientTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, const Ssl::ContextConfig** config, bool& raw_socket_allowed) const { @@ -1237,7 +1237,7 @@ class PolicyInstanceImpl : public PolicyInstance { ingress_(parent, policy_proto_.ingress_per_port_policies()), egress_(parent, policy_proto_.egress_per_port_policies()) {} - bool allowed(bool ingress, uint32_t proxy_id, uint32_t remote_id, uint16_t port, + bool allowed(bool ingress, uint16_t proxy_id, uint32_t remote_id, uint16_t port, Envoy::Http::RequestHeaderMap& headers, Cilium::AccessLog::Entry& log_entry) const override { const auto port_policy = findPortPolicy(ingress, port); @@ -1247,7 +1247,7 @@ class PolicyInstanceImpl : public PolicyInstance { return port_policy.allowed(proxy_id, remote_id, headers, log_entry); } - bool allowed(bool ingress, uint32_t proxy_id, uint32_t remote_id, absl::string_view sni, + bool allowed(bool ingress, uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, uint16_t port) const override { const auto port_policy = findPortPolicy(ingress, port); return port_policy.allowed(proxy_id, remote_id, sni); @@ -1257,7 +1257,7 @@ class PolicyInstanceImpl : public PolicyInstance { return ingress ? ingress_.findPortPolicy(port) : egress_.findPortPolicy(port); } - bool useProxylib(bool ingress, uint32_t proxy_id, uint32_t remote_id, uint16_t port, + bool useProxylib(bool ingress, uint16_t proxy_id, uint32_t remote_id, uint16_t port, std::string& l7_proto) const override { const auto port_policy = findPortPolicy(ingress, port); return port_policy.useProxylib(proxy_id, remote_id, l7_proto); @@ -1575,12 +1575,12 @@ class AllowAllEgressPolicyInstanceImpl : public PolicyInstance { empty_map_.emplace(std::make_pair(uint16_t(1), uint16_t(1)), PortNetworkPolicyRules{}); } - bool allowed(bool ingress, uint32_t, uint32_t, uint16_t, Envoy::Http::RequestHeaderMap&, + bool allowed(bool ingress, uint16_t, uint32_t, uint16_t, Envoy::Http::RequestHeaderMap&, Cilium::AccessLog::Entry&) const override { return ingress ? false : true; } - bool allowed(bool ingress, uint32_t, uint32_t, absl::string_view, uint16_t) const override { + bool allowed(bool ingress, uint16_t, uint32_t, absl::string_view, uint16_t) const override { return ingress ? false : true; } @@ -1588,7 +1588,7 @@ class AllowAllEgressPolicyInstanceImpl : public PolicyInstance { return ingress ? PortPolicy(empty_map_, 0) : PortPolicy(empty_map_, 1); } - bool useProxylib(bool, uint32_t, uint32_t, uint16_t, std::string&) const override { + bool useProxylib(bool, uint16_t, uint32_t, uint16_t, std::string&) const override { return false; } @@ -1619,12 +1619,12 @@ class DenyAllPolicyInstanceImpl : public PolicyInstance { public: DenyAllPolicyInstanceImpl() = default; - bool allowed(bool, uint32_t, uint32_t, uint16_t, Envoy::Http::RequestHeaderMap&, + bool allowed(bool, uint16_t, uint32_t, uint16_t, Envoy::Http::RequestHeaderMap&, Cilium::AccessLog::Entry&) const override { return false; } - bool allowed(bool, uint32_t, uint32_t, absl::string_view, uint16_t) const override { + bool allowed(bool, uint16_t, uint32_t, absl::string_view, uint16_t) const override { return false; } @@ -1632,7 +1632,7 @@ class DenyAllPolicyInstanceImpl : public PolicyInstance { return PortPolicy(empty_map_, 0); } - bool useProxylib(bool, uint32_t, uint32_t, uint16_t, std::string&) const override { + bool useProxylib(bool, uint16_t, uint32_t, uint16_t, std::string&) const override { return false; } diff --git a/cilium/network_policy.h b/cilium/network_policy.h index ec07116f4..13c376564 100644 --- a/cilium/network_policy.h +++ b/cilium/network_policy.h @@ -93,20 +93,20 @@ class PortPolicy : public Logger::Loggable { // useProxylib returns true if a proxylib parser should be used. // 'l7_proto' is set to the parser name in that case. - bool useProxylib(uint32_t proxy_id, uint32_t remote_id, std::string& l7_proto) const; + bool useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto) const; // HTTP-layer policy check. 'headers' and 'log_entry' may be manipulated by the policy. - bool allowed(uint32_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers, + bool allowed(uint16_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers, Cilium::AccessLog::Entry& log_entry) const; // Network-layer policy check - bool allowed(uint32_t proxy_id, uint32_t remote_id, absl::string_view sni) const; + bool allowed(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni) const; // Envoy filter metadata policy check - bool allowed(uint32_t proxy_id, uint32_t remote_id, + bool allowed(uint16_t proxy_id, 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. // 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 proxy_id, uint32_t remote_id, + Ssl::ContextSharedPtr getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, const Ssl::ContextConfig** config, bool& raw_socket_allowed) const; @@ -114,7 +114,7 @@ class PortPolicy : public Logger::Loggable { // then also the config pointer '*config' is set. // 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 proxy_id, uint32_t remote_id, + Ssl::ContextSharedPtr getClientTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, const Ssl::ContextConfig** config, bool& raw_socket_allowed) const; @@ -158,18 +158,18 @@ class PolicyInstance { } }; - virtual bool allowed(bool ingress, uint32_t proxy_id, uint32_t remote_id, uint16_t port, + virtual bool allowed(bool ingress, uint16_t proxy_id, uint32_t remote_id, uint16_t port, Envoy::Http::RequestHeaderMap& headers, Cilium::AccessLog::Entry& log_entry) const PURE; - virtual bool allowed(bool ingress, uint32_t proxy_id, uint32_t remote_id, absl::string_view sni, + virtual bool allowed(bool ingress, uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, uint16_t port) const PURE; virtual const PortPolicy findPortPolicy(bool ingress, uint16_t port) const PURE; // Returns true if the policy specifies l7 protocol for the connection, and // returns the l7 protocol string in 'l7_proto' - virtual bool useProxylib(bool ingress, uint32_t proxy_id, uint32_t remote_id, uint16_t port, + virtual bool useProxylib(bool ingress, uint16_t proxy_id, uint32_t remote_id, uint16_t port, std::string& l7_proto) const PURE; virtual const std::string& conntrackName() const PURE; diff --git a/cilium/websocket.cc b/cilium/websocket.cc index 65c3718c1..c84a2b279 100644 --- a/cilium/websocket.cc +++ b/cilium/websocket.cc @@ -134,7 +134,7 @@ Network::FilterStatus Instance::onNewConnection() { std::string pod_ip; bool is_ingress; uint32_t identity, destination_identity; - uint32_t proxy_id; + uint16_t proxy_id; auto& conn = callbacks_->connection(); diff --git a/go/cilium/api/bpf_metadata.pb.go b/go/cilium/api/bpf_metadata.pb.go index eeecbfbd4..3b860b778 100644 --- a/go/cilium/api/bpf_metadata.pb.go +++ b/go/cilium/api/bpf_metadata.pb.go @@ -7,6 +7,7 @@ package cilium import ( + _ "github.com/envoyproxy/protoc-gen-validate/validate" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" durationpb "google.golang.org/protobuf/types/known/durationpb" @@ -228,7 +229,7 @@ var File_cilium_api_bpf_metadata_proto protoreflect.FileDescriptor const file_cilium_api_bpf_metadata_proto_rawDesc = "" + "\n" + - "\x1dcilium/api/bpf_metadata.proto\x12\x06cilium\x1a\x1egoogle/protobuf/duration.proto\"\x89\x06\n" + + "\x1dcilium/api/bpf_metadata.proto\x12\x06cilium\x1a\x1egoogle/protobuf/duration.proto\x1a\x17validate/validate.proto\"\x94\x06\n" + "\vBpfMetadata\x12\x19\n" + "\bbpf_root\x18\x01 \x01(\tR\abpfRoot\x12\x1d\n" + "\n" + @@ -237,8 +238,8 @@ const file_cilium_api_bpf_metadata_proto_rawDesc = "" + "\ais_l7lb\x18\x04 \x01(\bR\x06isL7lb\x12.\n" + "\x13ipv4_source_address\x18\x05 \x01(\tR\x11ipv4SourceAddress\x12.\n" + "\x13ipv6_source_address\x18\x06 \x01(\tR\x11ipv6SourceAddress\x123\n" + - "\x16enforce_policy_on_l7lb\x18\a \x01(\bR\x13enforcePolicyOnL7lb\x12\x19\n" + - "\bproxy_id\x18\b \x01(\rR\aproxyId\x12X\n" + + "\x16enforce_policy_on_l7lb\x18\a \x01(\bR\x13enforcePolicyOnL7lb\x12$\n" + + "\bproxy_id\x18\b \x01(\rB\t\xfaB\x06*\x04\x18\xff\xff\x03R\aproxyId\x12X\n" + "\x1bpolicy_update_warning_limit\x18\t \x01(\v2\x19.google.protobuf.DurationR\x18policyUpdateWarningLimit\x12(\n" + "\x10l7lb_policy_name\x18\n" + " \x01(\tR\x0el7lbPolicyName\x12G\n" + diff --git a/go/cilium/api/bpf_metadata.pb.validate.go b/go/cilium/api/bpf_metadata.pb.validate.go index b9c4ae84e..8180998d2 100644 --- a/go/cilium/api/bpf_metadata.pb.validate.go +++ b/go/cilium/api/bpf_metadata.pb.validate.go @@ -71,7 +71,16 @@ func (m *BpfMetadata) validate(all bool) error { // no validation rules for EnforcePolicyOnL7Lb - // no validation rules for ProxyId + if m.GetProxyId() > 65535 { + err := BpfMetadataValidationError{ + field: "ProxyId", + reason: "value must be less than or equal to 65535", + } + if !all { + return err + } + errors = append(errors, err) + } if all { switch v := interface{}(m.GetPolicyUpdateWarningLimit()).(type) { diff --git a/go/cilium/api/npds.pb.go b/go/cilium/api/npds.pb.go index 4ca1ca12a..d3618c2fc 100644 --- a/go/cilium/api/npds.pb.go +++ b/go/cilium/api/npds.pb.go @@ -1177,14 +1177,14 @@ const file_cilium_api_npds_proto_rawDesc = "" + "\fserver_names\x18\x04 \x03(\tR\vserverNames\x12A\n" + "\x1dvalidation_context_sds_secret\x18\x05 \x01(\tR\x1avalidationContextSdsSecret\x12$\n" + "\x0etls_sds_secret\x18\x06 \x01(\tR\ftlsSdsSecret\x12%\n" + - "\x0ealpn_protocols\x18\a \x03(\tR\ralpnProtocols\"\xbe\x05\n" + + "\x0ealpn_protocols\x18\a \x03(\tR\ralpnProtocols\"\xc9\x05\n" + "\x15PortNetworkPolicyRule\x12\x1e\n" + "\n" + "precedence\x18\n" + " \x01(\rR\n" + "precedence\x12\x12\n" + - "\x04deny\x18\b \x01(\bR\x04deny\x12\x19\n" + - "\bproxy_id\x18\t \x01(\rR\aproxyId\x12\x12\n" + + "\x04deny\x18\b \x01(\bR\x04deny\x12$\n" + + "\bproxy_id\x18\t \x01(\rB\t\xfaB\x06*\x04\x18\xff\xff\x03R\aproxyId\x12\x12\n" + "\x04name\x18\x05 \x01(\tR\x04name\x12'\n" + "\x0fremote_policies\x18\a \x03(\rR\x0eremotePolicies\x12H\n" + "\x16downstream_tls_context\x18\x03 \x01(\v2\x12.cilium.TLSContextR\x14downstreamTlsContext\x12D\n" + diff --git a/go/cilium/api/npds.pb.validate.go b/go/cilium/api/npds.pb.validate.go index 88f7dd02c..17dc550da 100644 --- a/go/cilium/api/npds.pb.validate.go +++ b/go/cilium/api/npds.pb.validate.go @@ -533,7 +533,16 @@ func (m *PortNetworkPolicyRule) validate(all bool) error { // no validation rules for Deny - // no validation rules for ProxyId + if m.GetProxyId() > 65535 { + err := PortNetworkPolicyRuleValidationError{ + field: "ProxyId", + reason: "value must be less than or equal to 65535", + } + if !all { + return err + } + errors = append(errors, err) + } // no validation rules for Name diff --git a/tests/cilium_network_policy_test.cc b/tests/cilium_network_policy_test.cc index 72bc22d6e..3004313c9 100644 --- a/tests/cilium_network_policy_test.cc +++ b/tests/cilium_network_policy_test.cc @@ -217,7 +217,7 @@ class CiliumNetworkPolicyTest : public ::testing::Test { NiceMock secret_manager_; std::shared_ptr policy_map_; NiceMock store_; - uint32_t proxy_id_ = 42; + uint16_t proxy_id_ = 42; }; TEST_F(CiliumNetworkPolicyTest, UpdatesRejectedStatName) { From 5c9a610f133e307df61ba46c2f1e1ddad4a607af Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Thu, 8 Jan 2026 19:13:44 -0800 Subject: [PATCH 2/8] policy: Make PortNetworkPolicyRule copyable Pass verdict handling needs to make copies of PortNetworkPolicyRules. Change the unique pointers to a shared pointers to allow for this. Signed-off-by: Jarno Rajahalme --- cilium/network_policy.cc | 22 ++++++++++++---------- cilium/network_policy.h | 2 +- cilium/secret_watcher.h | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index e8b635b3e..af351a070 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -440,11 +440,12 @@ class PortNetworkPolicyRule : public Logger::Loggable { allowed_snis_.emplace_back(parent.regexEngine(), sni); } if (rule.has_http_rules()) { + http_rules_ = std::make_shared>(); for (const auto& http_rule : rule.http_rules().http_rules()) { if (http_rule.header_matches_size() > 0) { has_headermatches_ = true; } - http_rules_.emplace_back(parent, http_rule); + http_rules_->emplace_back(parent, http_rule); } } if (l7_proto_.length() > 0 && rule.has_l7_rules()) { @@ -512,9 +513,9 @@ class PortNetworkPolicyRule : public Logger::Loggable { if (!allowed(proxy_id, remote_id, denied)) { return false; } - if (!http_rules_.empty()) { + if (hasHttpRules()) { bool allowed = false; - for (const auto& rule : http_rules_) { + for (const auto& rule : *http_rules_) { if (rule.allowed(headers)) { // Return on the first match if no rule has HeaderMatches if (!has_headermatches_) { @@ -647,9 +648,9 @@ class PortNetworkPolicyRule : public Logger::Loggable { res.append("]\n"); } - if (!http_rules_.empty()) { + if (hasHttpRules()) { res.append(indent, ' ').append("http_rules:\n"); - for (auto& rule : http_rules_) { + for (auto& rule : *http_rules_) { rule.toString(indent + 2, res); } } @@ -671,19 +672,20 @@ class PortNetworkPolicyRule : public Logger::Loggable { } } - bool hasHttpRules() const { return !http_rules_.empty(); } + bool hasHttpRules() const { return http_rules_ && !http_rules_->empty(); } std::string name_; - DownstreamTLSContextPtr server_context_; - UpstreamTLSContextPtr client_context_; + DownstreamTLSContextSharedPtr server_context_; + UpstreamTLSContextSharedPtr client_context_; bool has_headermatches_{false}; bool deny_; uint16_t proxy_id_; uint32_t precedence_; absl::btree_set remotes_; - std::vector allowed_snis_; // All SNIs allowed if empty. - std::vector http_rules_; // Allowed if empty, but remote is checked first. + std::vector allowed_snis_; // All SNIs allowed if empty. + std::shared_ptr> + http_rules_; // Allowed if empty, but remote is checked first. std::string l7_proto_{}; std::vector l7_allow_rules_; std::vector l7_deny_rules_; diff --git a/cilium/network_policy.h b/cilium/network_policy.h index 13c376564..e2ddf5cff 100644 --- a/cilium/network_policy.h +++ b/cilium/network_policy.h @@ -451,7 +451,7 @@ class SniPattern : public Logger::Loggable { bool isExplicitFullMatch() const { return !match_name_.empty(); } std::string match_name_; - std::unique_ptr matcher_; + std::shared_ptr matcher_; }; } // namespace Cilium diff --git a/cilium/secret_watcher.h b/cilium/secret_watcher.h index 703bd7900..c532d0ade 100644 --- a/cilium/secret_watcher.h +++ b/cilium/secret_watcher.h @@ -83,7 +83,7 @@ class DownstreamTLSContext : protected TLSContext { std::vector server_names_; Ssl::ServerContextSharedPtr server_context_ ABSL_GUARDED_BY(ssl_context_mutex_){}; }; -using DownstreamTLSContextPtr = std::unique_ptr; +using DownstreamTLSContextSharedPtr = std::shared_ptr; class UpstreamTLSContext : protected TLSContext { public: @@ -100,7 +100,7 @@ class UpstreamTLSContext : protected TLSContext { Ssl::ClientContextConfigPtr client_config_; Ssl::ClientContextSharedPtr client_context_ ABSL_GUARDED_BY(ssl_context_mutex_){}; }; -using UpstreamTLSContextPtr = std::unique_ptr; +using UpstreamTLSContextSharedPtr = std::shared_ptr; } // namespace Cilium } // namespace Envoy From a8caf83466d72c9a8a9635889b84bf50523aee8e Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Fri, 9 Jan 2026 11:47:53 -0800 Subject: [PATCH 3/8] api: Add support for pass verdict Signed-off-by: Jarno Rajahalme --- Makefile.api | 2 +- cilium/api/npds.proto | 13 +- go.mod | 1 - go/cilium/api/npds.pb.go | 66 +++- go/cilium/api/npds.pb.validate.go | 30 +- tests/cilium_network_policy_test.cc | 12 +- vendor/google.golang.org/protobuf/.gitignore | 9 - .../protobuf/CONTRIBUTING.md | 80 ---- vendor/google.golang.org/protobuf/README.md | 344 ------------------ vendor/google.golang.org/protobuf/SECURITY.md | 4 - .../protobuf/regenerate.bash | 7 - .../google.golang.org/protobuf/release.bash | 98 ----- vendor/google.golang.org/protobuf/test.bash | 7 - vendor/modules.txt | 1 - 14 files changed, 100 insertions(+), 574 deletions(-) delete mode 100644 vendor/google.golang.org/protobuf/.gitignore delete mode 100644 vendor/google.golang.org/protobuf/CONTRIBUTING.md delete mode 100644 vendor/google.golang.org/protobuf/README.md delete mode 100644 vendor/google.golang.org/protobuf/SECURITY.md delete mode 100644 vendor/google.golang.org/protobuf/regenerate.bash delete mode 100644 vendor/google.golang.org/protobuf/release.bash delete mode 100644 vendor/google.golang.org/protobuf/test.bash diff --git a/Makefile.api b/Makefile.api index de6cd2fc2..8939558c6 100644 --- a/Makefile.api +++ b/Makefile.api @@ -87,8 +87,8 @@ all: cilium-go-targets .PHONY: cilium-go-targets cilium-go-targets: $(CILIUM_PROTO_SOURCES) $(ENVOY_API_PROTO_PATH) Makefile.api + go install tool $(QUIET)set -e; \ - echo "NOTE: protoc tools referred to in go.mod tools section must be installed with "go install" for protoc to find them."; \ for path in $(CILIUM_PROTO_DIRS) ; do \ $(ECHO_GEN) envoy/$$path; \ $(PROTOC) -I $(ENVOY_API_PROTO_PATH) -I $(CILIUM_PROTO_PATH) $(PROTO_DEPS) "--go_out=plugins=grpc$(GO_MAPPINGS):$(GO_OUT)" --go_opt=module=github.com/cilium/proxy/go "--validate_out=lang=go$(GO_MAPPINGS):$(GO_OUT)" --validate_opt=module=github.com/cilium/proxy/go $${path}*.proto; \ diff --git a/cilium/api/npds.proto b/cilium/api/npds.proto index 7ab44ba04..d60335d96 100644 --- a/cilium/api/npds.proto +++ b/cilium/api/npds.proto @@ -127,15 +127,20 @@ message TLSContext { // If all the predicates of a rule match a flow, the flow is matched by the // rule. message PortNetworkPolicyRule { - reserved 1; // used in Cilium versions upto 1.14 - // Precedence level for this rule. Rules with **higher** numeric values take // precedence, even over deny rules of lower precedence level. // The lowest precedence (zero) is used when not specified. uint32 precedence = 10; - // Traffic on this port is denied for all `remote_policies` if true - bool deny = 8; + // Optional verdict, mutually exclusive. If missing then the verdict is an allow. + oneof verdict { + // Precedence after which policy evaluation should be continued at for the selected + // remotes_policies. + uint32 pass_precedence = 1; + + // Traffic on this port is denied for all `remote_policies` if true + bool deny = 8; + } // ProxyID is non-zero if the rule was an allow rule with an explicit listener reference. // The given value corresponds to the 'proxy_id' value in the BpfMetadata listener filter diff --git a/go.mod b/go.mod index 88e55c908..7cc7f9187 100644 --- a/go.mod +++ b/go.mod @@ -40,5 +40,4 @@ require ( tool ( github.com/envoyproxy/protoc-gen-validate github.com/golang/protobuf/protoc-gen-go - google.golang.org/protobuf ) diff --git a/go/cilium/api/npds.pb.go b/go/cilium/api/npds.pb.go index d3618c2fc..7d8094e97 100644 --- a/go/cilium/api/npds.pb.go +++ b/go/cilium/api/npds.pb.go @@ -430,8 +430,13 @@ type PortNetworkPolicyRule struct { // precedence, even over deny rules of lower precedence level. // The lowest precedence (zero) is used when not specified. Precedence uint32 `protobuf:"varint,10,opt,name=precedence,proto3" json:"precedence,omitempty"` - // Traffic on this port is denied for all `remote_policies` if true - Deny bool `protobuf:"varint,8,opt,name=deny,proto3" json:"deny,omitempty"` + // Optional verdict, mutually exclusive. If missing then the verdict is an allow. + // + // Types that are valid to be assigned to Verdict: + // + // *PortNetworkPolicyRule_PassPrecedence + // *PortNetworkPolicyRule_Deny + Verdict isPortNetworkPolicyRule_Verdict `protobuf_oneof:"verdict"` // ProxyID is non-zero if the rule was an allow rule with an explicit listener reference. // The given value corresponds to the 'proxy_id' value in the BpfMetadata listener filter // configuration. @@ -519,9 +524,27 @@ func (x *PortNetworkPolicyRule) GetPrecedence() uint32 { return 0 } +func (x *PortNetworkPolicyRule) GetVerdict() isPortNetworkPolicyRule_Verdict { + if x != nil { + return x.Verdict + } + return nil +} + +func (x *PortNetworkPolicyRule) GetPassPrecedence() uint32 { + if x != nil { + if x, ok := x.Verdict.(*PortNetworkPolicyRule_PassPrecedence); ok { + return x.PassPrecedence + } + } + return 0 +} + func (x *PortNetworkPolicyRule) GetDeny() bool { if x != nil { - return x.Deny + if x, ok := x.Verdict.(*PortNetworkPolicyRule_Deny); ok { + return x.Deny + } } return false } @@ -609,6 +632,25 @@ func (x *PortNetworkPolicyRule) GetL7Rules() *L7NetworkPolicyRules { return nil } +type isPortNetworkPolicyRule_Verdict interface { + isPortNetworkPolicyRule_Verdict() +} + +type PortNetworkPolicyRule_PassPrecedence struct { + // Precedence after which policy evaluation should be continued at for the selected + // remotes_policies. + PassPrecedence uint32 `protobuf:"varint,1,opt,name=pass_precedence,json=passPrecedence,proto3,oneof"` +} + +type PortNetworkPolicyRule_Deny struct { + // Traffic on this port is denied for all `remote_policies` if true + Deny bool `protobuf:"varint,8,opt,name=deny,proto3,oneof"` +} + +func (*PortNetworkPolicyRule_PassPrecedence) isPortNetworkPolicyRule_Verdict() {} + +func (*PortNetworkPolicyRule_Deny) isPortNetworkPolicyRule_Verdict() {} + type isPortNetworkPolicyRule_L7 interface { isPortNetworkPolicyRule_L7() } @@ -1177,13 +1219,14 @@ const file_cilium_api_npds_proto_rawDesc = "" + "\fserver_names\x18\x04 \x03(\tR\vserverNames\x12A\n" + "\x1dvalidation_context_sds_secret\x18\x05 \x01(\tR\x1avalidationContextSdsSecret\x12$\n" + "\x0etls_sds_secret\x18\x06 \x01(\tR\ftlsSdsSecret\x12%\n" + - "\x0ealpn_protocols\x18\a \x03(\tR\ralpnProtocols\"\xc9\x05\n" + + "\x0ealpn_protocols\x18\a \x03(\tR\ralpnProtocols\"\xfb\x05\n" + "\x15PortNetworkPolicyRule\x12\x1e\n" + "\n" + "precedence\x18\n" + " \x01(\rR\n" + - "precedence\x12\x12\n" + - "\x04deny\x18\b \x01(\bR\x04deny\x12$\n" + + "precedence\x12)\n" + + "\x0fpass_precedence\x18\x01 \x01(\rH\x00R\x0epassPrecedence\x12\x14\n" + + "\x04deny\x18\b \x01(\bH\x00R\x04deny\x12$\n" + "\bproxy_id\x18\t \x01(\rB\t\xfaB\x06*\x04\x18\xff\xff\x03R\aproxyId\x12\x12\n" + "\x04name\x18\x05 \x01(\tR\x04name\x12'\n" + "\x0fremote_policies\x18\a \x03(\rR\x0eremotePolicies\x12H\n" + @@ -1192,11 +1235,12 @@ const file_cilium_api_npds_proto_rawDesc = "" + "\fserver_names\x18\x06 \x03(\tB~\xfaB{\x92\x01x\"vrt2r^(([*]{1,2}|[*]?[-a-zA-Z0-9_]+([*][-a-zA-Z0-9_]+)*[*]?)[.])*([*]{1,2}|[*]?[-a-zA-Z0-9_]+([*][-a-zA-Z0-9_]+)*[*]?)$R\vserverNames\x12\x19\n" + "\bl7_proto\x18\x02 \x01(\tR\al7Proto\x12?\n" + "\n" + - "http_rules\x18d \x01(\v2\x1e.cilium.HttpNetworkPolicyRulesH\x00R\thttpRules\x12B\n" + - "\vkafka_rules\x18e \x01(\v2\x1f.cilium.KafkaNetworkPolicyRulesH\x00R\n" + + "http_rules\x18d \x01(\v2\x1e.cilium.HttpNetworkPolicyRulesH\x01R\thttpRules\x12B\n" + + "\vkafka_rules\x18e \x01(\v2\x1f.cilium.KafkaNetworkPolicyRulesH\x01R\n" + "kafkaRules\x129\n" + - "\bl7_rules\x18f \x01(\v2\x1c.cilium.L7NetworkPolicyRulesH\x00R\al7RulesB\x04\n" + - "\x02l7J\x04\b\x01\x10\x02\"`\n" + + "\bl7_rules\x18f \x01(\v2\x1c.cilium.L7NetworkPolicyRulesH\x01R\al7RulesB\t\n" + + "\averdictB\x04\n" + + "\x02l7\"`\n" + "\x16HttpNetworkPolicyRules\x12F\n" + "\n" + "http_rules\x18\x01 \x03(\v2\x1d.cilium.HttpNetworkPolicyRuleB\b\xfaB\x05\x92\x01\x02\b\x01R\thttpRules\"\xd2\x03\n" + @@ -1319,6 +1363,8 @@ func file_cilium_api_npds_proto_init() { return } file_cilium_api_npds_proto_msgTypes[3].OneofWrappers = []any{ + (*PortNetworkPolicyRule_PassPrecedence)(nil), + (*PortNetworkPolicyRule_Deny)(nil), (*PortNetworkPolicyRule_HttpRules)(nil), (*PortNetworkPolicyRule_KafkaRules)(nil), (*PortNetworkPolicyRule_L7Rules)(nil), diff --git a/go/cilium/api/npds.pb.validate.go b/go/cilium/api/npds.pb.validate.go index 17dc550da..e1b5b1a69 100644 --- a/go/cilium/api/npds.pb.validate.go +++ b/go/cilium/api/npds.pb.validate.go @@ -531,8 +531,6 @@ func (m *PortNetworkPolicyRule) validate(all bool) error { // no validation rules for Precedence - // no validation rules for Deny - if m.GetProxyId() > 65535 { err := PortNetworkPolicyRuleValidationError{ field: "ProxyId", @@ -622,6 +620,34 @@ func (m *PortNetworkPolicyRule) validate(all bool) error { // no validation rules for L7Proto + switch v := m.Verdict.(type) { + case *PortNetworkPolicyRule_PassPrecedence: + if v == nil { + err := PortNetworkPolicyRuleValidationError{ + field: "Verdict", + reason: "oneof value cannot be a typed-nil", + } + if !all { + return err + } + errors = append(errors, err) + } + // no validation rules for PassPrecedence + case *PortNetworkPolicyRule_Deny: + if v == nil { + err := PortNetworkPolicyRuleValidationError{ + field: "Verdict", + reason: "oneof value cannot be a typed-nil", + } + if !all { + return err + } + errors = append(errors, err) + } + // no validation rules for Deny + default: + _ = v // ensures v is used + } switch v := m.L7.(type) { case *PortNetworkPolicyRule_HttpRules: if v == nil { diff --git a/tests/cilium_network_policy_test.cc b/tests/cilium_network_policy_test.cc index 3004313c9..d0b1568b1 100644 --- a/tests/cilium_network_policy_test.cc +++ b/tests/cilium_network_policy_test.cc @@ -806,7 +806,7 @@ TEST_F(CiliumNetworkPolicyTest, HttpPolicyUpdate) { EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/publicz"}})); // 3rd update with Ingress deny rules - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "3" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -837,7 +837,7 @@ TEST_F(CiliumNetworkPolicyTest, HttpPolicyUpdate) { google_re2: {} regex: '.*public$' )EOF")); - EXPECT_EQ(version, "2"); + EXPECT_EQ(version, "3"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); expected = R"EOF(ingress: @@ -889,7 +889,7 @@ TEST_F(CiliumNetworkPolicyTest, HttpPolicyUpdate) { EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/publicz"}})); // 4th update with matching proxy_id in policy - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "4" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -920,7 +920,7 @@ TEST_F(CiliumNetworkPolicyTest, HttpPolicyUpdate) { google_re2: {} regex: '.*public$' )EOF")); - EXPECT_EQ(version, "2"); + EXPECT_EQ(version, "4"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); expected = R"EOF(ingress: @@ -966,7 +966,7 @@ TEST_F(CiliumNetworkPolicyTest, HttpPolicyUpdate) { EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 10001, {{":path", "/notallowed"}})); // 5th update with non-matching proxy_id in policy - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "5" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -997,7 +997,7 @@ TEST_F(CiliumNetworkPolicyTest, HttpPolicyUpdate) { google_re2: {} regex: '.*public$' )EOF")); - EXPECT_EQ(version, "2"); + EXPECT_EQ(version, "5"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); expected = R"EOF(ingress: diff --git a/vendor/google.golang.org/protobuf/.gitignore b/vendor/google.golang.org/protobuf/.gitignore deleted file mode 100644 index 698e891b6..000000000 --- a/vendor/google.golang.org/protobuf/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -/.cache/ -/.gocache/ -/bin/ -/vendor/ - -# This file includes artifacts of the system test that should not be checked in. -# For files created by specific development environment (e.g. editor), -# use alternative ways to exclude files from git. -# For example, set up .git/info/exclude or use a global .gitignore. diff --git a/vendor/google.golang.org/protobuf/CONTRIBUTING.md b/vendor/google.golang.org/protobuf/CONTRIBUTING.md deleted file mode 100644 index 1f8083bb5..000000000 --- a/vendor/google.golang.org/protobuf/CONTRIBUTING.md +++ /dev/null @@ -1,80 +0,0 @@ -# Contributing to Go Protocol Buffers - -Go protocol buffers is an open source project and accepts contributions. -The source of truth for this repository is at -[go.googlesource.com/protobuf](https://go.googlesource.com/protobuf). -The code review tool used is -[Gerrit Code Review](https://www.gerritcodereview.com/). -At this time, we are unfortunately unable to accept GitHub pull requests. - - -## Becoming a contributor - -The first step is to configure your environment. -Please follow the steps outlined in -["Becoming a contributor" (golang.org)](https://golang.org/doc/contribute.html#contributor) -as the setup for contributing to the `protobuf` project is identical -to that for contributing to the `go` project. - - -## Before contributing code - -The project welcomes submissions, but to make sure things are well coordinated -we ask that contributors discuss any significant changes before starting work. -Best practice is to connect your work to the -[issue tracker](https://github.com/golang/protobuf/issues), -either by filing a new issue or by claiming an existing issue. - - -## Sending a change via Gerrit - -The `protobuf` project performs development in Gerrit. -Below are the steps to send a change using Gerrit. - - -**Step 1:** Clone the Go source code: -``` -$ git clone https://go.googlesource.com/protobuf -``` - -**Step 2:** Setup a Git hook: -Setup a hook to run the tests prior to submitting changes to Gerrit: -``` -$ (cd protobuf/.git/hooks && echo -e '#!/bin/bash\n./test.bash' > pre-push && chmod a+x pre-push) -``` - -**Step 3:** Prepare changes in a new branch, created from the `master` branch. -To commit the changes, use `git codereview change`; -that will create or amend a single commit in the branch. - -``` -$ git checkout -b mybranch -$ [edit files...] -$ git add [files...] -$ git codereview change # create commit in the branch -$ [edit again...] -$ git add [files...] -$ git codereview change # amend the existing commit with new changes -$ [etc.] -``` - -**Step 4:** Send the changes for review to Gerrit using `git codereview mail`. -``` -$ git codereview mail # send changes to Gerrit -``` - -**Step 5:** After a review, there may be changes that are required. -Do so by applying changes to the same commit and mail them to Gerrit again: -``` -$ [edit files...] -$ git add [files...] -$ git codereview change # update same commit -$ git codereview mail # send to Gerrit again -``` - -When calling `git codereview mail`, it will call `git push` under the hood, -which will trigger the test hook that was setup in step 2. - -The [Contribution Guidelines](https://golang.org/doc/contribute.html) for the -Go project provides additional details that are also relevant to -contributing to the Go `protobuf` project. diff --git a/vendor/google.golang.org/protobuf/README.md b/vendor/google.golang.org/protobuf/README.md deleted file mode 100644 index 81d6438f4..000000000 --- a/vendor/google.golang.org/protobuf/README.md +++ /dev/null @@ -1,344 +0,0 @@ -# Go support for Protocol Buffers - -[![Go Reference](https://pkg.go.dev/badge/google.golang.org/protobuf.svg)](https://pkg.go.dev/google.golang.org/protobuf) -[![Build Status](https://travis-ci.org/protocolbuffers/protobuf-go.svg?branch=master)](https://travis-ci.org/protocolbuffers/protobuf-go) - -This project hosts the Go implementation for -[protocol buffers](https://protobuf.dev), which is a -language-neutral, platform-neutral, extensible mechanism for serializing -structured data. The protocol buffer language is a language for specifying the -schema for structured data. This schema is compiled into language specific -bindings. This project provides both a tool to generate Go code for the -protocol buffer language, and also the runtime implementation to handle -serialization of messages in Go. See the -[protocol buffer developer guide](https://protobuf.dev/overview) -for more information about protocol buffers themselves. - -This project is comprised of two components: - -* Code generator: The - [`protoc-gen-go`](https://pkg.go.dev/google.golang.org/protobuf/cmd/protoc-gen-go) - tool is a compiler plugin to `protoc`, the protocol buffer compiler. It - augments the `protoc` compiler so that it knows how to - [generate Go specific code for a given `.proto` file](https://protobuf.dev/reference/go/go-generated). - -* Runtime library: The - [`protobuf`](https://pkg.go.dev/mod/google.golang.org/protobuf) module - contains a set of Go packages that form the runtime implementation of - protobufs in Go. This provides the set of interfaces that - [define what a message is](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect) - and functionality to serialize message in various formats (e.g., - [wire](https://pkg.go.dev/google.golang.org/protobuf/proto), - [JSON](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson), - and - [text](https://pkg.go.dev/google.golang.org/protobuf/encoding/prototext)). - -See the -[developer guide for protocol buffers in Go](https://protobuf.dev/getting-started/gotutorial) -for a general guide for how to get started using protobufs in Go. - -This project is the second major revision of the Go protocol buffer API -implemented by the -[`google.golang.org/protobuf`](https://pkg.go.dev/mod/google.golang.org/protobuf) -module. The first major version is implemented by the -[`github.com/golang/protobuf`](https://pkg.go.dev/mod/github.com/golang/protobuf) -module. - -## Package index - -Summary of the packages provided by this module: - -* [`proto`](https://pkg.go.dev/google.golang.org/protobuf/proto): Package - `proto` provides functions operating on protobuf messages such as cloning, - merging, and checking equality, as well as binary serialization. -* [`encoding/protojson`](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson): - Package `protojson` serializes protobuf messages as JSON. -* [`encoding/prototext`](https://pkg.go.dev/google.golang.org/protobuf/encoding/prototext): - Package `prototext` serializes protobuf messages as the text format. -* [`encoding/protowire`](https://pkg.go.dev/google.golang.org/protobuf/encoding/protowire): - Package `protowire` parses and formats the low-level raw wire encoding. Most - users should use package `proto` to serialize messages in the wire format. -* [`reflect/protoreflect`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect): - Package `protoreflect` provides interfaces to dynamically manipulate - protobuf messages. -* [`reflect/protoregistry`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoregistry): - Package `protoregistry` provides data structures to register and lookup - protobuf descriptor types. -* [`reflect/protodesc`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protodesc): - Package `protodesc` provides functionality for converting - `descriptorpb.FileDescriptorProto` messages to/from the reflective - `protoreflect.FileDescriptor`. -* [`reflect/protopath`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protopath): - Package `protopath` provides a representation of a sequence of - protobuf reflection operations on a message. -* [`reflect/protorange`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protorange): - Package `protorange` provides functionality to traverse a protobuf message. -* [`testing/protocmp`](https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp): - Package `protocmp` provides protobuf specific options for the `cmp` package. -* [`testing/protopack`](https://pkg.go.dev/google.golang.org/protobuf/testing/protopack): - Package `protopack` aids manual encoding and decoding of the wire format. -* [`testing/prototest`](https://pkg.go.dev/google.golang.org/protobuf/testing/prototest): - Package `prototest` exercises the protobuf reflection implementation for - concrete message types. -* [`types/dynamicpb`](https://pkg.go.dev/google.golang.org/protobuf/types/dynamicpb): - Package `dynamicpb` creates protobuf messages at runtime from protobuf - descriptors. -* [`types/known/anypb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/anypb): - Package `anypb` is the generated package for `google/protobuf/any.proto`. -* [`types/known/timestamppb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/timestamppb): - Package `timestamppb` is the generated package for - `google/protobuf/timestamp.proto`. -* [`types/known/durationpb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/durationpb): - Package `durationpb` is the generated package for - `google/protobuf/duration.proto`. -* [`types/known/wrapperspb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/wrapperspb): - Package `wrapperspb` is the generated package for - `google/protobuf/wrappers.proto`. -* [`types/known/structpb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/structpb): - Package `structpb` is the generated package for - `google/protobuf/struct.proto`. -* [`types/known/fieldmaskpb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/fieldmaskpb): - Package `fieldmaskpb` is the generated package for - `google/protobuf/field_mask.proto`. -* [`types/known/apipb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/apipb): - Package `apipb` is the generated package for - `google/protobuf/api.proto`. -* [`types/known/typepb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/typepb): - Package `typepb` is the generated package for - `google/protobuf/type.proto`. -* [`types/known/sourcecontextpb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/sourcecontextpb): - Package `sourcecontextpb` is the generated package for - `google/protobuf/source_context.proto`. -* [`types/known/emptypb`](https://pkg.go.dev/google.golang.org/protobuf/types/known/emptypb): - Package `emptypb` is the generated package for - `google/protobuf/empty.proto`. -* [`types/descriptorpb`](https://pkg.go.dev/google.golang.org/protobuf/types/descriptorpb): - Package `descriptorpb` is the generated package for - `google/protobuf/descriptor.proto`. -* [`types/pluginpb`](https://pkg.go.dev/google.golang.org/protobuf/types/pluginpb): - Package `pluginpb` is the generated package for - `google/protobuf/compiler/plugin.proto`. -* [`compiler/protogen`](https://pkg.go.dev/google.golang.org/protobuf/compiler/protogen): - Package `protogen` provides support for writing protoc plugins. -* [`cmd/protoc-gen-go`](https://pkg.go.dev/google.golang.org/protobuf/cmd/protoc-gen-go): - The `protoc-gen-go` binary is a protoc plugin to generate a Go protocol - buffer package. - -## Reporting issues - -The issue tracker for this project is currently located at -[golang/protobuf](https://github.com/golang/protobuf/issues). - -Please report any issues there with a sufficient description of the bug or -feature request. Bug reports should ideally be accompanied by a minimal -reproduction of the issue. Irreproducible bugs are difficult to diagnose and fix -(and likely to be closed after some period of time). Bug reports must specify -the version of the -[Go protocol buffer module](https://github.com/protocolbuffers/protobuf-go/releases) -and also the version of the -[protocol buffer toolchain](https://github.com/protocolbuffers/protobuf/releases) -being used. - -## Contributing - -This project is open-source and accepts contributions. See the -[contribution guide](https://github.com/protocolbuffers/protobuf-go/blob/master/CONTRIBUTING.md) -for more information. - -## Compatibility - -This module and the generated code are expected to be stable over time. However, -we reserve the right to make breaking changes without notice for the following -reasons: - -* **Security:** A security issue in the specification or implementation may - come to light whose resolution requires breaking compatibility. We reserve - the right to address such issues. -* **Unspecified behavior:** There are some aspects of the protocol buffer - specification that are undefined. Programs that depend on unspecified - behavior may break in future releases. -* **Specification changes:** It may become necessary to address an - inconsistency, incompleteness, or change in the protocol buffer - specification, which may affect the behavior of existing programs. We - reserve the right to address such changes. -* **Bugs:** If a package has a bug that violates correctness, a program - depending on the buggy behavior may break if the bug is fixed. We reserve - the right to fix such bugs. -* **Generated additions**: We reserve the right to add new declarations to - generated Go packages of `.proto` files. This includes declared constants, - variables, functions, types, fields in structs, and methods on types. This - may break attempts at injecting additional code on top of what is generated - by `protoc-gen-go`. Such practice is not supported by this project. -* **Internal changes**: We reserve the right to add, modify, and remove - internal code, which includes all unexported declarations, the - [`protoc-gen-go/internal_gengo`](https://pkg.go.dev/google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo) - package, the - [`runtime/protoimpl`](https://pkg.go.dev/google.golang.org/protobuf/runtime/protoimpl?tab=doc) - package, and all packages under - [`internal`](https://pkg.go.dev/google.golang.org/protobuf/internal). - -Any breaking changes outside of these will be announced 6 months in advance to -[protobuf@googlegroups.com](https://groups.google.com/forum/#!forum/protobuf). - -Users should use generated code produced by a version of -[`protoc-gen-go`](https://pkg.go.dev/google.golang.org/protobuf/cmd/protoc-gen-go) -that is identical to the runtime version provided by the -[protobuf module](https://pkg.go.dev/mod/google.golang.org/protobuf). This -project promises that the runtime remains compatible with code produced by a -version of the generator that is no older than 1 year from the version of the -runtime used, according to the release dates of the minor version. Generated -code is expected to use a runtime version that is at least as new as the -generator used to produce it. Generated code contains references to -[`protoimpl.EnforceVersion`](https://pkg.go.dev/google.golang.org/protobuf/runtime/protoimpl?tab=doc#EnforceVersion) -to statically ensure that the generated code and runtime do not drift -sufficiently far apart. - -## Historical legacy - -This project is the second major revision -([released in 2020](https://blog.golang.org/a-new-go-api-for-protocol-buffers)) -of the Go protocol buffer API implemented by the -[`google.golang.org/protobuf`](https://pkg.go.dev/mod/google.golang.org/protobuf) -module. The first major version -([released publicly in 2010](https://blog.golang.org/third-party-libraries-goprotobuf-and)) -is implemented by the -[`github.com/golang/protobuf`](https://pkg.go.dev/mod/github.com/golang/protobuf) -module. - -The first version predates the release of Go 1 by several years. It has a long -history as one of the first core pieces of infrastructure software ever written -in Go. As such, the Go protobuf project was one of many pioneers for determining -what the Go language should even look like and what would eventually be -considered good design patterns and “idiomatic” Go (by simultaneously being -both positive and negative examples of it). - -Consider the changing signature of the `proto.Unmarshal` function as an example -of Go language and library evolution throughout the life of this project: - -```go -// 2007/09/25 - Conception of Go - -// 2008/11/12 -export func UnMarshal(r io.Read, pb_e reflect.Empty) *os.Error - -// 2008/11/13 -export func UnMarshal(buf *[]byte, pb_e reflect.Empty) *os.Error - -// 2008/11/24 -export func UnMarshal(buf *[]byte, pb_e interface{}) *os.Error - -// 2008/12/18 -export func UnMarshal(buf []byte, pb_e interface{}) *os.Error - -// 2009/01/20 -func UnMarshal(buf []byte, pb_e interface{}) *os.Error - -// 2009/04/17 -func UnMarshal(buf []byte, pb_e interface{}) os.Error - -// 2009/05/22 -func Unmarshal(buf []byte, pb_e interface{}) os.Error - -// 2011/11/03 -func Unmarshal(buf []byte, pb_e interface{}) error - -// 2012/03/28 - Release of Go 1 - -// 2012/06/12 -func Unmarshal(buf []byte, pb Message) error -``` - -These changes demonstrate the difficulty of determining what the right API is -for any new technology. It takes time multiplied by many users to determine what -is best; even then, “best” is often still somewhere over the horizon. - -The change on June 6th, 2012 added a degree of type-safety to Go protobufs by -declaring a new interface that all protobuf messages were required to implement: - -```go -type Message interface { - Reset() - String() string - ProtoMessage() -} -``` - -This interface reduced the set of types that can be passed to `proto.Unmarshal` -from the universal set of all possible Go types to those with a special -`ProtoMessage` marker method. The intention of this change is to limit the -protobuf API to only operate on protobuf data types (i.e., protobuf messages). -For example, there is no sensible operation if a Go channel were passed to the -protobuf API as a channel cannot be serialized. The restricted interface would -prevent that. - -This interface does not behaviorally describe what a protobuf message is, but -acts as a marker with an undocumented expectation that protobuf messages must be -a Go struct with a specific layout of fields with formatted tags. This -expectation is not statically enforced by the Go language, for it is an -implementation detail checked dynamically at runtime using Go reflection. Back -in 2012, the only types with this marker were those generated by -`protoc-gen-go`. Since `protoc-gen-go` would always generate messages with the -proper layout of fields, this was deemed an acceptable and dramatic improvement -over `interface{}`. - -Over the next 10 years, -[use of Go would skyrocket](https://blog.golang.org/10years) and use of -protobufs in Go would skyrocket as well. With increased popularity also came -more diverse usages and requirements for Go protobufs and an increased number of -custom `proto.Message` implementations that were not generated by -`protoc-gen-go`. - -The increasingly diverse ecosystem of Go types implementing the `proto.Message` -interface led to incompatibilities, which often occurred when: - -* **Passing custom `proto.Message` types to the protobuf APIs**: A concrete - message implementation might work with some top-level functions (e.g., - `proto.Marshal`), but cause others (e.g., `proto.Equal`) to choke and panic. - This occurs because the type only had partial support for being an actual - message by only implementing the `proto.Marshaler` interface or having - malformed struct field tags that happened to work with one function, but not - another. - -* **Using Go reflection on any `proto.Message` types**: A common desire is to - write general-purpose code that operates on any protobuf message. For - example, a microservice might want to populate a `trace_id` field if it is - present in a message. To accomplish this, one would use Go reflection to - introspect the message type, and assume it were a pointer to a Go struct - with a field named `TraceId` (as would be commonly produced by - `protoc-gen-go`). If the concrete message type did not match this - expectation, it either failed to work or even resulted in a panic. Such was - the case for concrete message types that might be backed by a Go map instead - of a Go struct. - -Both of these issues are solved by following the idiom that _interfaces should -describe behavior, not data_. This means that the interface itself should -provide sufficient functionality through its methods that users can introspect -and interact with all aspects of a protobuf message through a principled API. -This feature is called _protobuf reflection_. Just as how Go reflection provides -an API for programmatically interacting with any arbitrary Go value, protobuf -reflection provides an API for programmatically interacting with any arbitrary -protobuf message. - -Since an interface cannot be extended in a backwards compatible way, this -suggested the need for a new major version that defines a new `proto.Message` -interface: - -```go -type Message interface { - ProtoReflect() protoreflect.Message -} -``` - -The new -[`proto.Message`](https://pkg.go.dev/google.golang.org/protobuf/proto?tab=doc#Message) -interface contains a single `ProtoReflect` method that returns a -[`protoreflect.Message`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Message), -which is a reflective view over a protobuf message. In addition to making a -breaking change to the `proto.Message` interface, we took this opportunity to -cleanup the supporting functionality that operate on a `proto.Message`, split up -complicated functionality apart into manageable packages, and to hide -implementation details away from the public API. - -The goal for this major revision is to improve upon all the benefits of, while -addressing all the shortcomings of the old API. We hope that it will serve the -Go ecosystem well for the next 10 years and beyond. diff --git a/vendor/google.golang.org/protobuf/SECURITY.md b/vendor/google.golang.org/protobuf/SECURITY.md deleted file mode 100644 index 4648e5e3a..000000000 --- a/vendor/google.golang.org/protobuf/SECURITY.md +++ /dev/null @@ -1,4 +0,0 @@ -To report a security issue, please use [https://g.co/vulnz](https://g.co/vulnz). -We use g.co/vulnz for our intake, and do coordination and disclosure here on -GitHub (including using GitHub Security Advisory). The Google Security Team will -respond within 5 working days of your report on g.co/vulnz. diff --git a/vendor/google.golang.org/protobuf/regenerate.bash b/vendor/google.golang.org/protobuf/regenerate.bash deleted file mode 100644 index 44bdacbc7..000000000 --- a/vendor/google.golang.org/protobuf/regenerate.bash +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Copyright 2018 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -go test google.golang.org/protobuf -run='^TestIntegration$' -v -timeout=60m -count=1 "$@" -regenerate -exit $? diff --git a/vendor/google.golang.org/protobuf/release.bash b/vendor/google.golang.org/protobuf/release.bash deleted file mode 100644 index 0966da1d8..000000000 --- a/vendor/google.golang.org/protobuf/release.bash +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# Copyright 2019 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -cd "$(git rev-parse --show-toplevel)" - -read -p "What is the next release version (e.g., 'v1.26.0')? " VERSION -SEMVER_REGEX='^v\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([.a-zA-Z0-9A-Z-]*\)$' -if ! [[ -z $(echo $VERSION | sed -e "s/$SEMVER_REGEX//") ]]; then - echo; echo "invalid: must be a semver string"; exit 1 -fi -VERSION_MAJOR=$(echo $VERSION | sed -e "s/$SEMVER_REGEX/\1/") -VERSION_MINOR=$(echo $VERSION | sed -e "s/$SEMVER_REGEX/\2/") -VERSION_PATCH=$(echo $VERSION | sed -e "s/$SEMVER_REGEX/\3/") -VERSION_PRERELEASE=$(echo $VERSION | sed -e "s/$SEMVER_REGEX/\4/") -if ! [[ "$VERSION_MAJOR" =~ ^1$ ]]; then - echo; echo "invalid: major version must be 1"; exit 1 -fi -if ! [[ -z $VERSION_PRERELEASE ]] && ! [[ "$VERSION_PRERELEASE" =~ ^-rc[.][0-9]+$ ]]; then - echo; echo "invalid: pre-release suffix must be empty or '-rc.X'"; exit 1 -fi -VERSION_PRERELEASE=${VERSION_PRERELEASE#"-"} # trim possible leading dash - -function version_string() { - VERSION_STRING="v${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" - if ! [[ -z $VERSION_PRERELEASE ]]; then - VERSION_STRING="${VERSION_STRING}-${VERSION_PRERELEASE}" - fi - echo $VERSION_STRING -} - -read -p "Were there any changes to the generator that relies on new runtime functionality? " YN -case $YN in -[Yy]* ) - read -p " What minor version of the runtime is required now? " GEN_VERSION - if ! [[ "$GEN_VERSION" =~ ^[0-9]+$ ]]; then echo; echo "invalid: must be an integer"; exit 1; fi;; -[Nn]* ) ;; -* ) echo; echo "invalid: must be 'yes' or 'no'"; exit 1;; -esac - -read -p "Were there any dropped functionality in the runtime for old generated code? " YN -case $YN in -[Yy]* ) - read -p " What minor version of the runtime is required now? " MIN_VERSION - if ! [[ "$MIN_VERSION" =~ ^[0-9]+$ ]]; then echo; echo "invalid: must be an integer"; exit 1; fi;; -[Nn]* ) ;; -* ) echo; echo "invalid: must be 'yes' or 'no'"; exit 1;; -esac - - -echo -echo "Preparing changes to release $(version_string)." -echo - -set -e - -# Create a new branch to contain the release changes. -if [[ $(git branch --list release) ]]; then - echo "error: release branch already exists"; exit 1 -fi -git codereview change release -git codereview sync - -# Create commit for actual release. -INPLACE='-i ""' # BSD version of sed expects argument after -i -if [[ "$(sed --version)" == *"GNU"* ]]; then - INPLACE="-i" # GNU version of sed does not expect argument after -i -fi -sed $INPLACE -e "s/\(Minor *= *\)[0-9]*/\1$VERSION_MINOR/" internal/version/version.go -sed $INPLACE -e "s/\(Patch *= *\)[0-9]*/\1$VERSION_PATCH/" internal/version/version.go -sed $INPLACE -e "s/\(PreRelease *= *\)\"[^\"]*\"/\1\"$VERSION_PRERELEASE\"/" internal/version/version.go -if ! [[ -z $GEN_VERSION ]]; then - sed $INPLACE -e "s/\(GenVersion *= *\)[0-9]*/\1$GEN_VERSION/" runtime/protoimpl/version.go -fi -if ! [[ -z $MIN_VERSION ]]; then - sed $INPLACE -e "s/\(MinVersion *= *\)[0-9]*/\1$MIN_VERSION/" runtime/protoimpl/version.go -fi -git commit -a -m "all: release $(version_string)" - -# Build release binaries. -go test google.golang.org/protobuf -run='^TestIntegration$' -timeout=60m -count=1 "$@" -buildRelease - -# Create commit to start development after release. -VERSION_PRERELEASE="${VERSION_PRERELEASE}.devel" # append ".devel" -VERSION_PRERELEASE="${VERSION_PRERELEASE#"."}" # trim possible leading "." -sed $INPLACE -e "s/\(PreRelease *= *\)\"[^\"]*\"/\1\"$VERSION_PRERELEASE\"/" internal/version/version.go -git commit -a -m "all: start $(version_string)" - -echo -echo "Release changes prepared. Additional steps:" -echo " 1) Submit the changes:" -echo " a. Mail out the changes: git mail HEAD" -echo " b. Request a review on the changes and merge them." -echo " 2) Tag a new release on GitHub:" -echo " a. Write release notes highlighting notable changes." -echo " b. Attach pre-compiled binaries as assets to the release." -echo diff --git a/vendor/google.golang.org/protobuf/test.bash b/vendor/google.golang.org/protobuf/test.bash deleted file mode 100644 index b2e2f1c38..000000000 --- a/vendor/google.golang.org/protobuf/test.bash +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Copyright 2018 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -go test google.golang.org/protobuf -run='^TestIntegration$' -v -timeout=60m -count=1 -failfast "$@" -exit $? diff --git a/vendor/modules.txt b/vendor/modules.txt index 005308233..8d3eb2f0c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -192,7 +192,6 @@ google.golang.org/grpc/status google.golang.org/grpc/tap # google.golang.org/protobuf v1.36.11 ## explicit; go 1.23 -google.golang.org/protobuf google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo google.golang.org/protobuf/compiler/protogen From 628c370dd5dc1484e357b9daf19f91ecb0b18f6a Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Fri, 9 Jan 2026 12:10:28 -0800 Subject: [PATCH 4/8] policy: Parse pass_precedence Parse the new pass_precedence field. Signed-off-by: Jarno Rajahalme --- cilium/network_policy.cc | 51 +++++++++----- tests/cilium_network_policy_test.cc | 100 ++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 15 deletions(-) diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index af351a070..9c2272619 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -421,7 +421,13 @@ class PortNetworkPolicyRule : public Logger::Loggable { PortNetworkPolicyRule(const NetworkPolicyMapImpl& parent, const cilium::PortNetworkPolicyRule& rule) : name_(rule.name()), deny_(rule.deny()), proxy_id_(uint16_t(rule.proxy_id())), - precedence_(rule.precedence()), l7_proto_(rule.l7_proto()) { + precedence_(rule.precedence()), tier_last_precedence_(rule.pass_precedence()), + l7_proto_(rule.l7_proto()) { + if (tier_last_precedence_ > precedence_) { + throw EnvoyException( + fmt::format("PortNetworkPolicyRule: pass_precedence {} must be lower than precedence {}", + tier_last_precedence_, precedence_)); + } for (const auto& remote : rule.remote_policies()) { ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule(): {} remote {} by rule: {}", deny_ ? "Denying" : "Allowing", remote, name_); @@ -632,6 +638,10 @@ class PortNetworkPolicyRule : public Logger::Loggable { if (precedence_) { res.append(indent, ' ').append(fmt::format("precedence: {}\n", precedence_)); } + if (tier_last_precedence_) { + res.append(indent, ' ') + .append(fmt::format("tier_last_precedence: {}\n", tier_last_precedence_)); + } if (proxy_id_ != 0) { res.append(indent, ' ').append(fmt::format("proxy_id: {}\n", proxy_id_)); } @@ -681,6 +691,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { bool deny_; uint16_t proxy_id_; uint32_t precedence_; + uint32_t tier_last_precedence_; absl::btree_set remotes_; std::vector allowed_snis_; // All SNIs allowed if empty. @@ -703,13 +714,24 @@ class PortNetworkPolicyRules : public Logger::Loggable { } } + void updateFor(const PortNetworkPolicyRuleConstSharedPtr& rule) { + if (rule->has_headermatches_) { + can_short_circuit_ = false; + } + if (rule->tier_last_precedence_ != 0) { + has_pass_rules_ = true; + } + } + + void insert(PortNetworkPolicyRuleConstSharedPtr rule) { + rules_.emplace_back(rule); + updateFor(rules_.back()); + } + void append(const NetworkPolicyMapImpl& parent, const Protobuf::RepeatedPtrField& rules) { for (const auto& it : rules) { - rules_.emplace_back(std::make_shared(parent, it)); - if (rules_.back()->has_headermatches_) { - can_short_circuit_ = false; - } + insert(std::make_shared(parent, it)); } } @@ -717,20 +739,18 @@ class PortNetworkPolicyRules : public Logger::Loggable { const Protobuf::RepeatedPtrField& rules) { for (const auto& it : rules) { rules_.emplace(rules_.begin(), std::make_shared(parent, it)); - if (rules_.front()->has_headermatches_) { - can_short_circuit_ = false; - } + updateFor(rules_.front()); } } - // sort by descending precedence + // sort by descending precedence, retaining the original order within each precedence level void sort() { - std::sort(rules_.begin(), rules_.end(), - [](const PortNetworkPolicyRuleConstSharedPtr& a, - const PortNetworkPolicyRuleConstSharedPtr& b) { - return (a->precedence_ > b->precedence_) || - (a->precedence_ == b->precedence_ && (a->deny_ && !b->deny_)); - }); + std::stable_sort(rules_.begin(), rules_.end(), + [](const PortNetworkPolicyRuleConstSharedPtr& a, + const PortNetworkPolicyRuleConstSharedPtr& b) { + return (a->precedence_ > b->precedence_) || + (a->precedence_ == b->precedence_ && (a->deny_ && !b->deny_)); + }); } bool empty() const { return rules_.empty(); } @@ -915,6 +935,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { // ordered set of rules as a sorted vector std::vector rules_; // Allowed if empty. bool can_short_circuit_{true}; + bool has_pass_rules_{false}; }; // end port is zero on lookup! diff --git a/tests/cilium_network_policy_test.cc b/tests/cilium_network_policy_test.cc index d0b1568b1..129b692d2 100644 --- a/tests/cilium_network_policy_test.cc +++ b/tests/cilium_network_policy_test.cc @@ -1043,6 +1043,106 @@ TEST_F(CiliumNetworkPolicyTest, HttpPolicyUpdate) { EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 10001, {{":path", "/notallowed"}})); } +TEST_F(CiliumNetworkPolicyTest, Precedence) { + 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(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + + // pass_precedence must be lower than precedence + EXPECT_THROW_WITH_MESSAGE( + 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: + - precedence: 1000 + pass_precedence: 2000 + remote_policies: [ 43 ] +)EOF"), + EnvoyException, + "PortNetworkPolicyRule: pass_precedence 2000 must be lower than precedence 1000"); + + // deny and pass_precedence are mutually exclusive + EXPECT_THROW_WITH_REGEX(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: + - deny: true + precedence: 1000 + pass_precedence: 100 + remote_policies: [ 43 ] +)EOF"), + EnvoyException, + "Unable to parse JSON as proto.*INVALID_ARGUMENT:.*oneof"); + + // 1st update: higher precedence deny + 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: + - precedence: 1000 + deny: true + - precedence: 100 + remote_policies: [ 43 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allowed' +)EOF")); + EXPECT_EQ(version, "1"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [] + deny: true + precedence: 1000 + - remotes: [43] + precedence: 100 + http_rules: + - headers: + - name: ":path" + value: "/allowed" +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected)); + + // Allowed remote ID, port, & path: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + // Wrong remote ID: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 40, 80, {{":path", "/allowed"}})); + // Wrong port: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 8080, {{":path", "/allowed"}})); + // Wrong path: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/notallowed"}})); + + // No egress is allowed: + EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); +} + TEST_F(CiliumNetworkPolicyTest, HttpOverlappingPortRanges) { std::string version; EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "0" From fdac076f6879281d02ce4807149fdea313f731c2 Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Fri, 9 Jan 2026 12:04:02 -0800 Subject: [PATCH 5/8] policy: Add isRemoteWildcard() Improve readability with the new isRemoteWildcard helper. Signed-off-by: Jarno Rajahalme --- cilium/network_policy.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index 9c2272619..2ca5496d4 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -465,13 +465,15 @@ class PortNetworkPolicyRule : public Logger::Loggable { } } + bool isRemoteWildcard() const { return remotes_.empty(); } + bool allowed(uint16_t proxy_id, uint32_t remote_id, bool& denied) const { // proxy_id must match if we have any. if (proxy_id_ != 0 && proxy_id != proxy_id_) { return false; } // Remote ID must match if we have any. - if (!remotes_.empty()) { + if (!isRemoteWildcard()) { auto match = remotes_.find(remote_id); if (match != remotes_.end()) { // remote ID matched From feecfdf34154816f44a31f20abfdbece8a3531f7 Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Thu, 8 Jan 2026 16:11:22 -0800 Subject: [PATCH 6/8] policy: Add support for pass verdict Pre-process policy rules with pass verdicts on on policy updates. This involves promoting the precedence of lower tier rules based on the pass verdicts on higher tiers. Depending on the remote identities being matched, the lower tier rule may need to be split up to different sets of remote identities, or if remotes match exactly then the whole rule can be promoted to the higher tier. Splitting up, as reqiuired, increases the number of rules. We counter this by keeping track of "shadowed" identities of higher tier rules, both within a tier and between tiers, and eliminate rules that can not be matched when they become completely shadowed. Signed-off-by: Jarno Rajahalme --- cilium/network_policy.cc | 486 +++++++++++++++++- tests/cilium_network_policy_test.cc | 764 ++++++++++++++++++++++++++++ 2 files changed, 1249 insertions(+), 1 deletion(-) diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index 2ca5496d4..30ae3ea12 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -4,8 +4,11 @@ #include #include +#include #include +#include #include +#include #include #include #include @@ -465,6 +468,13 @@ class PortNetworkPolicyRule : public Logger::Loggable { } } + // inheritpassprecedence bumps up the precedence of a rule in a lower tier to the precedence + // range reserved right after the precedence of the given pass rule. + void inheritPassPrecedence(const PortNetworkPolicyRule& pass_rule) { + precedence_ -= pass_rule.tier_last_precedence_; + precedence_ += pass_rule.precedence_; + } + bool isRemoteWildcard() const { return remotes_.empty(); } bool allowed(uint16_t proxy_id, uint32_t remote_id, bool& denied) const { @@ -703,6 +713,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { std::vector l7_allow_rules_; std::vector l7_deny_rules_; }; +using PortNetworkPolicyRuleSharedPtr = std::shared_ptr; using PortNetworkPolicyRuleConstSharedPtr = std::shared_ptr; class PortNetworkPolicyRules : public Logger::Loggable { @@ -716,6 +727,22 @@ class PortNetworkPolicyRules : public Logger::Loggable { } } + void clear() { + rules_.clear(); + can_short_circuit_ = true; + has_pass_rules_ = false; + } + + // Move assignment operator + PortNetworkPolicyRules& operator=(PortNetworkPolicyRules&& other) noexcept = default; + + // Move constructor + PortNetworkPolicyRules(PortNetworkPolicyRules&& other) noexcept = default; + + // Copy constructors + PortNetworkPolicyRules& operator=(const PortNetworkPolicyRules&) = default; + PortNetworkPolicyRules(const PortNetworkPolicyRules&) = default; + void updateFor(const PortNetworkPolicyRuleConstSharedPtr& rule) { if (rule->has_headermatches_) { can_short_circuit_ = false; @@ -747,6 +774,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { // sort by descending precedence, retaining the original order within each precedence level void sort() { + // sortRules(rules_); std::stable_sort(rules_.begin(), rules_.end(), [](const PortNetworkPolicyRuleConstSharedPtr& a, const PortNetworkPolicyRuleConstSharedPtr& b) { @@ -1057,6 +1085,434 @@ bool inline rangesOverlap(const PortRange& a, const PortRange& b) { return a.second >= b.first && a.first <= b.second; } +template +absl::btree_set intersection(const absl::btree_set& a, const absl::btree_set& b) { + absl::btree_set result; + std::set_intersection(a.begin(), a.end(), b.begin(), b.end(), + std::inserter(result, result.begin())); + return result; +} + +// ShadowedRemotes maintains state for shadowed remote identities within a tier. When an higher +// precedence rule has a verdict for a given remote identity, that identity becomes "shadowed" and +// is removed from the set of remote identities of the remaining rules of the tier. For pass +// verdicts this shadowing is immediate, for allow/deny verdicts the shadowing takes place for the +// next precedence level, so that rules on the same precedence level do not shadow each other. +class ShadowedRemotes { +public: + // reset is used to re-initialize state for new port range + void reset(uint32_t first_precedence) { + shadowed_pass_remotes_.clear(); + shadowed_nonpass_remotes_.clear(); + current_precedence_nonpass_remotes_.clear(); + shadow_all_lower_precedence_ = false; + current_precedence_has_wildcard_ = false; + previous_precedence_ = first_precedence; + } + + // resetForNewTier is used to re-initialize state for each new tier + void resetForNewTier(uint32_t first_precedence) { + shadowed_pass_remotes_.clear(); + // shadowed_nonpass_remotes_ are kept for the new tier + // shadow_all_lower_precedence_ is not reset for the new tier + current_precedence_nonpass_remotes_.clear(); + // current_precedence_has_wildcard_ = false; + previous_precedence_ = first_precedence; + } + + // shadowRemotes marks 'remotes' as shadowed and returns 'true' if any of them were not already + // shadowed. + bool shadowPassRemotes(const absl::btree_set& pass_remotes) { + bool any_unshadowed = false; + for (auto remote : pass_remotes) { + if (!shadowed_pass_remotes_.contains(remote)) { + if (!shadowed_nonpass_remotes_.contains(remote)) { + any_unshadowed = true; + } + shadowed_pass_remotes_.insert(remote); + } + } + return any_unshadowed; + } + + // filterShadowPassRemotes filters out any already shadowed remotes from the pass rule and marks + // the remaining remotes as shadowed. Returns 'true' if any remotes remain. + bool filterShadowPassRemotes(PortNetworkPolicyRule& rule) { + absl::erase_if(rule.remotes_, [&](const auto& x) { + return shadowed_pass_remotes_.contains(x) || shadowed_nonpass_remotes_.contains(x); + }); + if (rule.remotes_.empty()) { + return false; + } + shadowed_pass_remotes_.insert(rule.remotes_.begin(), rule.remotes_.end()); + return true; + } + + // shadowRule collects the remotes from 'rule' for shadowing once the first rule on the next + // (lower) precedence level is processed. No shadowing between rules on the same precedence + // level. Returns 'true' if the rule itself should be skipped. + bool shadowNonpassRule(PortNetworkPolicyRule& rule) { + // Same-precedence allow/deny rules do not shadow each other. + // Only after leaving a precedence level do its verdict identities shadow + // lower-precedence rules. + if (rule.precedence_ != previous_precedence_) { + shadowed_nonpass_remotes_.insert(current_precedence_nonpass_remotes_.begin(), + current_precedence_nonpass_remotes_.end()); + current_precedence_nonpass_remotes_.clear(); + if (current_precedence_has_wildcard_) { + shadow_all_lower_precedence_ = true; + } + // current_precedence_has_wildcard_ = false; + previous_precedence_ = rule.precedence_; + } + + if (shadow_all_lower_precedence_) { + return true; + } + + if (rule.isRemoteWildcard()) { + current_precedence_has_wildcard_ = true; + } else { + // Check if this rule is (partially) shadowed by nonpass rules on any higher tier. + if (!shadowed_nonpass_remotes_.empty()) { + absl::erase_if(rule.remotes_, + [&](const auto& x) { return shadowed_nonpass_remotes_.contains(x); }); + if (rule.remotes_.empty()) { + return true; + } + } + + // Check if this rule is (partially) shadowed by pass rules on this tier. + if (!shadowed_pass_remotes_.empty()) { + absl::erase_if(rule.remotes_, + [&](const auto& x) { return shadowed_pass_remotes_.contains(x); }); + if (rule.remotes_.empty()) { + return true; + } + } + + // Defer shadowing identities until this precedence level is complete, + // so same-precedence rules do not shadow each other. + current_precedence_nonpass_remotes_.insert(rule.remotes_.begin(), rule.remotes_.end()); + } + + return false; + } + +private: + absl::btree_set shadowed_pass_remotes_; + absl::btree_set shadowed_nonpass_remotes_; + absl::btree_set current_precedence_nonpass_remotes_; + uint32_t previous_precedence_{0}; + bool shadow_all_lower_precedence_{false}; + bool current_precedence_has_wildcard_{false}; +}; + +class Passes { +public: + // reset state for a new port range + void reset(uint32_t first_precedence) { + wildcard_it_ = wildcard_pass_rules_.begin(); + pass_rules_.clear(); + pass_rules_tier_index_.clear(); + + tier_pass_rules_.clear(); + tier_wildcard_pass_ = false; + pass_precedence_ = 0; + + shadowing_.reset(first_precedence); + } + + void resetForNextTier(uint32_t first_precedence) { + // Move the collected pass rules to be considered for lower tiers. + pass_rules_tier_index_.push_back(pass_rules_.size()); + pass_rules_.insert(pass_rules_.end(), std::make_move_iterator(tier_pass_rules_.begin()), + std::make_move_iterator(tier_pass_rules_.end())); + + tier_pass_rules_.clear(); + tier_wildcard_pass_ = false; + pass_precedence_ = 0; + + shadowing_.resetForNewTier(first_precedence); + } + + void inheritHigherTierWildcardPassRules(uint32_t precedence) { + // Inherit *higher tier* pass rules from the wildcard port. + // Needed since the wildcard port may have more tiers. + for (; wildcard_it_ != wildcard_pass_rules_.end() && + (*wildcard_it_)->tier_last_precedence_ > precedence; + wildcard_it_++) { + const auto& wildcard_rule = *wildcard_it_; + // Add index entry if this starts a new pass tier. + if (pass_rules_.empty() || + wildcard_rule->tier_last_precedence_ != pass_rules_.back()->tier_last_precedence_) { + pass_rules_tier_index_.push_back(pass_rules_.size()); + } + pass_rules_.insert(pass_rules_.end(), wildcard_rule); + } + } + + void inheritCurrentTierWildcardPassRules(uint32_t precedence) { + // Inherit *current tier* higher or equal precedence pass rules from wildcard port. + for (; + wildcard_it_ != wildcard_pass_rules_.end() && (*wildcard_it_)->precedence_ >= precedence && + (*wildcard_it_)->tier_last_precedence_ <= precedence; + wildcard_it_++) { + const auto& wildcard_rule = *wildcard_it_; + + ensurePassPrecedence(wildcard_rule->tier_last_precedence_); + + if (tier_wildcard_pass_) { + continue; + } + + // Insert to tier_pass_rules_ if any unshadowed remotes remain. + if (wildcard_rule->isRemoteWildcard()) { + tier_wildcard_pass_ = true; + } else if (!shadowing_.shadowPassRemotes(wildcard_rule->remotes_)) { + // Only insert if some remotes are not already shadowed. + // We do not remove already-shadowed remotes from wildcard_rule because + // wildcard pass entries are shared and deep-copying here is expensive. + continue; + } + tier_pass_rules_.emplace_back(wildcard_rule); + } + } + + // addPassRule adds state from a rule with a pass verdict. + void addPassRule(const PortNetworkPolicyRuleConstSharedPtr& rule) { + ensurePassPrecedence(rule->tier_last_precedence_); + + auto& mutable_rule = const_cast(*rule); + + if (!tier_wildcard_pass_) { + if (mutable_rule.isRemoteWildcard()) { + tier_wildcard_pass_ = true; + } else if (!shadowing_.filterShadowPassRemotes(mutable_rule)) { + return; + } + if (!tier_pass_rules_.empty() && tier_pass_rules_.back()->precedence_ == rule->precedence_) { + // Same-precedence pass rule already exists; merge remotes. + tier_pass_rules_.back()->remotes_.insert(mutable_rule.remotes_.begin(), + mutable_rule.remotes_.end()); + } else { + tier_pass_rules_.emplace_back(std::const_pointer_cast(rule)); + } + } + } + + bool + promoteRuleFromHigherTierPasses(std::vector& rules, + std::vector::iterator& it) { + // Mutable reference to the rule for in-place updates below. + auto& rule = const_cast(**it); + + bool promoted = false; + + // Check if this rule needs to be (partially) promoted due to higher-tier passes: + // - pick highest-precedence pass from each higher tier for each remote ID + // - apply in reverse order of tiers + // - if all remotes are covered, promotion can happen fully in-place. + int tier_end = pass_rules_.size(); + for (int tier_start : pass_rules_tier_index_ | std::views::reverse) { + // Skip pass rules on same or lower tiers. + if (pass_rules_[tier_start]->tier_last_precedence_ < rule.precedence_) { + continue; + } + + for (int idx = tier_start; idx < tier_end; idx++) { + auto& pass_rule = pass_rules_[idx]; + // Whole rule is promoted in-place if pass is wildcard or sets are equal. + if (pass_rule->isRemoteWildcard() || rule.remotes_ == pass_rule->remotes_) { + rule.inheritPassPrecedence(*pass_rule); + promoted = true; + break; // Later pass verdicts on this tier have no effect. + } + + // Pass rule is not wildcard and sets differ. + // If mutable_rule is wildcard, keep original and add promoted clone. + if (rule.isRemoteWildcard()) { + auto new_rule = std::make_shared(rule); + new_rule->remotes_ = pass_rule->remotes_; + new_rule->inheritPassPrecedence(*pass_rule); + it = rules.insert(it, new_rule); + it++; + promoted = true; + continue; // Later pass verdicts may specify other remote sets. + } + + // Neither side is wildcard; split by set intersection. + auto remotes = intersection(pass_rule->remotes_, rule.remotes_); + if (!remotes.empty()) { + auto new_rule = std::make_shared(rule); + new_rule->remotes_ = remotes; + new_rule->inheritPassPrecedence(*pass_rule); + it = rules.insert(it, new_rule); + it++; + promoted = true; + for (const auto& remote : remotes) { + rule.remotes_.erase(remote); + } + } + } + // Update for previous tier, if any. + tier_end = tier_start; + } + + return promoted; + } + + // storeWildcardPassRules stores the current pass rules as wildcard port pass rules to be + // considered when processing non-wildcard port rules. + void storeWildcardPassRules(const PortRange& port_range) { + if (!wildcard_pass_rules_.empty()) { + throw EnvoyException(fmt::format("PortNetworkPolicy: Wildcard port range {}-{}, but " + "wildcard pass rules has already been set", + port_range.first, port_range.second)); + } + wildcard_pass_rules_ = pass_rules_; + + // store also the pass rules for the last tier, as they are not yet included in pass_rules_. + if (pass_precedence_ != 0 && !tier_pass_rules_.empty()) { + wildcard_pass_rules_.insert(wildcard_pass_rules_.end(), + std::make_move_iterator(tier_pass_rules_.begin()), + std::make_move_iterator(tier_pass_rules_.end())); + } + } + + // Applies pass verdicts for rules on a given port range. + // Returns true if resulting rules should be kept, false if the rules became empty. + // + // - a pass verdict rule applies on a given port (range) (can be the wildcard port), + // and a set of remote IDs (L3). If the remote ID set is empty, then it applies to all peers. + // (there is no "wildcard identity" (e.g., '0') in the set of the remote IDs) + // - a pass verdict rule (like a deny verdict rule) has no L7 rule components + // - each pass verdict rule has a specific precedence and pass_precedence, and the function is + // to bypass the remaining lower precedence rules upto the pass_precedence, and to promote the + // priority of the intersecting remote ID set lower precedence rules to immediately follow + // the precedence of the pass verdict rule. + // - Precedence promotion is needed to make a verdicts found via actual port vs. wildcard port + // comparable. This allows a higher precedence allow rule to take precedence over a lower + // precedence deny rule, even it the allow originally had a lower precedence, but was + // "passed-to" from a higher precedece pass rule. + // + // We pre-process the rules accordingly here so that the policy lookup at enforcement time + // does not need to consider pass verdicts at all. The key insights to consider are: + // - rules are already split up to non-overlapping port ranges, so we only need + // to consider the remote ID sets (and the wildcard port) + // - if the lower precedence rule remote ID set is covered by the pass rule remote ID set, + // then we can simply promote the precedence (and re-sort afterwards) + // - if the pass rule applies to all remote IDs (empty set == wildcard), then it covers all + // possible sets of remote IDs + // - if not wildcard, but the sets are the same, then the pass verdicts "covers" the rule + // in question + // - otherwise the rule needs to be split into two: + // - one with the intersection of the remote IDs of the two rules, with precedence promotion + // - other with the remaining remote IDs, left with the original precedence + // - this includes the case where the lower precedence rule applies to all identities + // (empty ID set) + void apply(const PortRange& port_range, PortNetworkPolicyRules& rules) { + if (!rules.rules_.empty() && (!wildcard_pass_rules_.empty() || rules.has_pass_rules_)) { + bool must_sort = false; + + // reset state for the new range's rules + reset(rules.rules_.front()->precedence_); + + bool keep = false; // assume rule is dropped + for (auto it = rules.rules_.begin(); it != rules.rules_.end(); + it = keep ? (keep = false, ++it) : rules.rules_.erase(it)) { + auto& rule = *it; + + // Check if we have reached the next tier. + if (pass_precedence_ != 0 && rule->precedence_ < pass_precedence_) { + resetForNextTier(rule->precedence_); + } + + // Skip remaining rules on this tier? + if (tier_wildcard_pass_) { + continue; + } + + // Inherit wildcard-port pass rules affecting this rule. + inheritHigherTierWildcardPassRules(rule->precedence_); + inheritCurrentTierWildcardPassRules(rule->precedence_); + + // skip remaining rules on this tier? + if (tier_wildcard_pass_) { + continue; + } + + // Is this a pass verdict rule? + if (rule->tier_last_precedence_ != 0) { + addPassRule(rule); + // Pass rules are not kept + continue; + } + + // Is the rule shadowed? (If not then updates shadowed state) + if (shadowing_.shadowNonpassRule(const_cast(*rule))) { + continue; + } + + // Apply passes to the rule and insert. + if (promoteRuleFromHigherTierPasses(rules.rules_, it)) { + must_sort = true; + } + // keep rule in place + keep = true; + } + + // Have to sort if precedences have been updated in-place. + if (must_sort) { + rules.sort(); + + // remove shadowed rules due to promoted precedences + shadowing_.reset(rules.rules_.front()->precedence_); + for (auto it = rules.rules_.begin(); it != rules.rules_.end(); + it = keep ? ++it : rules.rules_.erase(it)) { + keep = !shadowing_.shadowNonpassRule(const_cast(**it)); + } + } + + // Store wildcard port passes for consideration for non-wildcard ports. + if (port_range.first == 0) { + storeWildcardPassRules(port_range); + } + + // Mark ranges with no rules for removal. + if (rules.empty()) { + // Empty rule set would always allow. Mark for removal. + empty_ranges_.push_back(port_range); + } + } + } + + std::vector& emptyRanges() { return empty_ranges_; } + +private: + void ensurePassPrecedence(uint32_t tier_last_precedence) { + // pass_precedence_ is non-zero when a pass verdict has been seen and + // defines the end of the current tier. All pass verdicts on a specific + // tier must have the same pass_precedence so tier boundaries stay unambiguous. + if (tier_last_precedence == 0 || + (pass_precedence_ != 0 && tier_last_precedence != pass_precedence_)) { + throw EnvoyException(fmt::format("PortNetworkPolicy: Inconsistent pass precedence {} != {}", + tier_last_precedence, pass_precedence_)); + } + pass_precedence_ = tier_last_precedence; + } + + std::vector empty_ranges_; + std::vector wildcard_pass_rules_; + std::vector::iterator wildcard_it_; + ShadowedRemotes shadowing_; + std::vector pass_rules_; + std::vector pass_rules_tier_index_; + uint32_t pass_precedence_{0}; + std::vector tier_pass_rules_; + bool tier_wildcard_pass_{false}; +}; + class PortNetworkPolicy : public Logger::Loggable { public: PortNetworkPolicy(const NetworkPolicyMapImpl& parent, @@ -1215,6 +1671,10 @@ class PortNetworkPolicy : public Logger::Loggable { // Exact port rules go to the front of the list. // This gives precedence for trivial range rules for proxylib parser // and TLS context selection. + // prepend() inserts each rule at begin() while iterating forward, + // so the relative order of rules from this batch is reversed. This + // is harmless: equal-precedence rules are evaluated as alternatives + // (stable sort only affects presentation/debug ordering). rules.prepend(parent, rule.rules()); } else { // Rules with a non-trivial range go to the back of the list @@ -1226,9 +1686,33 @@ class PortNetworkPolicy : public Logger::Loggable { } } - // sort rules into descending precedence + bool have_passes = false; + + // sort rules on each non-overlapping port range into descending precedence + // port ranges themselves remain in the sorted order. + // This way we can efficiently find the list of rules applicable to any given port, + // and then process those rules in the order of decreasing precedence. for (auto& pair : rules_) { pair.second.sort(); + if (pair.second.has_pass_rules_) { + have_passes = true; + } + } + + // Apply pass verdicts, if any. + if (have_passes) { + Passes passes; + + // This loop always iterates the wildcard port first, if rules for it exist. + for (auto& [port_range, rules] : rules_) { + passes.apply(port_range, rules); + } + + // Delete port ranges that only contained pass rules. + // Otherwise the policy would always accept. + for (auto port_range : passes.emptyRanges()) { + rules_.erase(port_range); + } } } diff --git a/tests/cilium_network_policy_test.cc b/tests/cilium_network_policy_test.cc index 129b692d2..b6fd86c15 100644 --- a/tests/cilium_network_policy_test.cc +++ b/tests/cilium_network_policy_test.cc @@ -1088,7 +1088,32 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EnvoyException, "Unable to parse JSON as proto.*INVALID_ARGUMENT:.*oneof"); + // pass rules on the same tier must use a consistent pass_precedence. + // "pass_precedence" defines the last (lowest) precedence on the "tier". + // any pass rules with precedence higher than the previous pass_precedence + // must have the same pass_precedence. + EXPECT_THROW_WITH_MESSAGE(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: + - precedence: 1000 + pass_precedence: 100 + remote_policies: [ 43 ] + - precedence: 900 + pass_precedence: 200 + remote_policies: [ 44 ] +)EOF"), + EnvoyException, + "PortNetworkPolicy: Inconsistent pass precedence 200 != 100"); + + // // 1st update: higher precedence deny + // EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "1" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy @@ -1141,6 +1166,745 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { // No egress is allowed: EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); + + // + // 2nd update: pass for '43' + // + 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: + - precedence: 1000 + pass_precedence: 501 + remote_policies: [ 43 ] + - precedence: 900 + deny: true + - precedence: 500 + remote_policies: [ 43 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allowed' +)EOF")); + EXPECT_EQ(version, "2"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected2 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [43] + precedence: 999 + http_rules: + - headers: + - name: ":path" + value: "/allowed" + - remotes: [] + deny: true + precedence: 900 +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected2)); + + // Allowed remote ID, port, & path: + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + // Wrong remote ID: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 40, 80, {{":path", "/allowed"}})); + // Wrong port: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 8080, {{":path", "/allowed"}})); + // Wrong path: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/notallowed"}})); + + // No egress is allowed: + EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); + + // + // 3rd update: pass with partial overlap + // + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "3" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 80 + rules: + - precedence: 1000 + pass_precedence: 501 + remote_policies: [ 43 ] + - precedence: 900 + deny: true + - precedence: 500 + remote_policies: [ 43, 44 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allowed' +)EOF")); + EXPECT_EQ(version, "3"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected3 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [43] + precedence: 999 + http_rules: + - headers: + - name: ":path" + value: "/allowed" + - remotes: [] + deny: true + precedence: 900 +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected3)); + + // Allowed remote ID, port, & path: + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + // Denied remote ID, port, & path: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/allowed"}})); + // Wrong remote ID: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 40, 80, {{":path", "/allowed"}})); + // Wrong port: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 8080, {{":path", "/allowed"}})); + // Wrong path: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/notallowed"}})); + + // No egress is allowed: + EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); + + // + // 4th update: wildcard pass + // + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "4" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 80 + rules: + - precedence: 1000 + pass_precedence: 501 + - precedence: 900 + deny: true + - precedence: 500 + remote_policies: [ 43, 44 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allowed' +)EOF")); + EXPECT_EQ(version, "4"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected4 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [43,44] + precedence: 999 + http_rules: + - headers: + - name: ":path" + value: "/allowed" +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected4)); + + // Allowed remote ID, port, & path: + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + // Allowed remote ID, port, & path: + EXPECT_TRUE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/allowed"}})); + // Wrong remote ID: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 40, 80, {{":path", "/allowed"}})); + // Wrong port: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 8080, {{":path", "/allowed"}})); + // Wrong path: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/notallowed"}})); + + // No egress is allowed: + EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); + + // + // 5th update: split wildcard lower-precedence rule due to pass + // + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "5" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 80 + rules: + - precedence: 1000 + pass_precedence: 501 + remote_policies: [ 43 ] + - precedence: 900 + deny: true + - precedence: 500 + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allowed' +)EOF")); + EXPECT_EQ(version, "5"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected5 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [43] + precedence: 999 + http_rules: + - headers: + - name: ":path" + value: "/allowed" + - remotes: [] + deny: true + precedence: 900 +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected5)); + + // Remote 43 is promoted above deny by pass. + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + // Other remotes are still denied by the deny rule. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/allowed"}})); + EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/allowed"}})); + + // + // 6th update: wildcard-port pass inherited by specific port rules + // + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "6" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - precedence: 1000 + pass_precedence: 501 + remote_policies: [ 43 ] + - port: 80 + rules: + - precedence: 900 + deny: true + - precedence: 500 + remote_policies: [ 43, 44 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allowed' +)EOF")); + EXPECT_EQ(version, "6"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected6 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [43] + precedence: 999 + http_rules: + - headers: + - name: ":path" + value: "/allowed" + - remotes: [] + deny: true + precedence: 900 +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected6)); + + // Pass from wildcard port should promote remote 43 above deny on port 80. + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + // Remote 44 is denied due to only 43 being promoted. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/allowed"}})); + // Unspecified remotes remain denied. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/allowed"}})); + + // No egress is allowed: + EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); + + // + // 7th update: wildcard-port and specific-port pass rules at equal precedence + // + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "7" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - precedence: 1000 + pass_precedence: 501 + remote_policies: [ 44 ] + - port: 80 + rules: + - precedence: 1000 + pass_precedence: 501 + remote_policies: [ 43 ] + - precedence: 900 + deny: true + - precedence: 500 + remote_policies: [ 43, 44 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allowed' +)EOF")); + EXPECT_EQ(version, "7"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected7 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [43,44] + precedence: 999 + http_rules: + - headers: + - name: ":path" + value: "/allowed" + - remotes: [] + deny: true + precedence: 900 +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected7)); + + // Both IDs are passed to the lower allow despite the intermediate deny. + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/allowed"}})); + EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/allowed"}})); + + // + // 8th update: non-pass rule shadowing inside a pass tier + // + // The pass rule is required to enable tier processing, but it targets only + // remote 45 so the tier is not wildcard-pass and does not pre-shadow 43/44. + // Within this tier: + // - A higher-precedence deny for remote 44 establishes a final verdict for 44. + // - A lower-precedence allow for [43,44] must have 44 removed due to shadowing. + // - A second allow at the same precedence for [43] must keep 43, confirming + // no same-precedence identity shadowing between allow rules. + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "8" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 80 + rules: + - precedence: 1000 + pass_precedence: 701 + remote_policies: [ 45 ] + - precedence: 900 + deny: true + remote_policies: [ 44 ] + - precedence: 800 + remote_policies: [ 43, 44 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allow-a' + - precedence: 800 + remote_policies: [ 43 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allow-b' + - precedence: 700 + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allow-c' +)EOF")); + EXPECT_EQ(version, "8"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected8 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [45] + precedence: 999 + http_rules: + - headers: + - name: ":path" + value: "/allow-c" + - remotes: [44] + deny: true + precedence: 900 + - remotes: [43] + precedence: 800 + http_rules: + - headers: + - name: ":path" + value: "/allow-b" + - remotes: [43] + precedence: 800 + http_rules: + - headers: + - name: ":path" + value: "/allow-a" + - remotes: [] + precedence: 700 + http_rules: + - headers: + - name: ":path" + value: "/allow-c" +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected8)); + + // Remote 43 is not passed, but both same-precedence allow rules remain effective. + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allow-a"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allow-b"}})); + // Remote 44 is denied by the higher-precedence deny and removed from allow-a. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/allow-a"}})); + EXPECT_FALSE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/allow-b"}})); + // Pass remote 45 does not match /allow-a because only /allow-c is promoted for it. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/allow-a"}})); + // Wildcard allow at precedence 700 is promoted to precedence 999 only for pass remote 45. + EXPECT_TRUE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/allow-c"}})); + // Non-pass remotes not already denied at higher precedence still match the + // original wildcard rule at precedence 700. + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allow-c"}})); + EXPECT_FALSE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/allow-c"}})); + + // + // 9th update: inherited wildcard current-tier pass fully shadowed + // + // Wildcard port has a current-tier pass for remote 43, and specific port has + // a higher precedence pass for the same remote on the same tier. When the + // wildcard pass is inherited, it is fully shadowed and skipped, as evidenced by the + // precedence of the passed-to rule for remote 43, which is 999 rather than 899. + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "9" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - precedence: 900 + pass_precedence: 701 + remote_policies: [ 43 ] + - port: 80 + rules: + - precedence: 1000 + pass_precedence: 701 + remote_policies: [ 43 ] + - precedence: 800 + deny: true + - precedence: 700 + remote_policies: [ 43, 44 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/shadowed-inherited-pass' +)EOF")); + EXPECT_EQ(version, "9"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected9 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [43] + precedence: 999 + http_rules: + - headers: + - name: ":path" + value: "/shadowed-inherited-pass" + - remotes: [] + deny: true + precedence: 800 +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected9)); + + // Remote 43 is promoted above deny due to the specific-port pass. + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/shadowed-inherited-pass"}})); + // Remote 44 remains denied by the intermediate deny. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/shadowed-inherited-pass"}})); + EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/shadowed-inherited-pass"}})); + + // + // 10th update: multiple wildcard pass tiers inherited by a specific port + // + // Wildcard port contributes two pass tiers: + // Tier boundaries are inclusive. + // - tier 1 pass (1300/1000) for remote 41: tier boundaries [1300..1000] + // - tier 2 pass (900/700) for remote 42: tier boundaries [999..700] + // For port 80: + // - deny at 850 is within tier 2, so it is promoted by tier 1 pass for remote 41 to 1150 + // - allow [41,42,43] at 600 is split and promoted by both tiers: + // - 41 to tier 1 precedence 900 + // - 42 to tier 2 precedence 800 + // - 43 remains at tier 3 at precedence 600 + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "10" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - precedence: 1300 + pass_precedence: 1000 + remote_policies: [ 41 ] + - precedence: 900 + pass_precedence: 700 + remote_policies: [ 42 ] + - port: 80 + rules: + - precedence: 850 + deny: true + - precedence: 600 + remote_policies: [ 41, 42, 43 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/multi-tier' +)EOF")); + EXPECT_EQ(version, "10"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected10 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [41] + deny: true + precedence: 1150 + - remotes: [] + deny: true + precedence: 850 +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected10)); + + // Remote 41 hits the promoted deny from tier 1. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 41, 80, {{":path", "/multi-tier"}})); + // Remote 42 is promoted by the lower wildcard tier, but remains below deny. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 42, 80, {{":path", "/multi-tier"}})); + // Remote 43 is not promoted and is denied. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/multi-tier"}})); + + // + // 11th update: inconsistent pass precedence via inherited wildcard + local pass + // + // Wildcard current-tier pass (900/700) is inherited for port 80 at local + // pass precedence 850. A local pass with pass_precedence 600 on the same tier + // must fail as inconsistent. + EXPECT_THROW_WITH_MESSAGE(updateFromYaml(R"EOF(version_info: "11" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - precedence: 900 + pass_precedence: 700 + remote_policies: [ 50 ] + - port: 80 + rules: + - precedence: 850 + pass_precedence: 600 + remote_policies: [ 51 ] + - precedence: 500 + remote_policies: [ 50, 51 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/inconsistent-inherited-pass' +)EOF"), + EnvoyException, + "PortNetworkPolicy: Inconsistent pass precedence 600 != 700"); + + // Failed update must leave policy unchanged from version 10. + EXPECT_TRUE(validate("10.1.2.3", expected10)); + EXPECT_FALSE(ingressAllowed("10.1.2.3", 41, 80, {{":path", "/multi-tier"}})); + EXPECT_FALSE(ingressAllowed("10.1.2.3", 42, 80, {{":path", "/multi-tier"}})); + + // + // 12th update: inherited wildcard pass skips remaining rules on that tier + // + // Wildcard port has a wildcard pass (2000/700), which is inherited for port 80. + // Rules in that same tier [1999..700] are skipped; a lower-tier rule at 600 is + // retained and promoted to 1900 by the inherited wildcard pass. + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "12" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - precedence: 2000 + pass_precedence: 700 + - port: 80 + rules: + - precedence: 1200 + deny: true + remote_policies: [ 43 ] + - precedence: 1100 + remote_policies: [ 44 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/should-skip' + - precedence: 600 + remote_policies: [ 43, 44 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/promoted-after-skip' +)EOF")); + EXPECT_EQ(version, "12"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected12 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [43,44] + precedence: 1900 + http_rules: + - headers: + - name: ":path" + value: "/promoted-after-skip" +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected12)); + + // Both remotes are allowed by the promoted lower-tier rule. + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/promoted-after-skip"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/promoted-after-skip"}})); + // Tier rule at 800 is skipped by inherited wildcard pass. + EXPECT_FALSE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/should-skip"}})); + EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/promoted-after-skip"}})); + + // + // 13th update: Shadowed rules are eliminated + // + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "12" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - precedence: 1000 + pass_precedence: 901 + - port: 80 + rules: + - precedence: 900 + deny: true + remote_policies: [ 43 ] + - precedence: 800 + remote_policies: [ 43 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/should-skip' + - precedence: 600 + remote_policies: [ 43, 44 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/partially-skipped' +)EOF")); + EXPECT_EQ(version, "12"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected13 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [43] + deny: true + precedence: 999 + - remotes: [44] + precedence: 699 + http_rules: + - headers: + - name: ":path" + value: "/partially-skipped" +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected13)); + + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/partially-skipped"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/partially-skipped"}})); + // Rule at 800 is shadowed by higher precedence deny + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/should-skip"}})); + // inapplicable identity + EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/partially-skipped"}})); } TEST_F(CiliumNetworkPolicyTest, HttpOverlappingPortRanges) { From f38a7a0b5ac7ac1de9cefe774c79928292e486f8 Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Mon, 16 Feb 2026 15:57:36 +0100 Subject: [PATCH 7/8] policy: Fix precedence between wildcard port and specific port rule We only looked for wildcard port match if a specific port did not find any matches. Fix this by distributing wildcard port rules to all explicit port (range) rules so that they are checked in the right precedence order. This is cheap as they are referred to by a shared pointer, so no copies of the actual PortNetworkPolicyRule's are made. Refactor policy lookups to return a RuleVerdict type. Fix empty rules with other rules semantics: A port rule with an empty rules list is an implicit allow of the lowest precedence. Retain this semantic if a pass rule is added either on the same port, or to the wildcard port. Without this fix the pass rule processing does not find the (missing) default-allow-rule and the precedence of the default allow remains at 0, while it should have been promoted due to the pass rule. test TLS with deny Harden TLS test with deny policies. Make sure raw sockets are not allowed if there is overriding deny policy. Signed-off-by: Jarno Rajahalme --- cilium/network_policy.cc | 641 +++++++++++++++------------- cilium/network_policy.h | 16 +- cilium/tls_wrapper.cc | 4 +- tests/cilium_network_policy_test.cc | 514 ++++++++++++++++++---- 4 files changed, 783 insertions(+), 392 deletions(-) diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index 30ae3ea12..715a15ba3 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -421,11 +421,15 @@ SniPattern::SniPattern(const Regex::Engine& engine, absl::string_view sni) { class PortNetworkPolicyRule : public Logger::Loggable { public: + PortNetworkPolicyRule() + : name_("default allow rule"), deny_(false), proxy_id_(0), precedence_(0), + tier_last_precedence_(0), mutable_remotes_(false), l7_proto_("") {} + PortNetworkPolicyRule(const NetworkPolicyMapImpl& parent, - const cilium::PortNetworkPolicyRule& rule) + const cilium::PortNetworkPolicyRule& rule, bool shared_resource) : name_(rule.name()), deny_(rule.deny()), proxy_id_(uint16_t(rule.proxy_id())), precedence_(rule.precedence()), tier_last_precedence_(rule.pass_precedence()), - l7_proto_(rule.l7_proto()) { + mutable_remotes_(!shared_resource), l7_proto_(rule.l7_proto()) { if (tier_last_precedence_ > precedence_) { throw EnvoyException( fmt::format("PortNetworkPolicyRule: pass_precedence {} must be lower than precedence {}", @@ -477,10 +481,10 @@ class PortNetworkPolicyRule : public Logger::Loggable { bool isRemoteWildcard() const { return remotes_.empty(); } - bool allowed(uint16_t proxy_id, uint32_t remote_id, bool& denied) const { + RuleVerdict getVerdict(uint16_t proxy_id, uint32_t remote_id) const { // proxy_id must match if we have any. if (proxy_id_ != 0 && proxy_id != proxy_id_) { - return false; + return RuleVerdict::None; } // Remote ID must match if we have any. if (!isRemoteWildcard()) { @@ -489,28 +493,23 @@ class PortNetworkPolicyRule : public Logger::Loggable { // remote ID matched if (deny_) { // Explicit deny - denied = true; - return false; + return RuleVerdict::Deny; } // Explicit allow - return true; + return RuleVerdict::Allow; } // Not found, not allowed, but also not explicitly denied - return false; + return RuleVerdict{}; } // Allow rules allow by default when remotes_ is empty, deny rules do not - if (deny_) { - denied = true; - return false; - } - return true; + return deny_ ? RuleVerdict::Deny : RuleVerdict::Allow; } - bool allowed(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, bool& denied) const { + RuleVerdict getVerdict(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni) const { // sni must match if we have any if (!allowed_snis_.empty()) { if (sni.length() == 0) { - return false; + return RuleVerdict::None; // no verdict, not allowed, not denied, some other rule may allow } bool matched = false; for (const auto& pattern : allowed_snis_) { @@ -520,114 +519,114 @@ class PortNetworkPolicyRule : public Logger::Loggable { } } if (!matched) { - return false; + return RuleVerdict::None; } } - return allowed(proxy_id, remote_id, denied); + return getVerdict(proxy_id, remote_id); } - bool allowed(uint16_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers, - Cilium::AccessLog::Entry& log_entry, bool& denied) const { - if (!allowed(proxy_id, remote_id, denied)) { - return false; - } - if (hasHttpRules()) { - bool allowed = false; + RuleVerdict getVerdict(uint16_t proxy_id, uint32_t remote_id, + Envoy::Http::RequestHeaderMap& headers, + Cilium::AccessLog::Entry& log_entry) const { + auto verdict = getVerdict(proxy_id, remote_id); + if (verdict == RuleVerdict::Allow && hasHttpRules()) { + bool header_matched = false; for (const auto& rule : *http_rules_) { if (rule.allowed(headers)) { // Return on the first match if no rule has HeaderMatches if (!has_headermatches_) { - allowed = true; - break; + return verdict; } - // orherwise evaluate all rules to run all the header actions, + // Otherwise evaluate all rules to run all the header actions, // and remember if any of them matched if (rule.headerMatches(headers, log_entry)) { - allowed = true; + header_matched = true; } } } - return allowed; + if (!header_matched) { + verdict = RuleVerdict::None; + } } - // Empty set matches any payload - return true; + return verdict; } - bool useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto, - bool& denied) const { - if (!allowed(proxy_id, remote_id, denied)) { - return false; - } - if (l7_proto_.length() > 0) { - ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule::useProxylib(): returning {}", l7_proto_); - l7_proto = l7_proto_; - return true; + RuleVerdict useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto) const { + auto verdict = getVerdict(proxy_id, remote_id); + if (verdict == RuleVerdict::Allow) { + if (l7_proto_.length() > 0) { + ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule::useProxylib(): returning {}", l7_proto_); + l7_proto = l7_proto_; + return verdict; + } + // keep looking past allows if no proxylib + verdict = RuleVerdict::None; } - return false; + return verdict; } // Envoy Metadata matcher, called after deny has already been checked for - bool allowed(uint16_t proxy_id, uint32_t remote_id, - const envoy::config::core::v3::Metadata& metadata, bool& denied) const { - if (!allowed(proxy_id, remote_id, denied)) { - return false; - } - for (const auto& rule : l7_deny_rules_) { - if (rule.matches(metadata)) { - ENVOY_LOG(trace, - "Cilium L7 PortNetworkPolicyRule::allowed(): DENY due to " - "a matching deny rule {}", - rule.name_); - return false; // request is denied if any deny rule matches - } - } - if (!l7_allow_rules_.empty()) { - for (const auto& rule : l7_allow_rules_) { + RuleVerdict getVerdict(uint16_t proxy_id, uint32_t remote_id, + const envoy::config::core::v3::Metadata& metadata) const { + auto verdict = getVerdict(proxy_id, remote_id); + if (verdict == RuleVerdict::Allow) { + for (const auto& rule : l7_deny_rules_) { if (rule.matches(metadata)) { ENVOY_LOG(trace, - "Cilium L7 PortNetworkPolicyRule::allowed(): ALLOW due " - "to a matching allow rule {}", + "Cilium L7 PortNetworkPolicyRule::allowed(): DENY due to " + "a matching deny rule {}", rule.name_); - return true; + // request is denied if any deny rule matches + verdict = RuleVerdict::Deny; + return verdict; } } - ENVOY_LOG(trace, - "Cilium L7 PortNetworkPolicyRule::allowed(): DENY due to " - "all {} allow rules mismatching", - l7_allow_rules_.size()); - return false; + if (!l7_allow_rules_.empty()) { + for (const auto& rule : l7_allow_rules_) { + if (rule.matches(metadata)) { + ENVOY_LOG(trace, + "Cilium L7 PortNetworkPolicyRule::allowed(): ALLOW due " + "to a matching allow rule {}", + rule.name_); + return verdict; + } + } + ENVOY_LOG(trace, + "Cilium L7 PortNetworkPolicyRule::allowed(): SKIP due to " + "all {} allow rules mismatching", + l7_allow_rules_.size()); + verdict = RuleVerdict::None; + return verdict; + } + ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule::allowed(): default ALLOW " + "due to no allow rules"); + return verdict; // allowed by default } - ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule::allowed(): default ALLOW " - "due to no allow rules"); - return true; // allowed by default + return verdict; } - Ssl::ContextSharedPtr getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, - absl::string_view sni, - const Ssl::ContextConfig** config, - bool& raw_socket_allowed, bool& denied) const { - if (allowed(proxy_id, remote_id, sni, denied)) { - if (server_context_) { - *config = &server_context_->getTlsContextConfig(); - return server_context_->getTlsContext(); - } - raw_socket_allowed = true; + // getServerTlsContext returns true if the rule has server TLS context that was passed to the + // caller via the reference arguments. + bool getServerTlsContext(Ssl::ContextSharedPtr& tls_context, + const Ssl::ContextConfig*& config) const { + if (server_context_) { + tls_context = server_context_->getTlsContext(); + config = &server_context_->getTlsContextConfig(); + return true; } - return nullptr; + return false; } - Ssl::ContextSharedPtr getClientTlsContext(uint16_t proxy_id, uint32_t remote_id, - absl::string_view sni, - const Ssl::ContextConfig** config, - bool& raw_socket_allowed, bool& denied) const { - if (allowed(proxy_id, remote_id, sni, denied)) { - if (client_context_) { - *config = &client_context_->getTlsContextConfig(); - return client_context_->getTlsContext(); - } - raw_socket_allowed = true; + // getClientTlsContext returns true if the rule has client TLS context that was passed to the + // caller via the reference arguments. + bool getClientTlsContext(Ssl::ContextSharedPtr& tls_context, + const Ssl::ContextConfig*& config) const { + if (client_context_) { + tls_context = client_context_->getTlsContext(); + config = &client_context_->getTlsContextConfig(); + return true; } - return nullptr; + return false; } void toString(int indent, std::string& res) const { @@ -705,6 +704,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { uint32_t precedence_; uint32_t tier_last_precedence_; absl::btree_set remotes_; + bool mutable_remotes_; std::vector allowed_snis_; // All SNIs allowed if empty. std::shared_ptr> @@ -752,24 +752,58 @@ class PortNetworkPolicyRules : public Logger::Loggable { } } - void insert(PortNetworkPolicyRuleConstSharedPtr rule) { - rules_.emplace_back(rule); - updateFor(rules_.back()); - } + void addDefaultAllowRule() { rules_.emplace_back(std::make_shared()); } + // append merges 'rules' to 'rules_' by placing the new 'rules' to the end of 'rules_'. + // First call marks 'rules_' as initialized. Of further calls, if either is empty, + // we must add a default allow rule to retain the semantics of an empty rules. void append(const NetworkPolicyMapImpl& parent, - const Protobuf::RepeatedPtrField& rules) { + const Protobuf::RepeatedPtrField& rules, + bool shared_resource) { + if (initialized_ && rules.empty() != rules_.empty()) { + // add an explicit allow-all rule to keep the combined semantics + addDefaultAllowRule(); + } for (const auto& it : rules) { - insert(std::make_shared(parent, it)); + rules_.emplace_back(std::make_shared(parent, it, shared_resource)); + updateFor(rules_.back()); } + initialized_ = true; } + // prepend merges 'rules' to 'rules_' by placing the new 'rules' to the front of 'rules_'. + // First call marks 'rules_' as initialized. Of further calls, if either is empty, + // we must add a default allow rule to retain the semantics of an empty rules. void prepend(const NetworkPolicyMapImpl& parent, - const Protobuf::RepeatedPtrField& rules) { + const Protobuf::RepeatedPtrField& rules, + bool shared_resource) { + if (initialized_ && rules.empty() != rules_.empty()) { + // add an explicit allow-all rule to keep the combined semantics + rules_.emplace(rules_.begin(), std::make_shared()); + } for (const auto& it : rules) { - rules_.emplace(rules_.begin(), std::make_shared(parent, it)); + rules_.emplace(rules_.begin(), + std::make_shared(parent, it, shared_resource)); updateFor(rules_.front()); } + initialized_ = true; + } + + // appendNonPassRules merges non-pass rules from 'rules' to 'rules_' by placing the new rules to + // the end of 'rules_'. First call marks 'rules_' as initialized. Of further calls, if either is + // empty, we must add a default allow rule to retain the semantics of an empty rules. + void appendNonPassRules(const std::vector& rules) { + if (initialized_ && rules.empty() != rules_.empty()) { + // add an explicit allow-all rule to keep the combined semantics + addDefaultAllowRule(); + } + for (auto& rule : rules) { + if (rule->tier_last_precedence_ == 0) { + rules_.insert(rules_.end(), rule); + updateFor(rule); + } + } + initialized_ = true; } // sort by descending precedence, retaining the original order within each precedence level @@ -785,162 +819,149 @@ class PortNetworkPolicyRules : public Logger::Loggable { bool empty() const { return rules_.empty(); } - enum class RuleVerdict { - None, - Intermediate, - Final, - }; + template RuleVerdict forEachRule(bool can_short_circuit, F&& func) const { + RuleVerdict verdict = RuleVerdict::None; + uint32_t verdict_precedence = 0; - template void forEachRule(F&& func) const { - uint32_t precedence = 0; - bool have_verdict = false; + // Uninitialized rules match nothing + ASSERT(initialized_, "uninitialized rules"); + if (!initialized_) { + return verdict; + } + + // Empty set matches any payload from anyone + if (empty()) { + return RuleVerdict::Allow; + } for (const auto& rule : rules_) { // lower precedence rules are skipped if there is a verdict - if (rule->precedence_ < precedence && have_verdict) { + if (verdict != RuleVerdict::None && rule->precedence_ < verdict_precedence) { break; } - precedence = rule->precedence_; - - RuleVerdict v = func(*rule); + auto rule_verdict = func(*rule); + if (rule_verdict != RuleVerdict::None) { + verdict = rule_verdict; + verdict_precedence = rule->precedence_; - if (v == RuleVerdict::Final) { - break; - } - if (v == RuleVerdict::Intermediate) { - have_verdict = true; + // Short-circuit on the first deny or on first allow if no rules have HeaderMatches + if (rule_verdict == RuleVerdict::Deny || can_short_circuit) { + break; + } } } + return verdict; } - bool allowed(uint16_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers, - Cilium::AccessLog::Entry& log_entry, bool& denied) const { + // forEachRulePred return a RuleVerdict by scanning through all rules, getting the verdict for + // each rule and checking if the predicate matches. Returns RuleVerdict::Allow if any rule allows + // the traffic, even if the predicate returns false. Stops as soon as the first rule returns + // 'true' for the predicate. + // This is used to the applicable TLS context from the rules, if any. Note that in this use 'pred' + // has side effects, but it is idempotent. + template RuleVerdict forEachRulePred(F&& get_verdict, P&& pred) const { + RuleVerdict verdict = RuleVerdict::None; + uint32_t verdict_precedence = 0; + + // Uninitialized rules match nothing + ASSERT(initialized_, "uninitialized rules"); + if (!initialized_) { + return verdict; + } + // Empty set matches any payload from anyone - if (rules_.empty()) { - return true; + if (empty()) { + return RuleVerdict::Allow; } - bool allowed = false; - forEachRule([&](const auto& rule) { - if (rule.allowed(proxy_id, remote_id, headers, log_entry, denied)) { - allowed = true; - // Short-circuit on the first match if no rules have HeaderMatches - if (can_short_circuit_) { - return RuleVerdict::Final; + for (const auto& rule : rules_) { + auto rule_verdict = get_verdict(*rule); + switch (rule_verdict) { + case RuleVerdict::Deny: + // return higher precedence allow verdict if any. + if (verdict != RuleVerdict::None && verdict_precedence > rule->precedence_) { + return verdict; + } + return rule_verdict; + case RuleVerdict::Allow: + if (pred(*rule)) { + // Return after the first allow verdict that fulfills the predicate + return rule_verdict; + } + // store highest precedence allow verdict that does not fulfill the predicate + if (verdict == RuleVerdict::None) { + verdict = rule_verdict; + verdict_precedence = rule->precedence_; } - return RuleVerdict::Intermediate; + break; + case RuleVerdict::None: + break; } - return denied ? RuleVerdict::Final : RuleVerdict::None; + } + return verdict; + } + + RuleVerdict getVerdict(uint16_t proxy_id, uint32_t remote_id, + Envoy::Http::RequestHeaderMap& headers, + Cilium::AccessLog::Entry& log_entry) const { + auto verdict = forEachRule(can_short_circuit_, [&](const auto& rule) { + return rule.getVerdict(proxy_id, remote_id, headers, log_entry); }); ENVOY_LOG(trace, - "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, headers: {}): " - "{}", - proxy_id, remote_id, headers, allowed && !denied ? "ALLOWED" : "DENIED"); - return allowed && !denied; + "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, headers: {}): {}", + proxy_id, remote_id, headers, + verdict != RuleVerdict::None ? (verdict == RuleVerdict::Allow ? "ALLOWED" : "DENIED") + : "no verdict"); + return verdict; } - bool allowed(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, bool& denied) const { - // Empty set matches any payload from anyone - if (rules_.empty()) { - return true; - } - - bool allowed = false; - forEachRule([&](const auto& rule) { - if (rule.allowed(proxy_id, remote_id, sni, denied)) { - allowed = true; - // Short-circuit on the first match if no rules have HeaderMatches - if (can_short_circuit_) { - return RuleVerdict::Final; - } - return RuleVerdict::Intermediate; - } - return denied ? RuleVerdict::Final : RuleVerdict::None; - }); + RuleVerdict getVerdict(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni) const { + auto verdict = forEachRule( + true, [&](const auto& rule) { return rule.getVerdict(proxy_id, remote_id, sni); }); - ENVOY_LOG(trace, - "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, sni: {}): " - "{}", - proxy_id, remote_id, sni, allowed && !denied ? "ALLOWED" : "DENIED"); - return allowed && !denied; - } - - bool useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto) const { - bool denied = false; - bool use_proxylib = false; - forEachRule([&](const auto& rule) { - if (rule.useProxylib(proxy_id, remote_id, l7_proto, denied)) { - use_proxylib = true; - return RuleVerdict::Final; - } - return denied ? RuleVerdict::Final : RuleVerdict::None; - }); - return use_proxylib && !denied; + ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, sni: {}): {}", + proxy_id, remote_id, sni, + verdict != RuleVerdict::None ? (verdict == RuleVerdict::Allow ? "ALLOWED" : "DENIED") + : "no verdict"); + return verdict; } - bool allowed(uint16_t proxy_id, uint32_t remote_id, - const envoy::config::core::v3::Metadata& metadata, bool& denied) const { - // Empty set matches any payload from anyone - if (rules_.empty()) { - return true; - } + RuleVerdict useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto) const { + return forEachRule( + true, [&](const auto& rule) { return rule.useProxylib(proxy_id, remote_id, l7_proto); }); + } - bool allowed = false; - forEachRule([&](const auto& rule) { - if (rule.allowed(proxy_id, remote_id, metadata, denied)) { - allowed = true; - // Short-circuit on the first match if no rules have HeaderMatches - if (can_short_circuit_) { - return RuleVerdict::Final; - } - return RuleVerdict::Intermediate; - } - return denied ? RuleVerdict::Final : RuleVerdict::None; - }); + RuleVerdict getVerdict(uint16_t proxy_id, uint32_t remote_id, + const envoy::config::core::v3::Metadata& metadata) const { + auto verdict = forEachRule( + true, [&](const auto& rule) { return rule.getVerdict(proxy_id, remote_id, metadata); }); ENVOY_LOG(trace, - "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, metadata: {}): " - "{}", + "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, metadata: {}): {}", proxy_id, remote_id, metadata.DebugString(), - allowed && !denied ? "ALLOWED" : "DENIED"); - return allowed && !denied; - } - - Ssl::ContextSharedPtr getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, - absl::string_view sni, - const Ssl::ContextConfig** config, - bool& raw_socket_allowed) const { - bool denied = false; - Ssl::ContextSharedPtr tls_ctx = nullptr; - forEachRule([&](const auto& rule) { - Ssl::ContextSharedPtr server_context = - rule.getServerTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed, denied); - if (server_context) { - tls_ctx = server_context; - return RuleVerdict::Final; - } - return denied ? RuleVerdict::Final : RuleVerdict::None; - }); - return denied ? nullptr : tls_ctx; - } - - Ssl::ContextSharedPtr getClientTlsContext(uint16_t proxy_id, uint32_t remote_id, - absl::string_view sni, - const Ssl::ContextConfig** config, - bool& raw_socket_allowed) const { - bool denied = false; - Ssl::ContextSharedPtr tls_ctx = nullptr; - forEachRule([&](const auto& rule) { - Ssl::ContextSharedPtr client_context = - rule.getClientTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed, denied); - if (client_context) { - tls_ctx = client_context; - return RuleVerdict::Final; - } - return denied ? RuleVerdict::Final : RuleVerdict::None; - }); - return denied ? nullptr : tls_ctx; + verdict != RuleVerdict::None ? (verdict == RuleVerdict::Allow ? "ALLOWED" : "DENIED") + : "no verdict"); + + return verdict; + } + + RuleVerdict getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, + Ssl::ContextSharedPtr& tls_ctx, + const Ssl::ContextConfig*& config) const { + tls_ctx = nullptr; + return forEachRulePred( + [&](const auto& rule) { return rule.getVerdict(proxy_id, remote_id, sni); }, + [&](const auto& rule) { return rule.getServerTlsContext(tls_ctx, config); }); + } + + RuleVerdict getClientTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, + Ssl::ContextSharedPtr& tls_ctx, + const Ssl::ContextConfig*& config) const { + tls_ctx = nullptr; + return forEachRulePred( + [&](const auto& rule) { return rule.getVerdict(proxy_id, remote_id, sni); }, + [&](const auto& rule) { return rule.getClientTlsContext(tls_ctx, config); }); } void toString(int indent, std::string& res) const { @@ -966,67 +987,40 @@ class PortNetworkPolicyRules : public Logger::Loggable { std::vector rules_; // Allowed if empty. bool can_short_circuit_{true}; bool has_pass_rules_{false}; + bool initialized_{false}; }; // end port is zero on lookup! PortPolicy::PortPolicy(const PolicyMap& map, uint16_t port) - : map_(map), wildcard_rules_([&]() { - const auto it = map_.find({0, 0}); - return it != map_.cend() ? &it->second : nullptr; - }()), - port_rules_([&]() { - const auto it = map_.find({port, port}); + : map_(map), port_rules_([&]() { + auto it = map_.find({port, port}); + if (it != map_.cend()) { + return &it->second; + } + it = map_.find({0, 0}); return it != map_.cend() ? &it->second : nullptr; }()), - has_http_rules_((port_rules_ && port_rules_->hasHttpRules()) || - (wildcard_rules_ && wildcard_rules_->hasHttpRules())) {} + has_http_rules_(port_rules_ && port_rules_->hasHttpRules()) {} -// forRange is used for policy lookups, so it will need to check both port-specific and -// wildcard-port rules, as either of them could contain rules that must be evaluated (i.e., deny -// or header match rules with side effects). bool PortPolicy::forRange( - std::function allowed) const { - bool allow = false; - bool denied = false; + std::function get_verdict) const { if (port_rules_) { - if (allowed(*port_rules_, denied)) { - allow = true; - } - } - // Wildcard port can deny a specific remote, so need to check for it too. - if (!(allow && wildcard_rules_ && wildcard_rules_->can_short_circuit_)) { - if (wildcard_rules_ && allowed(*wildcard_rules_, denied)) { - allow = true; - } - } - return allow && !denied; -} + auto verdict = get_verdict(*port_rules_); -// forFirstRange is used for proxylib parser and TLS context selection. -// -// rules for the specific ports are checked first, and within there singe-port ranges are placed in -// the front, while actual ranges are placed in the back. This results in the following precedence -// order for both proxylib parser and TLS context selection: -// -// 1. single port rules (e.g., port 80) -// 2. port ranges (e.g., ports 80-90) -// 3. Wildcard port rules -// -bool PortPolicy::forFirstRange(std::function f) const { - if (port_rules_ && f(*port_rules_)) { - return true; - } - // Check the wildcard port entry - if (wildcard_rules_ && f(*wildcard_rules_)) { - return true; + return verdict == RuleVerdict::Allow; } return false; } bool PortPolicy::useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto) const { - return forFirstRange([&](const PortNetworkPolicyRules& rules) -> bool { - return rules.useProxylib(proxy_id, remote_id, l7_proto); - }); + if (port_rules_) { + auto verdict = port_rules_->useProxylib(proxy_id, remote_id, l7_proto); + if (verdict == RuleVerdict::Allow) { + return true; + } + } + l7_proto = ""; + return false; } bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, @@ -1037,46 +1031,52 @@ bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, if (!has_http_rules_) { return true; } - return forRange([&](const PortNetworkPolicyRules& rules, bool& denied) -> bool { - return rules.allowed(proxy_id, remote_id, headers, log_entry, denied); + return forRange([&](const PortNetworkPolicyRules& rules) { + return rules.getVerdict(proxy_id, remote_id, headers, log_entry); }); } bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni) const { - return forRange([&](const PortNetworkPolicyRules& rules, bool& denied) -> bool { - return rules.allowed(proxy_id, remote_id, sni, denied); + return forRange([&](const PortNetworkPolicyRules& rules) -> RuleVerdict { + return rules.getVerdict(proxy_id, remote_id, sni); }); } bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, const envoy::config::core::v3::Metadata& metadata) const { - return forRange([&](const PortNetworkPolicyRules& rules, bool& denied) -> bool { - return rules.allowed(proxy_id, remote_id, metadata, denied); + return forRange([&](const PortNetworkPolicyRules& rules) -> RuleVerdict { + return rules.getVerdict(proxy_id, remote_id, metadata); }); } Ssl::ContextSharedPtr PortPolicy::getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, - const Ssl::ContextConfig** config, + const Ssl::ContextConfig*& config, bool& raw_socket_allowed) const { - Ssl::ContextSharedPtr ret; - forFirstRange([&](const PortNetworkPolicyRules& rules) -> bool { - ret = rules.getServerTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed); - return ret != nullptr; - }); - return ret; + Ssl::ContextSharedPtr tls_ctx; + + config = nullptr; + raw_socket_allowed = false; + if (port_rules_) { + auto verdict = port_rules_->getServerTlsContext(proxy_id, remote_id, sni, tls_ctx, config); + raw_socket_allowed = verdict == RuleVerdict::Allow && tls_ctx == nullptr && config == nullptr; + } + return tls_ctx; } Ssl::ContextSharedPtr PortPolicy::getClientTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, - const Ssl::ContextConfig** config, + const Ssl::ContextConfig*& config, bool& raw_socket_allowed) const { - Ssl::ContextSharedPtr ret; - forFirstRange([&](const PortNetworkPolicyRules& rules) -> bool { - ret = rules.getClientTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed); - return ret != nullptr; - }); - return ret; + Ssl::ContextSharedPtr tls_ctx; + + config = nullptr; + raw_socket_allowed = false; + if (port_rules_) { + auto verdict = port_rules_->getClientTlsContext(proxy_id, remote_id, sni, tls_ctx, config); + raw_socket_allowed = verdict == RuleVerdict::Allow && tls_ctx == nullptr && config == nullptr; + } + return tls_ctx; } // Ranges overlap when one is not completely below or above the other @@ -1173,20 +1173,29 @@ class ShadowedRemotes { if (rule.isRemoteWildcard()) { current_precedence_has_wildcard_ = true; } else { - // Check if this rule is (partially) shadowed by nonpass rules on any higher tier. - if (!shadowed_nonpass_remotes_.empty()) { - absl::erase_if(rule.remotes_, - [&](const auto& x) { return shadowed_nonpass_remotes_.contains(x); }); - if (rule.remotes_.empty()) { - return true; + if (rule.mutable_remotes_) { + // Check if this rule is (partially) shadowed by nonpass rules on any higher tier. + if (!shadowed_nonpass_remotes_.empty()) { + absl::erase_if(rule.remotes_, + [&](const auto& x) { return shadowed_nonpass_remotes_.contains(x); }); + if (rule.remotes_.empty()) { + return true; + } } - } - // Check if this rule is (partially) shadowed by pass rules on this tier. - if (!shadowed_pass_remotes_.empty()) { - absl::erase_if(rule.remotes_, - [&](const auto& x) { return shadowed_pass_remotes_.contains(x); }); - if (rule.remotes_.empty()) { + // Check if this rule is (partially) shadowed by pass rules on this tier. + if (!shadowed_pass_remotes_.empty()) { + absl::erase_if(rule.remotes_, + [&](const auto& x) { return shadowed_pass_remotes_.contains(x); }); + if (rule.remotes_.empty()) { + return true; + } + } + } else { + // rule.remotes_ can not be modified, check if it is completely shadowed + if (std::ranges::all_of(rule.remotes_, [&](const auto& x) { + return shadowed_nonpass_remotes_.contains(x) || shadowed_pass_remotes_.contains(x); + })) { return true; } } @@ -1412,6 +1421,10 @@ class Passes { // - this includes the case where the lower precedence rule applies to all identities // (empty ID set) void apply(const PortRange& port_range, PortNetworkPolicyRules& rules) { + if (rules.rules_.empty() && !wildcard_pass_rules_.empty()) { + // add the default allow rule so that the wildcard port pass can apply to it. + rules.addDefaultAllowRule(); + } if (!rules.rules_.empty() && (!wildcard_pass_rules_.empty() || rules.has_pass_rules_)) { bool must_sort = false; @@ -1479,7 +1492,7 @@ class Passes { storeWildcardPassRules(port_range); } - // Mark ranges with no rules for removal. + // Mark ranges with all rules removed for clean-up. if (rules.empty()) { // Empty rule set would always allow. Mark for removal. empty_ranges_.push_back(port_range); @@ -1661,6 +1674,7 @@ class PortNetworkPolicy : public Logger::Loggable { RELEASE_ASSERT(it != rules_.end(), "first overlapping entry not found"); } // Add rules to all the overlapping entries + bool shared_resource = rule_range.first == 0; // wildcard port rules are shared bool singular = rule_range.first == rule_range.second; for (; it != rules_.end() && rangesOverlap(it->first, rule_range); it++) { auto range = it->first; @@ -1675,10 +1689,10 @@ class PortNetworkPolicy : public Logger::Loggable { // so the relative order of rules from this batch is reversed. This // is harmless: equal-precedence rules are evaluated as alternatives // (stable sort only affects presentation/debug ordering). - rules.prepend(parent, rule.rules()); + rules.prepend(parent, rule.rules(), shared_resource); } else { // Rules with a non-trivial range go to the back of the list - rules.append(parent, rule.rules()); + rules.append(parent, rule.rules(), shared_resource); } } } else { @@ -1686,6 +1700,19 @@ class PortNetworkPolicy : public Logger::Loggable { } } + // Apply wildcard port rules to all ranges + const PortNetworkPolicyRules* wildcard_rules = nullptr; + for (auto& [port_range, rules] : rules_) { + if (port_range.first == 0) { + wildcard_rules = &rules; + continue; + } + if (!wildcard_rules) { + break; + } + rules.appendNonPassRules(wildcard_rules->rules_); + } + bool have_passes = false; // sort rules on each non-overlapping port range into descending precedence diff --git a/cilium/network_policy.h b/cilium/network_policy.h index e2ddf5cff..0e9a388d4 100644 --- a/cilium/network_policy.h +++ b/cilium/network_policy.h @@ -75,6 +75,13 @@ class PortNetworkPolicyRules; // use of named ports and numbered ports in Cilium Network Policy at the same time). using PolicyMap = absl::btree_map; +// Supported message types +using RuleVerdict = enum { + None = 0, + Allow = 1, + Deny = 2, +}; + // PortPolicy holds a reference to a set of rules in a policy map that apply to the given port. // Methods then iterate through the set to determine if policy allows or denies. This is needed to // support multiple rules on the same port, like when named ports are used, or when deny policies @@ -108,7 +115,7 @@ class PortPolicy : public Logger::Loggable { // allows the connection without TLS and a raw socket should be used. Ssl::ContextSharedPtr getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, - const Ssl::ContextConfig** config, + 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. @@ -116,12 +123,12 @@ class PortPolicy : public Logger::Loggable { // allows the connection without TLS and a raw socket should be used. Ssl::ContextSharedPtr getClientTlsContext(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni, - const Ssl::ContextConfig** config, + const Ssl::ContextConfig*& config, bool& raw_socket_allowed) const; private: - bool forRange(std::function allowed) const; - bool forFirstRange(std::function f) const; + bool forRange(std::function get_verdict) const; + RuleVerdict forFirstRange(std::function f) const; const PolicyMap& map_; // using raw pointers by design: @@ -132,7 +139,6 @@ class PortPolicy : public Logger::Loggable { // before the old rules are deleted; worker thread drop references to policy rules before // returning to the event loop, so after the posted lambda executes it is safe to delete the old // rules. - const PortNetworkPolicyRules* wildcard_rules_; const PortNetworkPolicyRules* port_rules_; const bool has_http_rules_; }; diff --git a/cilium/tls_wrapper.cc b/cilium/tls_wrapper.cc index 6a2e453ab..e6a9907b8 100644 --- a/cilium/tls_wrapper.cc +++ b/cilium/tls_wrapper.cc @@ -131,9 +131,9 @@ class SslSocketWrapper : public Network::TransportSocket, Logger::Loggableproxy_id_; Envoy::Ssl::ContextSharedPtr ctx = - is_client ? port_policy.getClientTlsContext(proxy_id, remote_id, sni, &config, + is_client ? port_policy.getClientTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed) - : port_policy.getServerTlsContext(proxy_id, remote_id, sni, &config, + : port_policy.getServerTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed); if (ctx) { // create the underlying SslSocket diff --git a/tests/cilium_network_policy_test.cc b/tests/cilium_network_policy_test.cc index b6fd86c15..91e4ede44 100644 --- a/tests/cilium_network_policy_test.cc +++ b/tests/cilium_network_policy_test.cc @@ -140,10 +140,10 @@ class CiliumNetworkPolicyTest : public ::testing::Test { tls_socket_required = false; raw_socket_allowed = false; Envoy::Ssl::ContextSharedPtr ctx = - !ingress ? port_policy.getClientTlsContext(proxy_id_, remote_id, sni, &config, - raw_socket_allowed) - : port_policy.getServerTlsContext(proxy_id_, remote_id, sni, &config, - raw_socket_allowed); + !ingress + ? port_policy.getClientTlsContext(proxy_id_, remote_id, sni, config, raw_socket_allowed) + : port_policy.getServerTlsContext(proxy_id_, remote_id, sni, config, + raw_socket_allowed); // separate policy lookup for validation bool allowed = policy.allowed(ingress, proxy_id_, remote_id, sni, port); @@ -1112,10 +1112,144 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { "PortNetworkPolicy: Inconsistent pass precedence 200 != 100"); // - // 1st update: higher precedence deny + // 1st update: Default allow rule combining with an HTTP allow rule // 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: + - http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allowed' + - port: 80 +)EOF")); + EXPECT_EQ(version, "1"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected1 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [] + name: "default allow rule" + - remotes: [] + http_rules: + - headers: + - name: ":path" + value: "/allowed" +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected1)); + + // All remotes allowed on port 80 + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 40, 80, {{":path", "/allowed"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/also-allowed"}})); + // Wrong port: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 8080, {{":path", "/allowed"}})); + + // No egress is allowed: + EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); + + // + // 2nd update: Default allow rule combining with a pass rule. + // + 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 + end_port: 81 + rules: + - precedence: 10 + pass_precedence: 1 + - port: 80 +)EOF")); + EXPECT_EQ(version, "2"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected2 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [] + name: "default allow rule" + precedence: 9 +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected2)); + + // All remotes allowed on port 80 + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 40, 80, {{":path", "/allowed"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/also-allowed"}})); + // Wrong port: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 8080, {{":path", "/allowed"}})); + + // No egress is allowed: + EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); + + // + // 3rd update: Default allow rule combining with a pass rule on wildcard port. + // + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "3" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - precedence: 10 + pass_precedence: 1 + - port: 80 +)EOF")); + EXPECT_EQ(version, "3"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected3 = R"EOF(ingress: + rules: + [80-80]: + - rules: + - remotes: [] + name: "default allow rule" + precedence: 9 +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected3)); + + // All remotes allowed on port 80 + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 40, 80, {{":path", "/allowed"}})); + EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/also-allowed"}})); + // Wrong port: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 8080, {{":path", "/allowed"}})); + + // No egress is allowed: + EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); + + // + // 4th update: higher precedence deny + // + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "4" +resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: - "10.1.2.3" @@ -1133,10 +1267,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/allowed' )EOF")); - EXPECT_EQ(version, "1"); + EXPECT_EQ(version, "4"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected = R"EOF(ingress: + std::string expected4 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1153,9 +1287,9 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected)); + EXPECT_TRUE(validate("10.1.2.3", expected4)); - // Allowed remote ID, port, & path: + // Denied remote ID, port, & path: EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); // Wrong remote ID: EXPECT_FALSE(ingressAllowed("10.1.2.3", 40, 80, {{":path", "/allowed"}})); @@ -1168,9 +1302,72 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); // - // 2nd update: pass for '43' + // 5th update: higher precedence deny on wildcard port // - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "5" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - precedence: 1000 + deny: true + - port: 80 + rules: + - precedence: 100 + remote_policies: [ 43 ] + http_rules: + http_rules: + - headers: + - name: ':path' + exact_match: '/allowed' +)EOF")); + EXPECT_EQ(version, "5"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected5 = R"EOF(ingress: + rules: + [0-0]: + - rules: + - remotes: [] + deny: true + precedence: 1000 + [80-80]: + - rules: + - remotes: [] + deny: true + precedence: 1000 + - remotes: [43] + precedence: 100 + http_rules: + - headers: + - name: ":path" + value: "/allowed" +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected5)); + + // Denied remote ID, port, & path: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); + // Wrong remote ID: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 40, 80, {{":path", "/allowed"}})); + // Wrong port: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 8080, {{":path", "/allowed"}})); + // Wrong path: + EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/notallowed"}})); + + // No egress is allowed: + EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); + + // + // 6th update: pass for '43' + // + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "6" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1192,10 +1389,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/allowed' )EOF")); - EXPECT_EQ(version, "2"); + EXPECT_EQ(version, "6"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected2 = R"EOF(ingress: + std::string expected6 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1212,7 +1409,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected2)); + EXPECT_TRUE(validate("10.1.2.3", expected6)); // Allowed remote ID, port, & path: EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); @@ -1227,9 +1424,9 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); // - // 3rd update: pass with partial overlap + // 7th update: pass with partial overlap // - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "3" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "7" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1251,10 +1448,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/allowed' )EOF")); - EXPECT_EQ(version, "3"); + EXPECT_EQ(version, "7"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected3 = R"EOF(ingress: + std::string expected7 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1271,7 +1468,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected3)); + EXPECT_TRUE(validate("10.1.2.3", expected7)); // Allowed remote ID, port, & path: EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); @@ -1288,9 +1485,9 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); // - // 4th update: wildcard pass + // 8th update: wildcard pass // - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "4" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "8" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1311,10 +1508,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/allowed' )EOF")); - EXPECT_EQ(version, "4"); + EXPECT_EQ(version, "8"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected4 = R"EOF(ingress: + std::string expected8 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1328,7 +1525,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected4)); + EXPECT_TRUE(validate("10.1.2.3", expected8)); // Allowed remote ID, port, & path: EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); @@ -1345,9 +1542,9 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); // - // 5th update: split wildcard lower-precedence rule due to pass + // 9th update: split wildcard lower-precedence rule due to pass // - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "5" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "9" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1368,10 +1565,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/allowed' )EOF")); - EXPECT_EQ(version, "5"); + EXPECT_EQ(version, "9"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected5 = R"EOF(ingress: + std::string expected9 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1388,7 +1585,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected5)); + EXPECT_TRUE(validate("10.1.2.3", expected9)); // Remote 43 is promoted above deny by pass. EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); @@ -1397,9 +1594,9 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/allowed"}})); // - // 6th update: wildcard-port pass inherited by specific port rules + // 10th update: wildcard-port pass inherited by specific port rules // - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "6" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "10" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1423,10 +1620,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/allowed' )EOF")); - EXPECT_EQ(version, "6"); + EXPECT_EQ(version, "10"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected6 = R"EOF(ingress: + std::string expected10 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1443,7 +1640,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected6)); + EXPECT_TRUE(validate("10.1.2.3", expected10)); // Pass from wildcard port should promote remote 43 above deny on port 80. EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); @@ -1456,9 +1653,9 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(egressAllowed("10.1.2.3", 43, 80, {{":path", "/public"}})); // - // 7th update: wildcard-port and specific-port pass rules at equal precedence + // 11th update: wildcard-port and specific-port pass rules at equal precedence // - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "7" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "11" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1485,10 +1682,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/allowed' )EOF")); - EXPECT_EQ(version, "7"); + EXPECT_EQ(version, "11"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected7 = R"EOF(ingress: + std::string expected11 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1505,7 +1702,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected7)); + EXPECT_TRUE(validate("10.1.2.3", expected11)); // Both IDs are passed to the lower allow despite the intermediate deny. EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allowed"}})); @@ -1513,7 +1710,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/allowed"}})); // - // 8th update: non-pass rule shadowing inside a pass tier + // 12th update: non-pass rule shadowing inside a pass tier // // The pass rule is required to enable tier processing, but it targets only // remote 45 so the tier is not wildcard-pass and does not pre-shadow 43/44. @@ -1522,7 +1719,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { // - A lower-precedence allow for [43,44] must have 44 removed due to shadowing. // - A second allow at the same precedence for [43] must keep 43, confirming // no same-precedence identity shadowing between allow rules. - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "8" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "12" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1558,10 +1755,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/allow-c' )EOF")); - EXPECT_EQ(version, "8"); + EXPECT_EQ(version, "12"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected8 = R"EOF(ingress: + std::string expected12 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1596,7 +1793,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected8)); + EXPECT_TRUE(validate("10.1.2.3", expected12)); // Remote 43 is not passed, but both same-precedence allow rules remain effective. EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/allow-a"}})); @@ -1614,13 +1811,13 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/allow-c"}})); // - // 9th update: inherited wildcard current-tier pass fully shadowed + // 13th update: inherited wildcard current-tier pass fully shadowed // // Wildcard port has a current-tier pass for remote 43, and specific port has // a higher precedence pass for the same remote on the same tier. When the // wildcard pass is inherited, it is fully shadowed and skipped, as evidenced by the // precedence of the passed-to rule for remote 43, which is 999 rather than 899. - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "9" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "13" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1647,10 +1844,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/shadowed-inherited-pass' )EOF")); - EXPECT_EQ(version, "9"); + EXPECT_EQ(version, "13"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected9 = R"EOF(ingress: + std::string expected13 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1667,7 +1864,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected9)); + EXPECT_TRUE(validate("10.1.2.3", expected13)); // Remote 43 is promoted above deny due to the specific-port pass. EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/shadowed-inherited-pass"}})); @@ -1676,7 +1873,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/shadowed-inherited-pass"}})); // - // 10th update: multiple wildcard pass tiers inherited by a specific port + // 14th update: multiple wildcard pass tiers inherited by a specific port // // Wildcard port contributes two pass tiers: // Tier boundaries are inclusive. @@ -1688,7 +1885,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { // - 41 to tier 1 precedence 900 // - 42 to tier 2 precedence 800 // - 43 remains at tier 3 at precedence 600 - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "10" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "14" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1715,10 +1912,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/multi-tier' )EOF")); - EXPECT_EQ(version, "10"); + EXPECT_EQ(version, "14"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected10 = R"EOF(ingress: + std::string expected14 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1732,7 +1929,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected10)); + EXPECT_TRUE(validate("10.1.2.3", expected14)); // Remote 41 hits the promoted deny from tier 1. EXPECT_FALSE(ingressAllowed("10.1.2.3", 41, 80, {{":path", "/multi-tier"}})); @@ -1742,12 +1939,12 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/multi-tier"}})); // - // 11th update: inconsistent pass precedence via inherited wildcard + local pass + // 15th update: inconsistent pass precedence via inherited wildcard + local pass // // Wildcard current-tier pass (900/700) is inherited for port 80 at local // pass precedence 850. A local pass with pass_precedence 600 on the same tier // must fail as inconsistent. - EXPECT_THROW_WITH_MESSAGE(updateFromYaml(R"EOF(version_info: "11" + EXPECT_THROW_WITH_MESSAGE(updateFromYaml(R"EOF(version_info: "15" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1776,17 +1973,17 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { "PortNetworkPolicy: Inconsistent pass precedence 600 != 700"); // Failed update must leave policy unchanged from version 10. - EXPECT_TRUE(validate("10.1.2.3", expected10)); + EXPECT_TRUE(validate("10.1.2.3", expected14)); EXPECT_FALSE(ingressAllowed("10.1.2.3", 41, 80, {{":path", "/multi-tier"}})); EXPECT_FALSE(ingressAllowed("10.1.2.3", 42, 80, {{":path", "/multi-tier"}})); // - // 12th update: inherited wildcard pass skips remaining rules on that tier + // 16th update: inherited wildcard pass skips remaining rules on that tier // // Wildcard port has a wildcard pass (2000/700), which is inherited for port 80. // Rules in that same tier [1999..700] are skipped; a lower-tier rule at 600 is // retained and promoted to 1900 by the inherited wildcard pass. - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "12" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "16" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1817,10 +2014,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/promoted-after-skip' )EOF")); - EXPECT_EQ(version, "12"); + EXPECT_EQ(version, "16"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected12 = R"EOF(ingress: + std::string expected16 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1834,7 +2031,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected12)); + EXPECT_TRUE(validate("10.1.2.3", expected16)); // Both remotes are allowed by the promoted lower-tier rule. EXPECT_TRUE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/promoted-after-skip"}})); @@ -1844,9 +2041,9 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { EXPECT_FALSE(ingressAllowed("10.1.2.3", 45, 80, {{":path", "/promoted-after-skip"}})); // - // 13th update: Shadowed rules are eliminated + // 17th update: Shadowed rules are eliminated // - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "12" + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "17" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -1877,10 +2074,10 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { - name: ':path' exact_match: '/partially-skipped' )EOF")); - EXPECT_EQ(version, "12"); + EXPECT_EQ(version, "17"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); - std::string expected13 = R"EOF(ingress: + std::string expected17 = R"EOF(ingress: rules: [80-80]: - rules: @@ -1897,7 +2094,7 @@ TEST_F(CiliumNetworkPolicyTest, Precedence) { rules: [] )EOF"; - EXPECT_TRUE(validate("10.1.2.3", expected13)); + EXPECT_TRUE(validate("10.1.2.3", expected17)); EXPECT_FALSE(ingressAllowed("10.1.2.3", 43, 80, {{":path", "/partially-skipped"}})); EXPECT_TRUE(ingressAllowed("10.1.2.3", 44, 80, {{":path", "/partially-skipped"}})); @@ -2490,6 +2687,7 @@ TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { )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)); @@ -2515,7 +2713,7 @@ TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { EXPECT_FALSE(tls_socket_required); EXPECT_FALSE(raw_socket_allowed); - // TLS SNI update + // 2nd update without TLS requirements, with lower precedence wildcard port deny EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy @@ -2523,13 +2721,170 @@ TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { - "10.1.2.3" endpoint_id: 42 ingress_per_port_policies: + - port: 0 + rules: + - remote_policies: [ 43 ] + deny: true + precedence: 0 - port: 80 rules: - remote_policies: [ 43 ] - server_names: [ "cilium.io", "example.com" ] + precedence: 1 )EOF")); EXPECT_EQ(version, "2"); EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + std::string expected2 = R"EOF(ingress: + rules: + [0-0]: + - rules: + - remotes: [43] + deny: true + [80-80]: + - rules: + - remotes: [43] + precedence: 1 + - remotes: [43] + deny: true +egress: + rules: [] +)EOF"; + + EXPECT_TRUE(validate("10.1.2.3", expected2)); + + // 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); + + // 3rd update without TLS requirements, with same precedence wildcard port deny + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "3" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - remote_policies: [ 43 ] + deny: true + precedence: 0 + - port: 80 + rules: + - remote_policies: [ 43 ] + precedence: 0 +)EOF")); + EXPECT_EQ(version, "3"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + // Denied remote ID & port: + EXPECT_FALSE(tlsIngressAllowed("10.1.2.3", 43, 80, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // SNI does not matter: + 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); + + // 4th update without TLS requirements, with higher precedence wildcard port deny + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "4" +resources: +- "@type": type.googleapis.com/cilium.NetworkPolicy + endpoint_ips: + - "10.1.2.3" + endpoint_id: 42 + ingress_per_port_policies: + - port: 0 + rules: + - remote_policies: [ 43 ] + deny: true + precedence: 1 + - port: 80 + rules: + - remote_policies: [ 43 ] + precedence: 0 +)EOF")); + EXPECT_EQ(version, "4"); + EXPECT_TRUE(policy_map_->exists("10.1.2.3")); + + // Denied remote ID & port: + EXPECT_FALSE(tlsIngressAllowed("10.1.2.3", 43, 80, "example.com", tls_socket_required, + raw_socket_allowed)); + EXPECT_FALSE(tls_socket_required); + EXPECT_FALSE(raw_socket_allowed); + // SNI does not matter: + 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); + + // 5th Update: TLS SNI update + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "5" +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, "5"); + 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)); @@ -2565,8 +2920,8 @@ TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { EXPECT_FALSE(tls_socket_required); EXPECT_FALSE(raw_socket_allowed); - // TLS Interception update - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" + // 6th update: TLS Interception update + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "6" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -2582,8 +2937,9 @@ TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { upstream_tls_context: validation_context_sds_secret: "cacerts" )EOF")); - EXPECT_EQ(version, "2"); + EXPECT_EQ(version, "6"); 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)); @@ -2619,8 +2975,8 @@ TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { EXPECT_FALSE(tls_socket_required); EXPECT_FALSE(raw_socket_allowed); - // TLS Termination update - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" + // 7th update: TLS Termination update + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "7" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -2634,8 +2990,9 @@ TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { downstream_tls_context: tls_sds_secret: "secret1" )EOF")); - EXPECT_EQ(version, "2"); + EXPECT_EQ(version, "7"); 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)); @@ -2671,8 +3028,8 @@ TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { EXPECT_FALSE(tls_socket_required); EXPECT_FALSE(raw_socket_allowed); - // TLS Origination update - EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "2" + // 8th update: TLS Origination update + EXPECT_NO_THROW(version = updateFromYaml(R"EOF(version_info: "8" resources: - "@type": type.googleapis.com/cilium.NetworkPolicy endpoint_ips: @@ -2685,8 +3042,9 @@ TEST_F(CiliumNetworkPolicyTest, TlsPolicyUpdate) { upstream_tls_context: validation_context_sds_secret: "cacerts" )EOF")); - EXPECT_EQ(version, "2"); + EXPECT_EQ(version, "8"); 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)); From 018ff0dbee239905252a8c7e32bdcee7bc7d24a1 Mon Sep 17 00:00:00 2001 From: Jarno Rajahalme Date: Tue, 24 Feb 2026 10:03:21 +0100 Subject: [PATCH 8/8] policy: Refactor to simplify No functional changes, but easier to read. Signed-off-by: Jarno Rajahalme --- cilium/network_policy.cc | 253 +++++++++++++++++++-------------------- cilium/network_policy.h | 3 - 2 files changed, 124 insertions(+), 132 deletions(-) diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index 715a15ba3..992b72e1f 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -1,6 +1,8 @@ #include "cilium/network_policy.h" +#include #include +#include #include #include @@ -58,6 +60,34 @@ #include "cilium/ipcache.h" #include "cilium/secret_watcher.h" +namespace fmt { + +template <> struct formatter { + constexpr auto parse(fmt::format_parse_context& ctx) { return ctx.begin(); } + + template + auto format(Envoy::Cilium::RuleVerdict verdict, FormatContext& ctx) const { + absl::string_view name; + switch (verdict) { + case Envoy::Cilium::RuleVerdict::None: + name = "NONE"; + break; + case Envoy::Cilium::RuleVerdict::Allow: + name = "ALLOW"; + break; + case Envoy::Cilium::RuleVerdict::Deny: + name = "DENY"; + break; + default: + name = "UNKNOWN"; + break; + } + return std::ranges::copy(name, ctx.out()).out; + } +}; + +} // namespace fmt + namespace Envoy { namespace Cilium { @@ -487,19 +517,8 @@ class PortNetworkPolicyRule : public Logger::Loggable { return RuleVerdict::None; } // Remote ID must match if we have any. - if (!isRemoteWildcard()) { - auto match = remotes_.find(remote_id); - if (match != remotes_.end()) { - // remote ID matched - if (deny_) { - // Explicit deny - return RuleVerdict::Deny; - } - // Explicit allow - return RuleVerdict::Allow; - } - // Not found, not allowed, but also not explicitly denied - return RuleVerdict{}; + if (!isRemoteWildcard() && !remotes_.contains(remote_id)) { + return RuleVerdict::None; // no verdict } // Allow rules allow by default when remotes_ is empty, deny rules do not return deny_ ? RuleVerdict::Deny : RuleVerdict::Allow; @@ -507,20 +526,11 @@ class PortNetworkPolicyRule : public Logger::Loggable { RuleVerdict getVerdict(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni) const { // sni must match if we have any - if (!allowed_snis_.empty()) { - if (sni.length() == 0) { - return RuleVerdict::None; // no verdict, not allowed, not denied, some other rule may allow - } - bool matched = false; - for (const auto& pattern : allowed_snis_) { - if (pattern.matches(sni)) { - matched = true; - break; - } - } - if (!matched) { - return RuleVerdict::None; - } + if (!allowed_snis_.empty() && + (sni.length() == 0 || std::ranges::none_of(allowed_snis_, [&](const auto& pattern) { + return pattern.matches(sni); + }))) { + return RuleVerdict::None; } return getVerdict(proxy_id, remote_id); } @@ -529,80 +539,70 @@ class PortNetworkPolicyRule : public Logger::Loggable { Envoy::Http::RequestHeaderMap& headers, Cilium::AccessLog::Entry& log_entry) const { auto verdict = getVerdict(proxy_id, remote_id); - if (verdict == RuleVerdict::Allow && hasHttpRules()) { - bool header_matched = false; - for (const auto& rule : *http_rules_) { - if (rule.allowed(headers)) { - // Return on the first match if no rule has HeaderMatches - if (!has_headermatches_) { - return verdict; - } - // Otherwise evaluate all rules to run all the header actions, - // and remember if any of them matched - if (rule.headerMatches(headers, log_entry)) { - header_matched = true; - } - } + if (!hasHttpRules() || verdict != RuleVerdict::Allow) { + return verdict; + } + if (!has_headermatches_) { + if (std::ranges::any_of(*http_rules_, [&](auto& r) { return r.allowed(headers); })) { + return RuleVerdict::Allow; } - if (!header_matched) { - verdict = RuleVerdict::None; + return RuleVerdict::None; + } + + // Evaluate all rules to run all the header actions, + // and remember if any of them matched + bool header_matched = false; + for (const auto& rule : *http_rules_) { + if (rule.allowed(headers)) { + if (rule.headerMatches(headers, log_entry)) { + header_matched = true; + } } } - return verdict; + return (header_matched) ? RuleVerdict::Allow : RuleVerdict::None; } RuleVerdict useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto) const { auto verdict = getVerdict(proxy_id, remote_id); - if (verdict == RuleVerdict::Allow) { - if (l7_proto_.length() > 0) { - ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule::useProxylib(): returning {}", l7_proto_); - l7_proto = l7_proto_; - return verdict; - } - // keep looking past allows if no proxylib - verdict = RuleVerdict::None; + if (verdict != RuleVerdict::Allow) { + return verdict; } - return verdict; + if (l7_proto_.length() > 0) { + ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule::useProxylib(): returning {}", l7_proto_); + l7_proto = l7_proto_; + return RuleVerdict::Allow; // found a proxylib match + } + // keep looking past allows if no proxylib + return RuleVerdict::None; } // Envoy Metadata matcher, called after deny has already been checked for RuleVerdict getVerdict(uint16_t proxy_id, uint32_t remote_id, const envoy::config::core::v3::Metadata& metadata) const { auto verdict = getVerdict(proxy_id, remote_id); - if (verdict == RuleVerdict::Allow) { - for (const auto& rule : l7_deny_rules_) { - if (rule.matches(metadata)) { - ENVOY_LOG(trace, - "Cilium L7 PortNetworkPolicyRule::allowed(): DENY due to " - "a matching deny rule {}", - rule.name_); - // request is denied if any deny rule matches - verdict = RuleVerdict::Deny; - return verdict; - } - } - if (!l7_allow_rules_.empty()) { - for (const auto& rule : l7_allow_rules_) { - if (rule.matches(metadata)) { - ENVOY_LOG(trace, - "Cilium L7 PortNetworkPolicyRule::allowed(): ALLOW due " - "to a matching allow rule {}", - rule.name_); - return verdict; - } - } - ENVOY_LOG(trace, - "Cilium L7 PortNetworkPolicyRule::allowed(): SKIP due to " - "all {} allow rules mismatching", - l7_allow_rules_.size()); - verdict = RuleVerdict::None; - return verdict; - } - ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule::allowed(): default ALLOW " - "due to no allow rules"); - return verdict; // allowed by default + if (verdict != RuleVerdict::Allow) { + return verdict; } - return verdict; + + if (std::ranges::any_of(l7_deny_rules_, + [&](const auto& rule) { return rule.matches(metadata); })) { + ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule: DENY due to a matching deny rule"); + return RuleVerdict::Deny; // request is denied if any deny rule matches + } + + if (l7_allow_rules_.empty()) { + ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule: default ALLOW due to no allow rules"); + return RuleVerdict::Allow; // allowed by default + } + + if (std::ranges::any_of(l7_allow_rules_, + [&](const auto& rule) { return rule.matches(metadata); })) { + ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule: ALLOW due to a matching allow rule"); + return RuleVerdict::Allow; + } + + ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule: SKIP due to all allow rules mismatching"); + return RuleVerdict::None; } // getServerTlsContext returns true if the rule has server TLS context that was passed to the @@ -631,13 +631,7 @@ class PortNetworkPolicyRule : public Logger::Loggable { void toString(int indent, std::string& res) const { res.append(indent - 2, ' ').append("- remotes: ["); - int count = 0; - for (auto remote : remotes_) { - if (count++ > 0) { - res.append(","); - } - res.append(fmt::format("{}", remote)); - } + res.append(fmt::format("{}", fmt::join(remotes_, ","))); res.append("]\n"); if (name_.length() > 0) { @@ -910,9 +904,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, headers: {}): {}", - proxy_id, remote_id, headers, - verdict != RuleVerdict::None ? (verdict == RuleVerdict::Allow ? "ALLOWED" : "DENIED") - : "no verdict"); + proxy_id, remote_id, headers, verdict); return verdict; } @@ -921,9 +913,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { true, [&](const auto& rule) { return rule.getVerdict(proxy_id, remote_id, sni); }); ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, sni: {}): {}", - proxy_id, remote_id, sni, - verdict != RuleVerdict::None ? (verdict == RuleVerdict::Allow ? "ALLOWED" : "DENIED") - : "no verdict"); + proxy_id, remote_id, sni, verdict); return verdict; } @@ -939,9 +929,7 @@ class PortNetworkPolicyRules : public Logger::Loggable { ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, metadata: {}): {}", - proxy_id, remote_id, metadata.DebugString(), - verdict != RuleVerdict::None ? (verdict == RuleVerdict::Allow ? "ALLOWED" : "DENIED") - : "no verdict"); + proxy_id, remote_id, metadata.DebugString(), verdict); return verdict; } @@ -990,28 +978,32 @@ class PortNetworkPolicyRules : public Logger::Loggable { bool initialized_{false}; }; -// end port is zero on lookup! -PortPolicy::PortPolicy(const PolicyMap& map, uint16_t port) - : map_(map), port_rules_([&]() { - auto it = map_.find({port, port}); - if (it != map_.cend()) { - return &it->second; - } - it = map_.find({0, 0}); - return it != map_.cend() ? &it->second : nullptr; - }()), - has_http_rules_(port_rules_ && port_rules_->hasHttpRules()) {} - -bool PortPolicy::forRange( - std::function get_verdict) const { - if (port_rules_) { - auto verdict = get_verdict(*port_rules_); +namespace { - return verdict == RuleVerdict::Allow; +const PortNetworkPolicyRules* findPortRules(const PolicyMap& map, uint16_t port) { + // Look up with an exact port first, then fall back to the wildcard port (0). If policy is found + // with the exact port, then the returned policy also contains all the wildcard port rules, so we + // do not need to perform a separate wildcard port policy lookup. If no policy is defined for the + // given port, then the wildcard port policy, consisting just of the wildcard port rules, is used, + // if one exists. + // + // On lookups we'll set both ends of the port range to the same port number, which will find the + // one range that it overlaps with in the map, if one exists (ref. PortRangeCompare definition). + if (const auto it = map.find({port, port}); it != map.cend()) { + return &it->second; } - return false; + if (const auto wildcard = map.find({0, 0}); wildcard != map.cend()) { + return &wildcard->second; + } + return nullptr; } +} // namespace + +PortPolicy::PortPolicy(const PolicyMap& map, uint16_t port) + : map_(map), port_rules_(findPortRules(map_, port)), + has_http_rules_(port_rules_ && port_rules_->hasHttpRules()) {} + bool PortPolicy::useProxylib(uint16_t proxy_id, uint32_t remote_id, std::string& l7_proto) const { if (port_rules_) { auto verdict = port_rules_->useProxylib(proxy_id, remote_id, l7_proto); @@ -1031,22 +1023,25 @@ bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, if (!has_http_rules_) { return true; } - return forRange([&](const PortNetworkPolicyRules& rules) { - return rules.getVerdict(proxy_id, remote_id, headers, log_entry); - }); + if (!port_rules_) { + return false; + } + return port_rules_->getVerdict(proxy_id, remote_id, headers, log_entry) == RuleVerdict::Allow; } bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, absl::string_view sni) const { - return forRange([&](const PortNetworkPolicyRules& rules) -> RuleVerdict { - return rules.getVerdict(proxy_id, remote_id, sni); - }); + if (!port_rules_) { + return false; + } + return port_rules_->getVerdict(proxy_id, remote_id, sni) == RuleVerdict::Allow; } bool PortPolicy::allowed(uint16_t proxy_id, uint32_t remote_id, const envoy::config::core::v3::Metadata& metadata) const { - return forRange([&](const PortNetworkPolicyRules& rules) -> RuleVerdict { - return rules.getVerdict(proxy_id, remote_id, metadata); - }); + if (!port_rules_) { + return false; + } + return port_rules_->getVerdict(proxy_id, remote_id, metadata) == RuleVerdict::Allow; } Ssl::ContextSharedPtr PortPolicy::getServerTlsContext(uint16_t proxy_id, uint32_t remote_id, diff --git a/cilium/network_policy.h b/cilium/network_policy.h index 0e9a388d4..128a15170 100644 --- a/cilium/network_policy.h +++ b/cilium/network_policy.h @@ -127,9 +127,6 @@ class PortPolicy : public Logger::Loggable { bool& raw_socket_allowed) const; private: - bool forRange(std::function get_verdict) const; - RuleVerdict forFirstRange(std::function f) const; - const PolicyMap& map_; // using raw pointers by design: // - pointer to distinguish between no rules and empty rules