railiance-platform/scripts/openbao-isolated-restore/preflight.py
codex 0cc3281ab3 RPF-WP-0064-T02: isolated original-OpenBao restore adapter (phase A)
Networkless disposable Docker target (--network none, read-only, tmpfs only,
no volumes/ports/kubeconfig), metadata-only preflight before any prompt,
SSH-signed admission in namespace railiance-openbao-restore-attempt, hidden
custody prompts with sealed memfds, throwaway init + forced Raft restore from
stdin, original-share unseal, generate-root readback credential (revoked),
metadata-only readback against committed sources, reaper for parent loss and
hard deadline. Original material refuses until T06 sets the image digest pin.
Fix stale v2.5.3 app version in docs/openbao.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4384@bnt-lap001
Assistant-Session: 80d1f3da-7a91-4519-9737-9ba128b4da64
2026-10-23 20:49:24 +02:00

71 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Metadata-only preflight for the isolated original-OpenBao restore (RPF-WP-0064-T02).
Runs before any custody prompt. Never asks for, opens or decrypts custody
material. Checks: signed admission (exact bytes, sha256, fixed SSH namespace,
protected allowed_signers anchor, window), exact committed source closure,
pinned image digest present locally (``--pull=never``), ciphertext size and
sha256, required tools, ``/dev/shm`` tmpfs and that no prior container of this
adapter exists. Prints only OK/ERROR, never values.
"""
from __future__ import annotations
import argparse
import importlib.util
import json
import shutil
import subprocess
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
_spec = importlib.util.spec_from_file_location("restore_adapter", HERE / "restore_adapter.py")
adapter = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(adapter)
require, Refused = adapter.require, adapter.Refused
def check_local(adm: dict, ciphertext: Path, docker: str = "docker") -> None:
for tool in ("age", "age-keygen", "ssh-keygen", "git", docker):
require(shutil.which(tool), "preflight_refused")
mounts = Path("/proc/mounts").read_text().splitlines()
require(any(line.split()[1:3] == ["/dev/shm", "tmpfs"] for line in mounts), "preflight_refused")
inspect = subprocess.run([docker, "image", "inspect", adm["image"], "--format", "{{json .RepoDigests}}"],
capture_output=True, timeout=30)
require(inspect.returncode == 0 and adm["image"] in json.loads(inspect.stdout or b"[]"), "preflight_refused")
stale = subprocess.run([docker, "ps", "-a", "-q", "--filter", "name=rpf64-restore-"],
capture_output=True, timeout=30)
require(stale.returncode == 0 and not stale.stdout.strip(), "preflight_refused")
raw = adapter.read_owner_file(ciphertext, 512 * 1024 * 1024)
snap = adm["snapshot"]
require(len(raw) == snap["ciphertext_bytes"] and adapter.sha256_bytes(raw) == snap["ciphertext_sha256"],
"ciphertext_mismatch")
require(raw.startswith(b"age-encryption.org/v1\n"), "ciphertext_mismatch")
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
for name in ("admission", "admission-sig", "admission-sha256", "ciphertext", "receipt"):
parser.add_argument("--" + name, required=True)
opts = parser.parse_args(argv)
try:
adm, _raw, _signer, _protected = adapter.load_admission(
Path(opts.admission), Path(opts.admission_sig), opts.admission_sha256, adapter.anchor_path())
require(adm["material"] == "original", "admission_refused")
adapter.check_window(adm)
adapter.check_source_closure(adm, adapter.ROOT)
check_local(adm, Path(opts.ciphertext))
receipt = Path(opts.receipt)
require(receipt.is_absolute() and not receipt.exists() and receipt.parent.is_dir(), "preflight_refused")
except Refused as exc:
print(f"ERROR: preflight refused ({exc.reason}); no custody input requested.")
return 1
except Exception:
print("ERROR: preflight refused (internal_error); no custody input requested.")
return 1
print("OK: signed admission, source closure, pinned image, ciphertext and local target verified.")
return 0
if __name__ == "__main__":
raise SystemExit(main())