diff --git a/CHANGELOG.md b/CHANGELOG.md index a4cb7fe..a6005e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # CHANGELOG — Set-OPS +## 2026-08-08 — Déconnexion OIDC : Keycloak valide une SECONDE liste d'URI + +Nextcloud se connectait parfaitement et échouait à la déconnexion, sur un +« We are sorry… invalid redirect uri » qui ne dit pas de quelle liste il parle. + +Keycloak valide les URI de retour **après déconnexion** séparément des URI de rappel. +Aucun des quatre clients ne déclarait l'attribut ; Nextcloud était seulement le seul à +envoyer une URI de retour, donc le seul à révéler le trou. Les trois autres l'auraient +rencontré dès qu'on leur aurait câblé une déconnexion propre. + +`post.logout.redirect.uris` est désormais **dérivée de `web_origins`**, qui porte déjà +l'URL de base de chaque service : la connaissance existait, il n'y avait pas à la +réécrire. Posée par l'API et non par `kcadm -s` — `attributes` est une map, et sur une map +kcadm accepte la commande, sort en succès et n'écrit rien (mesuré le même jour sur +`smtpServer`). Relu après écriture, comme il se doit maintenant. + +**Et le second passage a révélé un défaut dans le travail de l'heure précédente.** La tâche +de journalisation se déclarait `changed` à chaque déploiement : écrits champ par champ, +Jinja rendait `True` et `1209600` en **chaînes**, et la comparaison au réel (booléen, +entier) ne pouvait jamais être satisfaite. Corrigé en composant le dictionnaire en une +seule expression, qui rend des types natifs. Un `changed` permanent n'est pas cosmétique : +c'est un bruit qui finit par masquer un vrai changement. Deux passages consécutifs à +`changed=0` désormais. + ## 2026-08-08 — 502 sur Icinga : le tampon de nginx, après une authentification réussie Symptôme trompeur s'il en est : `oauth2-proxy` journalisait `AuthSuccess` — jeton, jeton diff --git a/roles/serveur_keycloak/tasks/deconnexion-oidc.yml b/roles/serveur_keycloak/tasks/deconnexion-oidc.yml new file mode 100644 index 0000000..b422331 --- /dev/null +++ b/roles/serveur_keycloak/tasks/deconnexion-oidc.yml @@ -0,0 +1,56 @@ +--- +# URI de retour APRES DECONNEXION, par client. +# +# Keycloak les valide separement des URI de rappel : un client peut donc se connecter +# parfaitement et echouer a la deconnexion, sur un « We are sorry… invalid redirect uri » +# qui ne dit pas de quelle liste il parle. Constate le 2026-08-08 sur Nextcloud — et +# AUCUN des quatre clients ne declarait cet attribut ; Nextcloud etait seulement le seul +# a envoyer une URI de retour. +# +# DERIVEE de `web_origins`, qui porte deja l'URL de base du service : la connaissance +# existe, il n'y a pas a la reecrire. `/*` couvre la page d'accueil et les chemins de +# retour que chaque application choisit. +# +# Par l'API et non `kcadm -s` : `attributes` est une MAP, et sur une map kcadm accepte +# la commande, sort en succes et n'ecrit rien (mesure du meme jour sur `smtpServer`). +# On relit systematiquement apres avoir pose. + +- name: Lire les clients du realm + ansible.builtin.uri: + url: "http://localhost:8080/admin/realms/{{ serveur_keycloak_realm }}/clients" + headers: + Authorization: "Bearer {{ serveur_keycloak_pol_jeton.json.access_token }}" + register: serveur_keycloak_clients_actuels + no_log: true + +- name: Composer les URI de déconnexion attendues + ansible.builtin.set_fact: + serveur_keycloak_deconnexion: >- + {{ dict(serveur_keycloak_clients | map(attribute='clientId') | list + | zip(serveur_keycloak_clients + | map(attribute='web_origins') + | map('map', 'regex_replace', '/?$', '/*') + | map('join', '##') | list)) }} + +- name: Déclarer l'URI de retour après déconnexion + ansible.builtin.uri: + url: >- + http://localhost:8080/admin/realms/{{ serveur_keycloak_realm + }}/clients/{{ item.id }} + method: PUT + status_code: [204] + headers: + Authorization: "Bearer {{ serveur_keycloak_pol_jeton.json.access_token }}" + body_format: json + body: >- + {{ item | combine({'attributes': (item.attributes | default({})) + | combine({'post.logout.redirect.uris': serveur_keycloak_deconnexion[item.clientId]})}) }} + loop: "{{ serveur_keycloak_clients_actuels.json }}" + loop_control: + label: "{{ item.clientId }}" + no_log: true + changed_when: true + when: + - item.clientId in serveur_keycloak_deconnexion + - (item.attributes | default({})).get('post.logout.redirect.uris', '') + != serveur_keycloak_deconnexion[item.clientId] diff --git a/roles/serveur_keycloak/tasks/journalisation.yml b/roles/serveur_keycloak/tasks/journalisation.yml index d3b1fbb..a4ec116 100644 --- a/roles/serveur_keycloak/tasks/journalisation.yml +++ b/roles/serveur_keycloak/tasks/journalisation.yml @@ -17,12 +17,16 @@ no_log: true - name: Composer la configuration attendue + # Une seule expression, pour obtenir des types NATIFS. Ecrit champ par champ, Jinja + # rendait « True » et « 1209600 » en CHAINES : la comparaison au reel (booleen, entier) + # ne pouvait jamais etre satisfaite, et la tache se declarait `changed` a chaque + # passage. Un bruit permanent finit par masquer un vrai changement. ansible.builtin.set_fact: - serveur_keycloak_journal_attendu: - eventsEnabled: "{{ serveur_keycloak_journal_actif | bool }}" - eventsExpiration: "{{ serveur_keycloak_journal_retention | int }}" - adminEventsEnabled: "{{ serveur_keycloak_journal_admin | bool }}" - adminEventsDetailsEnabled: "{{ serveur_keycloak_journal_admin | bool }}" + serveur_keycloak_journal_attendu: >- + {{ {'eventsEnabled': serveur_keycloak_journal_actif | bool, + 'eventsExpiration': serveur_keycloak_journal_retention | int, + 'adminEventsEnabled': serveur_keycloak_journal_admin | bool, + 'adminEventsDetailsEnabled': serveur_keycloak_journal_admin | bool} }} - name: Activer le journal des évènements du realm ansible.builtin.uri: diff --git a/roles/serveur_keycloak/tasks/main.yml b/roles/serveur_keycloak/tasks/main.yml index 9acbbfb..1885e06 100644 --- a/roles/serveur_keycloak/tasks/main.yml +++ b/roles/serveur_keycloak/tasks/main.yml @@ -181,6 +181,10 @@ ansible.builtin.include_tasks: clients-oidc.yml when: serveur_keycloak_clients | length > 0 +- name: Déclarer les URI de retour après déconnexion + ansible.builtin.include_tasks: deconnexion-oidc.yml + when: not ansible_check_mode + - name: Configurer le RBAC OIDC (roles de realm + mapper + assignations) ansible.builtin.include_tasks: rbac-oidc.yml when: >