SDN : un puits sur le supernet du tenant dans son propre VRF
« 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 <noreply@anthropic.com>
This commit is contained in:
parent
91d9bd19f4
commit
78f2f7fd6f
2 changed files with 65 additions and 1 deletions
48
CHANGELOG.md
48
CHANGELOG.md
|
|
@ -1,5 +1,53 @@
|
||||||
# CHANGELOG — Set-OPS
|
# 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
|
## 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 :
|
Le wiki datait du 3 août — six jours avant tout ce qui précède. Vérifié avant d'y toucher :
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ import yaml
|
||||||
RACINE = Path(__file__).resolve().parents[1]
|
RACINE = Path(__file__).resolve().parents[1]
|
||||||
sys.path.insert(0, str(RACINE / "scripts"))
|
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
|
from devis_reseau import decouvrir # noqa: E402
|
||||||
import underlay as underlay_mod # noqa: E402
|
import underlay as underlay_mod # noqa: E402
|
||||||
|
|
||||||
|
|
@ -139,6 +139,7 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||||
blocs.append({
|
blocs.append({
|
||||||
"tenant": nom_instance,
|
"tenant": nom_instance,
|
||||||
"index": index,
|
"index": index,
|
||||||
|
"supernet": supernet_de(index),
|
||||||
"zone": zone_de(index),
|
"zone": zone_de(index),
|
||||||
"vrf_vxlan": index,
|
"vrf_vxlan": index,
|
||||||
"mtu": mtu,
|
"mtu": mtu,
|
||||||
|
|
@ -185,6 +186,21 @@ def strophe_frr(devis: dict) -> str:
|
||||||
for b in devis["zones"]:
|
for b in devis["zones"]:
|
||||||
lignes += [f"vrf vrf_{b['zone']}",
|
lignes += [f"vrf vrf_{b['zone']}",
|
||||||
f" ip route 0.0.0.0/0 {devis['frontiere']} nexthop-vrf default",
|
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"]
|
"exit-vrf"]
|
||||||
return "\n".join(lignes) + "\n"
|
return "\n".join(lignes) + "\n"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue