Keycloak¶
Keycloak provides OpenID Connect (OIDC) single sign-on for all HomeKube services. Once configured, you log in once via Keycloak and access Grafana (and future apps) without separate credentials.
See ADR-009 and ADR-011 for the full decision records.
Concepts¶
| Term | Meaning |
|---|---|
| Realm | An isolated namespace for users, clients, and roles (homekube) |
| Client | An application that delegates authentication to Keycloak (grafana) |
| User | A person who logs in |
| Role | A permission level (admin, viewer) mapped to Grafana roles |
| OIDC | OpenID Connect — the protocol used for login flows |
Architecture¶
sequenceDiagram
participant B as Browser
participant G as Grafana pod
participant K as Keycloak pod
B->>G: GET http://grafana.local
G->>B: 302 redirect to Keycloak login
B->>K: GET /realms/homekube/.../auth (keycloak.local)
K->>B: Show login form
B->>K: POST credentials
K->>B: 302 redirect back to Grafana with auth code
B->>G: GET /login/generic_oauth?code=...
G->>K: POST /token (keycloak.identity.svc.cluster.local)
K->>G: id_token + access_token
G->>B: Set session cookie, show dashboard
Split-DNS
The browser uses keycloak.local (resolved via /etc/hosts). The Grafana pod uses keycloak.identity.svc.cluster.local for server-side token exchange — pods cannot resolve /etc/hosts entries.
Deployment Model¶
Keycloak runs as a plain Kubernetes Deployment using the official image:
| Property | Value |
|---|---|
| Image | quay.io/keycloak/keycloak:26.3 |
| Mode | start-dev (embedded H2 database) |
| Realm import | --import-realm reads from /opt/keycloak/data/import/ on startup |
| Admin credentials | From keycloak-admin-secret (SOPS-encrypted) |
| Realm config | From realm-config.sops.yaml (SOPS-encrypted ConfigMap) |
| Manifest | identity/keycloak-deployment.yaml |
Ephemeral realm data
start-dev mode uses an in-memory H2 database. Any changes made via the Keycloak admin UI are lost on pod restart. All realm changes must go through identity/realm-config.sops.yaml — see Updating the realm config below.
Access Keycloak¶
Ensure /etc/hosts has:
Ensure minikube tunnel is running in a terminal (see ingress-nginx).
Open http://keycloak.local — admin console is at http://keycloak.local/admin.
Credentials are stored in identity/keycloak-secret.sops.yaml. Default (dev): admin / admin.
Pre-configured Realm¶
The homekube realm is auto-imported on every Keycloak startup from identity/realm-config.sops.yaml. It includes:
| Resource | Details |
|---|---|
| Realm | homekube |
| Client | grafana (confidential OIDC, maps roles to Grafana roles) |
| Roles | admin (full access), viewer (read-only) |
| Default user | admin / admin — change after first login |
Updating the Realm Config¶
All realm changes must be committed to Git and applied via Flux:
# 1. Edit the encrypted realm JSON
sops identity/realm-config.sops.yaml
# 2. Commit and push
git add identity/realm-config.sops.yaml
git commit -m "feat: add new OIDC client for my-app"
git push origin main
# 3. Force Flux to apply
flux reconcile kustomization identity --with-source
# 4. Restart Keycloak to reimport
kubectl rollout restart deployment/keycloak -n identity
kubectl rollout status deployment/keycloak -n identity
Grafana SSO¶
Grafana is pre-configured for Keycloak OIDC:
- Local login is disabled — Keycloak is the only login method
- Role mapping: Keycloak
adminrole → GrafanaAdmin; anything else →Viewer - Client secret stored in
observability/grafana-oidc-secret.sops.yaml
To log in: open http://grafana.local → you are redirected to Keycloak automatically.
Verify SSO is Working¶
# Check Keycloak pod is healthy
kubectl get pods -n identity
# Check Grafana can reach Keycloak (server-side)
kubectl exec -n monitoring deploy/kube-prometheus-stack-grafana -c grafana -- \
curl -s http://keycloak.identity.svc.cluster.local/realms/homekube/.well-known/openid-configuration \
| python3 -m json.tool | grep issuer
Open Grafana in an incognito window: http://grafana.local → sign in with admin / admin.
Operations¶
# View Keycloak logs
kubectl logs -n identity deployment/keycloak --tail=50 -f
# Force realm reimport (restart pod)
kubectl rollout restart deployment/keycloak -n identity
# Check admin console is reachable
curl -s -o /dev/null -w "%{http_code}" http://keycloak.local/realms/master
# Expected: 200
Phase 4: kubectl OIDC Auth¶
With OIDC configured, kubectl commands can authenticate via Keycloak instead of client certificates:
brew install int128/kubelogin/kubelogin
kubectl oidc-login setup \
--oidc-issuer-url=http://keycloak.local/realms/homekube \
--oidc-client-id=kubectl \
--oidc-client-secret=<secret>
This requires a kubectl client in Keycloak and kube-apiserver OIDC flags in minikube — covered in Phase 4.
Phase 4: oauth2-proxy¶
For apps that don't natively support OIDC, oauth2-proxy enforces authentication via ingress-nginx annotations: