Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Décrire un realm Keycloak en code avec Terraform

Par mis à jour le

Améliorer cette page

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.

À 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

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.

La pratique demande Docker, Terraform 1.16.1, jq et openssl.

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.

Cycle de configuration en tant que code : les fichiers versionnés sont lus par terraform plan, qui relit l'état réel du realm formation dans Keycloak ; terraform apply écrit l'écart accepté par l'API ; une modification faite à la main sur le realm crée une dérive que le plan suivant détecte.lectureécart acceptéécriture par l'APIétat réel reludériveFichiers .tfversionnésterraform planterraform applyRealm formationdans KeycloakModificationà la main
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

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èreProvider Terraform keycloak/keycloakkeycloak-config-cli (adorsys)
Dernière version publiée5.10.0, le 2026-10-056.5.1, le 2026-05-22
Dernier commit sur la branche principale2026-10-132026-09-14 (une correction non publiée)
Versions de Keycloak annoncéesCI 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 descriptionHCL, un fichier par préoccupationJSON ou YAML au format de l'export de realm
Comparaison avant écritureterraform planaucun plan documenté, l'outil applique
État conservéun state, qui contient les secretsaucun 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.

Démarrage de Keycloak 26.7.0
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-dev
until curl -sf http://localhost:18180/realms/master >/dev/null; do sleep 3; done

L'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.

versions.tf
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!.

Fenêtre de terminal
export KEYCLOAK_URL=http://localhost:18180
export KEYCLOAK_CLIENT_ID=admin-cli
export KEYCLOAK_USER=admin
export KEYCLOAK_PASSWORD=admin
terraform init

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.

main.tf
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.

variables.tf
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.

Fenêtre de terminal
( 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é.

Fenêtre de terminal
terraform plan -var realm_name=master

La 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.

Fenêtre de terminal
terraform plan -out=tfplan
terraform apply tfplan

Le 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.

Fenêtre de terminal
kcadm() { docker exec cac-keycloak /opt/keycloak/bin/kcadm.sh "$@"; }
kcadm config credentials --server http://localhost:8080 --realm master --user admin --password admin
kcadm get realms/formation --fields realm,displayName,accessTokenLifespan
kcadm get roles -r formation --fields name
kcadm get clients -r formation -q clientId=boutique --fields clientId,redirectUris,publicClient

Le 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.

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.

Fenêtre de terminal
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=86400
kcadm create roles -r formation -s name=intrus
terraform 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.

Fenêtre de terminal
terraform apply -auto-approve
kcadm get realms/formation --fields accessTokenLifespan
kcadm get clients/$BID -r formation --fields redirectUris
terraform 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).

Fenêtre de terminal
kcadm create realms/formation/partial-export \
-q exportClients=true -q exportGroupsAndRoles=true -o > export-formation.json
jq -r '.roles.realm | map(.name) | join(",")' export-formation.json
jq -r '.clients[] | select(.clientId=="boutique") | .secret' export-formation.json

Le 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.tf
import {
provider = keycloak
to = keycloak_realm.formation
id = "formation"
}
import {
provider = keycloak
to = keycloak_openid_client.boutique
id = "formation/<id-interne-du-client>"
}
Fenêtre de terminal
terraform plan -generate-config-out=generated.tf

Terraform é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.

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-cli avec le compte administrateur.
  • Un backend distant chiffré pour le state, et un pipeline qui lance le plan pé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'apply qui l'inscrirait dans le state.
  • Keycloak 26.8.1, dernière version publiée, sur laquelle la leçon n'est pas rejouée.

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.

Fenêtre de terminal
docker rm -f cac-keycloak
rm -f terraform.tfstate terraform.tfstate.backup tfplan ../boutique.secret
rm -rf reprise export-formation.json
unset TF_VAR_boutique_secret KEYCLOAK_PASSWORD

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ômeCauseSolution
No value for required variableTF_VAR_boutique_secret n'est pas exporté dans ce terminalexporter la variable depuis le fichier de secret, puis relancer
Invalid value for variable ... masterla valeur master a été passée à realm_namec'est le garde-fou : corriger la valeur
failed to read schema ... registry.terraform.io/hashicorp/keycloakbloc import sans provider = keycloak pendant la générationajouter 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 configurationprovider = keycloak laissé après la générationretirer la ligne dans import.tf
Le plan ne voit pas un rôle ajouté à la mainl'objet n'est pas déclaré dans les fichiersle décrire (puis l'importer) ou le supprimer
Le JSON de partial-export ne contient pas les clientsoptions passées avec -sutiliser -q exportClients=true -q exportGroupsAndRoles=true
Le plan propose de changer le secret à chaque exécutionla 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

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

6 questions
6 min.
70% requis

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

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn