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
|
|
@ -14,7 +14,6 @@ import hashlib
|
|||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
|
@ -122,14 +121,41 @@ def safe_run(
|
|||
return completed
|
||||
|
||||
|
||||
# Operator OpenBao access is the SSH tunnel; the public endpoint was retracted
|
||||
# 2026-09-15 (RMASTER-WP-0020-T09).
|
||||
TUNNEL_ADDR = "http://127.0.0.1:18200"
|
||||
CONTAINMENT_MARKER = "WARDEN_ROUTING_CATALOG"
|
||||
TOKEN_ENV = ("BAO_TOKEN", "VAULT_TOKEN", "OPENBAO_TOKEN")
|
||||
|
||||
|
||||
def contained_bao_env(environ: dict[str, str] | None = None) -> dict[str, str]:
|
||||
"""Environment for ``bao`` inside the contained attended login only.
|
||||
|
||||
``scripts/openbao-attended-exec.py`` runs ``warden access
|
||||
openbao-platform-admin-login --exec`` with BAO_ADDR pinned to the tunnel,
|
||||
no token variable, and the Warden routing marker; ``bao`` then
|
||||
authenticates through Warden's isolated token helper. Anything else --
|
||||
a token file, a token variable, or another address -- is refused.
|
||||
"""
|
||||
env = dict(os.environ if environ is None else environ)
|
||||
if not env.get(CONTAINMENT_MARKER):
|
||||
raise ProcedureError(
|
||||
"live OpenBao access requires the contained attended login "
|
||||
"(scripts/openbao-attended-exec.py)"
|
||||
)
|
||||
if env.get("BAO_ADDR", TUNNEL_ADDR) != TUNNEL_ADDR:
|
||||
raise ProcedureError(f"BAO_ADDR must be the operator tunnel {TUNNEL_ADDR}")
|
||||
if any(env.get(name) for name in TOKEN_ENV):
|
||||
raise ProcedureError("token variables must be unset inside the contained login")
|
||||
for name in TOKEN_ENV:
|
||||
env.pop(name, None)
|
||||
env["BAO_ADDR"] = TUNNEL_ADDR
|
||||
return env
|
||||
|
||||
|
||||
class Authority:
|
||||
def __init__(self, token_file: Path) -> None:
|
||||
if not token_file.is_file() or stat.S_IMODE(token_file.stat().st_mode) != 0o600:
|
||||
raise ProcedureError("OpenBao token file must exist with mode 0600")
|
||||
token = token_file.read_text(encoding="utf-8").splitlines()[0].strip()
|
||||
if not token:
|
||||
raise ProcedureError("OpenBao token file is empty")
|
||||
self.env = dict(os.environ, BAO_ADDR="https://bao.coulomb.social", BAO_TOKEN=token)
|
||||
def __init__(self) -> None:
|
||||
self.env = contained_bao_env()
|
||||
|
||||
def bao(self, args: list[str], *, label: str) -> str:
|
||||
return safe_run(["bao", *args], label=label, env=self.env).stdout.strip()
|
||||
|
|
@ -333,7 +359,7 @@ def exercise(args: argparse.Namespace) -> dict[str, Any]:
|
|||
if args.confirm != CONFIRM:
|
||||
raise ProcedureError(f"live exercise requires --confirm {CONFIRM}")
|
||||
remote = PREFLIGHT.Remote(args.remote)
|
||||
authority = Authority(args.token_file)
|
||||
authority = Authority()
|
||||
driver = LoadDriver(
|
||||
args.load_driver,
|
||||
approval["synthetic_load"]["contract_id"],
|
||||
|
|
@ -450,18 +476,30 @@ def exercise(args: argparse.Namespace) -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
|
||||
def emit_result(result: dict, result_file: "Path | None") -> None:
|
||||
"""Under the contained login the child must stay silent: write, never print."""
|
||||
if result_file is None:
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
return
|
||||
fd = os.open(result_file, 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(result, handle, indent=2, sort_keys=True)
|
||||
handle.write("\n")
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("command", choices=["validate-approval", "exercise"])
|
||||
parser.add_argument("--approval", type=Path, required=True)
|
||||
parser.add_argument("--remote", default="railiance01")
|
||||
parser.add_argument(
|
||||
"--token-file", type=Path,
|
||||
default=Path.home() / ".local/openbao/platform-admin.token",
|
||||
"--contained", action="store_true",
|
||||
help="required for exercise: run inside the contained attended OpenBao login",
|
||||
)
|
||||
parser.add_argument("--load-driver", type=Path)
|
||||
parser.add_argument("--confirm")
|
||||
args = parser.parse_args()
|
||||
parser.add_argument("--result-file", type=Path, help="write the JSON result here (0600, new) and print nothing")
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
if args.command == "validate-approval":
|
||||
approval = load_approval(args.approval, datetime.now(UTC), require_open_window=False)
|
||||
|
|
@ -472,13 +510,18 @@ def main() -> int:
|
|||
"secret_values_observed": False,
|
||||
}
|
||||
else:
|
||||
if not args.contained:
|
||||
raise ProcedureError("live exercise requires --contained (scripts/openbao-attended-exec.py)")
|
||||
if args.load_driver is None:
|
||||
raise ProcedureError("live exercise requires --load-driver")
|
||||
result = exercise(args)
|
||||
except (OSError, IndexError, ProcedureError, PREFLIGHT.PreflightError) as exc:
|
||||
print(f"database lease recovery failed: {exc}", file=sys.stderr)
|
||||
if args.result_file is None:
|
||||
print(f"database lease recovery failed: {exc}", file=sys.stderr)
|
||||
else:
|
||||
emit_result({"status": "failed", "error": str(exc), "secret_values_observed": False}, args.result_file)
|
||||
return 1
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
emit_result(result, args.result_file)
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
|||
54
scripts/audit_core_synthetic_sender_lane.py
Normal file → Executable file
54
scripts/audit_core_synthetic_sender_lane.py
Normal file → Executable file
|
|
@ -262,6 +262,24 @@ def settled_state(remote: Any, *, previous_uid: str | None, timeout: float = 90)
|
|||
time.sleep(3)
|
||||
|
||||
|
||||
class ContainedOperator(CREDS.Operator):
|
||||
"""The -02 registry/kubectl helpers, authenticated only by the contained login.
|
||||
|
||||
No token file and no token variable: ``bao`` runs with the tunnel address
|
||||
and Warden's isolated token helper (see ``LEASE.contained_bao_env``).
|
||||
"""
|
||||
|
||||
def __init__(self, remote: str) -> None: # noqa: D107 - deliberately no super().__init__
|
||||
self.remote = remote
|
||||
self.bao_env = LEASE.contained_bao_env()
|
||||
|
||||
|
||||
def contained_operator(args: argparse.Namespace) -> Any:
|
||||
if not args.contained:
|
||||
raise LaneError(f"{args.command} requires --contained (scripts/openbao-attended-exec.py)")
|
||||
return ContainedOperator(args.remote)
|
||||
|
||||
|
||||
def registry_metadata(operator: Any) -> dict[str, Any]:
|
||||
raw = operator.bao(["kv", "metadata", "get", "-format=json", REGISTRY_PATH], label="read sender registry metadata").stdout
|
||||
try:
|
||||
|
|
@ -303,9 +321,16 @@ def preflight(args: argparse.Namespace) -> dict[str, Any]:
|
|||
if args.offline:
|
||||
result["live_checks"] = "skipped"
|
||||
return result
|
||||
if args.inspect_registry and not args.contained:
|
||||
raise LaneError("--inspect-registry requires --contained")
|
||||
remote = PREFLIGHT.Remote(args.remote)
|
||||
result["audit_core"] = cluster_state(remote)
|
||||
operator = CREDS.Operator(args.remote, args.token_file)
|
||||
result["automated_checks_passed"] = result["audit_core"]["automated_checks_passed"]
|
||||
if not args.contained:
|
||||
# Outside the attended login there is no OpenBao credential at all.
|
||||
result["sender_registry_metadata"] = "skipped-not-contained"
|
||||
return result
|
||||
operator = contained_operator(args)
|
||||
result["sender_registry_metadata"] = registry_metadata(operator)
|
||||
if args.inspect_registry:
|
||||
# Attended only: values are held in memory and never emitted.
|
||||
|
|
@ -316,7 +341,6 @@ def preflight(args: argparse.Namespace) -> dict[str, Any]:
|
|||
)
|
||||
result["registry_conflicts"] = registry_conflicts(registry, spec)
|
||||
registry.clear()
|
||||
result["automated_checks_passed"] = result["audit_core"]["automated_checks_passed"]
|
||||
return result
|
||||
|
||||
|
||||
|
|
@ -334,6 +358,7 @@ def project(args: argparse.Namespace) -> dict[str, Any]:
|
|||
approval, spec = load_receipt(args.approval, now, require_open_window=True)
|
||||
if args.confirm != confirm_token(approval):
|
||||
raise LaneError(f"project requires --confirm {confirm_token(approval)}")
|
||||
operator = contained_operator(args)
|
||||
assert_projection_time(approval, spec, now)
|
||||
if args.bearer_file is None:
|
||||
raise LaneError("project requires --bearer-file outside Git")
|
||||
|
|
@ -343,7 +368,6 @@ def project(args: argparse.Namespace) -> dict[str, Any]:
|
|||
if not before["automated_checks_passed"]:
|
||||
raise LaneError("audit-core recovery preflight is not green; refusing to project")
|
||||
LEASE.attest_chain_state(remote)
|
||||
operator = CREDS.Operator(args.remote, args.token_file)
|
||||
registry = operator.registry()
|
||||
conflicts = registry_conflicts(registry, spec)
|
||||
if conflicts:
|
||||
|
|
@ -384,8 +408,8 @@ def cleanup(args: argparse.Namespace) -> dict[str, Any]:
|
|||
approval, spec = load_receipt(args.approval, utc_now(), require_open_window=False)
|
||||
if args.confirm != confirm_token(approval):
|
||||
raise LaneError(f"cleanup requires --confirm {confirm_token(approval)}")
|
||||
operator = contained_operator(args)
|
||||
gate = assert_evidence(args.exercise_evidence, args.abort_reason, approval)
|
||||
operator = CREDS.Operator(args.remote, args.token_file)
|
||||
removed = _cleanup_registry(operator, spec)
|
||||
remaining = any(
|
||||
isinstance(entry, dict) and entry.get("name") == spec["name"] for entry in operator.registry()
|
||||
|
|
@ -412,26 +436,42 @@ def cleanup(args: argparse.Namespace) -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
|
||||
def emit_result(result: dict, result_file: "Path | None") -> None:
|
||||
"""Under the contained login the child must stay silent: write, never print."""
|
||||
if result_file is None:
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
return
|
||||
fd = os.open(result_file, 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(result, handle, indent=2, sort_keys=True)
|
||||
handle.write("\n")
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument("command", choices=["preflight", "project", "cleanup"])
|
||||
parser.add_argument("--approval", type=Path, required=True, help="approved T02 window receipt")
|
||||
parser.add_argument("--bearer-file", type=Path, help="absolute path outside Git (mode-0700 directory)")
|
||||
parser.add_argument("--remote", default="railiance01")
|
||||
parser.add_argument("--token-file", type=Path, default=Path.home() / ".local/openbao/platform-admin.token")
|
||||
parser.add_argument("--contained", action="store_true",
|
||||
help="run inside the contained attended OpenBao login (required for project/cleanup)")
|
||||
parser.add_argument("--confirm")
|
||||
parser.add_argument("--offline", action="store_true", help="preflight: validate receipt/path only")
|
||||
parser.add_argument("--inspect-registry", action="store_true", help="preflight: attended registry conflict check")
|
||||
parser.add_argument("--exercise-evidence", type=Path, help="cleanup: recorded T02 exercise result JSON")
|
||||
parser.add_argument("--abort-reason", help="cleanup: abort path when no exercise evidence exists")
|
||||
parser.add_argument("--result-file", type=Path, help="write the JSON result here (0600, new) and print nothing")
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
handler = {"preflight": preflight, "project": project, "cleanup": cleanup}[args.command]
|
||||
result = handler(args)
|
||||
except ERRORS as exc:
|
||||
print(f"synthetic sender lane failed: {exc}", file=sys.stderr)
|
||||
if args.result_file is None:
|
||||
print(f"synthetic sender lane failed: {exc}", file=sys.stderr)
|
||||
else:
|
||||
emit_result({"status": "failed", "error": str(exc), "secret_values_observed": False}, args.result_file)
|
||||
return 1
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
emit_result(result, args.result_file)
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
|||
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