From 78f2f7fd6fc41d10759f28e49721ea6ef6414310 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sun, 9 Aug 2026 16:01:24 -0400 Subject: [PATCH] SDN : un puits sur le supernet du tenant dans son propre VRF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit « Je ne trouve pas ca normal » — l'exploitant avait raison. Pendant deux jours nous avons attribue ce comportement a l'anti-usurpation de la frontiere, et je l'avais consigne comme tel. Mesure depuis DEUX points de vue : depuis le poste, quatre connexions sur quatre s'etablissaient ; depuis l'interieur du tenant, le comportement etait correct partout ou un VNet existe. L'anomalie ne touchait que les portions de supernet sans VNet. Cause : vrf_t17 portait les six /24 et un defaut, RIEN pour le reste du /16. Une adresse non attribuee sortait du VRF, atteignait la frontiere, revenait a l'hyperviseur dans la table PRINCIPALE — pas dans le VRF — et repartait vers 192.168.11.254, la passerelle du reseau d'ADMINISTRATION. strophe_frr pose desormais un puits sur le supernet du tenant, moins specifique que ses /24 donc invisible au trafic legitime. Derive du seed. Verifie : depuis le tenant, 10.27.99.99 et 10.27.18.99 refusees, 10.27.18.21 etablie. Une machine du tenant ne peut plus atteindre le reseau de gestion par une faute de frappe. Reste, sans arbitrage : depuis le VLAN d'administration le connect() reussit encore, ce trafic n'entrant jamais dans le VRF. Deux remedes possibles, dont l'un touche la table qui porte l'administration des hyperviseurs. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++++++++++++++ scripts/devis_sdn.py | 18 ++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 109c46d..a7a5d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,53 @@ # CHANGELOG — Set-OPS +## 2026-08-09 — Le `connect()` qui « ne prouvait rien » n'était pas de l'anti-usurpation + +L'exploitant : « je ne trouve pas ça normal ». Il avait raison, et pendant deux jours nous +avons tous les deux attribué ce comportement à une fonction d'anti-usurpation de la +frontière — moi le premier, et je l'avais même consigné comme tel. + +**Mesuré, pas raconté.** Depuis le poste, quatre connexions sur quatre s'établissaient, y +compris vers une adresse où aucune machine n'existe. Mais vers des réseaux hors du tenant, +tout était refusé — donc rien n'interceptait globalement. Et **depuis l'intérieur du +tenant**, le comportement était correct partout où un VNet existe : hôte absent → refusé, +port fermé → refusé. L'anomalie ne touchait que les portions de supernet **non couvertes +par un VNet**. + +**La cause était une route manquante, pas un pare-feu.** + +``` +vrf_t17 : les six /24 des VNets, puis default -> 10.0.4.1 + RIEN pour le reste de 10.27.0.0/16 +``` + +Une adresse non attribuée sortait donc du VRF par le défaut, atteignait la frontière, qui +la renvoyait à l'hyperviseur — où elle arrivait dans la table **principale**, pas dans le +VRF, et repartait vers `192.168.11.254`, la passerelle du réseau d'**administration**. +`ip route get 10.27.99.99` le disait en une ligne. + +**Corrigé où le dépôt a la main** : `strophe_frr` pose désormais, dans chaque VRF, un puits +sur le supernet du tenant — **moins spécifique** que les `/24` de ses VNets, donc invisible +au trafic légitime. Dérivé du seed, comme tout le reste. + +``` +depuis le tenant, apres : 10.27.99.99 refusee 10.27.18.99 refusee 10.27.18.21 ETABLIE +``` + +**Une machine du tenant ne peut plus atteindre le réseau de gestion par une faute de +frappe.** C'était le vrai risque, et il est fermé. + +**Ce qui reste, et que je ne corrige pas sans arbitrage.** Depuis le VLAN d'administration, +le `connect()` réussit encore : ce trafic n'entre jamais dans le VRF. OPNsense route tout +`10.27.0.0/16` vers l'hyperviseur, dont la table principale ne connaît que les six `/24`. +Deux remèdes possibles — un puits symétrique dans la table principale, ou n'annoncer à la +frontière que les `/24` réellement attribués. Le premier touche la table qui porte +l'administration des hyperviseurs ; ce n'est pas un geste à faire de sa propre initiative. + +**La leçon dépasse la route.** Nous avons expliqué pendant deux jours un symptôme par une +cause plausible et fausse, et cette explication est entrée dans la documentation. Ce qui l'a +défaite n'est pas un raisonnement plus fin : c'est d'avoir mesuré depuis **deux points de +vue différents**. Un seul point de vue donne une histoire cohérente — souvent la mauvaise. + ## 2026-08-09 — Le wiki rattrape ce que la reconstruction a appris Le wiki datait du 3 août — six jours avant tout ce qui précède. Vérifié avant d'y toucher : diff --git a/scripts/devis_sdn.py b/scripts/devis_sdn.py index 70ac584..cc65e7e 100644 --- a/scripts/devis_sdn.py +++ b/scripts/devis_sdn.py @@ -63,7 +63,7 @@ import yaml RACINE = Path(__file__).resolve().parents[1] sys.path.insert(0, str(RACINE / "scripts")) -from inventory_rules import passerelle_de, sous_reseau_de, vlan_de # noqa: E402 +from inventory_rules import passerelle_de, sous_reseau_de, supernet_de, vlan_de # noqa: E402 from devis_reseau import decouvrir # noqa: E402 import underlay as underlay_mod # noqa: E402 @@ -139,6 +139,7 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict: blocs.append({ "tenant": nom_instance, "index": index, + "supernet": supernet_de(index), "zone": zone_de(index), "vrf_vxlan": index, "mtu": mtu, @@ -185,6 +186,21 @@ def strophe_frr(devis: dict) -> str: for b in devis["zones"]: lignes += [f"vrf vrf_{b['zone']}", f" ip route 0.0.0.0/0 {devis['frontiere']} nexthop-vrf default", + # PUITS sur le supernet du tenant, MOINS SPECIFIQUE que les /24 de ses + # VNets : le trafic legitime ne le voit jamais, tout le reste s'y arrete. + # + # Sans lui, une adresse non attribuee du supernet ne trouve aucune route + # locale, sort par le defaut, atteint la frontiere qui la renvoie a + # l'hyperviseur — ou elle arrive dans la table PRINCIPALE, pas dans le + # VRF, et repart vers la passerelle du reseau d'ADMINISTRATION. Mesure du + # 2026-08-09 : `ip route get 10.27.99.99` rend `via 192.168.11.254`. + # + # Deux consequences. Le trafic d'un tenant pouvait atteindre le reseau de + # gestion par une simple faute de frappe. Et `connect()` reussissait vers + # n'importe quelle adresse inexistante du supernet — ce qui a longtemps + # passe pour de l'anti-usurpation de la frontiere, alors que c'etait une + # route manquante ici. + f" ip route {b['supernet']} blackhole", "exit-vrf"] return "\n".join(lignes) + "\n"