From 20db368aee21ae32040cea6ca52b375a54ef94c7 Mon Sep 17 00:00:00 2001 From: codex Date: Wed, 21 Oct 2026 01:24:45 +0200 Subject: [PATCH] RPF-WP-0035-T14: flex-auth signer custody founder gate, runbook and review dispositions - CCR-2026-0050: record secrets-engine-owner approval (two receipt conditions) and platform runbook review; link founder decision 18731e84. - Attended creation runbook with preconditions, rollback and negatives. - Warden routing receipt: openbao-platform-admin-login / oidc_login now selected. - Native adapter: owner-review digest excludes expires_at so reviews can precede the 120 s window (close-down checklist row 27). Nothing generated, written or applied. Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 809600@bnt-lap001 Assistant-Session: 40ba228f-c8a0-4ec9-bb29-3129951afdc4 --- WORK-RECORDS.md | 1 + ...-2026-0050-flex-auth-envelope-signing.yaml | 12 +- ...6-10-21-flex-auth-signer-warden-route.json | 103 +++++++++++++ ...auth-envelope-signing-attended-creation.md | 143 ++++++++++++++++++ scripts/flex_auth_signer_native.py | 10 +- tests/test_flex_auth_signer_native.py | 13 +- ...-WP-0035-credential-lane-implementation.md | 40 ++++- 7 files changed, 317 insertions(+), 5 deletions(-) create mode 100644 docs/evidence/2026-10-21-flex-auth-signer-warden-route.json create mode 100644 docs/runbooks/flex-auth-envelope-signing-attended-creation.md diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 482adfc..dfffe23 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -78,6 +78,7 @@ | task | RPF-WP-0035-T11 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md | | task | RPF-WP-0035-T12 | progress | — | workplans/RPF-WP-0035-credential-lane-implementation.md | | task | RPF-WP-0035-T13 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md | +| task | RPF-WP-0035-T14 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md | | task | RPF-WP-0036-T01 | done | — | workplans/RPF-WP-0036-platform-service-assurance.md | | task | RPF-WP-0036-T02 | done | — | workplans/RPF-WP-0036-platform-service-assurance.md | | task | RPF-WP-0036-T03 | wait | — | workplans/RPF-WP-0036-platform-service-assurance.md | diff --git a/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml b/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml index dc9dc2c..c482fc3 100644 --- a/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml +++ b/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml @@ -5,7 +5,7 @@ request_type: workload-kv-read title: Retained flex-auth lifecycle PDP Ed25519 seed ESO read lane status: proposed created: '2026-10-16' -updated: '2026-10-16' +updated: '2026-10-21' requester: agent: railiance-platform reason: CUST-WP-0080 / RPF-WP-0035-T12 / FLEX-WP-0024-T06 source proposal. Exact @@ -23,6 +23,12 @@ review: - reviewer: railiance-platform-owner (agent railiance-platform, codex) decision: approved comment: "INTENT Secure Custody/Stable Interfaces/Operable and Observable; RPF-WP-0035-T12. Approved Platform source/plan review only for frozen P4 manifest sha256:9eec861838a8e9138f2c821cff3014998fbe74bb01447642bf50d44e8b3b7cb3 and R2 manifest sha256:5be7a7f438624dcb35b7dbc96e7c9e5a8e2d5d1b1966cb4338aa5aecbe3a8b59; own bounded review artifact docs/evidence/2026-10-16-flex-auth-native-plan-review.json; sanitized supplement sha256:f02a9b595d9246a358d19b37bdad8fde158b7f0c9657a6ff088874732511a32c. Exact read policy/role/ESO proposal and one-use CAS0 writer stay separate. Identified manual Applications and recovery gates are mandatory. No founder decision, native execution, confirmed bindings or completed admission assent; current proposed status unchanged." + - reviewer: secrets-engine-owner (agent secrets-engine, hub message e73c9ffc-8a6d-4a17-9604-84324c678dc0) + decision: approved + comment: "2026-10-21, consumer side only. Consumer has no KV read role and receives no seed; public trust is admitted through a founder ssh-keygen -Y signed trust file (secrets-engine ebd8e49, docs/founder-admission.md, SECRETS-WP-0014-T04, decision e4eee965). Conditions: (1) the public receipt (kid, Ed25519 public key as 43-char unpadded base64url, evaluator/mode/policy_package/policy_version/policy_package_digest) is emitted by the attended writer at creation time or derived from its public output, never from a later read of the KV document; (2) the receipt carries the KV version and source/image provenance hashes. Not founder approval, native execution or a binding." + - reviewer: railiance-platform-owner (agent railiance-platform, claude) + decision: approved + comment: "2026-10-21 runbook review: docs/runbooks/flex-auth-envelope-signing-attended-creation.md prepared. Secrets condition 1 is met for kid/public key/fingerprint/kv_version/source/image hashes by deriving the consumer receipt from the writer receipt (no KV read); the evaluator/mode/policy_package/policy_version/policy_package_digest fields are not in the writer receipt and must come from Access's published pin. Review-scope digest no longer binds expires_at so owner reviews can precede the 120 s window. Pending: access-engine-owner (awaited), cluster-runtime-storage-owner (railiance-cluster, requested f8b4b229), founder. Nothing executed." target: domain: infotech tenant: platform @@ -135,4 +141,6 @@ lifecycle: compromised seed; archive historical public keys for offline audit only. state_hub: workplan_id: RPF-WP-0035 - task_id: RPF-WP-0035-T12 + task_id: RPF-WP-0035-T14 + decision_id: 18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508 + decision_api_url: http://127.0.0.1:8000/decisions/18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508 diff --git a/docs/evidence/2026-10-21-flex-auth-signer-warden-route.json b/docs/evidence/2026-10-21-flex-auth-signer-warden-route.json new file mode 100644 index 0000000..7c2e995 --- /dev/null +++ b/docs/evidence/2026-10-21-flex-auth-signer-warden-route.json @@ -0,0 +1,103 @@ +{ + "need": "attended OpenBao platform administration of flex-auth signer CAS0 seed and KV metadata setup", + "verdict": "founder_required", + "organization_posture": "build", + "policy_gate": "zone-aware \u2014 fetches gated by flex-auth-ops-warden-fetch at http://127.0.0.1:19091", + "lane_id": "openbao-platform-admin-login", + "lane_title": "Attended OpenBao platform administration login", + "match_score": 9, + "commands": [ + "warden access openbao-platform-admin-login --exec -- ", + "# owner login is contained by warden; do not invoke it separately" + ], + "founder_act": { + "kind": "oidc_login", + "summary": "Interactive OIDC/MFA login via railiance-platform", + "details": { + "lane_id": "openbao-platform-admin-login", + "auth_method": "attended KeyCape OIDC/MFA at OpenBao auth mount netkingdom, role platform-admin", + "fetch_command": "warden access openbao-platform-admin-login --exec -- ", + "desk_hint": "warden desk --from-plan (act=oidc_login); execute only through: warden access openbao-platform-admin-login --exec -- " + } + }, + "ccr_stub": null, + "catalog": { + "path": "/home/worsch/ops-warden/registry/routing/catalog.yaml", + "source": "repo", + "content_hash": "b007b9598551", + "mtime_iso": "2026-10-20T11:42:31.833164+00:00", + "package_version": "0.1.2", + "entry_count": 49, + "active_count": 41, + "newest_reviewed": "2026-10-20", + "oldest_reviewed": "2026-06-18", + "stale_entry_count": 18, + "using_bundled": false, + "warnings": [ + "18 catalog entries past 90d review cadence", + "17 interim delegations need re-verifying (risk-scaled blocker cadence) \u2014 see `warden route gaps`" + ] + }, + "candidates": [ + { + "id": "openbao-platform-admin-login", + "title": "Attended OpenBao platform administration login", + "score": 9, + "status": "active", + "resolvable": true, + "exec_capable": true, + "warden_executes": false, + "lane": "login", + "risk": "high" + }, + { + "id": "openbao-structure-survey", + "title": "OpenBao existing-structure survey for a construction plan (section 2)", + "score": 9, + "status": "active", + "resolvable": false, + "exec_capable": false, + "warden_executes": false, + "lane": "ceremony", + "risk": "high" + }, + { + "id": "openbao-api-key", + "title": "API key, DB credential, or dynamic lease", + "score": 6, + "status": "active", + "resolvable": false, + "exec_capable": true, + "warden_executes": false, + "lane": "secret", + "risk": "high" + }, + { + "id": "openbao-shamir-recovery-ceremony", + "title": "Attended OpenBao Shamir seal and unseal recovery ceremony", + "score": 6, + "status": "active", + "resolvable": false, + "exec_capable": false, + "warden_executes": false, + "lane": "ceremony", + "risk": "high" + }, + { + "id": "ops-warden-warden-sign-token", + "title": "Scoped OpenBao token for ops-warden SSH signing (warden-sign)", + "score": 6, + "status": "active", + "resolvable": false, + "exec_capable": false, + "warden_executes": false, + "lane": "secret", + "risk": "high" + } + ], + "reasons": [ + "login lane requires interactive founder/operator identity act" + ], + "actor": null, + "domain": null +} diff --git a/docs/runbooks/flex-auth-envelope-signing-attended-creation.md b/docs/runbooks/flex-auth-envelope-signing-attended-creation.md new file mode 100644 index 0000000..2238688 --- /dev/null +++ b/docs/runbooks/flex-auth-envelope-signing-attended-creation.md @@ -0,0 +1,143 @@ +# Attended creation: flex-auth decision-envelope signing key (CCR-2026-0050) + +Prepared 2026-10-21 under RPF-WP-0035-T14 (founder gate) for the custody route +`flex-auth-decision-envelope-signing-key`. **Nothing in this file has been +executed.** Every step that changes OpenBao or the cluster needs the founder's +explicit go for that change, given at the time of the change. No value of the +seed is printed, stored in Git, the hub, chat or a log at any point. + +Inputs that are fixed by reviewed source: + +| Item | Value | +| --- | --- | +| Read CCR | `credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml` | +| Native action | `docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json` (`RPF35-PDP-SIGNER-NATIVE-20261016-R1`) | +| KV document | `platform/data/workloads/flex-auth/envelope-signing`, fields `ED25519_PRIVATE_KEY`, `ED25519_PUBLIC_KEY`, `kid` | +| kid | `flex-auth-lifecycle-20261016-r1` | +| Metadata contract | `cas_required=true`, `max_versions=2`, `delete_version_after=0s`; bootstrap is CAS 0 | +| Read policy | `workload-kv-read-flex-auth-envelope-signing` (`openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl`) | +| Read role | `auth/kubernetes/role/external-secrets-flex-auth-envelope-signing`, SA `flex-auth-envelope-signing-eso` in `flex-auth`, audience `openbao`, 15m, no default policy | +| ESO projection | `docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed` (seed property only) | +| Writer | `scripts/flex_auth_signer_native.py` + `scripts/flex_auth_signer_custody.py` (`sha256:f169ad12…110c`, as pinned in the native plan) | +| Route | `warden access openbao-platform-admin-login --exec -- `; routing receipt `docs/evidence/2026-10-21-flex-auth-signer-warden-route.json` (`sha256:ad7d6a1829de53678b008b23627475e7570c71cf50f9f2ad5301635022a7f467`, lane `openbao-platform-admin-login`, verdict `founder_required`, act `oidc_login`) | +| Private receipt dir | `/home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing` (0700, files 0600) | + +The 2026-10-16 routing blocker (warden selected `openbao-structure-survey`) no +longer reproduces: the 2026-10-21 plan query selects the correct lane. + +## 0. Preconditions (all must hold; any miss = stop) + +1. Five dispositions on CCR-2026-0050 recorded in `review.comments`: + founder, railiance-platform-owner, access-engine-owner, secrets-engine-owner, + cluster-runtime-storage-owner (railiance-cluster). +2. Founder decision on the hub (see RPF-WP-0035-T14) resolved with + `APPROVE: CCR-2026-0050 ...`; then a platform session, not the requester, + runs `make credential-change-sync-decision CREDENTIAL_CHANGE=CCR-2026-0050`. +3. Access (FLEX-WP-0024-T06) has the retained deployment + `flex-auth/flex-auth-secrets-engine` on the immutable candidate image + `forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b27…981f` **stopped**: + replicas 0, no owned ReplicaSet replicas, no pods using the signer SA or + mounting the seed, no HPA. Protected consumers (secrets-engine) fenced. + GitOps/Helm writers for the release frozen for the window. +4. Cluster owner has reviewed the outbox PVC (1Gi local-path) and PV Retain. +5. Secrets owner is ready to admit the public receipt through + `secrets-engine/docs/founder-admission.md` (SECRETS-WP-0014-T04, + decision e4eee965). +6. Local tree is clean on the reviewed commit; tests pass: + `uv run python -m pytest -q tests/test_flex_auth_signer_native.py tests/test_flex_auth_signer_custody.py`. + +## 1. Read lane: policy and role (founder go #1) + +```bash +make credential-change-status CREDENTIAL_CHANGE=CCR-2026-0050 # expect status approved +make credential-change-applier-dry-run CREDENTIAL_CHANGE=CCR-2026-0050 +make credential-change-applier-apply-plan CREDENTIAL_CHANGE=CCR-2026-0050 +# founder go, then: +warden access openbao-platform-admin-login --exec -- \ + python3 scripts/credential-change.py applier-apply CCR-2026-0050 \ + --actor founder --record-state-hub +# the founder types the confirmation phrase the tool prompts for +``` + +Then verify native readback of the bound claims (no JWT or Secret printed) and +record it: `python3 scripts/credential-change.py confirm-binding CCR-2026-0050 +--reviewer railiance-platform-owner --comment ""`. + +Rollback: delete role `external-secrets-flex-auth-envelope-signing`, then +policy `workload-kv-read-flex-auth-envelope-signing`. No KV data exists yet. + +Negatives (record with `record-evidence`): login with a wrong SA, namespace and +audience is refused; with the right identity, sibling path read, `list`, +`metadata` read, write and delete are denied. + +## 2. Seed creation (founder go #2; single attended window) + +Do this inside one window of at most 120 seconds after building the admission. + +1. Founder builds the admission file (owner-private, 0600, outside Git) in the + schema `railiance-platform.signer-native-admission.v1` with + `action=bootstrap`, the kid above, the routing receipt digest above, the + immutable image, `caller_binding` and the five `owner_reviews`. Each review + binds `review_scope_digest(admission)`, which excludes `owner_reviews` and + `expires_at`, so reviewers can approve the exact scope ahead of the window. + Set `expires_at` to now + 90 s last. +2. Run, in the founder's terminal only: + +```bash +warden access openbao-platform-admin-login --exec -- \ + python3 scripts/flex_auth_signer_native.py \ + --admission --admission-sha256 sha256: \ + --receipt-directory /home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing +``` + +The child checks the cluster ID, unseal, non-root platform-admin entity, audit +enabled, read policy/role readback and the stop fence (rechecked before +metadata setup and before CAS). It requires metadata absence (HTTP 404), sets the +metadata contract, generates the seed in memory, writes with CAS 0, reads back +version 1, and writes a receipt that holds public fields only. It is silent; the +outcome is the receipt `status`: + +- `custody-verified-pending-independent-public-admission`: success. +- anything else, or no receipt: **unknown**. Keep everything fenced. Do not + rerun bootstrap. Use the read-only `recover` action with a fresh admission + that binds the original receipt digest. + +Rollback before CAS: abort; nothing private was written. After CAS: no restore +and no automatic retry. Forward-only recovery under a newly reviewed action. + +## 3. Public receipt for secrets-engine (no private read) + +Derive the consumer receipt from the writer receipt, never from a KV read (the +KV document also holds the seed and reads are not field-filtered): + +```bash +R=/home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing/.json +jq '{kid, public_key, public_fingerprint, kv_version, writer_sha256, + image_sha256: .bindings.image_sha256, policy_sha256: .bindings.policy_sha256, + executed_revision_sha256: .bindings.executed_revision_sha256, + deployment_sha256: .bindings.deployment_sha256, proposal_sha256: .bindings.proposal_sha256}' "$R" \ + > flex-auth-envelope-signing-public-receipt.json +``` + +Then add the policy pin fields secrets-engine requires (`evaluator`, `mode`, +`policy_package`, `policy_version`, `policy_package_digest`) from Access's +published pin for the candidate release; these are not in the writer receipt. +`public_key` is unpadded base64url, 43 characters. `public_fingerprint` is +`sha256:` + hex SHA-256 of that ASCII string, not of the raw bytes. The founder +delivers the file to the secrets-engine owner over an authenticated channel and +signs the consumer trust file per `secrets-engine/docs/founder-admission.md`. + +## 4. Delivery and signer start (founder go #3, Access-owned) + +1. Access/GitOps applies the reviewed ESO projection (SA, SecretStore, + ExternalSecret). Do not use client-side apply on Secrets. +2. Check readiness with the safe helpers only: + `scripts/eso_adoption_readiness.py` and + `scripts/secret_annotation_maintenance.py` (inspect mode). Never run + go-template or jsonpath against the Secret. +3. Access restarts the single signer replica. Verify kid/source/image, a signed + non-effect lifecycle allow and deny, and independent consumer verification + before reopening protected consumers. + +Negatives: unknown or old kid, wrong policy, tampered, expired or unsigned +envelope, wrong caller and missing seed are all refused. No taskpilot enablement. diff --git a/scripts/flex_auth_signer_native.py b/scripts/flex_auth_signer_native.py index 4f1e79c..4fc8de6 100644 --- a/scripts/flex_auth_signer_native.py +++ b/scripts/flex_auth_signer_native.py @@ -72,6 +72,14 @@ def parsed(raw): except Exception:raise Refused('invalid closed JSON input') from None +def review_scope_digest(value): + """Owner reviews bind the exact action scope minus the reviews themselves and the + invocation-time expiry. The 5..120s expiry stays enforced separately; binding it + into the reviewed digest made five asynchronous reviews impossible to collect + inside the attended window (2026-10-21 build-mode fix, close-down checklist).""" + return digest(encoded({k:v for k,v in value.items() if k not in {'owner_reviews','expires_at'}})) + + def validate_admission(value): fields={'schema','action','operation_id','kid','expires_at','founder_entity_id','ccr_sha256','source_bundle','bindings', 'producer_image','caller_binding','deployment_projection_sha256','authority','routing','owner_reviews', @@ -93,7 +101,7 @@ def validate_admission(value): is_digest(review['approved_admission_sha256']) and type(review['approval_ref']) is str and 0