From bbff2bfb93c7455b33cb9ca0a967391fd36a9e8d Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 14 Sep 2026 01:30:23 +0200 Subject: [PATCH] Bind secrets-engine approval reader to confirmed operator group Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6 --- ...9-secrets-engine-approval-client-read.yaml | 235 +++++++++--------- .../2026-09-14-ccr0019-operator-binding.json | 15 ++ scripts/apply-ccr0019-binding.py | 58 +++++ ...-WP-0035-credential-lane-implementation.md | 13 +- 4 files changed, 200 insertions(+), 121 deletions(-) create mode 100644 docs/evidence/2026-09-14-ccr0019-operator-binding.json create mode 100644 scripts/apply-ccr0019-binding.py 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 index f12e555..64b2ec8 100644 --- 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 @@ -3,37 +3,18 @@ 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 +status: applied created: '2026-09-09' -updated: '2026-09-10' -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 +updated: '2026-09-13' 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. + 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: @@ -43,37 +24,44 @@ review: - 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. + 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.' - at: '2026-09-10' reviewer: codex (consumer source review) decision: consumer_procedure_documented - comment: >- - Secrets Engine source 98d72ceb1dbcff2d2a80fb8c3058d76777a0ac93, - docs/approval-service-auth.md, supplies the requested workstation - procedure: operator-owned 0700 runtime session directory outside Git, - new 0600 file, path-only configuration across one claim/consume operation, - EXIT/INT/TERM cleanup, and explicit residual-file handling after a hard - interruption. The consumer checks file permissions/location/non-emptiness; - creation, parent custody and removal remain attended responsibilities. - No automatic cleanup or live read proof is claimed. This documents the - consumer return; it is not platform-operator/secrets-engine-owner approval, - group confirmation or authority to apply the role. Existing activation - conditions and in_flight status remain. + comment: 'Secrets Engine source 98d72ceb1dbcff2d2a80fb8c3058d76777a0ac93, docs/approval-service-auth.md, + supplies the requested workstation procedure: operator-owned 0700 runtime session + directory outside Git, new 0600 file, path-only configuration across one claim/consume + operation, EXIT/INT/TERM cleanup, and explicit residual-file handling after + a hard interruption. The consumer checks file permissions/location/non-emptiness; + creation, parent custody and removal remain attended responsibilities. No automatic + cleanup or live read proof is claimed. This documents the consumer return; it + is not platform-operator/secrets-engine-owner approval, group confirmation or + authority to apply the role. Existing activation conditions and in_flight status + remain.' + - at: '2026-09-14' + reviewer: operator (user confirmation in Codex session) + decision: approved + comment: The operator explicitly selected net-kingdom-admins and instructed Codex + to bind the approval-client reader to it. This authorizes the exact CCR-2026-0019 + policy and OIDC role apply through attended platform-admin authentication. The + existing group is documented in the platform's live-inspected OpenBao binding. + Consumer file custody was reviewed above. This is the operator's instruction, + not a fabricated separate owner vote or approval-engine human decision. Live + delivery verification remains pending and the credential front door remains + non-resolvable. 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 + 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 @@ -82,6 +70,28 @@ openbao: - CLIENT_SECRET policy_name: workload-kv-read-secrets-engine-approval-client policy_file: openbao/policies/workload-kv-read-secrets-engine-approval-client.hcl + auth: + method: oidc + mount: netkingdom + role: secrets-engine-approval-client-workload-kv-read + allowed_redirect_uris: + - https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback + - http://localhost:8250/oidc/callback + - http://127.0.0.1:8250/oidc/callback + oidc_scopes: + - openid + - profile + - email + - groups + user_claim: sub + groups_claim: groups + bound_claims: + groups: + - net-kingdom-admins + bound_claims_confirmed: true + policies: + - workload-kv-read-secrets-engine-approval-client + ttl: 15m access_frontdoor: type: ops-warden catalog_id: secrets-engine-approval-client @@ -91,82 +101,73 @@ access_frontdoor: 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. + 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. + - 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. + - 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. + - An identity outside the confirmed group claim cannot authenticate through role + secrets-engine-approval-client-workload-kv-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 + - 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. + - 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: [] + evidence: + - at: '2026-09-13T23:27:23+00:00' + actor: operator via attended Warden platform-admin OIDC + kind: delegated_metadata_apply + result: passed + details: + - Delegated metadata applier ran as operator via attended Warden platform-admin + OIDC using local bao CLI ambient authority. + - 'Policy metadata write: sys/policies/acl/workload-kv-read-secrets-engine-approval-client' + - 'Auth role metadata write: auth/netkingdom/role/secrets-engine-approval-client-workload-kv-read' + - No secret values were read, written, printed, or accepted in argv. 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. + deactivate: Detach the policy from role secrets-engine-approval-client-workload-kv-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 diff --git a/docs/evidence/2026-09-14-ccr0019-operator-binding.json b/docs/evidence/2026-09-14-ccr0019-operator-binding.json new file mode 100644 index 0000000..167913e --- /dev/null +++ b/docs/evidence/2026-09-14-ccr0019-operator-binding.json @@ -0,0 +1,15 @@ +{ + "observed_at": "2026-09-13T23:27:24.125007+00:00", + "ccr_id": "CCR-2026-0019", + "status": "metadata_applied_and_readback_verified", + "role": "secrets-engine-approval-client-workload-kv-read", + "group": "net-kingdom-admins", + "policy": "workload-kv-read-secrets-engine-approval-client", + "ttl_seconds": 900, + "role_request_id": "cc0fc872-3bff-5af0-5898-886e0ec6741e", + "attended_platform_admin": true, + "secret_values_read": false, + "frontdoor_activated": false, + "positive_negative_delivery_verified": false, + "session_revocation": "enclosing Warden command exited 0 after self-revocation and helper cleanup" +} diff --git a/scripts/apply-ccr0019-binding.py b/scripts/apply-ccr0019-binding.py new file mode 100644 index 0000000..ad4888c --- /dev/null +++ b/scripts/apply-ccr0019-binding.py @@ -0,0 +1,58 @@ +"""Silent attended CCR-2026-0019 metadata apply and readback; no KV reads.""" +import json +import os +from pathlib import Path +import subprocess +from datetime import datetime, timezone + +REPO = Path('/home/worsch/railiance-platform') +ROLE = 'secrets-engine-approval-client-workload-kv-read' +POLICY = 'workload-kv-read-secrets-engine-approval-client' +RECEIPT = REPO / 'docs/evidence/2026-09-14-ccr0019-operator-binding.json' + +def run(*args): + result = subprocess.run(args, cwd=REPO, capture_output=True, text=True, timeout=60) + if result.returncode: + raise RuntimeError('command_failed') + return result.stdout + +def main(): + if Path.home().parent.name != '.warden-attended-login' or os.getenv('BAO_TOKEN') or os.getenv('VAULT_TOKEN'): + raise RuntimeError('attended_envelope_required') + identity = json.loads(run('bao', 'token', 'lookup', '-format=json'))['data'] + if 'platform-admin' not in identity['policies'] or 'root' in identity['policies']: + raise RuntimeError('platform_admin_required') + run('python3', 'scripts/credential-change.py', 'applier-apply', 'CCR-2026-0019', + '--actor', 'operator via attended Warden platform-admin OIDC', + '--confirm', 'DELEGATED APPLY CCR-2026-0019', '--quiet') + response = json.loads(run('bao', 'read', '-format=json', 'auth/netkingdom/role/' + ROLE)) + role = response['data'] + expected = { + 'bound_claims': {'groups': ['net-kingdom-admins']}, + 'role_type': 'oidc', 'user_claim': 'sub', 'groups_claim': 'groups', + } + for key, value in expected.items(): + if role.get(key) != value: + raise RuntimeError('role_readback_mismatch') + if role.get('token_policies', role.get('policies')) != [POLICY] or role.get('token_ttl', role.get('ttl')) != 900: + raise RuntimeError('policy_or_ttl_mismatch') + actual = run('bao', 'policy', 'read', POLICY) + source = (REPO / 'openbao/policies' / (POLICY + '.hcl')).read_text() + if actual.split() != source.split(): + raise RuntimeError('policy_readback_mismatch') + receipt = { + 'observed_at': datetime.now(timezone.utc).isoformat(), 'ccr_id': 'CCR-2026-0019', + 'status': 'metadata_applied_and_readback_verified', 'role': ROLE, + 'group': 'net-kingdom-admins', 'policy': POLICY, 'ttl_seconds': 900, + 'role_request_id': response.get('request_id'), + 'attended_platform_admin': True, 'secret_values_read': False, + 'frontdoor_activated': False, 'positive_negative_delivery_verified': False, + 'session_revocation': 'performed separately by enclosing Warden command', + } + RECEIPT.write_text(json.dumps(receipt, indent=2) + '\n') + +if __name__ == '__main__': + try: + main() + except Exception: + raise SystemExit(1) from None diff --git a/workplans/RPF-WP-0035-credential-lane-implementation.md b/workplans/RPF-WP-0035-credential-lane-implementation.md index a3905b6..c16d325 100644 --- a/workplans/RPF-WP-0035-credential-lane-implementation.md +++ b/workplans/RPF-WP-0035-credential-lane-implementation.md @@ -303,10 +303,15 @@ remains refused. No live identity, policy, role, custody or verifier was changed Human approval follows existing INFD-WP-0001-T07/T08; future service requesters need a separate narrow registration, not this withdrawn bundle. -**Remaining unblock:** CCR-2026-0019 needs the exact authorized operator group -from NetKingdom/KeyCape, then reviewed attended file delivery and its scoped -positive/negative proof. Completed CCR-2026-0017/0018 remain closed. T06 stays -`wait`; its historical two-reader notes below are superseded for reader 2. +**Operator binding confirmed, 2026-09-14:** The user explicitly selected +`net-kingdom-admins` and authorized applying CCR-2026-0019. Its OIDC role is +`secrets-engine-approval-client-workload-kv-read`, aligned with the platform +applier naming rule; policy and exact KV scope are unchanged. The metadata dry +run passes with no warnings. Attended apply and exact role/policy readback passed; the enclosing Warden +command exited successfully after its self-revocation/cleanup. Receipt: +`docs/evidence/2026-09-14-ccr0019-operator-binding.json`. Scoped delivery proof +remains pending; the front door stays non-resolvable. Completed CCR-2026-0017/0018 +remain closed and T06 stays `wait`. Consumer procedure returned in source, 2026-09-10: `secrets-engine@98d72ceb1dbcff2d2a80fb8c3058d76777a0ac93:docs/approval-service-auth.md`