diff --git a/deploy/deploy-test-server.sh b/deploy/deploy-test-server.sh index 299cd41..d49d336 100755 --- a/deploy/deploy-test-server.sh +++ b/deploy/deploy-test-server.sh @@ -113,6 +113,12 @@ $sudo_cmd chown left4me:left4me \ /var/lib/left4me/runtime \ /var/lib/left4me/workshop_cache \ /var/lib/left4me/tmp + +# /var/lib/left4me is left4me's home dir (mode 0700 from useradd --create-home). +# Allow other uids (notably l4d2-sandbox, used by script overlay builds) to +# traverse — but not list — so the bwrap bind-mount can resolve the overlay +# path under the dropped privilege. +$sudo_cmd chmod 0711 /var/lib/left4me $sudo_cmd chown -R left4me:left4me /opt/left4me mkdir -p "$repo_tmp" diff --git a/deploy/files/usr/local/libexec/left4me/left4me-script-sandbox b/deploy/files/usr/local/libexec/left4me/left4me-script-sandbox index 625f026..d317b5b 100755 --- a/deploy/files/usr/local/libexec/left4me/left4me-script-sandbox +++ b/deploy/files/usr/local/libexec/left4me/left4me-script-sandbox @@ -25,21 +25,30 @@ OVERLAY_DIR=/var/lib/left4me/overlays/$OVERLAY_ID [[ -d $OVERLAY_DIR ]] || { echo "no overlay dir at $OVERLAY_DIR" >&2; exit 65; } [[ -f $SCRIPT ]] || { echo "no script at $SCRIPT" >&2; exit 65; } -SBX_UID=$(id -u l4d2-sandbox) -SBX_GID=$(id -g l4d2-sandbox) - if [[ "${LEFT4ME_SCRIPT_SANDBOX_DRY_RUN:-}" == "1" ]]; then - echo "DRY RUN: overlay_id=$OVERLAY_ID script=$SCRIPT uid=$SBX_UID gid=$SBX_GID overlay_dir=$OVERLAY_DIR" + echo "DRY RUN: overlay_id=$OVERLAY_ID script=$SCRIPT overlay_dir=$OVERLAY_DIR" exit 0 fi +# Make sure the sandbox UID owns the overlay dir so the script can write there. +# Idempotent: a no-op when the dir is already l4d2-sandbox-owned (re-run case), +# and corrects the ownership the first time the dir was created by the web app +# under the left4me UID. Group-readable so the gameserver process (left4me) +# can read the overlay contents via the kernel-overlayfs lowerdir at runtime. +chown -R l4d2-sandbox:l4d2-sandbox "$OVERLAY_DIR" +chmod 0755 "$OVERLAY_DIR" + +# UID/GID drop happens via systemd-run --uid/--gid before bwrap is invoked. +# bwrap then runs unprivileged as l4d2-sandbox; --unshare-user-try gives it +# the user-namespace context it needs for bind-mounts as a regular user. exec systemd-run --quiet --scope --collect \ + --uid=l4d2-sandbox --gid=l4d2-sandbox \ -p MemoryMax=4G -p MemorySwapMax=0 -p TasksMax=512 \ -p CPUQuota=200% -p RuntimeMaxSec=3600 \ -- bwrap \ --die-with-parent --new-session \ + --unshare-user-try \ --unshare-pid --unshare-ipc --unshare-uts --unshare-cgroup \ - --uid "$SBX_UID" --gid "$SBX_GID" \ --proc /proc --dev /dev --tmpfs /tmp --tmpfs /run \ --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 \ --symlink usr/bin /bin --symlink usr/sbin /sbin \ @@ -47,6 +56,7 @@ exec systemd-run --quiet --scope --collect \ --ro-bind /etc/ssl /etc/ssl \ --ro-bind /etc/ca-certificates /etc/ca-certificates \ --ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \ + --ro-bind /etc/alternatives /etc/alternatives \ --bind "$OVERLAY_DIR" /overlay \ --chdir /overlay \ --setenv HOME /tmp --setenv PATH /usr/bin:/usr/sbin \ diff --git a/deploy/tests/test_deploy_artifacts.py b/deploy/tests/test_deploy_artifacts.py index 2fa9d73..0601fa8 100644 --- a/deploy/tests/test_deploy_artifacts.py +++ b/deploy/tests/test_deploy_artifacts.py @@ -327,8 +327,11 @@ def test_script_sandbox_helper_invokes_systemd_run_and_bwrap(): assert "bwrap" in text assert "--unshare-pid" in text assert "--unshare-net" not in text, "scripts must keep host network access" - assert 'id -u l4d2-sandbox' in text - assert 'id -g l4d2-sandbox' in text + # UID drop happens at systemd-run, not inside bwrap (modern bwrap requires + # --unshare-user for --uid; doing the drop earlier keeps file ownership + # straight on the host bind-mount). + assert "--uid=l4d2-sandbox" in text + assert "--gid=l4d2-sandbox" in text def test_script_sandbox_helper_validates_overlay_id():