diff --git a/pkg/render/istio/istio.go b/pkg/render/istio/istio.go index cd75d60643..255d4516d0 100644 --- a/pkg/render/istio/istio.go +++ b/pkg/render/istio/istio.go @@ -319,6 +319,20 @@ func (c *IstioComponent) istiodCalicoSystemPolicy() *v3.NetworkPolicy { }, } + // In BPF mode, CTLB is disabled for Istio ambient compatibility. Without CTLB and + // kube-proxy, return traffic (SYN-ACKs) from istiod to ztunnel on remote nodes may not + // be matched by conntrack. Allow explicit egress to ztunnel pods to ensure cross-node + // ztunnel<->istiod communication works. + if c.cfg.Installation.BPFEnabled() { + egressRules = append(egressRules, v3.Rule{ + Action: v3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: v3.EntityRule{ + Selector: networkpolicy.KubernetesAppSelector(IstioZTunnelDaemonSetName), + }, + }) + } + // * Port 15012, gRPC, XDS and CA services (TLS and mTLS) // ztunnel and waypoints connect to it to request certs and dataplane // info. @@ -385,6 +399,20 @@ func (c *IstioComponent) ztunnelCalicoSystemPolicy() *v3.NetworkPolicy { Destination: networkpolicy.CreateServiceSelectorEntityRule(c.cfg.IstioNamespace, IstioIstiodServiceName), }, } + + // In BPF mode, CTLB is disabled for Istio ambient compatibility. Service-based egress + // selectors don't resolve correctly for cross-node traffic without CTLB and kube-proxy. + // Add a direct pod-selector rule so ztunnel can reach istiod by pod IP on any node. + if c.cfg.Installation.BPFEnabled() { + egressRules = append(egressRules, v3.Rule{ + Action: v3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: v3.EntityRule{ + Selector: networkpolicy.KubernetesAppSelector(IstioIstiodDeploymentName), + }, + }) + } + egressRules = networkpolicy.AppendDNSEgressRules(egressRules, c.cfg.Installation.KubernetesProvider.IsOpenShift()) return &v3.NetworkPolicy{ diff --git a/pkg/render/istio/istio_test.go b/pkg/render/istio/istio_test.go index 335c07939c..5dbfabc125 100644 --- a/pkg/render/istio/istio_test.go +++ b/pkg/render/istio/istio_test.go @@ -279,6 +279,58 @@ var _ = Describe("Istio Component Rendering", func() { Expect(foundIstiodRule).To(BeTrue(), "Expected egress rule for istiod service") }) + It("should add pod-selector egress rules when BPF dataplane is enabled", func() { + bpfDataplane := operatorv1.LinuxDataplaneBPF + cfg.Installation.CalicoNetwork = &operatorv1.CalicoNetworkSpec{ + LinuxDataplane: &bpfDataplane, + } + _, component, err := istio.Istio(cfg) + Expect(err).ShouldNot(HaveOccurred()) + Expect(component.ResolveImages(getCalicoTestImageSet())).ShouldNot(HaveOccurred()) + objsToCreate, _ := component.Objects() + + // Verify istiod policy has egress rule to ztunnel pods + istiodPolicy, err := rtest.GetResourceOfType[*v3.NetworkPolicy](objsToCreate, istio.IstioIstiodPolicyName, istio.IstioNamespace) + Expect(err).ShouldNot(HaveOccurred()) + Expect(istiodPolicy.Spec.Egress).To(HaveLen(2)) + Expect(istiodPolicy.Spec.Egress[1].Destination.Selector).To(Equal( + networkpolicy.KubernetesAppSelector(istio.IstioZTunnelDaemonSetName), + )) + + // Verify ztunnel policy has pod-selector egress rule to istiod + ztunnelPolicy, err := rtest.GetResourceOfType[*v3.NetworkPolicy](objsToCreate, istio.IstioZTunnelPolicyName, istio.IstioNamespace) + Expect(err).ShouldNot(HaveOccurred()) + foundPodSelectorRule := false + for _, rule := range ztunnelPolicy.Spec.Egress { + if rule.Destination.Selector == networkpolicy.KubernetesAppSelector(istio.IstioIstiodDeploymentName) { + foundPodSelectorRule = true + break + } + } + Expect(foundPodSelectorRule).To(BeTrue(), "Expected pod-selector egress rule for istiod when BPF enabled") + }) + + It("should not add pod-selector egress rules when BPF dataplane is not enabled", func() { + _, component, err := istio.Istio(cfg) + Expect(err).ShouldNot(HaveOccurred()) + Expect(component.ResolveImages(getCalicoTestImageSet())).ShouldNot(HaveOccurred()) + objsToCreate, _ := component.Objects() + + // Verify istiod policy has only 1 egress rule (kube API server) + istiodPolicy, err := rtest.GetResourceOfType[*v3.NetworkPolicy](objsToCreate, istio.IstioIstiodPolicyName, istio.IstioNamespace) + Expect(err).ShouldNot(HaveOccurred()) + Expect(istiodPolicy.Spec.Egress).To(HaveLen(1)) + + // Verify ztunnel policy has no pod-selector rule for istiod + ztunnelPolicy, err := rtest.GetResourceOfType[*v3.NetworkPolicy](objsToCreate, istio.IstioZTunnelPolicyName, istio.IstioNamespace) + Expect(err).ShouldNot(HaveOccurred()) + for _, rule := range ztunnelPolicy.Spec.Egress { + Expect(rule.Destination.Selector).NotTo(Equal( + networkpolicy.KubernetesAppSelector(istio.IstioIstiodDeploymentName), + ), "Should not have pod-selector egress rule for istiod when BPF is not enabled") + } + }) + It("should set TRANSPARENT_NETWORK_POLICIES env var on ztunnel", func() { _, component, err := istio.Istio(cfg) Expect(err).ShouldNot(HaveOccurred())