How to Add a WAF (OWASP Core Rule Set) to Envoy with ext_proc
To add a web application firewall to Envoy, run a WAF engine as an external processor and connect it through Envoy's ext_proc HTTP filter. In Elchi that engine is Shield (elchi-shield), a sidecar on each edge host that runs Coraza with the OWASP Core Rule Set (CRS) embedded in its binary. You enable the CRS in a Shield policy (coraza: {include_owasp: true}), deploy it in detect mode to see what it would block, tune out false positives, then switch the policy to block, where a match returns 403 before the request reaches the backend.
When you need this
- You want CRS protection (SQL injection, XSS, command injection, path traversal, protocol abuse) in front of services behind Envoy.
- You want to roll a WAF out without blocking legitimate traffic on day one.
- You want different WAF strictness per host or per route, for example a higher paranoia level on a payments API and no inspection on a health check.
- You need the WAF to run on the edge host itself, with traffic never leaving the box for inspection.
Steps
-
Install Shield on the edge host. The elchi-client installer installs
elchi-shieldby default, in the same run, as a hardened systemd service (--no-shieldskips it). Hosts that run Envoy without elchi-client can use the standalone Shield install. See The Bundled Shield Sidecar. -
Wire Envoy to Shield with
ext_proc. Envoy reaches Shield through the External Processor HTTP filter over a local Unix domain socket (/run/elchi-shield/extproc.sock) or loopback TCP. In Elchi, turning API Security on for a listener's HTTP connection manager adds theelchi-shieldext_proc filter at the head of the HTTP filter chain, ahead of the router. Setuse_remote_addresson the HTTP connection manager so Shield gets a client IP that cannot be forged. The full cluster and filter configuration is in Wiring Shield into Envoy. -
Write a policy with the CRS in detect mode. In Shield → Policies, create a policy for your host and enable the Coraza engine with
include_owasp: true. Coraza inspects bodies, so setinspect_request_body: trueand amax_request_body_bytescap:apiVersion: sentinel.elchi.io/v1kind: SecurityPolicymetadata:name: app-wafspec:defaults:mode: detect # evaluate and record, never blockfail_mode: fail_openinspect_request_body: truemax_request_body_bytes: 1048576domains:- hosts: ["app.example.com"]routes:- match: { path_prefix: "/" }policy:engines:coraza:include_owasp: true -
Save to deploy. Saving a policy queues a
SHIELD_DEPLOYjob that pushes the project's policy bundle to every connected edge. elchi-client validates the files with the localelchi-shield validatebinary, writes them atomically, and Shield hot-reloads them. An invalid policy never replaces the last valid one. See Deploying Policies to Edges. -
Send a test probe. A request such as
curl -i "https://app.example.com/search?q=1%27%20UNION%20SELECT%20password%20FROM%20users--"still returns the upstream's response indetectmode, and appears in Security Events with actiondetect, enginecorazaand the CRS rule that matched. -
Tune. Watch Security Events on real traffic. Put rule ids that match legitimate requests into
exclude_rule_ids, or add a narrower rule exception. Keep the paranoia level at the default (1) unless you have tuned for a higher one. -
Move to
shadow, thenblock.shadowevaluates as if blocking and records what would be blocked while still allowing the request. When that stream contains only traffic you want blocked, setmode: block. See Modes & Fail Postures.
How it works
Each HTTP transaction is one ext_proc gRPC stream between Envoy and Shield. Envoy sends request headers; Shield switches the body mode to buffered only for routes whose policy inspects bodies, so routes without body inspection carry no body streaming cost. Shield resolves the host and route to a policy, runs header-phase engines, then body checks and body-phase engines such as Coraza, and answers allow, block (403) or continue.
Coraza runs the CRS with collaborative anomaly scoring: each matching rule adds to the transaction's score, and the request is blocked when the score crosses inbound_anomaly_threshold (CRS default 5). Shield forces SecRuleEngine On and expresses monitoring through the policy mode, so detect and shadow see the full detection trail without changing traffic. The CRS version is compiled into the Shield binary; Shield reports it on /configz and in its build_info metric.
There are two failure layers. Envoy's failure_mode_allow: true on the ext_proc filter lets traffic through uninspected if the sidecar is unreachable. Inside Shield, the policy's fail_mode decides what happens when an engine errors or times out. A confirmed block always wins over an error.
Related docs
- Coraza WAF (OWASP CRS) - every field, paranoia levels, thresholds, CRS settings and rule exceptions.
- Shield: API Security Overview - the other engines a policy can run next to the WAF.
- How Shield Works - the request pipeline and atomic hot reload.
- Wiring Shield into Envoy - the ext_proc cluster and filter.
- Tutorial: Secure an API with Shield - from API Discovery to an enforced policy.
- Shield metrics, audit and health - the metric set and the ClickHouse audit sink.