From ae5f3f018664601e9081f2f5bbdaadb80e239180 Mon Sep 17 00:00:00 2001 From: Seth Malaki Date: Tue, 19 May 2026 14:38:12 +0100 Subject: [PATCH] PMREQ-384: deprecate waf-http-filter ext_proc sidecar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CRD-driven Coraza WASM data plane (PMREQ-384 v1) replaces the legacy waf-http-filter ext_proc sidecar entirely. Per design#25 walkthrough 2026-05-12 (Phil sign-off on AI-4/AI-5), drop the sidecar render path from the gateway-api operator render. Removed: - wafHTTPFilter init container injection on every envoy-proxy pod - /var/run/waf-http-filter socket volume + mounts on the envoy container - /var/log/calico hostPath volume (was only consumed by the sidecar) - wafHTTPFilterImage field + ResolveImages plumbing for the calico CombinedCalicoImage component reference - tokenreviews verb on the cluster-scoped ClusterRole (per-request license enforcement moved to the kube-controllers reconciler) Kept (Walter's #4690 reuses them for L7 log collector enrichment): - waf-http-filter ServiceAccount (per Gateway namespace) — proxy pod identity for namespaced Gateway-API reads - waf-http-filter-cluster-scoped ClusterRole (licensekeys verb only) - waf-http-filter-gateway-resources ClusterRole + per-namespace RoleBindings - gatewayNamespacesCRB The L7 log collector init container, /access_logs + felix-sync volumes, serviceAccountName patch on the envoy deployment, and the access-log telemetry config are unaffected. Logs-to-felix replacement for WAF-specific events is tracked separately in tigera/gateway-extensions-controller/docs/planning/briefs/2026-05-12-sidecar-deprecation-brief.md --- pkg/render/gatewayapi/gateway_api.go | 138 +++------------------- pkg/render/gatewayapi/gateway_api_test.go | 95 +++++---------- 2 files changed, 46 insertions(+), 187 deletions(-) diff --git a/pkg/render/gatewayapi/gateway_api.go b/pkg/render/gatewayapi/gateway_api.go index 53e313cbbe..75b0218d6a 100644 --- a/pkg/render/gatewayapi/gateway_api.go +++ b/pkg/render/gatewayapi/gateway_api.go @@ -419,7 +419,6 @@ type gatewayAPIImplementationComponent struct { envoyGatewayImage string envoyProxyImage string envoyRatelimitImage string - wafHTTPFilterImage string L7LogCollectorImage string // Pre-rendered helm chart resources. @@ -453,10 +452,6 @@ func (pr *gatewayAPIImplementationComponent) ResolveImages(is *operatorv1.ImageS if err != nil { return err } - pr.wafHTTPFilterImage, err = components.GetReference(components.CombinedCalicoImage(pr.cfg.Installation), reg, path, prefix, is) - if err != nil { - return err - } pr.L7LogCollectorImage, err = components.GetReference(components.ComponentGatewayL7Collector, reg, path, prefix, is) if err != nil { return err @@ -927,42 +922,14 @@ func (pr *gatewayAPIImplementationComponent) envoyProxyConfig(className, ns stri } applyEnvoyProxyServiceOverrides(envoyProxy, classSpec.GatewayService) - // Setup WAF HTTP Filter and l7 Log collector on Enterprise. + // Inject the l7-log-collector init container on Enterprise so envoy access + // logs ship to felix. PMREQ-384 (2026-05-12 PM sign-off) deprecated the + // waf-http-filter ext_proc sidecar that used to live alongside it; the + // CRD-driven WASM data plane is the WAF enforcement path now. if pr.cfg.Installation.Variant.IsEnterprise() { - // The WAF HTTP filter is not supported when the envoy proxy is deployed as a DaemonSet - // as there is no support for init containers in a DaemonSet. + // L7 log collector relies on init-container support, so DaemonSet + // providers are skipped (no init containers on a DaemonSet). if envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment != nil { - // Add or update the Init Container to the deployment - wafHTTPFilter := corev1.Container{ - Name: wafFilterName, - Image: pr.wafHTTPFilterImage, - Command: []string{components.CalicoBinaryPath, "component", "waf-http-filter"}, - Args: []string{ - "--logFileDirectory", - "/var/log/calico/waf", - "--logFileName", - "waf.log", - "--socketPath", - "/var/run/waf-http-filter/extproc.sock", - }, - RestartPolicy: ptr.To(corev1.ContainerRestartPolicyAlways), - VolumeMounts: []corev1.VolumeMount{ - { - Name: wafFilterName, - MountPath: "/var/run/waf-http-filter", - }, - { - Name: "var-log-calico", - MountPath: "/var/log/calico", - }, - }, - Env: []corev1.EnvVar{ - GatewayNameEnvVar, - GatewayNamespaceEnvVar, - }, - SecurityContext: securitycontext.NewRootContext(true), - } - // need to make changes to the envoy container to mount the socket l7LogCollector := corev1.Container{ Name: "l7-log-collector", Image: pr.L7LogCollectorImage, @@ -997,16 +964,8 @@ func (pr *gatewayAPIImplementationComponent) envoyProxyConfig(className, ns stri SecurityContext: securitycontext.NewRootContext(true), } - hasWAFHTTPFilter := false hasL7LogCollector := false for i, initContainer := range envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.InitContainers { - if initContainer.Name == wafHTTPFilter.Name { - hasWAFHTTPFilter = true - // Handle update - if initContainer.Image != wafHTTPFilter.Image { - envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.InitContainers[i] = wafHTTPFilter - } - } if initContainer.Name == l7LogCollector.Name { hasL7LogCollector = true // Handle update @@ -1019,68 +978,30 @@ func (pr *gatewayAPIImplementationComponent) envoyProxyConfig(className, ns stri } } } - if !hasWAFHTTPFilter { - envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.InitContainers = append(envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.InitContainers, wafHTTPFilter) - } - if !hasL7LogCollector { envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.InitContainers = append(envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.InitContainers, l7LogCollector) } - accessLogsName := "access-logs" - // Add or update Container volume mount - wafSocketVolumeMount := corev1.VolumeMount{ - Name: wafFilterName, - MountPath: "/var/run/waf-http-filter", - } - + const accessLogsName = "access-logs" l7SocketVolumeMount := corev1.VolumeMount{ Name: accessLogsName, MountPath: "/access_logs", } - hasWAFFilterSocketVolumeMount := false hasAccessLogsVolumeMount := false - for i, volumeMount := range envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Container.VolumeMounts { - switch volumeMount.Name { - case wafSocketVolumeMount.Name: - hasWAFFilterSocketVolumeMount = true - if volumeMount.MountPath != wafSocketVolumeMount.MountPath { - envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Container.VolumeMounts[i] = wafSocketVolumeMount - } - case l7SocketVolumeMount.Name: + if volumeMount.Name == l7SocketVolumeMount.Name { hasAccessLogsVolumeMount = true if volumeMount.MountPath != l7SocketVolumeMount.MountPath { envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Container.VolumeMounts[i] = l7SocketVolumeMount } - } } - if !hasWAFFilterSocketVolumeMount { - envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Container.VolumeMounts = append(envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Container.VolumeMounts, wafSocketVolumeMount) - } - if !hasAccessLogsVolumeMount { envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Container.VolumeMounts = append(envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Container.VolumeMounts, l7SocketVolumeMount) } - // Add or update Pod volumes - logsVolume := corev1.Volume{ - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/var/log/calico", - Type: ptr.To(corev1.HostPathDirectoryOrCreate), - }, - }, - Name: "var-log-calico", - } - WAFHttpFilterSocketVolume := corev1.Volume{ - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - Name: wafFilterName, - } + // Add or update Pod volumes. AccessLogsVolume := []corev1.Volume{ { VolumeSource: corev1.VolumeSource{ @@ -1097,23 +1018,8 @@ func (pr *gatewayAPIImplementationComponent) envoyProxyConfig(className, ns stri Name: "felix-sync", }, } - hasLogsVolume := false - hasSocketVolume := false hasAccessLogsVolume := false for i, volume := range envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Pod.Volumes { - if volume.Name == logsVolume.Name { - hasLogsVolume = true - // Handle update - if volume.HostPath.Path != logsVolume.HostPath.Path || volume.HostPath.Type != logsVolume.HostPath.Type { - envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Pod.Volumes[i] = logsVolume - } - } - if volume.Name == WAFHttpFilterSocketVolume.Name { - hasSocketVolume = true - if volume.EmptyDir != WAFHttpFilterSocketVolume.EmptyDir { - envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Pod.Volumes[i] = WAFHttpFilterSocketVolume - } - } for _, acVolume := range AccessLogsVolume { if volume.Name == acVolume.Name { hasAccessLogsVolume = true @@ -1122,20 +1028,16 @@ func (pr *gatewayAPIImplementationComponent) envoyProxyConfig(className, ns stri } } } - - } - if !hasLogsVolume { - envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Pod.Volumes = append(envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Pod.Volumes, logsVolume) - } - if !hasSocketVolume { - envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Pod.Volumes = append(envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Pod.Volumes, WAFHttpFilterSocketVolume) } if !hasAccessLogsVolume { envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Pod.Volumes = append(envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Pod.Volumes, AccessLogsVolume...) } - // Configure service account for WAF HTTP Filter license client - // Use EnvoyProxy patch mechanism to set serviceAccountName and automountServiceAccountToken + // Pin the proxy pod's identity to the per-namespace SA created in + // Objects() so l7-log-collector can read Gateway-API resources via + // the namespaced RoleBinding. Token-based license enforcement + // (previously done by the waf-http-filter sidecar) moved to the + // reconciler before EEP generation. serviceAccountPatch := map[string]interface{}{ "spec": map[string]interface{}{ "template": map[string]interface{}{ @@ -1147,7 +1049,6 @@ func (pr *gatewayAPIImplementationComponent) envoyProxyConfig(className, ns stri }, } - // Convert patch to JSON patchBytes, err := json.Marshal(serviceAccountPatch) if err == nil { if envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Patch == nil { @@ -1283,8 +1184,10 @@ const ( wafFilterGatewayResourcesRoleName = wafFilterName + "-gateway-resources" ) -// wafHttpFilterClusterScopedRole creates the ClusterRole granting access to cluster-scoped -// resources (license keys, token reviews) needed by every WAF HTTP Filter / L7 Log Collector. +// wafHttpFilterClusterScopedRole creates the ClusterRole granting access to +// cluster-scoped resources needed by the L7 Log Collector. The tokenreviews +// rule was dropped when the waf-http-filter sidecar was deprecated; license +// enforcement now happens at the reconciler before EEP generation. func (pr *gatewayAPIImplementationComponent) wafHttpFilterClusterScopedRole() *rbacv1.ClusterRole { return &rbacv1.ClusterRole{ TypeMeta: metav1.TypeMeta{Kind: "ClusterRole", APIVersion: "rbac.authorization.k8s.io/v1"}, @@ -1297,11 +1200,6 @@ func (pr *gatewayAPIImplementationComponent) wafHttpFilterClusterScopedRole() *r Resources: []string{"licensekeys"}, Verbs: []string{"get", "watch"}, }, - { - APIGroups: []string{"authentication.k8s.io"}, - Resources: []string{"tokenreviews"}, - Verbs: []string{"create"}, - }, }, } } diff --git a/pkg/render/gatewayapi/gateway_api_test.go b/pkg/render/gatewayapi/gateway_api_test.go index cde09a833c..774240dfd3 100644 --- a/pkg/render/gatewayapi/gateway_api_test.go +++ b/pkg/render/gatewayapi/gateway_api_test.go @@ -1031,7 +1031,7 @@ value: Expect(proxyMountPaths).To(ContainElement("/etc/pki/tls/certs")) }) - It("should not deploy waf-http-filter or l7-log-collector for open-source", func() { + It("should not deploy l7-log-collector for open-source", func() { installation := &operatorv1.InstallationSpec{ Variant: operatorv1.Calico, } @@ -1058,7 +1058,7 @@ value: Expect(envoyDeployment.Container.VolumeMounts).To(BeNil()) }) - It("should deploy waf-http-filter for Enterprise", func() { + It("should deploy l7-log-collector for Enterprise", func() { installation := &operatorv1.InstallationSpec{ Variant: operatorv1.CalicoEnterprise, } @@ -1082,31 +1082,20 @@ value: envoyDeployment := proxy.Spec.Provider.Kubernetes.EnvoyDeployment Expect(envoyDeployment).ToNot(BeNil()) + // waf-http-filter sidecar deprecated 2026-05-12 (PMREQ-384). Only l7-log-collector + // remains as the init container; its volumes feed envoy access logs to felix. Expect(envoyDeployment.Pod).ToNot(BeNil()) - Expect(envoyDeployment.Pod.Volumes).To(HaveLen(4)) - Expect(envoyDeployment.Pod.Volumes[0].Name).To(Equal("var-log-calico")) - Expect(envoyDeployment.Pod.Volumes[0].HostPath.Path).To(Equal("/var/log/calico")) - Expect(envoyDeployment.Pod.Volumes[1].Name).To(Equal("waf-http-filter")) - Expect(envoyDeployment.Pod.Volumes[1].EmptyDir).ToNot(BeNil()) + Expect(envoyDeployment.Pod.Volumes).To(HaveLen(2)) + Expect(envoyDeployment.Pod.Volumes[0].Name).To(Equal("access-logs")) + Expect(envoyDeployment.Pod.Volumes[0].EmptyDir).ToNot(BeNil()) + Expect(envoyDeployment.Pod.Volumes[1].Name).To(Equal("felix-sync")) + Expect(envoyDeployment.Pod.Volumes[1].CSI.Driver).To(Equal("csi.tigera.io")) - Expect(envoyDeployment.InitContainers[0].Name).To(Equal("waf-http-filter")) + Expect(envoyDeployment.InitContainers).To(HaveLen(1)) + Expect(envoyDeployment.InitContainers[0].Name).To(Equal("l7-log-collector")) Expect(*envoyDeployment.InitContainers[0].RestartPolicy).To(Equal(corev1.ContainerRestartPolicyAlways)) Expect(envoyDeployment.InitContainers[0].VolumeMounts).To(HaveLen(2)) Expect(envoyDeployment.InitContainers[0].VolumeMounts).To(ContainElements([]corev1.VolumeMount{ - { - Name: "waf-http-filter", - MountPath: "/var/run/waf-http-filter", - }, - { - Name: "var-log-calico", - MountPath: "/var/log/calico", - }, - })) - - Expect(envoyDeployment.InitContainers[1].Name).To(Equal("l7-log-collector")) - Expect(*envoyDeployment.InitContainers[1].RestartPolicy).To(Equal(corev1.ContainerRestartPolicyAlways)) - Expect(envoyDeployment.InitContainers[1].VolumeMounts).To(HaveLen(2)) - Expect(envoyDeployment.InitContainers[1].VolumeMounts).To(ContainElements([]corev1.VolumeMount{ { Name: "access-logs", MountPath: "/access_logs", @@ -1117,15 +1106,8 @@ value: }, })) - // logger gateway name and namespace are set from the k8s downward api pod metadata. - Expect(envoyDeployment.InitContainers[0].Env).To(ContainElements(GatewayNameEnvVar, GatewayNamespaceEnvVar)) - Expect(envoyDeployment.Container).ToNot(BeNil()) - Expect(envoyDeployment.Container.VolumeMounts).To(HaveLen(2)) - Expect(envoyDeployment.Container.VolumeMounts).To(ContainElement(corev1.VolumeMount{ - Name: "waf-http-filter", - MountPath: "/var/run/waf-http-filter", - })) + Expect(envoyDeployment.Container.VolumeMounts).To(HaveLen(1)) Expect(envoyDeployment.Container.VolumeMounts).To(ContainElement(corev1.VolumeMount{ Name: "access-logs", MountPath: "/access_logs", @@ -1134,7 +1116,7 @@ value: Expect(proxy.Spec.Telemetry.AccessLog.Settings).To(Equal(AccessLogSettings)) }) - It("should deploy waf-http-filter for Enterprise when using a custom proxy", func() { + It("should deploy l7-log-collector for Enterprise when using a custom proxy", func() { installation := &operatorv1.InstallationSpec{ Variant: operatorv1.CalicoEnterprise, } @@ -1222,26 +1204,15 @@ value: envoyDeployment := proxy.Spec.Provider.Kubernetes.EnvoyDeployment Expect(envoyDeployment).ToNot(BeNil()) - Expect(envoyDeployment.InitContainers).To(HaveLen(3)) + // waf-http-filter sidecar deprecated 2026-05-12 (PMREQ-384). User-provided + // init containers remain alongside the l7-log-collector we inject. + Expect(envoyDeployment.InitContainers).To(HaveLen(2)) Expect(envoyDeployment.InitContainers[0].Name).To(Equal("some-other-sidecar")) - Expect(envoyDeployment.InitContainers[1].Name).To(Equal("waf-http-filter")) + + Expect(envoyDeployment.InitContainers[1].Name).To(Equal("l7-log-collector")) Expect(*envoyDeployment.InitContainers[1].RestartPolicy).To(Equal(corev1.ContainerRestartPolicyAlways)) Expect(envoyDeployment.InitContainers[1].VolumeMounts).To(HaveLen(2)) Expect(envoyDeployment.InitContainers[1].VolumeMounts).To(ContainElements([]corev1.VolumeMount{ - { - Name: "waf-http-filter", - MountPath: "/var/run/waf-http-filter", - }, - { - Name: "var-log-calico", - MountPath: "/var/log/calico", - }, - })) - - Expect(envoyDeployment.InitContainers[2].Name).To(Equal("l7-log-collector")) - Expect(*envoyDeployment.InitContainers[2].RestartPolicy).To(Equal(corev1.ContainerRestartPolicyAlways)) - Expect(envoyDeployment.InitContainers[2].VolumeMounts).To(HaveLen(2)) - Expect(envoyDeployment.InitContainers[2].VolumeMounts).To(ContainElements([]corev1.VolumeMount{ { Name: "access-logs", MountPath: "/access_logs", @@ -1257,9 +1228,6 @@ value: corev1.VolumeMount{ Name: "some-other-volume", MountPath: "/test", - }, corev1.VolumeMount{ - Name: "waf-http-filter", - MountPath: "/var/run/waf-http-filter", }, corev1.VolumeMount{ Name: "access-logs", MountPath: "/access_logs", @@ -1267,17 +1235,13 @@ value: )) Expect(envoyDeployment.Pod).ToNot(BeNil()) - Expect(envoyDeployment.Pod.Volumes).To(HaveLen(5)) + Expect(envoyDeployment.Pod.Volumes).To(HaveLen(3)) Expect(envoyDeployment.Pod.Volumes[0].Name).To(Equal("some-other-volume")) Expect(envoyDeployment.Pod.Volumes[0].EmptyDir).ToNot(BeNil()) - Expect(envoyDeployment.Pod.Volumes[1].Name).To(Equal("var-log-calico")) - Expect(envoyDeployment.Pod.Volumes[1].HostPath.Path).To(Equal("/var/log/calico")) - Expect(envoyDeployment.Pod.Volumes[2].Name).To(Equal("waf-http-filter")) - Expect(envoyDeployment.Pod.Volumes[2].EmptyDir).ToNot(BeNil()) - Expect(envoyDeployment.Pod.Volumes[3].Name).To(Equal("access-logs")) - Expect(envoyDeployment.Pod.Volumes[3].EmptyDir).ToNot(BeNil()) - Expect(envoyDeployment.Pod.Volumes[4].Name).To(Equal("felix-sync")) - Expect(envoyDeployment.Pod.Volumes[4].CSI.Driver).To(Equal("csi.tigera.io")) + Expect(envoyDeployment.Pod.Volumes[1].Name).To(Equal("access-logs")) + Expect(envoyDeployment.Pod.Volumes[1].EmptyDir).ToNot(BeNil()) + Expect(envoyDeployment.Pod.Volumes[2].Name).To(Equal("felix-sync")) + Expect(envoyDeployment.Pod.Volumes[2].CSI.Driver).To(Equal("csi.tigera.io")) Expect(proxy.Spec.Telemetry.AccessLog.Settings).To(Equal(AccessLogSettings)) }) @@ -1304,7 +1268,7 @@ value: envoyDeployment := proxy.Spec.Provider.Kubernetes.EnvoyDeployment Expect(envoyDeployment).ToNot(BeNil()) - Expect(envoyDeployment.InitContainers).To(HaveLen(2)) + Expect(envoyDeployment.InitContainers).To(HaveLen(1)) // Find the l7-log-collector init container var l7LogCollector *corev1.Container @@ -1500,20 +1464,17 @@ value: objsToCreate, _ := gatewayComp.Objects() - // Verify cluster-scoped ClusterRole exists with license key + token review rules. + // Verify cluster-scoped ClusterRole exists with the licensekeys rule. The + // tokenreviews rule was dropped when the waf-http-filter sidecar was + // deprecated; license enforcement moved to the reconciler. csRole, err := rtest.GetResourceOfType[*rbacv1.ClusterRole](objsToCreate, "waf-http-filter-cluster-scoped", "") Expect(err).NotTo(HaveOccurred()) - Expect(csRole.Rules).To(HaveLen(2)) + Expect(csRole.Rules).To(HaveLen(1)) Expect(csRole.Rules).To(ContainElement(rbacv1.PolicyRule{ APIGroups: []string{"crd.projectcalico.org", "projectcalico.org"}, Resources: []string{"licensekeys"}, Verbs: []string{"get", "watch"}, })) - Expect(csRole.Rules).To(ContainElement(rbacv1.PolicyRule{ - APIGroups: []string{"authentication.k8s.io"}, - Resources: []string{"tokenreviews"}, - Verbs: []string{"create"}, - })) // Verify gateway-resources ClusterRole exists with route rules only. grRole, err := rtest.GetResourceOfType[*rbacv1.ClusterRole](objsToCreate, "waf-http-filter-gateway-resources", "")