From 8b0eaaec0a61ff7bfe434e6156712d7404dd9dd3 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sat, 8 Aug 2026 18:06:16 -0400 Subject: [PATCH] =?UTF-8?q?keycloak=20:=20le=20claim=20de=20groupes=20exig?= =?UTF-8?q?e=20les=20clients=20=E2=80=94=20troisieme=20defaut=20d'ordre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Meme motif que les roles de realm, une etape plus loin. groupes-ldap.yml posait aussi un oidc-group-membership-mapper sur les CLIENTS, alors que clients-oidc.yml s'execute apres. Invisible tant que les clients existaient d'un passage precedent. Extrait dans claim-groupes.yml, place APRES clients-oidc. J'avais d'abord insere l'appel AVANT — le defaut meme que je corrigeais ; rattrape avant tout deploiement. La lecon vaut au-dela du role : un fichier de taches nomme d'apres un SUJET (« les groupes ») rassemble des etapes aux dependances differentes, et l'ordre qui en resulte n'est correct que par accident. Ce qui doit gouverner le decoupage, c'est ce dont chaque etape a BESOIN, pas ce dont elle parle. Co-Authored-By: Claude Opus 5 --- .../serveur_keycloak/tasks/claim-groupes.yml | 62 +++++++++++++++++++ roles/serveur_keycloak/tasks/groupes-ldap.yml | 48 -------------- roles/serveur_keycloak/tasks/main.yml | 4 ++ 3 files changed, 66 insertions(+), 48 deletions(-) create mode 100644 roles/serveur_keycloak/tasks/claim-groupes.yml diff --git a/roles/serveur_keycloak/tasks/claim-groupes.yml b/roles/serveur_keycloak/tasks/claim-groupes.yml new file mode 100644 index 0000000..fced9a3 --- /dev/null +++ b/roles/serveur_keycloak/tasks/claim-groupes.yml @@ -0,0 +1,62 @@ +--- +# Emission du claim `groups` dans le jeton des CLIENTS — extrait de `groupes-ldap.yml` +# le 2026-08-08, pour la meme raison que `roles-realm.yml` une heure plus tot. +# +# Ce mapper se pose sur des clients OIDC, donc il exige que `clients-oidc.yml` soit +# passe. Il vivait pourtant au milieu de la projection des groupes, execute AVANT. +# Invisible tant que les clients existaient d'un passage precedent ; sur un realm neuf, +# la tache cherche des clients qui n'ont pas encore ete crees. +# +# Trouve par la reconstruction from-zero, une etape apres le defaut des roles de realm. +# La lecon est la meme et vaut d'etre retenue : un fichier de taches nomme d'apres un +# SUJET (« les groupes ») rassemble des etapes aux dependances differentes, et l'ordre +# qui en resulte n'est correct que par accident. Ce qui doit gouverner le decoupage, +# c'est ce dont chaque etape a BESOIN — pas ce dont elle parle. +- name: Émettre le claim de groupes dans le jeton des clients + ansible.builtin.shell: + executable: /bin/bash + cmd: | + set -euo pipefail + KC={{ serveur_keycloak_home }}/bin/kcadm.sh + "$KC" config credentials --server http://localhost:8080 --realm master \ + --user {{ serveur_keycloak_admin_user }} --password "$KC_ADMIN_PW" >/dev/null + CID=$("$KC" get clients -r {{ serveur_keycloak_realm }} -q clientId={{ item }} 2>/dev/null \ + | grep -oP '"id"\s*:\s*"\K[^"]+' | head -1) + if [ -z "$CID" ]; then echo "SETOPS_OK client absent"; exit 0; fi + if "$KC" get clients/$CID/protocol-mappers/models -r {{ serveur_keycloak_realm }} 2>/dev/null \ + | grep -q '"groupes-membres"'; then + echo SETOPS_OK + else + # `full.path=false` : le claim porte `sysadmin`, pas `/sysadmin`. Les + # services comparent un nom de groupe, pas un chemin. + "$KC" create clients/$CID/protocol-mappers/models -r {{ serveur_keycloak_realm }} \ + -s name=groupes-membres -s protocol=openid-connect \ + -s protocolMapper=oidc-group-membership-mapper \ + -s 'config."claim.name"={{ serveur_keycloak_groupes_claim }}' \ + -s 'config."full.path"=false' \ + -s 'config."id.token.claim"=true' \ + -s 'config."access.token.claim"=true' \ + -s 'config."userinfo.token.claim"=true' >/dev/null + echo SETOPS_CHANGED + fi + environment: + KC_ADMIN_PW: "{{ serveur_keycloak_admin_password }}" + loop: "{{ serveur_keycloak_groupes_mapper_clients }}" + register: serveur_keycloak_grp_mapper + changed_when: "'SETOPS_CHANGED' in serveur_keycloak_grp_mapper.stdout" + no_log: true + when: + - serveur_keycloak_groupes_mapper_clients | length > 0 + - not ansible_check_mode + +# --- Rendre le changement de mot de passe EXPLICITE --------------------------- +# OpenLDAP marque `pwdReset` et refuse les operations au niveau du PROTOCOLE ; +# Keycloak a son propre systeme d'actions requises et NE LIT PAS `pwdReset`. Les +# deux mecanismes ne se parlent pas : l'utilisateur entre avec son jeton, atterrit +# dans la console de compte, et rien ne lui dit pourquoi ni quoi faire. Constate le +# 2026-08-07 par l'operateur : « ce n'est pas du tout explicite ». +# +# On aligne donc Keycloak sur l'annuaire : tant que `pwdReset` est vrai, l'action +# `UPDATE_PASSWORD` est posee et Keycloak affiche un ecran qui l'explique. Elle +# disparait d'elle-meme quand le mot de passe change — l'annuaire retire `pwdReset`, +# et la condition cesse d'etre vraie. diff --git a/roles/serveur_keycloak/tasks/groupes-ldap.yml b/roles/serveur_keycloak/tasks/groupes-ldap.yml index 7be11bd..fe780ad 100644 --- a/roles/serveur_keycloak/tasks/groupes-ldap.yml +++ b/roles/serveur_keycloak/tasks/groupes-ldap.yml @@ -144,54 +144,6 @@ # Le mapper de PROTOCOLE : sans lui, les groupes existent dans le realm mais # n'apparaissent JAMAIS dans le jeton — Forgejo et Nextcloud lisent un claim vide # et n'accordent rien. C'est le dernier maillon de la chaine. -- name: Émettre le claim de groupes dans le jeton des clients - ansible.builtin.shell: - executable: /bin/bash - cmd: | - set -euo pipefail - KC={{ serveur_keycloak_home }}/bin/kcadm.sh - "$KC" config credentials --server http://localhost:8080 --realm master \ - --user {{ serveur_keycloak_admin_user }} --password "$KC_ADMIN_PW" >/dev/null - CID=$("$KC" get clients -r {{ serveur_keycloak_realm }} -q clientId={{ item }} 2>/dev/null \ - | grep -oP '"id"\s*:\s*"\K[^"]+' | head -1) - if [ -z "$CID" ]; then echo "SETOPS_OK client absent"; exit 0; fi - if "$KC" get clients/$CID/protocol-mappers/models -r {{ serveur_keycloak_realm }} 2>/dev/null \ - | grep -q '"groupes-membres"'; then - echo SETOPS_OK - else - # `full.path=false` : le claim porte `sysadmin`, pas `/sysadmin`. Les - # services comparent un nom de groupe, pas un chemin. - "$KC" create clients/$CID/protocol-mappers/models -r {{ serveur_keycloak_realm }} \ - -s name=groupes-membres -s protocol=openid-connect \ - -s protocolMapper=oidc-group-membership-mapper \ - -s 'config."claim.name"={{ serveur_keycloak_groupes_claim }}' \ - -s 'config."full.path"=false' \ - -s 'config."id.token.claim"=true' \ - -s 'config."access.token.claim"=true' \ - -s 'config."userinfo.token.claim"=true' >/dev/null - echo SETOPS_CHANGED - fi - environment: - KC_ADMIN_PW: "{{ serveur_keycloak_admin_password }}" - loop: "{{ serveur_keycloak_groupes_mapper_clients }}" - register: serveur_keycloak_grp_mapper - changed_when: "'SETOPS_CHANGED' in serveur_keycloak_grp_mapper.stdout" - no_log: true - when: - - serveur_keycloak_groupes_mapper_clients | length > 0 - - not ansible_check_mode - -# --- Rendre le changement de mot de passe EXPLICITE --------------------------- -# OpenLDAP marque `pwdReset` et refuse les operations au niveau du PROTOCOLE ; -# Keycloak a son propre systeme d'actions requises et NE LIT PAS `pwdReset`. Les -# deux mecanismes ne se parlent pas : l'utilisateur entre avec son jeton, atterrit -# dans la console de compte, et rien ne lui dit pourquoi ni quoi faire. Constate le -# 2026-08-07 par l'operateur : « ce n'est pas du tout explicite ». -# -# On aligne donc Keycloak sur l'annuaire : tant que `pwdReset` est vrai, l'action -# `UPDATE_PASSWORD` est posee et Keycloak affiche un ecran qui l'explique. Elle -# disparait d'elle-meme quand le mot de passe change — l'annuaire retire `pwdReset`, -# et la condition cesse d'etre vraie. - name: Lire `pwdReset` sur le compte d'amorçage (délégué à l'annuaire) ansible.builtin.command: argv: diff --git a/roles/serveur_keycloak/tasks/main.yml b/roles/serveur_keycloak/tasks/main.yml index 14bffcc..1f3268d 100644 --- a/roles/serveur_keycloak/tasks/main.yml +++ b/roles/serveur_keycloak/tasks/main.yml @@ -185,6 +185,10 @@ ansible.builtin.include_tasks: clients-oidc.yml when: serveur_keycloak_clients | length > 0 +- name: Émettre le claim de groupes dans le jeton des clients + ansible.builtin.include_tasks: claim-groupes.yml + when: not ansible_check_mode + - name: Déclarer les URI de retour après déconnexion ansible.builtin.include_tasks: deconnexion-oidc.yml when: not ansible_check_mode