#!/usr/bin/env bash # `boot --server` is deliberately public and must be safe before it reaches the # cloned repository. Exercise its root branch through a PTY, against only a # temporary filesystem and PATH adapters. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" boot="$repo_dir/boot" [[ -x "$boot" ]] || { printf 'root server bootstrap: %s is not executable\n' "$boot" >&2 exit 1 } python3 - "$boot" <<'PY' import atexit import errno import fcntl import os import pty import shutil import subprocess import sys import tempfile import termios from pathlib import Path boot = sys.argv[1] work = Path(tempfile.mkdtemp()) atexit.register(shutil.rmtree, work, ignore_errors=True) findings: list[str] = [] def note(message: str) -> None: findings.append(message) def write_executable(path: Path, contents: str) -> None: path.write_text(contents) path.chmod(0o755) def make_stubs(stub_dir: Path, fixture_root: Path, calls: Path) -> None: common = f'''#!/usr/bin/env bash set -u calls={str(calls)!r} log() {{ local argument {{ for argument in "$@"; do printf '%q ' "$argument"; done; printf '\\n'; }} >>"$calls" }} ''' write_executable(stub_dir / "id", common + r''' log id "$@" case "${1:-}" in -u) case "${2:-}" in '') printf '0\n' ;; root) printf '0\n' ;; gib) cat "$PANAMA_BOOT_FIXTURE_ROOT/state/target-uid" ;; *) exit 97 ;; esac ;; -nG) [[ "${2:-}" == gib ]] || exit 97; printf 'gib wheel\n' ;; *) exit 97 ;; esac ''') write_executable(stub_dir / "passwd", common + r''' log passwd "$@" [[ "${1:-}" == -S && "${2:-}" == gib ]] || exit 97 printf 'gib PS\n' ''') write_executable(stub_dir / "getent", common + r''' log getent "$@" [[ "${1:-}" == passwd && "${2:-}" == gib ]] || exit 97 home="$(<"$PANAMA_BOOT_FIXTURE_ROOT/state/home")" printf 'gib:x:1000:1000::%s:/bin/bash\n' "$home" ''') write_executable(stub_dir / "stat", common + r''' log stat "$@" [[ "${1:-}" == -Lc && "${2:-}" == '%u:%a' ]] || exit 97 case "${3:-}" in "$PANAMA_BOOT_FIXTURE_ROOT/home/gib/.ssh") cat "$PANAMA_BOOT_FIXTURE_ROOT/state/target-dir-meta" ;; "$PANAMA_BOOT_FIXTURE_ROOT/home/gib/.ssh/authorized_keys") cat "$PANAMA_BOOT_FIXTURE_ROOT/state/target-key-meta" ;; "$PANAMA_BOOT_FIXTURE_ROOT/root/.ssh/authorized_keys") cat "$PANAMA_BOOT_FIXTURE_ROOT/state/root-key-meta" ;; *) exit 97 ;; esac ''') write_executable(stub_dir / "runuser", common + r''' log runuser "$@" [[ "${1:-}" == -u && "${2:-}" == gib && "${3:-}" == -- ]] || exit 97 shift 3 "$@" ''') write_executable(stub_dir / "git", common + r''' log git "$@" case "${1:-}" in clone) mkdir -p "$3/.git" cp "$PANAMA_BOOT_FIXTURE_ROOT/stub-install" "$3/install" chmod +x "$3/install" ;; -C) [[ "${3:-}" == pull && "${4:-}" == --ff-only ]] || exit 97 ;; *) exit 97 ;; esac ''') write_executable(stub_dir / "dnf", common + r''' log dnf "$@" [[ "${1:-}" == install && "${2:-}" == -y && "${3:-}" == git ]] || exit 97 ''') write_executable(stub_dir / "sshd", common + r''' log sshd "$@" exit 97 ''') write_executable(stub_dir / "systemctl", common + r''' log systemctl "$@" case "${1:-}:${2:-}" in reload:sshd|reload:ssh) exit 0 ;; *) exit 97 ;; esac ''') for command in ("useradd", "usermod"): write_executable(stub_dir / command, common + f'''\nlog {command} "$@"\nexit 97\n''') def configure_case(name: str) -> tuple[Path, Path]: fixture_root = work / name / "root" stub_dir = work / name / "bin" calls = fixture_root / "calls" state = fixture_root / "state" ssh_dir = fixture_root / "home/gib/.ssh" root_ssh_dir = fixture_root / "root/.ssh" (fixture_root / "etc/ssh/sshd_config.d").mkdir(parents=True) ssh_dir.mkdir(parents=True) root_ssh_dir.mkdir(parents=True) stub_dir.mkdir(parents=True) state.mkdir() calls.touch() (state / "target-uid").write_text("1000\n") (state / "home").write_text("/home/gib\n") (state / "target-dir-meta").write_text("1000:700\n") (state / "target-key-meta").write_text("1000:600\n") (state / "root-key-meta").write_text("0:600\n") (fixture_root / "stub-install").write_text( "#!/usr/bin/env bash\nprintf 'install-handoff %s\\n' \"${PANAMA_PATH:-unset}\" >> \"$PANAMA_BOOT_FIXTURE_ROOT/calls\"\n" ) (fixture_root / "stub-install").chmod(0o755) make_stubs(stub_dir, fixture_root, calls) target_keys = ssh_dir / "authorized_keys" root_keys = root_ssh_dir / "authorized_keys" if name == "missing": pass elif name == "empty": target_keys.touch() elif name == "comment-only": target_keys.write_text("# no usable key\n\n") elif name == "ssh-directory-symlink": shutil.rmtree(ssh_dir) alternate = fixture_root / "unsafe-ssh" alternate.mkdir() (fixture_root / "home/gib/.ssh").symlink_to(alternate) elif name == "authorized-keys-symlink": alternate = fixture_root / "unsafe-authorized-keys" alternate.write_text("ssh-ed25519 unsafe\n") target_keys.symlink_to(alternate) elif name == "directory-wrong-mode": target_keys.write_text("ssh-ed25519 target\n") (state / "target-dir-meta").write_text("1000:755\n") elif name == "root-copy-directory-wrong-mode": root_keys.write_text("ssh-ed25519 root\n") (state / "target-dir-meta").write_text("1000:755\n") elif name == "file-wrong-mode": target_keys.write_text("ssh-ed25519 target\n") (state / "target-key-meta").write_text("1000:644\n") elif name == "directory-wrong-owner": target_keys.write_text("ssh-ed25519 target\n") (state / "target-dir-meta").write_text("0:700\n") elif name == "file-wrong-owner": target_keys.write_text("ssh-ed25519 target\n") (state / "target-key-meta").write_text("0:600\n") elif name == "root-target-account": target_keys.write_text("ssh-ed25519 target\n") (state / "target-uid").write_text("0\n") elif name == "relative-home": target_keys.write_text("ssh-ed25519 target\n") (state / "home").write_text("home/gib\n") elif name == "safe-existing-key": target_keys.write_text("ssh-ed25519 target\n") elif name == "safe-root-key-copy": root_keys.write_text("ssh-ed25519 root\n") else: raise ValueError(name) return fixture_root, stub_dir def run_case(name: str) -> tuple[int, str, str, Path]: fixture_root, stub_dir = configure_case(name) master, slave = pty.openpty() def attach_terminal() -> None: fcntl.ioctl(0, termios.TIOCSCTTY, 0) env = { **os.environ, "PATH": f"{stub_dir}:/usr/bin:/bin", "PANAMA_BOOT_FIXTURE_ROOT": str(fixture_root), "PANAMA_PATH": f"{fixture_root}/home/gib/.local/share/Panama", "HOME": f"{fixture_root}/root", } process = subprocess.Popen( ["bash", boot, "--server"], stdin=slave, stdout=slave, stderr=slave, env=env, start_new_session=True, preexec_fn=attach_terminal, ) os.close(slave) os.write(master, b"gib\nY\n") chunks: list[bytes] = [] while True: try: chunk = os.read(master, 4096) except OSError as error: if error.errno == errno.EIO: break raise if not chunk: break chunks.append(chunk) os.close(master) status = process.wait() calls = (fixture_root / "calls").read_text() output = b"".join(chunks).decode(errors="replace") return status, output, calls, fixture_root unsafe_cases = ( "missing", "empty", "comment-only", "ssh-directory-symlink", "authorized-keys-symlink", "directory-wrong-mode", "root-copy-directory-wrong-mode", "file-wrong-mode", "directory-wrong-owner", "file-wrong-owner", "root-target-account", "relative-home", ) for case in unsafe_cases: status, output, calls, fixture_root = run_case(case) if status != 0: note(f"{case}: bootstrap stopped with status {status}: {output.strip()}") if "SSH hardening unavailable" not in output: note(f"{case}: unsafe login path did not explain why hardening was unavailable") if "sshd -t" in calls: note(f"{case}: unsafe login path validated sshd") if "systemctl reload" in calls: note(f"{case}: unsafe login path reloaded SSH") if (fixture_root / "etc/ssh/sshd_config.d/90-panama.conf").exists(): note(f"{case}: unsafe login path changed the SSH drop-in") if case == "root-copy-directory-wrong-mode" and ( fixture_root / "home/gib/.ssh/authorized_keys" ).exists(): note("root-copy-directory-wrong-mode: copied a root key into an unsafe SSH directory") if "install-handoff " not in calls: note(f"{case}: unsafe login path did not hand off to install") for case in ("safe-existing-key", "safe-root-key-copy"): status, output, calls, fixture_root = run_case(case) if status != 0: note(f"{case}: safe login path stopped with status {status}: {output.strip()}") if "SSH hardening unavailable" in output: note(f"{case}: safe login path was rejected") if "systemctl reload" not in calls: note(f"{case}: safe login path did not reach SSH hardening") if "install-handoff " not in calls: note(f"{case}: safe login path did not hand off to install") dropin = fixture_root / "etc/ssh/sshd_config.d/90-panama.conf" if (dropin.read_text() if dropin.exists() else "") != "PermitRootLogin no\nPasswordAuthentication no\n": note(f"{case}: safe login path did not write the expected SSH drop-in") if case == "safe-root-key-copy": keys = fixture_root / "home/gib/.ssh/authorized_keys" if not keys.exists() or keys.read_text() != "ssh-ed25519 root\n": note("safe-root-key-copy: root key was not copied to the target account") if findings: print(f"root server bootstrap: {len(findings)} finding(s)", file=sys.stderr) for finding in findings: print(f" - {finding}", file=sys.stderr) raise SystemExit(1) print("root server bootstrap: PASS") PY