Support OIDC dynamic client registration - #6544
Merged
Merged
Conversation
OIDC-typed upstreams could not use Dynamic Client Registration, even when their discovery metadata exposed a registration endpoint. This forced operators to use the more verbose OAuth2 form and prevented issuer-based OIDC configurations from working with providers such as connector-gateway. Add DCR configuration and validation to OIDC runtime and CRD types, derive the standard discovery URL from the issuer when needed, and route both OAuth2 and OIDC upstreams through the shared DCR resolver. Propagate registered credentials into the OIDC provider, update secret bindings and generated manifests, and cover the adapter behavior with tests. Fixes #6538
jhrozek
requested review from
ChrisJBurns,
JAORMX,
blkt,
jerm-dro,
rdimitrov,
reyortiz3 and
tgrunnagle
as code owners
September 8, 2026 13:06
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #6544 +/- ##
==========================================
- Coverage 78.67% 78.66% -0.02%
==========================================
Files 777 777
Lines 76686 76925 +239
==========================================
+ Hits 60334 60512 +178
- Misses 16347 16408 +61
Partials 5 5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
buildUpstreamConfigs exceeded the cyclomatic complexity limit (19 > 15); split its per-upstream loop body into buildOneUpstreamConfig, validateUpstreamRunConfig, resolveUpstreamDCR, and applyDCRResolutionToConfig. Also wrap two lines that exceeded the line-length limit, and regenerate docs/server/swagger.* to include the new dcr_config field on OIDCUpstreamConfig. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
amirejaz
approved these changes
Sep 8, 2026
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
when their discovery metadata exposed a
registration_endpoint, forcingoperators onto the more verbose explicit-endpoint OAuth2 form for
providers such as
connector-gateway.stacklok.devthat publish standardOIDC discovery.
DCRConfigexisted only on the OAuth2 upstream types; thegap was structural (missing field, no conversion path, no call site), not
a runtime restriction — RFC 7591 DCR is discovery-source-agnostic and does
not distinguish OIDC-discovered vs. OAuth2-declared endpoints.
DCRConfigtoOIDCUpstreamRunConfig/OIDCUpstreamConfig(runtimeand CRD), deriving the discovery URL from
IssuerURL + /.well-known/openid-configurationwhen the operator doesn't set oneexplicitly — no changes needed in
pkg/auth/dcritself.type-agnostic
newDCRRequest, addconsumeOIDCResolution/applyResolutionToOIDCConfigmirroring the existing OAuth2 helpers, addOIDC XOR validation (
ClientIDvsDCRConfig), extend CRD CEL validationand the initial-access-token secret-ref plumbing to the OIDC path, and
regenerate manifests/docs.
Fixes #6538
Type of change
Test plan
task test)task test-e2e)task lint-fix)API Compatibility
v1beta1API, OR theapi-break-allowedlabel is applied and the migration guidance is described above.OIDCUpstreamConfig.ClientIDchanges from required to optional (was+kubebuilder:validation:Required), and a new optionaldcrConfigfield isadded — both additive, backward compatible with existing manifests.
Changes
pkg/authserver/config.goDCRConfigtoOIDCUpstreamRunConfig; addValidate()enforcing ClientID/DCRConfig exclusivitypkg/authserver/runner/dcr_adapter.gonewDCRRequestto both upstream types; add OIDC consume/apply helperspkg/authserver/runner/embeddedauthserver.gopkg/authserver/runner/dcr_adapter_test.gocmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.goDCRConfigtoOIDCUpstreamConfig; relaxClientIDto optional; add CEL rules andValidateOIDCDCRConfigcmd/thv-operator/pkg/controllerutil/authserver.godeploy/charts/**,docs/operator/crd-api.md,zz_generated.deepcopy.goDoes this introduce a user-facing change?
Yes — operators can now set
dcrConfigon an OIDC-typed upstream(
type: oidc) to use Dynamic Client Registration, instead of beingrequired to use the more verbose
type: oauth2form with explicitauthorizationEndpoint/tokenEndpointjust to get DCR.Special notes for reviewers
IssuerURL + "/.well-known/openid-configuration"when the operator doesn't setdcrConfig.discoveryUrl/registrationEndpointexplicitly. This round-tripscorrectly through the existing
pkg/auth/dcrresolver'sderiveExpectedIssuerFromDiscoveryURLwithout any changes there.and OIDC DCR wiring — the two config types already duplicate several
fields (
ClientID,Scopes,AllowPrivateIPs), and a shared abstractionhere would churn the CRD/run-config JSON shape for no behavioral gain.
discovery document that publishes
registration_endpoint(e.g. Keycloak,or
connector-gateway.stacklok.devitself) in a kind cluster — RunConfig-levelreachability isn't sufficient proof by itself for this class of change.
Implementation plan
Approved implementation plan
Design produced by an oauth-expert + go-architect review pair prior to
implementation:
Spec review (oauth-expert): RFC 7591 DCR is discovery-source-agnostic —
the
registration_endpointPOST/response contract doesn't depend on whetherthe endpoint was learned via OIDC discovery or explicit OAuth2 config. OIDC
Dynamic Client Registration 1.0 adds only OPTIONAL extra metadata fields
(subject_type, id_token_signed_response_alg, etc.); omitting them is
spec-compliant. No spec reason to keep OIDC and OAuth2 DCR separate.
Architecture (go-architect): Reuse the existing
DCRUpstreamConfigshape rather than introducing a shared interface/embed. Add the field
directly to
OIDCUpstreamRunConfig/OIDCUpstreamConfig(mirroring theOAuth2 struct, ~10 lines of duplication — cheaper than a shared type that
would churn the CRD/run-config JSON shape). Make
DiscoveryURLoptional forOIDC, defaulting to
IssuerURL + "/.well-known/openid-configuration"(round-trips through
deriveExpectedIssuerFromDiscoveryURLunmodified).Collapse
needsDCRinto a type-switchingnewDCRRequest; addconsumeOIDCResolution/applyResolutionToOIDCConfigmirroring the OAuth2versions; extend CRD CEL validation from "exactly one" to "at most one" of
discoveryUrl/registrationEndpoint at the shared struct level, with OAuth2
re-asserting "exactly one" at its own struct level.