railiance-apps/tools/vergabe_pilot_export_remote.py
tegwick d2e1377354
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Record populated pilot capture and prepare encrypted export
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
2026-11-06 01:02:59 +01:00

125 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""Founder workstation: seal and fetch this completed pilot recovery point.
Default reviews only. --fetch uses the existing interactive SSH/sudo lane to
encrypt on railiance01, then scp copies only ciphertext and a safe manifest.
No plaintext backup, credential or private recovery key leaves the node.
"""
import argparse
import hashlib
import json
import os
import shlex
import subprocess
import sys
import tempfile
from pathlib import Path
import vergabe_pilot_export as export
from vergabe_pilot_recovery_store import Refused, digest, private_directory, require
PINS = {
"vergabe_pilot_recovery_store": "724a2dd9db8a0f57e1efa709ec989d8a47c681a86b1fca923bfdfea1698ae23c",
"vergabe_pilot_export": "b45733fa10ae59b56563045c43c53b5f824a74e1114f68fbed28404f14b43e3a",
}
def remote_command():
sources = {}
for name, pin in PINS.items():
raw = Path(__file__).with_name(name + ".py").read_bytes()
require(hashlib.sha256(raw).hexdigest() == pin, "export_source_pin_mismatch")
sources[name] = raw.decode()
bootstrap = ("import sys,types;m=types.ModuleType('vergabe_pilot_recovery_store');"
"sys.modules[m.__name__]=m;exec(compile(" + repr(sources["vergabe_pilot_recovery_store"]) +
",'vergabe_pilot_recovery_store.py','exec'),m.__dict__);exec(compile(" +
repr(sources["vergabe_pilot_export"]) + ",'vergabe_pilot_export.py','exec'),"
"{'__name__':'__main__'})")
return ["ssh", "-t", "railiance01", shlex.join(["sudo", "/usr/bin/python3", "-c", bootstrap])]
def verify(directory):
private_directory(directory)
ciphertext = directory / (export.NAME + ".zip.age")
transfer = directory / (export.NAME + ".transfer.json")
export.regular(ciphertext, os.geteuid())
export.regular(transfer, os.geteuid())
require(0 < transfer.stat().st_size <= 16384, "transfer_metadata_bound")
receipt = json.loads(transfer.read_text())
require(receipt["schema"] == "railiance.vergabe-pilot-sealed-export.v1"
and receipt["run_id"] == export.RUN_ID and receipt["recipient"] == export.RECIPIENT
and receipt["packet"]["run_id"] == export.RUN_ID
and receipt["packet"]["image"] == export.IMAGE
and receipt["packet"]["database"] == "vergabe_demo_company", "wrong_recovery_point")
for name, (size, checksum) in export.EXPECTED.items():
require(receipt["packet"]["files"][name] == {"bytes": size, "sha256": checksum},
"source_hash_mismatch")
require(0 < ciphertext.stat().st_size <= 4 * 1024 * 1024
and ciphertext.stat().st_size == receipt["ciphertext_bytes"]
and digest(ciphertext) == receipt["ciphertext_sha256"], "ciphertext_transfer_mismatch")
with ciphertext.open("rb") as stream:
require(stream.read(22) == b"age-encryption.org/v1\n", "age_header_required")
return {"schema": "railiance.vergabe-pilot-workstation-copy.v1", "run_id": export.RUN_ID,
"status": "encrypted_workstation_copy_verified", "ciphertext_sha256": receipt["ciphertext_sha256"],
"ciphertext_bytes": receipt["ciphertext_bytes"], "encrypted_on_source_node": True,
"off_host_copy_verified": True, "provider_publication_verified": False,
"decryption_verified": False, "restore_verified": False, "application_changed": False}
def outside_git(path):
return not any((p / ".git").exists() for p in [path, *path.parents])
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--fetch", action="store_true")
args = parser.parse_args()
command = remote_command()
if not args.fetch:
print(json.dumps({"status": "prepared", "run_id": export.RUN_ID, "ssh_target": "railiance01",
"application_changed": False, "next": "--fetch", "sources": PINS}))
return 0
require(sys.stdin.isatty(), "founder_interactive_terminal_required")
destination = Path.home() / ".local/state/vergabe-pilot-recovery" / export.RUN_ID
require(outside_git(destination), "private_destination_outside_git_required")
destination.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
private_directory(destination.parent)
if destination.exists() or destination.is_symlink():
result = verify(destination)
else:
result = subprocess.run(command, check=False)
require(result.returncode == 0, "remote_seal_failed")
with tempfile.TemporaryDirectory(prefix="fetch-", dir=destination.parent) as temporary:
temporary = Path(temporary)
for suffix in (".zip.age", ".transfer.json"):
name = export.NAME + suffix
copied = subprocess.run(["scp", "-p", "railiance01:" + name, str(temporary / name)],
capture_output=True, timeout=120)
require(copied.returncode == 0, "encrypted_copy_failed")
result = verify(temporary)
# Keep a verified pair only; temporary failures never become success.
temporary.rename(destination)
result["local_directory"] = str(destination)
receipt = destination / "workstation-copy.json"
if receipt.exists() or receipt.is_symlink():
export.regular(receipt, os.geteuid())
require(json.loads(receipt.read_text()) == result, "existing_copy_receipt_mismatch")
else:
fd = os.open(receipt, os.O_CREAT | os.O_EXCL | os.O_WRONLY | os.O_NOFOLLOW, 0o600)
with os.fdopen(fd, "w") as output:
json.dump(result, output, indent=2)
output.flush()
os.fsync(output.fileno())
print(json.dumps(result, indent=2))
return 0
if __name__ == "__main__":
os.umask(0o077)
try:
raise SystemExit(main())
except Exception as error:
print(json.dumps({"status": "export_failed", "failure": str(error) if isinstance(error, Refused)
else "export_transport_failed", "details_suppressed": True}))
raise SystemExit(1)