livrare lot 2
This commit is contained in:
commit
8ecc78e729
763 changed files with 164593 additions and 0 deletions
203
backend/services/gateway-auth-layer/didiKeycloak/MIGRATION.md
Normal file
203
backend/services/gateway-auth-layer/didiKeycloak/MIGRATION.md
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
# Keycloak — migrat pe SSO cluster (2026-04-30)
|
||||
|
||||
> **TL;DR**: containerul local `keycloak` (Keycloak 22) nu mai rulează. DIDI folosește acum **SSO cluster** la `https://<sso-extern>` (Keycloak 26 HA, 3 replicas pe Dev Docker Swarm). Realm `didi-clients` migrat cu toate datele (users, clients, groups). Theme custom `didi-clients-theme` deployed 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
|
||||
|
||||
1. **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.
|
||||
2. **Single-point-of-failure**: 1 container, 1 host. Down când didi12 down.
|
||||
3. **DB password issue**: 2026-04-29 cineva a rotat parola `keycloak` user în Patroni → connection pool fail → service degraded.
|
||||
4. **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
|
||||
|
||||
```yaml
|
||||
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:
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
1. Pack theme local pe didi12:
|
||||
```bash
|
||||
cd backend/services/gateway-auth-layer/didiKeycloak/themes
|
||||
tar -czf /home/admin365/didi-clients-theme.tar.gz didi-clients-theme/
|
||||
```
|
||||
|
||||
2. Pe `dev-docker-mgr` (Swarm manager — 10.11.50.151):
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
3. Verify:
|
||||
```bash
|
||||
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 `::after` pe button** — PF4 button-uri au `<button>::after { border: ...; position: absolute }` care creează "chenarul". Trebuie killed cu `display:none !important` și `content: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`:
|
||||
|
||||
```typescript
|
||||
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.
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
1. **Reset DB password** (era broken pentru user `keycloak`):
|
||||
```sql
|
||||
ALTER USER keycloak WITH PASSWORD 'keycloak123';
|
||||
```
|
||||
|
||||
2. **Restore docker-compose**:
|
||||
```bash
|
||||
git revert <commit-care-sterge-keycloak-din-compose>
|
||||
cd backend/production && docker compose up -d keycloak
|
||||
```
|
||||
|
||||
3. **Update SPA** să re-folosească local:
|
||||
- În `frontend/web/src/services/keycloak.service.ts`, schimbă URL la `${origin}/auth`
|
||||
- Rebuild + redeploy didi-frontend
|
||||
|
||||
4. **Restore /auth proxy** în `frontend/nginx-default.conf` (la cluster Kong sau local Keycloak)
|
||||
|
||||
## Linkuri rapide
|
||||
|
||||
- SSO public: <https://<sso-extern>/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-data` păstrat până 2026-05-07
|
||||
|
||||
## Lecții importante
|
||||
|
||||
1. **`KC_HOSTNAME` schimbă 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`).
|
||||
|
||||
2. **Keycloak 26 a renunțat la `/auth` prefix** — endpoints sunt `/realms/...`. Theme cu `parent=keycloak` moștenește templates PF4. Custom CSS trebuie să acopere PF4 markup specific (input-group, button::after pseudo-elements).
|
||||
|
||||
3. **`KC_HOSTNAME_ADMIN`** — separă admin de URL-ul public (security best practice). Admin via `<sso-extern-admin>` (intern), client-facing via `<sso-extern>`.
|
||||
|
||||
4. **Bind mount pe Swarm** cere theme files pe **toate** nodurile (constraint `max 1 per node` cu 3 replicas → cel puțin 3 din 4 noduri rulează task). Fișiere pe 4 noduri = sigur.
|
||||
|
||||
5. **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.
|
||||
|
||||
6. **PatternFly v4 button `::after`** — chenarul "fantomă" pe button-uri vine din pseudo-element absolut poziționat. Trebuie `content: none !important` ca să-l killezi.
|
||||
Loading…
Add table
Add a link
Reference in a new issue