From 7549c61e75ced56937a78bc916280a2ea885a2cb Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Thu, 2 Jul 2026 15:09:37 -0400 Subject: [PATCH] =?UTF-8?q?serveur=5Fdovecot=20(Dovecot=202.4)=20:=20IMAP?= =?UTF-8?q?=20+=20auth=20LDAP=20+=20TLS=20step=5Fca=20=E2=80=94=20prouv?= =?UTF-8?q?=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rôle mail : IMAP :993/:143 + LMTP, auth/annuaire LDAP (serveur_openldap), stockage Maildir (user vmail), TLS via step_ca (pont de cert + resync au renouvellement), auth système par défaut neutralisée. Config drop-in en syntaxe Dovecot 2.4 (mail_driver, ssl_server_cert_file, passdb ldap), validée par doveconf au déploiement. Sockets Postfix conditionnels à la présence de l'utilisateur postfix (co-localisation). Déployé et prouvé sur infra-mail-01 : doveadm auth test — bon mot de passe accepté, mauvais refusé, cert IMAPS émis par step_ca. Format 2.4 validé empiriquement avant l'écriture (leçon « éprouver l'outil »). Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 8 + playbooks/groupes/serveur_dovecot.yml | 15 ++ roles/serveur_dovecot/defaults/main.yml | 37 +++++ roles/serveur_dovecot/handlers/main.yml | 7 + roles/serveur_dovecot/meta/empreinte.yml | 8 + roles/serveur_dovecot/tasks/main.yml | 144 ++++++++++++++++++ .../templates/99-setops.conf.j2 | 58 +++++++ .../setops-dovecot-cert-sync.path.j2 | 12 ++ .../setops-dovecot-cert-sync.service.j2 | 8 + .../templates/setops-dovecot-cert-sync.sh.j2 | 19 +++ 10 files changed, 316 insertions(+) create mode 100644 playbooks/groupes/serveur_dovecot.yml create mode 100644 roles/serveur_dovecot/defaults/main.yml create mode 100644 roles/serveur_dovecot/handlers/main.yml create mode 100644 roles/serveur_dovecot/meta/empreinte.yml create mode 100644 roles/serveur_dovecot/tasks/main.yml create mode 100644 roles/serveur_dovecot/templates/99-setops.conf.j2 create mode 100644 roles/serveur_dovecot/templates/setops-dovecot-cert-sync.path.j2 create mode 100644 roles/serveur_dovecot/templates/setops-dovecot-cert-sync.service.j2 create mode 100644 roles/serveur_dovecot/templates/setops-dovecot-cert-sync.sh.j2 diff --git a/CHANGELOG.md b/CHANGELOG.md index be30f66..fea6aea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,14 @@ mis à jour. Réévaluer Stalwart ~2028. ### Ajouté +- **Rôle `serveur_dovecot` (Dovecot 2.4) — déployé et prouvé.** IMAP `:993`/`:143` + + LMTP, **auth/annuaire LDAP** (vers `serveur_openldap`, filtre `mail`), stockage Maildir + (user système `vmail`), **TLS via step_ca** (pont de cert + resync au renouvellement), + neutralisation de l'auth système par défaut. Config en drop-in **syntaxe Dovecot 2.4** + (`mail_driver`, `ssl_server_cert_file`, `passdb ldap`/`userdb static`, `%{user}`), + **validée par `doveconf`** au déploiement. Sockets d'intégration Postfix **conditionnels** + (rendus si l'utilisateur `postfix` est co-localisé). Prouvé : `doveadm auth test` — bon + mot de passe accepté, mauvais refusé, sur cert step_ca. Secret de bind : `vault_openldap_admin`. - **`serveur_openldap` durci pour la prod : TLS via step_ca + organisation en intrant.** - **TLS (LDAPS + STARTTLS)** : le certificat d'hôte step_ca (déposé par `client_pki`, `root:root 600`) est synchronisé vers un emplacement lisible par `openldap` (`/etc/ldap/tls`) diff --git a/playbooks/groupes/serveur_dovecot.yml b/playbooks/groupes/serveur_dovecot.yml new file mode 100644 index 0000000..c101eee --- /dev/null +++ b/playbooks/groupes/serveur_dovecot.yml @@ -0,0 +1,15 @@ +--- +- name: Appliquer le groupe serveur_dovecot + hosts: serveur_dovecot + become: true + gather_facts: true + + pre_tasks: + - name: Vérifier que la cible est Debian + ansible.builtin.assert: + that: + - ansible_facts.distribution == "Debian" + fail_msg: "Ce playbook est prévu pour Debian." + + roles: + - serveur_dovecot diff --git a/roles/serveur_dovecot/defaults/main.yml b/roles/serveur_dovecot/defaults/main.yml new file mode 100644 index 0000000..636aeb8 --- /dev/null +++ b/roles/serveur_dovecot/defaults/main.yml @@ -0,0 +1,37 @@ +--- +# Dovecot 2.4 (Debian 13) — IMAP + LMTP + auth LDAP, stockage Maildir des boîtes virtuelles. +# Config par drop-in conf.d/99-setops.conf (syntaxe 2.4). Cf. docs/courriel-conception.md. + +serveur_dovecot_paquets: + - dovecot-core + - dovecot-imapd + - dovecot-lmtpd + - dovecot-ldap + +serveur_dovecot_service: "dovecot" + +# --- Stockage des boîtes virtuelles --- +serveur_dovecot_vmail_utilisateur: "vmail" +serveur_dovecot_vmail_uid: 5000 +serveur_dovecot_vmail_gid: 5000 +serveur_dovecot_vmail_base: "/var/vmail" +# Maildir par domaine/utilisateur (variables 2.4). +serveur_dovecot_mail_path: "{{ serveur_dovecot_vmail_base }}/%{user | domain}/%{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('') }}" +# Filtre de connexion : utilisateur = adresse courriel (attribut mail). +serveur_dovecot_ldap_filter: "(&(objectClass=inetOrgPerson)(mail=%{user}))" + +# --- TLS via le certificat d'hôte step_ca (client_pki) --- +serveur_dovecot_tls_actif: true +serveur_dovecot_tls_source_cert: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.crt" +serveur_dovecot_tls_source_cle: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.key" +serveur_dovecot_tls_dir: "/etc/dovecot/tls" + +# --- Intégration Postfix (sockets sous le chroot Postfix) --- +serveur_dovecot_postfix_socket_dir: "/var/spool/postfix/private" diff --git a/roles/serveur_dovecot/handlers/main.yml b/roles/serveur_dovecot/handlers/main.yml new file mode 100644 index 0000000..7e4bcee --- /dev/null +++ b/roles/serveur_dovecot/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Redémarrer dovecot + ansible.builtin.systemd: + name: "{{ serveur_dovecot_service }}" + state: restarted + daemon_reload: true + when: not ansible_check_mode diff --git a/roles/serveur_dovecot/meta/empreinte.yml b/roles/serveur_dovecot/meta/empreinte.yml new file mode 100644 index 0000000..d749186 --- /dev/null +++ b/roles/serveur_dovecot/meta/empreinte.yml @@ -0,0 +1,8 @@ +--- +# Empreinte ressources du logiciel — dimensionnement VM derive (scripts/instancier.py). +# Sommee au socle SE + marge pour estimer coeurs/RAM/disque de la VM hote. +# Override possible par hote dans le plan (serveurs.yml: coeurs/memoire/disque). +setops_empreinte: + coeurs: 1 + memoire_mo: 512 + disque_go: 5 diff --git a/roles/serveur_dovecot/tasks/main.yml b/roles/serveur_dovecot/tasks/main.yml new file mode 100644 index 0000000..b4d5940 --- /dev/null +++ b/roles/serveur_dovecot/tasks/main.yml @@ -0,0 +1,144 @@ +--- +- name: Exiger le mot de passe de bind LDAP (Vault) + ansible.builtin.assert: + that: + - serveur_dovecot_ldap_bind_password | length > 0 + fail_msg: "serveur_dovecot_ldap_bind_password requis (via Ansible Vault, ex. vault_openldap_admin)." + +- name: Installer Dovecot + ansible.builtin.apt: + name: "{{ serveur_dovecot_paquets }}" + state: present + update_cache: true + +- name: Créer le groupe vmail + ansible.builtin.group: + name: "{{ serveur_dovecot_vmail_utilisateur }}" + gid: "{{ serveur_dovecot_vmail_gid }}" + system: true + +- name: Créer l'utilisateur vmail + ansible.builtin.user: + name: "{{ serveur_dovecot_vmail_utilisateur }}" + uid: "{{ serveur_dovecot_vmail_uid }}" + group: "{{ serveur_dovecot_vmail_utilisateur }}" + system: true + home: "{{ serveur_dovecot_vmail_base }}" + create_home: false + shell: /usr/sbin/nologin + +- name: Créer le répertoire de stockage des boîtes + ansible.builtin.file: + path: "{{ serveur_dovecot_vmail_base }}" + state: directory + owner: "{{ serveur_dovecot_vmail_utilisateur }}" + group: "{{ serveur_dovecot_vmail_utilisateur }}" + mode: "0750" + +# --- Pont de certificat step_ca (déposé par client_pki) --- +- name: TLS — créer le répertoire des certificats + ansible.builtin.file: + path: "{{ serveur_dovecot_tls_dir }}" + state: directory + owner: root + group: root + mode: "0750" + when: serveur_dovecot_tls_actif | bool + +- name: TLS — déployer le script de synchronisation du certificat + ansible.builtin.template: + src: setops-dovecot-cert-sync.sh.j2 + dest: /usr/local/sbin/setops-dovecot-cert-sync + owner: root + group: root + mode: "0755" + when: serveur_dovecot_tls_actif | bool + +- name: TLS — déployer les unités de synchronisation (service + path) + ansible.builtin.template: + src: "{{ item.s }}" + dest: "{{ item.d }}" + owner: root + group: root + mode: "0644" + loop: + - { s: "setops-dovecot-cert-sync.service.j2", d: "/etc/systemd/system/setops-dovecot-cert-sync.service" } + - { s: "setops-dovecot-cert-sync.path.j2", d: "/etc/systemd/system/setops-dovecot-cert-sync.path" } + loop_control: + label: "{{ item.d | basename }}" + when: serveur_dovecot_tls_actif | bool + +- name: TLS — synchroniser le certificat maintenant + ansible.builtin.command: /usr/local/sbin/setops-dovecot-cert-sync + changed_when: false + when: + - serveur_dovecot_tls_actif | bool + - not ansible_check_mode + +- name: TLS — activer la surveillance du certificat (renouvellement) + ansible.builtin.systemd: + name: setops-dovecot-cert-sync.path + enabled: true + state: started + daemon_reload: true + when: + - serveur_dovecot_tls_actif | bool + - not ansible_check_mode + +- name: TLS — vérifier que le certificat est en place + ansible.builtin.stat: + path: "{{ serveur_dovecot_tls_dir }}/cert.pem" + register: serveur_dovecot_cert + when: serveur_dovecot_tls_actif | bool + +- name: TLS — exiger le certificat (intégration client_pki requise sur ce nœud) + ansible.builtin.assert: + that: + - serveur_dovecot_cert.stat.exists | default(false) + fail_msg: >- + Aucun certificat step_ca ({{ serveur_dovecot_tls_source_cert }}). Le nœud Dovecot doit + porter l'intégration client_pki. Déploie-la, puis relance. + when: + - serveur_dovecot_tls_actif | bool + - not ansible_check_mode + +# --- Configuration --- +- name: Détecter si Postfix est co-localisé (pour les sockets d'intégration) + ansible.builtin.command: getent passwd postfix + register: serveur_dovecot_postfix_check + changed_when: false + failed_when: false + +- name: Mémoriser la présence de Postfix + ansible.builtin.set_fact: + serveur_dovecot_postfix_present: "{{ serveur_dovecot_postfix_check.rc == 0 }}" + +- name: Déployer la configuration Set-OPS (drop-in Dovecot 2.4) + ansible.builtin.template: + src: 99-setops.conf.j2 + dest: /etc/dovecot/conf.d/99-setops.conf + owner: root + group: root + mode: "0640" + no_log: true # contient le mot de passe de bind LDAP + notify: Redémarrer dovecot + +- name: Désactiver l'auth système par défaut (LDAP est l'unique autorité) + ansible.builtin.replace: + path: /etc/dovecot/conf.d/10-auth.conf + regexp: '^!include auth-system\.conf\.ext' + replace: '#!include auth-system.conf.ext' + notify: Redémarrer dovecot + +- name: Valider la configuration Dovecot (doveconf) + ansible.builtin.command: doveconf -n + changed_when: false + when: not ansible_check_mode + +- name: Activer et démarrer Dovecot + when: not ansible_check_mode + ansible.builtin.systemd: + name: "{{ serveur_dovecot_service }}" + enabled: true + state: started + daemon_reload: true diff --git a/roles/serveur_dovecot/templates/99-setops.conf.j2 b/roles/serveur_dovecot/templates/99-setops.conf.j2 new file mode 100644 index 0000000..aaf74ec --- /dev/null +++ b/roles/serveur_dovecot/templates/99-setops.conf.j2 @@ -0,0 +1,58 @@ +# Géré par Set-OPS (rôle serveur_dovecot). Ne pas éditer à la main. Syntaxe Dovecot 2.4. +dovecot_config_version = 2.4.0 + +protocols { + imap = yes + lmtp = yes +} + +# --- Stockage Maildir (boîtes virtuelles) --- +mail_driver = maildir +mail_path = {{ serveur_dovecot_mail_path }} +first_valid_uid = {{ serveur_dovecot_vmail_uid }} +first_valid_gid = {{ serveur_dovecot_vmail_gid }} + +# --- TLS (certificat step_ca, via le pont) --- +ssl = required +ssl_server_cert_file = {{ serveur_dovecot_tls_dir }}/cert.pem +ssl_server_key_file = {{ serveur_dovecot_tls_dir }}/key.pem +ssl_min_protocol = TLSv1.2 + +# --- Auth / annuaire LDAP (serveur_openldap) --- +auth_mechanisms = plain login +ldap_uris = {{ serveur_dovecot_ldap_uris }} +ldap_base = {{ serveur_dovecot_ldap_base }} +ldap_auth_dn = {{ serveur_dovecot_ldap_bind_dn }} +ldap_auth_dn_password = {{ serveur_dovecot_ldap_bind_password }} + +passdb ldap { + ldap_filter = {{ serveur_dovecot_ldap_filter }} + ldap_bind = yes +} +userdb static { + fields { + uid = {{ serveur_dovecot_vmail_utilisateur }} + gid = {{ serveur_dovecot_vmail_utilisateur }} + home = {{ serveur_dovecot_mail_path }} + } +} + +# --- Sockets pour Postfix (remise LMTP + auth SASL) --- +# Seulement si Postfix est co-localisé (l'utilisateur postfix existe). Sinon Dovecot +# tourne en autonome (IMAP) ; les sockets seront ajoutés au déploiement de serveur_postfix. +{% if serveur_dovecot_postfix_present | default(false) %} +service lmtp { + unix_listener {{ serveur_dovecot_postfix_socket_dir }}/dovecot-lmtp { + user = postfix + group = postfix + mode = 0600 + } +} +service auth { + unix_listener {{ serveur_dovecot_postfix_socket_dir }}/auth { + user = postfix + group = postfix + mode = 0660 + } +} +{% endif %} diff --git a/roles/serveur_dovecot/templates/setops-dovecot-cert-sync.path.j2 b/roles/serveur_dovecot/templates/setops-dovecot-cert-sync.path.j2 new file mode 100644 index 0000000..1474a10 --- /dev/null +++ b/roles/serveur_dovecot/templates/setops-dovecot-cert-sync.path.j2 @@ -0,0 +1,12 @@ +# Géré par Set-OPS (rôle serveur_dovecot). Ne pas éditer à la main. +# Surveille le certificat step_ca : au renouvellement (client_pki), resynchronise vers +# Dovecot et le recharge. +[Unit] +Description=Set-OPS — surveille le certificat step_ca de Dovecot + +[Path] +PathChanged={{ serveur_dovecot_tls_source_cert }} +Unit=setops-dovecot-cert-sync.service + +[Install] +WantedBy=multi-user.target diff --git a/roles/serveur_dovecot/templates/setops-dovecot-cert-sync.service.j2 b/roles/serveur_dovecot/templates/setops-dovecot-cert-sync.service.j2 new file mode 100644 index 0000000..32e3e30 --- /dev/null +++ b/roles/serveur_dovecot/templates/setops-dovecot-cert-sync.service.j2 @@ -0,0 +1,8 @@ +# Géré par Set-OPS (rôle serveur_dovecot). Ne pas éditer à la main. +[Unit] +Description=Set-OPS — synchronise le certificat step_ca vers Dovecot +After=dovecot.service + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/setops-dovecot-cert-sync diff --git a/roles/serveur_dovecot/templates/setops-dovecot-cert-sync.sh.j2 b/roles/serveur_dovecot/templates/setops-dovecot-cert-sync.sh.j2 new file mode 100644 index 0000000..863070c --- /dev/null +++ b/roles/serveur_dovecot/templates/setops-dovecot-cert-sync.sh.j2 @@ -0,0 +1,19 @@ +#!/bin/sh +# Géré par Set-OPS (rôle serveur_dovecot). Ne pas éditer à la main. +# Synchronise le certificat step_ca (déposé par client_pki, root:root 600) vers Dovecot, +# puis recharge. Idempotent ; appelé au déploiement ET au renouvellement (unité path). +# Dovecot lit le certificat via son maître (root), donc root:root suffit. +set -eu + +SRC_CERT="{{ serveur_dovecot_tls_source_cert }}" +SRC_KEY="{{ serveur_dovecot_tls_source_cle }}" +DST="{{ serveur_dovecot_tls_dir }}" + +[ -f "$SRC_CERT" ] && [ -f "$SRC_KEY" ] || exit 0 + +install -o root -g root -m 0644 "$SRC_CERT" "$DST/cert.pem" +install -o root -g root -m 0600 "$SRC_KEY" "$DST/key.pem" + +if systemctl --quiet is-active dovecot 2>/dev/null; then + systemctl reload dovecot 2>/dev/null || systemctl restart dovecot +fi