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

@ -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 |

View file

@ -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

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.

View file

@ -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<len(review['approval_ref'])<=200,'exact external review required')
# Dispositions bind a claim-free scope digest, avoiding self-referential JSON.
scope={k:v for k,v in value.items() if k!='owner_reviews'};scope_digest=digest(encoded(scope))
scope_digest=review_scope_digest(value)
require(all(x['approved_admission_sha256']==scope_digest for x in reviews.values()),'owner review scope mismatch')
require(type(value['source_bundle']) is dict and set(value['source_bundle'])==SOURCE_FILES and
all(is_digest(x) for x in value['source_bundle'].values()),'exact execution source bundle required')

View file

@ -78,7 +78,7 @@ def native(bao_dev,tmp_path,monkeypatch):
if value['action']=='recover' and value['original_execution'] is None:
old=json.loads((directory/(c['operation_id']+'.json')).read_bytes())
value['original_execution']={key:deepcopy(old[key]) for key in ['bindings','writer_sha256','source_ccr_sha256']}
scope=m.digest(m.encoded({k:v for k,v in value.items() if k!='owner_reviews'}))
scope=m.review_scope_digest(value)
value['owner_reviews']={role:{'approved_admission_sha256':scope,'approval_ref':'synthetic-fixture-'+role} for role in m.REVIEWS}
return value
approve(c)
@ -333,3 +333,14 @@ def test_exact_runtime_storage_owner_slot_replaces_conflated_role(native):
with pytest.raises(Refused,match='distinct owner reviews'):
m.validate_admission(extra)
assert io.calls==[]
def test_owner_reviews_survive_fresh_expiry_but_not_scope_change(native):
c,io,directory,call,approve,source=native
reviewed=m.review_scope_digest(c)
c['expires_at']=(datetime.now(timezone.utc)+timedelta(seconds=90)).isoformat()
assert m.review_scope_digest(c)==reviewed
m.validate_admission(c)
changed=deepcopy(c);changed['kid']='other-native-kid'
assert m.review_scope_digest(changed)!=reviewed
with pytest.raises(m.Refused):m.validate_admission(changed)

View file

@ -8,7 +8,7 @@ status: blocked
flavor: implementation
owner: codex
created: "2026-09-05"
updated: "2026-10-13"
updated: "2026-10-21"
related:
- RPF-WP-0032
- RPF-WP-0033
@ -1405,3 +1405,41 @@ eso-token-renewer stay unchanged. Exact CCR read grant and one-use custody write
remain separate. Actual CCR/founder/entity/expiry/window/fence, native ESO/readback,
independent Secrets public trust and Access/cluster runtime proofs remain gates.
No authority/admission field changes; T12 remains progress.
## Founder approval of the flex-auth envelope signing key custody scope (CCR-2026-0050)
```task
id: RPF-WP-0035-T14
status: wait
priority: high
needs_human: true
depends_on: [RPF-WP-0035-T12]
decision_id: 18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508
```
Human gate for custody route `flex-auth-decision-envelope-signing-key`. The
founder resolves hub decision `18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508`, which
states the exact new scope: KV document
`platform/data/workloads/flex-auth/envelope-signing` (seed, public key, kid;
CAS-required, max 2 versions), one attended CAS0 bootstrap with kid
`flex-auth-lifecycle-20261016-r1`, read policy
`workload-kv-read-flex-auth-envelope-signing` and Kubernetes role
`external-secrets-flex-auth-envelope-signing` (SA `flex-auth-envelope-signing-eso`,
namespace `flex-auth`, audience `openbao`, 15m). Approval is scope only; each
live step in `docs/runbooks/flex-auth-envelope-signing-attended-creation.md`
still needs the founder's go at the time.
2026-10-21 state: railiance-platform-owner approved (runbook review);
secrets-engine-owner approved with two receipt conditions (hub message
e73c9ffc, recorded in the CCR); access-engine-owner disposition awaited;
cluster-runtime-storage-owner (railiance-cluster) review requested in hub
message f8b4b229. The warden routing defect from 2026-10-16 no longer
reproduces (`docs/evidence/2026-10-21-flex-auth-signer-warden-route.json`).
The native adapter's owner-review digest no longer binds `expires_at`, so
reviews can be given ahead of the 120 s attended window; the expiry check is
unchanged. Recorded as a build-mode change in the-custodian
`docs/security-close-down-checklist.md`. Nothing generated, written or applied.
Done when: the decision is resolved, `sync-decision` copies it into the CCR,
all five dispositions are recorded, and the runbook preconditions hold.