nebari-dev / nebari-dev/data-science-pack
feat(auth): extend split-horizon OIDC to Keycloak Admin API (realm_api_url)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 5
- Forks
- 7
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 13
Description
Summary
PR #161 gave the hub split-horizon for two of the three Keycloak backchannel legs — token_url and userdata_url. But KeyCloakOAuthenticator also calls a third Keycloak endpoint that #161 didn't cover: the Admin API at realm_api_url, used by update_auth_model() and refresh_user() to filter group membership by client role. On private-VPC clusters where the external Keycloak hostname doesn't resolve in-cluster (the exact scenario #158/#161 addressed), this fails the same way — the auto-derived URL points at an unreachable hostname.
Failure mode
config/jupyterhub/00-gateway-auth.py derives realm_api_url from the issuer URL read out of the OIDC-client Secret:
_issuer = _read_secret_file(_secret_dir, "issuer-url") # external URL
_realm_api_url = (
os.environ.get("KC_REALM_API_URL")
or _derive_realm_api_url(_issuer) # -> /admin/... same external host
)
On a private-VPC deploy this yields https://keycloak.<external>/admin/realms/<realm>, which the hub pod can't resolve. Downstream:
update_auth_model()catches the exception and stampsauth_state["groups_with_permission_to_mount"] = []_resolve_allowed_profiles()catches the exception and stampsauth_state["allowed_jupyterlab_profiles"] = []- Spawner's
_get_user_groups()sees the empty key present in auth_state and skips its fallback toauth_state.get("groups", []) - Result: no
/shared/<group>mounts, no keycloak-gated profiles, silent — the only signal is aWARNINGin the hub log
The chart's KC_REALM_API_URL env-var escape hatch works as a bridge (I've verified it end-to-end on a real deploy) but it means every private-VPC deployer has to know about it and configure it out-of-band.
Proposal — universal backchannel base, plus targeted override
Two additive changes to keep the existing backchannelURL mental model:
1. Widen keycloak.backchannelURL to be the base for all three backchannel legs
The docstring already frames backchannelURL as "base URL used for backchannel legs" — extend that to cover the admin URL derivation too. <backchannelURL>/admin/realms/<realm> becomes the default realm_api_url when backchannelURL is set. No new value; existing users of #161 get the Admin-API leg for free.
2. Optional keycloak.adminBackchannelURL explicit override
For the rare case where the Admin API sits somewhere the backchannelURL + /admin/realms/<realm> pattern doesn't reach (different port, different path prefix, separate admin gateway). Mirrors #161's custom.keycloak-backchannel-issuer-url explicit override.
Precedence at the Python side
Same layering shape as #161 already established for keycloak-token-url:
os.environ.get("KC_REALM_API_URL") # legacy env override, kept
or get_chart_config("keycloak-admin-api-url") # NEW — chart-derived
or _derive_realm_api_url(_issuer) # existing fallback
Helper (_helpers.tpl) emits keycloak-admin-api-url with the precedence:
explicit `keycloak.adminBackchannelURL`
else `keycloak.backchannelURL` + "/admin/realms/" + `keycloak.realm`
else "" (Python falls back to issuer-derived)
Fully backward-compat — deploys not on private-VPC never set backchannelURL so admin URL stays issuer-derived exactly as today.
Related
- Closes the third backchannel leg #158/#161 opened
- Suggest also adding a hub-log
INFOline atupdate_auth_modelstartup echoing whichrealm_api_urlthe authenticator resolved to — the current failure mode is entirely silent to users unless someone knows tokubectl logs | grep rbac:. Even a one-line "using realm_api_url=" at startup would have saved the discovery time on my side.
Runtime dependency (unchanged from #161)
Keycloak must be running with KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true so tokens minted via the backchannel URL still carry the browser-facing iss claim — same requirement as #161 documented for token/userinfo.
Verification path once implemented
- On a cluster with
keycloak.backchannelURLset (no explicitadminBackchannelURL):update_auth_modelsucceeds,groups_with_permission_to_mountpopulated,/shared/<group>mounts appear - On a cluster without
backchannelURL: no behavior change, admin URL still derives from issuer - Explicit
adminBackchannelURLtakes precedence when set KC_REALM_API_URLenv var still overrides both (legacy escape hatch preserved)
Happy to open a PR against this if the maintainers agree on shape. Would build on top of #161.
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 config/jupyterhub/00-gateway-auth.py and the chart's _helpers.tpl, tracing how realm_api_url and keycloak-token-url are derived in the layering established by #161. Check the update_auth_model and refresh_user paths, then verify the listed backchannelURL, adminBackchannelURL, fallback, and KC_REALM_API_URL precedence cases, including group and profile behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- helm, kubernetes, python
- Domain
- authentication, backend, infrastructure
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100