From 0fb2305def01ee7e8dde06d6ee92be51a0a887dd Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 2 Nov 2026 02:37:01 +0100 Subject: [PATCH] Record founder approval of the MAS backup reader. CCR-2026-0062 is approved with the live delivery account confirmed. The OpenBao policy is not installed and no provider key was created. Assistant: grok Assistant-Session: 01a198fa-fd27-7e41-a0a2-57355c3e4efd --- .../CCR-2026-0062-mas-backup-primary.yaml | 23 +++- .../mas-evidence-backup-grant-2026-11-02.md | 47 ++++--- scripts/install-mas-backup-primary-reader.sh | 128 ++++++++++++++++++ .../RPF-WP-0070-mas-evidence-recovery.md | 20 +-- 4 files changed, 184 insertions(+), 34 deletions(-) create mode 100755 scripts/install-mas-backup-primary-reader.sh diff --git a/credential-change-requests/CCR-2026-0062-mas-backup-primary.yaml b/credential-change-requests/CCR-2026-0062-mas-backup-primary.yaml index bf6f923..9bac3f1 100644 --- a/credential-change-requests/CCR-2026-0062-mas-backup-primary.yaml +++ b/credential-change-requests/CCR-2026-0062-mas-backup-primary.yaml @@ -4,7 +4,7 @@ "schema_version": 1, "request_type": "workload-kv-read", "title": "MAS evidence archive node-delivery reader", - "status": "draft", + "status": "approved", "created": "2026-11-02", "updated": "2026-11-02", "requester": { @@ -17,7 +17,22 @@ "platform-owner", "mas-owner" ], - "comments": [] + "comments": [ + { + "at": "2026-11-02T01:35:00Z", + "reviewer": "User (platform-owner)", + "decision": "approved", + "comment": "Founder approved this reader on 2026-11-02. One read policy, ServiceAccount backup-primary-delivery in namespace mas, audience openbao.", + "source_ref": "founder-approval-2026-11-02" + }, + { + "at": "2026-11-02T01:35:00Z", + "reviewer": "User (mas-owner)", + "decision": "approved", + "comment": "Founder approved MAS node delivery of this backup pair. The IAM application, pair custody, age escrow, and offsite publication stay separate.", + "source_ref": "founder-approval-2026-11-02" + } + ] }, "target": { "domain": "communication", @@ -59,13 +74,13 @@ "mas" ] }, - "bound_claims_confirmed": false + "bound_claims_confirmed": true } }, "access_frontdoor": { "type": "platform-owner", "catalog_id": "mas-backup-primary-delivery", - "readiness": "pending-review", + "readiness": "approved-pending-apply", "resolvable": false }, "delivery": { diff --git a/docs/credential-lane-designs/mas-evidence-backup-grant-2026-11-02.md b/docs/credential-lane-designs/mas-evidence-backup-grant-2026-11-02.md index 59cce8e..2a47a78 100644 --- a/docs/credential-lane-designs/mas-evidence-backup-grant-2026-11-02.md +++ b/docs/credential-lane-designs/mas-evidence-backup-grant-2026-11-02.md @@ -1,9 +1,10 @@ # MAS evidence publisher grant — 2026-11-02 -Prepared for RPF-WP-0070. CCR-2026-0062 is a **draft**. Its Kubernetes binding -is unconfirmed and its delivery admission is false. This packet creates no -provider identity, OpenBao policy, role, or value, ServiceAccount, node file, -offsite upload, or timer. +Prepared for RPF-WP-0070. The founder approved CCR-2026-0062 on 2026-11-02 +as platform-owner and mas-owner. The Kubernetes binding is confirmed against +ServiceAccount `backup-primary-delivery` in namespace `mas`, which exists with +automount disabled. Delivery admission is still false. No provider identity, +OpenBao policy, role, or value, node file, offsite upload, or timer exists yet. The accepted shape is the email-connect ledger reader, CCR-2026-0037. The MAS prefix is `platform-pg/application-archives/mas/` in bucket @@ -13,14 +14,14 @@ local. It is not an offsite object. | Item | Prepared value | Live state | | --- | --- | --- | -| Reader request | CCR-2026-0062 | draft, not approved | +| Reader request | CCR-2026-0062 | approved; not applied | | KV path | platform/workloads/mas/backup-primary | no value written | | Fields | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY | absent | -| Policy | workload-kv-read-mas-backup-primary | source only | +| Policy | workload-kv-read-mas-backup-primary | source only; install is silent and attended | | Role | mas-backup-primary-workload-kv-read | not created | -| ServiceAccount | backup-primary-delivery in namespace mas | source list only; automount false; no RoleBinding | +| ServiceAccount | backup-primary-delivery in namespace mas | live, automount false, uid c0e5867b-b4be-49b7-94db-6aae0a6e566e; no RoleBinding | | Node file | /etc/mas-backup/primary.env, root:root 0600, directory 0700 | not created | -| Provider identity | proposed application mas-evidence-backup-primary | not created | +| Provider identity | proposed application mas-evidence-backup-primary | not created; this workstation has no Scaleway credential | Do not reuse the email-connect publisher or its node file. Object-storage IAM is not prefix-scoped inside a project, so a later admission needs its own IAM @@ -30,19 +31,23 @@ and is not created here. ## Still required before any offsite copy -1. Founder reviews as platform-owner and mas-owner, with the namespace, - ServiceAccount, and audience confirmed. -2. Create the separate IAM application and prove the prefix boundary, including - denial of sibling prefixes, retained delete, and IAM or bucket administration. -3. Apply the policy and role through the delegated metadata applier. Ops-mason - remains survey-only. -4. Provision the pair atomically with KV CAS=0 under an explicit platform - custody act. The single-field paste desk is unsuitable. Pair-custody and - slot-preparation lists stay closed to coulomb-social and email-connect until - that act. -5. Record positive reader proof and negative denials, then set delivery - admission. Export the admitted CCR and pin its SHA256 before creating the - node file. +1. Founder review is recorded: platform-owner and mas-owner, source + `founder-approval-2026-11-02`. The live account matches the binding. +2. Apply the policy and role with the attended platform-admin login: + `BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 warden access openbao-platform-admin-login --exec -- /home/worsch/railiance-platform/scripts/install-mas-backup-primary-reader.sh` + Sign in as platform-root. The helper is silent and writes only a receipt. +3. In the Scaleway console, create a new IAM application named + `mas-evidence-backup-primary`. Do not add a key to email-connect or to a + broad application. Then say that the application exists. This workstation + has no Scaleway credential, so it cannot create the application or its key. +4. Add three bucket-policy statements for that application, limited to + `platform-pg/application-archives/mas/`, with no object delete. Prove sibling + denial, retained-delete denial, and no IAM or bucket administration. +5. Provision the pair atomically with KV CAS=0 under the pair-writer policy + extended to this one path. The single-field paste desk is unsuitable. +6. Record positive reader proof and negative denials, then set delivery + admission. Export the admitted CCR and pin its SHA256 before creating + `/etc/mas-backup/primary.env`. Age-identity escrow is a separate custody act. This grant does not move the workstation private key, and the node must not hold it. TEM and SMTP diff --git a/scripts/install-mas-backup-primary-reader.sh b/scripts/install-mas-backup-primary-reader.sh new file mode 100755 index 0000000..bb7f2ec --- /dev/null +++ b/scripts/install-mas-backup-primary-reader.sh @@ -0,0 +1,128 @@ +#!/bin/sh +# Install the MAS backup-primary reader policy and Kubernetes auth role. +# Silent: warden access rejects any child output. Receipts stay on absolute paths. +# This does not create a provider key, write a KV value, or create a node file. +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/mas-backup-primary-reader.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) + +name = "CCR-2026-0062-mas-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() + +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", {}) +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: + observed = {key: data.get(key) for key in safe_role_keys if key in data} + stage.write_text( + json.dumps({"role": auth["role"], "problems": problems, "observed": observed}, indent=2) + + "\n", + encoding="utf-8", + ) + raise SystemExit(47) +receipt = { + "status": "installed", + "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/mas-backup-primary-reader.json") +out.write_text(json.dumps(receipt, indent=2) + "\n", encoding="utf-8") +out.chmod(0o600) +PY +if [ -s "$stage" ]; then + exit 48 +fi +exit 0 diff --git a/workplans/RPF-WP-0070-mas-evidence-recovery.md b/workplans/RPF-WP-0070-mas-evidence-recovery.md index 920c25c..0cdab4e 100644 --- a/workplans/RPF-WP-0070-mas-evidence-recovery.md +++ b/workplans/RPF-WP-0070-mas-evidence-recovery.md @@ -40,7 +40,7 @@ status: progress priority: medium needs_human: true depends_on: [RPF-WP-0053-T05] -intervention_note: "Draft CCR-2026-0062 prepares the MAS prefix publisher. It is not approved or applied. Age-identity escrow is still required before offsite publication." +intervention_note: "Founder approved CCR-2026-0062. The delivery account exists. OpenBao policy/role install still needs the attended platform-admin login, and the IAM application is not created. Age-identity escrow remains separate." state_hub_task_id: "4f6484fb-8303-5dd5-94cb-b2622e1322c8" ``` @@ -61,12 +61,14 @@ The age identity is only on the operator workstation. It is not escrowed, and nothing was published to `platform-pg/application-archives/mas/`. The profile is accepted by `scripts/primary_application_archive.py`. -2026-11-02: the prefix publisher is prepared as draft `CCR-2026-0062`. -It names `platform/workloads/mas/backup-primary`, policy -`workload-kv-read-mas-backup-primary`, ServiceAccount -`backup-primary-delivery` in namespace `mas`, and delivery at -`/etc/mas-backup/primary.env`. Bindings are unconfirmed and delivery -admission is false. The IAM application, OpenBao value, live -ServiceAccount, and node file are not created. Pair-custody and -slot-preparation lists still name only coulomb-social and email-connect. +2026-11-02: the founder approved `CCR-2026-0062` as platform-owner and +mas-owner. ServiceAccount `backup-primary-delivery` now exists in +namespace `mas` with automount disabled and no RoleBinding. The binding +is confirmed. Delivery admission stays false. The OpenBao policy and +role are not installed: the local token is denied, so the install waits +for `warden access openbao-platform-admin-login --exec` running +`scripts/install-mas-backup-primary-reader.sh`. No Scaleway credential +is configured here, so IAM application `mas-evidence-backup-primary` +is not created. The KV pair and `/etc/mas-backup/primary.env` do not +exist. Pair-custody lists still name only coulomb-social and email-connect. Design: `docs/credential-lane-designs/mas-evidence-backup-grant-2026-11-02.md`.