Envoy Gateway
Protect traffic with kubeWAF and Envoy Gateway
Protect Gateway API traffic when your cluster runs Envoy Gateway. kubeWAF
does not create EnvoyExtensionPolicy anymore. Instead it:
- Publishes modsecurity-proxy-wasm config over gRPC ECDS
- Injects the filter slot via the Envoy Gateway Extension Server
For the full multi-provider model see Data plane (ECDS).
How it works
Prerequisites
- Envoy Gateway installed (e.g. GatewayClass
eg) - Gateway API CRDs
- kubeWAF operator with dataplane ports (Helm chart defaults)
- Wasm binary available to the operator (
dataplane.modsecurityWasmSourceURL/modsecurityWasmFile, or mounted under/wasm/) - Envoy Gateway extensionManager pointing at kubeWAF (below)
Configure Envoy Gateway Extension Server
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyGateway
provider:
type: Kubernetes
gateway:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
extensionManager:
policyResources:
- group: waf.kubewaf.io
version: v1beta1
kind: WAF
hooks:
xdsTranslator:
post:
- HTTPListener
- Translation
service:
fqdn:
# Match your Helm release Service name
hostname: kubewaf-ecds.kubewaf-system.svc.cluster.local
port: 5005Apply to the Envoy Gateway config (often a ConfigMap in envoy-gateway-system) and restart the controller.
Also grant the Envoy Gateway ServiceAccount permission to list/watch wafs (and optionally patch status):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kubewaf-envoy-gateway-waf-reader
rules:
- apiGroups: [waf.kubewaf.io]
resources: [wafs, wafs/status]
verbs: [get, list, watch, update, patch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubewaf-envoy-gateway-waf-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubewaf-envoy-gateway-waf-reader
subjects:
- kind: ServiceAccount
name: envoy-gateway
namespace: envoy-gateway-systemBootstrap: static kubewaf_ecds cluster (required)
Envoy rejects ECDS filter stubs unless the gRPC cluster named in
ApiConfigSource is a bootstrap static (non-EDS) cluster. Adding
kubewaf_ecds only via CDS (PostTranslateModify) produces:
Error adding/updating listener(s) ...:
ApiConfigSource must have a statically defined non-EDS cluster: 'kubewaf_ecds'Patch the EnvoyProxy used by your GatewayClass (JSONPatch appends to the
default bootstrap):
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: envoy-proxy-config
namespace: envoy-gateway-system
spec:
bootstrap:
type: JSONPatch
jsonPatches:
- op: add
path: /static_resources/clusters/-
value:
name: kubewaf_ecds
type: STRICT_DNS
connect_timeout: 2s
lb_policy: ROUND_ROBIN
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
load_assignment:
cluster_name: kubewaf_ecds
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: kubewaf-ecds.kubewaf-system.svc.cluster.local
port_value: 18001Hostname/port must match the kubeWAF ECDS Service (kubewaf-ecds port 18001).
Restart Envoy proxy pods after changing bootstrap.
Basic example
Protect an entire Gateway:
apiVersion: waf.kubewaf.io/v1beta1
kind: WAF
metadata:
name: shop-waf
namespace: shop
spec:
engine: ModSecurity
provider:
type: EnvoyGateway # default if omitted (Auto → EnvoyGateway)
parentRefs:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: external-gateway
ruleRefs:
- kind: RuleSet
name: shop-rules
namespace: shop
crsEnable: true
logLevel: 3You may also target HTTPRoute (same namespace). Prefer Gateway targets so
the Extension Server can match listeners reliably.
CRS + custom rules
spec:
crsEnable: true
crs:
paranoiaLevel: 2
inboundAnomalyThreshold: 10
removeById: [942100]
ruleRefs:
- kind: RuleSet
name: baseline
namespace: platform
- kind: RuleSet
name: shop-specific
namespace: shopDirective ordering is enforced by the operator (setup → CRS include → exclusions → user rules).
Wasm image vs HTTP binary
| Field | Role |
|---|---|
engine: ModSecurity | Product engine — modsecurity-proxy-wasm |
wasmHTTP | Override URL Envoy uses to download the .wasm binary |
wasmImage | OCI reference (documentation / future); not used alone for ECDS fetch |
Operator modsecurityWasmSourceURL / modsecurityWasmFile | Load binary into the operator; default fetch URL points at operator :18002 |
Observing status
kubectl get waf shop-waf -n shop -o yamlExpect:
status.provider: EnvoyGatewaystatus.slotKind: ExtensionServerstatus.ecdsResourceName: kubewaf/shop/shop-wafReady=True,ReferencesResolved=True
Debugging
-
Rules not applied
kubectl describe waf …→ conditions- Envoy Gateway logs: extension server errors
- Envoy admin: filter chain contains
kubewaf/…
-
Wasm load failures
- Curl the operator wasm endpoint from a debug pod:
http://kubewaf-ecds.kubewaf-system.svc:18002/wasm/modsecurity-proxy-wasm.wasm - Check
X-Checksum-Sha256matchesstatus/ ECDS
- Curl the operator wasm endpoint from a debug pod:
-
403 on every request
- Missing CRS init / thresholds — enable
crsEnableor set thresholds viaspec.crs
- Missing CRS init / thresholds — enable
-
Extension Server not called
- Confirm
extensionManagerhostname/port - NetworkPolicy must allow EG → operator:5005
- Confirm
Common patterns
Protect everything on a Gateway
One WAF targeting the Gateway — all attached HTTPRoutes inherit the filter.
Per-route policies
Multiple WAF objects with more specific parentRefs (HTTPRoute). Extension
Server injects all matching configs for the listener.
Staging vs production
Different RuleSets (staging-strict vs production-balanced) referenced by
different WAF objects.
Limitations
- Full HTTP support via Gateway API; TCP/TLS depends on EG Wasm capabilities
- Requires EG Extension Server privilege (platform-admin setup)
- Old
EnvoyExtensionPolicyobjects from prior kubeWAF versions are unused — delete them