#!/usr/bin/env python3 """Aucune sortie vers Internet n'est donnee a TOUTE machine, et le site lit les conditions. POURQUOI (2026-10-10). Le socle donnait a chaque machine une sortie 80/443 vers tout Internet, « pour apt ». Apt passait deja par le cache du site : la sortie ne servait plus qu'a ce qu'aucun role ne declarait — telemetrie de Grafana, d'Alloy et de Loki, verification de version de Collabora, cartes de rspamd. Elle est devenue conditionnelle (`seulement_si: artefacts_amorcage` vide), et deux defauts sont apparus : 1. un role porte par toute machine (le socle, les `client_*`) peut rouvrir Internet a toute la flotte sans que rien ne le signale ; 2. le devis de la frontiere n'evaluait `seulement_si` que chez les locataires : au site, tout flux conditionnel etait emis. `make flux` retirait la sortie des machines du site, la frontiere la gardait. """ from __future__ import annotations import re import sys from pathlib import Path RACINE = Path(__file__).resolve().parents[2] sys.path.insert(0, str(RACINE / "scripts")) import devis_opnsense as dv # noqa: E402 from resoudre_flux import charger_flux # noqa: E402 UNIVERSELS = {"serveur_debian", "serveur_durci"} def main() -> int: echecs: list[str] = [] flux = charger_flux() # 1. Les roles universels n'ouvrent pas Internet sans condition (hors ICMP du PMTUD). for role, liste in flux.items(): if role not in UNIVERSELS and not role.startswith("client_"): continue for fl in liste: pairs = fl.get("pair") if isinstance(fl.get("pair"), list) else [fl.get("pair")] if (fl.get("sens") == "egress" and "externe" in pairs and str(fl.get("protocole")).lower() in ("tcp", "udp") and not fl.get("seulement_si")): echecs.append(f"{role} : sortie {fl.get('protocole')}/{fl.get('port')} vers " f"`externe` sans condition — donnee a TOUTE machine") # 2. L'evaluation au site, sur des variables d'hote connues. socle = [fl for fl in flux.get("serveur_debian", []) if fl.get("sens") == "egress" and fl.get("seulement_si") and (fl.get("seulement_si") or {}).get("variable") == "artefacts_amorcage"] if not socle: echecs.append("serveur_debian : la sortie apt n'est plus conditionnee a `artefacts_amorcage`") else: fl = socle[0] dv._HOSTVARS_SITE = {"avec": {"artefacts_amorcage": "10.0.0.1:3142"}, "sans": {}} attendus = [(["avec"], False), (["sans"], True), (["avec", "sans"], True)] for machines, attendu in attendus: obtenu = dv.applicable_au_site(fl, "serveur_debian", machines) if obtenu is not attendu: echecs.append(f"applicable_au_site({machines}) = {obtenu}, attendu {attendu}") dv._HOSTVARS_SITE = {} if dv.applicable_au_site(fl, "serveur_debian", ["avec"]) is not True: echecs.append("inventaire du site illisible : la regle doit rester emise (statu quo)") dv._HOSTVARS_SITE = None # 3. La boucle du site appelle bien l'evaluation, avant tout rendu de regle. source = (RACINE / "scripts" / "devis_opnsense.py").read_text(encoding="utf-8") boucle = re.search(r"for _fl in flux_par_role\.get\(_role, \[\]\):(.{0,600})", source, re.S) if not boucle or "applicable_au_site(" not in boucle.group(1): echecs.append("devis_opnsense : la boucle des flux du site n'evalue plus `seulement_si`") for e in echecs: print(f"ECHEC {e}") if echecs: return 1 print("OK — aucun role universel n'ouvre Internet ; le site lit les conditions.") return 0 if __name__ == "__main__": sys.exit(main())