diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index b47cf80..b089e21 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -44,7 +44,6 @@ | workplan | RPF-WP-0062 | finished | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md | | workplan | RPF-WP-0063 | finished | — | workplans/RPF-WP-0063-wave2-resource-right-sizing.md | | workplan | RPF-WP-0064 | active | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| workplan | RPF-WP-0065 | proposed | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | | task | RPF-WP-ADHOC-2026-10-10-T01 | done | — | workplans/ADHOC-2026-10-10.md | | task | RPF-WP-ADHOC-2026-10-11-T01 | done | — | workplans/ADHOC-2026-10-11.md | | task | RPF-WP-ADHOC-2026-10-11-T02 | done | — | workplans/ADHOC-2026-10-11.md | @@ -202,11 +201,6 @@ | task | RPF-WP-0064-T06 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | | task | RPF-WP-0064-T07 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | | task | RPF-WP-0064-T08 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0065-T01 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| task | RPF-WP-0065-T02 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| task | RPF-WP-0065-T03 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| task | RPF-WP-0065-T04 | wait | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| task | RPF-WP-0065-T05 | wait | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | | intake | RPF-IN-0001 | closed | — | intakes/intakes.md | | intake | RPF-IN-0002 | open | — | intakes/intakes.md | | intake | RPF-IN-0003 | open | — | intakes/intakes.md | diff --git a/docs/coding-agent-openbao-identity.md b/docs/coding-agent-openbao-identity.md index 0a20b63..1a024ee 100644 --- a/docs/coding-agent-openbao-identity.md +++ b/docs/coding-agent-openbao-identity.md @@ -55,7 +55,7 @@ coordinates: | --- | --- | | `aud` / client id | `codex-railiance-platform` | | `sub` | `service:codex:railiance-platform` | -| `principal_type` | `service` today; `agent` after the KeyCape change (RPF-WP-0065). The role accepts both during the transition | +| `principal_type` | `service` | | `tenant` | `tenant:platform` | | `roles` | contains `coding-agent` | | requested scope | `openbao:login` | diff --git a/openbao/auth/coding-agent-jwt-role.json b/openbao/auth/coding-agent-jwt-role.json index f4343c5..8fa6147 100644 --- a/openbao/auth/coding-agent-jwt-role.json +++ b/openbao/auth/coding-agent-jwt-role.json @@ -7,7 +7,7 @@ "bound_claims_type": "string", "bound_claims": { "sub": "service:codex:railiance-platform", - "principal_type": ["service", "agent"], + "principal_type": "service", "tenant": "tenant:platform", "roles": "coding-agent" }, diff --git a/openbao/auth/netkingdom-platform-admin-role.json b/openbao/auth/netkingdom-platform-admin-role.json index 6e32fdf..103061a 100644 --- a/openbao/auth/netkingdom-platform-admin-role.json +++ b/openbao/auth/netkingdom-platform-admin-role.json @@ -8,8 +8,7 @@ "bound_claims": { "groups": [ "net-kingdom-admins" - ], - "/assurance/level": "aal2" + ] }, "bound_claims_type": "string", "bound_subject": "", diff --git a/tests/test_netkingdom_role_files.py b/tests/test_netkingdom_role_files.py deleted file mode 100644 index 31cbdcc..0000000 --- a/tests/test_netkingdom_role_files.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Source checks for the netkingdom OpenBao role files (RPF-WP-0065). No live access.""" -import json -from pathlib import Path - -AUTH = Path(__file__).resolve().parents[1] / 'openbao/auth' - - -def role(name): - return json.loads((AUTH / name).read_text()) - - -def test_platform_admin_binds_group_and_aal2(): - r = role('netkingdom-platform-admin-role.json') - assert r['bound_claims_type'] == 'string' - assert r['bound_claims'] == {'groups': ['net-kingdom-admins'], '/assurance/level': 'aal2'} - assert r['groups_claim'] == 'groups' - assert r['token_policies'] == ['platform-admin', 'operator-custody'] - - -def test_platform_admin_matches_credential_broker_assurance_binding(): - admin = role('netkingdom-platform-admin-role.json')['bound_claims'] - broker = role('credential-broker-keycape-client-secret-generate-oidc-role.json')['bound_claims'] - assert admin['/assurance/level'] == broker['/assurance/level'] == 'aal2' - - -def test_coding_agent_accepts_service_and_agent_during_transition(): - r = role('coding-agent-jwt-role.json') - claims = r['bound_claims'] - assert sorted(claims['principal_type']) == ['agent', 'service'] - # nothing else loosened - assert claims['sub'] == 'service:codex:railiance-platform' - assert claims['tenant'] == 'tenant:platform' - assert claims['roles'] == 'coding-agent' - assert r['bound_audiences'] == ['codex-railiance-platform'] - assert r['token_ttl'] == r['token_max_ttl'] == '15m' - assert r['token_no_default_policy'] is True - assert 'default' not in r['token_policies'] diff --git a/tests/test_openbao_platform_admin_callback_prune.py b/tests/test_openbao_platform_admin_callback_prune.py index 3bf97b8..2efb379 100644 --- a/tests/test_openbao_platform_admin_callback_prune.py +++ b/tests/test_openbao_platform_admin_callback_prune.py @@ -32,7 +32,7 @@ def test_prunes_only_the_two_retired_callbacks_and_keeps_settings(): assert not set(m.RETIRED) & set(uris) assert m.KEEP in uris and 'http://localhost:8250/oidc/callback' in uris assert state['role']['token_policies'] == ['platform-admin', 'operator-custody'] - assert state['role']['bound_claims'] == DECLARED['bound_claims'] + assert state['role']['bound_claims'] == {'groups': ['net-kingdom-admins']} def test_idempotent_when_already_pruned(): diff --git a/workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md b/workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md deleted file mode 100644 index 35aa1f7..0000000 --- a/workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -id: RPF-WP-0065 -type: workplan -title: "NetKingdom OpenBao role claim bindings: coding-agent principal_type and platform-admin aal2" -domain: financials -repo: railiance-platform -status: proposed -flavor: implementation -owner: claude -created: "2026-10-24" -updated: "2026-10-24" -related: - - NK-WP-0054 - - RPF-WP-0022 - - RPF-WP-0035 ---- - -# RPF-WP-0065 — Role claim bindings from the NK-WP-0054 review - -Receiving workplan for hub message `8da287e2` from net-kingdom (NK-WP-0054, net-kingdom -`b774927`; standard `canon/standards/service-identity-privilege_v0.1.md` draft-3, -review `history/2026-10-24-security-identity-and-agent-review.md`). These are -proposals from net-kingdom. This workplan is source preparation only; nothing is applied live. - -## Evidence - -**F-1 coding agent.** The token is minted by KeyCape `client_credentials` for client -`codex-railiance-platform` (`docs/coding-agent-openbao-identity.md`, KeyCape -`docs/openbao-service-auth-contract.md`). KeyCape `src/internal/server/oidc/token.go` -(~line 382) hardcodes `principal_type: "service"` and an `assurance` of `aal1`/`client_secret`, -with no `agent` object. So today's real tokens say `service`; IAM Profile v0.3 "Agent Principal -Flow" requires `principal_type: agent` plus `agent.{id,mode}`. Flipping the role to `agent` -alone would reject every real token. The role is a migration target: no client secret is -delivered and no JWT exchange is established (docs section "Source clarification — 2026-10-11"); -the operational identity is the AppRole. - -**aal2 on the platform-admin path.** Humans log in via client `openbao-admin` (authorization -code, public PKCE). KeyCape `token.go` sets `claims["assurance"]` for that path with -`level: aal2` only when MFA was verified in that authorization. KeyCape -`docs/secrets-engine-openbao-owner-packet.md` states the same and recommends the binding. -Live evidence: KeyCape `docs/evidence/2026-10-07-secrets-engine-openbao-issuance.json` -records the founder's OIDC login through `openbao-admin` under bound `/assurance/level aal2` -(role `credential-broker-...` family, same client and mount). Conclusion: verified from source and -prior live evidence; one residual risk is that this exact role has not been exercised with the claim, -so the rollout below keeps an open admin session and a one-command rollback. - -## Tasks - -```task -id: RPF-WP-0065-T01 -status: done -priority: high -``` -**Coding-agent transition (option a).** `coding-agent-jwt-role.json` now binds -`principal_type` to `["service", "agent"]` (OpenBao list = any-of). Sub, audience, tenant, roles, -TTL 15m, 8 uses, no default policy are unchanged. Doc table updated. -Exception record (compensating controls while `service` is accepted): the exact `sub` -`service:codex:railiance-platform`, audience, tenant and `coding-agent` role stay bound; 15-minute, -eight-use token without default policy (inside the SI-14 60-minute ceiling); `agent-high-risk-boundary` -deny wins over the issue-core read policy. Removal condition: drop `"service"` once -(1) KeyCape emits `principal_type: agent` with `agent.id` and `agent.mode` for this client, -(2) a value-safe login with such a token succeeded, and (3) the AppRole path is no longer the -standing identity. At removal also bind `/agent/mode` to the agreed mode(s). Review by 2026-12-31. - -```task -id: RPF-WP-0065-T02 -status: done -priority: high -``` -**platform-admin aal2 (founder decision 2026-10-24).** `netkingdom-platform-admin-role.json` -now binds `groups: [net-kingdom-admins]` and `"/assurance/level": "aal2"`, as the credential-broker -role does. Closes exception SI-X4 once applied. - -```task -id: RPF-WP-0065-T03 -status: done -priority: medium -``` -**Tests.** `tests/test_netkingdom_role_files.py` (new) and the bound-claims assertion in -`tests/test_openbao_platform_admin_callback_prune.py` now follow the declaration: -`python3 -m pytest -q tests/test_netkingdom_role_files.py tests/test_openbao_platform_admin_callback_prune.py`. - -```task -id: RPF-WP-0065-T04 -status: wait -priority: high -``` -**Founder live step: apply platform-admin aal2 (needs the founder).** See runbook below. - -```task -id: RPF-WP-0065-T05 -status: wait -priority: medium -``` -**Coding-agent: issuer first, then role.** Blocked on the KeyCape owner (see rollout order). -Then the founder applies the role and later removes `service`. - -## Runbook for the founder (T04) - -Preconditions: you are on the workstation with the `openbao-ui-railiance01` tunnel up -(`bridge status`), in the repo checkout, and you are a `net-kingdom-admins` member who completes MFA. -Role JSON contains no secrets. Keep one existing admin session open in another terminal -until step 4 passes. - -```bash -set +x; umask 077; unset BAO_TOKEN VAULT_TOKEN -cd ~/railiance-platform -python3 -m pytest -q tests/test_netkingdom_role_files.py tests/test_openbao_platform_admin_callback_prune.py -# 1 backup of the live role (rollback source) -warden access openbao-platform-admin-login --exec -- \ - bao read -format=json auth/netkingdom/role/platform-admin > ~/.local/state/railiance/platform-admin-role.before.json -# 2 apply -warden access openbao-platform-admin-login --exec -- \ - bao write auth/netkingdom/role/platform-admin @openbao/auth/netkingdom-platform-admin-role.json -# 3 read back -warden access openbao-platform-admin-login --exec -- \ - bao read -format=json auth/netkingdom/role/platform-admin | jq '.data.bound_claims' -# expect {"groups":["net-kingdom-admins"],"/assurance/level":"aal2"} -``` - -Step 4, prove a fresh MFA login still works: in a new terminal run -`warden access openbao-platform-admin-login --exec -- bao token lookup -format=json | jq '.data.policies'` -and confirm `platform-admin` and `operator-custody`. Only then close the old session. -Optionally record a receipt with `scripts/openbao-platform-admin-check.sh --receipt `. - -Rollback (if step 4 fails, from the still-open session): - -```bash -# previous declaration = the file as of the parent of this workplan's commit -git show 2126457^:openbao/auth/netkingdom-platform-admin-role.json > ~/.local/state/railiance/pa-role.prev.json -grep -c assurance ~/.local/state/railiance/pa-role.prev.json # expect 0 -bao write auth/netkingdom/role/platform-admin @$HOME/.local/state/railiance/pa-role.prev.json -``` - -If no session is open, use the break-glass recovery path in `docs/openbao.md`. -If the login fails because the token lacks `assurance.level`, check the sign-in diagnosis page of -`openbao-admin` (NK-WP-0051-T02) and report to KeyCape; do not pin MFA on the client without its own go. - -## Rollout order for the coding-agent role (T05) - -1. KeyCape owner changes the issuer: `client_credentials` tokens for `codex-railiance-platform` carry - `principal_type: agent` and `agent.id`, `agent.mode` (IAM v0.3). This must come first. -2. Apply the transitional role (accepts both), value-safe check per `docs/coding-agent-openbao-identity.md`: - `bao write auth/netkingdom/role/coding-agent @openbao/auth/coding-agent-jwt-role.json`. -3. After a successful agent-token login, remove `"service"` and add the `/agent/mode` binding, apply, re-verify. -Rollback: `bao delete auth/netkingdom/role/coding-agent` (AppRole remains the operational identity).