import os import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[2] DEPLOY = ROOT / "deploy" WEB_UNIT = DEPLOY / "files/usr/local/lib/systemd/system/left4me-web.service" SERVER_UNIT = DEPLOY / "files/usr/local/lib/systemd/system/left4me-server@.service" GLOBAL_REFRESH_SERVICE = DEPLOY / "files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.service" GLOBAL_REFRESH_TIMER = DEPLOY / "files/usr/local/lib/systemd/system/left4me-refresh-global-overlays.timer" SANDBOX_UNIT_DIR = DEPLOY / "files/usr/local/lib/systemd/system" SYSTEMCTL_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-systemctl" JOURNALCTL_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-journalctl" OVERLAY_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-overlay" SCRIPT_SANDBOX_HELPER = DEPLOY / "files/usr/local/libexec/left4me/left4me-script-sandbox" SANDBOX_RESOLV_CONF = DEPLOY / "files/etc/left4me/sandbox-resolv.conf" SUDOERS = DEPLOY / "files/etc/sudoers.d/left4me" HOST_ENV = DEPLOY / "templates/etc/left4me/host.env" WEB_ENV_TEMPLATE = DEPLOY / "templates/etc/left4me/web.env.template" DEPLOY_SCRIPT = DEPLOY / "deploy-test-server.sh" def test_global_unit_files_exist_at_product_level_paths(): assert WEB_UNIT.is_file() assert SERVER_UNIT.is_file() def test_web_unit_contains_required_runtime_contract(): unit = WEB_UNIT.read_text() assert "User=left4me" in unit assert "Group=left4me" in unit assert "WorkingDirectory=/opt/left4me" in unit assert "Environment=PATH=/opt/left4me/.venv/bin:" in unit assert "EnvironmentFile=/etc/left4me/host.env" in unit assert "EnvironmentFile=/etc/left4me/web.env" in unit assert "ExecStart=/opt/left4me/.venv/bin/gunicorn" in unit assert "--workers 1" 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 # Restored now that fuse-overlayfs propagation is no longer the mechanism. assert "PrivateTmp=true" in unit assert "ProtectSystem=full" in unit assert "ReadWritePaths=/var/lib/left4me" 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(): unit = SERVER_UNIT.read_text() assert "User=left4me" in unit assert "Group=left4me" in unit assert "EnvironmentFile=/etc/left4me/host.env" in unit assert "EnvironmentFile=/var/lib/left4me/instances/%i/instance.env" in unit assert "WorkingDirectory=/var/lib/left4me/runtime/%i/merged/left4dead2" in unit assert "ExecStart=/var/lib/left4me/installation/srcds_run" in unit assert "$L4D2_ARGS" in unit assert "${L4D2_ARGS}" not in unit assert "NoNewPrivileges=true" in unit assert "PrivateTmp=true" in unit assert "PrivateDevices=true" in unit assert "ProtectHome=true" in unit assert "ProtectSystem=strict" in unit assert "ReadOnlyPaths=/var/lib/left4me/installation /var/lib/left4me/overlays" in unit assert "ReadWritePaths=/var/lib/left4me/runtime/%i" in unit assert "RestrictSUIDSGID=true" in unit assert "LockPersonality=true" in unit def _fake_command(tmp_path, command_name): marker = tmp_path / f"{command_name}.args" command = tmp_path / command_name command.write_text(f"#!/bin/sh\nprintf '%s\n' \"$*\" > '{marker}'\nexit 0\n") command.chmod(0o755) return marker def _env_with_fake_commands(tmp_path): env = os.environ.copy() env["PATH"] = f"{tmp_path}{os.pathsep}{env.get('PATH', '')}" return env def test_helpers_use_fixed_system_tool_paths_not_sudo_path(): systemctl = SYSTEMCTL_HELPER.read_text() journalctl = JOURNALCTL_HELPER.read_text() assert "command -v systemctl" not in systemctl assert "command -v journalctl" not in journalctl assert "/bin/systemctl" in systemctl or "/usr/bin/systemctl" in systemctl assert "/bin/journalctl" in journalctl or "/usr/bin/journalctl" in journalctl def test_systemctl_helper_passes_shell_syntax_check_and_rejects_bad_args(tmp_path): subprocess.run(["sh", "-n", str(SYSTEMCTL_HELPER)], check=True) marker = _fake_command(tmp_path, "systemctl") for args in [ ["bad/action", "alpha"], ["start", ""], ["start", ".hidden"], ["start", "bad..name"], ["start", "bad/name"], ["start", "bad\\name"], ["start", "bad name"], ]: result = subprocess.run(["sh", str(SYSTEMCTL_HELPER), *args], env=_env_with_fake_commands(tmp_path), check=False) assert result.returncode != 0 assert not marker.exists() script = SYSTEMCTL_HELPER.read_text() assert 'unit="left4me-server@${name}.service"' in script assert 'start) exec "$systemctl" start "$unit"' in script assert 'stop) exec "$systemctl" stop "$unit"' in script assert "--property=ActiveState" in script assert "--property=SubState" in script def test_journalctl_helper_passes_shell_syntax_check_and_rejects_bad_args(tmp_path): subprocess.run(["sh", "-n", str(JOURNALCTL_HELPER)], check=True) marker = _fake_command(tmp_path, "journalctl") for args in [ ["../evil", "--lines", "25", "--no-follow"], ["alpha", "--bad", "25", "--no-follow"], ["alpha", "--lines", "not-number", "--no-follow"], ["alpha", "--lines", "25", "--bad-follow"], ["bad/name", "--lines", "25", "--no-follow"], ]: result = subprocess.run(["sh", str(JOURNALCTL_HELPER), *args], env=_env_with_fake_commands(tmp_path), check=False) assert result.returncode != 0 assert not marker.exists() script = JOURNALCTL_HELPER.read_text() assert 'unit="left4me-server@${name}.service"' in script assert 'exec "$journalctl" -u "$unit" -n "$lines" -o cat "$follow_arg"' in script assert 'exec "$journalctl" -u "$unit" -n "$lines" -o cat' in script def test_sudoers_allows_only_left4me_helpers_not_raw_system_tools(): sudoers = SUDOERS.read_text() assert ( "left4me ALL=(root) NOPASSWD: " "/usr/local/libexec/left4me/left4me-systemctl *" ) in sudoers assert ( "left4me ALL=(root) NOPASSWD: " "/usr/local/libexec/left4me/left4me-journalctl *" ) in sudoers assert "/usr/local/libexec/left4me/left4me-overlay mount *" in sudoers assert "/usr/local/libexec/left4me/left4me-overlay umount *" in sudoers assert ( "left4me ALL=(root) NOPASSWD: " "/usr/local/libexec/left4me/left4me-script-sandbox" ) in sudoers assert "/bin/systemctl" not in sudoers assert "/usr/bin/systemctl" not in sudoers assert "/bin/journalctl" not in sudoers assert "/usr/bin/journalctl" not in sudoers assert "/bin/mount" not in sudoers assert "/bin/umount" not in sudoers def test_overlay_helper_is_python_with_strict_validation(): text = OVERLAY_HELPER.read_text() assert text.startswith("#!/usr/bin/python3") # Validation surface assert "NAME_RE = re.compile" in text assert "LOWERDIR_ALLOWLIST" in text assert "user.fuseoverlayfs." in text assert "MAX_LOWERDIRS = 500" in text # Mounts via PID 1's mount namespace assert "/proc/1/ns/mnt" in text assert "nsenter" in text # Verbs are mount and umount (not unmount) assert '"mount"' in text and '"umount"' in text assert '"unmount"' not in text def test_deploy_script_installs_overlay_helper_with_executable_mode(): script = DEPLOY_SCRIPT.read_text() assert "/usr/local/libexec/left4me/left4me-overlay" in script assert "chmod 0755" in script and "left4me-overlay" in script def test_deploy_script_does_not_install_fuse_overlayfs_apt_dep(): # fuse-overlayfs / fuse3 were the previous mount engine; kernel overlayfs # replaces them. Comments in the migration block may legitimately mention # the names, so scope this to the actual apt-get / dnf install lines. install_lines = [ line for line in DEPLOY_SCRIPT.read_text().splitlines() if ("apt-get install" in line or "dnf install" in line) ] assert install_lines, "expected at least one apt/dnf install line" for line in install_lines: assert "fuse-overlayfs" not in line, line assert "fuse3" not in line, line def test_deploy_script_runs_one_shot_kernel_overlay_migration(): script = DEPLOY_SCRIPT.read_text() assert "/var/lib/left4me/.kernel-overlay-migrated" in script # Migration should stop services + force-unmount stale mounts + wipe upper/work assert "systemctl stop 'left4me-server@" in script assert "systemctl stop left4me-web.service" in script assert "findmnt -t overlay" in script assert "/runtime/" in script and "rm -rf" in script and 'upper"' in script and 'work"' in script def test_env_templates_contain_required_defaults(): host_env = HOST_ENV.read_text() assert "Deployment units use fixed /var/lib/left4me paths" in host_env assert host_env.endswith("LEFT4ME_ROOT=/var/lib/left4me\n") assert WEB_ENV_TEMPLATE.read_text() == ( "DATABASE_URL=sqlite:////var/lib/left4me/left4me.db\n" "SECRET_KEY=replace-with-generated-secret\n" "JOB_WORKER_THREADS=4\n" ) def test_deploy_script_has_safe_defaults_and_preserves_state() -> None: script = DEPLOY_SCRIPT.read_text() assert "useradd --system --home-dir /var/lib/left4me" in script assert "/var/lib/left4me/installation" in script assert "/var/lib/left4me/overlays" in script assert "/var/lib/left4me/instances" in script assert "/var/lib/left4me/runtime" in script assert "tar" in script assert "--exclude .venv" in script assert "--exclude .claude" in script assert "pip install -e /opt/left4me/l4d2host -e /opt/left4me/l4d2web" in script assert "systemctl enable --now left4me-web.service" in script assert "for attempt in" in script assert "/opt/left4me/.venv" in script assert "visudo -cf /etc/sudoers.d/left4me" in script assert "if [ ! -f /etc/left4me/web.env ]" in script assert ". /etc/left4me/web.env\n" not in script assert "run_left4me_with_env" in script assert "LEFT4ME_ADMIN_USERNAME" in script assert "LEFT4ME_ADMIN_PASSWORD" in script assert "user already exists" in script assert "deploy/files" in script def test_deploy_script_does_not_recurse_into_runtime_state_mounts() -> None: script = DEPLOY_SCRIPT.read_text() assert "$sudo_cmd chown -R left4me:left4me /var/lib/left4me" not in script assert "$sudo_cmd chown left4me:left4me \\" in script assert "/var/lib/left4me/runtime \\" in script assert "$sudo_cmd chown -R left4me:left4me /opt/left4me" in script def test_deploy_script_runs_migrations_before_app_initialization() -> None: script = DEPLOY_SCRIPT.read_text() assert "alembic -c /opt/left4me/l4d2web/alembic.ini upgrade head" in script assert "from l4d2web.app import create_app; create_app()" not in script def test_deploy_script_shell_syntax() -> None: subprocess.run(["sh", "-n", str(DEPLOY_SCRIPT)], check=True) def test_globals_refresh_units_removed(): """Global-overlays subsystem deleted in favor of script overlays.""" assert not GLOBAL_REFRESH_SERVICE.exists() assert not GLOBAL_REFRESH_TIMER.exists() def test_deploy_script_does_not_provision_globals_subsystem(): script = DEPLOY_SCRIPT.read_text() # No mkdir/install of the deleted cache dir; mention in a one-shot # `rm -rf` cleanup is fine. for line in script.splitlines(): if "/var/lib/left4me/global_overlay_cache" not in line: continue assert "rm -rf" in line, line assert "left4me-refresh-global-overlays" not in script def test_deploy_script_provisions_sandbox_user(): script = DEPLOY_SCRIPT.read_text() assert "useradd --system --no-create-home --shell /usr/sbin/nologin l4d2-sandbox" in script def test_deploy_script_does_not_install_bubblewrap(): install_lines = [ line for line in DEPLOY_SCRIPT.read_text().splitlines() if ("apt-get install" in line or "dnf install" in line) ] assert install_lines, "expected at least one apt/dnf install line" for line in install_lines: assert "bubblewrap" not in line, line assert "bwrap" not in line, line def test_deploy_script_installs_script_overlay_tooling(): # Script overlays commonly need 7z and md5sum (e.g. l4d2center map sync). # coreutils ships md5sum and is technically essential, but listing it # explicitly makes the contract obvious and survives slim base images. script = DEPLOY_SCRIPT.read_text().splitlines() apt_lines = [l for l in script if "apt-get install" in l] dnf_lines = [l for l in script if "dnf install" in l] assert apt_lines, "expected an apt-get install line" assert dnf_lines, "expected a dnf install line" for line in apt_lines: assert "p7zip-full" in line, line assert "coreutils" in line, line for line in dnf_lines: # Fedora/RHEL split: p7zip provides 7za, p7zip-plugins provides 7z. assert "p7zip" in line and "p7zip-plugins" in line, line assert "coreutils" in line, line def test_deploy_script_tightens_left4me_db_permissions(): script = DEPLOY_SCRIPT.read_text() # The DB and its WAL/SHM sidecars must be left4me:left4me 0640 — owner # (web service) keeps rw, group is read-only, "other" (incl. l4d2-sandbox) # gets nothing. The sidecars matter because SQLite in WAL mode requires # write access to all three; if a sidecar ends up root-owned (e.g. from # ad-hoc root-side inspection), the next write fails as "readonly db". assert "chown left4me:left4me" in script assert "chmod 0640" in script for db_file in ( "/var/lib/left4me/left4me.db", "/var/lib/left4me/left4me.db-wal", "/var/lib/left4me/left4me.db-shm", ): assert db_file in script, f"deploy script must touch {db_file}" def test_deploy_script_installs_script_sandbox_helper(): script = DEPLOY_SCRIPT.read_text() assert "/usr/local/libexec/left4me/left4me-script-sandbox" in script assert "chmod 0755" in script and "left4me-script-sandbox" in script def test_script_sandbox_helper_present(): assert SCRIPT_SANDBOX_HELPER.is_file() assert SCRIPT_SANDBOX_HELPER.read_text().startswith("#!/bin/bash") mode = SCRIPT_SANDBOX_HELPER.stat().st_mode & 0o777 assert mode == 0o755, f"expected 0755, got {oct(mode)}" def test_script_sandbox_helper_passes_shell_syntax_check(): subprocess.run(["bash", "-n", str(SCRIPT_SANDBOX_HELPER)], check=True) def test_script_sandbox_helper_invokes_systemd_run_with_hardening(): text = SCRIPT_SANDBOX_HELPER.read_text() # systemd-run service mode (no --scope), with synchronous I/O to caller. assert "systemd-run" in text assert "--scope" not in text, "v2 uses transient service units, not scopes" assert "--pipe" in text assert "--wait" in text assert "--collect" in text assert "--unit=" in text # No bwrap. assert "bwrap" not in text assert "bubblewrap" not in text # UID drop via systemd directives. assert "User=l4d2-sandbox" in text assert "Group=l4d2-sandbox" in text # Cgroup limits unchanged from v1. assert "MemoryMax=4G" in text assert "MemorySwapMax=0" in text assert "TasksMax=512" in text assert "CPUQuota=200%" in text assert "RuntimeMaxSec=3600" in text # Hardening directives that v1 (scope mode) couldn't carry. assert "NoNewPrivileges=yes" in text assert "ProtectSystem=strict" in text assert "ProtectHome=yes" in text assert "PrivateTmp=yes" in text assert "PrivateDevices=yes" in text assert "PrivateIPC=yes" in text assert "ProtectKernelTunables=yes" in text assert "ProtectKernelModules=yes" in text assert "ProtectKernelLogs=yes" in text assert "ProtectControlGroups=yes" in text assert "RestrictNamespaces=yes" in text assert "RestrictSUIDSGID=yes" in text assert "LockPersonality=yes" in text assert "MemoryDenyWriteExecute=yes" in text assert "SystemCallFilter=" in text assert "@system-service" in text assert "@network-io" in text assert "CapabilityBoundingSet=" in text assert "AmbientCapabilities=" in text assert 'RestrictAddressFamilies="AF_INET AF_INET6 AF_UNIX"' in text # Network namespace stays shared with host. assert "PrivateNetwork=" not in text # Mount setup: /etc and /var/lib masked with tmpfs; selective binds back. assert 'TemporaryFileSystem="/etc /var/lib"' in text assert "BindReadOnlyPaths=" in text # The resolv.conf bind points at the sandbox-only file (not the host's # /etc/resolv.conf, which typically references a private-IP DNS server # that IPAddressDeny= blocks). assert "/etc/left4me/sandbox-resolv.conf:/etc/resolv.conf" in text assert "/etc/ssl" in text assert "/etc/ca-certificates" in text assert "/etc/nsswitch.conf" in text assert "/etc/alternatives" in text assert "${SCRIPT}:/script.sh" in text assert 'BindPaths="${OVERLAY_DIR}:/overlay"' in text # IP egress filter: allow public, deny localhost / RFC1918 / link-local / # multicast / CGNAT / ULA. systemd's "more specific rule wins" semantics # mean public IPs hit the allow and listed ranges hit the deny. # IPAddressDeny alone — no IPAddressAllow=any. Empirically, having both # set causes the allow to win on this systemd/kernel combo regardless of # the documented "more specific rule wins" behaviour. With only Deny, # the kernel's default "allow all" applies to non-listed addresses. assert "IPAddressDeny=" in text assert "IPAddressAllow=any" not in text # Explicit CIDRs — systemd-run's -p parser doesn't accept the # `localhost` / `link-local` / `multicast` shorthand keywords that # work in unit files (only the full strings parse). for token in ( "127.0.0.0/8", "::1/128", "169.254.0.0/16", "fe80::/10", "224.0.0.0/4", "ff00::/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10", "fc00::/7", ): assert token in text, f"missing {token!r} in IPAddressDeny set" def test_sandbox_resolv_conf_exists(): assert SANDBOX_RESOLV_CONF.is_file() text = SANDBOX_RESOLV_CONF.read_text() nameservers = [ line.split()[1] for line in text.splitlines() if line.startswith("nameserver ") ] assert len(nameservers) >= 2, "expected at least two nameservers for redundancy" # Sanity: the resolvers must be public (not RFC1918 / loopback). We don't # pin the exact IPs — Cloudflare/Google/Quad9 are all acceptable. for ns in nameservers: assert not ns.startswith("127."), ns assert not ns.startswith("10."), ns assert not ns.startswith("192.168."), ns first_octet = int(ns.split(".")[0]) # Reject 172.16.0.0/12. if first_octet == 172: second_octet = int(ns.split(".")[1]) assert not (16 <= second_octet <= 31), ns def test_deploy_script_installs_sandbox_resolv_conf(): script = DEPLOY_SCRIPT.read_text() assert "deploy/files/etc/left4me/sandbox-resolv.conf" in script assert "/etc/left4me/sandbox-resolv.conf" in script def test_script_sandbox_helper_validates_overlay_id(): text = SCRIPT_SANDBOX_HELPER.read_text() # Numeric-only overlay id assert '[[ "$OVERLAY_ID" =~ ^[0-9]+$ ]]' in text # Overlay dir must exist assert "/var/lib/left4me/overlays/" in text assert "[[ -d $OVERLAY_DIR ]]" in text # Script path must exist assert "[[ -f $SCRIPT ]]" in text def test_script_sandbox_helper_dry_run_mode(tmp_path): overlay_root = tmp_path / "var/lib/left4me/overlays/42" overlay_root.mkdir(parents=True) fake_script = tmp_path / "fake.sh" fake_script.write_text("echo hi") # Run in DRY_RUN mode against a fake l4d2-sandbox UID via a tiny shim that # simulates `id -u l4d2-sandbox` resolving to a valid number. helper_text = SCRIPT_SANDBOX_HELPER.read_text() # We can't actually exec this without root + a real sandbox user; just # verify the dry-run guard short-circuits before systemd-run / bwrap. assert 'LEFT4ME_SCRIPT_SANDBOX_DRY_RUN' in helper_text assert 'exit 0' in helper_text