Revise KeyCape secrets-engine-openbao custody: OpenBao-only, operator reader CCR-2026-0034 (source only)
Founder direction 2026-10-06 and ops-mason plan keycape-secrets-engine-openbao-client-custody (658cdbc, MASON-WP-0009): the client secret lives only in OpenBao. - CCR-2026-0033: consumer file and secrets-engine D1-D4 removed; KV metadata declared (cas_required, max_versions=2, delete_version_after=0s, custom_metadata); target.domain infotech; live order and roll-forward rollback revised; status stays proposed. - New sibling CCR-2026-0034 + policy: attended OIDC operator reader on auth/netkingdom, read data path + revoke-self only, 5m/4 uses, no default policy, bound groups=net-kingdom-admins and /assurance/level=aal2 (KeyCape 7d96ee4). Proposed, bound claims unconfirmed (founder item for both readers). - credential-change.py: OIDC roles carry token_max_ttl/explicit_max_ttl/no_default_policy/ num_uses/type and bound_claims_type (string only); token_revoke_self policy option; validated openbao.kv_metadata. Tests added. - Custody child: consumer write and check-secret-file removed; cas=0 create / --rotate cas=<current>; compare-before-write metadata put; refusals unchanged. - Founder runbook (keycape/founder-runbook.md), window docs, RPF-WP-0035-T09 (waits on A1/A2/A3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 132360@bnt-lap001 Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
This commit is contained in:
parent
9e23eaa067
commit
4a284f0a3b
11 changed files with 734 additions and 307 deletions
|
|
@ -47,27 +47,43 @@ the refusal. This is separate from actual provider registration/custody.
|
|||
The KeyCape packet's candidate path `platform/services/keycape/secrets-engine-openbao`
|
||||
is outside the platform-mount applier scope (`platform/workloads/` only) and was
|
||||
corrected on 2026-10-06 to `platform/workloads/keycape/secrets-engine-openbao`,
|
||||
field CLIENT_SECRET. Consumers bind to the sso Secret and the consumer file, not
|
||||
the KV path. The attended custody child
|
||||
`scripts/keycape_secrets_engine_openbao_custody.py` (source only, never run) and
|
||||
the secrets-engine consumer copy (SECRETS-WP-0012, a3897c2) are recorded in
|
||||
CCR-2026-0033 with the explicit live order.
|
||||
Proposed CCR-2026-0033 and input
|
||||
`docs/credential-lane-designs/keycape-secrets-engine-openbao-issuer-input.yaml`
|
||||
declare only the issuer reader. Policy
|
||||
`workload-kv-read-keycape-secrets-engine-openbao-eso` reads exactly that data path.
|
||||
Role `external-secrets-keycape-secrets-engine-openbao` binds the existing
|
||||
external-secrets/external-secrets identity, audience openbao, 15-minute
|
||||
TTL/max/explicit max and no default policy. The proposed store admits only sso;
|
||||
target Secret keycape-secrets-engine-openbao-client projects client-secret.
|
||||
Target annotations are explicit. Auth payload, ESO and receipt checklist are
|
||||
prepared under this window's `keycape/` directory and outside active GitOps.
|
||||
field CLIENT_SECRET.
|
||||
|
||||
CCR-2026-0033 is proposed, claims unconfirmed and frontdoor unresolvable.
|
||||
No value exists by implication of this draft. The service client custody child
|
||||
cannot be finalized before the secrets-engine consumer coordinates are accepted:
|
||||
issuer-side ESO does not supply a host/path/mechanism for the consumer copy.
|
||||
No guessed runtime directory, automatic token seed or live registration is added.
|
||||
**Revised 2026-10-06 (founder direction: the secret lives only in OpenBao; ops-mason
|
||||
plan `keycape-secrets-engine-openbao-client-custody`, 658cdbc, MASON-WP-0009).**
|
||||
The secrets-engine consumer file and secrets-engine D1-D4 are withdrawn. Two readers:
|
||||
|
||||
- Issuer: CCR-2026-0033, unchanged ESO reader. Policy
|
||||
`workload-kv-read-keycape-secrets-engine-openbao-eso`, Kubernetes role
|
||||
`external-secrets-keycape-secrets-engine-openbao` (external-secrets/external-secrets,
|
||||
audience openbao, 15m, no default policy). The store admits sso only; target Secret
|
||||
`sso/keycape-secrets-engine-openbao-client` key `client-secret`. It must stay on
|
||||
Kubernetes auth.
|
||||
- Operator: sibling CCR-2026-0034, OIDC role
|
||||
`keycape-secrets-engine-openbao-operator-workload-kv-read` on `auth/netkingdom`.
|
||||
Policy `workload-kv-read-keycape-secrets-engine-openbao-operator` grants read on
|
||||
the data path and update on `auth/token/revoke-self` only. The token is 5m, 4 uses,
|
||||
service type, with no default policy. Bound claims are `groups=net-kingdom-admins`
|
||||
and `/assurance/level=aal2` (KeyCape 7d96ee4: MFA is not pinned on the openbao-admin
|
||||
client). secrets-engine reads the value into memory per command, with no copy
|
||||
at rest.
|
||||
|
||||
CCR-2026-0033 now declares the KV metadata: `cas_required=true`, `max_versions=2`,
|
||||
`delete_version_after=0s`, and custom_metadata as in the plan. Its target.domain is
|
||||
corrected to infotech. The custody child
|
||||
`scripts/keycape_secrets_engine_openbao_custody.py` (source only, never run) writes
|
||||
KV with `cas=0`, or with `cas=<current>` under `--rotate`, then compares the
|
||||
metadata before it writes. It writes no consumer file. Both CCRs remain proposed with
|
||||
bound claims unconfirmed. Confirming them is a founder item for both readers.
|
||||
|
||||
Founder approvals: A1 design, A2 both CCRs, A3 custody-child decision id (not
|
||||
632a8893). Live order: custody child → platform applies policies/roles/ESO (Secret
|
||||
sso/keycape-secrets-engine-openbao-client exists) → NetKingdom env patch (NK-WP-0046)
|
||||
→ KeyCape registration merge (KEY-WP-0037-T03) → KeyCape restart immediately → live
|
||||
issuance check (KEY-WP-0037-T04) → OpenBao JWT role/login proof (RPF-WP-0035-T02).
|
||||
Exact founder commands, including the metadata-only survey:
|
||||
`keycape/founder-runbook.md`. Payloads and ESO stay outside active GitOps in this
|
||||
window's `keycape/` directory. No value exists by implication of these drafts.
|
||||
|
||||
## Dependency boundary
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
# Founder runbook: KeyCape `secrets-engine-openbao` client secret custody
|
||||
|
||||
Source only. Nothing here has been run. Every command below is run by the
|
||||
founder, attended, from `/home/worsch/railiance-platform` at the reviewed
|
||||
commit. No command prints a secret value. Design: ops-mason
|
||||
`plans/keycape-secrets-engine-openbao-client-custody.md` (658cdbc, MASON-WP-0009).
|
||||
CCRs: `credential-change-requests/CCR-2026-0033.yaml` (authoritative custody +
|
||||
issuer ESO reader) and `CCR-2026-0034.yaml` (attended operator OIDC reader).
|
||||
|
||||
## Gates before any command
|
||||
|
||||
- **A1** approve the ops-mason design.
|
||||
- **A2** approve CCR-2026-0033 and CCR-2026-0034, each with platform-owner,
|
||||
key-cape-owner and net-kingdom-owner reviews, and confirm the bound claims of
|
||||
**both** readers (`bound_claims_confirmed: true`):
|
||||
- CCR-2026-0033: Kubernetes SA `external-secrets/external-secrets`, audience `openbao`.
|
||||
- CCR-2026-0034: `groups=net-kingdom-admins` **and** `/assurance/level=aal2`
|
||||
(`bound_claims_type: string`). KeyCape (7d96ee4) reports that MFA is not pinned on
|
||||
the openbao-admin client, so this aal2 binding is the lane's MFA guarantee.
|
||||
- **A3** a founder decision id for the one custody-child run, recorded in an
|
||||
approved review comment of CCR-2026-0033. It must differ from boundary
|
||||
decision `632a8893-0d6a-4591-a50b-40bbdb798f32`. Written below as `<A3-decision-id>`.
|
||||
|
||||
## Live order
|
||||
|
||||
1. Custody child (section B) writes KV v1 plus metadata.
|
||||
2. Platform applies the policies/roles of both CCRs and the sso-only ESO
|
||||
(section C). Secret `sso/keycape-secrets-engine-openbao-client` exists.
|
||||
3. NetKingdom env patch (NK-WP-0046).
|
||||
4. KeyCape registration merge (KEY-WP-0037-T03).
|
||||
5. KeyCape restart **immediately** after step 4. Between the KV write and the
|
||||
restart, exchanges fail closed (401).
|
||||
6. Live issuance check (KEY-WP-0037-T04).
|
||||
7. OpenBao JWT role/login proof (RPF-WP-0035-T02).
|
||||
|
||||
Steps 3 to 7 belong to their owners and each needs its own go.
|
||||
|
||||
## A. Metadata-only survey (read-only; before step 1)
|
||||
|
||||
ops-mason's survey generator refuses data paths and runs under the scoped,
|
||||
value-blind `ops-mason-build` grant:
|
||||
|
||||
```bash
|
||||
cd /home/worsch/ops-mason
|
||||
./scripts/bao-session.sh grant MASON-WP-0009-T04
|
||||
./scripts/plan-survey.py --kv-path platform/workloads/keycape/secrets-engine-openbao \
|
||||
--policy workload-kv-read-keycape-secrets-engine-openbao-operator \
|
||||
--k8s-role external-secrets-keycape-secrets-engine-openbao \
|
||||
--consumer secrets-engine --keyword keycape --keyword secrets-engine-openbao
|
||||
./scripts/bao-session.sh revoke
|
||||
```
|
||||
|
||||
Expected: a Markdown survey block. The block shows KV metadata **absent** at the
|
||||
path (otherwise the first run must not be used; see `--rotate`). It also shows no
|
||||
existing role or policy with the two proposed names, and no `auth/jwt` role touching
|
||||
the path. Paste it into MASON-WP-0009-T04. It contains no value.
|
||||
|
||||
## B. Attended custody run (step 1)
|
||||
|
||||
Pre-check, offline (no OpenBao call): both CCRs validate and the child is the
|
||||
reviewed revision.
|
||||
|
||||
```bash
|
||||
python3 scripts/credential-change.py validate credential-change-requests/CCR-2026-0033.yaml
|
||||
python3 scripts/credential-change.py validate credential-change-requests/CCR-2026-0034.yaml
|
||||
python3 -m pytest -q tests/test_keycape_secrets_engine_openbao_custody.py
|
||||
git log -1 --format=%H -- scripts/keycape_secrets_engine_openbao_custody.py
|
||||
```
|
||||
|
||||
Expected: `[OK]` for both with **no** "bound claim is not confirmed" warning;
|
||||
tests pass. The child has no separate dry-run mode. It refuses before any OpenBao
|
||||
call when the decision id is missing, wrong or unrecorded, or when either CCR is
|
||||
not approved and confirmed. It also refuses outside the attended envelope or when
|
||||
`BAO_TOKEN`/`VAULT_TOKEN` is set.
|
||||
|
||||
Run (first write, `cas=0`):
|
||||
|
||||
```bash
|
||||
set +x; umask 077; unset BAO_TOKEN VAULT_TOKEN
|
||||
warden access openbao-platform-admin-login --exec -- \
|
||||
python3 scripts/keycape_secrets_engine_openbao_custody.py provision \
|
||||
--approval-decision <A3-decision-id> \
|
||||
--receipt /home/worsch/.local/state/railiance/receipts/CCR-2026-0033-custody-$(date -u +%Y%m%dT%H%M%SZ).json
|
||||
```
|
||||
|
||||
Rotation uses the same command with `--rotate` (`cas=<observed current version>`)
|
||||
under a standing rotation approval for CCR-2026-0033. It is roll-forward only.
|
||||
|
||||
Expected: exit 0, no output, and a new mode-0600 receipt (create its parent
|
||||
directory 0700 first if absent):
|
||||
|
||||
```json
|
||||
{"schema": "platform.keycape-secrets-engine-openbao-custody.v1", "ccr": "CCR-2026-0033",
|
||||
"status": "custody_written_pending_issuer_delivery", "phase": "done",
|
||||
"credential_values_emitted": false, "client_side_read_admitted": false,
|
||||
"approval_decision_id": "<A3-decision-id>", "ccr_sha256": "<digest>",
|
||||
"kv_written": true, "kv_version": 1, "kv_cas": 0, "kv_request_id": "<id>",
|
||||
"kv_metadata": "applied", "started_at": "...", "finished_at": "..."}
|
||||
```
|
||||
|
||||
On `status: failed`, read `error` and `phase`. A failure with `kv_written: true`
|
||||
keeps that version. Recover only with a fresh `--rotate` run, never by reading
|
||||
the value.
|
||||
|
||||
## C. Policies, roles and ESO (step 2)
|
||||
|
||||
Policies and roles go through the sanctioned delegated applier. It refuses
|
||||
unapproved CCRs and unconfirmed bindings, and it diffs the policy files against
|
||||
the generated bodies.
|
||||
|
||||
```bash
|
||||
# dry-run (offline review of policy diff and role payload, incl. token_num_uses=4, aal2 claim)
|
||||
python3 scripts/credential-change.py applier-dry-run credential-change-requests/CCR-2026-0033.yaml
|
||||
python3 scripts/credential-change.py applier-dry-run credential-change-requests/CCR-2026-0034.yaml
|
||||
python3 scripts/credential-change.py applier-apply credential-change-requests/CCR-2026-0034.yaml --plan-only
|
||||
|
||||
# apply (attended platform-admin; one phrase per CCR)
|
||||
warden access openbao-platform-admin-login --exec -- \
|
||||
python3 scripts/credential-change.py applier-apply credential-change-requests/CCR-2026-0033.yaml \
|
||||
--confirm "DELEGATED APPLY CCR-2026-0033" --actor founder --record-state-hub
|
||||
warden access openbao-platform-admin-login --exec -- \
|
||||
python3 scripts/credential-change.py applier-apply credential-change-requests/CCR-2026-0034.yaml \
|
||||
--confirm "DELEGATED APPLY CCR-2026-0034" --actor founder --record-state-hub
|
||||
```
|
||||
|
||||
Expected: `[OK] CCR-2026-0033.yaml delegated metadata apply recorded`, the same for
|
||||
0034, and a value-free evidence entry in each CCR.
|
||||
|
||||
ESO: `eso.draft.yaml` in this directory carries `railiance.io/admission:
|
||||
pending-review`. Promote it to `approved` in a reviewed commit after A2 first. Then:
|
||||
|
||||
```bash
|
||||
kubectl apply --dry-run=server -f docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml
|
||||
kubectl apply -f docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml
|
||||
kubectl -n sso get externalsecret keycape-secrets-engine-openbao-client \
|
||||
-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}{"\n"}'
|
||||
kubectl -n sso get secret keycape-secrets-engine-openbao-client -o name
|
||||
```
|
||||
|
||||
Expected: `ClusterSecretStore ... created (server dry run)` and `ExternalSecret ...
|
||||
created (server dry run)`, then the real apply, then `True`, then
|
||||
`secret/keycape-secrets-engine-openbao-client`. Never use another output format
|
||||
or a template on the Secret.
|
||||
|
||||
After step 2, ops-mason runs its capabilities-only verification (MASON-WP-0009-T04).
|
||||
It checks operator-token read on the data path and deny everywhere else, and that a
|
||||
non-member or non-aal2 identity cannot obtain the role. It also checks the
|
||||
circularity invariants and the metadata settings.
|
||||
|
||||
## Rollback (roll-forward; never a read of an old value)
|
||||
|
||||
Before step 3: delete the CCR-2026-0034 OIDC role, then its policy. Delete the
|
||||
ExternalSecret (uid precondition), then the ESO role and policy. The KV entry is
|
||||
kept until a separate destroy approval. After step 3: the NetKingdom rollout
|
||||
helper restores the prior config/Deployment first, then the same sequence.
|
||||
Loading…
Add table
Add a link
Reference in a new issue