Files
document/RBER Connect/keycloak-iam-documentation.md

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

  1. Architecture Générale
  2. Document d'Installation
  3. Document d'Administration Générale
  4. Administration par Realm
  5. Procédures Opérationnelles
  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

# 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

  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

# 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

  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

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 :

  1. Vérifier que la persistence de session est activée dans HAProxy
  2. 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