From 211e5ace049e5dd7ea2dee6536d913637bb8fd3a Mon Sep 17 00:00:00 2001 From: Walter Neto Date: Tue, 15 Oct 2024 21:24:28 +0100 Subject: [PATCH 1/3] Introdoces SidecarWebhook status --- api/v1/applicationlayer_types.go | 8 ++++++ api/v1/zz_generated.deepcopy.go | 5 ++++ pkg/common/common.go | 3 +++ .../applicationlayer_controller.go | 26 +++++++++++++++++++ .../operator.tigera.io_applicationlayers.yaml | 7 +++++ pkg/render/apiserver.go | 7 +++-- 6 files changed, 52 insertions(+), 4 deletions(-) diff --git a/api/v1/applicationlayer_types.go b/api/v1/applicationlayer_types.go index 76ced6ba94..0c6c48875c 100644 --- a/api/v1/applicationlayer_types.go +++ b/api/v1/applicationlayer_types.go @@ -58,6 +58,9 @@ type ApplicationLayerPolicyStatusType string // +kubebuilder:validation:Enum=Enabled;Disabled type SidecarStatusType string +// +kubebuilder:validation:Enum=Enabled;Disabled +type SidecarWebhookStateType string + const ( WAFDisabled WAFStatusType = "Disabled" WAFEnabled WAFStatusType = "Enabled" @@ -67,6 +70,8 @@ const ( ApplicationLayerPolicyDisabled ApplicationLayerPolicyStatusType = "Disabled" SidecarEnabled SidecarStatusType = "Enabled" SidecarDisabled SidecarStatusType = "Disabled" + SidecarWebhookStateEnabled SidecarWebhookStateType = "Enabled" + SidecarWebhookStateDisabled SidecarWebhookStateType = "Disabled" ) type EnvoySettings struct { @@ -110,6 +115,9 @@ type ApplicationLayerStatus struct { // State provides user-readable status. State string `json:"state,omitempty"` + // SidecarWebhook provides the state of sidecar injection mutatinwebhookconfiguration + SidecarWebhook *SidecarWebhookStateType `json:"sidecarWebhook,omitempty"` + // Conditions represents the latest observed set of conditions for the component. A component may be one or more of // Ready, Progressing, Degraded or other customer types. // +optional diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 9fb9462aeb..77b6144ca4 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -532,6 +532,11 @@ func (in *ApplicationLayerSpec) DeepCopy() *ApplicationLayerSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplicationLayerStatus) DeepCopyInto(out *ApplicationLayerStatus) { *out = *in + if in.SidecarWebhook != nil { + in, out := &in.SidecarWebhook, &out.SidecarWebhook + *out = new(SidecarWebhookStateType) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]metav1.Condition, len(*in)) diff --git a/pkg/common/common.go b/pkg/common/common.go index a7a54a9eff..b65bf60c7b 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -41,4 +41,7 @@ const ( // references with any that already exist on the object rather than replace the owner references. Further // the controller in the owner reference will not be set. MultipleOwnersLabel = "operator.tigera.io/multipleOwners" + + // Sidecar common names + SidecarMutatingWebhookConfigName = "tigera-sidecar-webhook-configuration" ) diff --git a/pkg/controller/applicationlayer/applicationlayer_controller.go b/pkg/controller/applicationlayer/applicationlayer_controller.go index 71c88f76ab..3929e80253 100644 --- a/pkg/controller/applicationlayer/applicationlayer_controller.go +++ b/pkg/controller/applicationlayer/applicationlayer_controller.go @@ -32,8 +32,10 @@ import ( "github.com/tigera/operator/pkg/render/applicationlayer/embed" rmeta "github.com/tigera/operator/pkg/render/common/meta" + admregv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" @@ -122,6 +124,13 @@ func add(mgr manager.Manager, c ctrlruntime.Controller) error { ) } + // Watch mutatingwebhookconfiguration responsible for sidecar injetion + err = c.WatchObject(&admregv1.MutatingWebhookConfiguration{ObjectMeta: metav1.ObjectMeta{Name: common.SidecarMutatingWebhookConfigName}}, + &handler.EnqueueRequestForObject{}) + if err != nil { + return fmt.Errorf("applicationlayer-controller failed to watch sidecar MutatingWebhookConfiguration resource: %w", err) + } + // Watch configmaps created for envoy and dikastes in calico-system namespace: maps := []string{ applicationlayer.EnvoyConfigMapName, @@ -305,6 +314,18 @@ func (r *ReconcileApplicationLayer) Reconcile(ctx context.Context, request recon return reconcile.Result{RequeueAfter: utils.StandardRetry}, nil } + err = r.client.Get(ctx, types.NamespacedName{Name: common.SidecarMutatingWebhookConfigName}, &admregv1.MutatingWebhookConfiguration{}) + if err != nil { + sidecarWebhookDisabled := operatorv1.SidecarWebhookStateDisabled + instance.Status.SidecarWebhook = &sidecarWebhookDisabled + if !apierrors.IsNotFound(err) { + return reconcile.Result{}, err + } + } else { + sidecarWebhookEnabled := operatorv1.SidecarWebhookStateEnabled + instance.Status.SidecarWebhook = &sidecarWebhookEnabled + } + // Everything is available - update the CRD status. instance.Status.State = operatorv1.TigeraStatusReady if err = r.client.Status().Update(ctx, instance); err != nil { @@ -323,6 +344,7 @@ func updateApplicationLayerWithDefaults(al *operatorv1.ApplicationLayer) { defaultWebApplicationFirewallStatusType operatorv1.WAFStatusType = operatorv1.WAFDisabled defaultApplicationLayerPolicyStatusType operatorv1.ApplicationLayerPolicyStatusType = operatorv1.ApplicationLayerPolicyDisabled defaultSidecarStatusType operatorv1.SidecarStatusType = operatorv1.SidecarDisabled + defaultSidecarWebhookStateType operatorv1.SidecarWebhookStateType = operatorv1.SidecarWebhookStateDisabled ) if al.Spec.LogCollection == nil { @@ -360,6 +382,10 @@ func updateApplicationLayerWithDefaults(al *operatorv1.ApplicationLayer) { if al.Spec.SidecarInjection == nil { al.Spec.SidecarInjection = &defaultSidecarStatusType } + + if al.Status.SidecarWebhook == nil { + al.Status.SidecarWebhook = &defaultSidecarWebhookStateType + } } // validateApplicationLayer validates ApplicationLayer diff --git a/pkg/crds/operator/operator.tigera.io_applicationlayers.yaml b/pkg/crds/operator/operator.tigera.io_applicationlayers.yaml index 1fadfe8268..8b03379f62 100644 --- a/pkg/crds/operator/operator.tigera.io_applicationlayers.yaml +++ b/pkg/crds/operator/operator.tigera.io_applicationlayers.yaml @@ -356,6 +356,13 @@ spec: - type type: object type: array + sidecarWebhook: + description: SidecarWebhook provides the state of sidecar injection + mutatinwebhookconfiguration + enum: + - Enabled + - Disabled + type: string state: description: State provides user-readable status. type: string diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index 395b53fd93..3a45f60ae4 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -33,6 +33,7 @@ import ( "github.com/tigera/api/pkg/lib/numorstring" operatorv1 "github.com/tigera/operator/api/v1" + "github.com/tigera/operator/pkg/common" "github.com/tigera/operator/pkg/components" "github.com/tigera/operator/pkg/controller/k8sapi" rcomp "github.com/tigera/operator/pkg/render/common/components" @@ -69,8 +70,6 @@ const ( MultiTenantManagedClustersAccessClusterRoleName = "tigera-managed-cluster-access" L7AdmissionControllerContainerName = "calico-l7-admission-controller" L7AdmissionControllerPort = 6443 - - SidecarMutatingWebhookConfigName = "tigera-sidecar-webhook-configuration" ) var TigeraAPIServerEntityRule = v3.EntityRule{ @@ -293,7 +292,7 @@ func (c *apiServerComponent) Objects() ([]client.Object, []client.Object) { if c.cfg.IsSidecarInjectionEnabled() { namespacedEnterpriseObjects = append(namespacedEnterpriseObjects, c.sidecarMutatingWebhookConfig()) } else { - objsToDelete = append(objsToDelete, &admregv1.MutatingWebhookConfiguration{ObjectMeta: metav1.ObjectMeta{Name: SidecarMutatingWebhookConfigName}}) + objsToDelete = append(objsToDelete, &admregv1.MutatingWebhookConfiguration{ObjectMeta: metav1.ObjectMeta{Name: common.SidecarMutatingWebhookConfigName}}) } podSecurityNamespaceLabel := PodSecurityStandard(PSSRestricted) @@ -1104,7 +1103,7 @@ func (c *apiServerComponent) sidecarMutatingWebhookConfig() *admregv1.MutatingWe Kind: "MutatingWebhookConfiguration", APIVersion: "admissionregistration.k8s.io/v1", }, - ObjectMeta: metav1.ObjectMeta{Name: SidecarMutatingWebhookConfigName}, + ObjectMeta: metav1.ObjectMeta{Name: common.SidecarMutatingWebhookConfigName}, Webhooks: []admregv1.MutatingWebhook{ { AdmissionReviewVersions: []string{"v1"}, From a02cd45661c1726033ae57346b025dc840ae5ac4 Mon Sep 17 00:00:00 2001 From: Walter Neto Date: Tue, 15 Oct 2024 23:16:35 +0100 Subject: [PATCH 2/3] Create SidecarWebhook status unit test --- .../applicationlayer_controller_test.go | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pkg/controller/applicationlayer/applicationlayer_controller_test.go b/pkg/controller/applicationlayer/applicationlayer_controller_test.go index 56af9fa981..7ecbb830e9 100644 --- a/pkg/controller/applicationlayer/applicationlayer_controller_test.go +++ b/pkg/controller/applicationlayer/applicationlayer_controller_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/gomega" "github.com/stretchr/testify/mock" + admregv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -61,6 +62,7 @@ var _ = Describe("Application layer controller tests", func() { Expect(rbacv1.SchemeBuilder.AddToScheme(scheme)).ShouldNot(HaveOccurred()) Expect(batchv1.SchemeBuilder.AddToScheme(scheme)).ShouldNot(HaveOccurred()) Expect(operatorv1.SchemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred()) + Expect(admregv1.SchemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred()) // Create a client that will have a crud interface of k8s objects. c = ctrlrfake.DefaultFakeClientBuilder(scheme).Build() ctx = context.Background() @@ -303,6 +305,66 @@ var _ = Describe("Application layer controller tests", func() { Expect(test.GetResource(c, &fc)).To(BeNil()) Expect(*fc.Spec.TPROXYMode).To(Equal(crdv1.TPROXYModeOptionDisabled)) }) + + It("should render proper SidecarWebhook status", func() { + mockStatus.On("AddDaemonsets", mock.Anything).Return() + mockStatus.On("AddDeployments", mock.Anything).Return() + mockStatus.On("IsAvailable").Return(true) + mockStatus.On("AddStatefulSets", mock.Anything).Return() + mockStatus.On("AddCronJobs", mock.Anything) + mockStatus.On("OnCRNotFound").Return() + mockStatus.On("ClearDegraded") + mockStatus.On("SetDegraded", "Waiting for LicenseKeyAPI to be ready", "").Return().Maybe() + mockStatus.On("ReadyToMonitor") + mockStatus.On("SetMetaData", mock.Anything).Return() + Expect(c.Create(ctx, installation)).NotTo(HaveOccurred()) + + By("applying the ApplicationLayer CR to the fake cluster") + enabled := operatorv1.L7LogCollectionEnabled + Expect(c.Create(ctx, &operatorv1.ApplicationLayer{ + ObjectMeta: metav1.ObjectMeta{Name: "tigera-secure"}, + Spec: operatorv1.ApplicationLayerSpec{ + LogCollection: &operatorv1.LogCollectionSpec{ + CollectLogs: &enabled, + }, + }, + })).NotTo(HaveOccurred()) + + _, err := r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + instance, err := getApplicationLayer(ctx, r.client) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(*instance.Status.SidecarWebhook).To(Equal(operatorv1.SidecarWebhookStateDisabled)) + + By("creating sidecar mutatingwebhookconfiguration") + sidecarMutatingWebhook := &admregv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: common.SidecarMutatingWebhookConfigName, + }, + } + Expect(c.Create(ctx, sidecarMutatingWebhook)).NotTo(HaveOccurred()) + + _, err = r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + instance, err = getApplicationLayer(ctx, r.client) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(*instance.Status.SidecarWebhook).To(Equal(operatorv1.SidecarWebhookStateEnabled)) + + By("deleting sidecar mutatingwebhookconfiguration") + Expect(c.Delete(ctx, sidecarMutatingWebhook)).NotTo(HaveOccurred()) + + _, err = r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + instance, err = getApplicationLayer(ctx, r.client) + Expect(err).ShouldNot(HaveOccurred()) + + Expect(*instance.Status.SidecarWebhook).To(Equal(operatorv1.SidecarWebhookStateDisabled)) + }) Context("Reconcile for Condition status", func() { generation := int64(2) BeforeEach(func() { From aa45e998accdafe113a4b67f8ee6227617849d1f Mon Sep 17 00:00:00 2001 From: Walter Neto Date: Tue, 15 Oct 2024 23:31:03 +0100 Subject: [PATCH 3/3] Run validate-gen-versions --- ...etworking.k8s.io_adminnetworkpolicies.yaml | 118 +++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/pkg/crds/enterprise/policy.networking.k8s.io_adminnetworkpolicies.yaml b/pkg/crds/enterprise/policy.networking.k8s.io_adminnetworkpolicies.yaml index 9494e478e5..174d4c1ace 100644 --- a/pkg/crds/enterprise/policy.networking.k8s.io_adminnetworkpolicies.yaml +++ b/pkg/crds/enterprise/policy.networking.k8s.io_adminnetworkpolicies.yaml @@ -4,7 +4,7 @@ metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: standard + policy.networking.k8s.io/channel: experimental creationTimestamp: null name: adminnetworkpolicies.policy.networking.k8s.io spec: @@ -115,6 +115,16 @@ spec: maxProperties: 1 minProperties: 1 properties: + namedPort: + description: |- + NamedPort selects a port on a pod(s) based on name. + + + Support: Extended + + + + type: string portNumber: description: |- Port selects a port on a pod(s) based on number. @@ -258,6 +268,97 @@ spec: type: object type: object x-kubernetes-map-type: atomic + networks: + description: |- + Networks defines a way to select peers via CIDR blocks. + This is intended for representing entities that live outside the cluster, + which can't be selected by pods, namespaces and nodes peers, but note + that cluster-internal traffic will be checked against the rule as + well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + or deny all IPv4 pod-to-pod traffic as well. If you don't want that, + add a rule that Passes all pod traffic before the Networks rule. + + + Each item in Networks should be provided in the CIDR format and should be + IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". + + + Networks can have upto 25 CIDRs specified. + + + Support: Extended + + + + items: + description: |- + CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). + This string must be validated by implementations using net.ParseCIDR + TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + maxLength: 43 + type: string + x-kubernetes-validations: + - message: CIDR must be either an IPv4 or IPv6 address. + IPv4 address embedded in IPv6 addresses are not + supported + rule: self.contains(':') != self.contains('.') + maxItems: 25 + minItems: 1 + type: array + x-kubernetes-list-type: set + nodes: + description: |- + Nodes defines a way to select a set of nodes in + the cluster. This field follows standard label selector + semantics; if present but empty, it selects all Nodes. + + + Support: Extended + + + + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic pods: description: |- Pods defines a way to select a set of pods in @@ -373,6 +474,11 @@ spec: - action - to type: object + x-kubernetes-validations: + - message: networks/nodes peer cannot be set with namedPorts since + there are no namedPorts for networks/nodes + rule: '!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) + && has(self.ports) && self.ports.exists(port, has(port.namedPort)))' maxItems: 100 type: array ingress: @@ -617,6 +723,16 @@ spec: maxProperties: 1 minProperties: 1 properties: + namedPort: + description: |- + NamedPort selects a port on a pod(s) based on name. + + + Support: Extended + + + + type: string portNumber: description: |- Port selects a port on a pod(s) based on number.