mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-28 21:17:36 +00:00
feat(haproxy,torrent): declarative per-vhost waf_bypass + durable torrent exposure (ref #917)
haproxyctl: honour an opt-in `waf_bypass = true` field per vhost so the generator emits `use_backend <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.<host>.<suffix> 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 <devel@cybermind.fr>
This commit is contained in:
parent
771b1da3fc
commit
940aa64fcf
23
CLAUDE.md
23
CLAUDE.md
|
|
@ -239,7 +239,8 @@ secubox-deb/
|
||||||
## 🔑 Règles impératives
|
## 🔑 Règles impératives
|
||||||
|
|
||||||
### Réseau / Sécurité
|
### 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
|
- nftables DEFAULT DROP — ouvrir explicitement seulement ce qui est nécessaire
|
||||||
- HAProxy en frontal TLS 1.3 pour toute exposition externe
|
- HAProxy en frontal TLS 1.3 pour toute exposition externe
|
||||||
- AppArmor profile enforce pour chaque service
|
- AppArmor profile enforce pour chaque service
|
||||||
|
|
@ -317,14 +318,20 @@ distinctes sans collision.
|
||||||
|
|
||||||
## 🔒 Security Policies — Héritées de secubox-openwrt, adaptées Debian
|
## 🔒 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
|
* **Défaut : JAMAIS de bypass** — tout le trafic DOIT passer par mitmproxy/sbxwaf
|
||||||
par mitmproxy pour inspection
|
pour inspection. Quand tu ajoutes un nouveau vhost, route systématiquement via
|
||||||
* Quand tu ajoutes un nouveau vhost, route systématiquement via le backend
|
le backend `mitmproxy_inspector` dans HAProxy.
|
||||||
`mitmproxy_inspector` dans HAProxy
|
* Si un service nécessite WebSocket ou long-polling, configure d'abord
|
||||||
* Si un service nécessite WebSocket ou long-polling, configure mitmproxy
|
mitmproxy pour forward correctement — n'atteins le bypass qu'en dernier recours.
|
||||||
pour forward correctement — ne pas bypasser le WAF
|
* **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`
|
* Après ajout d'un backend HAProxy, mettre à jour `/srv/mitmproxy/haproxy-routes.json`
|
||||||
ET `/srv/mitmproxy-in/haproxy-routes.json` :
|
ET `/srv/mitmproxy-in/haproxy-routes.json` :
|
||||||
```json
|
```json
|
||||||
|
|
|
||||||
|
|
@ -670,6 +670,12 @@ EOF
|
||||||
local domain=$(echo "$section" | grep -E '^domain\s*=' | cut -d'"' -f2)
|
local domain=$(echo "$section" | grep -E '^domain\s*=' | cut -d'"' -f2)
|
||||||
local backend=$(echo "$section" | grep -E '^backend\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")
|
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}
|
# WebUI strict-regex already covers admin.${SECUBOX_HOSTNAME}.${SECUBOX_DOMAIN_SUFFIX}
|
||||||
if [ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ]; then continue; fi
|
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.
|
# short-circuited &&-chain (e.g. a non-SSL vhost) aborts generation.
|
||||||
if [ "$enabled" = "1" ] && [ -n "$domain" ]; then
|
if [ "$enabled" = "1" ] && [ -n "$domain" ]; then
|
||||||
echo " acl host_$name hdr(host) -i $domain"
|
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"
|
echo " use_backend mitmproxy_inspector if host_$name"
|
||||||
else
|
else
|
||||||
echo " use_backend $backend if host_$name"
|
echo " use_backend ${backend:-nginx_vhosts} if host_$name"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done >> "$out"
|
done >> "$out"
|
||||||
|
|
@ -720,16 +726,18 @@ EOF
|
||||||
local backend=$(echo "$section" | grep -E '^backend\s*=' | cut -d'"' -f2)
|
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 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")
|
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}
|
# WebUI strict-regex already covers admin.${SECUBOX_HOSTNAME}.${SECUBOX_DOMAIN_SUFFIX}
|
||||||
if [ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ]; then continue; fi
|
if [ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ]; then continue; fi
|
||||||
|
|
||||||
# if/then/fi (not `&& { }`) — set -e safe for non-SSL vhosts.
|
# if/then/fi (not `&& { }`) — set -e safe for non-SSL vhosts.
|
||||||
if [ "$enabled" = "1" ] && [ "$ssl" = "1" ] && [ -n "$domain" ]; then
|
if [ "$enabled" = "1" ] && [ "$ssl" = "1" ] && [ -n "$domain" ]; then
|
||||||
echo " acl host_$name hdr(host) -i $domain"
|
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"
|
echo " use_backend mitmproxy_inspector if host_$name"
|
||||||
else
|
else
|
||||||
echo " use_backend $backend if host_$name"
|
echo " use_backend ${backend:-nginx_vhosts} if host_$name"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done >> "$out"
|
done >> "$out"
|
||||||
|
|
|
||||||
|
|
@ -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 <devel@cybermind.fr> Tue, 28 Jul 2026 20:15:00 +0200
|
||||||
|
|
||||||
secubox-torrent (2.1.2-1~bookworm1) bookworm; urgency=high
|
secubox-torrent (2.1.2-1~bookworm1) bookworm; urgency=high
|
||||||
|
|
||||||
* Fix remove/files/stream 500 + broken stats: WebTorrent 2.x's client.get()
|
* Fix remove/files/stream 500 + broken stats: WebTorrent 2.x's client.get()
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,7 @@ case "$1" in
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Symlink the public vhost into sites-enabled (idempotent). Proxies
|
# Symlink the public vhost into sites-enabled (idempotent). Proxies
|
||||||
# torrent.gk2.secubox.in -> torrent LXC at 10.100.0.160:8090. Operator
|
# torrent.<host>.<suffix> -> torrent LXC at 10.100.0.160:8090.
|
||||||
# wires the HAProxy SNI ACL + ACME cert + sbxwaf route separately.
|
|
||||||
if [ -f /etc/nginx/sites-available/torrent.conf ] && [ -d /etc/nginx/sites-enabled ]; then
|
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
|
ln -sf ../sites-available/torrent.conf /etc/nginx/sites-enabled/torrent.conf
|
||||||
fi
|
fi
|
||||||
|
|
@ -46,6 +45,43 @@ case "$1" in
|
||||||
if command -v nginx >/dev/null 2>&1 && nginx -t >/dev/null 2>&1; then
|
if command -v nginx >/dev/null 2>&1 && nginx -t >/dev/null 2>&1; then
|
||||||
systemctl reload nginx 2>/dev/null || true
|
systemctl reload nginx 2>/dev/null || true
|
||||||
fi
|
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
|
esac
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user