Scenario 03 · WAF for regulated apps
A public storefront handles cardholder data, so PCI-DSS 6.4.1 mandates a web application firewall in front of it. The team doesn't want to rack an appliance, run yet another sidecar, or pay a cloud-WAF vendor per million requests. They need the OWASP Top 10 blocked at the edge — auditable, in the request path, with a metric to prove it's on.
Architecture
The WAF runs as a WebAssembly module inside the proxy — no extra hop. A bug in a rule is contained by the wasmtime sandbox; it cannot corrupt the data plane.
The setup
A WafPolicy points at the bundled Coraza module and loads the OWASP Core Rule Set in
order. Request-body inspection is opt-in (it costs latency), and detectionOnly lets you
roll out in log-only mode first:
apiVersion: zenoingress.io/v1alpha1
kind: WafPolicy
metadata:
name: storefront-waf
namespace: default
spec:
# Coraza proxy-wasm module shipped inside the proxy image
modulePath: /waf/coraza.wasm
rules: # loaded in order
- "Include @recommended-conf"
- "Include @crs-setup-conf"
- "Include @owasp_crs/*.conf"
- "SecRuleEngine On"
inspectRequestBody: true
maxRequestBodyBytes: 131072 # 128 KiB cap
detectionOnly: false # true = log only (rollout)
Attach it to the storefront route with an ExtensionRef filter — the WAF now runs before
anything is forwarded:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: storefront
namespace: default
spec:
parentRefs:
- name: zeno-gateway
hostnames:
- shop.example.com
rules:
- matches:
- path: { type: PathPrefix, value: / }
filters:
- type: ExtensionRef
extensionRef:
group: zenoingress.io
kind: WafPolicy
name: storefront-waf
backendRefs:
- name: storefront
port: 80
What happens
inspectRequestBody: true, the body up to 128 KiB.
942100; the request is rejected with
403 and zenoingress_waf_blocked_total{rule_id="942100"} increments.
blocked
GET /products/42 matches no rule, so it's forwarded untouched to the storefront.
200 · forwarded
detectionOnly: true, watch the metrics, silence
false positives with SecRuleRemoveById, then flip to enforce.
Outcome
A real Coraza + OWASP CRS engine runs inside the ingress in a memory-safe wasm sandbox — the auditor gets a WAF in the request path with a metric to prove it, and finance never sees a per-request WAF bill.
Run this yourself
Apply the WafPolicy and route, then send a textbook SQL-injection probe and a clean
request, and read the counter off the metrics endpoint:
$ # SQL injection probe
$ curl -s -o /dev/null -w "%{http_code}\n" \
"https://shop.example.com/items?id=1'%20OR%20'1'='1"
403
$ # legitimate request
$ curl -s -o /dev/null -w "%{http_code}\n" https://shop.example.com/products/42
200
$ # the auditor's proof: blocks counted by rule id
$ curl -s http://127.0.0.1:9090/metrics | grep waf_blocked_total
zenoingress_waf_blocked_total{rule_id="942100"} 1
Coraza + OWASP CRS, in-process. Free for one production cluster.
Deploy Free