courriel du site : chiffre de bout en bout, et le relais verifie qui il joint

La forge exige STARTTLS et verifie le certificat du relais. Le relais du site
exige le TLS en entree, verifie le serveur joint en sortie (autorites
publiques, lues avant le chroot), et journalise chaque session TLS.

Mesure sur un envoi reel : TLS 1.3 sur les deux troncons, remis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-10-12 02:30:50 -04:00
parent 73fd941780
commit 203f7c5436
5 changed files with 78 additions and 3 deletions

View file

@ -1,5 +1,33 @@
# CHANGELOG — Set-OPS
## 2026-10-12 (139) — Le courriel de la forge du site est chiffré de bout en bout, et le relais vérifie qui il joint
**95 preuves.** `make verifier` passe ; `test_relais_du_site.py` vérifie aussi les réglages TLS.
Déployé au site après simulation.
Directive de Daniel : **toutes les données en transit sont chiffrées, à l'interne comme à
l'externe.** Premier flux traité : celui qu'on venait d'ouvrir.
- **La forge exige STARTTLS** (`PROTOCOL = smtp+starttls`) et vérifie le certificat du relais.
`smtp` seul remettait en clair ; Forgejo le signalait au démarrage, et ne le signale plus.
- **Le relais du site exige le TLS** en entrée (`encrypt`) et **vérifie** le serveur qu'il joint
en sortie (`verify`, autorités publiques du système). Il n'écrit qu'à quelques destinataires
connus : un MX au certificat invalide fait refuser le message plutôt que de le livrer.
`smtp_tls_CAfile` et non `CApath` : le démon d'envoi tourne en chroot ; le fichier est lu avant
d'y entrer, le dossier serait une copie que Debian refait au redémarrage.
- **Chaque session TLS s'écrit au journal** (`smtpd_tls_loglevel`, `smtp_tls_loglevel`) : ce qui se
dit chiffré se mesure.
- Le serveur de courriel **complet** d'un locataire reste en `may` : un MX public qui exige le
TLS refuse le courrier des serveurs qui ne l'offrent pas (RFC 3207). À trancher à l'Étape B.
Mesuré sur un envoi réel (« mot de passe oublié », 02:21) : forge → relais en TLS 1.3, relais →
`mx.chezlepro.ca` en TLS 1.3, remis (250). Avant `verify`, la sortie était « Untrusted » ;
`posttls-finger` en `verify` rend « Verified » pour `mx.chezlepro.ca` et Gmail.
Reste du chantier : le registre déclare 52 flux `clair` et 6 `starttls`, dont des étiquettes
antérieures au passage des métriques et des journaux en HTTPS. Chaque flux sera mesuré avant
d'être corrigé ; le plus gros écart réel attendu est edge → applications.
## 2026-10-12 (138) — La forge du site remet ses courriels
**95 preuves.** `make verifier` passe ; `test_relais_du_site.py` est nouveau, et échoue sur

View file

