Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
268 lines
14 KiB
Python
268 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""Founder workstation attended backup for the admitted demo-company release.
|
|
|
|
Default reviews only. --execute captures a fresh point (brief app pause), seals
|
|
on-node, fetches ciphertext, then opens contained owner login to publish/readback/
|
|
decrypt. --fetch RUN or --publish RUN retries without another capture/pause.
|
|
--retention previews owner-verified provider retention; --prune executes it.
|
|
"""
|
|
|
|
import argparse
|
|
import datetime as dt
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
import vergabe_pilot_export as export
|
|
import vergabe_pilot_export_remote as transfer
|
|
import vergabe_pilot_point as point
|
|
import vergabe_pilot_publish as publisher
|
|
from vergabe_pilot_recovery_store import Refused, private_directory, require
|
|
|
|
PLAN = Path(__file__).resolve().parents[1] / "docs/proposals/2026-11-06-vergabe-attended-backups/plan.json"
|
|
NODE_MODULES = ("vergabe_pilot_recovery_store", "vergabe_pilot_export", "vergabe_pilot_point",
|
|
"vergabe_pilot_snapshot", "vergabe_pilot_backup_node")
|
|
|
|
|
|
def source_pins():
|
|
plan = json.loads(PLAN.read_text())
|
|
expected = {*NODE_MODULES, "vergabe_pilot_export_remote", "vergabe_pilot_publish",
|
|
"vergabe_pilot_backup", "vergabe_pilot_retention", "vergabe_pilot_operating_restore",
|
|
"vergabe_pilot_restore", "vergabe_pilot_restore_remote"}
|
|
require(set(plan["sources"]) == expected, "attended_source_set_changed")
|
|
sources = {}
|
|
for name, checksum in plan["sources"].items():
|
|
raw = Path(__file__).with_name(name + ".py").read_bytes()
|
|
require(hashlib.sha256(raw).hexdigest() == checksum, "attended_source_pin_changed")
|
|
sources[name] = raw.decode()
|
|
publisher.pins()
|
|
return sources
|
|
|
|
|
|
def remote_command(run, mode):
|
|
point.run_time(run)
|
|
require(mode in {"check", "capture", "seal", "resume"}, "invalid_backup_mode")
|
|
sources = source_pins()
|
|
bootstrap = "import sys,types;"
|
|
for name in NODE_MODULES[:-1]:
|
|
bootstrap += ("m=types.ModuleType(" + repr(name) + ");sys.modules[m.__name__]=m;"
|
|
"exec(compile(" + repr(sources[name]) + "," + repr(name + ".py") + ",'exec'),m.__dict__);")
|
|
bootstrap += ("sys.argv=['vergabe_pilot_backup_node.py',*sys.argv[1:]];exec(compile(" +
|
|
repr(sources[NODE_MODULES[-1]]) + ",'vergabe_pilot_backup_node.py','exec'),{'__name__':'__main__'})")
|
|
command = shlex.join(["sudo", "/usr/bin/env", "KUBECONFIG=/etc/rancher/k3s/k3s.yaml",
|
|
"/usr/bin/python3", "-c", bootstrap, "--run", run, mode])
|
|
require(len(command.encode()) < 120000, "attended_transport_size_bound")
|
|
return ["ssh", "-t", "railiance01", command]
|
|
|
|
|
|
def kit_root():
|
|
return Path.home() / ".local/state/vergabe-pilot-recovery"
|
|
|
|
|
|
def configure(directory, run):
|
|
require(transfer.outside_git(directory.resolve()), "private_kit_outside_git_required")
|
|
sealed = point.select_transfer(directory, run)
|
|
verified = transfer.verify(directory)
|
|
publisher.OBJECT = export.NAME + ".zip.age"
|
|
publisher.CIPHER_SHA256 = sealed["ciphertext_sha256"]
|
|
publisher.CIPHER_BYTES = sealed["ciphertext_bytes"]
|
|
require(verified["ciphertext_sha256"] == sealed["ciphertext_sha256"], "sealed_point_changed")
|
|
return sealed
|
|
|
|
|
|
def fetch(run, already_sealed=False):
|
|
point.run_time(run)
|
|
root = kit_root()
|
|
require(transfer.outside_git(root.resolve()), "private_kit_outside_git_required")
|
|
root.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
private_directory(root)
|
|
directory = root / run
|
|
if directory.exists() or directory.is_symlink():
|
|
configure(directory, run)
|
|
return directory
|
|
if not already_sealed:
|
|
result = subprocess.run(remote_command(run, "seal"), check=False)
|
|
require(result.returncode == 0, "node_seal_failed_use_same_run_for_retry")
|
|
with tempfile.TemporaryDirectory(prefix="fetch-", dir=root) as temporary:
|
|
temporary = Path(temporary)
|
|
for suffix in (".transfer.json", ".zip.age"):
|
|
name = "vergabe-pilot-" + run + suffix
|
|
copied = subprocess.run(["scp", "-p", "railiance01:" + name, str(temporary / name)],
|
|
capture_output=True, timeout=120, check=False)
|
|
require(copied.returncode == 0, "encrypted_fetch_failed_use_same_run_for_retry")
|
|
configure(temporary, run)
|
|
temporary.rename(directory)
|
|
return directory
|
|
|
|
|
|
def inventory(root):
|
|
"""Only private local kits with ciphertext and complete owner receipts qualify."""
|
|
private_directory(root)
|
|
children = list(root.iterdir())
|
|
require(len(children) <= 2000, "local_inventory_bound")
|
|
points = []
|
|
for directory in sorted(children):
|
|
if not directory.is_dir() or directory.is_symlink():
|
|
continue
|
|
try:
|
|
point.run_time(directory.name)
|
|
sealed = configure(directory, directory.name)
|
|
tombstone = directory / "provider-removed.json"
|
|
if tombstone.exists() or tombstone.is_symlink():
|
|
removed = point.read_private(tombstone)
|
|
require(removed["run_id"] == directory.name and removed["ciphertext_sha256"] == sealed["ciphertext_sha256"]
|
|
and removed["status"] == "provider_removed_and_readback_absent", "retention_tombstone_invalid")
|
|
continue
|
|
receipts = list(directory.glob("provider-receipt-*.json"))
|
|
require(len(receipts) <= 100, "provider_receipt_count_bound")
|
|
if any(point.successful_provider(point.read_private(p), directory.name, sealed) for p in receipts):
|
|
points.append({"run_id": directory.name, "ciphertext_sha256": sealed["ciphertext_sha256"],
|
|
"ciphertext_bytes": sealed["ciphertext_bytes"]})
|
|
except (OSError, ValueError, KeyError, TypeError, Refused):
|
|
continue # Incomplete/unknown kits never qualify for provider deletion.
|
|
return points
|
|
|
|
|
|
def write_receipt(path, body):
|
|
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
|
|
with os.fdopen(fd, "w") as stream:
|
|
json.dump(body, stream, indent=2)
|
|
stream.flush()
|
|
os.fsync(stream.fileno())
|
|
|
|
|
|
def owner_child(args):
|
|
# Child stays silent under Warden, including setup and parser failures.
|
|
try:
|
|
source_pins()
|
|
require(args.directory and args.receipt and args.directory.is_absolute()
|
|
and args.receipt.parent == args.directory, "private_owner_paths_required")
|
|
configure(args.directory, args.run)
|
|
if args.action == "publish":
|
|
return publisher.child(args.directory, args.receipt)
|
|
require(args.action == "prune", "invalid_owner_action")
|
|
import vergabe_pilot_retention as retention
|
|
return retention.child(args.directory.parent, args.receipt, inventory)
|
|
except BaseException: # noqa: BLE001 — contained child must never print private output
|
|
return 1
|
|
|
|
|
|
def owner_action(directory, run, action="publish"):
|
|
configure(directory, run)
|
|
require(all(shutil.which(t) for t in ("warden", "bao", "age", "age-keygen")), "owner_tools_required")
|
|
output = directory / (("provider-receipt-" if action == "publish" else "retention-receipt-") + uuid.uuid4().hex[:12] + ".json")
|
|
command = [sys.executable, str(publisher.PLATFORM / "scripts/openbao-attended-exec.py"), "--",
|
|
sys.executable, str(Path(__file__).resolve()), "--contained", "--run", run,
|
|
"--directory", str(directory), "--receipt", str(output), "--action", action]
|
|
result = subprocess.run(command, check=False, env=publisher.owner_login_environment())
|
|
if output.exists() or output.is_symlink():
|
|
body = point.read_private(output)
|
|
else:
|
|
body = {"status": "owner_login_or_child_not_completed", "provider_outcome": "unconfirmed"}
|
|
body.update(warden_exit_code=result.returncode, receipt_file=str(output), **publisher.session_outcome(result.returncode))
|
|
if output.exists():
|
|
export.regular(output, os.geteuid())
|
|
fd = os.open(output, os.O_WRONLY | os.O_TRUNC | os.O_NOFOLLOW)
|
|
with os.fdopen(fd, "w") as stream:
|
|
json.dump(body, stream, indent=2)
|
|
stream.flush()
|
|
os.fsync(stream.fileno())
|
|
else:
|
|
write_receipt(output, body)
|
|
print(json.dumps(body, indent=2))
|
|
expected = "published_read_back_and_decrypted" if action == "publish" else "provider_retention_complete"
|
|
require(result.returncode == 0 and body["status"] == expected, "owner_operation_incomplete")
|
|
return body
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
modes = parser.add_mutually_exclusive_group()
|
|
modes.add_argument("--check", action="store_true")
|
|
modes.add_argument("--execute", action="store_true")
|
|
modes.add_argument("--fetch")
|
|
modes.add_argument("--publish")
|
|
modes.add_argument("--resume")
|
|
modes.add_argument("--retention", action="store_true")
|
|
modes.add_argument("--prune", action="store_true")
|
|
modes.add_argument("--restore")
|
|
modes.add_argument("--restore-cleanup", nargs=2, metavar=("RUN", "NONCE"))
|
|
modes.add_argument("--contained", action="store_true", help=argparse.SUPPRESS)
|
|
parser.add_argument("--run", help=argparse.SUPPRESS)
|
|
parser.add_argument("--directory", type=Path, help=argparse.SUPPRESS)
|
|
parser.add_argument("--receipt", type=Path, help=argparse.SUPPRESS)
|
|
parser.add_argument("--action", choices=("publish", "prune"), default="publish", help=argparse.SUPPRESS)
|
|
parser.add_argument("--verify-restore", action="store_true", help="also rehearse this point in fresh isolated claims")
|
|
args = parser.parse_args()
|
|
if args.contained:
|
|
return owner_child(args)
|
|
require(not args.verify_restore or args.execute or args.publish, "verify_restore_requires_capture_or_publish")
|
|
sources = source_pins()
|
|
if not any((args.check, args.execute, args.fetch, args.publish, args.resume, args.retention, args.prune,
|
|
args.restore, args.restore_cleanup)):
|
|
print(json.dumps({"status": "prepared", "target": "railiance01", "helm_revision": 4,
|
|
"next": "--check then --execute", "runtime_mutations": False,
|
|
"capture_briefly_pauses_pilot": True, "scheduled_service": False}))
|
|
return 0
|
|
require(sys.stdin.isatty(), "founder_interactive_terminal_required")
|
|
if args.restore or args.restore_cleanup:
|
|
import vergabe_pilot_operating_restore as recovery
|
|
if args.restore_cleanup:
|
|
return recovery.cleanup(sources, *args.restore_cleanup)
|
|
directory = kit_root() / args.restore
|
|
sealed = configure(directory, args.restore)
|
|
return recovery.execute(directory, args.restore, sealed, sources)
|
|
if args.retention or args.prune:
|
|
plan = point.retention_plan(inventory(kit_root()))
|
|
print(json.dumps({"status": "retention_preview", **plan}, indent=2), flush=True)
|
|
if args.prune and plan["candidates"]:
|
|
require(any(p["run_id"] == point.BASELINE for p in plan["keep"]), "baseline_receipt_required")
|
|
newest = plan["keep"][0]["run_id"]
|
|
owner_action(kit_root() / newest, newest, "prune")
|
|
return 0
|
|
run = args.fetch or args.publish or args.resume or dt.datetime.now(dt.UTC).strftime("%Y%m%dT%H%M%SZ-") + uuid.uuid4().hex[:12]
|
|
point.run_time(run)
|
|
if args.check or args.resume:
|
|
return subprocess.run(remote_command(run, "resume" if args.resume else "check"), check=False).returncode
|
|
print(json.dumps({"status": "attended_backup_started", "run_id": run,
|
|
"capture_requested": args.execute}), flush=True)
|
|
if args.execute:
|
|
result = subprocess.run(remote_command(run, "capture"), check=False)
|
|
require(result.returncode == 0, "capture_or_seal_failed_keep_run_id_and_review_resume")
|
|
directory = kit_root() / run if args.publish else fetch(run, already_sealed=args.execute)
|
|
configure(directory, run)
|
|
if args.fetch:
|
|
print(json.dumps({"status": "encrypted_workstation_copy_verified", "run_id": run,
|
|
"provider_publication_verified": False, "local_directory": str(directory)}))
|
|
return 0
|
|
body = owner_action(directory, run)
|
|
if args.verify_restore:
|
|
import vergabe_pilot_operating_restore as recovery
|
|
sealed = configure(directory, run)
|
|
require(recovery.execute(directory, run, sealed, sources) == 0, "backup_published_but_restore_requires_review")
|
|
summary = {"schema": "railiance.vergabe-pilot-attended-backup.v1", "status": "complete", "run_id": run,
|
|
"provider_publication_verified": True, "independent_readback_verified": True,
|
|
"decryption_verified": True, "session_cleanup_confirmed": True, "restore_verified": args.verify_restore,
|
|
"provider_receipt": body["receipt_file"], "capture_requested": args.execute,
|
|
"retention_execution_pending": bool(point.retention_plan(inventory(kit_root()))["candidates"])}
|
|
write_receipt(directory / ("attended-backup-" + uuid.uuid4().hex[:12] + ".json"), summary)
|
|
print(json.dumps(summary, indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
os.umask(0o077)
|
|
try:
|
|
raise SystemExit(main())
|
|
except Exception as error: # noqa: BLE001 — keep private transport/parser output suppressed
|
|
if "--contained" not in sys.argv:
|
|
print(json.dumps({"status": "attended_backup_failed", "failure": str(error) if isinstance(error, Refused)
|
|
else "attended_backup_error", "details_suppressed": True}))
|
|
raise SystemExit(1)
|