serveur_rspamd (rspamd 3.x) : antispam + DKIM en milter Postfix — prouvé

Rôle antispam sur edge-mta : Redis local, worker proxy en mode milter
auto-scan (:11332), signature DKIM sortante (clé générée idempotente,
enregistrement DNS affiché pour l'Étape B). Config par local.d/.
Postfix branché via smtpd_milters (option serveur_postfix_rspamd_milter,
milter_default_action=accept = tolérant si rspamd down).

Prouvé : courriel traversant le milter → scanné (rspamc stat: 1) +
signé DKIM (DKIM-Signature d=...), livré, lu en IMAP.
Étape A (courriel interne) complète : dovecot + postfix + rspamd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-07-02 18:04:44 -04:00
parent 5846a08667
commit 8df1db3b47
11 changed files with 173 additions and 0 deletions

View file

@ -19,6 +19,14 @@
mis à jour. Réévaluer Stalwart ~2028.
### Ajouté
- **Rôle `serveur_rspamd` (rspamd 3.x) — antispam + DKIM, en milter sur Postfix.** Installé
sur le nœud edge-mta (avec Postfix), backend **Redis** local, worker proxy en **mode milter
auto-scan** (`:11332`), **signature DKIM** sortante (clé générée par le rôle de façon
idempotente ; enregistrement DNS public affiché pour l'Étape B). Config par surcharges
`/etc/rspamd/local.d/`. Postfix branché via `smtpd_milters` (option `serveur_postfix_rspamd_milter`,
`milter_default_action = accept` → tolérant si rspamd indisponible). **Prouvé** : un courriel
traversant le milter ressort **scanné** (`rspamc stat` : 1) et **signé DKIM** (`DKIM-Signature:
d=…`), puis livré et lu en IMAP. **Étape A (courriel interne) complète** : dovecot + postfix + rspamd.
- **Flux courriel interne PROUVÉ de bout en bout (Étape A).** Envoi → Postfix (`edge-mta`,
validation LDAP) → LMTP réseau → Dovecot (mail-store) → boîte Maildir → **lu en IMAP**
(auth LDAP, TLS step_ca) : `status=sent`, message lu (sujet + corps). Réglages Dovecot 2.4

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_rspamd
hosts: serveur_rspamd
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_rspamd

View file

@ -24,6 +24,10 @@ serveur_postfix_ldap_base: "ou=people,dc={{ domaine_interne.split('.') | join(',
serveur_postfix_ldap_bind_dn: "cn=admin,dc={{ domaine_interne.split('.') | join(',dc=') }}"
serveur_postfix_ldap_bind_password: "{{ vault_openldap_admin | default('') }}"
# --- Milter rspamd (antispam + DKIM) — si rspamd est co-localisé (nœud edge-mta) ---
serveur_postfix_rspamd_milter: false
serveur_postfix_rspamd_milter_port: 11332
# --- TLS via le certificat d'hôte step_ca (client_pki) ---
serveur_postfix_tls_actif: true
serveur_postfix_tls_source_cert: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.crt"

View file

@ -31,6 +31,14 @@ smtpd_tls_protocols = >=TLSv1.2, !SSLv2, !SSLv3
smtp_tls_security_level = may
{% endif %}
{% if serveur_postfix_rspamd_milter %}
# --- Milter rspamd (antispam + signature DKIM), co-localisé sur edge-mta ---
smtpd_milters = inet:localhost:{{ serveur_postfix_rspamd_milter_port }}
non_smtpd_milters = $smtpd_milters
milter_protocol = 6
milter_default_action = accept
{% endif %}
# --- Restrictions de base ---
smtpd_helo_required = yes
smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination

View file

@ -0,0 +1,23 @@
---
# rspamd 3.x (Debian 13) — antispam + signature DKIM, branché en MILTER sur Postfix
# (nœud edge-mta). Backend Redis local. Config par surcharges dans /etc/rspamd/local.d/.
# Cf. docs/courriel-conception.md. NE STOCKE AUCUNE BOÎTE.
serveur_rspamd_paquets:
- rspamd
- redis-server
serveur_rspamd_service: "rspamd"
# --- Backend Redis (local à ce nœud) ---
serveur_rspamd_redis_serveur: "127.0.0.1:6379"
# --- Milter (worker proxy, auto-scan) — Postfix s'y branche via :11332 ---
serveur_rspamd_milter_port: 11332
# --- Signature DKIM ---
# Domaine signé : interne en bac à sable ; domaine PUBLIC en prod (Étape B).
serveur_rspamd_dkim_actif: true
serveur_rspamd_dkim_domaine: "{{ domaine_interne }}"
serveur_rspamd_dkim_selecteur: "setops"
serveur_rspamd_dkim_repertoire: "/var/lib/rspamd/dkim"

View file

@ -0,0 +1,6 @@
---
- name: Redémarrer rspamd
ansible.builtin.systemd:
name: "{{ serveur_rspamd_service }}"
state: restarted
when: not ansible_check_mode

View file

@ -0,0 +1,7 @@
---
# Empreinte ressources du logiciel — dimensionnement VM derive (scripts/instancier.py).
# rspamd + Redis (backend). Sommee au socle SE + marge.
setops_empreinte:
coeurs: 1
memoire_mo: 1024
disque_go: 4

View file

@ -0,0 +1,82 @@
---
- name: Installer rspamd et Redis
ansible.builtin.apt:
name: "{{ serveur_rspamd_paquets }}"
state: present
update_cache: true
- name: Activer et démarrer Redis (backend rspamd)
ansible.builtin.systemd:
name: redis-server
enabled: true
state: started
when: not ansible_check_mode
# --- Clé DKIM (privée locale ; publique à publier en DNS — Étape B) ---
- name: Créer le répertoire des clés DKIM
ansible.builtin.file:
path: "{{ serveur_rspamd_dkim_repertoire }}"
state: directory
owner: _rspamd
group: _rspamd
mode: "0750"
when: serveur_rspamd_dkim_actif | bool
- name: Générer la clé DKIM (idempotent — seulement si absente)
ansible.builtin.command:
cmd: >-
rspamadm dkim_keygen
-s {{ serveur_rspamd_dkim_selecteur }}
-d {{ serveur_rspamd_dkim_domaine }}
-k {{ serveur_rspamd_dkim_repertoire }}/{{ serveur_rspamd_dkim_domaine }}.{{ serveur_rspamd_dkim_selecteur }}.key
creates: "{{ serveur_rspamd_dkim_repertoire }}/{{ serveur_rspamd_dkim_domaine }}.{{ serveur_rspamd_dkim_selecteur }}.key"
register: serveur_rspamd_dkim_gen
when:
- serveur_rspamd_dkim_actif | bool
- not ansible_check_mode
notify: Redémarrer rspamd
- name: Fixer les droits de la clé DKIM privée
ansible.builtin.file:
path: "{{ serveur_rspamd_dkim_repertoire }}/{{ serveur_rspamd_dkim_domaine }}.{{ serveur_rspamd_dkim_selecteur }}.key"
owner: _rspamd
group: _rspamd
mode: "0640"
when:
- serveur_rspamd_dkim_actif | bool
- not ansible_check_mode
- name: Afficher l'enregistrement DNS DKIM à publier (Étape B)
ansible.builtin.debug:
msg: "{{ serveur_rspamd_dkim_gen.stdout_lines | default(['(clé déjà présente — cf. ' + serveur_rspamd_dkim_repertoire + ')']) }}"
when: serveur_rspamd_dkim_actif | bool
# --- Surcharges de configuration (local.d) ---
- name: Déployer les surcharges local.d (redis, proxy milter)
ansible.builtin.template:
src: "{{ item }}.j2"
dest: "/etc/rspamd/local.d/{{ item }}"
owner: root
group: root
mode: "0644"
loop:
- redis.conf
- worker-proxy.inc
notify: Redémarrer rspamd
- name: Déployer la signature DKIM (local.d)
ansible.builtin.template:
src: dkim_signing.conf.j2
dest: /etc/rspamd/local.d/dkim_signing.conf
owner: root
group: root
mode: "0644"
when: serveur_rspamd_dkim_actif | bool
notify: Redémarrer rspamd
- name: Activer et démarrer rspamd
when: not ansible_check_mode
ansible.builtin.systemd:
name: "{{ serveur_rspamd_service }}"
enabled: true
state: started

View file

@ -0,0 +1,9 @@
# Géré par Set-OPS (rôle serveur_rspamd). Ne pas éditer à la main.
# Signature DKIM du courrier sortant. La clé privée est générée par le rôle ;
# la clé PUBLIQUE doit être publiée en DNS (enregistrement TXT) — Étape B pour le public.
enabled = true;
selector = "{{ serveur_rspamd_dkim_selecteur }}";
path = "{{ serveur_rspamd_dkim_repertoire }}/$domain.$selector.key";
use_domain = "header";
allow_hdrfrom_mismatch = true;
allow_username_mismatch = true;

View file

@ -0,0 +1,3 @@
# Géré par Set-OPS (rôle serveur_rspamd). Ne pas éditer à la main.
# Backend Redis (statistiques, greylisting, ratelimit, etc.).
servers = "{{ serveur_rspamd_redis_serveur }}";

View file

@ -0,0 +1,8 @@
# Géré par Set-OPS (rôle serveur_rspamd). Ne pas éditer à la main.
# Worker proxy en mode MILTER auto-scan : Postfix s'y branche (inet:localhost:{{ serveur_rspamd_milter_port }}).
milter = yes;
timeout = 120s;
upstream "local" {
default = yes;
self_scan = yes;
}