fix(deploy): ExecStartPre runs overlay helper with + prefix, not sudo

The unit has NoNewPrivileges=true (security hardening for srcds), which
blocks sudo's setuid escalation. The previous sudo'd ExecStartPre failed
on every start with "sudo: the 'no new privileges' switch is set, which
prevents sudo from running as root" -> Restart=on-failure loop.

systemd's `+` prefix runs the Exec command as PID 1 (root, no sandbox),
bypassing User=/Group=/NoNewPrivileges=. Equivalent privilege scope to
the sudoers rule the web app already uses for the same helper, just
without the sudo middleman.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-09 12:55:16 +02:00
parent 56f5c30296
commit a982995d5b
No known key found for this signature in database
2 changed files with 11 additions and 2 deletions

View file

@ -19,7 +19,13 @@ WorkingDirectory=-/var/lib/left4me/runtime/%i/merged/left4dead2
# start_instance only stages cfg files and asks systemd to enable+start # start_instance only stages cfg files and asks systemd to enable+start
# this unit; the actual `mount -t overlay` lives here so reboot auto-start # this unit; the actual `mount -t overlay` lives here so reboot auto-start
# works the same as a UI-driven start. Helper is idempotent. # works the same as a UI-driven start. Helper is idempotent.
ExecStartPre=/usr/bin/sudo -n /usr/local/libexec/left4me/left4me-overlay mount %i #
# `+` prefix runs the helper as PID 1 (root, no sandbox). Required because
# the unit has NoNewPrivileges=true, which blocks sudo's setuid escalation
# — and the helper itself needs root to nsenter into PID 1's mnt namespace
# anyway, so bypassing sandbox here is no looser than the sudoers rule
# the web app uses for its own helper invocations.
ExecStartPre=+/usr/local/libexec/left4me/left4me-overlay mount %i
ExecStart=/var/lib/left4me/installation/srcds_run -game left4dead2 +hostport ${L4D2_PORT} $L4D2_ARGS ExecStart=/var/lib/left4me/installation/srcds_run -game left4dead2 +hostport ${L4D2_PORT} $L4D2_ARGS
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec=5

View file

@ -88,8 +88,11 @@ def test_server_unit_mounts_overlay_via_exec_start_pre():
idempotency check (test_overlay_helper_mount_is_idempotent_when_mounted). idempotency check (test_overlay_helper_mount_is_idempotent_when_mounted).
""" """
unit = SERVER_UNIT.read_text() unit = SERVER_UNIT.read_text()
# `+` prefix: ExecStartPre runs as PID 1 (root, no sandbox). Required
# because the unit has NoNewPrivileges=true, which blocks sudo's setuid
# escalation — and the helper needs root for nsenter anyway.
assert ( assert (
"ExecStartPre=/usr/bin/sudo -n /usr/local/libexec/left4me/left4me-overlay mount %i" "ExecStartPre=+/usr/local/libexec/left4me/left4me-overlay mount %i"
in unit in unit
) )
# Bound the restart loop; without these, a CHDIR-failure (or any other # Bound the restart loop; without these, a CHDIR-failure (or any other