JIT
← Notebook
Field notes SecurityWAFHomelab

Twee poorten voor de website

Defense-in-depth aan de homelab-edge met CrowdSec — bekende kwaadwillende IP's meteen geweerd, daarna leest een in-band WAF wat overblijft. En waarom het voorlopig bewust fail-open is.

De website draait achter Traefik, dat al voor TLS en nette routing zorgt. Maar TLS bewijst alleen wie je voor je hebt — niet wat diegene verstuurt. Ik wilde er twee poorten vóór: één die bezoekers met een slechte reputatie weert vóór ze me iets kosten, en één die het verzoek écht leest en het overduidelijk kwaadaardige weigert. Beide zijn CrowdSec.

Client inkomend HTTPS-verzoek Traefik — TLS-terminatie reverse proxy · schrijft een JSON-accesslog ① Bouncer — IP-reputatie bron-IP vs de ban-cache (elke 60s ververst) lokale scenario's + CrowdSec community-blocklist ✗ bekend kwaadwillend IP — geweerd toegestaan ② AppSec-WAF · Coraza (in-band) leest het verzoek tegen WAF-regels nu: virtual patching (bekende-CVE-signatures) ✗ exploit-signature — 403, inline schoon Website — geserveerd · 200 CrowdSec-agent parset de accesslog → draait scenario's → schrijft ban-decisions CrowdSec CAPI (cloud) community-blocklist — automatisch opgehaald geanonimiseerde signalen gedeeld accesslog ban-decisions
Twee poorten per verzoek: reputatie weert bekende kwaadwillende IP's, daarna leest de WAF wat overblijft en blokkeert exploit-signatures inline. Schoon verkeer bereikt de site. Eronder wordt de accesslog omgezet in ban-decisions die — samen met de community-blocklist — de eerste poort blijven verversen.

Poort één — reputatie, aan de deur

De eerste poort is de goedkoopste. Een CrowdSec-bouncer-plugin zit in Traefik en controleert voor elk verzoek het bron-IP tegen een cache van ban-decisions die elke minuut wordt ververst. Twee dingen vullen die cache: lokale scenario’s die Traefiks eigen accesslog lezen en IP’s bannen die staan te scannen (de crowdsecurity/traefik- en http-cve-regels), en de CrowdSec community-blocklist — adressen die andere installaties al hebben gemeld, automatisch opgehaald. Een bekend kwaadwillend IP bereikt de app nooit. Bot en snel, precies goed voor de bulk aan internetruis.

Poort twee — een WAF die het verzoek leest

Reputatie vangt geen net-ogend IP dat een vuil verzoek stuurt. Dat is de tweede poort: de AppSec-component van CrowdSec, een WAF op de Coraza-engine. Dezelfde bouncer stuurt elk toegestaan verzoek door voor in-band inspectie — het oordeel komt terug vóór het verzoek de site raakt. Nu draait het virtual-patching-regels: 182 signatures voor specifieke, bekende CVE-exploits, het soort met bijna geen false positives. Eén match en je krijgt een 403, inline. De test is bevredigend: GET /200, GET /.git/config403.

Het eerlijke deel — fail-open, en een crash op dag één

Bij de eerste deploy crashte CrowdSec in een loop — de standaardconfig verwees naar een regelset die nergens geïnstalleerd was, en hij weigerde te starten. De site merkte er niets van. De bouncer is fail-open: is de WAF onbereikbaar, dan blijft verkeer gewoon stromen. Ik serveer liever een ongefilterd verzoek dan dat ik een haperende securitytool een noodknop over mijn eigen website geef. De fix was een kleine, op zichzelf staande config die alleen de regels laadt die ik écht heb; daarmee kwam hij schoon op.

Een WAF die je site platlegt als hijzelf stuk gaat, beschermt je site niet. Begin fail-open; verdien fail-closed.

Fail-open is een bewuste keuze voor nu. Hierna: de OWASP Core Rule Set als breder net, dan een default-deny-allowlist van wat de app legitiem doet, en pas dán de overstap naar fail-closed — als ik het lang genoeg heb zien draaien om het te vertrouwen. Eén los eindje: de community-blocklist werkt zichzelf bij, de regel-signatures niet. Een geplande refresh staat op de lijst — een virtuele patch die je nooit bijwerkt, is gewoon een comment.