diff --git a/RBER Connect/keycloak-iam-documentation.md b/RBER Connect/keycloak-iam-documentation.md new file mode 100644 index 0000000..971b41a --- /dev/null +++ b/RBER Connect/keycloak-iam-documentation.md @@ -0,0 +1,812 @@ +# Keycloak IAM - Universités du Bénin +## Documentation Technique Complète + +--- + +**Version :** 1.0 +**Date :** 5 Janvier 2026 +**Projet :** IAM Multi-Universités (UAC, UNA, UNSTIM, UP) +**Infrastructure :** RKE2 / Kubernetes + +--- + +# Table des matières + +1. [Architecture Générale](#1-architecture-générale) +2. [Document d'Installation](#2-document-dinstallation) +3. [Document d'Administration Générale](#3-document-dadministration-générale) +4. [Administration par Realm](#4-administration-par-realm) +5. [Procédures Opérationnelles](#5-procédures-opérationnelles) +6. [Dépannage](#6-dépannage) + +--- + +# 1. Architecture Générale + +## 1.1 Vue d'ensemble + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ INTERNET │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 102.222.216.6 │ + │ (NAT/DNS) │ + └────────┬────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ HAProxy Externe │ +│ (rber-ldb-int-01) │ +│ 10.29.113.21 │ +│ - Terminaison SNI (port 443) │ +│ - Routage par domaine │ +│ - Health checks │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ (auth.rber.bj → bk_k8s_ingress_https) +┌─────────────────────────────────────────────────────────────────────┐ +│ HAProxy Ingress Controller │ +│ 10.29.113.201:443 │ +│ (Kubernetes Ingress) │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ Keycloak │ +│ Namespace: keycloak │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ keycloak-0 │ │ keycloak-1 │ (2 réplicas) │ +│ │ Port 8080 │ │ Port 8080 │ │ +│ └────────┬────────┘ └────────┬────────┘ │ +│ │ │ │ +│ └──────────┬───────────┘ │ +│ ▼ │ +│ ┌─────────────────────┐ │ +│ │ PostgreSQL HA │ │ +│ │ (CloudNativePG) │ │ +│ │ keycloak-pg-1/2/3 │ │ +│ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ (LDAP Federation) +┌─────────────────────────────────────────────────────────────────────┐ +│ Annuaires LDAP Universités │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ UAC │ │ UNA │ │ UNSTIM │ │ UP │ │ +│ │10.24.112.33 │ │10.20.112.33 │ │10.21.112.33 │ │10.25.112.33 │ │ +│ │ Port 389 │ │ Port 389 │ │ Port 389 │ │ Port 389 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## 1.2 Composants + +| Composant | Version | Rôle | +|-----------|---------|------| +| Keycloak | 26.0 | Serveur IAM | +| PostgreSQL | CloudNativePG | Base de données HA | +| HAProxy | 2.8.5 | Load Balancer externe | +| HAProxy Ingress | - | Ingress Controller K8s | +| RKE2 | - | Distribution Kubernetes | + +## 1.3 Realms configurés + +| Realm | Université | LDAP | Base DN | +|-------|-----------|------|---------| +| master | Administration | - | - | +| uac | Université d'Abomey-Calavi | 10.24.112.33 | DC=uac,DC=bj | +| una | Université Nationale d'Agriculture | 10.20.112.33 | DC=una,DC=bj | +| unstim | Université des Sciences, Technologies, Ingénierie et Mathématiques | 10.21.112.33 | DC=unstim,DC=bj | +| up | Université de Parakou | 10.25.112.33 | DC=univ-parakou,DC=bj | + +## 1.4 URLs d'accès + +| Service | URL | +|---------|-----| +| Console Admin | https://auth.rber.bj/admin | +| Realm UAC | https://auth.rber.bj/realms/uac | +| Realm UNA | https://auth.rber.bj/realms/una | +| Realm UNSTIM | https://auth.rber.bj/realms/unstim | +| Realm UP | https://auth.rber.bj/realms/up | + +--- + +# 2. Document d'Installation + +## 2.1 Prérequis + +### Infrastructure +- Cluster RKE2 opérationnel +- Namespace `keycloak` créé +- HAProxy Ingress Controller déployé (10.29.113.201) +- Certificat SSL pour auth.rber.bj + +### Réseau +- Connectivité vers les LDAP des universités (ports 389) +- DNS configuré : auth.rber.bj → 102.222.216.6 + +## 2.2 Installation PostgreSQL (CloudNativePG) + +### 2.2.1 Créer le cluster PostgreSQL + +```yaml +# keycloak-pg-cluster.yaml +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: keycloak-pg + namespace: keycloak +spec: + instances: 3 + primaryUpdateStrategy: unsupervised + storage: + size: 10Gi + bootstrap: + initdb: + database: keycloak + owner: keycloak + backup: + barmanObjectStore: + # Configurer selon votre stockage + retentionPolicy: "30d" +``` + +```bash +kubectl apply -f keycloak-pg-cluster.yaml +``` + +### 2.2.2 Vérifier le déploiement + +```bash +kubectl get pods -n keycloak +# Attendu: keycloak-pg-1, keycloak-pg-2, keycloak-pg-3 en Running +``` + +## 2.3 Installation Keycloak + +### 2.3.1 Créer le secret admin + +```bash +kubectl create secret generic keycloak-admin-secret -n keycloak \ + --from-literal=admin-password='VotreMotDePasseSecurise' +``` + +### 2.3.2 Déployer Keycloak + +```yaml +# keycloak-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak + namespace: keycloak + labels: + app: keycloak +spec: + replicas: 2 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:26.0 + args: ["start"] + env: + - name: KC_HOSTNAME + value: "auth.rber.bj" + - name: KC_PROXY_HEADERS + value: "xforwarded" + - name: KC_HTTP_ENABLED + value: "true" + - name: KC_HEALTH_ENABLED + value: "true" + - name: KC_HTTP_MANAGEMENT_PORT + value: "9000" + - name: KC_DB + value: "postgres" + - name: KC_DB_URL_HOST + value: "keycloak-pg-rw" + - name: KC_DB_URL_PORT + value: "5432" + - name: KC_DB_URL_DATABASE + value: "keycloak" + - name: KC_DB_USERNAME + valueFrom: + secretKeyRef: + name: keycloak-pg-app + key: username + - name: KC_DB_PASSWORD + valueFrom: + secretKeyRef: + name: keycloak-pg-app + key: password + - name: KEYCLOAK_ADMIN + value: "admin" + - name: KEYCLOAK_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: keycloak-admin-secret + key: admin-password + ports: + - name: http + containerPort: 8080 + - name: management + containerPort: 9000 + readinessProbe: + httpGet: + path: /health/ready + port: 9000 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /health/live + port: 9000 + initialDelaySeconds: 120 + periodSeconds: 10 + timeoutSeconds: 5 + startupProbe: + httpGet: + path: /health/started + port: 9000 + initialDelaySeconds: 30 + periodSeconds: 5 + failureThreshold: 30 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1" +--- +apiVersion: v1 +kind: Service +metadata: + name: keycloak + namespace: keycloak + labels: + app: keycloak +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: keycloak +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: keycloak + namespace: keycloak + annotations: + haproxy.org/cookie-persistence: "SERVERID" + haproxy.org/timeout-server: "120s" +spec: + ingressClassName: haproxy + rules: + - host: auth.rber.bj + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: keycloak + port: + number: 8080 +``` + +```bash +kubectl apply -f keycloak-deployment.yaml +``` + +### 2.3.3 Vérifier le déploiement + +```bash +# Pods +kubectl get pods -n keycloak + +# Logs +kubectl logs -n keycloak -l app=keycloak --tail=50 + +# Test health +kubectl exec -it -n keycloak deploy/keycloak -- curl -s localhost:9000/health/ready +``` + +## 2.4 Configuration HAProxy + +### 2.4.1 ACL SNI pour Keycloak + +Ajouter dans `/etc/haproxy/haproxy.cfg` section `frontend https_frontend` : + +``` +# ACL Keycloak +acl sni_keycloak req_ssl_sni -i auth.rber.bj + +# Routage (ajouter sni_keycloak à la règle existante) +use_backend bk_k8s_ingress_https if sni_unstim or sni_uac or sni_una or sni_up or sni_test_unstim or sni_keycloak +``` + +### 2.4.2 ACL HTTP pour Keycloak + +Ajouter dans `frontend http_frontend` : + +``` +acl is_keycloak hdr(host) -i auth.rber.bj + +# Ajouter à la règle use_backend bk_k8s_ingress +use_backend bk_k8s_ingress if ... or is_keycloak +``` + +### 2.4.3 Recharger HAProxy + +```bash +haproxy -c -f /etc/haproxy/haproxy.cfg +systemctl reload haproxy +``` + +## 2.5 Création des Realms + +### 2.5.1 Script de création + +```bash +#!/bin/bash +# create-realms.sh + +export KEYCLOAK_URL="https://auth.rber.bj" +export ADMIN_PASSWORD=$(kubectl get secret keycloak-admin-secret -n keycloak -o jsonpath='{.data.admin-password}' | base64 -d) + +# Obtenir token +export TOKEN=$(curl -sk -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=admin" \ + -d "password=$ADMIN_PASSWORD" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" | jq -r '.access_token') + +# Créer les realms +declare -A REALMS=( + ["uac"]="Université d Abomey-Calavi" + ["una"]="Université Nationale d Agriculture" + ["unstim"]="Université des Sciences Technologies Ingénierie et Mathématiques" + ["up"]="Université de Parakou" +) + +for REALM in "${!REALMS[@]}"; do + curl -sk -X POST "$KEYCLOAK_URL/admin/realms" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"realm\":\"$REALM\",\"enabled\":true,\"displayName\":\"${REALMS[$REALM]}\"}" + echo "Realm $REALM créé" +done +``` + +## 2.6 Configuration des Fédérations LDAP + +### 2.6.1 Script de configuration LDAP + +```bash +#!/bin/bash +# configure-ldap.sh + +export KEYCLOAK_URL="https://auth.rber.bj" +export ADMIN_PASSWORD=$(kubectl get secret keycloak-admin-secret -n keycloak -o jsonpath='{.data.admin-password}' | base64 -d) + +# Obtenir token +export TOKEN=$(curl -sk -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=admin" \ + -d "password=$ADMIN_PASSWORD" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" | jq -r '.access_token') + +# Configuration LDAP par université +configure_ldap() { + local REALM=$1 + local LDAP_URL=$2 + local BIND_DN=$3 + local BIND_PASSWORD=$4 + local USERS_DN=$5 + + curl -sk -X POST "$KEYCLOAK_URL/admin/realms/$REALM/components" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"ldap-$REALM\", + \"providerId\": \"ldap\", + \"providerType\": \"org.keycloak.storage.UserStorageProvider\", + \"config\": { + \"vendor\": [\"ad\"], + \"connectionUrl\": [\"$LDAP_URL\"], + \"bindDn\": [\"$BIND_DN\"], + \"bindCredential\": [\"$BIND_PASSWORD\"], + \"usersDn\": [\"$USERS_DN\"], + \"usernameLDAPAttribute\": [\"sAMAccountName\"], + \"rdnLDAPAttribute\": [\"cn\"], + \"uuidLDAPAttribute\": [\"objectGUID\"], + \"userObjectClasses\": [\"person, organizationalPerson, user\"], + \"editMode\": [\"READ_ONLY\"], + \"importEnabled\": [\"true\"], + \"enabled\": [\"true\"] + } + }" + echo "LDAP configuré pour $REALM" +} + +# Appliquer les configurations +configure_ldap "uac" "ldap://10.24.112.33:389" "Administrator@UAC.BJ" "MOT_DE_PASSE" "CN=Users,DC=uac,DC=bj" +configure_ldap "una" "ldap://10.20.112.33:389" "Administrator@UNA.BJ" "MOT_DE_PASSE" "CN=Users,DC=una,DC=bj" +configure_ldap "unstim" "ldap://10.21.112.33:389" "Administrator@UNSTIM.BJ" "MOT_DE_PASSE" "CN=Users,DC=unstim,DC=bj" +configure_ldap "up" "ldap://10.25.112.33:389" "Administrator@UNIV-PARAKOU.BJ" "MOT_DE_PASSE" "CN=Users,DC=univ-parakou,DC=bj" +``` + +--- + +# 3. Document d'Administration Générale + +## 3.1 Accès à la console d'administration + +### URL +``` +https://auth.rber.bj/admin +``` + +### Récupérer le mot de passe admin +```bash +kubectl get secret keycloak-admin-secret -n keycloak -o jsonpath='{.data.admin-password}' | base64 -d && echo +``` + +## 3.2 Gestion des tokens API + +### Obtenir un token d'administration +```bash +export KEYCLOAK_URL="https://auth.rber.bj" +export ADMIN_PASSWORD="votre_mot_de_passe" + +export TOKEN=$(curl -sk -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=admin" \ + -d "password=$ADMIN_PASSWORD" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" | jq -r '.access_token') +``` + +**Note :** Le token expire après 1 minute par défaut. + +## 3.3 Commandes de base Kubernetes + +### Voir les pods Keycloak +```bash +kubectl get pods -n keycloak +``` + +### Logs Keycloak +```bash +kubectl logs -n keycloak -l app=keycloak -f +``` + +### Redémarrer Keycloak +```bash +kubectl rollout restart deployment/keycloak -n keycloak +``` + +### Scaler les réplicas +```bash +kubectl scale deployment/keycloak -n keycloak --replicas=3 +``` + +## 3.4 Gestion de la base de données + +### Se connecter à PostgreSQL +```bash +kubectl exec -it keycloak-pg-1 -n keycloak -- psql -U keycloak -d keycloak +``` + +### Vérifier l'état du cluster PostgreSQL +```bash +kubectl get cluster -n keycloak +kubectl describe cluster keycloak-pg -n keycloak +``` + +## 3.5 Synchronisation LDAP + +### Via API +```bash +# Obtenir l'ID du composant LDAP +LDAP_ID=$(curl -sk "$KEYCLOAK_URL/admin/realms/uac/components?type=org.keycloak.storage.UserStorageProvider" \ + -H "Authorization: Bearer $TOKEN" | jq -r '.[0].id') + +# Synchronisation complète +curl -sk -X POST "$KEYCLOAK_URL/admin/realms/uac/user-storage/$LDAP_ID/sync?action=triggerFullSync" \ + -H "Authorization: Bearer $TOKEN" + +# Synchronisation des modifications +curl -sk -X POST "$KEYCLOAK_URL/admin/realms/uac/user-storage/$LDAP_ID/sync?action=triggerChangedUsersSync" \ + -H "Authorization: Bearer $TOKEN" +``` + +### Via Console +1. Aller dans le realm concerné +2. User Federation → ldap-xxx +3. Cliquer sur "Synchronize all users" ou "Synchronize changed users" + +## 3.6 Sauvegarde et restauration + +### Exporter un realm +```bash +# Via API +curl -sk "$KEYCLOAK_URL/admin/realms/uac" \ + -H "Authorization: Bearer $TOKEN" > realm-uac-export.json + +# Via pod (export complet avec utilisateurs) +kubectl exec -it deploy/keycloak -n keycloak -- /opt/keycloak/bin/kc.sh export \ + --dir /tmp/export --realm uac +kubectl cp keycloak/keycloak-xxx:/tmp/export ./keycloak-export +``` + +### Importer un realm +```bash +curl -sk -X POST "$KEYCLOAK_URL/admin/realms" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d @realm-uac-export.json +``` + +--- + +# 4. Administration par Realm + +## 4.1 Realm UAC (Université d'Abomey-Calavi) + +### Informations +| Paramètre | Valeur | +|-----------|--------| +| Nom | uac | +| Display Name | Université d'Abomey-Calavi | +| URL | https://auth.rber.bj/realms/uac | +| LDAP | 10.24.112.33:389 | +| Base DN | DC=uac,DC=bj | +| Users DN | CN=Users,DC=uac,DC=bj | +| Bind DN | Administrator@UAC.BJ | + +### Endpoints OIDC +``` +Well-Known: https://auth.rber.bj/realms/uac/.well-known/openid-configuration +Authorization: https://auth.rber.bj/realms/uac/protocol/openid-connect/auth +Token: https://auth.rber.bj/realms/uac/protocol/openid-connect/token +UserInfo: https://auth.rber.bj/realms/uac/protocol/openid-connect/userinfo +``` + +--- + +## 4.2 Realm UNA (Université Nationale d'Agriculture) + +### Informations +| Paramètre | Valeur | +|-----------|--------| +| Nom | una | +| Display Name | Université Nationale d'Agriculture | +| URL | https://auth.rber.bj/realms/una | +| LDAP | 10.20.112.33:389 | +| Base DN | DC=una,DC=bj | +| Users DN | CN=Users,DC=una,DC=bj | +| Bind DN | Administrator@UNA.BJ | + +### Endpoints OIDC +``` +Well-Known: https://auth.rber.bj/realms/una/.well-known/openid-configuration +Authorization: https://auth.rber.bj/realms/una/protocol/openid-connect/auth +Token: https://auth.rber.bj/realms/una/protocol/openid-connect/token +UserInfo: https://auth.rber.bj/realms/una/protocol/openid-connect/userinfo +``` + +--- + +## 4.3 Realm UNSTIM + +### Informations +| Paramètre | Valeur | +|-----------|--------| +| Nom | unstim | +| Display Name | Université des Sciences, Technologies, Ingénierie et Mathématiques | +| URL | https://auth.rber.bj/realms/unstim | +| LDAP | 10.21.112.33:389 | +| Base DN | DC=unstim,DC=bj | +| Users DN | CN=Users,DC=unstim,DC=bj | +| Bind DN | Administrator@UNSTIM.BJ | + +### Endpoints OIDC +``` +Well-Known: https://auth.rber.bj/realms/unstim/.well-known/openid-configuration +Authorization: https://auth.rber.bj/realms/unstim/protocol/openid-connect/auth +Token: https://auth.rber.bj/realms/unstim/protocol/openid-connect/token +UserInfo: https://auth.rber.bj/realms/unstim/protocol/openid-connect/userinfo +``` + +--- + +## 4.4 Realm UP (Université de Parakou) + +### Informations +| Paramètre | Valeur | +|-----------|--------| +| Nom | up | +| Display Name | Université de Parakou | +| URL | https://auth.rber.bj/realms/up | +| LDAP | 10.25.112.33:389 | +| Base DN | DC=univ-parakou,DC=bj | +| Users DN | CN=Users,DC=univ-parakou,DC=bj | +| Bind DN | Administrator@UNIV-PARAKOU.BJ | + +### Endpoints OIDC +``` +Well-Known: https://auth.rber.bj/realms/up/.well-known/openid-configuration +Authorization: https://auth.rber.bj/realms/up/protocol/openid-connect/auth +Token: https://auth.rber.bj/realms/up/protocol/openid-connect/token +UserInfo: https://auth.rber.bj/realms/up/protocol/openid-connect/userinfo +``` + +--- + +# 5. Procédures Opérationnelles + +## 5.1 Créer un client OIDC (exemple Moodle) + +### Via Console +1. Realm concerné → Clients → Create client +2. Client ID: `moodle-unstim` +3. Client Protocol: `openid-connect` +4. Root URL: `https://elearning.unstim.bj` +5. Valid Redirect URIs: `https://elearning.unstim.bj/*` +6. Client authentication: ON +7. Sauvegarder → Onglet Credentials → Copier le secret + +### Via API +```bash +curl -sk -X POST "$KEYCLOAK_URL/admin/realms/unstim/clients" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "clientId": "moodle-unstim", + "enabled": true, + "protocol": "openid-connect", + "rootUrl": "https://elearning.unstim.bj", + "redirectUris": ["https://elearning.unstim.bj/*"], + "publicClient": false, + "clientAuthenticatorType": "client-secret" + }' +``` + +## 5.2 Créer un groupe + +```bash +curl -sk -X POST "$KEYCLOAK_URL/admin/realms/uac/groups" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name": "enseignants"}' +``` + +## 5.3 Créer un rôle + +```bash +curl -sk -X POST "$KEYCLOAK_URL/admin/realms/uac/roles" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name": "professeur", "description": "Rôle enseignant"}' +``` + +## 5.4 Mapper les groupes LDAP + +Dans User Federation → ldap-xxx → Mappers → Add mapper : +- Name: `group-mapper` +- Mapper type: `group-ldap-mapper` +- LDAP Groups DN: `CN=Users,DC=uac,DC=bj` +- Group Object Classes: `group` + +--- + +# 6. Dépannage + +## 6.1 Keycloak ne démarre pas + +### Vérifier les logs +```bash +kubectl logs -n keycloak -l app=keycloak --tail=100 +``` + +### Problèmes courants +| Erreur | Cause | Solution | +|--------|-------|----------| +| Connection refused DB | PostgreSQL non accessible | Vérifier keycloak-pg-rw service | +| health/ready timeout | Démarrage lent | Augmenter initialDelaySeconds | +| OutOfMemory | Mémoire insuffisante | Augmenter limits.memory | + +## 6.2 LDAP ne se connecte pas + +### Tester la connectivité +```bash +kubectl run ldap-test --rm -it --image=alpine --restart=Never -- sh -c " + apk add --no-cache openldap-clients && + ldapsearch -x -H ldap://10.24.112.33:389 -D 'Administrator@UAC.BJ' -W -b 'DC=uac,DC=bj' '(objectClass=person)' dn | head -10 +" +``` + +### Problèmes courants +| Erreur | Cause | Solution | +|--------|-------|----------| +| Can't contact LDAP server | Réseau/Firewall | Vérifier connectivité port 389 | +| Invalid credentials | Mauvais mot de passe | Vérifier bindCredential | +| No such object | Base DN incorrect | Vérifier usersDn | + +## 6.3 Erreur 503 sur HAProxy + +### Vérifier le backend +```bash +echo "show stat" | socat stdio /run/haproxy/admin.sock | grep k8s_ingress +``` + +### Vérifier l'Ingress +```bash +kubectl get ingress -n keycloak +curl -sk -H "Host: auth.rber.bj" https://10.29.113.201/ +``` + +## 6.4 Problème de session / cookies + +Si les utilisateurs sont déconnectés aléatoirement : +1. Vérifier que la persistence de session est activée dans HAProxy +2. Vérifier que les cookies SERVERID sont transmis + +```bash +curl -sk -c cookies.txt -b cookies.txt https://auth.rber.bj/realms/uac/account +cat cookies.txt +``` + +--- + +# Annexes + +## A. Fichiers de configuration + +### keycloak-deployment.yaml +Voir section 2.3.2 + +### haproxy.cfg (extrait Keycloak) +Voir section 2.4 + +## B. Mots de passe (À SÉCURISER) + +**ATTENTION : Ces mots de passe doivent être stockés dans un coffre-fort sécurisé (Vault, etc.)** + +| Service | Compte | Secret | +|---------|--------|--------| +| Keycloak Admin | admin | (dans secret k8s keycloak-admin-secret) | +| PostgreSQL | keycloak | (dans secret k8s keycloak-pg-app) | +| LDAP UAC | Administrator@UAC.BJ | *** | +| LDAP UNA | Administrator@UNA.BJ | *** | +| LDAP UNSTIM | Administrator@UNSTIM.BJ | *** | +| LDAP UP | Administrator@UNIV-PARAKOU.BJ | *** | + +## C. Contacts + +| Rôle | Contact | +|------|---------| +| Admin Infrastructure | À définir | +| Support Keycloak | À définir | + +--- + +**Document généré le 5 janvier 2026** +**Prochaine mise à jour prévue : Après configuration Identity Linking** diff --git a/RBER Connect/keycloak-setup.md b/RBER Connect/keycloak-setup.md new file mode 100644 index 0000000..d06201a --- /dev/null +++ b/RBER Connect/keycloak-setup.md @@ -0,0 +1,610 @@ +# Procédure de configuration Keycloak pour l'API LDAP+ + +| Info | Valeur | +|------|--------| +| **Projet** | API LDAP+ — Universités Béninoises | +| **Référence** | `docs/keycloak-setup.md` | +| **Version** | 1.0 | +| **Date** | Mars 2026 | +| **Auteur** | IDADU TECH | +| **Destinataire** | Administrateur Keycloak (auth.rber.com) | + +--- + +## Sommaire + +1. [Contexte](#1-contexte) +2. [Prérequis](#2-prérequis) +3. [Architecture concernée](#3-architecture-concernée) +4. [Étape 1 — Obtenir un token admin](#4-étape-1--obtenir-un-token-admin) +5. [Étape 2 — Créer le client api-admin dans chaque realm](#5-étape-2--créer-le-client-api-admin-dans-chaque-realm) +6. [Étape 3 — Récupérer les secrets des clients](#6-étape-3--récupérer-les-secrets-des-clients) +7. [Étape 4 — Attribuer les rôles au service account](#7-étape-4--attribuer-les-rôles-au-service-account) +8. [Étape 5 — Injecter les secrets dans Kubernetes](#8-étape-5--injecter-les-secrets-dans-kubernetes) +9. [Étape 6 — Redémarrer les pods](#9-étape-6--redémarrer-les-pods) +10. [Étape 7 — Vérifier le fonctionnement](#10-étape-7--vérifier-le-fonctionnement) +11. [Procédure via l'interface web Keycloak](#11-procédure-via-linterface-web-keycloak) +12. [Rotation des secrets](#12-rotation-des-secrets) +13. [Dépannage](#13-dépannage) +14. [Annexe — Résumé des rôles et accès](#14-annexe--résumé-des-rôles-et-accès) + +--- + +## 1. Contexte + +L'API LDAP+ est un service REST déployé sur le cluster RKE2 de RBER. Elle gère la structure académique (facultés, filières, groupes d'étudiants) des universités béninoises et synchronise ces groupes vers Keycloak. + +L'API a besoin d'un **client Keycloak de type confidentiel** dans chaque realm universitaire pour : + +1. **Valider les tokens JWT** des utilisateurs qui appellent l'API. +2. **Appeler l'Admin API Keycloak** pour créer/modifier des groupes et y affecter des utilisateurs (synchronisation). + +Ce client s'appelle `api-admin`. Il doit être créé dans les 4 realms suivants : + +| Realm | Université | +|-------|-----------| +| `uac` | Université d'Abomey-Calavi | +| `una` | Université Nationale d'Agriculture | +| `univ-parakou` | Université de Parakou | +| `unstim` | UNSTIM | + +--- + +## 2. Prérequis + +| Élément | Détail | +|---------|--------| +| Accès admin Keycloak | Compte avec droits admin sur `https://auth.rber.com` (realm `master` ou droits admin sur les 4 realms) | +| curl | Installé sur la machine depuis laquelle tu exécutes les commandes | +| python3 | Pour parser les réponses JSON (installé par défaut sur la plupart des Linux) | +| kubectl | Accès au cluster RKE2 avec droits sur le namespace `ldap-api` (pour l'injection des secrets) | + +> **Note :** si tu préfères utiliser l'interface web de Keycloak plutôt que les commandes curl, va directement à la [section 11](#11-procédure-via-linterface-web-keycloak). + +--- + +## 3. Architecture concernée + +``` +Applications universitaires + │ + │ JWT (émis par Keycloak) + ▼ +┌──────────────────────────────────┐ +│ API LDAP+ (api.rber.bj) │ +│ │ +│ 1. Valide le JWT │──── Keycloak (auth.rber.com) +│ (clés publiques du realm) │ ← c'est ici qu'on crée le client +│ │ +│ 2. Sync groupes → Keycloak │──── Keycloak Admin API +│ (via client api-admin) │ ← c'est ici qu'on a besoin du secret +└──────────────────────────────────┘ +``` + +--- + +## 4. Étape 1 — Obtenir un token admin + +Ce token permet d'appeler l'Admin API de Keycloak pour créer les clients. + +```bash +# Variables à adapter +KEYCLOAK_URL="https://auth.rber.com" +ADMIN_USER="admin" +ADMIN_PASS="" + +# Obtenir le token +TOKEN=$(curl -s -X POST \ + "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \ + -d "client_id=admin-cli" \ + -d "username=${ADMIN_USER}" \ + -d "password=${ADMIN_PASS}" \ + -d "grant_type=password" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") + +# Vérifier que le token est récupéré +if [ -z "$TOKEN" ]; then + echo "ERREUR : impossible d'obtenir le token admin." + echo "Vérifie le nom d'utilisateur, le mot de passe et l'URL." + exit 1 +fi + +echo "Token admin obtenu (${#TOKEN} caractères)" +``` + +> **Durée de validité :** le token admin expire au bout de 60 secondes par défaut. Si les étapes suivantes échouent avec une erreur 401, relance cette commande pour en obtenir un nouveau. + +--- + +## 5. Étape 2 — Créer le client api-admin dans chaque realm + +Le script suivant crée un client `api-admin` dans les 4 realms. + +```bash +for REALM in uac una univ-parakou unstim; do + echo "" + echo "=== Realm : ${REALM} ===" + + HTTP_CODE=$(curl -s -o /tmp/kc_response_${REALM}.json -w "%{http_code}" \ + -X POST "${KEYCLOAK_URL}/admin/realms/${REALM}/clients" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "clientId": "api-admin", + "name": "API LDAP+ Administration", + "description": "Client utilisé par l'\''API LDAP+ pour valider les JWT et synchroniser les groupes académiques.", + "enabled": true, + "protocol": "openid-connect", + "publicClient": false, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": false, + "directAccessGrantsEnabled": false, + "standardFlowEnabled": false, + "clientAuthenticatorType": "client-secret", + "defaultClientScopes": ["email", "profile", "roles"], + "attributes": { + "use.refresh.tokens": "false" + } + }') + + case "$HTTP_CODE" in + 201) echo " SUCCÈS — client api-admin créé" ;; + 409) echo " EXISTE DÉJÀ — aucune action nécessaire" ;; + 401) echo " ERREUR 401 — token expiré, relance l'étape 1" ;; + *) echo " ERREUR HTTP ${HTTP_CODE} :" + cat /tmp/kc_response_${REALM}.json + echo "" ;; + esac +done +``` + +### Explication des paramètres du client + +| Paramètre | Valeur | Rôle | +|-----------|--------|------| +| `publicClient` | `false` | Client confidentiel : un `client_secret` est généré. Sans ça, pas de secret. | +| `serviceAccountsEnabled` | `true` | Active un compte de service. L'API utilise ce compte pour appeler l'Admin API Keycloak (sync des groupes). | +| `standardFlowEnabled` | `false` | Désactive le flux de login navigateur. L'API ne redirige pas des utilisateurs vers Keycloak, elle valide des tokens déjà émis. | +| `directAccessGrantsEnabled` | `false` | Désactive le Resource Owner Password Grant. L'API ne collecte jamais de mots de passe. | +| `clientAuthenticatorType` | `client-secret` | Méthode d'authentification du client auprès de Keycloak. | + +--- + +## 6. Étape 3 — Récupérer les secrets des clients + +Après la création, chaque client a un `client_secret` généré automatiquement. Ce script le récupère : + +```bash +echo "" +echo "============================================" +echo " SECRETS CLIENT api-admin PAR REALM" +echo "============================================" +echo "" +echo " CONSERVE CES VALEURS EN LIEU SÛR." +echo " NE LES PARTAGE PAS PAR EMAIL OU CHAT." +echo "" +echo "--------------------------------------------" + +for REALM in uac una univ-parakou unstim; do + # Trouver l'ID interne du client (UUID Keycloak, différent du clientId) + CLIENT_UUID=$(curl -s \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=api-admin" \ + -H "Authorization: Bearer ${TOKEN}" \ + | python3 -c "import sys,json; data=json.load(sys.stdin); print(data[0]['id'] if data else 'NOT_FOUND')") + + if [ "$CLIENT_UUID" = "NOT_FOUND" ]; then + echo " ${REALM}: ERREUR — client api-admin introuvable dans ce realm" + continue + fi + + # Récupérer le secret + SECRET=$(curl -s \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" \ + -H "Authorization: Bearer ${TOKEN}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin).get('value', 'ERREUR'))") + + echo " ${REALM}: ${SECRET}" +done + +echo "" +echo "--------------------------------------------" +``` + +**Sortie attendue :** +``` + uac: a1b2c3d4-e5f6-7890-abcd-ef1234567890 + una: b2c3d4e5-f6a7-8901-bcde-f12345678901 + univ-parakou: c3d4e5f6-a7b8-9012-cdef-123456789012 + unstim: d4e5f6a7-b8c9-0123-defa-234567890123 +``` + +> **Note ces valeurs.** Tu en auras besoin pour l'étape 5. + +--- + +## 7. Étape 4 — Attribuer les rôles au service account + +Le compte de service du client `api-admin` a besoin de droits pour gérer les groupes et lire les utilisateurs dans chaque realm. Sans ces rôles, la synchronisation des groupes académiques échouera. + +```bash +for REALM in uac una univ-parakou unstim; do + echo "" + echo "=== Attribution des rôles — realm ${REALM} ===" + + # ID du client api-admin + CLIENT_UUID=$(curl -s \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=api-admin" \ + -H "Authorization: Bearer ${TOKEN}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])") + + # ID du client realm-management (client interne qui porte les rôles d'admin) + REALM_MGMT_UUID=$(curl -s \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=realm-management" \ + -H "Authorization: Bearer ${TOKEN}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])") + + # ID du service account (l'utilisateur technique créé automatiquement) + SA_USER_ID=$(curl -s \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/service-account-user" \ + -H "Authorization: Bearer ${TOKEN}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") + + # Rôles nécessaires : + # manage-users → créer/modifier/supprimer des utilisateurs dans les groupes + # query-users → lister les utilisateurs + # query-groups → lister les groupes + # manage-clients → (optionnel) gérer les scopes si nécessaire + # view-users → voir le détail des utilisateurs + + for ROLE_NAME in manage-users query-groups query-users manage-clients view-users; do + # Récupérer la définition JSON du rôle + ROLE_JSON=$(curl -s \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${REALM_MGMT_UUID}/roles/${ROLE_NAME}" \ + -H "Authorization: Bearer ${TOKEN}") + + # Vérifier que le rôle existe + ROLE_CHECK=$(echo "$ROLE_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('name','ERREUR'))" 2>/dev/null) + + if [ "$ROLE_CHECK" = "ERREUR" ] || [ -z "$ROLE_CHECK" ]; then + echo " ATTENTION : rôle ${ROLE_NAME} introuvable dans realm-management de ${REALM}" + continue + fi + + # Attribuer le rôle au service account + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -X POST "${KEYCLOAK_URL}/admin/realms/${REALM}/users/${SA_USER_ID}/role-mappings/clients/${REALM_MGMT_UUID}" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "[${ROLE_JSON}]") + + if [ "$HTTP_CODE" = "204" ]; then + echo " ${ROLE_NAME} → attribué" + else + echo " ${ROLE_NAME} → ERREUR HTTP ${HTTP_CODE}" + fi + done +done +``` + +### Récapitulatif des rôles attribués + +| Rôle realm-management | Usage par l'API LDAP+ | +|-----------------------|-----------------------| +| `manage-users` | Affecter/retirer des utilisateurs des groupes Keycloak | +| `query-users` | Lister les utilisateurs d'un realm | +| `view-users` | Voir le détail d'un utilisateur | +| `query-groups` | Lister les groupes existants dans un realm | +| `manage-clients` | Optionnel — gestion des scopes si nécessaire | + +--- + +## 8. Étape 5 — Injecter les secrets dans Kubernetes + +Cette étape est exécutée par la personne ayant accès au cluster RKE2. + +```bash +# Remplacer les valeurs ci-dessous par les vrais secrets récupérés à l'étape 3 + +kubectl create secret generic keycloak-secrets \ + --from-literal=UAC_KC_SECRET="" \ + --from-literal=UNA_KC_SECRET="" \ + --from-literal=UP_KC_SECRET="" \ + --from-literal=UNSTIM_KC_SECRET="" \ + -n ldap-api \ + --dry-run=client -o yaml | kubectl apply -f - +``` + +**Vérification :** +```bash +# Le secret doit contenir 4 clés +kubectl get secret keycloak-secrets -n ldap-api -o jsonpath='{.data}' | python3 -c " +import sys,json +data = json.load(sys.stdin) +for key in sorted(data.keys()): + print(f' {key}: (encodé, {len(data[key])} chars)') +" +``` + +--- + +## 9. Étape 6 — Redémarrer les pods + +Les pods doivent redémarrer pour charger les nouveaux secrets depuis les variables d'environnement. + +```bash +# Redémarrer les pods API +kubectl rollout restart deployment/ldap-api -n ldap-api + +# Redémarrer les workers Celery (sync Keycloak) +kubectl rollout restart deployment/celery-worker -n ldap-workers +kubectl rollout restart deployment/celery-beat -n ldap-workers + +# Surveiller le redémarrage +kubectl get pods -n ldap-api -w +kubectl get pods -n ldap-workers -w +``` + +**Résultat attendu :** tous les pods en état `Running` avec `READY 1/1`. + +--- + +## 10. Étape 7 — Vérifier le fonctionnement + +### 10.1 Health check + +```bash +curl -s https://uac.api.rber.bj/health | python3 -m json.tool +``` + +Résultat attendu — tous les composants `healthy` : +```json +{ + "status": "healthy", + "components": { + "database": "healthy", + "redis": "healthy", + "ldap": "healthy", + "keycloak": "healthy" + } +} +``` + +### 10.2 Obtenir un token de test + +```bash +KEYCLOAK_URL="https://auth.rber.com" +REALM="uac" +CLIENT_SECRET="" + +# Token via client credentials (service account) +TOKEN=$(curl -s -X POST \ + "${KEYCLOAK_URL}/realms/${REALM}/protocol/openid-connect/token" \ + -d "client_id=api-admin" \ + -d "client_secret=${CLIENT_SECRET}" \ + -d "grant_type=client_credentials" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") + +echo "Token obtenu (${#TOKEN} caractères)" +``` + +### 10.3 Tester les endpoints protégés + +```bash +# Lister les facultés (doit retourner une liste, même vide) +curl -s https://uac.api.rber.bj/faculties \ + -H "Authorization: Bearer ${TOKEN}" | python3 -m json.tool + +# Lister les utilisateurs LDAP (5 premiers) +curl -s "https://uac.api.rber.bj/users?limit=5" \ + -H "Authorization: Bearer ${TOKEN}" | python3 -m json.tool + +# Lister les groupes +curl -s https://uac.api.rber.bj/groups \ + -H "Authorization: Bearer ${TOKEN}" | python3 -m json.tool + +# Statut de la synchronisation +curl -s https://uac.api.rber.bj/sync/status \ + -H "Authorization: Bearer ${TOKEN}" | python3 -m json.tool +``` + +### 10.4 Tester la synchronisation Keycloak + +```bash +# Déclencher une sync manuelle +curl -s -X POST https://uac.api.rber.bj/sync/keycloak \ + -H "Authorization: Bearer ${TOKEN}" | python3 -m json.tool +``` + +### 10.5 Vérifier dans Keycloak + +Après une sync réussie, connecte-toi à `https://auth.rber.com`, va dans le realm `uac` → Groups. Tu dois voir apparaître l'arborescence : + +``` +/facultes + /fast + /informatique + /L1-2024-2025 + /L2-2024-2025 + ... +``` + +--- + +## 11. Procédure via l'interface web Keycloak + +Si tu préfères utiliser l'interface graphique plutôt que les commandes curl, voici la marche à suivre. **Répète cette procédure pour chaque realm** (uac, una, univ-parakou, unstim). + +### 11.1 Créer le client + +1. Connecte-toi à `https://auth.rber.com/admin` +2. Sélectionne le realm dans le menu déroulant en haut à gauche (ex : `uac`) +3. Menu latéral → **Clients** → bouton **Create client** +4. Remplis les champs : + +| Champ | Valeur | +|-------|--------| +| Client type | OpenID Connect | +| Client ID | `api-admin` | +| Name | `API LDAP+ Administration` | +| Description | `Client utilisé par l'API LDAP+ pour valider les JWT et synchroniser les groupes académiques.` | + +5. Clique **Next** +6. Configuration d'authentification : + +| Champ | Valeur | +|-------|--------| +| Client authentication | **ON** (active le mode confidentiel → génère un secret) | +| Authorization | OFF | +| Standard flow | **OFF** | +| Direct access grants | **OFF** | +| Service accounts roles | **ON** (nécessaire pour la sync) | + +7. Clique **Next** puis **Save** + +### 11.2 Récupérer le secret + +1. Dans la page du client `api-admin`, onglet **Credentials** +2. Le champ **Client secret** contient la valeur à noter +3. **Note cette valeur** — tu en auras besoin pour l'injection Kubernetes + +### 11.3 Attribuer les rôles au service account + +1. Dans la page du client `api-admin`, onglet **Service account roles** +2. Clique **Assign role** +3. Dans le filtre, sélectionne **Filter by clients** +4. Cherche `realm-management` +5. Coche les rôles suivants : + +| Rôle | Coché | +|------|-------| +| `manage-users` | ✅ | +| `query-users` | ✅ | +| `view-users` | ✅ | +| `query-groups` | ✅ | +| `manage-clients` | ✅ | + +6. Clique **Assign** + +### 11.4 Répéter + +Répète les sections 11.1 à 11.3 pour les realms `una`, `univ-parakou` et `unstim`. + +--- + +## 12. Rotation des secrets + +Si un secret est compromis ou si la politique de sécurité exige une rotation périodique : + +```bash +# 1. Régénérer le secret dans Keycloak (exemple pour le realm uac) +REALM="uac" +CLIENT_UUID=$(curl -s \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=api-admin" \ + -H "Authorization: Bearer ${TOKEN}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])") + +# Générer un nouveau secret +NEW_SECRET=$(curl -s -X POST \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" \ + -H "Authorization: Bearer ${TOKEN}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['value'])") + +echo "Nouveau secret pour ${REALM}: ${NEW_SECRET}" + +# 2. Mettre à jour le secret Kubernetes +kubectl create secret generic keycloak-secrets \ + --from-literal=UAC_KC_SECRET="${NEW_SECRET}" \ + --from-literal=UNA_KC_SECRET="" \ + --from-literal=UP_KC_SECRET="" \ + --from-literal=UNSTIM_KC_SECRET="" \ + -n ldap-api \ + --dry-run=client -o yaml | kubectl apply -f - + +# 3. Redémarrer les pods +kubectl rollout restart deployment/ldap-api -n ldap-api +kubectl rollout restart deployment/celery-worker -n ldap-workers +``` + +--- + +## 13. Dépannage + +### Le token admin ne s'obtient pas (étape 1) + +| Symptôme | Cause probable | Solution | +|----------|---------------|----------| +| `{"error":"invalid_grant"}` | Mauvais identifiant ou mot de passe | Vérifie les credentials admin | +| `curl: (7) Failed to connect` | Keycloak inaccessible | Vérifie que `auth.rber.com` est joignable depuis ta machine | +| `{"error":"unauthorized_client"}` | Le client `admin-cli` est désactivé | Active-le dans le realm `master` → Clients → `admin-cli` → Enabled: ON | + +### La création du client échoue (étape 2) + +| Code HTTP | Cause | Solution | +|-----------|-------|----------| +| 401 | Token expiré | Relance l'étape 1 | +| 403 | Pas les droits admin sur le realm | Vérifie que ton compte a le rôle `admin` dans le realm `master` | +| 409 | Le client `api-admin` existe déjà | Passe à l'étape 3 | + +### Le health check retourne `keycloak: unhealthy` (étape 7) + +| Cause probable | Solution | +|---------------|----------| +| Secret incorrect dans Kubernetes | Relance l'étape 3 pour récupérer les vrais secrets, puis étape 5 | +| Pods pas redémarrés | Relance l'étape 6 | +| Keycloak inaccessible depuis le cluster | Vérifier que les pods peuvent atteindre `auth.rber.com:443` | + +### Les endpoints protégés retournent 401 + +| Cause probable | Solution | +|---------------|----------| +| Token expiré | Récupère un nouveau token (étape 7.2) | +| Client `api-admin` désactivé | Vérifie dans Keycloak → Clients → api-admin → Enabled: ON | +| Secret ne correspond pas | Régénère le secret (section 12) | + +### La sync Keycloak échoue + +| Cause probable | Solution | +|---------------|----------| +| Rôles service account manquants | Relance l'étape 4 | +| `403 Forbidden` dans les logs | Le service account n'a pas `manage-users` — voir étape 4 | +| Pas de groupes dans Keycloak après sync | Vérifie qu'il y a des groupes dans PostgreSQL (`GET /groups`) | + +Consulter les logs des pods : +```bash +# Logs API +kubectl logs -l app=ldap-api -n ldap-api --tail=50 + +# Logs Celery Worker (sync) +kubectl logs -l app=celery-worker -n ldap-workers --tail=50 +``` + +--- + +## 14. Annexe — Résumé des rôles et accès + +### Ce que le client api-admin peut faire + +| Action | Realm concerné | Utilisé pour | +|--------|---------------|-------------| +| Valider des tokens JWT | Chaque realm | Authentification des requêtes API | +| Lister les utilisateurs | Chaque realm | Vérification d'existence (sync) | +| Créer/modifier/supprimer des groupes | Chaque realm | Synchronisation de la structure académique | +| Affecter des utilisateurs à des groupes | Chaque realm | Synchronisation des membres | + +### Ce que le client api-admin ne peut PAS faire + +| Action | Raison | +|--------|--------| +| Connecter des utilisateurs (login) | `standardFlowEnabled: false` | +| Collecter des mots de passe | `directAccessGrantsEnabled: false` | +| Modifier la configuration du realm | Pas de rôle `realm-admin` | +| Accéder aux données d'un autre realm | Chaque client est limité à son realm | + +### Variables d'environnement attendues par l'API + +| Variable | Secret Kubernetes | Contenu | +|----------|-------------------|---------| +| `UAC_KC_SECRET` | `keycloak-secrets` | Client secret du realm `uac` | +| `UNA_KC_SECRET` | `keycloak-secrets` | Client secret du realm `una` | +| `UP_KC_SECRET` | `keycloak-secrets` | Client secret du realm `univ-parakou` | +| `UNSTIM_KC_SECRET` | `keycloak-secrets` | Client secret du realm `unstim` |