2026-06-24 20:17:46 -04:00
|
|
|
---
|
2026-08-07 11:47:34 -04:00
|
|
|
|
|
|
|
|
# Le nom d'hote de Keycloak EST son identite OIDC. Le fabriquer par convention
|
|
|
|
|
# (`keycloak.<domaine>`) produisait un nom que rien ne publie : le plan expose `auth`,
|
|
|
|
|
# l'edge sert `auth`, le DNS resout `auth` — et Keycloak s'annoncait `keycloak`. Tout
|
|
|
|
|
# client OIDC comparait les deux et refusait. Constate le 2026-08-07 sur oauth2-proxy :
|
|
|
|
|
# « issuer did not match the issuer returned by provider ».
|
|
|
|
|
- name: Charger les registres applications et domaines (expositions)
|
|
|
|
|
ansible.builtin.include_vars:
|
|
|
|
|
file: "{{ item }}"
|
|
|
|
|
loop:
|
|
|
|
|
- "{{ setops_plan_dir }}/applications.yml"
|
|
|
|
|
- "{{ setops_plan_dir }}/domaines.yml"
|
|
|
|
|
when: serveur_keycloak_hostname_derive | bool
|
|
|
|
|
|
|
|
|
|
- name: Dériver le nom d'hôte public depuis l'exposition déclarée
|
|
|
|
|
ansible.builtin.set_fact:
|
|
|
|
|
serveur_keycloak_hostname: >-
|
|
|
|
|
{{ (applications | default({})).get('keycloak', {}).get('expose', []) | first }}
|
|
|
|
|
when:
|
|
|
|
|
- serveur_keycloak_hostname_derive | bool
|
|
|
|
|
- ((applications | default({})).get('keycloak', {}).get('expose', []) | length) > 0
|
2026-06-24 20:17:46 -04:00
|
|
|
- name: Exiger le mot de passe admin Keycloak (Vault)
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- serveur_keycloak_admin_password | length > 0
|
|
|
|
|
fail_msg: "serveur_keycloak_admin_password est requis (Ansible Vault)."
|
|
|
|
|
|
2026-07-03 15:57:06 -04:00
|
|
|
- name: Resoudre la base de donnees depuis le registre (role partage)
|
|
|
|
|
ansible.builtin.include_role:
|
|
|
|
|
name: resoudre_base
|
|
|
|
|
vars:
|
|
|
|
|
resoudre_base_groupe: "{{ serveur_keycloak_groupe }}"
|
2026-06-24 20:17:46 -04:00
|
|
|
|
2026-07-03 15:57:06 -04:00
|
|
|
- name: Adopter les facts de base pour Keycloak
|
2026-06-24 20:17:46 -04:00
|
|
|
ansible.builtin.set_fact:
|
2026-07-03 15:57:06 -04:00
|
|
|
serveur_keycloak_entree: "{{ resoudre_base_entree }}"
|
|
|
|
|
serveur_keycloak_db_password: "{{ resoudre_base_db_password }}"
|
|
|
|
|
serveur_keycloak_db_host: "{{ resoudre_base_db_host }}"
|
2026-06-24 20:17:46 -04:00
|
|
|
no_log: true
|
|
|
|
|
|
2026-07-03 22:23:22 -04:00
|
|
|
- name: Resoudre l'annuaire (role partage)
|
|
|
|
|
ansible.builtin.include_role:
|
|
|
|
|
name: resoudre_annuaire
|
|
|
|
|
when: serveur_keycloak_ldap_federation | bool
|
|
|
|
|
|
|
|
|
|
- name: Adopter la connexion annuaire pour Keycloak
|
|
|
|
|
ansible.builtin.set_fact:
|
|
|
|
|
serveur_keycloak_ldap_url: "{{ resoudre_annuaire_uri }}:{{ resoudre_annuaire_port }}"
|
|
|
|
|
serveur_keycloak_ldap_users_dn: "{{ resoudre_annuaire_users_dn }}"
|
|
|
|
|
serveur_keycloak_ldap_bind_dn: "{{ resoudre_annuaire_bind_dn }}"
|
|
|
|
|
serveur_keycloak_ldap_bind_password: "{{ resoudre_annuaire_bind_password }}"
|
|
|
|
|
no_log: true
|
|
|
|
|
when: serveur_keycloak_ldap_federation | bool
|
|
|
|
|
|
2026-06-24 20:17:46 -04:00
|
|
|
- name: Installer le JRE
|
|
|
|
|
ansible.builtin.apt:
|
|
|
|
|
name: "{{ serveur_keycloak_java_paquet }}"
|
|
|
|
|
state: present
|
|
|
|
|
update_cache: true
|
|
|
|
|
cache_valid_time: 3600
|
|
|
|
|
|
|
|
|
|
- name: Creer l'utilisateur systeme keycloak
|
|
|
|
|
ansible.builtin.user:
|
|
|
|
|
name: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
system: true
|
|
|
|
|
home: "{{ serveur_keycloak_home }}"
|
|
|
|
|
create_home: false
|
|
|
|
|
shell: /usr/sbin/nologin
|
|
|
|
|
|
|
|
|
|
- name: Creer le repertoire d'installation
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ serveur_keycloak_home }}"
|
|
|
|
|
state: directory
|
|
|
|
|
owner: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
mode: "0750"
|
|
|
|
|
|
|
|
|
|
- name: Telecharger la distribution Keycloak
|
|
|
|
|
ansible.builtin.get_url:
|
|
|
|
|
url: "{{ serveur_keycloak_url }}"
|
|
|
|
|
dest: "/tmp/keycloak-{{ serveur_keycloak_version }}.tar.gz"
|
|
|
|
|
mode: "0644"
|
|
|
|
|
|
|
|
|
|
- name: Extraire Keycloak
|
|
|
|
|
ansible.builtin.unarchive:
|
|
|
|
|
src: "/tmp/keycloak-{{ serveur_keycloak_version }}.tar.gz"
|
|
|
|
|
dest: "{{ serveur_keycloak_home }}"
|
|
|
|
|
remote_src: true
|
|
|
|
|
extra_opts: ["--strip-components=1"]
|
|
|
|
|
owner: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
creates: "{{ serveur_keycloak_home }}/bin/kc.sh"
|
|
|
|
|
|
|
|
|
|
- name: Deployer keycloak.conf (config non sensible)
|
|
|
|
|
ansible.builtin.template:
|
|
|
|
|
src: keycloak.conf.j2
|
|
|
|
|
dest: "{{ serveur_keycloak_home }}/conf/keycloak.conf"
|
|
|
|
|
owner: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
mode: "0640"
|
|
|
|
|
notify: Redemarrer keycloak
|
|
|
|
|
|
|
|
|
|
- name: Creer le repertoire de configuration des secrets
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: /etc/keycloak
|
|
|
|
|
state: directory
|
|
|
|
|
owner: root
|
|
|
|
|
group: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
mode: "0750"
|
|
|
|
|
|
|
|
|
|
- name: Deployer le fichier d'environnement (secrets)
|
|
|
|
|
ansible.builtin.template:
|
|
|
|
|
src: keycloak.env.j2
|
|
|
|
|
dest: /etc/keycloak/keycloak.env
|
|
|
|
|
owner: root
|
|
|
|
|
group: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
mode: "0640"
|
|
|
|
|
no_log: true
|
|
|
|
|
notify: Redemarrer keycloak
|
|
|
|
|
|
|
|
|
|
- name: Construire Keycloak (une fois par version)
|
|
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: >-
|
|
|
|
|
{{ serveur_keycloak_home }}/bin/kc.sh build --db=postgres
|
|
|
|
|
&& touch {{ serveur_keycloak_home }}/.kc-built-{{ serveur_keycloak_version }}
|
|
|
|
|
creates: "{{ serveur_keycloak_home }}/.kc-built-{{ serveur_keycloak_version }}"
|
|
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
notify: Redemarrer keycloak
|
|
|
|
|
|
|
|
|
|
- name: Deployer l'unite systemd keycloak
|
|
|
|
|
ansible.builtin.template:
|
|
|
|
|
src: keycloak.service.j2
|
|
|
|
|
dest: /etc/systemd/system/keycloak.service
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: "0644"
|
|
|
|
|
notify: Redemarrer keycloak
|
|
|
|
|
|
2026-07-03 23:42:57 -04:00
|
|
|
- name: Deployer les themes Set-OPS (identite Alliance Boreale)
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
src: themes/
|
|
|
|
|
dest: "{{ serveur_keycloak_home }}/themes/"
|
|
|
|
|
owner: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_keycloak_utilisateur }}"
|
|
|
|
|
mode: "0644"
|
|
|
|
|
directory_mode: "0755"
|
|
|
|
|
notify: Redemarrer keycloak
|
|
|
|
|
|
2026-06-24 20:17:46 -04:00
|
|
|
- name: Activer et demarrer keycloak
|
2026-07-01 21:01:53 -04:00
|
|
|
when: not ansible_check_mode
|
2026-06-24 20:17:46 -04:00
|
|
|
ansible.builtin.systemd:
|
|
|
|
|
name: "{{ serveur_keycloak_service }}"
|
|
|
|
|
enabled: true
|
|
|
|
|
state: started
|
|
|
|
|
daemon_reload: true
|
2026-07-03 11:29:23 -04:00
|
|
|
|
2026-07-03 23:42:57 -04:00
|
|
|
- name: Appliquer le theme (recharger Keycloak avant la config du realm)
|
|
|
|
|
ansible.builtin.meta: flush_handlers
|
|
|
|
|
|
2026-07-03 11:29:23 -04:00
|
|
|
- name: Configurer la federation LDAP (realm + provider OpenLDAP)
|
|
|
|
|
ansible.builtin.include_tasks: federation-ldap.yml
|
|
|
|
|
when: serveur_keycloak_ldap_federation | bool
|
2026-07-03 13:37:33 -04:00
|
|
|
|
identite : une declaration de politique de mot de passe, deux executants
Quatre defauts mesures dans l'integration Keycloak/LDAP, meme famille : une
valeur declaree d'un cote, consommee de l'autre, rien qui verifie la jonction.
1. Aucune regle ne s'appliquait sur le chemin d'un vrai utilisateur. Sonde :
« abcd » refuse par l'operation etendue LDAP, accepte par Keycloak (204),
puis actif pour l'authentification. Keycloak ecrivait userPassword en
direct (ppolicy aveugle) et le realm n'avait aucune passwordPolicy.
2. ldap_entry ne fait que CREER : la politique etait figee a sa creation. Le
depot disait pwdMustChange TRUE, le serveur FALSE — une reconstruction
from-zero aurait ressuscite la boucle du 2026-08-07. ldap_attrs state=exact
reconcilie la politique et l'overlay (DN lu, pas devine).
3. syncRegistrations absent : un compte cree dans Keycloak n'atteignait jamais
ou=people — acces web, aucune boite, invisible du modele de groupes.
4. Le prenom pointait sur cn (nom complet) : « Administrateur systeme systeme ».
Ajoute roles/resoudre_politique_mdp : LA declaration, traduite en pwdPolicy,
passwordPolicy et anti-force-brute. Les deux roles la consomment sans la
redeclarer.
usePasswordModifyExtendedOp ET validatePasswordPolicy : la seconde est
porteuse, Keycloak se liant en rootDN et slapd n'appliquant pas ses controles
de qualite au rootDN. La premiere seule aurait paru juste sans tenir.
Verification : abcd -> 400 « minimum length 12 » et absent de LDAP ; mot de
passe conforme -> 204 puis ldapwhoami accepte ; POST users -> 201 ET present
dans ou=people. Second passage des deux playbooks : changed=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:28:10 -04:00
|
|
|
- name: Appliquer la politique de mot de passe et durcir la fédération
|
|
|
|
|
ansible.builtin.include_tasks: politique-mdp.yml
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
|
2026-08-08 06:08:06 -04:00
|
|
|
- name: Configurer le courriel du realm (« mot de passe oublié »)
|
|
|
|
|
ansible.builtin.include_tasks: courriel-realm.yml
|
|
|
|
|
when: serveur_keycloak_smtp_actif | bool
|
|
|
|
|
|
2026-08-07 14:32:47 -04:00
|
|
|
- name: Projeter les groupes LDAP (mapper + roles de groupe)
|
|
|
|
|
ansible.builtin.include_tasks: groupes-ldap.yml
|
|
|
|
|
when:
|
|
|
|
|
- serveur_keycloak_ldap_federation | bool
|
|
|
|
|
- serveur_keycloak_groupes_ldap | bool
|
|
|
|
|
|
2026-07-03 13:37:33 -04:00
|
|
|
- name: Enregistrer les clients OIDC applicatifs
|
|
|
|
|
ansible.builtin.include_tasks: clients-oidc.yml
|
|
|
|
|
when: serveur_keycloak_clients | length > 0
|
2026-07-03 23:42:57 -04:00
|
|
|
|
2026-08-08 11:24:02 -04:00
|
|
|
- name: Déclarer les URI de retour après déconnexion
|
|
|
|
|
ansible.builtin.include_tasks: deconnexion-oidc.yml
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
|
2026-07-04 16:58:15 -04:00
|
|
|
- name: Configurer le RBAC OIDC (roles de realm + mapper + assignations)
|
|
|
|
|
ansible.builtin.include_tasks: rbac-oidc.yml
|
|
|
|
|
when: >
|
|
|
|
|
serveur_keycloak_realm_roles | length > 0
|
|
|
|
|
or serveur_keycloak_role_assignments | length > 0
|
|
|
|
|
|
2026-07-03 23:42:57 -04:00
|
|
|
- name: Definir le theme de login du realm (identite Alliance Boreale)
|
|
|
|
|
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
|
|
|
|
|
if "$KC" get realms/{{ serveur_keycloak_realm }} --fields loginTheme 2>/dev/null \
|
|
|
|
|
| grep -q '"{{ serveur_keycloak_login_theme }}"'; then
|
|
|
|
|
echo SETOPS_OK
|
|
|
|
|
else
|
|
|
|
|
"$KC" update realms/{{ serveur_keycloak_realm }} \
|
|
|
|
|
-s loginTheme={{ serveur_keycloak_login_theme }} >/dev/null
|
|
|
|
|
echo SETOPS_CHANGED
|
|
|
|
|
fi
|
|
|
|
|
environment:
|
|
|
|
|
KC_ADMIN_PW: "{{ serveur_keycloak_admin_password }}"
|
|
|
|
|
register: serveur_keycloak_theme_res
|
|
|
|
|
changed_when: "'SETOPS_CHANGED' in serveur_keycloak_theme_res.stdout"
|
|
|
|
|
when:
|
|
|
|
|
- serveur_keycloak_login_theme | length > 0
|
|
|
|
|
- not ansible_check_mode
|
2026-07-03 23:54:19 -04:00
|
|
|
|
|
|
|
|
- name: Definir le theme de la console de compte (identite Alliance Boreale)
|
|
|
|
|
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
|
|
|
|
|
if "$KC" get realms/{{ serveur_keycloak_realm }} --fields accountTheme 2>/dev/null \
|
|
|
|
|
| grep -q '"{{ serveur_keycloak_account_theme }}"'; then
|
|
|
|
|
echo SETOPS_OK
|
|
|
|
|
else
|
|
|
|
|
"$KC" update realms/{{ serveur_keycloak_realm }} \
|
|
|
|
|
-s accountTheme={{ serveur_keycloak_account_theme }} >/dev/null
|
|
|
|
|
echo SETOPS_CHANGED
|
|
|
|
|
fi
|
|
|
|
|
environment:
|
|
|
|
|
KC_ADMIN_PW: "{{ serveur_keycloak_admin_password }}"
|
|
|
|
|
register: serveur_keycloak_account_res
|
|
|
|
|
changed_when: "'SETOPS_CHANGED' in serveur_keycloak_account_res.stdout"
|
|
|
|
|
when:
|
|
|
|
|
- serveur_keycloak_account_theme | length > 0
|
|
|
|
|
- not ansible_check_mode
|