diff --git a/credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml b/credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml new file mode 100644 index 0000000..01982df --- /dev/null +++ b/credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml @@ -0,0 +1,158 @@ +id: CCR-2026-0019 +kind: credential-change-request +schema_version: 1 +request_type: workload-kv-read +title: secrets-engine client-side read of its approval client secret +status: in_flight +created: '2026-09-09' +updated: '2026-09-09' +in_flight: + missing_fields: + - openbao.auth + blocking_reason: >- + The reader is an attended operator running the secrets-engine CLI on a + workstation, not an in-cluster identity. The auth binding is therefore + determined in shape but not bindable: method oidc on mount netkingdom, role + secrets-engine-approval-client-read, the three standard NetKingdom redirect + URIs, scopes openid/profile/email/groups, user_claim sub, groups_claim + groups, policy workload-kv-read-secrets-engine-approval-client, ttl 15m -- + with the one missing input being the exact group claim for the authorized + operator, which NetKingdom/KeyCape own. A role cannot be created without it + and no placeholder is recorded here. Live inspection on 2026-09-09 found namespace + secrets-engine holding only ServiceAccount secrets-engine with no workload, + which confirms there is no in-cluster consumer to bind instead. + owner: railiance-platform +requester: + agent: claude + reason: >- + RPF-WP-0035-T06 residual from the completed verifier custody + (CCR-2026-0017). The verifier copy lets KeyCape authenticate the + secrets-engine-approval client; it does not let secrets-engine present that + client. secrets-engine implemented the consumer on 2026-09-09 + (SECRETS-WP-0009-T03, repo revision 9eb07fd, + secrets-engine/docs/approval-service-auth.md) and reads the value from + SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE, an explicit protected file. This + request establishes the separate exact read that fills that file. It reuses + the existing version-1 custody and requests no reseed and no rotation. +review: + required: true + required_approvers: + - platform-operator + - secrets-engine-owner + comments: + - at: '2026-09-09' + reviewer: railiance-platform (claude) + decision: shape_determined_actor_pending + comment: >- + Reader shape determined from owner source rather than assumed: the + consumer is an operator-run CLI that exchanges the client secret for a + short-lived token immediately before each approval request, holds it only + in memory, and has no refresh token, renewal or fallback identity. That + makes this an attended operator-workstation lane on the OIDC mount, not an + External Secrets lane. The named operator group claim is the one input + neither this repo nor secrets-engine can supply; NetKingdom/KeyCape own it. +target: + domain: financials + tenant: platform + workload: secrets-engine + environment: production + purpose: >- + Let the authorized operator running the secrets-engine approval CLI fetch the + secrets-engine-approval client secret into a protected file, without granting + any in-cluster reader, any sibling path, or any write on the custody path. +openbao: + mount: platform + kv_path: platform/workloads/secrets-engine/approval-client + fields: + - CLIENT_SECRET + policy_name: workload-kv-read-secrets-engine-approval-client + policy_file: openbao/policies/workload-kv-read-secrets-engine-approval-client.hcl +access_frontdoor: + type: ops-warden + catalog_id: secrets-engine-approval-client + selector: secrets-engine approval client secret + command: warden access secrets-engine-approval-client --out FILE + resolvable: false + readiness: pending-review +delivery: + surface: operator-workstation + target: >- + Protected file consumed as SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE by the + secrets-engine approval CLI: owner-only mode, outside any Git work tree, + removed at the end of the session. No Kubernetes Secret, no ExternalSecret + and no reuse of the sso verifier Secret keycape-secrets-engine-approval-client. +risk: + classification: high + notes: + - >- + This is the presenting copy. Anyone holding it can obtain tokens as + service:secrets-engine for approval:read and approval:consume until KeyCape + disables the registration. The verifier copy admitted in CCR-2026-0017 does + not carry that power; the two must not be conflated because they hold the + same value. + - >- + A file-delivered credential outlives the process that read it. The lane + depends on operator hygiene for removal, which is weaker than an + ESO-delivered Secret bounded by a pod lifetime. That is a consequence of the + consumer being a workstation CLI and is recorded rather than engineered away. + - >- + approval:consume is a production-effect scope. This lane grants the ability + to present the client; it does not grant approval decisions, which remain + with access-engine, approval-engine and the existing consume gates. + - >- + No reseed and no rotation. Custody is the existing version 1 from the + verifier activation; a rotation is a distinct version-guarded operation that + also invalidates the KeyCape verifier copy and needs both owners. +verification: + positive: + - >- + The bound operator identity reads CLIENT_SECRET from the exact path into a + protected file with no value in chat, argv, shell history or logs. + - >- + The secrets-engine approval CLI exchanges it for a token with subject + service:secrets-engine, audience approval-engine, tenant tenant:platform and + the single requested scope, verified against live JWKS. + negative: + - >- + An identity outside the confirmed group claim cannot authenticate through + role secrets-engine-approval-client-read. + - >- + The role cannot read the approval-engine operator-client path, cannot list + any parent, and cannot write or patch the custody path. + - >- + No in-cluster ServiceAccount, including secrets-engine/secrets-engine, gains + a read through this lane. + - >- + The KeyCape verifier role cannot be substituted for this reader, and this + reader cannot read the verifier's delivery Secret. + activation_conditions: + - >- + NetKingdom/KeyCape confirm the exact group claim for the authorized operator + and it is recorded in openbao.auth.bound_claims with + bound_claims_confirmed true. + - >- + secrets-engine confirms the workstation procedure, file mode and removal step + for SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE. + - >- + Policy and OIDC role applied under attended authority + (openbao-platform-admin-login, founder_required) with metadata-only receipts. + - Positive and negative results recorded with non-secret request ids. + evidence: [] +lifecycle: + deactivate: >- + Detach the policy from role secrets-engine-approval-client-read and disable + the ops-warden catalog entry. The KeyCape verifier lane and its delivery are + unaffected. + rotate: >- + Shared with CCR-2026-0017: the value is one client secret. Rotation is minted + by KeyCape and rewritten once under attended authority; both the verifier + delivery and this reader then see the new version. No independent rotation + exists for this lane alone. + compromised: >- + KeyCape disables the secrets-engine-approval registration first, since that + is what stops token issuance, then the value is rotated and this role's + policy detached pending review. +state_hub: + workplan_id: RPF-WP-0035 + task_id: RPF-WP-0035-T06 + related_request: CCR-2026-0017 diff --git a/credential-change-requests/CCR-2026-0020-approval-engine-operator-client-read.yaml b/credential-change-requests/CCR-2026-0020-approval-engine-operator-client-read.yaml new file mode 100644 index 0000000..db25601 --- /dev/null +++ b/credential-change-requests/CCR-2026-0020-approval-engine-operator-client-read.yaml @@ -0,0 +1,145 @@ +id: CCR-2026-0020 +kind: credential-change-request +schema_version: 1 +request_type: workload-kv-read +title: Client-side read of the approval-engine-operator client secret +status: in_flight +created: '2026-09-09' +updated: '2026-09-09' +in_flight: + missing_fields: + - openbao.auth + blocking_reason: >- + The presenting actor is not identified by any owner source. approval-engine + verifies this client's tokens but never presents them, and no other repo + claims the approver surface that would. Live inspection on 2026-09-09 found + no approval-engine namespace, workload or Service in the cluster; the owner + deploy manifest targets namespace approval-engine and remains unapplied. An + auth method cannot be chosen before it is known whether the presenter is an + attended operator, an in-cluster component, or an agent surface, and the + three imply different roles, policies and delivery. + owner: railiance-platform +requester: + agent: claude + reason: >- + RPF-WP-0035-T06 residual from the completed verifier custody + (CCR-2026-0018). The verifier copy lets KeyCape authenticate the + approval-engine-operator client; something must also present it. This request + holds the client-side reader open with the determined parts recorded and the + undetermined parts named, rather than guessing a reader for the widest scope + set in the pair. It reuses existing version-1 custody; no reseed, no rotation. +review: + required: true + required_approvers: + - platform-operator + - approval-engine-owner + comments: + - at: '2026-09-09' + reviewer: railiance-platform (claude) + decision: actor_not_determined + comment: >- + Determined from owner source: the approval-engine-operator registration + holds approval:create, read, approve, revoke, supersede, observe and emit, + and explicitly not consume; approval-engine itself only verifies presented + tokens (approval_engine/api.py identity check) and does not hold this + client. approval-engine's own records note the engine does not restrict + /entries by principal type, so this client can supply approver evidence + today, and whether it should is approval doctrine owned by gate-house. The + reader therefore cannot be named here without an owner decision, and + naming one anyway would hand the widest approval scope to a guessed + identity. Held in_flight deliberately. +target: + domain: financials + tenant: platform + workload: approval-engine + environment: production + purpose: >- + Establish the separate exact read by which the eventual approval-engine + operator surface obtains its client secret, once that surface is named by its + owner. +openbao: + mount: platform + kv_path: platform/workloads/approval-engine/operator-client + fields: + - CLIENT_SECRET + policy_name: workload-kv-read-approval-engine-operator-client + policy_file: openbao/policies/workload-kv-read-approval-engine-operator-client.hcl +access_frontdoor: + type: ops-warden + catalog_id: approval-engine-operator-client + selector: approval-engine operator client secret + resolvable: false + readiness: pending-review +delivery: + surface: undetermined + target: >- + Not determined. The delivery surface follows the actor: an attended operator + implies a protected workstation file, an in-cluster component implies an + ExternalSecret into the eventual approval-engine namespace, and an agent + surface implies neither. No surface is admitted until the owner names one. +risk: + classification: high + notes: + - >- + This is the widest scope set in the pair: create, approve, revoke and + supersede. A holder can forge approval lifecycle actions, and because + approval-engine records principal_type rather than restricting it, a + non-human holder can supply approver evidence. That is why the actor is left + unnamed rather than defaulted. + - >- + approval:consume is absent by design and stays absent. Adding it is a new + lane decision, not an edit to this request. + - >- + The exact-path read policy is written and committed so the eventual reader + inherits a bounded grant rather than a broad one drafted under time pressure. + Writing the policy is not admitting a reader. + - >- + No reseed and no rotation; custody is the existing version 1 from the + verifier activation. +verification: + positive: + - >- + Once the actor is named, the bound identity reads CLIENT_SECRET from the + exact path into its admitted delivery surface with no value in chat, argv, + shell history or logs. + - >- + The named surface obtains a token with subject service:approval-engine-operator, + audience approval-engine and tenant tenant:platform, verified against live JWKS. + negative: + - >- + An identity outside the confirmed binding cannot authenticate through the + eventual role. + - >- + The role cannot read the secrets-engine approval-client path, cannot list any + parent, and cannot write or patch the custody path. + - The reader is denied approval:consume, and denial is observed rather than assumed. + - >- + The KeyCape verifier role cannot be substituted for this reader, and this + reader cannot read the verifier's delivery Secret. + activation_conditions: + - >- + The approval-engine owner names the presenting actor and its placement, or + records that no client-side reader is wanted and this request is cancelled. + - >- + Given that actor, the auth method, mount, role, bound claims and delivery + surface are completed and reviewed as a concrete request. + - >- + Policy and role applied under attended authority + (openbao-platform-admin-login, founder_required) with metadata-only receipts. + - Positive and negative results recorded with non-secret request ids. + evidence: [] +lifecycle: + deactivate: >- + Detach the policy from the eventual role and disable the ops-warden catalog + entry. The KeyCape verifier lane and its delivery are unaffected. + rotate: >- + Shared with CCR-2026-0018: one client secret, minted by KeyCape and rewritten + once under attended authority. No independent rotation for this lane alone. + compromised: >- + KeyCape disables the approval-engine-operator registration first, then the + value is rotated; emitted approval actions are referred to approval-engine + for audit review, since rotation does not retract them. +state_hub: + workplan_id: RPF-WP-0035 + task_id: RPF-WP-0035-T06 + related_request: CCR-2026-0018 diff --git a/docs/credential-lane-designs/README.md b/docs/credential-lane-designs/README.md index f617ffb..a8ae279 100644 --- a/docs/credential-lane-designs/README.md +++ b/docs/credential-lane-designs/README.md @@ -12,6 +12,7 @@ KeyCape approval-client admission below, which carries executable CCRs | [Fluid-telegram operator KV](fluid-telegram-operator-kv.md) | RPF-WP-0033 (finished) / RPF-WP-0035-T03 | MASON-WP-0005; FT-WP-0002 | Tenant acceptance, actual OIDC group, write-capable CCR support | | [State Hub preflight signing](state-hub-preflight-signing.md) | RPF-WP-0034 (finished) / RPF-WP-0035-T04 | FLEX-WP-0020-T05; STATE-WP-0085-T09 delivered | Deployment binding, owner-approved custody and rotation window | | [KeyCape approval-engine clients](keycape-approval-clients.md) | RPF-WP-0035-T05 | KEY-WP-0013-T02; GLAS-WP-0015 | Owner approval of CCR-2026-0017/0018, attended founder window, KeyCape reading build | +| [Approval client-side readers](keycape-approval-client-side-readers.md) | RPF-WP-0035-T06 | SECRETS-WP-0009-T03; APPROVAL-WP-0002 | Reader 1: exact operator group claim; Reader 2: the presenting actor is unnamed by any owner source | The completed design workplans are archived. RPF-WP-0035 is the canonical queue for owner review, implementation, and live acceptance still required. diff --git a/docs/credential-lane-designs/keycape-approval-client-side-readers.md b/docs/credential-lane-designs/keycape-approval-client-side-readers.md new file mode 100644 index 0000000..9b07cdf --- /dev/null +++ b/docs/credential-lane-designs/keycape-approval-client-side-readers.md @@ -0,0 +1,79 @@ +# Approval client-side readers + +RPF-WP-0035-T06, the residual handed over by the completed verifier custody +(T05). Requests: +[CCR-2026-0019](../../credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml) +and [CCR-2026-0020](../../credential-change-requests/CCR-2026-0020-approval-engine-operator-client-read.yaml). +Both are `in_flight`, neither is approved, and nothing is applied. + +## Why this exists as separate requests + +CCR-2026-0017/0018 admitted the **verifier** copies: KeyCape holds the two client +secrets so it can authenticate credentials presented to it. Presenting them is a +different power with a different holder, so it needs its own admission. The two +Warden fetch selectors that the verifier review left unresolved are exactly this +gap. Both readers use the **existing version-1 custody** — no reseed, no +rotation, no shared reader, no reuse of the `sso` verifier Secrets. + +## Survey, 2026-09-09 + +Read-only, against owner source and the live cluster. + +| Question | Finding | +| --- | --- | +| Is there an in-cluster secrets-engine consumer? | No. Namespace `secrets-engine` holds only ServiceAccount `secrets-engine` and no workload. `flex-auth/flex-auth-secrets-engine` is the flex-auth PDP for that consumer, not the service. | +| How does secrets-engine consume the client secret? | Operator-run CLI reading `SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE`, exchanged for a fresh token before each request, held in memory only, no refresh token or fallback identity. Implemented 2026-09-09, SECRETS-WP-0009-T03, `secrets-engine` revision `9eb07fd`. | +| Is approval-engine deployed? | No. No namespace, workload or Service. The owner manifest targets namespace `approval-engine` with a digest-pinned image and is unapplied. | +| Who presents `approval-engine-operator`? | Not identified by any owner source. approval-engine verifies these tokens and never presents them. | + +## Reader 1 — secrets-engine-approval (CCR-2026-0019) + +Shape is determined; one input is missing. + +| Item | Value | +| --- | --- | +| Path / field | `platform/workloads/secrets-engine/approval-client` / `CLIENT_SECRET` | +| Policy | `workload-kv-read-secrets-engine-approval-client` (exact data + metadata read, written and committed) | +| Auth | `oidc` on mount `netkingdom`, role `secrets-engine-approval-client-read`, standard three redirect URIs, scopes `openid profile email groups`, `user_claim: sub`, `groups_claim: groups`, ttl 15m | +| Front door | `warden access secrets-engine-approval-client --out FILE` | +| Delivery | Protected owner-only file outside any Git work tree, consumed as `SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE`, removed at end of session | + +**Missing:** the exact group claim for the authorized operator. NetKingdom and +KeyCape own it; this repo and secrets-engine cannot supply it. The role cannot +be created without it, so `openbao.auth` is declared missing rather than +recorded with a placeholder that would later be mistaken for a confirmed +binding. + +This is an operator-workstation lane, not an External Secrets lane — because the +consumer is a CLI, not a pod. A file-delivered credential outlives the process +that read it, so removal depends on operator hygiene. That is weaker than the +pod-lifetime bound of the verifier delivery and is recorded as a consequence of +the consumer's shape rather than engineered away. + +## Reader 2 — approval-engine-operator (CCR-2026-0020) + +Held open deliberately. The exact-path read policy +`workload-kv-read-approval-engine-operator-client` is written and committed so +the eventual reader inherits a bounded grant rather than one drafted under time +pressure. Writing the policy is not admitting a reader. + +**Missing:** the presenting actor and its placement. This client carries the +widest scope set in the pair — `approval:create`, `read`, `approve`, `revoke`, +`supersede`, `observe`, `emit`, and deliberately not `consume`. approval-engine +records `principal_type` on every entry rather than restricting who may supply +approver evidence, and its owner records that whether a non-human principal +*should* is approval doctrine belonging to `gate-house`. Naming a reader here +would hand the widest approval scope to a guessed identity, so the request names +what is undetermined instead. + +The delivery surface follows the actor: an attended operator implies a protected +workstation file; an in-cluster component implies an ExternalSecret into the +eventual `approval-engine` namespace; an agent surface implies neither. + +## Not in scope + +- No reseed or rotation of either custody path. Rotation is one version-guarded + operation shared with the verifier copies and needs both owners. +- No `approval:consume` on the operator client, now or by later scope edit. +- No reuse of the `sso` verifier Secrets or the KeyCape verifier roles. +- No audit-sender custody; that stays with the existing owner records. diff --git a/openbao/policies/workload-kv-read-approval-engine-operator-client.hcl b/openbao/policies/workload-kv-read-approval-engine-operator-client.hcl new file mode 100644 index 0000000..09a6ec2 --- /dev/null +++ b/openbao/policies/workload-kv-read-approval-engine-operator-client.hcl @@ -0,0 +1,7 @@ +path "platform/data/workloads/approval-engine/operator-client" { + capabilities = ["read"] +} + +path "platform/metadata/workloads/approval-engine/operator-client" { + capabilities = ["read"] +} diff --git a/openbao/policies/workload-kv-read-secrets-engine-approval-client.hcl b/openbao/policies/workload-kv-read-secrets-engine-approval-client.hcl new file mode 100644 index 0000000..e919636 --- /dev/null +++ b/openbao/policies/workload-kv-read-secrets-engine-approval-client.hcl @@ -0,0 +1,7 @@ +path "platform/data/workloads/secrets-engine/approval-client" { + capabilities = ["read"] +} + +path "platform/metadata/workloads/secrets-engine/approval-client" { + capabilities = ["read"] +} diff --git a/workplans/RPF-WP-0035-credential-lane-implementation.md b/workplans/RPF-WP-0035-credential-lane-implementation.md index 5cb2d81..8a2202d 100644 --- a/workplans/RPF-WP-0035-credential-lane-implementation.md +++ b/workplans/RPF-WP-0035-credential-lane-implementation.md @@ -246,7 +246,7 @@ Rotation is a distinct, version-guarded operation. ```task id: RPF-WP-0035-T06 -status: todo +status: wait priority: high needs_human: false intervention_note: "" @@ -265,6 +265,39 @@ consumer delivery, cleanup/revocation and rollback, then obtain the required owner reviews for those concrete requests. No reseeding, rotation, shared reader, reuse of the sso verifier Secret, or implicit operator consume grant. +2026-09-09 preparation return: both exact-reader admissions are prepared as +`CCR-2026-0019` (secrets-engine) and `CCR-2026-0020` (approval-engine-operator), +with their exact-path read policies written and committed. Design: +`docs/credential-lane-designs/keycape-approval-client-side-readers.md`. Both +reuse existing version-1 custody; no reseed, rotation, shared reader or verifier +Secret reuse. Both are `in_flight` and unapproved; nothing is applied. + +Read-only survey findings that decided the two shapes. Namespace +`secrets-engine` holds only ServiceAccount `secrets-engine` with no workload, +and `flex-auth/flex-auth-secrets-engine` is that consumer's PDP, not the +service; secrets-engine consumes the client secret through an operator-run CLI +reading `SECRETS_ENGINE_APPROVAL_CLIENT_SECRET_FILE` (SECRETS-WP-0009-T03, +revision `9eb07fd`). So reader 1 is an attended operator-workstation OIDC lane, +not an ESO lane. approval-engine has no namespace, workload or Service; its +manifest is unapplied, and no owner source names who presents the operator +client — approval-engine verifies these tokens and never holds them. + +**Unblock:** reader 1 needs the exact NetKingdom/KeyCape group claim for the +authorized operator, plus secrets-engine confirming the file mode and removal +step; `openbao.auth` is declared missing rather than filled with a placeholder +that would later read as a confirmed binding. Reader 2 needs its owner to name +the presenting actor and placement, or to record that no client-side reader is +wanted so the request is cancelled. Naming a reader for the widest approval +scope without that decision is the failure this task exists to avoid. + +**Done when:** each reader has a named actor and placement, a completed and +reviewed auth binding, admitted delivery, and its owner approvals; positive and +negative checks pass, including cross-path, wrong-identity and parent-list +denial and operator `consume` denial; or the request is explicitly cancelled by +its owner. Rotation stays a distinct version-guarded operation shared with +CCR-2026-0017/0018. + + Consumer input supplied 2026-09-09: secrets-engine now implements per-request `secrets-engine-approval` exchange from a protected temporary client-secret file, with exact approval-engine / tenant:platform / read-or-consume scope, no token