WAF and CrowdSec
Audience
PLANA staff working on edge security or investigating a blocked request.
Two security filters sit on every request that reaches the Envoy Gateway: CrowdSec for IP reputation and Coraza for payload inspection. Both are configured to fail closed — if the filter itself is unreachable, the request is denied.
CrowdSec — IP reputation (ExtAuthz)
CrowdSec is the open-source intrusion-prevention system. It maintains a list of bad-actor IPs (from a community feed plus our own detections) and the Envoy bouncer rejects requests from anyone on the list.
Components
| Pod | Role |
|---|---|
crowdsec-lapi | Local API + central decision store |
crowdsec-agents (DaemonSet) | Watch logs from edge components for attack patterns |
crowdsec-bouncer | ExtAuthz target for Envoy; queries LAPI on every request |
All in the crowdsec namespace.
How it decides
For each incoming request:
- Envoy ExtAuthz filter calls
crowdsec-bouncer:8080/api/v1/decisions?ip=<src> - Bouncer queries LAPI
- LAPI returns
BANif the IP is on the list,OKotherwise - Envoy enforces — 403 on BAN
The community feed updates every few hours. Local detections (e.g. "this IP scanned 10 Odoo login attempts in 60s") update in real time.
Configuration
kind: EnvoyExtensionPolicy
spec:
extAuthz:
backendRef: crowdsec-bouncer.crowdsec.svc:8080
failOpen: false # fail closed; if bouncer is down, denyThe fail-closed posture is deliberate. After the 2026-05-13 incident where a missing fail-closed flag let CrowdSec outages silently bypass the filter, we audited every filter for this default.
What happens to legitimate users on a banned IP
Rare but real: a corporate NAT, a residential ISP, a shared VPN may end up on the CrowdSec list. Customers who can't reach my.planapulse.ai:
- Have them visit
https://api.ipify.orgto find their public IP - Check Loki for that IP:
{namespace="crowdsec-lapi"} |= "<ip>" - If banned, unban via
crowdsecCLI:kubectl exec -n crowdsec deploy/crowdsec-lapi -- cscli decisions delete --ip <ip> - Investigate why they were banned (logs at LAPI)
Coraza — WAF (OWASP CRS)
Coraza implements the OWASP ModSecurity Core Rule Set as a Wasm filter inside Envoy. It inspects request bodies, headers, and URLs for known attack patterns:
- SQL injection
- Cross-site scripting
- Path traversal
- Command injection
- Suspicious user agents
- Protocol-level abuses
Configuration
kind: EnvoyExtensionPolicy
spec:
wasm:
image: coraza-waf:owasp-crs
failOpen: falseCRS-managed rules. Severity 2+ blocks; severity 1 logs only.
Logging
Coraza writes to stdout; promtail ships to Loki. Query blocked requests:
{namespace="envoy-gateway-system"} |= "Coraza" |= "blocked"Each entry includes the rule ID that triggered, the matched pattern, and the source IP.
False-positive tuning
Some CRS rules over-trigger on legitimate Odoo / pulse-account API calls (Odoo's URL patterns include things that look like SQLi to naive regex). We maintain a small exception list:
# In coraza-config.yaml
SecRuleRemoveById 942100 942130 # SQLi over-trigger on Odoo views
SecRuleRemoveByTag "OWASP_CRS/WEB_ATTACK/EVASION"Add exceptions sparingly. Each one widens the attack surface.
What this catches in practice
Sample of blocks in a typical month:
| Pattern | Volume |
|---|---|
SQL injection probes (UNION SELECT, 1' OR '1'='1) | ~100/day |
Path traversal (../../etc/passwd) | ~50/day |
Common backdoor probes (/wp-admin/, /admin.php) | ~200/day |
| Scanner UAs (Nessus, Nikto, Acunetix) | ~30/day |
| Tor exit nodes scanning | ~20/day |
None of these reach the application. The application servers are blissfully unaware.
Where to read more
- Threat model
- Audit logging — investigating after a block
- Network policies
- Architecture → Envoy Gateway — where the filters attach