#!/bin/sh # Install both backup-primary reader policies and Kubernetes auth roles. # Silent: warden access rejects any child output. Receipts stay on absolute paths. set -eu export BAO_ADDR="${BAO_ADDR:-http://127.0.0.1:18200}" export VAULT_ADDR="${VAULT_ADDR:-http://127.0.0.1:18200}" receipt_dir=/home/worsch/.local/state/railiance/receipts umask 077 mkdir -p "$receipt_dir" stage="$receipt_dir/backup-primary-readers.stage" : >"$stage" python3 - "$stage" <<'PY' import importlib.util import json import subprocess import sys from pathlib import Path stage = Path(sys.argv[1]) root = Path("/home/worsch/railiance-platform") spec = importlib.util.spec_from_file_location( "credential_change", root / "scripts/credential-change.py" ) cc = importlib.util.module_from_spec(spec) spec.loader.exec_module(cc) refs = [ "CCR-2026-0036-coulomb-social-backup-primary.yaml", "CCR-2026-0037-email-connect-backup-primary.yaml", ] safe_role_keys = ( "audience", "bound_service_account_names", "bound_service_account_namespaces", "policies", "token_explicit_max_ttl", "token_max_ttl", "token_no_default_policy", "token_num_uses", "token_policies", "token_type", "ttl", ) def run(argv): result = subprocess.run(argv, cwd=root, capture_output=True) if result.returncode != 0: stage.write_text(f"exit {result.returncode} step {argv[1]}\n", encoding="utf-8") raise SystemExit(result.returncode or 1) return result.stdout.decode() receipts = [] for name in refs: ccr = json.loads((root / "credential-change-requests" / name).read_text(encoding="utf-8")) if ccr.get("status") != "approved" or ccr["openbao"]["auth"].get("bound_claims_confirmed") is not True: stage.write_text(f"{ccr.get('id')} not approved for apply\n", encoding="utf-8") raise SystemExit(44) blockers = cc.applier_readiness_blockers(ccr) if blockers: stage.write_text(ccr["id"] + " blocked\n", encoding="utf-8") raise SystemExit(45) openbao = ccr["openbao"] auth = openbao["auth"] policy_path = root / openbao["policy_file"] run(["bao", "policy", "write", openbao["policy_name"], str(policy_path)]) installed = run(["bao", "policy", "read", openbao["policy_name"]]) if cc.normalized_policy_body(installed) != cc.normalized_policy_body(policy_path.read_text(encoding="utf-8")): stage.write_text(openbao["policy_name"] + " readback differs\n", encoding="utf-8") raise SystemExit(46) payload = cc.auth_payload(ccr) role_file = Path(f"/home/worsch/.local/state/railiance/receipts/{auth['role']}.payload.json") role_file.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") role_file.chmod(0o600) run(["bao", "write", f"auth/{auth['mount']}/role/{auth['role']}", f"@{role_file}"]) raw = run(["bao", "read", "-format=json", f"auth/{auth['mount']}/role/{auth['role']}"]) data = json.loads(raw).get("data", {}) observed = {key: data.get(key) for key in safe_role_keys if key in data} names = data.get("bound_service_account_names") namespaces = data.get("bound_service_account_namespaces") policies = data.get("token_policies") or data.get("policies") audience = data.get("audience") or data.get("audiences") problems = [] if names != payload["bound_service_account_names"]: problems.append("names") if namespaces != payload["bound_service_account_namespaces"]: problems.append("namespaces") if policies != payload["policies"].split(","): problems.append("policies") if audience not in (payload.get("audience"), [payload.get("audience")]): problems.append("audience") if data.get("token_no_default_policy") is not True: problems.append("no_default") if data.get("token_num_uses") != 4: problems.append("uses") if data.get("token_type") != "service": problems.append("type") if data.get("ttl") not in (300, "5m", "300s"): problems.append("ttl") if data.get("token_max_ttl") not in (300, "5m", "300s"): problems.append("max_ttl") if data.get("token_explicit_max_ttl") not in (300, "5m", "300s"): problems.append("explicit_max_ttl") if problems: stage.write_text( json.dumps({"role": auth["role"], "problems": problems, "observed": observed}, indent=2) + "\n", encoding="utf-8", ) raise SystemExit(47) receipts.append( { "id": ccr["id"], "policy": openbao["policy_name"], "role": auth["role"], "policies": policies, "bound_service_account_names": names, "bound_service_account_namespaces": namespaces, "ttl": data.get("ttl"), "token_max_ttl": data.get("token_max_ttl"), "token_explicit_max_ttl": data.get("token_explicit_max_ttl"), "token_num_uses": data.get("token_num_uses"), "token_no_default_policy": data.get("token_no_default_policy"), "token_type": data.get("token_type"), } ) out = Path("/home/worsch/.local/state/railiance/receipts/backup-primary-readers.json") out.write_text(json.dumps({"status": "installed", "readers": receipts}, indent=2) + "\n", encoding="utf-8") out.chmod(0o600) PY if [ -s "$stage" ]; then exit 48 fi exit 0