Let the manager run the helper through sudo

ProtectKernelTunables, ProtectKernelModules and LockPersonality imply
NoNewPrivileges for a non-root unit, so sudo refused to start the helper
(hostname, reboot, network changes all failed). Guarded by a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rothmanandClaude Opus 5.5 committed 2026-11-04 22:31:52 +01:00
1 parent e14e1f83ca
commit 1eabb839d3
2 files changed
+33 -3

No files matched your search

+3 -3
View File
@@ -22,10 +22,10 @@ SyslogIdentifier=voxip-kiosk-manager
# "sudo -n voxip-kiosk-helper" inherits this unit's mount namespace and must
# be able to write /etc (hostname, NetworkManager profiles). The web process
# itself runs unprivileged as user voxip and can only write its own dirs.
ProtectKernelTunables=yes
ProtectKernelModules=yes
# Also no ProtectKernelTunables/ProtectKernelModules/LockPersonality,
# RestrictSUIDSGID, SystemCall*, ...: for a non-root unit those imply
# NoNewPrivileges, and sudo then refuses to run (tests/test_units.py).
ProtectControlGroups=yes
LockPersonality=yes
[Install]
WantedBy=multi-user.target
+30
View File
@@ -0,0 +1,30 @@
from pathlib import Path
UNITS = Path(__file__).resolve().parent.parent / "systemd"
# Settings that make systemd set no_new_privs for a unit that does not run as root
# (systemd.exec(5)). sudo cannot gain root under that flag.
IMPLY_NO_NEW_PRIVILEGES = (
"NoNewPrivileges", "SystemCallFilter", "SystemCallArchitectures", "RestrictAddressFamilies",
"RestrictNamespaces", "PrivateDevices", "ProtectKernelTunables", "ProtectKernelModules",
"ProtectKernelLogs", "ProtectClock", "MemoryDenyWriteExecute", "RestrictRealtime",
"RestrictSUIDSGID", "DynamicUser", "LockPersonality",
)
def settings(unit):
result = {}
for line in (UNITS / unit).read_text().splitlines():
key, sep, value = line.partition("=")
if sep and not line.startswith("#"):
result[key.strip()] = value.strip()
return result
def test_manager_can_use_sudo_for_the_helper():
unit = settings("voxip-kiosk-manager.service")
assert unit["User"] == "voxip"
for key in IMPLY_NO_NEW_PRIVILEGES:
assert unit.get(key, "no") in ("no", "false", ""), key
for key in ("ProtectSystem", "ProtectHome", "PrivateTmp"):
assert key not in unit, key