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