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
8.3 KiB
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)
- 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). - Founder decision on the hub (see RPF-WP-0035-T14) resolved with
APPROVE: CCR-2026-0050 ...; then a platform session, not the requester, runsmake credential-change-sync-decision CREDENTIAL_CHANGE=CCR-2026-0050. - Access (FLEX-WP-0024-T06) has the retained deployment
flex-auth/flex-auth-secrets-engineon the immutable candidate imageforgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b27…981fstopped: 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. - Cluster owner has reviewed the outbox PVC (1Gi local-path) and PV Retain.
- Secrets owner is ready to admit the public receipt through
secrets-engine/docs/founder-admission.md(SECRETS-WP-0014-T04, decision e4eee965). - 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)
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.
- Founder builds the admission file (owner-private, 0600, outside Git) in the
schema
railiance-platform.signer-native-admission.v1withaction=bootstrap, the kid above, the routing receipt digest above, the immutable image,caller_bindingand the fiveowner_reviews. Each review bindsreview_scope_digest(admission), which excludesowner_reviewsandexpires_at, so reviewers can approve the exact scope ahead of the window. Setexpires_atto now + 90 s last. - Run, in the founder's terminal only:
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
recoveraction 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). Use Access's
existing tool, which reads public fields only, together with Access's published
policy pin (access-engine docs/native-signer-public-pin.json: flex-auth/local,
standalone, secrets-engine.catalog-lane.lifecycle v2,
sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4):
R=/home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing/<operation_id>.json
python3 ~/access-engine/tools/prepare_signer_public_receipt.py --help # then run per its usage on "$R"
The result must carry kid, public key, fingerprint, KV version, writer and
source/image/deployment hashes and the pin fields (evaluator, mode,
policy_package, policy_version, policy_package_digest); these are the two
secrets-engine conditions recorded in the CCR.
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)
- Access/GitOps applies the reviewed ESO projection (SA, SecretStore, ExternalSecret). Do not use client-side apply on Secrets.
- Check readiness with the safe helpers only:
scripts/eso_adoption_readiness.pyandscripts/secret_annotation_maintenance.py(inspect mode). Never run go-template or jsonpath against the Secret. - 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.