nebari-dev / nebari-dev/data-science-pack
[ENH] KC Admin API URL should have a backchannel override, like token/userinfo do
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 5
- Forks
- 7
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 13
Description
Problem
Chart 0.1.3 supports OIDC split-horizon via keycloak.backchannelURL: token_url and userdata_url are derived from this in-cluster URL while authorize_url and end_session_url continue to use the external keycloak.hostname. This makes the hub Pod on private-VPC / split-DNS clusters work correctly for the browser-facing legs and the hub-facing token/userinfo legs.
But KeyCloakOAuthenticator.realm_api_url (the KC Admin API URL used by the chart's own RBAC filter — filter_user_groups_by_role, get_profile_slugs_for_user) is derived from keycloak.hostname too, with no equivalent backchannel override. On clusters where the hub Pod cannot resolve the external Keycloak hostname (common on private-VPC deployments with split DNS), the Admin API calls fail:
[E ... oauth2:857] Error fetching 599 GET https://keycloak.<domain>/admin/realms/<realm>/clients:
HTTP 599: Could not resolve host: keycloak.<domain>
[W ... 00-gateway-auth:414] rbac: failed to refresh groups_with_permission_to_mount for <user> —
keeping last known set
Because the chart's exception handler in _get_user_groups preserves the previous (usually empty) auth_state["groups_with_permission_to_mount"] on error, the failure is silent: RBAC returns nothing, and every user's /shared/<group> mounts vanish. There is no visible spawn error — just missing folders. On the affected cluster this took several hours to root-cause, because the surface symptom (no shared mounts) points at spawner or auth_state hooks rather than an Admin API DNS failure.
Setting realm_api_url via hub.extraConfig to http://keycloak-keycloakx-http.keycloak.svc.cluster.local:8080/admin/realms/<realm> fixes it immediately — the Admin API uses a bearer-token flow (obtained via oauthenticator's client-credentials grant), so pointing at the in-cluster Service address has no iss-claim reconciliation issue like the OIDC token leg had.
Proposed enhancement
Add a chart value that drives realm_api_url the same way keycloak.backchannelURL drives token_url and userdata_url. Two shapes come to mind:
Option A — new dedicated value
keycloak:
hostname: keycloak.example.com # browser-facing
backchannelURL: http://keycloak.svc.cluster.local:8080 # already exists; drives token/userinfo
adminBackchannelURL: "" # NEW; drives realm_api_url. Empty = derived from hostname.
Option B — extend the existing backchannelURL to cover the admin API leg
When keycloak.backchannelURL is set, ALSO derive realm_api_url from it. Rationale: any cluster that needs a backchannel URL for token/userinfo (because external hostname doesn't resolve in-cluster) needs one for the Admin API for the same reason.
I'd advocate for Option B — I can't come up with a realistic scenario where a deployer would want the token backchannel to be in-cluster but the Admin API leg to keep failing on the external hostname. Simpler surface; one less value.
Either way, docstring should call out this failure mode explicitly ("silent RBAC failure → /shared/* mounts missing → looks like a spawner bug") because it's easy to miss.
Reproduction
-
Deploy chart 0.1.3 with
keycloak.hostname: keycloak.example.comandkeycloak.backchannelURL: http://keycloak-keycloakx-http.keycloak.svc.cluster.local:8080on any cluster where the hub Pod can't resolvekeycloak.example.com. -
Ensure
KeyCloakOAuthenticatoris the runtime authenticator (chart default),realm_api_urlis set (chart derives it from hostname), and at least one Keycloak group holds theallow-group-directory-creation-roleclient role on the hub client. -
Have that user log in fresh, decrypt their
auth_state:groups_with_permission_to_mount: [] # RBAC failure preserved as empty auth_state.groups : [...] # populated correctly oauth_user.groups : [...] # populated correctly -
Spawn a user pod: no
/shared/*mounts, noinitialize-shared-mountsinit container. -
Hub logs show
HTTP 599: Could not resolve hostfor.../admin/realms/<realm>/clients.
Workaround
Override realm_api_url in hub.extraConfig alongside the existing 99-oauth-style entries:
c.KeyCloakOAuthenticator.realm_api_url = "http://keycloak-keycloakx-http.keycloak.svc.cluster.local:8080/admin/realms/nebari"
Also verified: silent RBAC failure preserves the last known groups_with_permission_to_mount value; on FIRST auth after a broken chart install that value is [], so users get an empty allow-list and no mounts. Consider logging at a higher level (INFO or WARN) when the fetch throws — even one loud message on install would have cut root-cause time in half here.
Related
- Chart's KC subclass in
config/jupyterhub/00-gateway-auth.py(seeKeyCloakConfig.build()+ thewire_keycloakcall).KeyCloakConfig.build()already accepts abackchannel_issuerand derivestoken_url/userdata_urlfrom it — an equivalent could easily deriverealm_api_urlfrom the same input. - #206 (sharedStorage.groups allowlist) — different bug but adjacent surface area; both surface as silent shared-storage misbehavior.
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 in config/jupyterhub/00-gateway-auth.py, reading KeyCloakConfig.build() and the wire_keycloak call to trace how backchannel_issuer currently derives token_url and userdata_url. Decide how the existing backchannel setting should affect realm_api_url, update the chart configuration and explanatory docstring, then verify the rendered configuration uses the in-cluster Admin API URL while preserving the hostname fallback.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- helm, kubernetes, python
- Domain
- authentication, authorization, cloud
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100