From c391d1ed30ee895abcb1316d1ec455d64ccd76d7 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Mon, 24 Aug 2026 17:32:35 -0400 Subject: [PATCH] frontiere : rendre les flux inter-tenants, sans encore les declarer Le mot `voisins_site` etait accepte par la validation mais AUCUN generateur ne le rendait : zero regle au devis, et rien ne le signalait. Deux causes, toutes deux fermees. flux_frontiere() ne retenait que les flux `externe`. Or deux tenants de la meme fabric vivent sur des VLAN distincts, routes par la frontiere : leur trafic la traverse, donc elle doit le porter. Et le rendu manquait : une regle inter-tenant s'attache au meme lien de transit que le reste -- les tenants s'y distinguent par leur ALIAS SOURCE, pas par une interface. Ma mise en garde precedente reposait sur un modele faux. LA REGLE APPARTIENT AU SITE, pas a l'un des deux tenants : c'est son runner qui prepare le terrain, aucun ecosysteme n'ouvre de porte chez un autre. Les declarations de chainage restent RETIREES : le devis obtenu ouvrait plus que voulu -- maillage complet entre tous les caches, et un egress 3142 vers l'Internet au lieu du seul voisin. Deux raffinements a faire avant de declarer. Co-Authored-By: Claude Opus 5 --- scripts/devis_opnsense.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/scripts/devis_opnsense.py b/scripts/devis_opnsense.py index 7cfa528..05fd0f7 100644 --- a/scripts/devis_opnsense.py +++ b/scripts/devis_opnsense.py @@ -222,12 +222,24 @@ ROLE_SOCLE = "serveur_debian" def flux_frontiere(flux_par_role: dict[str, list[dict]]) -> list[tuple[str, dict]]: - """[(role, flux)] pour tout flux dont le pair inclut `externe`, trie.""" + """[(role, flux)] pour tout flux que la FRONTIERE doit porter, trie. + + Deux familles, et pas une : + + `externe` — le nord-sud : l'Internet d'un cote, un tenant de l'autre. + `voisins_site` — l'est-ouest INTER-TENANT. Deux tenants de la meme fabric vivent + sur des VLAN distincts, routes par la frontiere : leur trafic la + traverse, donc elle doit le porter. + + OUBLIER LA SECONDE, C'ETAIT DECLARER UN FLUX QUE PERSONNE N'APPLIQUE (2026-08-24). Le + mot `voisins_site` etait accepte par la validation, le devis rendait zero regle, et + rien ne le signalait — le chainage des caches n'aurait simplement jamais fonctionne. + """ retenus = [ (role, fl) for role, flux in sorted(flux_par_role.items()) for fl in flux - if "externe" in _pairs(fl) + if "externe" in _pairs(fl) or "voisins_site" in _pairs(fl) ] retenus.sort(key=lambda rf: (rf[1].get("sens", ""), rf[0], str(rf[1].get("port")))) return retenus @@ -421,6 +433,23 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict: portees.append((f"SETOPS_ADMIN_{etiquette}_GESTION", if_gestion)) if admin_par_if[etiquette]["wan"]: portees.append((f"SETOPS_ADMIN_{etiquette}_WAN", if_wan)) + elif entrant and "voisins_site" in _pairs(fl): + # UN FLUX ENTRE TENANTS DU MEME SITE. + # + # Ce n'est ni `flotte` (mon ecosysteme) ni `externe` (l'Internet) : le + # paquet vient d'un VOISIN pose sur la meme fabric. Il arrive par le meme + # lien de transit que tout le reste — les tenants ne se distinguent pas + # par une interface mais par leur ALIAS SOURCE, deja construit plus haut + # (`SETOPS_TENANT_`). + # + # LA REGLE APPARTIENT AU SITE, PAS A L'UN DES DEUX TENANTS. C'est le + # runner de site qui prepare le terrain : il configure les VNets, les + # routes et les flux pour que les plans des tenants tiennent. Aucun + # ecosysteme n'ouvre de porte chez un autre. + portees = [ + (f"SETOPS_TENANT_{p2}{n2['index']}", if_transit) + for nom2, p2, n2 in tenants if nom2 != nom + ] elif entrant: # Un service publie est joint DEPUIS INTERNET par le WAN, et le plus # souvent aussi depuis le VLAN d'administration — le poste de