From 816569fbdc784816abf477131ae9641020deb918 Mon Sep 17 00:00:00 2001 From: Mads Jon Nielsen Date: Fri, 9 Oct 2026 10:33:59 +0200 Subject: [PATCH] policy: Hold listeners until the first network policy update Listeners started accepting connections before the first NetworkPolicy update was installed, so connections accepted in that window had no policy for local endpoints and every request on them failed with 500 for the lifetime of the connection. Each listener now waits for the first policy update, or 5 seconds at most. Signed-off-by: Mads Jon Nielsen --- cilium/bpf_metadata.cc | 1 + cilium/network_policy.cc | 18 +++++++++++ cilium/network_policy.h | 2 ++ tests/cilium_network_policy_test.cc | 48 +++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/cilium/bpf_metadata.cc b/cilium/bpf_metadata.cc index d67552845..9550bf657 100644 --- a/cilium/bpf_metadata.cc +++ b/cilium/bpf_metadata.cc @@ -303,6 +303,7 @@ Config::Config(const ::cilium::BpfMetadata& config, pin_for_ads); // update desired config source on the map npmap_->configure(config_source_); + npmap_->addListenerInitTarget(context.initManager()); } } diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index 90f924ed7..045d5155b 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include "envoy/config/core/v3/base.pb.h" #include "envoy/config/core/v3/config_source.pb.h" #include "envoy/config/subscription.h" +#include "envoy/event/timer.h" #include "envoy/http/header_map.h" #include "envoy/init/manager.h" #include "envoy/network/address.h" @@ -427,6 +429,8 @@ class NetworkPolicyMapImpl : public ManagedGrpcSubscription { // init target which starts gRPC subscription Init::TargetImpl init_target_; + Init::SharedTargetImpl initial_policy_target_{"Cilium NetworkPolicy first update", []() {}}; + Event::TimerPtr initial_policy_timer_; std::shared_ptr transport_factory_context_; // Declared after transport_factory_context_ so that the cache, which retains a shared reference @@ -1948,6 +1952,10 @@ NetworkPolicyMap::~NetworkPolicyMap() { context_.mainThreadDispatcher().post([impl = std::move(impl_)]() mutable { impl.reset(); }); } +void NetworkPolicyMap::addListenerInitTarget(Init::Manager& listener_init_manager) { + listener_init_manager.add(impl_->initial_policy_target_); +} + bool NetworkPolicyMap::exists(const std::string& endpoint_policy_name) const { return impl_->getPolicyInstanceImpl(endpoint_policy_name); } @@ -2000,6 +2008,14 @@ NetworkPolicyMapImpl::NetworkPolicyMapImpl( store(new PolicyMapSnapshot()); ENVOY_LOG(trace, "NetworkPolicyMapImpl({}) created.", instance_id_); + initial_policy_timer_ = context_.mainThreadDispatcher().createTimer([this]() { + if (initial_policy_target_.ready()) { + ENVOY_LOG(warn, "Cilium NetworkPolicy: no policy update within 5 seconds, starting listeners " + "without network policy"); + } + }); + initial_policy_timer_->enableTimer(std::chrono::seconds(5)); + if (context_.admin().has_value()) { ENVOY_LOG(debug, "Registering NetworkPolicies to config tracker"); config_tracker_entry_ = context_.admin()->getConfigTracker().add( @@ -2228,6 +2244,7 @@ absl::Status NetworkPolicyMapImpl::onConfigUpdate( installNewPolicyMap(std::move(pending_resource_map), version_init_manager, std::move(version_name), policy_stream_state); + initial_policy_target_.ready(); return absl::OkStatus(); } @@ -2340,6 +2357,7 @@ absl::Status NetworkPolicyMapImpl::onConfigUpdate( } installNewPolicyMap(std::move(pending_resource_map), version_init_manager, std::move(version_name), policy_stream_state); + initial_policy_target_.ready(); return absl::OkStatus(); } diff --git a/cilium/network_policy.h b/cilium/network_policy.h index e6d096b80..69eb7d790 100644 --- a/cilium/network_policy.h +++ b/cilium/network_policy.h @@ -12,6 +12,7 @@ #include "envoy/config/core/v3/config_source.pb.h" #include "envoy/config/subscription.h" #include "envoy/http/header_map.h" +#include "envoy/init/manager.h" #include "envoy/network/address.h" #include "envoy/protobuf/message_validator.h" #include "envoy/server/factory_context.h" @@ -202,6 +203,7 @@ class NetworkPolicyMap : public Singleton::Instance, public Logger::Loggable(&factory_context_.server_factory_context_.dispatcher_); policy_map_ = std::make_shared(factory_context_, Cilium::CILIUM_XDS_API_CONFIG, false); } @@ -279,6 +284,7 @@ class CiliumNetworkPolicyTest : public ::testing::Test { NiceMock factory_context_; NiceMock secret_manager_; std::shared_ptr policy_map_; + NiceMock* initial_policy_timer_; NiceMock store_; uint16_t proxy_id_ = 42; }; @@ -287,6 +293,48 @@ TEST_F(CiliumNetworkPolicyTest, UpdatesRejectedStatName) { EXPECT_EQ("cilium.policy.updates_rejected", updatesRejectedStatName()); } +TEST_F(CiliumNetworkPolicyTest, ListenerInitWaitsForFirstPolicyUpdate) { + Init::ManagerImpl listener_init_manager("listener"); + policy_map_->addListenerInitTarget(listener_init_manager); + bool initialized = false; + Init::WatcherImpl listener_watcher("listener", [&initialized]() { initialized = true; }); + listener_init_manager.initialize(listener_watcher); + EXPECT_FALSE(initialized); + EXPECT_TRUE(subscriptionCallbacks().onConfigUpdate({}, "1").ok()); + EXPECT_TRUE(initialized); +} + +TEST_F(CiliumNetworkPolicyTest, ListenerInitStartedAfterFirstPolicyUpdateDoesNotWait) { + Init::ManagerImpl listener_init_manager("listener"); + policy_map_->addListenerInitTarget(listener_init_manager); + EXPECT_TRUE(subscriptionCallbacks().onConfigUpdate({}, "1").ok()); + bool initialized = false; + Init::WatcherImpl listener_watcher("listener", [&initialized]() { initialized = true; }); + listener_init_manager.initialize(listener_watcher); + EXPECT_TRUE(initialized); +} + +TEST_F(CiliumNetworkPolicyTest, ListenerAddedAfterFirstPolicyUpdateDoesNotWait) { + EXPECT_TRUE(subscriptionCallbacks().onConfigUpdate({}, "1").ok()); + Init::ManagerImpl listener_init_manager("listener"); + policy_map_->addListenerInitTarget(listener_init_manager); + bool initialized = false; + Init::WatcherImpl listener_watcher("listener", [&initialized]() { initialized = true; }); + listener_init_manager.initialize(listener_watcher); + EXPECT_TRUE(initialized); +} + +TEST_F(CiliumNetworkPolicyTest, ListenerInitWaitsUntilFirstPolicyUpdateTimeout) { + Init::ManagerImpl listener_init_manager("listener"); + policy_map_->addListenerInitTarget(listener_init_manager); + bool initialized = false; + Init::WatcherImpl listener_watcher("listener", [&initialized]() { initialized = true; }); + listener_init_manager.initialize(listener_watcher); + EXPECT_FALSE(initialized); + initial_policy_timer_->invokeCallback(); + EXPECT_TRUE(initialized); +} + TEST_F(CiliumNetworkPolicyTest, EmptyPolicyUpdate) { EXPECT_TRUE(subscriptionCallbacks().onConfigUpdate({}, "1").ok()); EXPECT_FALSE(validate("10.1.2.3", "")); // Policy not found