RPF-WP-0035-T14: flex-auth signer custody founder gate, runbook and review dispositions
All checks were successful
CCR intake gate / gate (push) Successful in 13s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

- 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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 809600@bnt-lap001
Assistant-Session: 40ba228f-c8a0-4ec9-bb29-3129951afdc4
This commit is contained in:
codex 2026-10-21 01:24:45 +02:00
parent 1228cd8407
commit 20db368aee
7 changed files with 317 additions and 5 deletions

View file

@ -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 -- <reviewed-command>",
"# 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 -- <reviewed-command>",
"desk_hint": "warden desk --from-plan (act=oidc_login); execute only through: warden access openbao-platform-admin-login --exec -- <reviewed-command>"
}
},
"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
}

View file

@ -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 -- <reviewed-command>`; 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 "<readback summary>"`.
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 <abs-path-admission.json> --admission-sha256 sha256:<hex> \
--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/<operation_id>.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.