Skip to main content

Platform admin login

kube-dc login --admin is the daily-driver for cluster operators. Cluster-wide RBAC runs through the Keycloak master realm's admin group, and the audit logs carry each engineer's email. There is no shared kubeconfig.

kube-dc login --domain <cluster-domain> --admin

What happens:

  1. A browser opens. The flow is the same as tenant sign-in, but it runs against https://login.<domain>/realms/master and the kube-dc-admin PKCE OIDC client.
  2. The CLI verifies your JWT carries the admin group (master realm). If it doesn't, the login is refused with a clear hint to ask a Keycloak admin to add you.
  3. Tokens land at ~/.kube-dc/credentials/<server>-master.json. That is a separate file from any tenant tokens for the same cluster, and the two coexist.
  4. A single context kube-dc/<domain>/admin is added; current-context is switched to it.

Verify:

kubectl config current-context # → kube-dc/<domain>/admin
kubectl get nodes # cluster-admin
kubectl auth can-i '*' '*' --all-namespaces # → yes

The audit log entry on the apiserver shows your email, not a shared service-account token.


Add an admin (once for each person)​

The admin RBAC chain is:

one Keycloak group (admin in master realm) → one K8s group (platform:admin) → one ClusterRoleBinding (platform-admin → cluster-admin).

There's no per-person fleet-repo change; granting admin to a new engineer is a Keycloak group-membership edit, nothing more.

Via the Keycloak UI​

  1. Open https://login.<cluster-domain>/admin → master realm → Users → Add user.
  2. Set username + email (the email is what kubectl auth whoami displays as Username).
  3. Credentials tab → set the password (uncheck "Temporary" if they don't need to rotate on first login).
  4. Groups tab → Join the admin group.
  5. Have them run kube-dc login --domain <cluster-domain> --admin.

Via kcadm​

Run from any host with the Keycloak admin password (or from a pod with kcadm.sh available):

kcadm.sh config credentials --server https://login.<cluster-domain> \
--realm master --user admin --password "<master-admin-pass>"

kcadm.sh create users -r master \
-s username=engineer -s email=engineer@example.com -s enabled=true

kcadm.sh set-password -r master --username engineer --new-password "<temp>"

# Look up the admin group's UUID once, then add the user
GID=$(kcadm.sh get groups -r master -q search=admin --fields id,name | jq -r '.[]|select(.name=="admin").id')
UID=$(kcadm.sh get users -r master -q username=engineer --fields id | jq -r '.[0].id')
kcadm.sh update users/$UID/groups/$GID -r master -n

Verify that it worked​

After the new admin runs kube-dc login --domain <cluster-domain> --admin, both of these should succeed:

# Identity check: Username should be their email, Groups should include platform:admin
kubectl auth whoami
# ATTRIBUTE VALUE
# Username platform:engineer@example.com ← email from the JWT
# Groups [platform:admin system:authenticated]

# RBAC check
kubectl auth can-i '*' '*' --all-namespaces # → yes

If Username is correct but Groups is missing platform:admin, the user is not in the Keycloak admin group. Re-check the Groups tab on the user.

If Username is correct AND Groups contains platform:admin but kubectl get nodes returns 403, the platform-admin ClusterRoleBinding isn't on the cluster yet. Check Flux:

kubectl get kustomization -n flux-system core
# Should be Ready=True. If not, `flux reconcile kustomization core --with-source`.

Remove an admin​

Remove the user from the admin group:

kcadm.sh delete users/$UID/groups/$GID -r master

Their next OIDC token refresh (≤5 min, governed by Keycloak's access-token lifespan) will land without the groups: ["admin"] claim, and the apiserver will start refusing privileged calls. To revoke immediately, delete the user account entirely with kcadm.sh delete users/$UID -r master. That also invalidates the outstanding refresh tokens.


Login from a headless machine​

Both browser and device login discover the API CA automatically on a fresh machine. The master-realm identity and permissions are independent of the public certificate metadata.

kube-dc login --domain kube-dc.cloud --admin --device-code

The CLI displays a URL and code. Open the URL in a browser on another device, enter the code, and approve the login. The same master-realm admin group check, credential cache, and admin kubeconfig context apply.

The kube-dc-admin client in master must have OAuth 2.0 Device Authorization Grant enabled. The updated fleet bootstrap/setup-keycloak-oidc.sh sets this on both creation and update. For an existing installation, rerun that script or enable the capability in Keycloak's client settings. Its JSON attribute is "oauth2.device.authorization.grant.enabled": "true".

The same client's Valid redirect URIs are http://localhost:* and http://127.0.0.1:* — the browser login's loopback callback and nothing else. The client is public, so this list alone decides where Keycloak returns a login code; clusters bootstrapped before 2026-10-07 carried *, which let any site that could show an administrator the real login page collect a platform-admin code. Rerunning bootstrap/setup-keycloak-oidc.sh converges an installed cluster. Keycloak matches a trailing * as a string prefix, which is why http://localhost:* admits the CLI's random port while http://localhost/* does not.

Pre-flight on a fresh cluster​

The CLI side works against any cluster, but a fresh cluster needs four pieces wired up before kube-dc login --admin resolves:

  1. The kube-dc-admin PKCE OIDC client in the master realm.
  2. Two protocol mappers on that client:
    • groups (oidc-group-membership-mapper, full.path: false) → JWT carries groups: ["admin"]
    • audience (oidc-audience-mapper, included.client.audience: kube-dc-admin) → JWT carries aud: [..., "kube-dc-admin", ...]. Without it the apiserver rejects every token with 401, even when the group and claim mapping look correct. the audience-validation path is silent in apiserver logs and easy to miss.
  3. The master OpenIDConnect.authentication.gardener.cloud/v1alpha1 CR (kube-dc-manager creates this on startup with audiences: [kube-dc-admin] + usernamePrefix: "platform:").
  4. The platform-admin ClusterRoleBinding (Group: platform:admin → cluster-admin).

Install them like this:

# 1. From the fleet repo, on a workstation with admin (or break-glass) access:
cd <fleet-repo-path>
git pull
export KUBECONFIG=~/.kube/<cluster>-admin-or-break-glass

# 2. Apply the ClusterRoleBinding (or wait for Flux to reconcile it)
kubectl apply -f infrastructure/platform-admin/clusterrolebinding-platform-admin.yaml

# 3. Create the kube-dc-admin client + both protocol mappers in Keycloak (idempotent)
bash bootstrap/setup-keycloak-oidc.sh <cluster>

# 4. Make sure kube-dc-manager (v0.3.0+) is running so its OIDCRegistry
# ReconcileAll creates the master OpenIDConnect CR. Roll if behind:
kubectl rollout restart deployment/kube-dc-manager -n kube-dc

Verify the master CR landed with the right shape:

kubectl get openidconnect master -o yaml | grep -A1 "audiences:\|usernamePrefix:"
# expect:
# audiences:
# - kube-dc-admin
# usernamePrefix: 'platform:'

The gardener oidc-webhook-authenticator picks the CR up through informer within seconds and registers it in its in-memory issuer→authenticator map. There is no apiserver restart and no file sync. kube-dc login --admin resolves on the next attempt.