--- # Enregistre les clients OIDC applicatifs dans le realm via kcadm — idempotent. # Crée le client s'il est absent, et RÉCONCILIE son secret et ses URI s'il existe. # # « Create-si-absent » rendait la rotation d'un secret OIDC impossible : la voûte # changeait, l'application recevait la nouvelle valeur, et Keycloak gardait # l'ancienne — le SSO cassait sans que rien ne l'annonce. Constaté le 2026-08-07, # au moment de faire tourner `vault_forgejo_oidc`, exposé dans une transcription. - name: Enregistrer les clients OIDC (kcadm, idempotent) 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.clientId }} \ --fields id --format csv --noquotes 2>/dev/null | head -1 || true) if [ -n "$CID" ]; then # Le secret EN PLACE, compare a celui voulu. `get client-secret` le rend en # clair : c'est la seule facon de savoir s'il faut ecrire. ACTUEL=$("$KC" get clients/"$CID"/client-secret -r {{ serveur_keycloak_realm }} 2>/dev/null \ | sed -n 's/.*"value" *: *"\([^"]*\)".*/\1/p' | head -1 || true) if [ "$ACTUEL" != "$OIDC_SECRET" ]; then "$KC" update clients/"$CID" -r {{ serveur_keycloak_realm }} \ -s secret="$OIDC_SECRET" \ -s 'redirectUris={{ item.redirect_uris | to_json }}' \ -s 'webOrigins={{ item.web_origins | default([]) | to_json }}' >/dev/null echo SETOPS_CHANGED else echo SETOPS_OK fi else "$KC" create clients -r {{ serveur_keycloak_realm }} \ -s clientId={{ item.clientId }} -s enabled=true -s protocol=openid-connect \ -s publicClient=false -s standardFlowEnabled=true -s directAccessGrantsEnabled=false \ -s 'redirectUris={{ item.redirect_uris | to_json }}' \ -s 'webOrigins={{ item.web_origins | default([]) | to_json }}' \ -s secret="$OIDC_SECRET" >/dev/null echo SETOPS_CHANGED fi environment: KC_ADMIN_PW: "{{ serveur_keycloak_admin_password }}" OIDC_SECRET: "{{ item.secret }}" loop: "{{ serveur_keycloak_clients }}" loop_control: label: "{{ item.clientId }}" register: serveur_keycloak_client_reg changed_when: "'SETOPS_CHANGED' in serveur_keycloak_client_reg.stdout" no_log: true when: not ansible_check_mode