Skip to content

fix: install downstream WAF data plane in e2e CI (#263)#268

Merged
ecv merged 4 commits into
mainfrom
fix/263-e2e-install-downstream-waf-dataplane
Jul 13, 2026
Merged

fix: install downstream WAF data plane in e2e CI (#263)#268
ecv merged 4 commits into
mainfrom
fix/263-e2e-install-downstream-waf-dataplane

Conversation

@ecv

@ecv ecv commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What this does

Turns the E2E Tests gating workflow green by installing the downstream Coraza WAF data plane on the CI infra cluster (kind-nso-infra).

The chainsaw test test/e2e/trafficprotectionpolicy-enforce asserts that Gateway waf-gw reaches Programmed=True. waf-gw requests gatewayClassName: datum-downstream-gateway, but the stock make prepare-infra-cluster installed only the vanilla Envoy Gateway (default controllerName) plus the NSO CRDs — nothing programmed the datum-downstream-gateway class, so the Gateway stayed Programmed=Unknown (status: {}) and the assert timed out on every PR.

This wires the WAF data plane into the infra-cluster setup, reusing the existing config/extension-server and config/tools/envoy-gateway-downstream manifests (previously orphaned — referenced by nothing in the Makefile/Taskfiles).

Changes

  • make downstream-waf-dataplane (new prepare-infra-cluster dependency): installs the downstream Envoy Gateway instance (config/tools/envoy-gateway-downstream, controllerName gateway.envoyproxy.io/datum-downstream-gateway), the extension server, and the datum-downstream-gateway GatewayClass + Coraza EnvoyProxy; also loads the operator image into nso-infra (new load-image-infra). Ordered after cert-manager, envoy-gateway, and downstream-crds so all required CRDs exist first.
  • config/e2e-downstream/ (new overlay): references the config/extension-server base and resolves its placeholders:
    • pki.yaml — a self-signed CA chain (selfSigned ClusterIssuer → CA Certificate → CA Issuer). The one CA issues both the extension server's TLS cert (via the cert-manager CSI driver) and Envoy Gateway's client cert; both sides trust the CA. The CA Secret is mounted directly for the client-CA bundle, so trust-manager is not required (production uses a trust-manager Bundle; this self-contained variant keeps the kind cluster dependency-light).
    • waf-gateway.yaml — the Coraza EnvoyProxy (NodePort 30080/30443 for the kind hostPort) + the datum-downstream-gateway GatewayClass.
    • extension-server-config.yaml — Coraza routeBaseDirectives and branded error page enabled (the enforce test is the extension-server: branded error page injected as Envoy SubstitutionFormatString → unescaped % rejects the listener (breaks TPP Enforce) #243 regression, so the error page must be exercised).
    • Deployment patch pins replicas: 1 (single-node infra cluster) and the ServiceMonitor is dropped (no Prometheus operator CRDs on the bare kind cluster).
  • config/tools/envoy-gateway-downstream/kustomization.yaml: resolves the extensionManager placeholders to the extension server FQDN (envoy-gateway-extension-server.datum-downstream-gateway.svc.cluster.local) and the self-signed CA Secret.

GatewayClass name reconciliation

I chose the lower-blast-radius option: provision datum-downstream-gateway in the e2e path and leave config/e2e/config.yaml (downstreamGatewayClassName: datum-downstream-gateway-e2e) untouched.

downstreamGatewayClassName drives the NSO operator's upstream→downstream Gateway replication, which the gateway and gateway-invalid-certificate tests depend on — they create the datum-downstream-gateway-e2e class inline and expect NSO to replicate onto it. The enforce test operates entirely on nso-infra (it creates the Gateway/HTTPRoute/TPP directly; the extension server reads them locally) and never flows through that config, so changing it would break the two gateway tests for no benefit. The WAF data plane installed here registers datum-downstream-gateway, which is exactly the class the enforce test hardcodes.

Validation

  • kustomize build config/e2e-downstream — builds clean; verified the mTLS wiring lands (tls-ca swapped to the CA Secret, CSI issuer-kind: Issuer / issuer-name: envoy-gateway-extension-server, client-cert issuerRef repointed to the Issuer, SERVER_CONFIG set, ServiceMonitor removed, no leftover placeholder-* refs).
  • kustomize build config/extension-server and config/e2e — still build clean.
  • make -n prepare-infra-cluster — target graph is coherent and correctly ordered (cert-manager + CSI driver → stock EG with CRDs → downstream CRDs → image load → downstream EG instance → e2e-downstream overlay).

Not validated locally (needs a CI run): I cannot run kind here, and local kustomize --enable-helm is blocked by a helm v4 / kustomize incompatibility, so the downstream EG Helm render, the mTLS handshake between Envoy Gateway and the extension server, extension-server health, and waf-gw actually reaching Programmed=True + a benign 200 on the wire are confirmed only by this PR's own E2E Tests run. The self-signed-CA Secret-mount approach diverges from production's trust-manager Bundle; if the CI run shows the CA is not resolved the way Envoy Gateway's certificateRef expects, the fallback is to add trust-manager + a Bundle and mount the resulting ConfigMap (matching production exactly).

Relationship to #248

Draft PR #248 ("Run edge e2e in CI against the real federated architecture") builds a production-fidelity Karmada-federated edge e2e — including a WAF data plane — but as a separate test-e2e-federated.yml workflow that runs alongside the current one, and it is blocked on an unmerged stack (#224#228 / #247). It does not turn the current test-e2e.yml green. This PR is the minimal fix that unblocks the presently-red gating workflow now. Once #248 lands and the single-stack test-e2e.yml is retired, make downstream-waf-dataplane and config/e2e-downstream/ can be removed.

Fixes #263. Refs #242, #248.

The E2E Tests workflow is red because the chainsaw test
trafficprotectionpolicy-enforce asserts Gateway waf-gw reaches
Programmed=True, but the test requests gatewayClassName
datum-downstream-gateway and nothing in CI programs that class. The
stock prepare-infra-cluster installs only the vanilla Envoy Gateway
and the NSO CRDs, never the Coraza WAF data plane the test needs.

Wire the WAF data plane into the infra-cluster setup so the
datum-downstream-gateway class is served end to end on kind-nso-infra.

Key changes:
- Add make target downstream-waf-dataplane (a prepare-infra-cluster
  dependency) that installs the downstream Envoy Gateway instance, the
  extension server, and the datum-downstream-gateway GatewayClass plus
  its Coraza EnvoyProxy, loading the operator image into nso-infra.
- Add config/e2e-downstream overlay: reuses the config/extension-server
  base and resolves its mTLS placeholders with a self-signed CA chain
  (cert-manager Issuer issues both server and client certs; the CA
  Secret is mounted directly, avoiding trust-manager). Drops the
  ServiceMonitor and pins replicas to one for the single-node cluster.
- Resolve the envoy-gateway-downstream extensionManager placeholders
  to the extension server FQDN and the self-signed CA Secret.
- Serve the datum-downstream-gateway GatewayClass with the downstream
  controllerName so the test's hardcoded class matches the installed
  data plane; config/e2e/config.yaml is left untouched since its
  datum-downstream-gateway-e2e value drives the separate gateway tests.

The test's stale "skipped by CI" note is corrected to reflect that
prepare-infra-cluster now installs the data plane.
@ecv ecv marked this pull request as ready for review July 13, 2026 17:45
@ecv ecv marked this pull request as draft July 13, 2026 17:58
ecv added 2 commits July 13, 2026 15:37
The trafficprotectionpolicy-enforce-serves-traffic e2e test never let waf-gw
reach Programmed=True. Two independent defects, both diverging from the
production downstream WAF wiring, kept the gateway offline.

First, the downstream Envoy Gateway watches only namespaces labelled
meta.datumapis.com/upstream-cluster-name, and its GatewayClass parametersRef
points at the EnvoyProxy in the datum-downstream-gateway namespace. That
namespace was created unlabelled, so Envoy Gateway could not see the
EnvoyProxy and left the GatewayClass unaccepted, giving every Gateway an
empty status. Production labels this namespace 0-datum-internal-anchor for
exactly this reason.

Second, the coraza-waf init container copied the WAF library from
/app/coraza-waf.so, which does not exist in coraza-waf:v1.3.0-alpha10; the
image ships it at /coraza-waf.so. The bad path made cp exit 1, the init
container CrashLoopBackOff'd, the merged Envoy pod stayed Pending, and the
Gateway reported Programmed=False with reason NoResources ("Envoy replicas
unavailable"). Production copies from /coraza-waf.so.

Key changes:
- Label the datum-downstream-gateway namespace (tools and e2e-downstream
  copies) so the downstream Envoy Gateway watches it and accepts the class.
- Copy the WAF library from /coraza-waf.so, matching production.
- Pin the downstream Envoy Gateway chart to v1.7.4, the version production
  runs for this control plane; CRDs still come from the base envoy-gateway
  install at v1.8.1.
- Set extensionManager.maxMessageSize to 256Mi, matching production and the
  extension server, so the WAF-augmented xDS response is not rejected.
Give the waf-gw Programmed assert a 3m timeout so the first cold pull of the
contrib Envoy and Coraza WAF images on the kind node cannot flake it, and add
failure diagnostics for the downstream WAF data plane.

Key changes:
- Raise the Programmed assert timeout to 3m, matching the longer timeouts the
  gateway-invalid-certificate suite already uses for this data plane.
- Add a chainsaw catch that dumps the live Gateway, GatewayClass, and
  downstream Envoy Gateway and dataplane logs before cleanup tears them down.
- Dump the downstream WAF data plane (GatewayClass, EnvoyProxy, controller and
  extension server state, certificates) from the workflow on e2e failure.
@ecv ecv force-pushed the fix/263-e2e-install-downstream-waf-dataplane branch from 06157e0 to 1722a30 Compare July 13, 2026 19:37
@ecv

ecv commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Heads-up while iterating on this: once the downstream WAF data plane actually installs and waf-gw starts programming, the enforce test stops being a pure harness check and begins exercising the real WAF Enforce data path. At that point, treat a flapping result as signal, not noise:

  • Deterministic red (every run, same Programmed=Unknown / status: {}) = still the harness gap — the datum-downstream-gateway class / EnvoyProxy / extension server isn't wired yet.
  • Flap (same commit green on some runs, red on others) = the test is likely catching a real intermittent defect — propagation/timing/race in Enforce programming (cf. the TPP Observe→Enforce propagation-lag family, #3402 / network-services-operator#254). Don't just re-run to green; capture the failing run's EnvoyProxy/extension-server/Envoy xDS state.

So: don't chase green blindly. A green that only holds sometimes is a bug the new coverage found, which is the point of the test.

Match the e2e overlay and production: coraza-waf:v1.3.0-alpha10 ships the
WAF library at /coraza-waf.so, not /app/coraza-waf.so. The stale path made
the init container's cp exit 1 and CrashLoopBackOff wherever this manifest
is applied.

This is the config/dev copy of the same defect fixed in config/e2e-downstream;
it is currently orphaned (referenced by no Makefile/Taskfile target) but is
corrected here to remove the latent trap and keep the two copies consistent.
@ecv ecv marked this pull request as ready for review July 13, 2026 19:54
@ecv ecv merged commit 49fccfe into main Jul 13, 2026
11 checks passed
@ecv ecv deleted the fix/263-e2e-install-downstream-waf-dataplane branch July 13, 2026 20:30
ecv added a commit that referenced this pull request Jul 13, 2026
An inverted-paranoia TrafficProtectionPolicy (OWASP CRS detection below
blocking) makes CRS rule 901500 fail closed and 500 every request. #252
set Accepted=False/Invalid and skipped attachment on the controller's
EnvoyPatchPolicy path, but the live edge enforces coraza through the
extension server, which builds per-route directives in a separate path
(internal/extensionserver/cache) that #252 never touched. A grandfathered
inverted policy therefore still 500'd benign traffic on the downstream
gateway.

Gate the extension server's directive emission on the same inverted-level
check the controller uses, so an inverted policy produces no directives
and the mutation layer withholds it from the data plane. A benign request
is then unaffected instead of 500'ing.

Key changes:
- add shared TrafficProtectionPolicySpec.InvertedParanoiaLevels predicate
  and route both the controller resolve-error guard and the extension
  server emission through it
- return nil directives for an inverted policy in computeCorazaDirectives
  so ApplyTPPRouteConfig skips the route
- add API and emission-layer unit tests: inverted not emitted, valid and
  equal levels emitted
- promote the neutralization e2e into the gating test/e2e suite and drop
  the now-empty test-e2e-nonblocking target

Fixes #273. Refs #252, #242, #268.
ecv added a commit that referenced this pull request Jul 13, 2026
An inverted-paranoia TrafficProtectionPolicy (OWASP CRS detection below
blocking) makes CRS rule 901500 fail closed and 500 every request. #252
set Accepted=False/Invalid and skipped attachment on the controller's
EnvoyPatchPolicy path, but the live edge enforces coraza through the
extension server, which builds per-route directives in a separate path
(internal/extensionserver/cache) that #252 never touched. A grandfathered
inverted policy therefore still 500'd benign traffic on the downstream
gateway.

Gate the extension server's directive emission on the same inverted-level
check the controller uses, so an inverted policy produces no directives
and the mutation layer withholds it from the data plane. A benign request
is then unaffected instead of 500'ing.

Key changes:
- add shared TrafficProtectionPolicySpec.InvertedParanoiaLevels predicate
  and route both the controller resolve-error guard and the extension
  server emission through it
- return nil directives for an inverted policy in computeCorazaDirectives
  so ApplyTPPRouteConfig skips the route
- add API and emission-layer unit tests: inverted not emitted, valid and
  equal levels emitted
- promote the neutralization e2e into the gating test/e2e suite and drop
  the now-empty test-e2e-nonblocking target

Fixes #273. Refs #252, #242, #268.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E2E gating test red: turn trafficprotectionpolicy-enforce-serves-traffic green

2 participants