@ -69,7 +69,10 @@ ACCOUNT_LINKING = auto
[mailer]
ENABLED = {{ 'true' if serveur_forgejo_smtp else 'false' }}
PROTOCOL = smtp
{# STARTTLS EXIGE, CERTIFICAT VERIFIE (2026-10-12). `smtp` seul remettait en clair :
Forgejo le signalait (« insecure SMTP protocol to non-local address »). Le relais
porte un certificat de l'autorite de l'ecosysteme, que la machine reconnait. #}
PROTOCOL = smtp+starttls
SMTP_ADDR = {{ serveur_forgejo_smtp }}
SMTP_PORT = 25
FROM = forge@{{ domaine_interne }}

View file

@ -59,6 +59,26 @@ serveur_postfix_tls_source_cert: "/etc/step/certs/{{ ansible_fqdn | default(ansi
serveur_postfix_tls_source_cle: "/etc/step/certs/{{ ansible_fqdn | default(ansible_hostname) }}.key"
serveur_postfix_tls_dir: "/etc/postfix/tls"
# TOUT CE QUI TRANSITE EST CHIFFRE, A L'INTERNE COMME A L'EXTERNE (directive du 2026-10-12).
#
# Un RELAIS ne parle qu'a des emetteurs de son ecosysteme (la forge) et a des MX distants :
# il peut EXIGER le TLS des deux cotes. `may` laissait une forge sans STARTTLS remettre en
# clair, sans que rien ne le dise. Le serveur de courriel COMPLET d'un locataire reste en
# `may` pour l'instant : un MX public qui exige le TLS refuse le courrier des serveurs qui ne
# l'offrent pas (RFC 3207) — c'est une decision de l'Etape B, pas un reglage par defaut.
serveur_postfix_tls_entrant: "{{ 'encrypt' if serveur_postfix_mode == 'relais' else 'may' }}"
# EN SORTIE, LE RELAIS VERIFIE AUSSI QUI IL JOINT (2026-10-12) : `encrypt` chiffrait vers un
# serveur dont il ne verifiait pas l'identite (« Untrusted » au journal). Il n'ecrit qu'a
# quelques destinataires connus ; un MX au certificat invalide fait refuser le message au lieu
# de le livrer, et c'est voulu. Mesure avant : mx.chezlepro.ca et Gmail « Verified ».
serveur_postfix_tls_sortant: "{{ 'verify' if serveur_postfix_mode == 'relais' else 'may' }}"
# Les autorites PUBLIQUES du systeme : celles des MX de l'Internet, pas celle de l'ecosysteme.
# UN FICHIER, PAS UN DOSSIER : `smtp` tourne en chroot ; `CAfile` est lu AVANT d'y entrer,
# `CApath` dedans — sur une copie que Debian refait au redemarrage, et qui vieillit entre deux.
serveur_postfix_tls_ca_publiques: "/etc/ssl/certs/ca-certificates.crt"
# Une ligne par session TLS au journal : ce qui se dit chiffre doit pouvoir se MESURER.
serveur_postfix_tls_journal: 1
# Zero-confiance : verifier le TLS de la livraison LMTP vers Dovecot (cert step-ca).
serveur_postfix_lmtp_tls_verify: false
serveur_postfix_lmtp_tls_cafile: "/etc/step/certs/root_ca.crt"

View file

@ -39,9 +39,14 @@ lmtp_host_lookup = native
# --- TLS (certificat step_ca) ---
smtpd_tls_cert_file = {{ serveur_postfix_tls_dir }}/cert.pem
smtpd_tls_key_file = {{ serveur_postfix_tls_dir }}/key.pem
smtpd_tls_security_level = may
smtpd_tls_security_level = {{ serveur_postfix_tls_entrant }}
smtpd_tls_protocols = >=TLSv1.2, !SSLv2, !SSLv3
smtp_tls_security_level = may
smtpd_tls_mandatory_protocols = >=TLSv1.2
smtpd_tls_loglevel = {{ serveur_postfix_tls_journal }}
smtp_tls_security_level = {{ serveur_postfix_tls_sortant }}
smtp_tls_CAfile = {{ serveur_postfix_tls_ca_publiques }}
smtp_tls_mandatory_protocols = >=TLSv1.2
smtp_tls_loglevel = {{ serveur_postfix_tls_journal }}
{% if serveur_postfix_lmtp_tls_verify | default(false) %}
# Zero-confiance : livraison LMTP vers Dovecot chiffree ET verifiee (cert step-ca).
# `wrappermode` = TLS des la connexion : c'est ce que sait offrir un `inet_listener`

View file

@ -29,6 +29,25 @@ def main() -> int:
if "infra-mail-01" in bloc or "groups['serveur_postfix']" not in bloc:
echecs.append("serveur_forgejo_smtp ne derive pas du groupe `serveur_postfix`")
# 1b. Tout ce qui transite est chiffre (directive du 2026-10-12) : la forge exige STARTTLS,
# le relais exige le TLS des deux cotes et le journalise.
ini = (RACINE / "roles/serveur_forgejo/templates/app.ini.j2").read_text(encoding="utf-8")
if "PROTOCOL = smtp+starttls" not in ini:
echecs.append("la forge n'exige pas STARTTLS (`PROTOCOL = smtp+starttls`)")
for cle, niveau in (("serveur_postfix_tls_entrant", "encrypt"),
("serveur_postfix_tls_sortant", "verify")):
ligne = next((l for l in (RACINE / "roles/serveur_postfix/defaults/main.yml")
.read_text(encoding="utf-8").splitlines() if l.startswith(cle + ":")), "")
if f"'{niveau}' if serveur_postfix_mode == 'relais'" not in ligne:
echecs.append(f"{cle} : un relais n'exige pas `{niveau}`")
gabarit = (RACINE / "roles/serveur_postfix/templates/main.cf.j2").read_text(encoding="utf-8")
for attendu in ("smtpd_tls_security_level = {{ serveur_postfix_tls_entrant }}",
"smtp_tls_security_level = {{ serveur_postfix_tls_sortant }}",
"smtpd_tls_loglevel = {{ serveur_postfix_tls_journal }}",
"smtp_tls_CAfile = {{ serveur_postfix_tls_ca_publiques }}"):
if attendu not in gabarit:
echecs.append(f"main.cf.j2 ne porte pas `{attendu}`")
# 2. Le relais admet les emetteurs declares, et eux seuls (pur).
serveurs = {"forge": {"ip": "10.0.33.11"}, "edge": {"ip": "10.0.37.11"},
"mon": {"ip": "10.0.36.11"}, "vieille": {"ip": "10.0.33.99", "etat": "retire"}}