fix(auth/keycloak): use external Keycloak URL for browser-facing OIDC endpoints#111
Merged
Merged
Conversation
… endpoints
The SecurityPolicy generated for a NebariApp with Keycloak auth had
all four oidc.provider endpoints populated from the in-cluster Keycloak
service URL. The Authorization and EndSession endpoints are
browser-facing - the user's browser is redirected to them - so the
in-cluster URL fails DNS resolution for the browser and the entire
OAuth2 flow dead-ends.
Split the override generation by who actually hits each endpoint:
- Token: Envoy proxy back-channel (server-side). Keep on the
in-cluster URL: lower latency, avoids requiring Envoy to trust the
public TLS chain.
- Authorization, EndSession: browser front-channel. Use the publicly
routable URL from KEYCLOAK_EXTERNAL_URL.
When KEYCLOAK_EXTERNAL_URL is unset, leave Authorization and EndSession
as nil overrides; Envoy then falls back to the values from the OIDC
discovery document fetched at the issuer URL, which works as long as
Keycloak's frontendUrl is set correctly.
Adds a new externalRealmURL() helper that mirrors internalRealmURL()
and trims the optional trailing slash on KEYCLOAK_EXTERNAL_URL.
Updated TestKeycloakProvider_GetEndpointOverrides to cover:
- the new split (Token internal, Authorization+EndSession external)
- the trailing-slash normalization
- the fallback when ExternalURL is empty
Surfaced during fresh-install validation of the Nebari LLM Serving
Pack on a clean NIC AWS deploy: browsers hitting the key-manager UI
were 302'd to http://keycloak-keycloakx-http.keycloak.svc.cluster.local:8080
which they cannot resolve.
Closes #110
Code review pointed out the previous comment was misleading: 'Envoy
falls back to OIDC discovery' is mechanically accurate but the
discovery doc returned by an in-cluster Keycloak only contains public
URLs if Keycloak itself has frontendUrl configured. Otherwise the
discovery doc round-trips the same in-cluster URLs the override would
have used, and the browser-facing redirect still dead-ends.
Two changes:
1. Tighten the GetEndpointOverrides doc comment to spell out the
fallback semantics: it only works when Keycloak's frontendUrl /
KC_HOSTNAME_URL is set, otherwise discovery will return in-cluster
URLs.
2. Log a one-line WARNING at operator startup when ExternalURL is
empty, telling the operator to either set KEYCLOAK_EXTERNAL_URL
or configure Keycloak frontendUrl. Visible in operator-manager
logs immediately on startup; no action at reconcile time so it
doesn't spam per-NebariApp.
This was referenced Apr 27, 2026
Docker Images BuiltImages pushed to Quay.io for branch
Test the operator: kubectl apply -k https://github.com/nebari-dev/nebari-operator.git/config/default?ref=fix/oauth-browser-facing-endpoints-public
kubectl set image deployment/nebari-operator-controller-manager manager=quay.io/nebari/nebari-operator:fix-oauth-browser-facing-endpoints-public -n nebari-operator-system |
marcelovilla
approved these changes
Apr 28, 2026
marcelovilla
left a comment
Member
There was a problem hiding this comment.
Thanks for the PR @dcmcand. Just a small comment but approving nonetheless
Comment on lines
+62
to
+72
| // externalRealmURL returns the publicly routable base URL for the Keycloak realm, | ||
| // or empty string when KEYCLOAK_EXTERNAL_URL is not configured. | ||
| func (p *KeycloakProvider) externalRealmURL() string { | ||
| if p.Config.ExternalURL == "" { | ||
| return "" | ||
| } | ||
| return fmt.Sprintf("%s/realms/%s", | ||
| strings.TrimRight(p.Config.ExternalURL, "/"), | ||
| p.Config.Realm) | ||
| } | ||
|
|
Member
There was a problem hiding this comment.
Not a blocker but this seems to be the same URL being built by the GetExternalIssuerURL function:
nebari-operator/internal/controller/reconcilers/auth/providers/keycloak.go
Lines 118 to 124 in 0d0a85b
Not sure if it's intended because of the difference in handling the p.Config.ExternalURL == "" case but just flagging it in case it's not.
Removes duplicated URL construction flagged in PR #111 review.
Docker Images BuiltImages pushed to Quay.io for branch
Test the operator: kubectl apply -k https://github.com/nebari-dev/nebari-operator.git/config/default?ref=fix/oauth-browser-facing-endpoints-public
kubectl set image deployment/nebari-operator-controller-manager manager=quay.io/nebari/nebari-operator:fix-oauth-browser-facing-endpoints-public -n nebari-operator-system |
Open
3 tasks
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
externalRealmURL()helper mirroringinternalRealmURL().KEYCLOAK_EXTERNAL_URLis unset, leaves Authorization/EndSession unset; Envoy Gateway then triggers OIDC discovery against the in-cluster issuer. Discovery only produces public URLs if Keycloak's ownfrontendUrlis configured. Operator now logs a startup warning so this trap is visible.TestKeycloakProvider_GetEndpointOverrideswith five cases covering the split, trailing-slash normalization, and the no-ExternalURL fallback.Why
On a clean NIC deploy, browsers hitting any NebariApp protected by Keycloak OAuth2 are redirected to
http://keycloak-keycloakx-http.keycloak.svc.cluster.local:8080- which the browser cannot resolve. The OAuth2 flow dead-ends and no UI is reachable.Surfaced during fresh-install validation of the Nebari LLM Serving Pack (nebari-dev/nebari-llm-serving-pack#65), which loads the key-manager UI as a NebariApp.
Closes #110
Follow-ups (deliberately out of scope here)
SecurityPolicy.spec.oidc.provider.Issueralso trackKEYCLOAK_EXTERNAL_URLto match theissclaim in tokens issued by Keycloak withfrontendUrlconfigured?buildSecurityPolicySpec.Test plan
go test ./internal/controller/reconcilers/auth/providers/...passes - five cases covering the split, trailing-slash normalization, no-ExternalURL fallbackgo test ./...no regressions vs main (pre-existingTestControllersHTTPRoute scheme failure unrelated)go build ./...cleanhttps://keycloak.<base>for Authorization+EndSession, confirm browser login completes