Recensement apres l'echec du passage d'idempotence : 9 get_url sans aucune garde, 1 avec. Les quatre artefacts epingles (forgejo, keycloak, nextcloud, oauth2-proxy) sont immuables PAR CONSTRUCTION — leur chemin de destination porte la version. Les retelecharger n'a aucun sens, les recontacter encore moins. Gardes par une verification d'existence. Restent cinq cles de signature apt et un trousseau .deb, recuperes a chaque passage : cinq serveurs externes x quatorze hotes = 70 allers-retours par deploiement. Les garder supprimerait la dependance au prix de ne plus detecter une rotation ; une cle tournee casse apt bruyamment, donc l'oubli se voit. Arbitrage a rendre — pas a moi. Sur une plateforme souveraine la question merite d'etre posee : combien de serveurs tiers doivent etre joignables pour redeployer ce qu'on possede deja ? Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
258 lines
9.5 KiB
YAML
258 lines
9.5 KiB
YAML
---
|
|
|
|
# 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
|
|
- 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)."
|
|
|
|
- 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 }}"
|
|
|
|
- name: Adopter les facts de base pour Keycloak
|
|
ansible.builtin.set_fact:
|
|
serveur_keycloak_entree: "{{ resoudre_base_entree }}"
|
|
serveur_keycloak_db_password: "{{ resoudre_base_db_password }}"
|
|
serveur_keycloak_db_host: "{{ resoudre_base_db_host }}"
|
|
no_log: true
|
|
|
|
- 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
|
|
|
|
- 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"
|
|
|
|
# Le chemin de destination PORTE la version : cet artefact est immuable une fois pose.
|
|
# Sans garde, `get_url` recontacte le serveur distant a CHAQUE deploiement — et un
|
|
# serveur tiers lent suffit alors a faire tomber un deploiement de flotte. Constate le
|
|
# 2026-08-09 sur le binaire Forgejo, meme motif ici.
|
|
- name: L artefact de cette version est-il deja pose ?
|
|
ansible.builtin.stat:
|
|
path: "/tmp/keycloak-{{ serveur_keycloak_version }}.tar.gz"
|
|
register: serveur_keycloak_archive_present
|
|
|
|
- name: Telecharger la distribution Keycloak
|
|
when: not serveur_keycloak_archive_present.stat.exists
|
|
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
|
|
|
|
- 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
|
|
|
|
- name: Activer et demarrer keycloak
|
|
when: not ansible_check_mode
|
|
ansible.builtin.systemd:
|
|
name: "{{ serveur_keycloak_service }}"
|
|
enabled: true
|
|
state: started
|
|
daemon_reload: true
|
|
|
|
- name: Appliquer le theme (recharger Keycloak avant la config du realm)
|
|
ansible.builtin.meta: flush_handlers
|
|
|
|
- name: Configurer la federation LDAP (realm + provider OpenLDAP)
|
|
ansible.builtin.include_tasks: federation-ldap.yml
|
|
when: serveur_keycloak_ldap_federation | bool
|
|
|
|
- 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
|
|
|
|
- name: Configurer le courriel du realm (« mot de passe oublié »)
|
|
ansible.builtin.include_tasks: courriel-realm.yml
|
|
when: serveur_keycloak_smtp_actif | bool
|
|
|
|
- name: Créer les rôles de realm (prérequis de la projection des groupes)
|
|
ansible.builtin.include_tasks: roles-realm.yml
|
|
when: not ansible_check_mode
|
|
|
|
- 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
|
|
|
|
- name: Enregistrer les clients OIDC applicatifs
|
|
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
|
|
|
|
- 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
|
|
|
|
- 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
|
|
|
|
- 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
|