Décrire un realm en code, c'est écrire ses clients, ses rôles et ses groupes dans des fichiers versionnés, puis laisser un outil comparer cette description à l'état réel de Keycloak et corriger l'écart. Cette leçon retient Terraform et son provider keycloak/keycloak 5.10.0, le greffon qui lui apprend à piloter l'API d'administration de Keycloak. Elle crée un realm formation sur un Keycloak 26.7.0 neuf, introduit à la main une dérive (un écart entre les fichiers et Keycloak), la détecte dans le plan puis la corrige. Elle montre aussi comment garder le secret d'un client hors des fichiers, refuser d'appliquer sur master et partir d'un realm déjà réglé. Public : administrateur qui a déjà configuré un realm dans la console et qui connaît les bases de Terraform.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »À la fin de cette leçon, vous saurez :
- Choisir un outil de configuration en tant que code sur des critères vérifiés à la source
- Décrire un realm, un client, des rôles et un groupe dans des fichiers Terraform
- Appliquer cette description sur un Keycloak neuf et lire le plan avant d'écrire
- Détecter une modification faite à la main et la corriger en réappliquant
- Protéger le secret du client et refuser de toucher au realm
master - Démarrer depuis un realm existant avec un export et un bloc
import
Prérequis
Section intitulée « Prérequis »Cette leçon suppose que vous savez déjà créer un realm, un client et un rôle ; elle ne réexplique ni la console ni les flux OIDC.
- Installer Keycloak avec Docker pour l'image épinglée par digest ;
kcadm.sh, l'outil d'administration en ligne de commande livré dans cette image, sert ici à vérifier le résultat et à simuler une modification à la main - Administrer Keycloak : realms, users, roles pour les objets que l'on va décrire
- Installer Terraform et le workflow Terraform pour
init,planetapply - Plan, état et réconciliation pour comprendre ce que le state mémorise
La pratique demande Docker, Terraform 1.16.1, jq et openssl.
Pourquoi décrire un realm dans des fichiers ?
Section intitulée « Pourquoi décrire un realm dans des fichiers ? »Un realm réglé dans la console n'a ni historique, ni relecture, ni moyen d'être rejoué : décrit dans des fichiers, il se versionne, se relit en revue et se recrée à l'identique. Trois situations le justifient : refaire le même realm sur un second environnement, savoir qui a changé un redirect_uri et quand, et repérer un réglage modifié à la main sans que personne ne l'ait dit. Les mécanismes d'import natifs ne couvrent que le premier cas : la leçon Déployer Keycloak sur Kubernetes montre que KeycloakRealmImport crée un realm, mais ignore en silence un manifeste dont le realm existe déjà.
Terraform, retenu dans cette leçon, ajoute donc une boucle de réconciliation. Le schéma en suit les étapes : les fichiers sont lus, terraform plan les compare à l'état réel que le provider relit par l'API d'administration de Keycloak, terraform apply n'écrit que l'écart accepté, et une modification à la main (en rouge) crée un écart que le plan suivant fait apparaître. Terraform tient aussi un state, un fichier où il mémorise les objets qu'il a créés et leurs identifiants.
La dérive est l'écart entre ce que décrivent les fichiers et ce que Keycloak contient vraiment. Le traitement organisationnel (cadence de détection, décider entre réintégrer, écraser ou exclure) est expliqué dans Détecter et traiter la dérive d'une infrastructure ; cette leçon joue le geste côté Keycloak.
Terraform ou keycloak-config-cli : quel outil choisir ?
Section intitulée « Terraform ou keycloak-config-cli : quel outil choisir ? »Pour cette leçon, le provider Terraform keycloak/keycloak 5.10.0 l'emporte : il est publié le plus récemment, déclare tester Keycloak 26.7 et 26.8 dans son intégration continue, et offre un plan qui rend la dérive visible avant toute écriture. Les deux candidats sont maintenus et sous licence Apache-2.0. Les faits ci-dessous ont été relevés le 2026-10-15 sur les dépôts GitHub et leurs notes de version.
| Critère | Provider Terraform keycloak/keycloak | keycloak-config-cli (adorsys) |
|---|---|---|
| Dernière version publiée | 5.10.0, le 2026-10-05 | 6.5.1, le 2026-05-22 |
| Dernier commit sur la branche principale | 2026-10-13 | 2026-09-14 (une correction non publiée) |
| Versions de Keycloak annoncées | CI sur 26.8.0, 26.7.5, 26.6.4, 26.5.7 et les mineures 26.0 à 26.4 | « les 4 dernières versions » ; les notes 6.5.1 ajoutent des profils jusqu'à 26.5.7 |
| Format de la description | HCL, un fichier par préoccupation | JSON ou YAML au format de l'export de realm |
| Comparaison avant écriture | terraform plan | aucun plan documenté, l'outil applique |
| État conservé | un state, qui contient les secrets | aucun state |
Le README du provider précise qu'il supporte officiellement les trois dernières versions mineures de Keycloak. Le dépôt keycloak/terraform-provider-keycloak appartient à l'organisation Keycloak, ce qui assure sa continuité. keycloak-config-cli reste un bon choix si votre équipe n'utilise pas Terraform : il applique un export JSON presque tel quel. Son changelog consulté ne mentionne des profils que jusqu'à Keycloak 26.5.7, et ni son README ni ses fonctionnalités documentées ne décrivent de mode plan ou « dry-run » ; cela ne prouve pas qu'il échoue sur une version plus récente. Les versions de Keycloak sont à revérifier avant chaque montée.
Le choix a un coût à connaître : le state de Terraform contient les secrets de clients, point traité avec le secret du client. Il faut aussi accepter de ne gérer que ce qu'on déclare, point mesuré à l'étape de détection de la dérive.
Comment préparer le dossier et le Keycloak neuf ?
Section intitulée « Comment préparer le dossier et le Keycloak neuf ? »Le dossier de travail contient trois fichiers : versions.tf épingle Terraform et le provider, variables.tf déclare les entrées, main.tf décrit le realm. Créez-le (realm-formation/ par exemple) et lancez-y toutes les commandes de la leçon. Terraform est épinglé à = 1.16.1 et le provider à = 5.10.0 : une description de realm n'est reproductible que si l'outil qui la lit ne bouge pas sous vos pieds. Le fichier de verrouillage .terraform.lock.hcl, créé par terraform init, se versionne avec les .tf.
docker run -d --name cac-keycloak -p 127.0.0.1:18180:8080 \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \ -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \ quay.io/keycloak/keycloak:26.7.0@sha256:0f198be292568439d700cdbfb893e69a6009bb43a94a06a945b1d3d506c76b13 \ start-devuntil curl -sf http://localhost:18180/realms/master >/dev/null; do sleep 3; doneL'image est celle de la leçon d'installation, le port est lié à 127.0.0.1 pour ne pas exposer le lab, et le mot de passe admin est celui d'un laboratoire jetable. Le fichier versions.tf épingle les versions et laisse le provider lire sa connexion dans l'environnement.
terraform { required_version = "= 1.16.1"
required_providers { keycloak = { source = "keycloak/keycloak" version = "= 5.10.0" } }}
provider "keycloak" { # KEYCLOAK_URL, KEYCLOAK_CLIENT_ID, KEYCLOAK_USER, KEYCLOAK_PASSWORD # sont lus dans l'environnement : aucun identifiant dans les fichiers.}Le provider s'authentifie auprès du realm master avec le client admin-cli et le compte d'administration. Exportez les quatre variables, puis lancez terraform init, qui doit se terminer par Terraform has been successfully initialized!.
export KEYCLOAK_URL=http://localhost:18180export KEYCLOAK_CLIENT_ID=admin-cliexport KEYCLOAK_USER=adminexport KEYCLOAK_PASSWORD=adminterraform initComment décrire le realm formation ?
Section intitulée « Comment décrire le realm formation ? »Chaque objet Keycloak devient une ressource Terraform : keycloak_realm pour le realm, keycloak_role pour un rôle, keycloak_group pour un groupe et keycloak_openid_client pour un client OIDC. Les ressources se référencent entre elles (keycloak_realm.formation.id), ce qui donne à Terraform l'ordre de création : le realm d'abord, puis tout ce qui en dépend. Le fichier ci-dessous décrit un realm formation avec un jeton d'accès à 10 minutes (dans la fourchette de 5 à 15 minutes retenue par la formation), deux rôles, un groupe qui porte le rôle lecteur, et un client confidentiel boutique.
resource "keycloak_realm" "formation" { realm = var.realm_name enabled = true display_name = "Formation IAM" access_token_lifespan = "10m" ssl_required = "external" login_with_email_allowed = true}
resource "keycloak_role" "lecteur" { realm_id = keycloak_realm.formation.id name = "lecteur" description = "Consulte le catalogue"}
resource "keycloak_role" "editeur" { realm_id = keycloak_realm.formation.id name = "editeur" description = "Modifie le catalogue"}
resource "keycloak_group" "equipe_boutique" { realm_id = keycloak_realm.formation.id name = "equipe-boutique"}
resource "keycloak_group_roles" "equipe_boutique" { realm_id = keycloak_realm.formation.id group_id = keycloak_group.equipe_boutique.id role_ids = [keycloak_role.lecteur.id]}
resource "keycloak_openid_client" "boutique" { realm_id = keycloak_realm.formation.id client_id = "boutique" name = "Boutique" access_type = "CONFIDENTIAL" client_secret = var.boutique_secret standard_flow_enabled = true direct_access_grants_enabled = false valid_redirect_uris = ["https://boutique.exemple.fr/callback"]}Seuls les arguments utiles sont écrits : tout ce qui est omis garde sa valeur par défaut de Keycloak. Le nom du realm et le secret du client viennent de deux variables, var.realm_name et var.boutique_secret, que variables.tf déclare dans les deux sections suivantes : la première sert au garde-fou contre master, la seconde garde le secret hors des fichiers. Si une ressource n'est pas décrite ici, Terraform ne la gère pas : c'est le point qui explique le comportement observé plus bas avec le rôle ajouté à la main.
Comment garder le secret du client hors des fichiers ?
Section intitulée « Comment garder le secret du client hors des fichiers ? »Le secret du client arrive par une variable sensible alimentée depuis l'environnement, jamais écrite dans un .tf, un .tfvars versionné ou un dépôt. La variable boutique_secret est déclarée sans valeur par défaut et marquée sensitive : Terraform masque sa valeur dans les plans, et refuse de travailler si elle manque.
variable "realm_name" { description = "Nom du realm géré par ce dossier" type = string default = "formation"
validation { condition = var.realm_name != "master" error_message = "Refus : ce dossier ne doit jamais gérer le realm master." }}
variable "boutique_secret" { description = "Secret du client boutique, fourni par TF_VAR_boutique_secret" type = string sensitive = true}Pour le lab, générez un secret aléatoire dans un fichier de permissions 600 rangé hors du dépôt, puis exportez-le dans TF_VAR_boutique_secret. En production, la source est un coffre : voir la gestion des secrets en infrastructure as code.
( umask 077; openssl rand -hex 16 | tr -d '\n' > ../boutique.secret )export TF_VAR_boutique_secret="$(cat ../boutique.secret)"Sans la variable, terraform plan s'arrête avec Error: No value for required variable : le refus est le comportement voulu. Trois vérifications faites sur ce lab : grep de la valeur dans main.tf, variables.tf et versions.tf rend 0 ; changer la valeur de TF_VAR_boutique_secret fait apparaître client_secret = (sensitive value) dans le plan, et l'apply suivant met à jour Keycloak (kcadm.sh get clients/<id>/client-secret renvoie la nouvelle valeur) ; mais le state terraform.tfstate contient la valeur en clair (grep -c rend 1).
Comment empêcher d'appliquer sur le realm master ?
Section intitulée « Comment empêcher d'appliquer sur le realm master ? »Le realm master administre Keycloak lui-même : le dossier doit refuser de le gérer, et la validation de la variable realm_name le fait avant tout appel à l'API. Le bloc validation de variables.tf rejette la valeur master à la lecture de la configuration, donc aucun plan n'est même calculé.
terraform plan -var realm_name=masterLa sortie est Error: Invalid value for variable, suivie de var.realm_name is "master" et de Refus : ce dossier ne doit jamais gérer le realm master. ; Terraform annonce Planning failed et ne calcule aucun plan. Ce garde-fou est réel mais limité : il protège contre une erreur de saisie de la variable, pas contre quelqu'un qui écrirait realm = "master" en dur dans main.tf. Le garde-fou complet est de ne pas donner à Terraform un compte capable d'écrire dans master, par exemple un client de service dont les rôles se limitent au realm géré. Ce compte restreint n'est pas rejoué dans cette leçon : le lab utilise l'administrateur initial, à ne pas reproduire en production.
Comment appliquer la description et lire le plan ?
Section intitulée « Comment appliquer la description et lire le plan ? »terraform plan calcule la différence entre les fichiers et Keycloak, terraform apply l'écrit : lisez le plan avant d'appliquer, c'est lui qui montre ce qui va changer. Sur un Keycloak neuf, le plan crée tout. Enregistrez-le dans un fichier pour appliquer exactement ce que vous avez relu.
terraform plan -out=tfplanterraform apply tfplanLe plan se termine par Plan: 6 to add, 0 to change, 0 to destroy. : le realm, deux rôles, le groupe, l'attribution de rôle et le client. L'apply répond Apply complete! Resources: 6 added, 0 changed, 0 destroyed. Le fichier tfplan contient les valeurs sensibles : supprimez-le après usage.
Vérifiez ensuite côté Keycloak, avec un outil indépendant de Terraform.
kcadm() { docker exec cac-keycloak /opt/keycloak/bin/kcadm.sh "$@"; }kcadm config credentials --server http://localhost:8080 --realm master --user admin --password adminkcadm get realms/formation --fields realm,displayName,accessTokenLifespankcadm get roles -r formation --fields namekcadm get clients -r formation -q clientId=boutique --fields clientId,redirectUris,publicClientLe realm répond "displayName" : "Formation IAM" et "accessTokenLifespan" : 600, la liste de rôles contient lecteur et editeur, et le client boutique porte l'unique redirect URI décrit et "publicClient" : false. Un second terraform plan -detailed-exitcode annonce no differences et rend le code de sortie 0 : l'état réel correspond à la description. Cette option rend un code de sortie exploitable par un script : 0 s'il n'y a rien à changer, 2 s'il y a des changements, 1 en cas d'erreur.
Comment détecter et corriger une dérive ?
Section intitulée « Comment détecter et corriger une dérive ? »Une modification faite hors de Terraform apparaît dans le plan suivant, avec le code de sortie 2 de terraform plan -detailed-exitcode ; la réappliquer ramène Keycloak à la description. Simulons un administrateur pressé avec kcadm : il passe les redirect URIs du client à * (le piège décrit dans Dépanner une intégration OIDC et SAML), allonge le jeton d'accès à 24 heures et crée un rôle intrus.
BID=$(kcadm get clients -r formation -q clientId=boutique --fields id --format csv --noquotes)kcadm update clients/$BID -r formation -s 'redirectUris=["*"]'kcadm update realms/formation -s accessTokenLifespan=86400kcadm create roles -r formation -s name=intrusterraform plan -detailed-exitcode; echo "code=$?"Le plan affiche Plan: 0 to add, 2 to change, 0 to destroy. et le code 2. Pour le client, valid_redirect_uris passe de "*" à "https://boutique.exemple.fr/callback" ; pour le realm, access_token_lifespan passe de "24h0m0s" à "10m". C'est la même panne que dans la leçon de dépannage : deux valeurs, celle de la description et celle de la réalité, et l'écart entre les deux.
Deux enseignements sortent de cette mesure. D'abord, le rôle intrus n'apparaît nulle part : Terraform ne gère que les objets déclarés, et un objet ajouté à la main reste invisible tant qu'on ne le décrit pas. Ensuite, terraform plan -refresh-only compare le state à la réalité, sans tenir compte des fichiers, et présente la même dérive sous l'angle « les objets ont changé hors de Terraform » (Objects have changed outside of Terraform), avec un léger bruit : des attributs vides (attributes = {}, extra_config = {}) apparaissent sur le groupe et le client. Pour réintégrer une valeur plutôt que l'écraser, voir Diagnostiquer le state Terraform.
Pour corriger, il suffit de réappliquer. Le plan vient d'être lu à l'étape précédente et le lab jetable ne risque rien, d'où l'option -auto-approve, qui évite la confirmation interactive.
terraform apply -auto-approvekcadm get realms/formation --fields accessTokenLifespankcadm get clients/$BID -r formation --fields redirectUristerraform plan -detailed-exitcode; echo "code=$?"L'apply annonce Resources: 0 added, 2 changed, 0 destroyed., le jeton revient à 600, la redirect URI à l'adresse décrite, et le dernier plan rend 0. Le rôle intrus est toujours présent : le supprimer à la main, ou le décrire puis l'ôter du code, relève d'une décision, pas d'une correction automatique. En pipeline, appliquez plutôt un plan enregistré et relu (terraform apply tfplan) qu'un -auto-approve en aveugle.
Comment démarrer depuis un realm déjà réglé à la main ?
Section intitulée « Comment démarrer depuis un realm déjà réglé à la main ? »Deux gestes se combinent : partial-export donne l'inventaire lisible du realm, et un bloc import avec -generate-config-out fabrique le code Terraform qui le décrit. Ne partez jamais d'une page blanche sur un realm qui vit déjà : vous oublieriez des réglages que vos utilisateurs utilisent. L'export se fait avec kcadm, en passant les options en paramètres de requête (-q).
kcadm create realms/formation/partial-export \ -q exportClients=true -q exportGroupsAndRoles=true -o > export-formation.jsonjq -r '.roles.realm | map(.name) | join(",")' export-formation.jsonjq -r '.clients[] | select(.clientId=="boutique") | .secret' export-formation.jsonLe premier jq liste les rôles du realm (dont intrus), le second renvoie ********** : l'export masque les secrets, il faudra donc les fournir autrement. Piège observé : avec -s exportClients=true au lieu de -q, le JSON obtenu ne contient ni rôles, ni groupes, ni clients.
L'export documente, mais ne s'applique pas à Terraform. Pour obtenir du code, il faut un bloc import, qui dit à Terraform « cet objet Keycloak existant correspond à cette ressource ». Créez un second dossier (reprise/ par exemple) qui ne contient que des copies de versions.tf et du fichier de verrouillage, lancez-y terraform init, puis écrivez-y un fichier import.tf qui nomme les objets existants. Le méta-argument provider = keycloak est nécessaire pendant la génération dans un dossier sans autre ressource (observé ici), faute de quoi Terraform cherche le provider hashicorp/keycloak, introuvable. Pour le client, l'id est formation/ suivi de l'identifiant interne du client, celui que renvoie kcadm get clients -r formation -q clientId=boutique --fields id (la valeur de $BID à l'étape précédente).
import { provider = keycloak to = keycloak_realm.formation id = "formation"}
import { provider = keycloak to = keycloak_openid_client.boutique id = "formation/<id-interne-du-client>"}terraform plan -generate-config-out=generated.tfTerraform écrit un generated.tf de 145 lignes pour ces deux objets, avec des dizaines d'attributs par défaut, et client_secret = null # sensitive : le secret n'est pas récupéré. La documentation HashiCorp présente cette génération comme expérimentale (le format produit peut changer d'une version à l'autre). Relisez ce fichier, gardez les attributs que vous voulez maîtriser, puis retirez la ligne provider = keycloak : Terraform refuse sinon (The provider argument can only be specified in import blocks that will generate configuration). Le plan suivant annonce Plan: 2 to import, 0 to add, 0 to change, 0 to destroy. : ce plan ne modifie rien dans Keycloak, et l'apply qui suivrait inscrirait les deux objets dans le state. Comme client_secret est resté à null, rebranchez-le sur la variable sensible de variables.tf, comme dans main.tf. La reprise générale d'un existant est détaillée dans Reprendre une infrastructure existante.
Qu'est-ce qui n'a pas été rejoué ?
Section intitulée « Qu'est-ce qui n'a pas été rejoué ? »Cette leçon a été jouée sur un Keycloak 26.7.0 jetable avec Terraform 1.16.1 et le provider 5.10.0 ; plusieurs points restent à vérifier sur votre plateforme. Ne les tenez pas pour acquis.
- keycloak-config-cli n'a pas été exécuté ; sa fiche vient du dépôt et de son changelog.
- OpenTofu n'a pas été rejoué avec ce provider.
- Un compte de service restreint au realm géré, en remplacement de
admin-cliavec le compte administrateur. - Un backend distant chiffré pour le state, et un pipeline qui lance le
planpériodiquement. - La reprise sur un realm de production complexe (flux d'authentification, fournisseurs d'identité, mappers) : seul le plan d'import du realm et du client a été joué, pas l'
applyqui l'inscrirait dans le state. - Keycloak 26.8.1, dernière version publiée, sur laquelle la leçon n'est pas rejouée.
Comment nettoyer le lab ?
Section intitulée « Comment nettoyer le lab ? »Supprimez le conteneur, qui efface le realm, puis les fichiers qui contiennent un secret : le state, le plan enregistré et le fichier de secret. Le state Terraform contient le secret du client en clair.
docker rm -f cac-keycloakrm -f terraform.tfstate terraform.tfstate.backup tfplan ../boutique.secretrm -rf reprise export-formation.jsonunset TF_VAR_boutique_secret KEYCLOAK_PASSWORDDépannage
Section intitulée « Dépannage »Quand une commande échoue, lisez d'abord le message de Terraform, puis vérifiez l'adresse, le compte et les versions. Les cas ci-dessous ont été rencontrés en préparant la leçon.
| Symptôme | Cause | Solution |
|---|---|---|
No value for required variable | TF_VAR_boutique_secret n'est pas exporté dans ce terminal | exporter la variable depuis le fichier de secret, puis relancer |
Invalid value for variable ... master | la valeur master a été passée à realm_name | c'est le garde-fou : corriger la valeur |
failed to read schema ... registry.terraform.io/hashicorp/keycloak | bloc import sans provider = keycloak pendant la génération | ajouter provider = keycloak dans chaque bloc import le temps de la génération |
The provider argument can only be specified in import blocks that will generate configuration | provider = keycloak laissé après la génération | retirer la ligne dans import.tf |
| Le plan ne voit pas un rôle ajouté à la main | l'objet n'est pas déclaré dans les fichiers | le décrire (puis l'importer) ou le supprimer |
Le JSON de partial-export ne contient pas les clients | options passées avec -s | utiliser -q exportClients=true -q exportGroupsAndRoles=true |
| Le plan propose de changer le secret à chaque exécution | la valeur de TF_VAR_boutique_secret change d'un terminal à l'autre (secret régénéré) | générer le secret une seule fois dans un fichier |
À retenir
Section intitulée « À retenir »Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Six questions tirées parmi huit vérifient que vous savez décrire un realm en code : choisir l'outil, lire un plan, repérer une dérive et ce qu'elle laisse passer, et garder le secret hors des fichiers.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Épreuve niveau 2 : exploiter et gouverner un fournisseur d'identité
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
Références
Section intitulée « Références »- Provider Terraform Keycloak, dépôt et README (versions de Keycloak testées)
- Provider Terraform Keycloak, registre (ressources et arguments)
- keycloak-config-cli, dépôt et changelog
- Keycloak, Server Administration Guide (export partiel d'un realm)
- Terraform, bloc import et génération de configuration