From 1c6199d0f538d900e44eaaa97bd6f20aa0c31d50 Mon Sep 17 00:00:00 2001 From: Evan Vetere Date: Thu, 9 Jul 2026 21:40:10 -0400 Subject: [PATCH 1/2] test: add e2e for TrafficProtectionPolicy Enforce serving traffic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chainsaw regression for #243. Deploys a backend, a Gateway on the WAF GatewayClass with an HTTPRoute, and a TrafficProtectionPolicy in Enforce mode, then probes the downstream Envoy over the wire and asserts a benign GET returns 200 rather than the empty/5xx reply the unescaped error-page body produces. Gateway.status.Programmed alone is insufficient — Envoy Gateway reports it from its own translation, independent of Envoy's xDS ACK — so the test asserts on an actual HTTP response. Requires the WAF data plane (extension-server + the extensionManager Envoy Gateway on the datum-downstream-gateway class); environments without it skip via the unmet Programmed assertion. Refs #243 Key changes: - add test/e2e/trafficprotectionpolicy-enforce/chainsaw-test.yaml --- .../chainsaw-test.yaml | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 test/e2e/trafficprotectionpolicy-enforce/chainsaw-test.yaml diff --git a/test/e2e/trafficprotectionpolicy-enforce/chainsaw-test.yaml b/test/e2e/trafficprotectionpolicy-enforce/chainsaw-test.yaml new file mode 100644 index 00000000..7086c30d --- /dev/null +++ b/test/e2e/trafficprotectionpolicy-enforce/chainsaw-test.yaml @@ -0,0 +1,161 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: trafficprotectionpolicy-enforce-serves-traffic +# Regression for issue #243: a TrafficProtectionPolicy in Enforce mode must not +# take the gateway offline. The branded error page NSO injects into Envoy's +# local_reply_config is a SubstitutionFormatString, so an unescaped '%' in the +# page (e.g. CSS "height: 100%") makes Envoy reject the whole listener and, with +# failOpen:false, drops the xDS update fleet-wide — every request then fails +# with an empty reply instead of the backend's 200. +# +# Precondition: the downstream (nso-infra) must have the WAF data plane wired up +# (extension-server + the extensionManager Envoy Gateway registered on the +# `datum-downstream-gateway` GatewayClass, with the Coraza filter). The stock +# `make prepare-infra-cluster` does NOT install this; this test is skipped by +# CI environments without it (the Gateway never reaches Programmed=True). +spec: + cluster: nso-infra + # EG only reconciles namespaces carrying this label. + namespaceTemplate: + metadata: + labels: + meta.datumapis.com/upstream-cluster-name: e2e + bindings: + - name: hostname + value: (join('.', [$namespace, 'e2e.test'])) + steps: + - name: Deploy a backend + try: + - apply: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: echo + spec: + replicas: 1 + selector: + matchLabels: + app: echo + template: + metadata: + labels: + app: echo + spec: + containers: + - name: echo + image: hashicorp/http-echo:1.0 + args: ["-text=hello from backend", "-listen=:8080"] + ports: + - containerPort: 8080 + - apply: + resource: + apiVersion: v1 + kind: Service + metadata: + name: echo + spec: + selector: + app: echo + ports: + - port: 80 + targetPort: 8080 + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: echo + status: + availableReplicas: 1 + + - name: Route through the WAF gateway with an Enforce policy + try: + - apply: + resource: + apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: waf-gw + spec: + gatewayClassName: datum-downstream-gateway + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: ($hostname) + allowedRoutes: + namespaces: + from: Same + - apply: + resource: + apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: echo + spec: + parentRefs: + - name: waf-gw + hostnames: + - ($hostname) + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: echo + port: 80 + - apply: + resource: + apiVersion: networking.datumapis.com/v1alpha + kind: TrafficProtectionPolicy + metadata: + name: enforce-waf + spec: + mode: Enforce + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: waf-gw + ruleSets: + - type: OWASPCoreRuleSet + owaspCoreRuleSet: {} + - assert: + resource: + apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: waf-gw + status: + (conditions[?type == 'Programmed']): + - status: "True" + + - name: Benign traffic must reach the backend (not an empty/5xx reply) + description: > + Probe the downstream Envoy via the kind hostPort (30080). Before the fix + the listener is rejected and curl returns 000 / empty reply; after the + fix a benign GET returns the backend's 200. Gateway.status.Programmed is + not sufficient on its own — EG reports Programmed from its own + translation, independent of Envoy's xDS ACK — so this asserts on the + wire. + try: + - script: + env: + - name: HOSTNAME + value: ($hostname) + content: | + set -u + for i in $(seq 1 30); do + code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 \ + -H "Host: ${HOSTNAME}" http://localhost:30080/) || code=000 + echo "attempt ${i}: HTTP ${code}" + if [ "${code}" = "200" ]; then + exit 0 + fi + sleep 3 + done + echo "benign request never returned 200 (issue #243 regression)" + exit 1 From 8a5eb185780fd8e9ce51ea54f358d2a4c6d33cbc Mon Sep 17 00:00:00 2001 From: Evan Vetere Date: Thu, 9 Jul 2026 22:20:48 -0400 Subject: [PATCH 2/2] test: fix $namespace binding scope in TPP Enforce e2e The hostname binding referenced $namespace at spec level, where it is not yet defined, so chainsaw aborted the test with "variable not defined: $namespace" before any step ran. Move the binding into the two steps that use it, where the per-test namespace is in scope. Verified against the fixed operator image on the local WAF stack: the test now runs and passes (benign GET returns the backend's 200). --- .../trafficprotectionpolicy-enforce/chainsaw-test.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/e2e/trafficprotectionpolicy-enforce/chainsaw-test.yaml b/test/e2e/trafficprotectionpolicy-enforce/chainsaw-test.yaml index 7086c30d..5d684ff7 100644 --- a/test/e2e/trafficprotectionpolicy-enforce/chainsaw-test.yaml +++ b/test/e2e/trafficprotectionpolicy-enforce/chainsaw-test.yaml @@ -22,9 +22,6 @@ spec: metadata: labels: meta.datumapis.com/upstream-cluster-name: e2e - bindings: - - name: hostname - value: (join('.', [$namespace, 'e2e.test'])) steps: - name: Deploy a backend try: @@ -72,6 +69,9 @@ spec: availableReplicas: 1 - name: Route through the WAF gateway with an Enforce policy + bindings: + - name: hostname + value: (join('.', [$namespace, 'e2e.test'])) try: - apply: resource: @@ -141,6 +141,9 @@ spec: not sufficient on its own — EG reports Programmed from its own translation, independent of Envoy's xDS ACK — so this asserts on the wire. + bindings: + - name: hostname + value: (join('.', [$namespace, 'e2e.test'])) try: - script: env: