key-cape/workplans/KEY-WP-0038-forgejo-oidc-client.md
tegwick ab5cccdcf3 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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 779409@bnt-lap001
Assistant-Session: c658ca06-19b5-468d-be64-2985dc20bfd3
2026-10-10 12:29:32 +02:00

181 lines
7.8 KiB
Markdown

---
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-10"
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.
## Tasks
```task
id: KEY-WP-0038-T01
status: done
priority: high
state_hub_task_id: "5f019d13-1fa3-5b88-a27a-75a3339ca96e"
```
### T01 — Define what Forgejo receives
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.
```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"
```
### T02 — Register the client and hand over its secret
**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.
```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"
```
### T03 — Prove the claims for each kind of identity
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.
```task
id: KEY-WP-0038-T04
status: cancel
priority: high
state_hub_task_id: "583cf1f1-16dc-515f-83bc-1efc45024a89"
```
### T04 — Implement the `keycape-client` entry point
**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 <rendered-client.yaml>` 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").