From 940aa64fcf7eacb52ce7d8cfafe7cccfa63197c9 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Tue, 28 Jul 2026 19:38:59 +0200 Subject: [PATCH] feat(haproxy,torrent): declarative per-vhost waf_bypass + durable torrent exposure (ref #917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit haproxyctl: honour an opt-in `waf_bypass = true` field per vhost so the generator emits `use_backend ` (nginx_vhosts) instead of forcing mitmproxy_inspector under waf_enabled. Makes the WAF-bypass of media/large- upload services (nc/photoprism/matrix/torrent) declarative, auditable and regen-durable instead of fragile hand-edits of the generated haproxy.cfg. Normal vhosts are unaffected (verified in isolation). secubox-torrent postinst: register torrent.. in haproxy.toml (backend=nginx_vhosts, waf_bypass=true, ssl=true) idempotently and run a drift-guarded `haproxyctl generate` — which refuses safely on a board that still has un-migrated hand-edit vhosts (live cfg untouched) and succeeds on a fresh board. Torrent bypasses sbxwaf by design, so no sbxwaf route is needed. CLAUDE.md: WAF-bypass section updated — default stays 'never bypass'; the ONLY sanctioned exception is the declarative waf_bypass=true in haproxy.toml, never a hand-edit of the generated cfg. Co-Authored-By: Gerald KERMA --- CLAUDE.md | 23 ++++++++----- packages/secubox-haproxy/sbin/haproxyctl | 16 ++++++--- packages/secubox-torrent/debian/changelog | 13 ++++++++ packages/secubox-torrent/debian/postinst | 40 +++++++++++++++++++++-- 4 files changed, 78 insertions(+), 14 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e3de3641..113b006b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -239,7 +239,8 @@ secubox-deb/ ## 🔑 Règles impératives ### Réseau / Sécurité -- **JAMAIS** de waf_bypass ni de port ouvert inutile +- **JAMAIS** de bypass WAF hand-edité ni de port ouvert inutile (seul le + `waf_bypass = true` déclaratif du `haproxy.toml` est autorisé — cf. §WAF Bypass) - nftables DEFAULT DROP — ouvrir explicitement seulement ce qui est nécessaire - HAProxy en frontal TLS 1.3 pour toute exposition externe - AppArmor profile enforce pour chaque service @@ -317,14 +318,20 @@ distinctes sans collision. ## 🔒 Security Policies — Héritées de secubox-openwrt, adaptées Debian -### WAF Bypass — Interdiction absolue +### WAF Bypass — Interdit par défaut, exception déclarative uniquement -* **JAMAIS de `waf_bypass` dans une config HAProxy** — tout le trafic DOIT passer - par mitmproxy pour inspection -* Quand tu ajoutes un nouveau vhost, route systématiquement via le backend - `mitmproxy_inspector` dans HAProxy -* Si un service nécessite WebSocket ou long-polling, configure mitmproxy - pour forward correctement — ne pas bypasser le WAF +* **Défaut : JAMAIS de bypass** — tout le trafic DOIT passer par mitmproxy/sbxwaf + pour inspection. Quand tu ajoutes un nouveau vhost, route systématiquement via + le backend `mitmproxy_inspector` dans HAProxy. +* Si un service nécessite WebSocket ou long-polling, configure d'abord + mitmproxy pour forward correctement — n'atteins le bypass qu'en dernier recours. +* **Seule exception sanctionnée : `waf_bypass = true` déclaratif par-vhost** dans + `/etc/secubox/haproxy.toml`, honoré par `haproxyctl` (backend `nginx_vhosts`, + droit vers nginx). Réservé aux services que la chaîne d'inspection casse + prouvablement — gros uploads / WebDAV / streaming média Range / fédération + (ex. `nc`, `photoprism`, `matrix`, `torrent`). Opt-in explicite, déclaratif, + auditable, versionné — **jamais un hand-edit du `haproxy.cfg` généré** (qui + serait écrasé au prochain `haproxyctl generate`) et jamais un défaut silencieux. * Après ajout d'un backend HAProxy, mettre à jour `/srv/mitmproxy/haproxy-routes.json` ET `/srv/mitmproxy-in/haproxy-routes.json` : ```json diff --git a/packages/secubox-haproxy/sbin/haproxyctl b/packages/secubox-haproxy/sbin/haproxyctl index 2563ba85..38b9308f 100755 --- a/packages/secubox-haproxy/sbin/haproxyctl +++ b/packages/secubox-haproxy/sbin/haproxyctl @@ -670,6 +670,12 @@ EOF local domain=$(echo "$section" | grep -E '^domain\s*=' | cut -d'"' -f2) local backend=$(echo "$section" | grep -E '^backend\s*=' | cut -d'"' -f2) local enabled=$(echo "$section" | grep -E '^enabled\s*=' | grep -q 'false' && echo "0" || echo "1") + # Opt-in, per-vhost WAF bypass. Sanctioned exception for services the + # inspection chain breaks (large uploads / WebDAV / media streaming / + # federation — nc, photoprism, matrix, torrent). Requires an explicit + # `waf_bypass = true` AND a real `backend` (nginx_vhosts) in the vhost + # block, so it is declarative and auditable — never a silent default. + local waf_bypass=$(echo "$section" | grep -E '^waf_bypass\s*=' | grep -q 'true' && echo "1" || echo "0") # WebUI strict-regex already covers admin.${SECUBOX_HOSTNAME}.${SECUBOX_DOMAIN_SUFFIX} if [ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ]; then continue; fi @@ -677,10 +683,10 @@ EOF # short-circuited &&-chain (e.g. a non-SSL vhost) aborts generation. if [ "$enabled" = "1" ] && [ -n "$domain" ]; then echo " acl host_$name hdr(host) -i $domain" - if [ "$waf_enabled" = "1" ]; then + if [ "$waf_enabled" = "1" ] && [ "$waf_bypass" != "1" ]; then echo " use_backend mitmproxy_inspector if host_$name" else - echo " use_backend $backend if host_$name" + echo " use_backend ${backend:-nginx_vhosts} if host_$name" fi fi done >> "$out" @@ -720,16 +726,18 @@ EOF local backend=$(echo "$section" | grep -E '^backend\s*=' | cut -d'"' -f2) local ssl=$(echo "$section" | grep -E '^ssl\s*=' | grep -q 'true' && echo "1" || echo "0") local enabled=$(echo "$section" | grep -E '^enabled\s*=' | grep -q 'false' && echo "0" || echo "1") + # Per-vhost WAF bypass (declarative, opt-in) — see the http-in loop. + local waf_bypass=$(echo "$section" | grep -E '^waf_bypass\s*=' | grep -q 'true' && echo "1" || echo "0") # WebUI strict-regex already covers admin.${SECUBOX_HOSTNAME}.${SECUBOX_DOMAIN_SUFFIX} if [ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ]; then continue; fi # if/then/fi (not `&& { }`) — set -e safe for non-SSL vhosts. if [ "$enabled" = "1" ] && [ "$ssl" = "1" ] && [ -n "$domain" ]; then echo " acl host_$name hdr(host) -i $domain" - if [ "$waf_enabled" = "1" ]; then + if [ "$waf_enabled" = "1" ] && [ "$waf_bypass" != "1" ]; then echo " use_backend mitmproxy_inspector if host_$name" else - echo " use_backend $backend if host_$name" + echo " use_backend ${backend:-nginx_vhosts} if host_$name" fi fi done >> "$out" diff --git a/packages/secubox-torrent/debian/changelog b/packages/secubox-torrent/debian/changelog index 5363a45c..bc7bf0dc 100644 --- a/packages/secubox-torrent/debian/changelog +++ b/packages/secubox-torrent/debian/changelog @@ -1,3 +1,16 @@ +secubox-torrent (2.1.3-1~bookworm1) bookworm; urgency=medium + + * Make the HAProxy exposure durable (was a hand-edit a `haproxyctl generate` + would wipe). postinst now registers the vhost in /etc/secubox/haproxy.toml + declaratively with `waf_bypass = true` (routes straight to nginx_vhosts — + torrent's Range streaming / large transfers break through the mitmproxy + inspection chain, same class as nc/photoprism/matrix). Regen is invoked but + drift-guarded (refuses on a board with un-migrated hand-edit vhosts, leaving + the live cfg intact; succeeds on a fresh board). Requires the companion + haproxyctl change that honours the per-vhost waf_bypass flag. (ref #917) + + -- Gerald KERMA Tue, 28 Jul 2026 20:15:00 +0200 + secubox-torrent (2.1.2-1~bookworm1) bookworm; urgency=high * Fix remove/files/stream 500 + broken stats: WebTorrent 2.x's client.get() diff --git a/packages/secubox-torrent/debian/postinst b/packages/secubox-torrent/debian/postinst index 40a00743..e16781a8 100755 --- a/packages/secubox-torrent/debian/postinst +++ b/packages/secubox-torrent/debian/postinst @@ -37,8 +37,7 @@ case "$1" in fi # Symlink the public vhost into sites-enabled (idempotent). Proxies - # torrent.gk2.secubox.in -> torrent LXC at 10.100.0.160:8090. Operator - # wires the HAProxy SNI ACL + ACME cert + sbxwaf route separately. + # torrent.. -> torrent LXC at 10.100.0.160:8090. if [ -f /etc/nginx/sites-available/torrent.conf ] && [ -d /etc/nginx/sites-enabled ]; then ln -sf ../sites-available/torrent.conf /etc/nginx/sites-enabled/torrent.conf fi @@ -46,6 +45,43 @@ case "$1" in if command -v nginx >/dev/null 2>&1 && nginx -t >/dev/null 2>&1; then systemctl reload nginx 2>/dev/null || true fi + + # Register the HAProxy exposure DURABLY (was previously a hand-edit that a + # `haproxyctl generate` would wipe). Torrent is a WAF-bypass vhost by design + # — like nc/photoprism/matrix, the mitmproxy inspection chain breaks its + # Range streaming / large transfers — so it declares `waf_bypass = true` + # (the sanctioned, auditable opt-in honoured by haproxyctl) and routes + # straight to the host nginx (nginx_vhosts), never through sbxwaf. This + # writes the declaration into /etc/secubox/haproxy.toml; the actual regen is + # drift-guarded (it refuses when the board still holds un-migrated hand-edit + # vhosts, leaving the live cfg untouched, and succeeds on a fresh board). + if [ -f /etc/secubox/haproxy.toml ]; then + # Canonical vhost name/domain from the board identity (falls back to gk2). + if [ -f /etc/default/secubox ]; then + . /etc/default/secubox 2>/dev/null || true + fi + SBX_HOST="${SECUBOX_HOSTNAME:-gk2}"; SBX_SUFFIX="${SECUBOX_DOMAIN_SUFFIX:-secubox.in}" + T_DOMAIN="torrent.${SBX_HOST}.${SBX_SUFFIX}" + T_NAME="torrent_$(echo "${SBX_HOST}.${SBX_SUFFIX}" | tr '.' '_')" + if ! grep -q "^\[vhosts\.${T_NAME}\]" /etc/secubox/haproxy.toml; then + { + printf '\n[vhosts.%s]\n' "$T_NAME" + printf 'domain = "%s"\n' "$T_DOMAIN" + printf 'backend = "nginx_vhosts"\n' + printf 'waf_bypass = true\n' + printf 'ssl = true\n' + printf 'ssl_redirect = true\n' + printf 'enabled = true\n' + } >> /etc/secubox/haproxy.toml + fi + if command -v haproxyctl >/dev/null 2>&1; then + if haproxyctl generate >/dev/null 2>&1; then + systemctl reload haproxy 2>/dev/null || true + else + echo "secubox-torrent: haproxy.toml updated; deferred regen (drift-guarded — run 'haproxyctl generate' once the board's hand-edit vhosts are migrated)" >&2 + fi + fi + fi ;; esac