From 203f7c543690ad98c9178405e38ca2d22af13a52 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Mon, 12 Oct 2026 02:30:50 -0400 Subject: [PATCH] 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 --- CHANGELOG.md | 28 ++++++++++++++++++++++ roles/serveur_forgejo/templates/app.ini.j2 | 5 +++- roles/serveur_postfix/defaults/main.yml | 20 ++++++++++++++++ roles/serveur_postfix/templates/main.cf.j2 | 9 +++++-- scripts/tests/test_relais_du_site.py | 19 +++++++++++++++ 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2051ec..63d3042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/roles/serveur_forgejo/templates/app.ini.j2 b/roles/serveur_forgejo/templates/app.ini.j2 index ac0d49c..9395401 100644 --- a/roles/serveur_forgejo/templates/app.ini.j2 +++ b/roles/serveur_forgejo/templates/app.ini.j2 @@ -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 }} diff --git a/roles/serveur_postfix/defaults/main.yml b/roles/serveur_postfix/defaults/main.yml index bc45287..6a12327 100644 --- a/roles/serveur_postfix/defaults/main.yml +++ b/roles/serveur_postfix/defaults/main.yml @@ -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" diff --git a/roles/serveur_postfix/templates/main.cf.j2 b/roles/serveur_postfix/templates/main.cf.j2 index 78531ef..5eee101 100644 --- a/roles/serveur_postfix/templates/main.cf.j2 +++ b/roles/serveur_postfix/templates/main.cf.j2 @@ -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` diff --git a/scripts/tests/test_relais_du_site.py b/scripts/tests/test_relais_du_site.py index 99ed935..518ffb7 100644 --- a/scripts/tests/test_relais_du_site.py +++ b/scripts/tests/test_relais_du_site.py @@ -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"}}