25 KiB
25 KiB
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
- Architecture Générale
- Document d'Installation
- Document d'Administration Générale
- Administration par Realm
- Procédures Opérationnelles
- 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
keycloakcréé - 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
# 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"
kubectl apply -f keycloak-pg-cluster.yaml
2.2.2 Vérifier le déploiement
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
kubectl create secret generic keycloak-admin-secret -n keycloak \
--from-literal=admin-password='VotreMotDePasseSecurise'
2.3.2 Déployer Keycloak
# 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
kubectl apply -f keycloak-deployment.yaml
2.3.3 Vérifier le déploiement
# 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
haproxy -c -f /etc/haproxy/haproxy.cfg
systemctl reload haproxy
2.5 Création des Realms
2.5.1 Script de création
#!/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
#!/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
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
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
kubectl get pods -n keycloak
Logs Keycloak
kubectl logs -n keycloak -l app=keycloak -f
Redémarrer Keycloak
kubectl rollout restart deployment/keycloak -n keycloak
Scaler les réplicas
kubectl scale deployment/keycloak -n keycloak --replicas=3
3.4 Gestion de la base de données
Se connecter à PostgreSQL
kubectl exec -it keycloak-pg-1 -n keycloak -- psql -U keycloak -d keycloak
Vérifier l'état du cluster PostgreSQL
kubectl get cluster -n keycloak
kubectl describe cluster keycloak-pg -n keycloak
3.5 Synchronisation LDAP
Via API
# 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
- Aller dans le realm concerné
- User Federation → ldap-xxx
- Cliquer sur "Synchronize all users" ou "Synchronize changed users"
3.6 Sauvegarde et restauration
Exporter un realm
# 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
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
- Realm concerné → Clients → Create client
- Client ID:
moodle-unstim - Client Protocol:
openid-connect - Root URL:
https://elearning.unstim.bj - Valid Redirect URIs:
https://elearning.unstim.bj/* - Client authentication: ON
- Sauvegarder → Onglet Credentials → Copier le secret
Via API
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
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
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
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é
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
echo "show stat" | socat stdio /run/haproxy/admin.sock | grep k8s_ingress
Vérifier l'Ingress
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 :
- Vérifier que la persistence de session est activée dans HAProxy
- Vérifier que les cookies SERVERID sont transmis
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