Set-OPS-Public/docs/courriel-conception.md

220 lines
12 KiB
Markdown
Raw Normal View History

# Conception — Service de courriel souverain (Chezlepro)
preuve : P34 — chaque document declare son lecteur (D-74) La refonte de ce matin posait une convention. Une convention qu'on n'outille pas tient tant que quelqu'un y pense : c'est le raisonnement de D-70, applique au corpus documentaire. Etat de depart mesure : 2 documents sur 34 declaraient leur lecteur. Les 32 autres disaient leur SUJET — ce qui avait enfoui le runbook de reprise le plus utile du depot au §6 de autorisation.md. Les 38 le declarent desormais, lecteur determine document par document et non colle au gabarit : l'exploitant (devis, migration de tenant, cycle de vie, gabarit d'or), le mainteneur (conceptions, registres, carte), le lecteur externe (ecosysteme-chezlepro), l'agent IA (MISE-A-JOUR-CODEX-CLAUDE). Deux exemptions DERIVEES, pas listees — un chemin en dur aurait vieilli a la premiere page ajoutee : un document qui s'annonce genere, et un fragment sans titre. Les 13 exemptes verifies un par un ; aucun document ecrit a la main n'est exempte par accident. La preuve ne lit que l'EN-TETE, ce qui empeche frontiere-opnsense.md et plan-et-generation.md — qui parlent de generation dans leur corps — d'etre exemptes a tort. Eprouvee dans les deux sens. Elle a echoue seule des sa premiere execution en nommant deux documents que mon inventaire avait manques (docs/audit/). Puis test negatif delibere : declaration retiree de meta-classe.md -> ECHEC la nommant ; restauree -> OK. Ce qu'elle ne teste pas : que le lecteur declare soit le BON. Ca se juge en revue ; elle garantit qu'on a du y penser. P01–P34. Comptes perimes corriges au passage (AGENTS.md et devis-services.md annoncaient encore 30 preuves). Verifie : prouver.py 0 (34 OK), plan-recette inchange. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 07:53:04 -04:00
> **Pour qui :** le **mainteneur** du service de courriel.
> **Statut : CONCEPTION (cadrage).** Aucun rôle n'est encore écrit. Ce document fixe
> les décisions, les prérequis et la topologie avant toute implémentation.
## 1. Décisions arrêtées
| Sujet | Décision |
|---|---|
| Périmètre | **Vrai service de courriel** (boîtes utilisateurs, réception publique, IMAP) — pas un simple relais sortant. |
| Modèle | **Full self-host** : MX entrant **et** sortant chez Chezlepro, réputation d'IP propre. |
| Stack | **Postfix (MTA) + Dovecot (IMAP/LMTP) + rspamd (antispam/DKIM)** — la stack libre **mature**, éprouvée depuis 20+ ans, **entièrement configurable par fichiers**. Trois rôles Set-OPS (`serveur_postfix`, `serveur_dovecot`, `serveur_rspamd`), comme on configure nginx/powerdns. |
| Identité | Boîtes/utilisateurs dans l'**OpenLDAP** existant (Postfix + Dovecot → LDAP). Pas de comptes en double. |
| DNS | Enregistrements publics dans la zone `chezlepro.ca` ; noms internes dans **PowerDNS**. |
| PKI | **Deux CA distinctes** (voir §6) : Let's Encrypt pour les faces publiques, **step_ca** pour l'interne. |
| Démarche | **Deux étapes** (voir §11) : **(A) fonctionnement INTERNE** d'abord (step_ca, LDAP, SMTP/IMAP internes, zéro dépendance publique) ; **(B) fonctionnement EXTERNE** ensuite (Namespro, Let's Encrypt, reprise du MX `.53`). |
Le courrier de **notification système** des nœuds (cron, alertes) est relayé vers le MTA
`serveur_postfix` (edge-mta) via `client_smtp`/msmtp. C'est distinct de ce service de boîtes
et ne le remplace pas. *(Note : l'ancien rôle `serveur_sendmail` a été retiré — supersédé par
Postfix.)*
> **Historique — pivot depuis Stalwart (2026-07-02).** On avait d'abord choisi Stalwart
> (binaire unique, moderne). Le prototypage a révélé un projet **jeune et volatil** :
> modèle de config **cassé entre 0.15 et 0.16**, outil IaC `stalwart config apply`
> **annoncé mais non livré** dans le binaire, API REST supprimée au profit de JMAP,
> backlog d'issues/PR important. Pour un **pilier critique souverain censé durer**, c'est
> trop précaire. On pivote vers la stack **mature Postfix/Dovecot/rspamd**, qui a en prime
> l'avantage d'être **100 % déclarable par fichiers** — donc parfaitement alignée avec le
> modèle Set-OPS (déclaratif, idempotent, exploitable sans IA). La Phase 1 Stalwart
> (rôle `serveur_stalwart`) est **retirée** ; git en garde la trace. Stalwart à réévaluer
> dans ~2 ans, une fois mûri et son `config apply` livré.
## 2. Prérequis IP publique — VÉRIFIÉ (recon DNS, 2026-07-02)
Le full self-host sortant exige un **PTR propre, forward-confirmed, hors blocklist**.
C'est **déjà en place** pour le MX primaire de Chezlepro :
- **`mx.chezlepro.ca = 69.70.26.53`** ; PTR `→ mx.chezlepro.ca` ; **FCrDNS aller-retour OK**.
- Bloc **`69.70.26.x` (Videotron statique)** contrôlé par Chezlepro ; PTR custom **obtenable**
(déjà fait pour `.53`).
**La condition dure de délivrabilité est levée pour le primaire.** Le full self-host n'est
pas un pari : c'est **reprendre `.53` en préservant sa réputation**.
Réserves :
- **`laposte-01 = 69.70.26.55`** (MX secours) a encore le **PTR Videotron par défaut**
(`modemcable055…`), pas `laposte-01.chezlepro.ca` — à corriger s'il doit **émettre**.
- **DKIM** : le sélecteur `default._domainkey` est **vide** publiquement — à **(re)poser**.
### État de la prod Chezlepro (recon)
| Élément | Réalité |
|---|---|
| DNS public | **Namespro** (`htns1/2/3.namespro.ca`) — **pas** PowerDNS. PowerDNS = interne uniquement. |
| Bloc IP | `69.70.26.x` (Videotron statique) : `.51` apex, `.53` mx, `.55` laposte-01, `.59` git. |
| Mail existant | MX `mx → .53` déjà en service ; SPF `-all` **correct** ; DMARC `quarantine` **correct**. |
| Vue interne | split-horizon (NS `delaviorne.net`, IP `10.10.x`/`192.168.14.x`) — ne pas confondre avec le public. |
> **Conséquence** : le service mail n'est pas greenfield. Il **reprend/modernise** l'existant
> (MX `.53`), il ne le recrée pas.
## 3. Topologie (2-3 nœuds)
```
INTERNET
DNS public chezlepro.ca : MX 10 → mail1 , MX 20 → mail2
┌───────────────┴───────────────┐
▼ ▼
┌────────────────────┐ ┌───────────────────┐
│ mail-01 (PRIMAIRE) │ │ mail-02 (MX secours)│
│ Postfix (MTA) │◄──relais──│ Postfix (relais) │
│ SMTP 25 / 587 / 465│ quand │ SMTP 25 seulement │
│ Dovecot (IMAP 993 │ mail-01 │ file d'attente puis │
│ + LMTP + Sieve) │ est down │ transfert à mail-01 │
│ rspamd (spam + DKIM)│ └───────────────────┘
│ BOÎTES stockées │
└─────────┬──────────┘
│ (réseau interne, mTLS step_ca)
┌───────┼─────────────┬──────────────┐
▼ ▼ ▼ ▼
OpenLDAP step_ca PowerDNS PostgreSQL
(identité)(certs int.)(DNS interne)(métadonnées, option)
```
> **Topologie révisée (2026-07-02) — MTA dédié en périphérie, boîtes à l'intérieur.** Le
> schéma ci-dessus (tout-en-un) est **remplacé** par le modèle ci-dessous.
```
Internet ─▶ edge-mta : Postfix + rspamd (EXPOSÉ, aucune boîte)
│ LMTP (remise) + SASL (auth) — réseau, mTLS step_ca
mail-store : Dovecot IMAP + boîtes (INTERNE, non exposé)
OpenLDAP · step_ca · PowerDNS
```
- **edge-mta (primaire)** — **Postfix** (`:25`, soumission `:587`/`:465`) + **rspamd**
(anti-spam, **DKIM**, greylisting). **Exposé à Internet ; ne stocke aucune boîte.** Auth des
soumissions déléguée à Dovecot (SASL réseau) ; remise via **LMTP réseau** vers `mail-store`.
- **mail-store (interne)** — **Dovecot** : IMAP `:993`, boîtes (Maildir), Sieve, **auth LDAP**.
**Non exposé au public** ; ne parle qu'à `edge-mta` (LMTP + SASL) et aux clients IMAP
internes. Expose LMTP + auth **sur le réseau, chiffré par step_ca**.
- **mta2 (MX de secours)** — **Postfix** relais, `:25` seulement ; file puis transfert quand
`edge-mta` est down ; idéalement sur un **lien/IP indépendant**.
**Séparation clé** : le MTA (surface la plus exposée) est isolé des **boîtes** (données
sensibles) ; le lien Postfix→Dovecot passe en **réseau chiffré par step_ca** — réutilise le mTLS.
**Décisions ouvertes** : emplacement du MX secours ; edge-mta co-localisé avec l'edge web ou non.
## 4. Stockage
- **v1** : stockage **embarqué (RocksDB) sur mail-01** + **sauvegardes rigoureuses**.
Simple et souverain. Le MX secours protège l'**entrée**, pas les **boîtes** → la
résilience des données repose sur les sauvegardes.
- **HA ultérieure** : métadonnées dans **PostgreSQL** (pilier existant) + blobs sur
stockage partagé, pour un primaire plus stateless.
## 5. Flux
- **Entrant** : expéditeur → MX DNS → mail-01 `:25` (ou mail-02 si down) → contrôles
**SPF/DKIM/DMARC** + anti-spam → livraison à la boîte (utilisateur résolu via **LDAP**).
- **Sortant** : client authentifié (LDAP) → soumission `:587`**signature DKIM**
remise directe MX vers le serveur destinataire `:25` *(← PTR + réputation décisifs)*.
- **Accès client** : client mail → IMAP `:993` / JMAP `:443` → auth **LDAP** → boîte.
## 6. TLS — deux CA distinctes (à ne pas confondre)
| Face | CA | Pourquoi |
|---|---|---|
| **Publique** (`25/465/587/993/443` vers le monde) | **Let's Encrypt** (ACME intégré à Stalwart) | Les serveurs et clients distants valident contre les **racines publiques** ; ils ne connaissent pas l'AC interne. |
| **Interne** (mail ↔ LDAP, ↔ PostgreSQL, ↔ MX secours) | **step_ca** | Le mTLS souverain déjà en place pour les liaisons internes. |
## 7. Pare-feu (nftables)
Le socle installe nftables **désactivé**. Les nœuds mail sont un des endroits où on
l'**active** avec des règles adaptées au rôle :
- **Ouvert au monde** : `25`, `465`, `587`, `993`, `443`, `80` (ACME).
- **Interne seulement** : LDAP, PostgreSQL, interface d'admin Stalwart.
## 8. Enregistrements DNS publics (`chezlepro.ca`)
> **Gérés chez Namespro** (registrar/DNS public), **PAS dans PowerDNS** (qui ne sert que
> l'interne). Un **MX** (`mx → .53`), un **SPF** (`-all`, correct) et un **DMARC**
> (`quarantine`) existent **déjà** — à **reprendre/ajuster**, pas à recréer. Le **DKIM**
> (`default._domainkey` vide) est à **(re)poser**.
```
mail1 A <IP publique mail-01>
mail2 A <IP publique mail-02>
@ MX 10 mail1.chezlepro.ca.
@ MX 20 mail2.chezlepro.ca.
@ TXT "v=spf1 mx -all"
default._domainkey TXT "v=DKIM1; k=rsa; p=<clé publique DKIM Stalwart>"
_dmarc TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@chezlepro.ca"
```
+ **PTR** (reverse) : `<IP mail-01>``mail1.chezlepro.ca` (chez le propriétaire de l'IP).
+ **MTA-STS / TLS-RPT** : optionnels, recommandés.
## 9. Traduction Set-OPS
- **Nomenclature** : 2 fonctions, p. ex. `comm-mail` (primaire) + `comm-mx2` (secours) ;
VMID / IP / VLAN dérivés comme le reste.
- **Rôles** (config 100 % par fichiers, comme nginx/powerdns) :
- `serveur_postfix` — MTA : `main.cf`/`master.cf`, TLS step_ca, cartes LDAP (domaines,
boîtes, alias), remise LMTP vers Dovecot, soumission `:587`/`:465`, milter rspamd.
- `serveur_dovecot` — IMAP `:993` + LMTP + Sieve, stockage des boîtes, **auth SASL** (pour
la soumission Postfix), backend **LDAP**, TLS step_ca.
- `serveur_rspamd` — anti-spam, **signature DKIM**, greylisting ; branché à Postfix par milter.
- `serveur_postfix` en **mode relais** (paramètre) pour le MX de secours.
- **Intégrations** des nœuds mail : `client_pki` (certs internes), `client_backup`,
`client_metrique` / `client_journal` (observabilité). L'annuaire est résolu par
`resoudre_annuaire` (côté rôle), pas une intégration OS.
- **Secrets (voûte)** : bind LDAP, **clé privée DKIM**, éventuel secret ACME (Étape B) —
via `vault_*`, câblés aux rôles.
## 10. Décisions
-**IP publique + PTR** — TRANCHÉ : `.53` FCrDNS OK, bloc Videotron contrôlé (§2).
-**Emplacement du MX de secours** (`.55` existe ; lien/PTR à cadrer) (§3).
-**Stockage** : embarqué v1 vs PostgreSQL dès le départ (§4).
-**Domaine(s)** desservi(s) : `chezlepro.ca` seul, ou multi-domaines (tenants) ?
-**Reprise du MX existant** : bascule directe de `.53` vers la nouvelle stack, ou cohabitation transitoire ?
## 11. Démarche en deux étapes
### Étape A — Fonctionnement INTERNE (d'abord, sans risque)
But : prouver **toute la pile en interne**, en **code de prod**, dans le bac à sable.
Aucune dépendance au public.
1. **Pilier identité** : `serveur_openldap` (TLS **step_ca**) — ✅ **déployé et prouvé** en bac à sable.
2. **`serveur_postfix` + `serveur_dovecot` + `serveur_rspamd`** sur `mail-01` : TLS **step_ca**,
annuaire/auth **LDAP**, DKIM interne, nftables mail.
3. **Prouver** : réception → boîte → accès **IMAP** → envoi **intra-écosystème**, le tout en
TLS interne, auth LDAP.
*(Étape actuelle : identité OK ; on démarre `serveur_postfix`.)*
### Étape B — Fonctionnement EXTERNE (transition prod, plus tard)
But : brancher sur le monde **en reprenant l'existant** (voir §2).
1. Basculer le TLS des faces publiques sur **Let's Encrypt** (ACME).
2. **Reprendre `mx.chezlepro.ca` (`.53`)** — préserve PTR/réputation déjà acquis.
3. Mettre à jour les enregistrements **chez Namespro** : MX, **DKIM** (nouveau sélecteur),
vérifier SPF/DMARC.
4. **MX secours `.55`** : PTR custom Videotron s'il doit émettre ; sinon réception/queue seulement.
5. Tests bout en bout : envoi vers Gmail/Outlook, **SPF/DKIM/DMARC verts**, bascule MX secours.
---
*Ce document est un cadrage vivant : il évolue à mesure que les décisions ouvertes se
tranchent. Il ne décrit pas encore de code livré.*