Set-OPS-Public/scripts/tests/test_relais_du_site.py
Daniel Allaire 203f7c5436 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>
2026-10-12 02:30:50 -04:00

84 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""La forge du site trouve son relais de courriel, et le relais la laisse passer.
POURQUOI (2026-10-12). « Mot de passe oublie » sur la forge du site n'a jamais remis un
courriel. Deux defauts, chacun suffisant :
1. `serveur_forgejo_smtp` valait `infra-mail-01.<domaine>` en dur : la machine des boites
d'un locataire, absente du site ;
2. le relais du site (`mynetworks`) n'admettait que sa boucle locale.
"""
from __future__ import annotations
import sys
from pathlib import Path
RACINE = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(RACINE / "scripts"))
import site_inventaire as si # noqa: E402
from resoudre_flux import charger_flux # noqa: E402
def main() -> int:
echecs: list[str] = []
# 1. La forge ne nomme plus de machine : elle lit le groupe du relais.
defauts = (RACINE / "roles/serveur_forgejo/defaults/main.yml").read_text(encoding="utf-8")
ligne = next((l for l in defauts.splitlines() if l.startswith("serveur_forgejo_smtp:")), "")
bloc = defauts[defauts.find(ligne):].split("\n\n")[0]
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"}}
groupes = {"forge": {"serveur_forgejo"}, "edge": {"serveur_nginx"},
"mon": {"serveur_postfix", "serveur_icinga"}, "vieille": {"serveur_forgejo"}}
obtenu = si.reseaux_confiance_relais(serveurs, lambda n: groupes[n], charger_flux()).split()
if obtenu != ["127.0.0.0/8", "[::1]/128", "10.0.33.11/32"]:
echecs.append(f"reseaux_confiance_relais = {obtenu}, attendu boucle + la forge seule")
# 3. Sur le site monte, s'il l'est : la machine du relais admet sa forge.
try:
inv = si.inventaire()
except Exception: # noqa: BLE001 — aucun site monte : la partie pure suffit
inv = {}
hv = (inv.get("_meta") or {}).get("hostvars") or {}
relais = (inv.get("serveur_postfix") or {}).get("hosts") or []
forges = (inv.get("serveur_forgejo") or {}).get("hosts") or []
for r in relais:
confiance = str((hv.get(r) or {}).get("serveur_postfix_reseaux_confiance") or "").split()
for f in forges:
ip = f"{(hv.get(f) or {}).get('ansible_host')}/32"
if ip not in confiance:
echecs.append(f"{r} n'admet pas sa forge {f} ({ip}) : {confiance}")
for e in echecs:
print(f"ECHEC : {e}")
if not echecs:
print(f"OK : la forge lit son relais ; le relais admet ses emetteurs declares"
f"{' (site monte verifie)' if relais else ''}.")
return 1 if echecs else 0
if __name__ == "__main__":
raise SystemExit(main())