diff --git a/pkg/controller/k8sapi/k8s-endpoint.go b/pkg/controller/k8sapi/k8s-endpoint.go index ad616eacfc..5a9ddcac2c 100644 --- a/pkg/controller/k8sapi/k8s-endpoint.go +++ b/pkg/controller/k8sapi/k8s-endpoint.go @@ -16,9 +16,12 @@ package k8sapi import ( "fmt" + "net" "os" "strings" + calicov3 "github.com/tigera/api/pkg/apis/projectcalico/v3" + "github.com/tigera/api/pkg/lib/numorstring" operator "github.com/tigera/operator/api/v1" v1 "k8s.io/api/core/v1" ) @@ -65,6 +68,38 @@ func (k8s ServiceEndpoint) EnvVars(hostNetworked bool, provider operator.Provide } } +// DestinationEntityRule returns an EntityRule to match the Host and Port +// if the ServiceEndpoint was set. It returns nil if either was empty. +func (k8s ServiceEndpoint) DestinationEntityRule() (*calicov3.EntityRule, error) { + if k8s.Host == "" || k8s.Port == "" { + return nil, nil + } + + p, err := numorstring.PortFromString(k8s.Port) + if err != nil { + return nil, err + } + + rule := calicov3.EntityRule{ + Ports: []numorstring.Port{p}, + } + + ip := net.ParseIP(k8s.Host) + if ip == nil { + rule.Domains = []string{k8s.Host} + } else { + var netSuffix string + if ip.To4() != nil { + netSuffix = "/32" + } else { + netSuffix = "/128" + } + rule.Nets = []string{ip.String() + netSuffix} + } + + return &rule, nil +} + func (k8s ServiceEndpoint) CNIAPIRoot() string { if k8s.Host == "" || k8s.Port == "" { return "" diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index 8c42169d1e..701dfe4ab8 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -503,6 +503,14 @@ func allowTigeraAPIServerPolicy(cfg *APIServerConfiguration) *v3.NetworkPolicy { }, }...) + if r, err := cfg.K8SServiceEndpoint.DestinationEntityRule(); r != nil && err == nil { + egressRules = append(egressRules, v3.Rule{ + Action: v3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: *r, + }) + } + // The ports Calico Enterprise API Server and Calico Enterprise Query Server are configured to listen on. ingressPorts := networkpolicy.Ports(443, APIServerPort, QueryServerPort, 10443) if cfg.IsSidecarInjectionEnabled() { diff --git a/pkg/render/apiserver_test.go b/pkg/render/apiserver_test.go index 8899ac246a..69f8e6a0cd 100644 --- a/pkg/render/apiserver_test.go +++ b/pkg/render/apiserver_test.go @@ -28,6 +28,7 @@ import ( "github.com/openshift/library-go/pkg/crypto" + calicov3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/apis" "github.com/tigera/operator/pkg/common" @@ -38,6 +39,7 @@ import ( "github.com/tigera/operator/pkg/dns" "github.com/tigera/operator/pkg/render" rmeta "github.com/tigera/operator/pkg/render/common/meta" + "github.com/tigera/operator/pkg/render/common/networkpolicy" "github.com/tigera/operator/pkg/render/common/podaffinity" rtest "github.com/tigera/operator/pkg/render/common/test" "github.com/tigera/operator/pkg/render/testutils" @@ -662,6 +664,50 @@ var _ = Describe("API server rendering tests (Calico Enterprise)", func() { rtest.ExpectK8sServiceEpEnvVars(deployment.Spec.Template.Spec, "k8shost", "1234") }) + It("should add egress policy with Enterprise variant and K8SServiceEndpoint defined", func() { + cfg.K8SServiceEndpoint.Host = "k8shost" + cfg.K8SServiceEndpoint.Port = "1234" + cfg.ForceHostNetwork = true + + component := render.APIServerPolicy(cfg) + resources, _ := component.Objects() + policyName := types.NamespacedName{Name: "allow-tigera.cnx-apiserver-access", Namespace: "tigera-system"} + policy := testutils.GetAllowTigeraPolicyFromResources(policyName, resources) + Expect(policy).ToNot(BeNil()) + Expect(policy.Spec).ToNot(BeNil()) + Expect(policy.Spec.Egress).ToNot(BeNil()) + Expect(policy.Spec.Egress).To(ContainElement(calicov3.Rule{ + Action: calicov3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: calicov3.EntityRule{ + Ports: networkpolicy.Ports(1234), + Domains: []string{"k8shost"}, + }, + })) + }) + + It("should add egress policy with Enterprise variant and K8SServiceEndpoint as IP defined", func() { + cfg.K8SServiceEndpoint.Host = "169.169.169.169" + cfg.K8SServiceEndpoint.Port = "4321" + cfg.ForceHostNetwork = false + + component := render.APIServerPolicy(cfg) + resources, _ := component.Objects() + policyName := types.NamespacedName{Name: "allow-tigera.cnx-apiserver-access", Namespace: "tigera-system"} + policy := testutils.GetAllowTigeraPolicyFromResources(policyName, resources) + Expect(policy).ToNot(BeNil()) + Expect(policy.Spec).ToNot(BeNil()) + Expect(policy.Spec.Egress).ToNot(BeNil()) + Expect(policy.Spec.Egress).To(ContainElement(calicov3.Rule{ + Action: calicov3.Allow, + Protocol: &networkpolicy.TCPProtocol, + Destination: calicov3.EntityRule{ + Ports: networkpolicy.Ports(4321), + Nets: []string{"169.169.169.169/32"}, + }, + })) + }) + It("should not set KUBERENETES_SERVICE_... variables if not host networked on Docker EE with proxy.local", func() { cfg.K8SServiceEndpoint.Host = "proxy.local" cfg.K8SServiceEndpoint.Port = "1234"