Consolider : binding annuaire (resoudre_annuaire) — keycloak/dovecot/postfix

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 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-07-03 22:23:22 -04:00
parent 3c1cd3e90e
commit 0a0563c226
9 changed files with 100 additions and 13 deletions

View file

@ -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

View file

@ -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"

View file

@ -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.<domaine>
# resoudre_annuaire_uri — ldaps://id-ldap-01.<domaine>
# 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

View file

@ -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}))"

View file

@ -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:

View file

@ -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"

View file

@ -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 }}"

View file

@ -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

View file

@ -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: