From 33bc1e173296df6bd3e56a28649dcfc89201ffc2 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 26 Feb 2026 16:05:37 +0100 Subject: [PATCH] feat(haproxy): Add CrowdSec HAProxy bouncer for dual-layer WAF - Add lua-load directive for CrowdSec bouncer script - Add http-request lua.crowdsec_check to HTTP/HTTPS frontends - Block requests where txn.blocked=1 with 403 status - Skip CrowdSec check for ACME challenges (HTTP frontend) - Dual-layer WAF: CrowdSec IP blocking + mitmproxy inspection Co-Authored-By: Claude Opus 4.5 --- .claude/HISTORY.md | 19 +++++++++++++++++++ .../files/usr/sbin/haproxyctl | 13 +++++++++++++ 2 files changed, 32 insertions(+) diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index eec28b7b..346b56e9 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -3797,3 +3797,22 @@ git checkout HEAD -- index.html - **Root Cause:** RPCD handler only checked Docker, not LXC containers - **Fix:** Added `webmail.type` UCI option check, use `lxc-info` for LXC type - Webmail status now correctly shows "Running" for LXC containers + +46. **CrowdSec HAProxy Bouncer - Dual Layer WAF (2026-02-26)** + - **Purpose:** IP-level blocking at HAProxy before mitmproxy WAF inspection + - **Implementation:** + - Created `/srv/haproxy/lua/crowdsec.lua` - Lua bouncer script + - Queries CrowdSec LAPI on port 8190 for IP decisions + - In-memory cache with 60s TTL (30s negative cache) + - Fail-open design: allows traffic if API unreachable + - Skips internal IPs (127.x, 192.168.x, 10.x) + - **HAProxy Integration:** + - `lua-load /opt/haproxy/lua/crowdsec.lua` in global section + - `http-request lua.crowdsec_check` in HTTP/HTTPS frontends + - `http-request deny deny_status 403 if { var(txn.blocked) -m str 1 }` + - **Registered Bouncer:** `haproxy-bouncer` in CrowdSec with API key + - **Result:** Dual-layer WAF protection + - Layer 1: CrowdSec HAProxy bouncer (IP reputation, 60s cache) + - Layer 2: mitmproxy WAF (request inspection, CVE detection) + - All 13 MetaBlogizer sites verified working with dual WAF + - **CrowdSec MCP:** Added crowdsec-local-mcp for AI-generated WAF rules diff --git a/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxyctl b/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxyctl index cceeb1e1..3d81d997 100644 --- a/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxyctl +++ b/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxyctl @@ -571,6 +571,7 @@ global ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets tune.ssl.default-dh-param 2048 + lua-load /opt/haproxy/lua/crowdsec.lua EOF @@ -643,6 +644,10 @@ frontend http-in acl is_acme_challenge path_beg /.well-known/acme-challenge/ use_backend acme_challenge if is_acme_challenge + # CrowdSec IP blocking (dual-layer WAF with mitmproxy) + http-request lua.crowdsec_check if !is_acme_challenge + http-request deny deny_status 403 if { var(txn.blocked) -m str 1 } + EOF # Add HTTPS redirect rules for vhosts with ssl_redirect @@ -684,6 +689,10 @@ frontend https-in acl is_sensitive_path path_beg /.htaccess acl is_sensitive_path path_beg /.htpasswd http-request deny if is_sensitive_path + + # CrowdSec IP blocking (dual-layer WAF with mitmproxy) + http-request lua.crowdsec_check + http-request deny deny_status 403 if { var(txn.blocked) -m str 1 } EOF else # Fallback to directory mode if no certs.list @@ -701,6 +710,10 @@ frontend https-in acl is_sensitive_path path_beg /.htaccess acl is_sensitive_path path_beg /.htpasswd http-request deny if is_sensitive_path + + # CrowdSec IP blocking (dual-layer WAF with mitmproxy) + http-request lua.crowdsec_check + http-request deny deny_status 403 if { var(txn.blocked) -m str 1 } EOF fi # Add path-based ACLs BEFORE vhost ACLs (path rules take precedence)