WAF operations¶
Day-to-day use of the WAF control plane GUI. Log in with your single sign-on account; you see only the apps your team owns (global admins see everything).
Instant changes vs. deployed changes¶
| Change | How it goes live | Rollback reverts it? |
|---|---|---|
| IP ban / allow list entries | Instantly, via the Runtime API | No: change it back in the GUI |
| WAF mode (detect/block), SPOA fail mode | Instantly | No |
| Geo policy, ASN policy | Instantly (toggles) or within about a minute (app editor Save) | No |
| Bot actions (spoofed claims, AI categories, unverified) | Within about a minute | No |
| Apps, hostnames, origins, headers, rule-set paranoia/threshold, exclusions, rate limits | Preview → Apply (a deploy) | Yes |
Generated robots.txt, or an AI category change while robots.txt is on |
Preview → Apply | Yes |
Instant changes are written to the database first, then pushed to every node. The control plane re-syncs every node to the database every minute, so a node that missed a change (restart, unreachable) catches up by itself. The GUI shows such a change as pending until it does.
Deploying¶
- Make the change in the GUI and save. The app shows Unpublished changes.
- Deploys → Preview. This runs Ansible in check/diff mode against the nodes and shows what would change. Nothing on the nodes is modified.
- Open the successful preview and click Apply. Nodes are updated one at a time; each new config
is checked with
haproxy -cand a post-deploy health check. A failure restores the previous config and stops before the next node. - Rollback to any earlier deployed config version from the Deploys page.
Deploy logs are shown in the GUI and in the CI pipeline.
Adding an app¶
- Apps → New app: name (immutable, used as the service label), hostnames, origin address, port
and TLS mode (
plain, orverifywith an optional SNI name). - Start in WAF mode detect, so attacks are logged but not blocked, and watch the logs for false positives for a few days. Add rule exclusions for them, then switch to block.
- Request a certificate (below), then Preview and Apply.
- Point the app's DNS at the WAF's public address.
Origins must only accept traffic from the WAF: firewall them so the WAF nodes are the only sources allowed to reach the origin's address and port.
Certificates¶
Certificates → Issue. The control plane obtains a Let's Encrypt certificate using either:
- HTTP-01: answered by HAProxy on port 80 (port 80 must reach the WAF); or
- DNS-01: via the DNS provider's API, for names that can't be validated over HTTP.
Issued certificates are stored encrypted in the database and loaded into HAProxy live, with no deploy needed. Renewal is automatic, 30 days before expiry. Manual upload is also supported.
Blocking and allowing IPs¶
IP lists: add a CIDR as ban or allow, globally (admin only) or for one app, with an optional expiry and a reason. An allowlisted source bypasses every WAF deny decision (geo/ASN, CrowdSec, bans, bots, rate limit and the OWASP rules) but is still subject to the body-size limit.
Bot detection¶
- Each app's Bots section sets the action for spoofed bot claims (log/block), each AI crawler category (training, search, user-triggered fetch: allow/log/block) and unverified bots.
- Real search-engine crawlers coming from their vendor's published ranges are always allowed.
- The bot lists are refreshed nightly by a scheduled CI job. If a vendor list fails to download, the job goes red and the previous data is kept.
- An optional per-app generated
robots.txtdisallows every AI category set to block.
Logs¶
| What | Where (on each WAF node) |
|---|---|
| Request log, one JSON line per request | /var/log/haproxy.log |
| OWASP rule matches | journalctl -u coraza-spoa |
| CrowdSec decisions | sudo cscli decisions list |
| Who changed what | GUI → Audit log |
# live request log, readable
sudo tail -f /var/log/haproxy.log | grep --line-buffered '{"ts"' | sed -u 's/^[^{]*//' \
| jq -c '{app,src,country,status,waf_action,waf_rule_ids,bot_id,bot_verified,path}'
# one request, by the X-Request-ID the origin received
sudo grep <request-id> /var/log/haproxy.log
Key log fields: waf_action (allow, detect, block, ip_ban, crowdsec_ban, allowlist,
ratelimit, bot_log, bot_block, spoa_error, …), policy_src (geo/asn), waf_rule_ids
(OWASP rules that matched), bot_id / bot_verified / bot_category, tls_ja3 / tls_ja4.
Scheduled jobs¶
| Job | Frequency | What |
|---|---|---|
| Geo/ASN maps | Weekly | Rebuild the GeoLite2 country and ASN maps and push them to the nodes |
| Bot lists | Nightly | Refresh vendor bot IP ranges and the ai.robots.txt crawler list |
Both are serialised with deploys, so they never run at the same time as one.
Backups¶
The control plane dumps its database daily and keeps two weeks of dumps. After restoring a dump, Preview and Apply so the nodes match the database. Restore steps are in the WAF repository's runbook.