mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-28 21:17:36 +00:00
feat(waf-ng): reconcile to a single HARDENED non-root unit; retire worker@ fan-out (ref #896)
Some checks failed
License Headers / check (push) Has been cancelled
Some checks failed
License Headers / check (push) Has been cancelled
The board ran a hand-created unhardened ROOT sbxwaf on :8085 while the package shipped only a worker@ fan-out that crash-looped (panic on the root-only cookie-audit log) and, as secubox-waf + RuntimeDirectory=secubox, re-chowned the shared /run/secubox on every crash-restart — breaking every secubox-user socket bind (profiles 502s). Ship ONE hardened unit (secubox-waf-ng.service, :8085, User=secubox-waf, full sandbox, NO RuntimeDirectory — sbxwaf only connects to waker.sock). postinst asserts the perms a non-root sbxwaf needs (haproxy-routes.json 0644, cookie-audit ledger writable by secubox-waf), disables leftover worker@ units, drops the /etc override. Validated live on gk2: 264 routes, real routing, admin/gitea/billets/yacy 200; a WAF restart no longer touches /run/secubox and profiles stays up. Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
parent
7b8fd5f1e3
commit
bd8e2e382e
|
|
@ -1,3 +1,18 @@
|
|||
secubox-waf-ng (1.1.0-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* #896 WAF-drift reconcile: ship a SINGLE HARDENED unit (secubox-waf-ng.service,
|
||||
:8085, User=secubox-waf, full sandbox) that replaces BOTH the crash-looping
|
||||
worker@ fan-out template (retired — it panicked on the root-only cookie-audit
|
||||
log and, via RuntimeDirectory=secubox as secubox-waf, re-chowned the shared
|
||||
/run/secubox and broke every secubox-user socket bind) AND the hand-created
|
||||
unhardened root :8085 override the board was running.
|
||||
* NO RuntimeDirectory=secubox (sbxwaf only CONNECTS to waker.sock) → kills the
|
||||
re-chown landmine. postinst asserts perms a non-root sbxwaf needs
|
||||
(haproxy-routes.json 0644, cookie-audit ledger writable by secubox-waf),
|
||||
disables leftover worker@ units, and drops the /etc override.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Tue, 21 Jul 2026 10:00:00 +0200
|
||||
|
||||
secubox-waf-ng (1.0.0-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Initial packaging of the Go WAF reverse-proxy engine (#744 Task 8.1).
|
||||
|
|
|
|||
|
|
@ -59,6 +59,20 @@ case "$1" in
|
|||
mkdir -p /var/log/secubox/cookie-audit
|
||||
chown secubox-waf:secubox-waf /var/log/secubox/cookie-audit
|
||||
chmod 0750 /var/log/secubox/cookie-audit
|
||||
# The ledger FILE must be writable by the non-root sbxwaf that appends to it.
|
||||
# A pre-existing file owned by another user (e.g. secubox from an old addon)
|
||||
# makes sbxwaf panic on open → crash-loop (#896 reconcile). Chown if present.
|
||||
if [ -e /var/log/secubox/cookie-audit/server.jsonl ]; then
|
||||
chown secubox-waf:secubox-waf /var/log/secubox/cookie-audit/server.jsonl || true
|
||||
fi
|
||||
|
||||
# haproxy-routes.json must be READABLE by the non-root sbxwaf (#896 reconcile):
|
||||
# root-only 0600 makes it load 0 routes → 421 everything. Routes are
|
||||
# [host,port] pairs, not secret → 0644 (the norm; the profiles actuator
|
||||
# preserves this mode on its atomic writes).
|
||||
if [ -e /etc/secubox/waf/haproxy-routes.json ]; then
|
||||
chmod 0644 /etc/secubox/waf/haproxy-routes.json || true
|
||||
fi
|
||||
|
||||
# ── 4. Cache directories ──────────────────────────────────────────────────
|
||||
# Same shared-parent pattern: /var/cache/secubox must stay 0755.
|
||||
|
|
@ -81,32 +95,32 @@ case "$1" in
|
|||
aa-enforce /etc/apparmor.d/usr.sbin.sbxwaf 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ── 6. Systemd: enable and start workers ─────────────────────────────────
|
||||
# ── 6. Systemd: single hardened unit (#896 reconcile) ────────────────────
|
||||
# The crash-looping worker@ fan-out is retired. Retire any leftover worker@
|
||||
# instances, drop the hand-created /etc override that shadowed the packaged
|
||||
# unit, then enable + (re)start the single hardened secubox-waf-ng.service.
|
||||
if [ -d /run/systemd/system ]; then
|
||||
# Disable + stop any surviving worker@ instances (they re-chowned the
|
||||
# shared /run/secubox and broke sibling sockets — see the unit header).
|
||||
for unit in secubox-waf-ng-worker@1.service secubox-waf-ng-worker@2.service; do
|
||||
systemctl disable --now "$unit" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# A previous cutover left a hand-created override at
|
||||
# /etc/systemd/system/secubox-waf-ng.service that shadows this packaged
|
||||
# /lib unit. Remove it so the packaged (hardened) unit takes effect. Only
|
||||
# a plain file (not a symlink an operator may have set deliberately).
|
||||
if [ -f /etc/systemd/system/secubox-waf-ng.service ] && \
|
||||
[ ! -L /etc/systemd/system/secubox-waf-ng.service ]; then
|
||||
rm -f /etc/systemd/system/secubox-waf-ng.service
|
||||
fi
|
||||
|
||||
systemctl daemon-reload || true
|
||||
|
||||
# Enable + start both worker instances.
|
||||
# Guard: only when dpkg is not in --simulate / no-systemd build env.
|
||||
for instance in 1 2; do
|
||||
unit="secubox-waf-ng-worker@${instance}.service"
|
||||
systemctl enable "$unit" 2>/dev/null || true
|
||||
systemctl start "$unit" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
|
||||
# ── 7. On UPGRADE: restart any running enabled workers ───────────────────
|
||||
# dpkg upgrade SIGTERMs the units BEFORE this postinst runs, so
|
||||
# `try-restart` (a no-op on a stopped unit) would leave workers DEAD.
|
||||
# Mirror the toolbox postinst fix: ENABLED units get a full `restart`.
|
||||
if [ -n "${2:-}" ] && [ -d /run/systemd/system ]; then
|
||||
for unit in secubox-waf-ng-worker@1.service \
|
||||
secubox-waf-ng-worker@2.service; do
|
||||
if systemctl is-enabled --quiet "$unit" 2>/dev/null; then
|
||||
systemctl restart "$unit" 2>/dev/null || true
|
||||
else
|
||||
systemctl try-restart "$unit" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
systemctl enable secubox-waf-ng.service 2>/dev/null || true
|
||||
# Fresh install starts it; upgrade restarts it (dpkg SIGTERMs the unit
|
||||
# before this postinst runs, so `restart` — not `try-restart` — is needed
|
||||
# to bring it back). HAProxy srv0 → 127.0.0.1:8085 keeps pointing at it.
|
||||
systemctl restart secubox-waf-ng.service 2>/dev/null || true
|
||||
fi
|
||||
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -52,16 +52,14 @@ override_dh_auto_clean:
|
|||
rm -rf _gocache _gopath
|
||||
|
||||
override_dh_installsystemd:
|
||||
# Install the worker template unit manually (dh_installsystemd expects the
|
||||
# unit file at debian/<package>.<unitname>.service or debian/<unitname>.service;
|
||||
# ours lives in systemd/ so we install it explicitly, mirroring the toolbox
|
||||
# package pattern for override_dh_installsystemd).
|
||||
# #896 reconcile: ship the SINGLE hardened unit (secubox-waf-ng.service,
|
||||
# :8085) — it replaces the crash-looping worker@ fan-out template, which is
|
||||
# NO LONGER installed (a reinstall must not resurrect the re-chown landmine;
|
||||
# the postinst disables + removes any leftover worker@ units on the board).
|
||||
install -d debian/secubox-waf-ng/lib/systemd/system
|
||||
install -m 0644 systemd/secubox-waf-ng-worker@.service \
|
||||
install -m 0644 systemd/secubox-waf-ng.service \
|
||||
debian/secubox-waf-ng/lib/systemd/system/
|
||||
# Let dh_installsystemd generate the enable helpers for @1 and @2.
|
||||
# The postinst's `systemctl enable --now` handles activation.
|
||||
dh_installsystemd --name=secubox-waf-ng-worker@
|
||||
dh_installsystemd --name=secubox-waf-ng
|
||||
|
||||
execute_after_dh_auto_install:
|
||||
install -d debian/secubox-waf-ng/usr/sbin
|
||||
|
|
|
|||
92
packages/secubox-waf-ng/systemd/secubox-waf-ng.service
Normal file
92
packages/secubox-waf-ng/systemd/secubox-waf-ng.service
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# SecuBox-Deb :: secubox-waf-ng — Go WAF single reverse-proxy unit (#896 reconcile)
|
||||
#
|
||||
# Reconcile of the WAF-ng drift (2026-07-21): the board ran a HAND-CREATED,
|
||||
# UNHARDENED root unit on :8085 (HAProxy srv0 -> 127.0.0.1:8085) while the
|
||||
# package shipped only a worker@ fan-out template that crash-looped (it panicked
|
||||
# on the cookie-audit log — writable only by root — and, running as secubox-waf
|
||||
# with RuntimeDirectory=secubox, each crash-restart re-chowned the SHARED
|
||||
# /run/secubox, breaking every secubox-user service's socket bind). This single
|
||||
# unit REPLACES both: one hardened, non-root sbxwaf on :8085, reproducible from
|
||||
# source. See docs / issue #896.
|
||||
#
|
||||
# KEY DIFFERENCES from the (retired) worker@ template:
|
||||
# * single instance on :8085 (matches the live HAProxy backend), NOT an
|
||||
# nft-DNAT fan-out to :8081/:8082 (that never actually ran);
|
||||
# * single --vhost-signals file (secubox-sleeper's reader falls back to the
|
||||
# legacy non-@ path);
|
||||
# * NO RuntimeDirectory=secubox — sbxwaf only CONNECTS to the waker socket
|
||||
# (/run/secubox/waker.sock) as a client; it creates nothing under
|
||||
# /run/secubox, so it must not take ownership of the shared dir. /run/secubox
|
||||
# stays in ReadWritePaths so the client connect works under ProtectSystem.
|
||||
#
|
||||
# PREREQUISITES the postinst asserts (so a non-root sbxwaf can read them):
|
||||
# * /etc/secubox/waf/haproxy-routes.json readable (0644 — [host,port] pairs,
|
||||
# not secret); root-only 0600 makes sbxwaf load 0 routes -> 421 everything;
|
||||
# * /var/log/secubox/cookie-audit/server.jsonl writable by secubox-waf.
|
||||
|
||||
[Unit]
|
||||
Description=SecuBox WAF-NG Go reverse-proxy (single instance, port 8085)
|
||||
Documentation=https://github.com/CyberMind-FR/secubox-deb/issues/896
|
||||
After=network.target
|
||||
ConditionPathExists=/usr/sbin/sbxwaf
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=secubox-waf
|
||||
Group=secubox-waf
|
||||
|
||||
ExecStart=/usr/sbin/sbxwaf \
|
||||
--listen 127.0.0.1:8085 \
|
||||
--routes /etc/secubox/waf/haproxy-routes.json \
|
||||
--rules /etc/secubox/waf/waf-rules.json \
|
||||
--on-demand-vhosts /etc/secubox/waf/on-demand-vhosts.json \
|
||||
--vhost-signals /var/cache/secubox/waf/vhost-signals.json \
|
||||
--threat-log /var/log/secubox/waf/waf-threats.log \
|
||||
--cookie-audit-log /var/log/secubox/cookie-audit/server.jsonl \
|
||||
--media-cache-dir /var/cache/secubox/waf/media \
|
||||
--crowdsec-url http://10.100.0.1:8080
|
||||
|
||||
# --crowdsec-url is the real CrowdSec LAPI (LXC-bridge gateway 10.100.0.1:8080),
|
||||
# NOT 127.0.0.1:8080 (the nft DNAT VIP that would self-loop into this proxy).
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
# ── Hardening ─────────────────────────────────────────────────────────────────
|
||||
# Same posture as the worker@ template (secubox-mesh convention, §6 CSPN), minus
|
||||
# RuntimeDirectory (see header). /run/secubox is READ-WRITE so the client connect
|
||||
# to waker.sock works under ProtectSystem=strict.
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
ReadOnlyPaths=/etc/secubox
|
||||
ReadWritePaths=/var/log/secubox/waf /var/log/secubox/cookie-audit /var/cache/secubox/waf /run/secubox
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictNamespaces=yes
|
||||
LockPersonality=yes
|
||||
RestrictSUIDSGID=yes
|
||||
RestrictRealtime=yes
|
||||
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallFilter=~@privileged @resources
|
||||
MemoryDenyWriteExecute=yes
|
||||
|
||||
MemoryHigh=256M
|
||||
MemoryMax=384M
|
||||
TasksMax=512
|
||||
RuntimeMaxSec=12h
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Reference in New Issue
Block a user