Files
Panama/tests/setup/root-server-bootstrap-contract
T

301 lines
10 KiB
Bash
Executable File

#!/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