#!/usr/bin/env python3 """One contained attended window for RAILIANCE-WP-0024-T02. Run only as the single child of the contained attended login, so the whole window costs one MFA login: python3 scripts/openbao-attended-exec.py -- scripts/t02-attended-window.py ... Order (each step a reviewed command; this file adds no OpenBao call of its own): 1. lane ``project`` -- one temporary sender, bearer to a 0600 file, audit-core restart 2. port-forward -- ``ssh -L`` to ``kubectl -n audit-core port-forward`` on the remote 3. harness ``exercise`` with the registered load driver 4. evidence -- exercise JSON written 0600 outside Git 5. lane ``cleanup`` -- with that evidence (restart #2) Any failure after projection was attempted runs ``cleanup --abort-reason``. The port-forward is always stopped. Children's stdout is captured and only their value-safe JSON is re-emitted; nothing here reads the bearer. """ from __future__ import annotations import argparse import importlib.util import json import os import re import subprocess import sys import time import urllib.error import urllib.request from datetime import UTC, datetime from pathlib import Path from typing import Any, Callable ROOT = Path(__file__).resolve().parents[1] LANE_SCRIPT = ROOT / "scripts" / "audit_core_synthetic_sender_lane.py" HARNESS_SCRIPT = ROOT / "scripts" / "audit-core-database-lease-recovery.py" HARNESS_CONFIRM = "RAILIANCE-WP-0024-T02:attended" REMOTE_PF_TIMEOUT_SECONDS = 15 * 60 CONTAINER_PORT = 8080 class WindowError(RuntimeError): pass def _load_lane() -> Any: spec = importlib.util.spec_from_file_location("audit_core_synthetic_sender_lane", LANE_SCRIPT) if not spec or not spec.loader: raise WindowError("cannot load synthetic sender lane") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module LANE = _load_lane() def stamp(value: datetime) -> str: return value.astimezone(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") def run_json(command: list[str], *, label: str, env: dict[str, str] | None = None) -> dict[str, Any]: """Run a value-safe reviewed child; capture both streams (warden fails on any output).""" completed = subprocess.run( command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, check=False ) if completed.returncode != 0: # Reviewed lane/harness errors are fixed value-safe strings; keep one bounded line. reason = (completed.stderr.strip().splitlines() or [""])[-1][:200] raise WindowError(f"{label} failed (exit {completed.returncode}): {reason}") try: payload = json.loads(completed.stdout) except json.JSONDecodeError as exc: raise WindowError(f"{label} returned invalid JSON") from exc if not isinstance(payload, dict) or payload.get("secret_values_observed") is not False: raise WindowError(f"{label} did not attest value safety") return payload def private_dir(path: Path) -> Path: """An operator-owned 0700 directory outside every Git work tree.""" path = path.resolve() LANE.check_bearer_path(path / "probe", must_exist=False) return path def write_private_json(path: Path, document: dict[str, Any]) -> None: fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as handle: json.dump(document, handle, indent=2, sort_keys=True) handle.write("\n") def slug(value: str) -> str: cleaned = re.sub(r"[^a-z0-9-]+", "-", value.lower()).strip("-") if not cleaned: raise WindowError("approval id cannot form a fixture id") return cleaned[:48] def driver_env(spec: dict[str, Any], approval: dict[str, Any], bearer: Path, port: int) -> dict[str, str]: env = dict(os.environ) env.update({ "AUDIT_T02_BASE_URL": f"http://127.0.0.1:{port}", "AUDIT_T02_TOKEN_FILE": str(bearer), "AUDIT_T02_SOURCE": spec["source"], "AUDIT_T02_TENANT": spec["tenant"], "AUDIT_T02_FIXTURE_ID": f"t02-{slug(approval['approval_id'])}", "AUDIT_T02_OCCURRED_AT": approval["window"]["start"], }) return env class PortForward: """``ssh -L`` to a remote, time-bounded ``kubectl port-forward`` on loopback. The lane and harness reach the cluster only through ``ssh kubectl``; the port-forward uses the same path so the window needs no local kubeconfig. """ def __init__(self, remote: str, port: int) -> None: self.remote = remote self.port = port self.process: subprocess.Popen[bytes] | None = None def command(self) -> list[str]: remote_cmd = ( f"timeout {REMOTE_PF_TIMEOUT_SECONDS} kubectl -n audit-core port-forward " f"--address 127.0.0.1 deploy/audit-core {self.port}:{CONTAINER_PORT}" ) return [ "ssh", "-o", "BatchMode=yes", "-o", "ExitOnForwardFailure=yes", "-L", f"127.0.0.1:{self.port}:127.0.0.1:{self.port}", self.remote, remote_cmd, ] def start(self, *, timeout: float = 30) -> None: self.process = subprocess.Popen( self.command(), stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) deadline = time.monotonic() + timeout while time.monotonic() < deadline: if self.process.poll() is not None: raise WindowError("audit-core port-forward exited early") # A local ssh listener accepts before the remote forward is ready, so # require a real /healthz answer through the whole chain. try: with urllib.request.urlopen(f"http://127.0.0.1:{self.port}/healthz", timeout=2) as response: if response.status == 200: return except (OSError, urllib.error.URLError): pass time.sleep(1) raise WindowError("audit-core port-forward did not become reachable") def stop(self) -> None: if self.process is None or self.process.poll() is not None: return self.process.terminate() try: self.process.wait(timeout=10) except subprocess.TimeoutExpired: self.process.kill() self.process.wait(timeout=5) def window(args: argparse.Namespace, *, runner: Callable[..., dict[str, Any]] = run_json, forward_factory: Callable[[str, int], Any] = PortForward) -> dict[str, Any]: if not args.bearer_file.is_absolute() or not args.evidence_dir.is_absolute(): raise WindowError("--bearer-file and --evidence-dir must be absolute") LANE.LEASE.contained_bao_env() # refuse before any step outside the contained login approval, spec = LANE.load_receipt(args.approval, datetime.now(UTC), require_open_window=True) if args.confirm != LANE.confirm_token(approval): raise WindowError(f"window requires --confirm {LANE.confirm_token(approval)}") evidence_dir = private_dir(args.evidence_dir) base = f"{slug(approval['approval_id'])}" exercise_path = evidence_dir / f"{base}-exercise.json" summary_path = evidence_dir / f"{base}-window.json" for path in (exercise_path, summary_path): if path.exists(): raise WindowError(f"evidence file already exists: {path.name}") lane = [sys.executable, str(LANE_SCRIPT)] common = ["--approval", str(args.approval), "--remote", args.remote, "--contained", "--confirm", args.confirm] summary: dict[str, Any] = { "procedure": "t02-attended-window", "approval_id": approval["approval_id"], "identity": LANE.identity_view(spec), "started_at": stamp(datetime.now(UTC)), "steps": [], "secret_values_observed": False, } projection_attempted = False forward = forward_factory(args.remote, args.port) try: projection_attempted = True summary["project"] = runner( [*lane, "project", *common, "--bearer-file", str(args.bearer_file)], label="lane project") summary["steps"].append("project") forward.start() summary["steps"].append("port-forward") e2 = getattr(args, "mode", "t02") == "e2" exercise = runner( [sys.executable, str(HARNESS_SCRIPT), "synthetic-e2" if e2 else "exercise", "--approval", str(args.approval), "--remote", args.remote, "--contained", "--load-driver", str(args.load_driver), "--confirm", LANE.LEASE.E2_CONFIRM if e2 else HARNESS_CONFIRM], label="T03 synthetic E2" if e2 else "T02 exercise", env=driver_env(spec, approval, args.bearer_file, args.port), ) summary["steps"].append("exercise") write_private_json(exercise_path, exercise) summary["exercise_evidence"] = exercise_path.name summary["steps"].append("evidence") forward.stop() summary["cleanup"] = runner( [*lane, "cleanup", *common, "--bearer-file", str(args.bearer_file), "--exercise-evidence", str(exercise_path)], label="lane cleanup") summary["steps"].append("cleanup") summary["outcome"] = "completed" except BaseException as exc: forward.stop() summary["outcome"] = "aborted" summary["abort_reason"] = str(exc)[:200] if isinstance(exc, WindowError) else type(exc).__name__ if projection_attempted and "cleanup" not in summary["steps"]: try: summary["cleanup"] = runner( [*lane, "cleanup", *common, "--bearer-file", str(args.bearer_file), "--abort-reason", f"t02 window aborted: {summary['abort_reason']}"], label="lane abort cleanup") summary["steps"].append("abort-cleanup") except BaseException as cleanup_exc: # keep the first failure; flag cleanup loudly summary["abort_cleanup_failed"] = str(cleanup_exc)[:200] summary["finished_at"] = stamp(datetime.now(UTC)) write_private_json(summary_path, summary) raise finally: forward.stop() summary["finished_at"] = stamp(datetime.now(UTC)) write_private_json(summary_path, summary) return summary def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("--approval", type=Path, required=True) parser.add_argument("--load-driver", type=Path, required=True) parser.add_argument("--bearer-file", type=Path, required=True) parser.add_argument("--evidence-dir", type=Path, required=True) parser.add_argument("--confirm", required=True) parser.add_argument("--remote", default="railiance01") parser.add_argument("--port", type=int, default=18081) parser.add_argument("--mode", choices=["t02", "e2"], default="t02", help="e2: post-reboot synthetic event accepted exactly once; no lease revocation") args = parser.parse_args(argv) # The contained login discards child output and fails closed on any byte, # so this script never prints; its outcome lives only in the evidence dir. try: window(args) except (OSError, KeyError, WindowError, LANE.LaneError, LANE.LEASE.ProcedureError) as exc: try: LANE.emit_result( {"status": "failed", "error": str(exc), "secret_values_observed": False}, args.evidence_dir / f"window-error-{int(time.time())}.json", ) except OSError: pass return 1 return 0 if __name__ == "__main__": raise SystemExit(main())