From 0a0563c226411e691352450585a032be4cdd08e3 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Fri, 3 Jul 2026 22:23:22 -0400 Subject: [PATCH] =?UTF-8?q?Consolider=20:=20binding=20annuaire=20(resoudre?= =?UTF-8?q?=5Fannuaire)=20=E2=80=94=20keycloak/dovecot/postfix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rôle utilitaire partagé resoudre_annuaire (comme resoudre_base) : dérive la connexion OpenLDAP du domaine_interne + un hôte surchargeable, au lieu de répéter ldaps://id-ldap-01... dans chaque rôle. Facts uri/port/base_dn/ users_dn/bind_dn/bind_password (secret no_log). Migrés + prouvés (config neutre, changed=0) : keycloak (fédération, testmail token 200), dovecot + postfix (flux courriel livré de bout en bout). Piège : les defaults d'un rôle inclus ne persistent pas hors de son exécution — publier via set_fact. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 13 +++++++++ roles/resoudre_annuaire/defaults/main.yml | 9 ++++++ roles/resoudre_annuaire/tasks/main.yml | 34 +++++++++++++++++++++++ roles/serveur_dovecot/defaults/main.yml | 7 ++--- roles/serveur_dovecot/tasks/main.yml | 12 ++++++++ roles/serveur_keycloak/defaults/main.yml | 6 ++-- roles/serveur_keycloak/tasks/main.yml | 14 ++++++++++ roles/serveur_postfix/defaults/main.yml | 6 ++-- roles/serveur_postfix/tasks/main.yml | 12 ++++++++ 9 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 roles/resoudre_annuaire/defaults/main.yml create mode 100644 roles/resoudre_annuaire/tasks/main.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index d58ad62..e5d2731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ ## 2026-07-03 ### Ajouté +- **Consolidation — binding `annuaire` (`resoudre_annuaire`) + retrait de la cruft.** + *Cruft* : supprimés les 8 dossiers-catégories inertes (`roles/{applications,backup,database, + identity,monitoring,proxmox,storage,web}/`, README seuls) et les 5 playbooks-échafaudages `debug` + sans rôle (nextcloud, collabora, client_supervision, web_frontal, web_dorsal) ; l'intention reste + documentée dans `docs/catalogue-services.md`. *Binding annuaire* : nouveau rôle utilitaire partagé + `resoudre_annuaire` (comme `resoudre_base`) qui **dérive** la connexion OpenLDAP du `domaine_interne` + + un hôte d'annuaire surchargeable — LE seul endroit où le nom d'hôte de l'annuaire est fixé, au lieu + d'être répété. Facts `resoudre_annuaire_{uri,port,base_dn,users_dn,bind_dn,bind_password}` (secret + déréférencé, `no_log`). **Migrés + prouvés (config neutre, `changed=0`)** : `serveur_keycloak` + (fédération LDAP — testmail token HTTP 200), `serveur_dovecot` + `serveur_postfix` (flux courriel + Postfix→LDAP→LMTP→Dovecot **livré de bout en bout**). Piège appris : les *defaults* d'un rôle inclus + ne persistent pas hors de son exécution — publier via `set_fact`. Reste : les defaults LDAP dormants + d'`icingaweb2` (mode SSO, LDAP inutilisé) et `client_ldap` (référence périmée `idm-01`). - **Rôle `serveur_oauth2_proxy` — passerelle SSO OIDC générique (Keycloak) + Icinga Web 2 au SSO.** oauth2-proxy (v7.15.3, binaire GitHub) place Keycloak **devant** n'importe quelle app sans OIDC natif : elle reçoit l'utilisateur authentifié via en-tête, en auth `external`. Rôle paramétrable diff --git a/roles/resoudre_annuaire/defaults/main.yml b/roles/resoudre_annuaire/defaults/main.yml new file mode 100644 index 0000000..d714132 --- /dev/null +++ b/roles/resoudre_annuaire/defaults/main.yml @@ -0,0 +1,9 @@ +--- +# Hôte qui sert l'annuaire (OpenLDAP). Surchargeable par instance (group_vars) — c'est +# LE seul point où le nom d'hôte de l'annuaire est fixé, au lieu d'être répété dans chaque rôle. +resoudre_annuaire_hote: "id-ldap-01" +resoudre_annuaire_port: 636 +resoudre_annuaire_encryption: "ldaps" +# Compte de service (bind) — secret déréférencé depuis la voûte dans le rôle consommateur. +resoudre_annuaire_bind_cn: "admin" +resoudre_annuaire_secret: "vault_openldap_admin" diff --git a/roles/resoudre_annuaire/tasks/main.yml b/roles/resoudre_annuaire/tasks/main.yml new file mode 100644 index 0000000..525e390 --- /dev/null +++ b/roles/resoudre_annuaire/tasks/main.yml @@ -0,0 +1,34 @@ +--- +# Rôle utilitaire (pas un groupe déployable) : résout la connexion à l'annuaire (OpenLDAP) +# de façon déclarative, dérivée du domaine interne + de l'hôte d'annuaire. Remplace le +# câblage LDAP répété dans serveur_dovecot/postfix/keycloak/icingaweb2. +# +# Sorties (facts) : +# resoudre_annuaire_fqdn — id-ldap-01. +# resoudre_annuaire_uri — ldaps://id-ldap-01. +# resoudre_annuaire_base_dn — dc=...,dc=... +# resoudre_annuaire_users_dn — ou=people,dc=... +# resoudre_annuaire_bind_dn — cn=admin,dc=... +# resoudre_annuaire_bind_password — secret déréférencé depuis la voûte (no_log) +- name: Exiger un domaine interne non vide + ansible.builtin.assert: + that: + - domaine_interne is defined + - domaine_interne | length > 0 + fail_msg: "domaine_interne doit être défini pour résoudre l'annuaire." + +# set_fact (pas les defaults) pour que ces valeurs persistent dans le rôle consommateur +# après include_role (les defaults du rôle inclus ne fuient pas hors de son exécution). +- name: Dériver la connexion à l'annuaire + ansible.builtin.set_fact: + resoudre_annuaire_fqdn: "{{ resoudre_annuaire_hote }}.{{ domaine_interne }}" + resoudre_annuaire_uri: "{{ resoudre_annuaire_encryption }}://{{ resoudre_annuaire_hote }}.{{ domaine_interne }}" + resoudre_annuaire_port: "{{ resoudre_annuaire_port }}" + resoudre_annuaire_base_dn: "dc={{ domaine_interne.split('.') | join(',dc=') }}" + resoudre_annuaire_users_dn: "ou=people,dc={{ domaine_interne.split('.') | join(',dc=') }}" + resoudre_annuaire_bind_dn: "cn={{ resoudre_annuaire_bind_cn }},dc={{ domaine_interne.split('.') | join(',dc=') }}" + +- name: Déréférencer le mot de passe de bind (Vault) + ansible.builtin.set_fact: + resoudre_annuaire_bind_password: "{{ lookup('vars', resoudre_annuaire_secret) }}" + no_log: true diff --git a/roles/serveur_dovecot/defaults/main.yml b/roles/serveur_dovecot/defaults/main.yml index ea9e346..3015b94 100644 --- a/roles/serveur_dovecot/defaults/main.yml +++ b/roles/serveur_dovecot/defaults/main.yml @@ -21,11 +21,8 @@ serveur_dovecot_vmail_base: "/var/vmail" serveur_dovecot_mail_path: "{{ serveur_dovecot_vmail_base }}/%{user | username}" # --- Annuaire/auth LDAP (vers serveur_openldap) --- -serveur_dovecot_ldap_uris: "ldaps://id-ldap-01.{{ domaine_interne }}:636" -serveur_dovecot_ldap_base: "dc={{ domaine_interne.split('.') | join(',dc=') }}" -# Compte de service pour la recherche (bind DN). En bac à sable : l'admin LDAP. -serveur_dovecot_ldap_bind_dn: "cn=admin,{{ serveur_dovecot_ldap_base }}" -serveur_dovecot_ldap_bind_password: "{{ vault_openldap_admin | default('') }}" +# Connexion à l'annuaire (uris, base, bind_dn, bind_password) : fournie par le rôle partagé +# resoudre_annuaire (voir tasks/main.yml « Adopter la connexion annuaire »). # Filtre de connexion : utilisateur = adresse courriel (attribut mail). serveur_dovecot_ldap_filter: "(&(objectClass=inetOrgPerson)(mail=%{user}))" diff --git a/roles/serveur_dovecot/tasks/main.yml b/roles/serveur_dovecot/tasks/main.yml index b4d5940..d5293d7 100644 --- a/roles/serveur_dovecot/tasks/main.yml +++ b/roles/serveur_dovecot/tasks/main.yml @@ -1,4 +1,16 @@ --- +- name: Resoudre l'annuaire (role partage) + ansible.builtin.include_role: + name: resoudre_annuaire + +- name: Adopter la connexion annuaire pour Dovecot + ansible.builtin.set_fact: + serveur_dovecot_ldap_uris: "{{ resoudre_annuaire_uri }}:{{ resoudre_annuaire_port }}" + serveur_dovecot_ldap_base: "{{ resoudre_annuaire_base_dn }}" + serveur_dovecot_ldap_bind_dn: "{{ resoudre_annuaire_bind_dn }}" + serveur_dovecot_ldap_bind_password: "{{ resoudre_annuaire_bind_password }}" + no_log: true + - name: Exiger le mot de passe de bind LDAP (Vault) ansible.builtin.assert: that: diff --git a/roles/serveur_keycloak/defaults/main.yml b/roles/serveur_keycloak/defaults/main.yml index 6685728..6bedeab 100644 --- a/roles/serveur_keycloak/defaults/main.yml +++ b/roles/serveur_keycloak/defaults/main.yml @@ -26,10 +26,8 @@ serveur_keycloak_admin_password: "{{ vault_keycloak_admin | default('') }}" # r serveur_keycloak_ldap_federation: true serveur_keycloak_realm: "chezlepro" serveur_keycloak_ldap_nom: "openldap" -serveur_keycloak_ldap_url: "ldaps://id-ldap-01.{{ domaine_interne }}:636" -serveur_keycloak_ldap_users_dn: "ou=people,dc={{ domaine_interne.split('.') | join(',dc=') }}" -serveur_keycloak_ldap_bind_dn: "cn=admin,dc={{ domaine_interne.split('.') | join(',dc=') }}" -serveur_keycloak_ldap_bind_password: "{{ vault_openldap_admin | default('') }}" +# Connexion à l'annuaire (url, users_dn, bind_dn, bind_password) : fournie par le rôle +# partagé resoudre_annuaire (voir tasks/main.yml « Adopter la connexion annuaire »). serveur_keycloak_ldap_username_attr: "uid" serveur_keycloak_ldap_uuid_attr: "entryUUID" serveur_keycloak_ldap_object_classes: "inetOrgPerson" diff --git a/roles/serveur_keycloak/tasks/main.yml b/roles/serveur_keycloak/tasks/main.yml index 4a5ab2f..73b05c4 100644 --- a/roles/serveur_keycloak/tasks/main.yml +++ b/roles/serveur_keycloak/tasks/main.yml @@ -18,6 +18,20 @@ 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 }}" diff --git a/roles/serveur_postfix/defaults/main.yml b/roles/serveur_postfix/defaults/main.yml index 4597fc0..d5d4954 100644 --- a/roles/serveur_postfix/defaults/main.yml +++ b/roles/serveur_postfix/defaults/main.yml @@ -19,10 +19,8 @@ serveur_postfix_mailstore_hote: "" serveur_postfix_lmtp_port: 24 # --- Annuaire LDAP (validation domaines/boîtes/alias) --- -serveur_postfix_ldap_serveur: "ldaps://id-ldap-01.{{ domaine_interne }}:636" -serveur_postfix_ldap_base: "ou=people,dc={{ domaine_interne.split('.') | join(',dc=') }}" -serveur_postfix_ldap_bind_dn: "cn=admin,dc={{ domaine_interne.split('.') | join(',dc=') }}" -serveur_postfix_ldap_bind_password: "{{ vault_openldap_admin | default('') }}" +# Connexion à l'annuaire (serveur, base, bind_dn, bind_password) : fournie par le rôle partagé +# resoudre_annuaire (voir tasks/main.yml « Adopter la connexion annuaire »). # --- Milter rspamd (antispam + DKIM) — si rspamd est co-localisé (nœud edge-mta) --- serveur_postfix_rspamd_milter: false diff --git a/roles/serveur_postfix/tasks/main.yml b/roles/serveur_postfix/tasks/main.yml index 514e561..395c096 100644 --- a/roles/serveur_postfix/tasks/main.yml +++ b/roles/serveur_postfix/tasks/main.yml @@ -1,4 +1,16 @@ --- +- name: Resoudre l'annuaire (role partage) + ansible.builtin.include_role: + name: resoudre_annuaire + +- name: Adopter la connexion annuaire pour Postfix + ansible.builtin.set_fact: + serveur_postfix_ldap_serveur: "{{ resoudre_annuaire_uri }}:{{ resoudre_annuaire_port }}" + serveur_postfix_ldap_base: "{{ resoudre_annuaire_users_dn }}" + serveur_postfix_ldap_bind_dn: "{{ resoudre_annuaire_bind_dn }}" + serveur_postfix_ldap_bind_password: "{{ resoudre_annuaire_bind_password }}" + no_log: true + - name: Exiger le mot de passe de bind LDAP (Vault) ansible.builtin.assert: that: