chore(deploy): cleanup left4me-web hardening + docs for kernel overlayfs
Drop MountFlags=shared (the assumption that it propagated fuse mounts
to host was incorrect on systemd 257 with ProtectSystem+ReadWritePaths).
Restore PrivateTmp=true (was dropped in 593611e for fuse propagation
that did not work). Rewrite the comment block to describe the new
model: mounts go through the left4me-overlay helper which nsenters
into PID 1's mount namespace, so the unit's mount-ns layout is no
longer load-bearing.
Update the three user-facing READMEs (root, l4d2host, deploy) to drop
fuse-overlayfs / fusermount3 prereqs and call out the kernel overlayfs
mount path through the privileged helper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
172e574a00
commit
9985ecc56c
5 changed files with 21 additions and 15 deletions
|
|
@ -56,7 +56,7 @@ See `deploy/README.md` for the Linux test deployment contract, including the run
|
||||||
- Typer, PyYAML, pytest
|
- Typer, PyYAML, pytest
|
||||||
- Flask, SQLAlchemy, Alembic
|
- Flask, SQLAlchemy, Alembic
|
||||||
- HTMX (vendored locally), custom CSS, SSE
|
- HTMX (vendored locally), custom CSS, SSE
|
||||||
- systemd user units, fuse-overlayfs, steamcmd
|
- systemd units, kernel overlayfs (mounted via the `left4me-overlay` privileged helper), steamcmd
|
||||||
|
|
||||||
## Recommended Implementation Order
|
## Recommended Implementation Order
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ The deployment uses these paths:
|
||||||
- `/var/lib/left4me/runtime`: per-instance runtime mount directories.
|
- `/var/lib/left4me/runtime`: per-instance runtime mount directories.
|
||||||
- `/var/lib/left4me/tmp`: temporary files used by deployment/runtime operations.
|
- `/var/lib/left4me/tmp`: temporary files used by deployment/runtime operations.
|
||||||
- `/usr/local/lib/systemd/system`: global systemd unit files, including `left4me-server@.service`.
|
- `/usr/local/lib/systemd/system`: global systemd unit files, including `left4me-server@.service`.
|
||||||
- `/usr/local/libexec/left4me`: privileged helper commands, including `left4me-systemctl` and `left4me-journalctl`.
|
- `/usr/local/libexec/left4me`: privileged helper commands, including `left4me-systemctl`, `left4me-journalctl`, and `left4me-overlay` (the latter mounts the per-instance kernel overlay in PID 1's mount namespace via `nsenter`).
|
||||||
- `/etc/sudoers.d/left4me`: sudoers rules allowing the web/runtime commands to call the helpers non-interactively.
|
- `/etc/sudoers.d/left4me`: sudoers rules allowing the web/runtime commands to call the helpers non-interactively.
|
||||||
|
|
||||||
Static units are generated for `/var/lib/left4me`. If `LEFT4ME_ROOT` changes, regenerate and reinstall the unit files instead of reusing the existing static units.
|
Static units are generated for `/var/lib/left4me`. If `LEFT4ME_ROOT` changes, regenerate and reinstall the unit files instead of reusing the existing static units.
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,17 @@ EnvironmentFile=/etc/left4me/web.env
|
||||||
ExecStart=/opt/left4me/.venv/bin/gunicorn --workers 1 --threads 32 --bind 0.0.0.0:8000 'l4d2web.app:create_app()'
|
ExecStart=/opt/left4me/.venv/bin/gunicorn --workers 1 --threads 32 --bind 0.0.0.0:8000 'l4d2web.app:create_app()'
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=3
|
RestartSec=3
|
||||||
# NoNewPrivileges intentionally not set: the worker invokes fusermount3
|
# NoNewPrivileges intentionally not set: the worker invokes sudo to run
|
||||||
# (setuid-root) and sudo to run the systemctl wrapper.
|
# the left4me-systemctl, left4me-journalctl, and left4me-overlay
|
||||||
# ProtectSystem=full + ReadWritePaths implicitly give this unit a
|
# privileged helpers, all setuid via sudo.
|
||||||
# private mount namespace. MountFlags=shared makes its mount events
|
# ProtectSystem=full + ReadWritePaths implicitly give this unit a private
|
||||||
# propagate back to the host so per-instance fuse-overlayfs mounts are
|
# mount namespace, but mount visibility no longer depends on it: overlay
|
||||||
# visible to the gameserver units (which inherit host mounts at their
|
# mounts are performed by the left4me-overlay helper, which nsenters into
|
||||||
# own unshare time). Without it, the per-instance mount only exists
|
# PID 1's mount namespace, so the resulting mount lives in the host
|
||||||
# inside the worker's namespace and the gameserver units fail CHDIR.
|
# namespace where the per-instance gameserver units can see it.
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
ReadWritePaths=/var/lib/left4me
|
ReadWritePaths=/var/lib/left4me
|
||||||
MountFlags=shared
|
PrivateTmp=true
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,17 @@ def test_web_unit_contains_required_runtime_contract():
|
||||||
assert "ExecStart=/opt/left4me/.venv/bin/gunicorn" in unit
|
assert "ExecStart=/opt/left4me/.venv/bin/gunicorn" in unit
|
||||||
assert "--workers 1" in unit
|
assert "--workers 1" in unit
|
||||||
assert "--threads 32" in unit
|
assert "--threads 32" in unit
|
||||||
|
# NoNewPrivileges must remain unset because sudo (used by the overlay,
|
||||||
|
# systemctl and journalctl helpers) is setuid.
|
||||||
assert "NoNewPrivileges=true" not in unit
|
assert "NoNewPrivileges=true" not in unit
|
||||||
assert "PrivateTmp=true" not in unit
|
# Restored now that fuse-overlayfs propagation is no longer the mechanism.
|
||||||
|
assert "PrivateTmp=true" in unit
|
||||||
assert "ProtectSystem=full" in unit
|
assert "ProtectSystem=full" in unit
|
||||||
assert "ReadWritePaths=/var/lib/left4me" in unit
|
assert "ReadWritePaths=/var/lib/left4me" in unit
|
||||||
assert "MountFlags=shared" in unit
|
# Mounts now happen in PID 1's namespace via the left4me-overlay helper,
|
||||||
|
# so MountFlags propagation is irrelevant — and the previous assumption
|
||||||
|
# that MountFlags=shared made it work was incorrect.
|
||||||
|
assert "MountFlags=" not in unit
|
||||||
|
|
||||||
|
|
||||||
def test_server_unit_contains_required_runtime_contract():
|
def test_server_unit_contains_required_runtime_contract():
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ Validated on Debian 13 during the `ckn@10.0.4.128` smoke test:
|
||||||
- Python 3.12+ with virtualenv/pip tooling for installing `l4d2host`.
|
- Python 3.12+ with virtualenv/pip tooling for installing `l4d2host`.
|
||||||
- `steamcmd` available on `PATH` and able to self-update as the runtime user.
|
- `steamcmd` available on `PATH` and able to self-update as the runtime user.
|
||||||
- 32-bit compatibility libraries for SteamCMD on amd64 Debian: `libc6-i386`, `lib32gcc-s1`, `lib32stdc++6`.
|
- 32-bit compatibility libraries for SteamCMD on amd64 Debian: `libc6-i386`, `lib32gcc-s1`, `lib32stdc++6`.
|
||||||
- `fuse-overlayfs` and `fusermount3` for per-instance overlay mounts.
|
- Kernel overlayfs (`mount -t overlay`); mount/umount go through the `left4me-overlay` privileged helper, which `nsenter`s into PID 1's mount namespace.
|
||||||
- `systemctl --user` and `journalctl --user` available for the runtime user.
|
- `systemctl --user` and `journalctl --user` available for the runtime user.
|
||||||
- User lingering enabled when services must survive SSH sessions: `sudo loginctl enable-linger <user>`.
|
- User lingering enabled when services must survive SSH sessions: `sudo loginctl enable-linger <user>`.
|
||||||
- `/var/lib/left4me` created and writable by the runtime user, unless `LEFT4ME_ROOT` is set to another deployment-managed root.
|
- `/var/lib/left4me` created and writable by the runtime user, unless `LEFT4ME_ROOT` is set to another deployment-managed root.
|
||||||
|
|
@ -61,7 +61,7 @@ sudo apt-get update
|
||||||
sudo apt-get install -y \
|
sudo apt-get install -y \
|
||||||
python3 python3-venv python3-pip \
|
python3 python3-venv python3-pip \
|
||||||
curl ca-certificates tar gzip \
|
curl ca-certificates tar gzip \
|
||||||
fuse-overlayfs fuse3 \
|
util-linux \
|
||||||
libc6-i386 lib32gcc-s1 lib32stdc++6
|
libc6-i386 lib32gcc-s1 lib32stdc++6
|
||||||
|
|
||||||
sudo mkdir -p /opt/steamcmd /var/lib/left4me/{installation,overlays,instances,runtime,tmp}
|
sudo mkdir -p /opt/steamcmd /var/lib/left4me/{installation,overlays,instances,runtime,tmp}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue