From ab5cccdcf35df8ed7a5c3dabb23fda12190416ba Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 10 Oct 2026 12:29:32 +0200 Subject: [PATCH] KEY-WP-0038: Forgejo custody path derived by case type; propose KEY-WP-0039 negative-probe tokens Forgejo's secret now lives at platform/workloads/keycape/forgejo, as derived by oidc-browser-relying-party 1.0 (net-kingdom 074b3a0). KEY-WP-0039 answers railiance-platform's CCR-2026-0038 question with one-mismatch probe registrations instead of an issuer path that could mint arbitrary claims. Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 779409@bnt-lap001 Assistant-Session: c658ca06-19b5-468d-be64-2985dc20bfd3 --- WORK-RECORDS.md | 1 + docs/forgejo-oidc-client-request.yaml | 7 +- workplans/KEY-WP-0038-forgejo-oidc-client.md | 7 +- ...Y-WP-0039-openbao-negative-probe-tokens.md | 92 +++++++++++++++++++ 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 workplans/KEY-WP-0039-openbao-negative-probe-tokens.md diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 9f6b756..c42a16b 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -210,4 +210,5 @@ | task | KEY-WP-0038-T01 | done | — | workplans/KEY-WP-0038-forgejo-oidc-client.md | | task | KEY-WP-0038-T02 | wait | — | workplans/KEY-WP-0038-forgejo-oidc-client.md | | task | KEY-WP-0038-T03 | wait | — | workplans/KEY-WP-0038-forgejo-oidc-client.md | +| task | KEY-WP-0038-T04 | cancel | — | workplans/KEY-WP-0038-forgejo-oidc-client.md | | intake | KEY-IN-0001 | closed | — | intakes/intakes.md | diff --git a/docs/forgejo-oidc-client-request.yaml b/docs/forgejo-oidc-client-request.yaml index b998ba9..04d0bcc 100644 --- a/docs/forgejo-oidc-client-request.yaml +++ b/docs/forgejo-oidc-client-request.yaml @@ -32,7 +32,12 @@ client: custody: # Minted straight into custody in T02; no value in git, State Hub or chat. - proposed_openbao_path: platform/workloads/forgejo/netkingdom-oidc-client + # Derived by SecurityCaseType oidc-browser-relying-party 1.0 as + # platform/workloads/keycape/{client_id}; a case may not set it (net-kingdom + # 074b3a0, after ops-mason MASON-WP-0010-T01). Replaces the earlier proposal + # platform/workloads/forgejo/netkingdom-oidc-client. + openbao_path: platform/workloads/keycape/forgejo + keycape_delivery: ESO Secret sso/keycape-forgejo-client (role external-secrets-keycape-forgejo) field: CLIENT_SECRET custody_owner: railiance-platform keycape_environment: KEYCAPE_FORGEJO_CLIENT_SECRET diff --git a/workplans/KEY-WP-0038-forgejo-oidc-client.md b/workplans/KEY-WP-0038-forgejo-oidc-client.md index ef79d9b..8cb7c00 100644 --- a/workplans/KEY-WP-0038-forgejo-oidc-client.md +++ b/workplans/KEY-WP-0038-forgejo-oidc-client.md @@ -9,7 +9,7 @@ flavor: implementation owner: claude-code topic_slug: railiance created: "2026-10-08" -updated: "2026-10-08" +updated: "2026-10-10" depends_on: [] related_repos: - prj-forgejo-org-refactor @@ -111,8 +111,9 @@ field rules and reviewing the type's composition. The original intent below still describes what must result. Register `forgejo` from the packet. Mint the client secret straight into custody -(proposed OpenBao path `platform/workloads/forgejo/netkingdom-oidc-client`, -custody owner railiance-platform, consumer Forgejo). Record a receipt with no +(OpenBao path `platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, derived +by the case type since net-kingdom 074b3a0; custody owner railiance-platform, +consumers KeyCape via ESO and Forgejo). Record a receipt with no secret value. Acceptance: discovery metadata lists the client's redirect URI. The secret is diff --git a/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md b/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md new file mode 100644 index 0000000..21ebebc --- /dev/null +++ b/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md @@ -0,0 +1,92 @@ +--- +id: KEY-WP-0039 +type: workplan +title: "Negative-check tokens for the secrets-engine OpenBao JWT role" +domain: infotech +repo: key-cape +status: proposed +flavor: implementation +owner: claude-code +topic_slug: netkingdom +created: "2026-10-10" +updated: "2026-10-10" +depends_on: [] +related_repos: + - railiance-platform + - secrets-engine +--- + +# KEY-WP-0039 - Negative-check tokens for the secrets-engine OpenBao JWT role + +## Why + +CCR-2026-0038 (railiance-platform RPF-WP-0035-T02) requires the OpenBao role +`secrets-engine-openbao` on `auth/keycape-services` to refuse JWTs with a wrong +`aud`, `sub`, `tenant`, `principal_type`, `roles` or `scope`, a coding-agent JWT +and an expired JWT. The positive login proof passed on 2026-10-10 (secrets-engine +`docs/evidence/2026-10-10-keycape-openbao-probe.json`). railiance-platform asked +whether KeyCape can issue the negative tokens without touching the +`secrets-engine` registration. + +KeyCape signs only what a registration declares. It has no way to mint a token +with arbitrary claims, and adding one would create a forging path in the issuer. +So the negative tokens must come from real, separate registrations that differ +from `secrets-engine` in exactly one property each. + +## Boundaries + +- The `secrets-engine` registration is not changed. +- Probe clients are confidential `client_credentials` clients with the minimum + 60-second lifetime, scopes that grant nothing, and secrets minted into custody + like any other client. They are removed after the proof. +- The probe script prints only accept or refuse booleans per case, never tokens or + secrets. It runs attended. + +## Tasks + +```task +id: KEY-WP-0039-T01 +status: todo +priority: medium +``` + +### T01 — Define the probe registrations + +Write a non-secret request packet under `docs/` with one probe client per claim +mismatch, each identical to `secrets-engine` except for: + +- `aud`: a different access-token resource audience +- `sub`: a different service subject +- `tenant`: a tenant other than `platform` +- `roles`: a role other than `secrets-engine` +- `scope`: a scope outside the role's bound scope + +Two cases need no probe client. A service token always carries +`principal_type: service` (`src/internal/server/oidc/token.go`), so the wrong +`principal_type` case uses a human access token from an attended login. The +coding-agent case uses the existing `codex-railiance-platform` registration (role +`coding-agent`, `docs/openbao-service-auth-contract.md`) if it is registered live; +T01 confirms that. + +The expired-JWT case reuses any probe client: request a token, wait past its +60-second lifetime, then present it. + +Acceptance: railiance-platform agrees the packet covers every refusal that +CCR-2026-0038 names. + +```task +id: KEY-WP-0039-T02 +status: wait +depends_on: [KEY-WP-0039-T01] +blocking_reason: "Live registration and custody need owner authorization." +priority: medium +``` + +### T02 — Register the probe clients, run the attended probe, then remove them + +Write the attended script, which prints booleans only. Register the clients +through the normal registration path, hand the run to railiance-platform, record +a value-free receipt in `docs/evidence/`, and deregister the clients afterwards. + +Acceptance: every case is refused, the receipt is committed, and the probe clients +are gone.