fix(deploy): script-sandbox helper — UID drop via systemd-run, --unshare-user-try, /etc/alternatives
Smoke testing on the test host revealed three issues with the helper as shipped: 1. bwrap 0.11+ rejects --uid without --unshare-user. Switching the UID drop from inside bwrap to systemd-run (--uid=l4d2-sandbox --gid=l4d2-sandbox) sidesteps the userns UID-mapping headaches and keeps file ownership on the bind-mounted /overlay matching l4d2-sandbox on the host (which the wipe path relies on). 2. bwrap running as an unprivileged uid still needs a user namespace to set up its mount-namespace bind-mounts. Adding --unshare-user-try gives it the userns context when needed and is a no-op otherwise. 3. /etc/alternatives wasn't bind-mounted, so symlinked tools like /usr/bin/awk -> /etc/alternatives/awk fell over inside the sandbox. Adds the ro-bind. Also: the helper now chowns the overlay dir to l4d2-sandbox before bwrap (idempotent — needed because the web app creates the dir as left4me), and the deploy script chmods /var/lib/left4me to 0711 so l4d2-sandbox can traverse to the bind-mount source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1e62a44c16
commit
06ae84fbe4
3 changed files with 26 additions and 7 deletions
|
|
@ -113,6 +113,12 @@ $sudo_cmd chown left4me:left4me \
|
||||||
/var/lib/left4me/runtime \
|
/var/lib/left4me/runtime \
|
||||||
/var/lib/left4me/workshop_cache \
|
/var/lib/left4me/workshop_cache \
|
||||||
/var/lib/left4me/tmp
|
/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
|
$sudo_cmd chown -R left4me:left4me /opt/left4me
|
||||||
|
|
||||||
mkdir -p "$repo_tmp"
|
mkdir -p "$repo_tmp"
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
[[ -d $OVERLAY_DIR ]] || { echo "no overlay dir at $OVERLAY_DIR" >&2; exit 65; }
|
||||||
[[ -f $SCRIPT ]] || { echo "no script at $SCRIPT" >&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
|
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
|
exit 0
|
||||||
fi
|
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 \
|
exec systemd-run --quiet --scope --collect \
|
||||||
|
--uid=l4d2-sandbox --gid=l4d2-sandbox \
|
||||||
-p MemoryMax=4G -p MemorySwapMax=0 -p TasksMax=512 \
|
-p MemoryMax=4G -p MemorySwapMax=0 -p TasksMax=512 \
|
||||||
-p CPUQuota=200% -p RuntimeMaxSec=3600 \
|
-p CPUQuota=200% -p RuntimeMaxSec=3600 \
|
||||||
-- bwrap \
|
-- bwrap \
|
||||||
--die-with-parent --new-session \
|
--die-with-parent --new-session \
|
||||||
|
--unshare-user-try \
|
||||||
--unshare-pid --unshare-ipc --unshare-uts --unshare-cgroup \
|
--unshare-pid --unshare-ipc --unshare-uts --unshare-cgroup \
|
||||||
--uid "$SBX_UID" --gid "$SBX_GID" \
|
|
||||||
--proc /proc --dev /dev --tmpfs /tmp --tmpfs /run \
|
--proc /proc --dev /dev --tmpfs /tmp --tmpfs /run \
|
||||||
--ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 \
|
--ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 \
|
||||||
--symlink usr/bin /bin --symlink usr/sbin /sbin \
|
--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/ssl /etc/ssl \
|
||||||
--ro-bind /etc/ca-certificates /etc/ca-certificates \
|
--ro-bind /etc/ca-certificates /etc/ca-certificates \
|
||||||
--ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \
|
--ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \
|
||||||
|
--ro-bind /etc/alternatives /etc/alternatives \
|
||||||
--bind "$OVERLAY_DIR" /overlay \
|
--bind "$OVERLAY_DIR" /overlay \
|
||||||
--chdir /overlay \
|
--chdir /overlay \
|
||||||
--setenv HOME /tmp --setenv PATH /usr/bin:/usr/sbin \
|
--setenv HOME /tmp --setenv PATH /usr/bin:/usr/sbin \
|
||||||
|
|
|
||||||
|
|
@ -327,8 +327,11 @@ def test_script_sandbox_helper_invokes_systemd_run_and_bwrap():
|
||||||
assert "bwrap" in text
|
assert "bwrap" in text
|
||||||
assert "--unshare-pid" in text
|
assert "--unshare-pid" in text
|
||||||
assert "--unshare-net" not in text, "scripts must keep host network access"
|
assert "--unshare-net" not in text, "scripts must keep host network access"
|
||||||
assert 'id -u l4d2-sandbox' in text
|
# UID drop happens at systemd-run, not inside bwrap (modern bwrap requires
|
||||||
assert 'id -g l4d2-sandbox' in text
|
# --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():
|
def test_script_sandbox_helper_validates_overlay_id():
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue