Files
felhom.eu/documentation/audits/d63-2026-10-29/ceremony.py
T

79 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""ceremony.py — one escrow ceremony on Tester 1 through the wizard's own endpoints (hub 0.175.0 check, 2026-10-29).
The recovery code lives in this process's memory only: never printed, never written, dropped at the end. The dashboard
password is read from the 0600 file and handed to curl on stdin (never on a command line)."""
import json, os, subprocess, sys, time
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
SCR = "/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0b76c4d7-9b39-4e4e-869d-d0d2c6006331/scratchpad"
os.environ.setdefault("SC", f"{SCR}/rt-tester1")
os.environ.setdefault("TARGET", "tester-1")
import box_walk as w
w.login()
sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip()
csrf = open(f"{w.SC}/csrf{os.getpid()}.txt").read().strip()
BASE_ARGS = ["curl", "-sk", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", "-H", f"X-CSRF-Token: {csrf}"]
def call(method, path, form_stdin=None, body=None):
args = BASE_ARGS + ["-X", method, "-w", "\n%{http_code}"]
inp = None
if form_stdin is not None:
args += ["--data-urlencode", "password@-"]
inp = form_stdin
if body is not None:
args += ["-H", "Content-Type: application/json", "--data-binary", "@-"]
inp = json.dumps(body)
r = subprocess.run(args + [f"{w.BASE}{path}"], input=inp, capture_output=True, text=True, timeout=120)
out, _, code = r.stdout.rpartition("\n")
try:
return code, json.loads(out)
except Exception:
return code, {"raw": out[:200]}
def safe(d):
"""A copy with the code removed — the only form that is printed."""
d = json.loads(json.dumps(d))
if isinstance(d.get("data"), dict):
d["data"].pop("recovery_code", None)
return d
t0 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
code, d = call("GET", "/api/escrow/preflight")
w.say(f"preflight → {code} {json.dumps(d)[:600]}")
pw = open(f"{w.SC}/.ctlpw").read().strip()
code, d = call("POST", "/api/escrow/start", form_stdin=pw)
pw = None
w.say(f"start → {code} {json.dumps(safe(d))[:300]}")
if code != "200":
sys.exit(1)
for _ in range(120):
time.sleep(3)
code, d = call("GET", "/api/escrow/status")
ph = (d.get("data") or {}).get("phase")
if ph not in ("running", "queued", "starting", None) or (d.get("data") or {}).get("claimable"):
break
w.say(f"status → {code} {json.dumps(safe(d))[:400]}")
code, d = call("POST", "/api/escrow/claim")
rc = (d.get("data") or {}).get("recovery_code") or ""
pos = (d.get("data") or {}).get("verify_positions") or []
w.say(f"claim → {code}; code received: {bool(rc)} ({len(rc.split('-'))} words); confirm_required={(d.get('data') or {}).get('confirm_required')} positions={pos}")
if rc and pos:
words = rc.split("-")
picked = [words[pos[0] - 1], words[pos[1] - 1]]
rc, words = None, None
code, d = call("POST", "/api/escrow/confirm", body={"words": picked})
picked = None
w.say(f"confirm → {code} {json.dumps(safe(d))[:400]}")
rc = None
for _ in range(40):
time.sleep(15)
st = w.guest("docker logs --since " + t0 + " felhom-controller 2>&1 | grep -iE 'escrow' | grep -viE 'recovery_code' | tail -12")
if "escrowed" in st or "auto-confirm" in st.lower():
break
w.say("controller escrow lines:\n" + st)
open(f"{SCR}/d63/ev/ceremony.log", "a").write("\n".join(w.LOG) + "\n")