#!/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 re 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" }} consume_result() {{ local name="$1" results result results="$PANAMA_BOOT_FIXTURE_ROOT/state/$name" if ! IFS= read -r result <"$results"; then return 0 fi /usr/bin/tail -n +2 "$results" >"$results.next" /usr/bin/mv -f -- "$results.next" "$results" [[ "$result" =~ ^[0-9]+$ ]] || exit 97 return "$result" }} ''' 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 "$@" [[ "$#" -eq 1 && "$1" == -t ]] || exit 97 for artifact in "$PANAMA_BOOT_FIXTURE_ROOT/etc/ssh/sshd_config.d"/.90-panama.*; do [[ -e "$artifact" ]] || continue log ssh-artifact "$artifact" "$(/usr/bin/stat -c %a -- "$artifact")" done consume_result SSHD_RESULTS ''') write_executable(stub_dir / "systemctl", common + r''' log systemctl "$@" case "${1:-}:${2:-}" in cat:sshd.service) [[ "$(<"$PANAMA_BOOT_FIXTURE_ROOT/state/SSHD_UNIT")" == present ]] ;; cat:ssh.service) [[ "$(<"$PANAMA_BOOT_FIXTURE_ROOT/state/SSH_UNIT")" == present ]] ;; reload:sshd.service|reload:ssh.service) consume_result RELOAD_RESULTS ;; *) exit 97 ;; esac ''') write_executable(stub_dir / "mv", common + r''' log mv "$@" "source-mode=$(/usr/bin/stat -c %a -- "${3:-}")" exec /usr/bin/mv "$@" ''') for command in ("useradd", "usermod"): write_executable(stub_dir / command, common + f'''\nlog {command} "$@"\nexit 97\n''') def configure_case( name: str, *, sshd_results: tuple[int, ...] = (), reload_results: tuple[int, ...] = (), prior_dropin: bytes | None = None, sshd_unit: bool = True, ssh_unit: bool = True, ) -> 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") (state / "SSHD_RESULTS").write_text("".join(f"{result}\n" for result in sshd_results)) (state / "RELOAD_RESULTS").write_text("".join(f"{result}\n" for result in reload_results)) (state / "SSHD_UNIT").write_text("present\n" if sshd_unit else "absent\n") (state / "SSH_UNIT").write_text("present\n" if ssh_unit else "absent\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) if prior_dropin is not None: dropin = fixture_root / "etc/ssh/sshd_config.d/90-panama.conf" dropin.write_bytes(prior_dropin) dropin.chmod(0o600) 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 in ( "safe-existing-key", "success-without-prior-dropin", "success-replaces-prior-dropin", "candidate-invalid", "candidate-reload-fails", "rollback-validation-fails", "rollback-reload-fails", ): 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, **configuration: object) -> tuple[int, str, str, Path]: fixture_root, stub_dir = configure_case(name, **configuration) 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") desired_dropin = b"PermitRootLogin no\nPasswordAuthentication no\n" prior_dropin = b"# prior Panama settings\nPasswordAuthentication yes\n" transaction_cases = { "success-without-prior-dropin": { "sshd_results": (0,), "reload_results": (0,), "prior_dropin": None, "sshd_unit": True, "ssh_unit": True, "succeeds": True, }, "success-replaces-prior-dropin": { "sshd_results": (0,), "reload_results": (0,), "prior_dropin": prior_dropin, "sshd_unit": False, "ssh_unit": True, "succeeds": True, }, "candidate-invalid": { "sshd_results": (1, 0), "reload_results": (), "prior_dropin": prior_dropin, "sshd_unit": True, "ssh_unit": True, "succeeds": False, }, "candidate-reload-fails": { "sshd_results": (0, 0), "reload_results": (1, 0), "prior_dropin": prior_dropin, "sshd_unit": True, "ssh_unit": True, "succeeds": False, }, "rollback-validation-fails": { "sshd_results": (0, 1), "reload_results": (1,), "prior_dropin": prior_dropin, "sshd_unit": True, "ssh_unit": True, "succeeds": False, "rollback_fails": True, }, "rollback-reload-fails": { "sshd_results": (0, 0), "reload_results": (1, 1), "prior_dropin": prior_dropin, "sshd_unit": True, "ssh_unit": True, "succeeds": False, "rollback_fails": True, }, } for case, expected in transaction_cases.items(): configuration = { key: value for key, value in expected.items() if key not in {"succeeds", "rollback_fails"} } status, output, calls, fixture_root = run_case(case, **configuration) call_lines = calls.splitlines() dropin = fixture_root / "etc/ssh/sshd_config.d/90-panama.conf" sshd_dir = dropin.parent validations = [index for index, line in enumerate(call_lines) if line.startswith("sshd -t ")] reloads = [ index for index, line in enumerate(call_lines) if line.startswith("systemctl reload ") ] activation_lines = [ (index, line) for index, line in enumerate(call_lines) if re.fullmatch( rf"mv -f -- {re.escape(str(sshd_dir))}/\.90-panama\.[A-Za-z0-9]+\.tmp " rf"{re.escape(str(dropin))} source-mode=600 ", line, ) ] restore_lines = [ index for index, line in enumerate(call_lines) if re.fullmatch( rf"mv -f -- {re.escape(str(sshd_dir))}/\.90-panama\.[A-Za-z0-9]+\.restore " rf"{re.escape(str(dropin))} source-mode=600 ", line, ) ] succeeds = bool(expected["succeeds"]) if succeeds and status != 0: note(f"{case}: transaction stopped with status {status}: {output.strip()}") if not succeeds and status == 0: note(f"{case}: failed transaction returned success") if succeeds and "install-handoff " not in calls: note(f"{case}: successful transaction did not hand off to install") if not succeeds and "install-handoff " in calls: note(f"{case}: failed transaction handed off to install") wanted_contents = desired_dropin if succeeds else prior_dropin actual_contents = dropin.read_bytes() if dropin.exists() else None if actual_contents != wanted_contents: note(f"{case}: SSH drop-in contents were not {'activated' if succeeds else 'restored'}") if len(activation_lines) != 1: note(f"{case}: candidate was not activated once through a restrictive same-directory rename") if succeeds: if len(validations) != 1 or len(reloads) != 1: note(f"{case}: success did not validate once and reload once") elif activation_lines and not activation_lines[0][0] < validations[0] < reloads[0]: note(f"{case}: success did not activate, validate, then reload") elif case == "candidate-invalid": if len(validations) != 2 or reloads: note(f"{case}: invalid candidate did not validate candidate and restoration without reload") elif activation_lines and restore_lines and not ( activation_lines[0][0] < validations[0] < restore_lines[0] < validations[1] ): note(f"{case}: rollback command order was wrong") else: if len(validations) != 2 or len(reloads) != 2: note(f"{case}: reload failure did not validate and reload the restored configuration") elif activation_lines and restore_lines and not ( activation_lines[0][0] < validations[0] < reloads[0] < restore_lines[0] < validations[1] < reloads[1] ): note(f"{case}: rollback command order was wrong") if succeeds and restore_lines: note(f"{case}: successful transaction performed a rollback") if not succeeds and len(restore_lines) != 1: note(f"{case}: prior drop-in was not restored exactly once") detected_unit = "ssh.service" if case == "success-replaces-prior-dropin" else "sshd.service" other_unit = "sshd.service" if detected_unit == "ssh.service" else "ssh.service" reload_lines = [call_lines[index] for index in reloads] if reload_lines and any(line != f"systemctl reload {detected_unit} " for line in reload_lines): note(f"{case}: reloaded a unit other than detected {detected_unit}") if any(line == f"systemctl reload {other_unit} " for line in call_lines): note(f"{case}: guessed {other_unit} after reload failure") if detected_unit == "sshd.service": if "systemctl cat sshd.service " not in call_lines: note(f"{case}: did not detect sshd.service") if "systemctl cat ssh.service " in call_lines: note(f"{case}: probed ssh.service after finding sshd.service") elif not ( "systemctl cat sshd.service " in call_lines and "systemctl cat ssh.service " in call_lines and call_lines.index("systemctl cat sshd.service ") < call_lines.index("systemctl cat ssh.service ") ): note(f"{case}: did not fall back from absent sshd.service to ssh.service") artifacts = list(sshd_dir.glob(".90-panama.*")) rollback_fails = bool(expected.get("rollback_fails", False)) if not rollback_fails and artifacts: note(f"{case}: successful or cleanly rolled-back transaction left temporary artifacts") if rollback_fails: backups = [artifact for artifact in artifacts if artifact.name.endswith(".backup")] if len(backups) != 1: note(f"{case}: rollback failure did not retain exactly one backup") else: backup = backups[0] if backup.parent != sshd_dir or backup.read_bytes() != prior_dropin: note(f"{case}: retained backup was not a same-directory byte copy") if backup.stat().st_mode & 0o777 != 0o600: note(f"{case}: retained backup permissions were not restrictive") if str(backup.resolve()) not in output: note(f"{case}: recovery output omitted the absolute backup path") if "sshd -t" not in output or f"systemctl reload {detected_unit}" not in output: note(f"{case}: recovery output omitted validation or reload commands") artifact_logs = [line for line in call_lines if line.startswith("ssh-artifact ")] if expected["prior_dropin"] is not None and not any( re.fullmatch( rf"ssh-artifact {re.escape(str(sshd_dir))}/\.90-panama\.[A-Za-z0-9]+\.backup 600 ", line, ) for line in artifact_logs ): note(f"{case}: backup was not collision-safe, same-directory, non-.conf, and restrictive") 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