Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
125 lines
5.9 KiB
Python
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)
|