secubox-deb/packages/secubox-waf-ng/systemd/secubox-waf-ng.service
CyberMind-FR bd8e2e382e
Some checks failed
License Headers / check (push) Has been cancelled
feat(waf-ng): reconcile to a single HARDENED non-root unit; retire worker@ fan-out (ref #896)
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>
2026-07-21 07:28:46 +02:00

93 lines
3.7 KiB
Desktop File

# 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