2026-10-14 14:23:46 +02:00
|
|
|
#!/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 <remote> 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")
|
2026-10-14 20:30:11 +02:00
|
|
|
e2 = getattr(args, "mode", "t02") == "e2"
|
2026-10-14 14:23:46 +02:00
|
|
|
exercise = runner(
|
2026-10-14 20:30:11 +02:00
|
|
|
[sys.executable, str(HARNESS_SCRIPT), "synthetic-e2" if e2 else "exercise",
|
|
|
|
|
"--approval", str(args.approval),
|
2026-10-14 14:23:46 +02:00
|
|
|
"--remote", args.remote, "--contained", "--load-driver", str(args.load_driver),
|
2026-10-14 20:30:11 +02:00
|
|
|
"--confirm", LANE.LEASE.E2_CONFIRM if e2 else HARNESS_CONFIRM],
|
|
|
|
|
label="T03 synthetic E2" if e2 else "T02 exercise",
|
2026-10-14 14:23:46 +02:00
|
|
|
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)
|
2026-10-14 20:30:11 +02:00
|
|
|
parser.add_argument("--mode", choices=["t02", "e2"], default="t02",
|
|
|
|
|
help="e2: post-reboot synthetic event accepted exactly once; no lease revocation")
|
2026-10-14 14:23:46 +02:00
|
|
|
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())
|