RPF-WP-0015-T02: contained-login only, single silent attended window
Harness used the retracted public OpenBao URL and a token file; lane used a standing token file. Both now run only inside the contained ops-warden login (tunnel, helper token), and t02-attended-window.py runs project, exercise and cleanup under one login without printing (warden fails closed on child output). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 680842@bnt-lap001 Assistant-Session: 54eb9e8e-7d6b-4f0c-b7a6-a6db62075883
This commit is contained in:
parent
badb91c71a
commit
311aa86eef
10 changed files with 773 additions and 109 deletions
274
scripts/t02-attended-window.py
Executable file
274
scripts/t02-attended-window.py
Executable file
|
|
@ -0,0 +1,274 @@
|
|||
#!/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")
|
||||
exercise = runner(
|
||||
[sys.executable, str(HARNESS_SCRIPT), "exercise", "--approval", str(args.approval),
|
||||
"--remote", args.remote, "--contained", "--load-driver", str(args.load_driver),
|
||||
"--confirm", HARNESS_CONFIRM],
|
||||
label="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)
|
||||
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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue