serveur_dovecot (Dovecot 2.4) : IMAP + auth LDAP + TLS step_ca — prouvé

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 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-07-02 15:09:37 -04:00
parent f3e78c55e1
commit 7549c61e75
10 changed files with 316 additions and 0 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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