design: approval identity + group resolution when gocdnext runs behind an external platform/IdP
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 7
- Forks
- 3
- Avg merge
- 2h 12m
- Merged PRs (30d)
- 30
Description
Context
gocdnext is being embedded into an internal developer platform that fronts it for users. That raises two questions the approval gate depends on: who is the acting identity, and where does group membership come from.
Today (server/internal/store/approvals.go:176-205):
approversis matched by string — the display label (User) or the email. Under OIDC thenameclaim wins asUser, which is the drift the code already documents (#51).approver_groupsis matched by UserID against gocdnext's localgroup_members, and the code correctly calls this "the most robust path, immune to name/email string drift".- Quorum accounting only runs when the caller supplied a
UserID— the authenticated path. Anonymous callers skip the vote table entirely.
The failure mode to design against
A platform approving on behalf of users with a service token. If the UserID reaching gocdnext is the platform's, the gate becomes theatre: quorum counts the service, the audit records the service, and the question a gate exists to answer — which human authorised this — has no answer. Same class as the webhook-drift path having no actor.
Corollary: no caller-asserted identity (an X-On-Behalf-Of-style header). Anyone holding the platform's token could then approve as anyone.
Proposed direction
-
Shared OIDC rather than delegation. The platform and gocdnext trust the same issuer; the user's own session reaches gocdnext. This is configuration, not a feature — OIDC login already exists. Approvals keep a real
UserID, quorum works, the audit names the human.Where the platform genuinely must proxy the API, require token exchange (RFC 8693): it exchanges for a token minted for the user. The identity assertion is then signed by the IdP, not asserted by the proxy.
-
One source of truth for groups, resolved at vote time. If the platform owns teams, mirroring them into
group_membersinvites silent drift and the gate ends up enforcing the stale copy.Worth distinguishing: token claims are fine for UI/navigation; authority should be resolved at the moment of decision. A token minted before someone left a team still carries the group. For the vote — the security-relevant instant — re-resolve membership against the IdP/platform instead of trusting the login-time snapshot.
-
Match approvers by a stable subject, not display strings. This is the debt the integration amplifies: another source of display names. Referencing the OIDC
sub(or the gocdnextUserID) makes the allow-list immune to rename/email changes — which is exactly why the group path is already the robust one. -
Keep the local group table as a fallback. Standalone and air-gapped installs still need it; external resolution should be an additional source, not a replacement.
Open questions
- Does the platform own approver policy (who may approve what) or only identity? If policy, the gate's
approvers/approver_groupsmay need to become a reference to an external decision rather than an inline list. - How should a resolution failure behave — fail closed (refuse the vote) or fall back to the local table? Fail-closed matches the project's posture elsewhere, but it couples approvals to the platform's availability.
- Rollout gates reuse this engine (ADR-0001 Phase 2), and their approve/reject route is deliberately viewer-level, authorised by the allow-list rather than by role. Any change here must preserve that separation: approval authority is independent of operational authority.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with server/internal/store/approvals.go:176-205 and trace the approval and quorum paths, including how UserID, approvers, approver_groups, and audit identity are populated. Define the integration behavior for shared OIDC, token exchange, vote-time group resolution, stable subject matching, and local fallback, while resolving the listed failure and rollout questions before implementation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authentication, authorization, backend-api-design, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100