diff --git a/scripts/resoudre_flux.py b/scripts/resoudre_flux.py index 35315d9..595f2e1 100644 --- a/scripts/resoudre_flux.py +++ b/scripts/resoudre_flux.py @@ -644,6 +644,22 @@ def _sources_admin_ssh_site() -> list[str]: return sorted(set(srcs)) +PAIRES_DU_SITE = {"fabric", "frontiere", "runner_site", "dns_public_site", "voisins_site"} + + +def _depend_du_site(data: dict) -> bool: + """Un groupe porte par cet ecosysteme recoit-il un flux d'une paire qui vient du site ?""" + flux = charger_flux() + for g, membres in _enfants(data).items(): + if not (membres.get("hosts") or {}): + continue + for fl in flux.get(g, []): + p = fl.get("pair") + if fl.get("sens") == "ingress" and PAIRES_DU_SITE & set(p if isinstance(p, list) else [p]): + return True + return False + + def generer_nftables(flux_par_role: dict[str, list[dict]], site: bool = False) -> list[Path]: if site: data = _inventaire_site() @@ -656,6 +672,25 @@ def generer_nftables(flux_par_role: dict[str, list[dict]], site: bool = False) - else: data = yaml.safe_load(_inventaire().read_text(encoding="utf-8")) or {} admin_ssh = _sources_admin_ssh() + # UN ECOSYSTEME QUI DEPEND DE SON SITE NE SE GENERE PAS SANS LE VOIR (2026-09-30). + # + # Le runner d'un locataire ne porte ni `underlay.yml` ni le plan du site. Les paires + # qui en dependent — `fabric`, `frontiere`, `runner_site`, `dns_public_site`, + # `voisins_site` — ne s'y resolvent a rien, et les regles sortaient AMPUTEES sans un + # mot : a la reconstruction de Technolibre, son runner a reecrit trois pare-feux + # sans le transfert de zone depuis le DNS public du site, sans la collecte de la + # fabric, sans l'insemination par le runner du site. Deployes, ils auraient coupe + # trois liens avec le site. + # + # On n'ecrit donc RIEN : les fichiers committes, generes par qui voit le site (le + # poste de l'exploitant, le runner du site), font foi — et le runner les deploie. + import underlay as underlay_mod + if _depend_du_site(data) and underlay_mod.chemin() is None: + print("note : cet ecosysteme depend de son site (fabric, frontiere, runner ou DNS " + "public du site), et le site n'est pas visible ici (aucun underlay.yml) : " + "rien n'est regenere — les regles committees font foi. Les generer depuis un " + "poste qui voit le site.", file=sys.stderr) + return [] actifs_noms = set(_hotes_du_groupe(data, "hotes_actifs")) ip_par_hote = _ip_par_hote(data) ports_plan = _ports_du_plan_site() if site else _ports_du_plan() diff --git a/scripts/tests/test_flux_sans_site.py b/scripts/tests/test_flux_sans_site.py new file mode 100644 index 0000000..466feb0 --- /dev/null +++ b/scripts/tests/test_flux_sans_site.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Sans voir son site, un ecosysteme qui en depend ne regenere pas ses pare-feux. + +POURQUOI (2026-09-30). Le runner d'un locataire ne porte ni `underlay.yml` ni le plan du +site : `make flux` y resolvait a rien les paires venues du site (`fabric`, `frontiere`, +`runner_site`, `dns_public_site`, `voisins_site`) et reecrivait des regles AMPUTEES — sans le +transfert de zone depuis le DNS public du site, sans la collecte de la fabric, sans +l'insemination. Le generateur doit s'abstenir : les fichiers committes font foi. +""" +from __future__ import annotations + +import sys +from pathlib import Path + +RACINE = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(RACINE / "scripts")) + +import resoudre_flux as rf # noqa: E402 +import underlay # noqa: E402 + + +def main() -> int: + try: + import yaml + data = yaml.safe_load(rf._inventaire().read_text(encoding="utf-8")) or {} + except Exception as e: # pas d'instance montee ici + print(f"SAUTE aucune instance lisible ({type(e).__name__})") + return 0 + if not rf._depend_du_site(data): + print("SAUTE l'instance montee ne depend pas de son site : rien a garder") + return 0 + vrai = underlay.chemin + underlay.chemin = lambda: None # un runner de locataire : le site est invisible + try: + ecrits = rf.generer_nftables(rf.charger_flux()) + finally: + underlay.chemin = vrai + if ecrits: + print(f"ECHEC site invisible, et pourtant {len(ecrits)} pare-feu(x) regeneres (amputes)") + return 1 + print("OK — site invisible : aucun pare-feu regenere, les regles committees font foi.") + return 0 + + +if __name__ == "__main__": + sys.exit(main())