--- id: KEY-WP-0038 type: workplan title: "Forgejo as a NetKingdom OIDC client, so the forge can close" domain: infotech repo: key-cape status: active flavor: implementation owner: claude-code topic_slug: railiance created: "2026-10-08" updated: "2026-10-14" depends_on: [] related_repos: - prj-forgejo-org-refactor - railiance-apps - railiance-platform state_hub_workstream_id: "098e3e2c-a138-560e-b2d3-3a15eb49d8a9" --- # KEY-WP-0038 - Forgejo as a NetKingdom OIDC client, so the forge can close ## Why `forgejo.coulomb.social` is closing: private by default, no anonymous access (prj-forgejo-org-refactor ORGREF-WP-0002, decision `decisions/forge-visibility-policy.md`). The founder's condition is that Forgejo sign-in runs through NetKingdom **before** anything closes, so that every human, agent and service reader has a governed identity rather than an ad-hoc local account. This plan is ORGREF-WP-0002-T04. Its Forgejo-side counterpart is railiance-apps `RAPPS-WP-0016` (ORGREF-WP-0002-T05), which owns the Forgejo Helm release. ## Boundaries - Registration follows the existing human browser-client pattern (`docs/approval-engine-provisioning-request.yaml`, `human_registration`) and the client fields validated in KEY-WP-0028. Redirect URIs are exact, never wildcards. - The client secret goes to OpenBao through railiance-platform custody. No secret value in git, State Hub, logs or chat. - Founder guardrail for the whole closure: tighten gradually, never lock anyone out, never risk assets. Nothing in this plan changes who can read the forge today. It only makes the NetKingdom path available. ## T01 — Define what Forgejo receives ```task id: KEY-WP-0038-T01 status: done priority: high state_hub_task_id: "5f019d13-1fa3-5b88-a27a-75a3339ca96e" ``` Write the claim contract Forgejo consumes, as a non-secret request packet under `docs/`: - issuer `https://kc.coulomb.social`; confidential client `forgejo`; authorization code with PKCE; scopes `openid profile email groups` - redirect URI `https://forgejo.coulomb.social/user/oauth2/netkingdom/callback`. The authentication-source name `netkingdom` must match railiance-apps' configuration exactly. - subject and username: canonical directory subject, plus a stable `preferred_username` that Forgejo can match to the existing local accounts (founder and agent accounts) for linking - `groups` naming the Forgejo organisations and teams to map to (for example `forge:coulomb:owners`, `forge:coulomb:readers`), and `principal_type` so that human and service principals stay distinguishable - MFA: forge administrators and owners require assurance with a fresh second factor; plain readers follow the client-aware MFA policy (KEY-WP-0008) Acceptance: the founder accepts the packet. The group names are agreed with railiance-apps. 2026-10-08: packet drafted at `docs/forgejo-oidc-client-request.yaml`. It covers a confidential client `forgejo` with PKCE and `mfaRequired: true`, the exact callback, scopes, a custody path, the claims as emitted at this revision, new LLDAP groups `forge-admins`, `forge-coulomb-owners` and `forge-coulomb-readers` with a required-claim gate, no automatic account creation, and linking of local accounts. It also proposes that agents and services use scoped tokens bound to service identities, not browser sign-in. 2026-10-08: accepted by the founder (in session). railiance-apps' RAPPS-WP-0016 uses the same authentication-source name `netkingdom` and the same group names, so the agreement condition holds. Before T02, the LLDAP groups `forge-admins`, `forge-coulomb-owners` and `forge-coulomb-readers` must exist, with the founder in the first two. ## T02 — Register the client and hand over its secret ```task id: KEY-WP-0038-T02 status: wait depends_on: [KEY-WP-0038-T01, NK-WP-0048-T06] blocking_reason: "Registration runs as the first KnownSecurityCase of type oidc-browser-relying-party (NK-WP-0048-T06), not as a hand-built rollout." priority: high state_hub_task_id: "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30" ``` **Changed 2026-10-08 (founder direction):** no case-specific helper script. Registration is the first case of the SecurityCaseType `oidc-browser-relying-party` (net-kingdom NK-WP-0048, canon `security-case-types_v0.1`). The parameters accepted in T01 become the case; ops-mason materializes it (MASON-WP-0010) and ops-warden follows through (WARDEN-WP-0044). The registration capability is net-kingdom's (`net-kingdom.keycape-client-registration`); key-cape's part is its 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 (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 readable by Forgejo's custody path only. 2026-10-10: key-cape reviewed the type's composition (`docs/oidc-browser-relying-party-type-review.md`). net-kingdom fixed the findings in `61ab6bc`, a 1.0 patch: redirects with `?` or `#` refused, the groups scope required, and descriptions corrected. A new 1.1 (`e2174ce`, founder go) adds the `consumer-enforces-groups` verification. For Forgejo, that is the required-claim setting railiance-apps configures. `security-cases/forgejo-netkingdom-oidc.yaml` is re-pinned to @1.1, with parameters unchanged. KeyCape added `keycape check-config` (`c528daa`), which register-client runs before the restart. T02 still waits on NK-WP-0048-T06, which needs both executors' light paths (MASON-WP-0010-T03, WARDEN-WP-0044-T03). ## T03 — Prove the claims for each kind of identity ```task id: KEY-WP-0038-T03 status: wait depends_on: [KEY-WP-0038-T02] blocking_reason: "Claims are verified against the registered client." priority: high state_hub_task_id: "40ddaa29-7245-508c-ad48-ddc063e25115" ``` Before Forgejo uses the client, verify the issued ID token and UserInfo for one human (the founder, with MFA), one agent identity and one service identity: subject, `preferred_username`, `groups`, `principal_type`, assurance. Record the claims, minus tokens, as evidence. Decide with railiance-apps whether agents and services sign in to Forgejo at all, or receive scoped Forgejo tokens bound to a NetKingdom service identity (ORGREF-WP-0002-T06). Record the decision. Acceptance: evidence for all three principals; the agent and service decision is recorded. Then hand over to RAPPS-WP-0016. ## T04 — Implement the `keycape-client` entry point ```task id: KEY-WP-0038-T04 status: cancel priority: high state_hub_task_id: "583cf1f1-16dc-515f-83bc-1efc45024a89" ``` **Cancelled 2026-10-08: moved to net-kingdom NK-WP-0048-T09 (done).** The live registration machinery (contained kubectl, cluster and issuer pins) already lives in net-kingdom `sso-mfa/k8s/keycape/`, where KeyCape is deployed; a copy here would drift. The entry point is `net-kingdom/sso-mfa/k8s/keycape/keycape-client.py` and the declaration is `net-kingdom.keycape-client-registration`; key-cape's draft declaration is removed. key-cape still owns the client field rules (canonical model) and their enforcement at authorization time. `capabilities/playbooks/key-cape.oidc-client-registration.yaml` (contract PASS, `maturity: planned`) declares client registration as a playbook capability, so that the SecurityCaseType `oidc-browser-relying-party` (net-kingdom NK-WP-0048) can compose it. Implement the entry point it names, `tools/keycape-client/`: - `keycape-client register --client-file ` with modes `inspect`, `dry-run`, `apply` and `remove`; adds, updates or removes exactly one client in the live registry; idempotent; never prints or writes secret values; a confidential client's secret is read from its custody reference - one implementation replacing the per-client helpers in net-kingdom `sso-mfa/k8s/keycape/` (register-coulomb-social, portal-client-rollout, vergabe-client-rollout, secrets-engine-openbao-client-rollout), keeping their safeguards (byte-preserving merge, compare-and-swap, cluster pin, safe receipts) and their tests as regression cases - client field validation by the KEY-WP-0028 rules - then set the declaration's `maturity` to `implemented` Longer term, loading clients from a non-secret registry with secrets referenced from custody would remove the type's only attended step (see the type README, "Attended steps").