9.1 KiB
Keycloak — migrat pe SSO cluster (2026-04-30)
TL;DR: containerul local
keycloak(Keycloak 22) nu mai rulează. DIDI folosește acum SSO cluster lahttps://<sso-extern>(Keycloak 26 HA, 3 replicas pe Dev Docker Swarm). Realmdidi-clientsmigrat cu toate datele (users, clients, groups). Theme customdidi-clients-themedeployed pe SSO via bind mount pe nodurile Swarm.
Ce era aici (înainte de 2026-04-30)
Container keycloak (Keycloak 22, image quay.io/keycloak/keycloak:22.0) definit în backend/production/docker-compose.yml. Single-instance pe didi12 (10.11.10.12:28000). DB pe Patroni cluster (keycloak_db). Hostname fix KC_HOSTNAME_URL=https://didi365.eu/auth.
Folosit doar de DIDI. Theme custom didi-clients-theme (purple gradient).
De ce migrare
- Single-tenant lock-in: Keycloak local servea doar didi365.eu. Pentru alte produse (lege365, rafai, etc.) ar fi trebuit instanțe separate sau hostname dinamic complex.
- Single-point-of-failure: 1 container, 1 host. Down când didi12 down.
- DB password issue: 2026-04-29 cineva a rotat parola
keycloakuser în Patroni → connection pool fail → service degraded. - SSO cluster live: 2026-04-29 Lucian a deploy-uit Keycloak 26 HA pe Dev Swarm, cu hostname public
<sso-extern>.
Ce e acum
SSO Cluster
| Componentă | Detaliu |
|---|---|
| Hostname public | <sso-extern> (DNS public, cert Let's Encrypt valid) |
| Hostname intern | <sso-extern-admin> (admin URL via KC_HOSTNAME_ADMIN) |
| IP public | 82.79.147.181 (port-forward la Traefik intern) |
| Edge router | Traefik central (10.11.10.171:443) |
| Keycloak version | 26.0 (quay.io/keycloak/keycloak:26.0) |
| HA | 3 replicas, max 1 per node |
| Cluster | Dev Docker Swarm (10.11.50.151-154) |
| DB | Patroni cluster (10.11.50.166:5000/keycloak_db) |
| Cache | ispn (Infinispan, dns.query=tasks.keycloak) |
| Stack name | keycloak-cluster (docker service ls) |
Hostname configuration
KC_HOSTNAME: https://<sso-extern> # public URL (used in tokens, redirects)
KC_HOSTNAME_ADMIN: https://<sso-extern-admin> # admin endpoints (internal-only via 307 redirect)
KC_HOSTNAME_STRICT_BACKCHANNEL: false
KC_PROXY_HEADERS: xforwarded
Issuer in tokens: https://<sso-extern>/realms/didi-clients. Endpoints (no /auth/ prefix in K26):
/realms/didi-clients/.well-known/openid-configuration/realms/didi-clients/protocol/openid-connect/auth/realms/didi-clients/protocol/openid-connect/token/realms/didi-clients/protocol/openid-connect/certs(JWKS)
Realm-uri pe SSO
master— admin Keycloak (NU folosi pentru apps)didi-clients— DIDI customer-facing app (migrat 1:1 din local)didi-admins— DIDI admin panel (creat de Lucian, neutilizat încă)
Theme deployment (didi-clients-theme)
Themes sunt mounted ca bind mount pe fiecare nod Swarm:
mount:
type: bind
source: /var/keycloak-themes/didi-clients-theme
target: /opt/keycloak/themes/didi-clients-theme
readonly: true
Adăugat via docker service update --mount-add (nu via stack file). Pentru ca toate 3 replicas să găsească tema, fișierele trebuie pe toate 4 nodurile Swarm (10.11.50.151-154).
Procedură deploy theme update
-
Pack theme local pe didi12:
cd backend/services/gateway-auth-layer/didiKeycloak/themes tar -czf /home/admin365/didi-clients-theme.tar.gz didi-clients-theme/ -
Pe
dev-docker-mgr(Swarm manager — 10.11.50.151):scp admin365@10.11.10.12:/home/admin365/didi-clients-theme.tar.gz /tmp/ sudo tar -xzf /tmp/didi-clients-theme.tar.gz -C /var/keycloak-themes/ for n in 152 153 154; do scp /tmp/didi-clients-theme.tar.gz admin365@10.11.50.$n:/tmp/ ssh -t admin365@10.11.50.$n 'sudo tar -xzf /tmp/didi-clients-theme.tar.gz -C /var/keycloak-themes/' done sudo docker service update --force keycloak-cluster_keycloak -
Verify:
curl -ksm 5 https://<sso-extern>/resources/<version>/login/didi-clients-theme/css/login.css | head
Theme structure (PatternFly v4 specific)
Keycloak 26 default theme (keycloak) folosește PatternFly v4 markup. Custom theme cu parent=keycloak moștenește template-urile, dar PF4 are reguli CSS specifice care necesită overrides în login.css:
- Password input wrap — în
<div class="pf-c-input-group">cu eye-icon button. Necesită CSS specific pentru.pf-c-input-group .pf-c-form-control - Pseudo-element
::afterpe button — PF4 button-uri au<button>::after { border: ...; position: absolute }care creează "chenarul". Trebuie killed cudisplay:none !importantșicontent:none !important
Vezi themes/didi-clients-theme/login/resources/css/login.css secțiunile PatternFly v4 input-group fix și Eye-icon button pentru detalii.
Cluster Kong JWT consumer
Consumer didi-keycloak-users în cluster Kong (10.11.10.176:8001) are 4 issuers acceptate pentru tranziție smooth:
https://didi365.eu/auth/realms/didi-clients # legacy local Keycloak
https://<host-local>/auth/realms/didi-clients # legacy intern alias
https://<sso-extern-admin>/realms/didi-clients # SSO intern (transition)
https://<sso-extern>/realms/didi-clients # SSO public canonical (CURRENT)
Toate 4 au același RSA public key (Lucian a exportat realm-ul cu key preserved la migrare). Tokens emise acum de SSO au iss=https://<sso-extern>/realms/didi-clients — cluster Kong validează corect.
Vezi didiKong/declarative/kong-cluster.yml pentru config consumer.
SPA configuration (didi-frontend)
web/src/services/keycloak.service.ts:
const keycloakUrl =
(import.meta.env.VITE_KEYCLOAK_URL as string | undefined) ||
'https://<sso-extern>';
const keycloak = new Keycloak({
url: keycloakUrl,
realm: 'didi-clients',
clientId: 'didi-web-app',
});
SPA construiește toate URL-urile (auth, token, logout) relativ la https://<sso-extern>. Browser-ul user-ului se redirectează direct la SSO (NU prin proxy local). Cookie-urile Keycloak sunt setate pentru <sso-extern> domain.
Ce a rămas local
În folder-ul ăsta (didiKeycloak/):
themes/— sursa originală a temelor (didi-clients-theme, didi-ai-theme, didi-backend-theme). Folosită ca master pentru deployment pe SSO cluster.realm-import/didi-clients-realm.json— backup realm config (legacy, nu mai e mounted)Dockerfile— pentru imagine custom Keycloak 22 (legacy, nemai folosit)
Volume cleanup
Volume-ul didi-production-keycloak-data (DB H2 local + cache) e păstrat 1 săptămână pentru rollback safety.
# Remove după 2026-05-07:
docker volume rm didi-production-keycloak-data
Rollback (în caz de probleme)
Containerul local Keycloak nu mai există. Pentru rollback:
-
Reset DB password (era broken pentru user
keycloak):ALTER USER keycloak WITH PASSWORD 'keycloak123'; -
Restore docker-compose:
git revert <commit-care-sterge-keycloak-din-compose> cd backend/production && docker compose up -d keycloak -
Update SPA să re-folosească local:
- În
frontend/web/src/services/keycloak.service.ts, schimbă URL la${origin}/auth - Rebuild + redeploy didi-frontend
- În
-
Restore /auth proxy în
frontend/nginx-default.conf(la cluster Kong sau local Keycloak)
Linkuri rapide
- SSO public: <https:///admin/master/console/> (admin: admin365 / parolă din credentials)
- SSO intern:
https://<sso-extern-admin>/admin/(via /etc/hosts → 10.11.10.171, doar intern) - Cluster setup repo:
landingzone/keycloak-sso/(git.finesynergy.eu/lucian/landingzone) - Stack file: pe
dev-docker-mgr(Lucian) sau în repo
Status
- ✅ Migrare aplicată: 2026-04-30
- ✅ Container local oprit + șters
- ✅ Service definition removed din docker-compose
- ✅ Theme deployed pe SSO 3 replicas
- ✅ End-to-end auth flow verificat (login, dashboard, JWT validare prin cluster Kong)
- ⏳ Volume
didi-production-keycloak-datapăstrat până 2026-05-07
Lecții importante
-
KC_HOSTNAMEschimbă tot răspunsul Keycloak — toate URL-urile generate, issuer-ul în tokens, cookie domain. Pentru aplicații web publice trebuie hostname public DNS-resolvable (NU.local). -
Keycloak 26 a renunțat la
/authprefix — endpoints sunt/realms/.... Theme cuparent=keycloakmoștenește templates PF4. Custom CSS trebuie să acopere PF4 markup specific (input-group, button::after pseudo-elements). -
KC_HOSTNAME_ADMIN— separă admin de URL-ul public (security best practice). Admin via<sso-extern-admin>(intern), client-facing via<sso-extern>. -
Bind mount pe Swarm cere theme files pe toate nodurile (constraint
max 1 per nodecu 3 replicas → cel puțin 3 din 4 noduri rulează task). Fișiere pe 4 noduri = sigur. -
Realm export/import păstrează RSA signing keys — JWT-urile vechi rămân valide după migrare. Cluster Kong consumer poate avea multiple issuers cu același public_key.
-
PatternFly v4 button
::after— chenarul "fantomă" pe button-uri vine din pseudo-element absolut poziționat. Trebuiecontent: none !importantca să-l killezi.