mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-29 16:31:31 +00:00
Compare commits
5 Commits
ce636273a6
...
9d1b0abade
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d1b0abade | |||
| 41d78ef455 | |||
| fbd474b2c3 | |||
| c47e454532 | |||
| e12790efbd |
|
|
@ -0,0 +1,56 @@
|
|||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
# Spec — HAProxy complete dynamic vhost auto-discovery
|
||||
|
||||
*2026-06-17 · landed for later (per user) · found while fixing #626/#627*
|
||||
|
||||
## Problem
|
||||
`haproxyctl generate` must produce a COMPLETE config so regen never drops
|
||||
hand-maintained vhosts. Today ~10 vhosts live only in the hand-edited
|
||||
`/etc/haproxy/haproxy.cfg` — not in `haproxy.toml` or `cfg.d/`:
|
||||
|
||||
- `kbin.gk2.secubox.in` → `toolbox_landing` (backend also live-only)
|
||||
- `matrix`, `gitea`, `peertube`, `photoprism` (.gk2.secubox.in) → `nginx_vhosts`
|
||||
|
||||
These bypass the WAF today (route direct, not through `mitmproxy_inspector`).
|
||||
A clean regen omits them. PR #627 added a **drift guard** so regen refuses to
|
||||
clobber when its output has fewer vhosts/backends than live — safe, but not
|
||||
complete.
|
||||
|
||||
## Design (approved direction: auto-discover from modules/LXC)
|
||||
1. **Drop-in registry.** Generator aggregates `haproxy.toml [vhosts.*]` **plus**
|
||||
`/etc/secubox/vhosts.d/*.toml` — one file per module/LXC, dropped by that
|
||||
module's postinst (self-registration). New modules appear automatically.
|
||||
2. **Per-vhost routing intent** replaces the blanket `waf_enabled` override
|
||||
(which currently forces *every* vhost through the WAF):
|
||||
```toml
|
||||
[vhost]
|
||||
domain = "peertube.gk2.secubox.in"
|
||||
backend = "nginx_vhosts" # or a module/LXC backend
|
||||
ssl = true
|
||||
inspect = false # true → mitmproxy_inspector (WAF); false → direct
|
||||
```
|
||||
3. **One-time migration.** Seed `vhosts.d/` from the ~10 drifted live entries
|
||||
with their real backends + `inspect=false`; register `toolbox_landing` as a
|
||||
known backend. After this, regen output == live → drift guard passes.
|
||||
4. **Module-registration helper** for postinsts (e.g. `haproxyctl vhost register
|
||||
--from-file` or a tiny library) so each LXC/module declares its vhost.
|
||||
5. **Keep the drift guard** as the transition safety net.
|
||||
|
||||
## Validation gate (non-negotiable)
|
||||
Never apply a regenerated cfg to production until a diff proves it reproduces all
|
||||
~100 live vhosts/backends 1:1 (drift-guard counts match).
|
||||
|
||||
## Related: finish the traversal-footgun sweep (#623)
|
||||
The systemic `install -d -m 0750 .../secubox` footgun is broader than first
|
||||
swept: **multi-arg** forms like `install -d -m 0750 /run/secubox /var/lib/secubox
|
||||
…` (e.g. secubox-haproxy, fixed in #627) were missed by the earlier grep
|
||||
(`…/secubox/[a-z]` required a leaf). Re-sweep with a pattern that catches bare
|
||||
`/var/{lib,log,cache}/secubox` arguments, and add a **tmpfiles.d + periodic
|
||||
guard** so the shared parents self-heal to `0755` regardless of which package
|
||||
clobbers them (this is the root of the recurring kbin/toolbox 500s).
|
||||
|
||||
## Status
|
||||
- Generator no longer crashes (set -e + dup-backend fixed, #627).
|
||||
- Drift guard prevents clobbering (#627).
|
||||
- Error pages live + served from `/etc/haproxy/secubox-errors/` (#627).
|
||||
- This auto-discovery rework + the #623 re-sweep are the remaining work.
|
||||
|
|
@ -6,7 +6,20 @@ secubox-haproxy (1.3.1-1~bookworm1) bookworm; urgency=medium
|
|||
with live status + manual retry. {400,403,408,500}.http: branded static.
|
||||
- haproxyctl generator: wire errorfile directives (durable across regen)
|
||||
and persist retries 3 + option redispatch in defaults.
|
||||
- Ship pages to /etc/haproxy/errors/; postinst regen-safe applies them.
|
||||
- Ship pages to /etc/haproxy/secubox-errors/ (own dir; /etc/haproxy/errors
|
||||
is owned by the haproxy package). errorfile wired there.
|
||||
* fix(generator): `haproxyctl generate` was broken (exited 1, produced no
|
||||
backends) — root causes: (a) `set -e` + `[ ] && [ ] && { }` vhost-loop
|
||||
chains aborted generation on the first non-SSL vhost; (b) duplicate
|
||||
`backend mitmproxy_inspector` (auto + user TOML) → fatal. Converted the
|
||||
chains to if/then/fi and dedup user backends already emitted.
|
||||
* fix(generator): drift guard — refuse to install a generated cfg with fewer
|
||||
vhosts/backends than the live one (prevents a successful regen from silently
|
||||
dropping hand-maintained vhosts like kbin/gitea/matrix not yet in
|
||||
haproxy.toml). Surfaces the drift instead of clobbering.
|
||||
* fix(postinst): keep /run/secubox + /var/lib/secubox at 0755 (was set 0750,
|
||||
breaking directory traversal for other secubox-* daemons — kbin/toolbox 500).
|
||||
Only the haproxy-private leaves are 0750.
|
||||
|
||||
-- Gerald Kerma <devel@cybermind.fr> Tue, 17 Jun 2026 09:00:00 +0200
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@ case "$1" in
|
|||
configure)
|
||||
id -u secubox >/dev/null 2>&1 || \
|
||||
adduser --system --group --no-create-home --home /var/lib/secubox --shell /usr/sbin/nologin secubox
|
||||
install -d -o secubox -g secubox -m 750 /run/secubox /var/lib/secubox /var/lib/secubox/haproxy /var/lib/secubox/haproxy/config_backups
|
||||
# Shared parents stay 0755 (traversable by every secubox-* daemon — setting
|
||||
# them 0750 here broke kbin/toolbox by blocking traversal, #626). Only the
|
||||
# haproxy-private leaves are restricted.
|
||||
install -d -o secubox -g secubox -m 755 /run/secubox /var/lib/secubox
|
||||
install -d -o secubox -g secubox -m 750 /var/lib/secubox/haproxy /var/lib/secubox/haproxy/config_backups
|
||||
chmod 0755 /var/lib/secubox /run/secubox 2>/dev/null || true
|
||||
# Create /etc/haproxy if not present (haproxy is Recommends, not Depends)
|
||||
# Required for systemd namespace setup
|
||||
install -d -m 755 /etc/haproxy
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@ override_dh_auto_install:
|
|||
[ -d www ] && cp -r www/. debian/secubox-haproxy/usr/share/secubox/www/ || true
|
||||
install -d debian/secubox-haproxy/usr/share/secubox/menu.d
|
||||
[ -d menu.d ] && cp -r menu.d/. debian/secubox-haproxy/usr/share/secubox/menu.d/ || true
|
||||
# Smart self-healing HAProxy error pages (#626) — wired via errorfile in haproxyctl
|
||||
install -d debian/secubox-haproxy/etc/haproxy/errors
|
||||
[ -d errors ] && install -m 644 errors/*.http debian/secubox-haproxy/etc/haproxy/errors/ || true
|
||||
# Smart self-healing HAProxy error pages (#626) — wired via errorfile in
|
||||
# haproxyctl. Own dir (NOT /etc/haproxy/errors, which the haproxy package owns).
|
||||
install -d debian/secubox-haproxy/etc/haproxy/secubox-errors
|
||||
[ -d errors ] && install -m 644 errors/*.http debian/secubox-haproxy/etc/haproxy/secubox-errors/ || true
|
||||
# Modular nginx config
|
||||
install -d debian/secubox-haproxy/etc/nginx/secubox.d
|
||||
[ -f nginx/haproxy.conf ] && cp nginx/haproxy.conf debian/secubox-haproxy/etc/nginx/secubox.d/ || true
|
||||
|
|
|
|||
|
|
@ -626,13 +626,13 @@ defaults
|
|||
option dontlognull
|
||||
option forwardfor
|
||||
# Smart self-healing error pages (#626) — shipped by secubox-haproxy.
|
||||
errorfile 400 /etc/haproxy/errors/400.http
|
||||
errorfile 403 /etc/haproxy/errors/403.http
|
||||
errorfile 408 /etc/haproxy/errors/408.http
|
||||
errorfile 500 /etc/haproxy/errors/500.http
|
||||
errorfile 502 /etc/haproxy/errors/502.http
|
||||
errorfile 503 /etc/haproxy/errors/503.http
|
||||
errorfile 504 /etc/haproxy/errors/504.http
|
||||
errorfile 400 /etc/haproxy/secubox-errors/400.http
|
||||
errorfile 403 /etc/haproxy/secubox-errors/403.http
|
||||
errorfile 408 /etc/haproxy/secubox-errors/408.http
|
||||
errorfile 500 /etc/haproxy/secubox-errors/500.http
|
||||
errorfile 502 /etc/haproxy/secubox-errors/502.http
|
||||
errorfile 503 /etc/haproxy/secubox-errors/503.http
|
||||
errorfile 504 /etc/haproxy/secubox-errors/504.http
|
||||
|
||||
frontend stats
|
||||
bind *:${stats_port}
|
||||
|
|
@ -671,16 +671,18 @@ EOF
|
|||
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")
|
||||
# WebUI strict-regex already covers admin.${SECUBOX_HOSTNAME}.${SECUBOX_DOMAIN_SUFFIX}
|
||||
[ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ] && continue
|
||||
if [ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ]; then continue; fi
|
||||
|
||||
[ "$enabled" = "1" ] && [ -n "$domain" ] && {
|
||||
# NB: use if/then/fi, not `[ ] && [ ] && { }` — under `set -e` a
|
||||
# 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
|
||||
echo " use_backend mitmproxy_inspector if host_$name"
|
||||
else
|
||||
echo " use_backend $backend if host_$name"
|
||||
fi
|
||||
}
|
||||
fi
|
||||
done >> "$out"
|
||||
fi
|
||||
|
||||
|
|
@ -719,16 +721,17 @@ EOF
|
|||
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")
|
||||
# WebUI strict-regex already covers admin.${SECUBOX_HOSTNAME}.${SECUBOX_DOMAIN_SUFFIX}
|
||||
[ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ] && continue
|
||||
if [ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ]; then continue; fi
|
||||
|
||||
[ "$enabled" = "1" ] && [ "$ssl" = "1" ] && [ -n "$domain" ] && {
|
||||
# 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
|
||||
echo " use_backend mitmproxy_inspector if host_$name"
|
||||
else
|
||||
echo " use_backend $backend if host_$name"
|
||||
fi
|
||||
}
|
||||
fi
|
||||
done >> "$out"
|
||||
fi
|
||||
|
||||
|
|
@ -738,7 +741,8 @@ EOF
|
|||
EOF
|
||||
|
||||
# WAF inspector backend (mitmproxy LXC container)
|
||||
[ "$waf_enabled" = "1" ] && cat >> "$out" << EOF
|
||||
# if/then/fi (not `&& cat`) — under set -e a false test here aborts generation.
|
||||
if [ "$waf_enabled" = "1" ]; then cat >> "$out" << EOF
|
||||
# WAF Inspector Backend (mitmproxy LXC at $waf_ip:$waf_port)
|
||||
backend mitmproxy_inspector
|
||||
mode http
|
||||
|
|
@ -748,6 +752,7 @@ backend mitmproxy_inspector
|
|||
server waf ${waf_ip}:${waf_port} check
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
# WebUI direct backend (issue #44 — for the strict-regex ACL above)
|
||||
# Only emit if SECUBOX_HOSTNAME is set (i.e., the strict ACL was injected).
|
||||
|
|
@ -763,6 +768,9 @@ EOF
|
|||
if [ -f "$CONF_PATH" ]; then
|
||||
grep '^\[backends\.' "$CONF_PATH" | while read -r line; do
|
||||
local name=$(echo "$line" | sed 's/\[backends\.//;s/\]//')
|
||||
# Skip backends already emitted (mitmproxy_inspector/webui_direct are
|
||||
# auto-generated above) to avoid duplicate-backend fatal errors.
|
||||
if grep -q "^backend $name\$" "$out"; then continue; fi
|
||||
local section=$(sed -n "/^\[backends\.$name\]/,/^\[/p" "$CONF_PATH" | head -n -1)
|
||||
local mode=$(echo "$section" | grep -E '^mode\s*=' | cut -d'"' -f2 || echo "http")
|
||||
local balance=$(echo "$section" | grep -E '^balance\s*=' | cut -d'"' -f2 || echo "roundrobin")
|
||||
|
|
@ -775,11 +783,11 @@ backend $name
|
|||
EOF
|
||||
local i=0
|
||||
echo "$servers" | while read -r srv; do
|
||||
[ -n "$srv" ] && {
|
||||
if [ -n "$srv" ]; then
|
||||
srv=$(echo "$srv" | tr -d ' ')
|
||||
echo " server srv$i $srv check" >> "$out"
|
||||
i=$((i + 1))
|
||||
}
|
||||
fi
|
||||
done
|
||||
echo "" >> "$out"
|
||||
done
|
||||
|
|
@ -834,6 +842,22 @@ EOF
|
|||
# to the live cfg and only validated at the end, leaving the file in a
|
||||
# broken state on failure (recurring "broken-by-vhost-add" backups).
|
||||
if haproxy -c -f "$out" 2>/dev/null; then
|
||||
# Drift guard (#626): refuse to overwrite a live config that has MORE
|
||||
# vhosts/backends than we just generated — that means the live cfg holds
|
||||
# entries absent from our inputs (haproxy.toml / cfg.d). Without this,
|
||||
# a successful regen would silently drop hand-maintained vhosts (kbin,
|
||||
# gitea, …). `local x=$(...)` masks grep's exit so set -e stays happy.
|
||||
if [ -f "$CONFIG_DIR/haproxy.cfg" ]; then
|
||||
local _nh=$(grep -c '^[[:space:]]*acl host_' "$out")
|
||||
local _oh=$(grep -c '^[[:space:]]*acl host_' "$CONFIG_DIR/haproxy.cfg")
|
||||
local _nb=$(grep -c '^backend ' "$out")
|
||||
local _ob=$(grep -c '^backend ' "$CONFIG_DIR/haproxy.cfg")
|
||||
if [ "${_nh:-0}" -lt "${_oh:-0}" ] || [ "${_nb:-0}" -lt "${_ob:-0}" ]; then
|
||||
error "Drift guard: generated cfg has fewer vhosts/backends than live (acl ${_nh}<${_oh} or backend ${_nb}<${_ob}) — refusing to clobber. Migrate the missing entries into haproxy.toml/cfg.d first."
|
||||
rm -f "$out"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
install -m 0644 -o root -g root "$out" "$CONFIG_DIR/haproxy.cfg"
|
||||
rm -f "$out"
|
||||
log "Configuration generated, validated and installed: $CONFIG_DIR/haproxy.cfg"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user