railiance-platform/scripts/install-backup-primary-readers.sh
codex 1900e24f80
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Accept both backup readers and deliver the email-connect pair.
Record founder acceptance on CCR-2026-0036 and CCR-2026-0037, install the reader roles, and admit only the email-connect node delivery. Treat a missing OpenBao token period as non-periodic.

Assistant: grok
Assistant-Session: 01a11cdb-f9fe-7011-947c-936e72007e54
2026-10-09 00:05:35 +02:00

134 lines
5.3 KiB
Bash

#!/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