sdn : le puits avalait le plan d administration — une route, et le chemin existe
CE QUE LA MESURE A ETABLI, saut par saut. Le poste de l exploitant atteint la
frontiere, qui LAISSE PASSER (rule 31/0 match, pass out vlan040). Asgard RECOIT le
paquet sur vlan40. La VM ne le voit jamais. Et le noyau dit pourquoi :
ip route get 10.17.19.41 from 10.0.31.11 iif vlan40 -> dev vrf_t17
ip route get 10.17.19.41 from 10.17.0.17 iif vlan40 -> Invalid cross-device link
LA SOURCE EST LE DISCRIMINANT, pas l interface. Le chemin de RETOUR, vu du VRF :
vers 10.0.31.11 -> via 10.0.4.1 dev vlan40
vers 10.17.0.17 -> Invalid argument <- le puits
LE PUITS A SES RAISONS et on n y touche pas : sans lui, une adresse non attribuee
du supernet sort par le defaut, revient par la frontiere dans la table PRINCIPALE
et repart vers le reseau de gestion (mesure du 2026-08-09). Le defaut n est pas
qu il existe, c est qu il est TROP LARGE : il couvre la bande basse ou D-77 place
justement l underlay d un site. Deux regles justes separement, contradictoires
ensemble.
LA CORRECTION EST DERIVEE, PAS ECRITE : pour chaque tenant, les reseaux
d administration qu il DECLARE (nftables_admin_ssh) et qui tombent dans son propre
supernet recoivent une route vers la frontiere, plus specifique que le puits. Ceux
qui vivent dehors n en ont pas besoin — la route par defaut les joint deja.
vrf_t17 ip route 10.17.0.0/24 ... l admin de Chezlepro
vrf_t23 (rien) le sien est hors de son supernet
vrf_t29 ip route 10.29.19.41/32 ... l admin de patient 0 : son ops-01
CE QUE CA REPARE AU-DELA DE L ACCES : les regles administration -> tenant de la
frontiere etaient VRAIES et INAPPLICABLES a la fois. Elles correspondaient, elles
laissaient passer, et le paquet mourait un saut plus loin. Un devis vert sur un
chemin qui ne pouvait pas aboutir.
make verifier : vert. make prouver : CONFORME, 54 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
parent
90ea474745
commit
732aff9c70
2 changed files with 112 additions and 1 deletions
57
CHANGELOG.md
57
CHANGELOG.md
|
|
@ -1,5 +1,62 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-28 — Le puits avalait le plan d'administration
|
||||
|
||||
**54 preuves.** Le poste de l'exploitant ne joignait aucune machine de tenant. La chaîne,
|
||||
mesurée saut par saut :
|
||||
|
||||
```
|
||||
1. le poste 10.17.0.17 -> 10.17.19.41:22 route et source correctes
|
||||
2. la frontiere rule 31/0(match): pass out vlan040 ELLE LAISSE PASSER
|
||||
3. asgard vlan40 In ... 10.17.19.41.22 IL RECOIT
|
||||
4. la VM (rien) IL N'ARRIVE JAMAIS
|
||||
```
|
||||
|
||||
Et le noyau dit pourquoi — **la source est le discriminant, pas l'interface** :
|
||||
|
||||
```
|
||||
ip route get 10.17.19.41 from 10.0.31.11 iif vlan40 -> dev vrf_t17
|
||||
ip route get 10.17.19.41 from 10.17.0.17 iif vlan40 -> Invalid cross-device link
|
||||
```
|
||||
|
||||
Parce que le **retour** est impossible : depuis le VRF du tenant, `10.17.0.17` tombe dans
|
||||
`blackhole 10.17.0.0/16`.
|
||||
|
||||
### Le puits a ses raisons — c'est sa largeur qui était fausse
|
||||
|
||||
Sans lui, une adresse non attribuée du supernet sort par le défaut, revient par la
|
||||
frontière **dans la table principale** et repart vers le réseau de gestion (mesuré le
|
||||
2026-08-09). On n'y touche pas.
|
||||
|
||||
Mais il couvre la **bande basse**, là où D-77 place justement l'underlay d'un site. *Deux
|
||||
règles justes séparément, contradictoires ensemble.*
|
||||
|
||||
### La correction est dérivée, pas écrite
|
||||
|
||||
Pour chaque tenant, les réseaux d'administration **qu'il déclare** (`nftables_admin_ssh`)
|
||||
et qui tombent dans son propre supernet reçoivent une route vers la frontière, plus
|
||||
spécifique que le puits. Ceux qui vivent dehors n'en ont pas besoin.
|
||||
|
||||
```
|
||||
vrf_t17 ip route 10.17.0.0/24 ... l'admin de Chezlepro
|
||||
vrf_t23 (rien) le sien est hors de son supernet
|
||||
vrf_t29 ip route 10.29.19.41/32 ... l'admin de patient 0 : son propre ops-01
|
||||
```
|
||||
|
||||
### Ce que ça répare au-delà de l'accès
|
||||
|
||||
Les règles `administration → tenant` de la frontière étaient **vraies et inapplicables à
|
||||
la fois**. Elles correspondaient, elles laissaient passer, et le paquet mourait un saut
|
||||
plus loin. *Un devis vert sur un chemin qui ne pouvait pas aboutir* — exactement le
|
||||
« périmètre vide » que ce dépôt traque partout ailleurs.
|
||||
|
||||
*Trois instruments m'ont menti avant d'y arriver : une capture qui ne tournait pas, un
|
||||
`ping` vers un ICMP que rien n'autorise, et un test TCP depuis la frontière dont la source
|
||||
n'est pas dans l'IPSet de la VM. La mesure qui a tranché est `ip route get ... iif`,
|
||||
comparée entre une source qui marche et une qui échoue.*
|
||||
|
||||
make verifier : vert. make prouver : CONFORME, 54 OK, 0 echec, 0 saute.
|
||||
|
||||
## 2026-08-28 — `make inseminer` : le geste sort de mes mains et entre dans le depot
|
||||
|
||||
**54 preuves.** L'insemination a d'abord ete conduite A LA MAIN depuis le runner du site —
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ Usage :
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ipaddress
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
|
@ -118,6 +119,36 @@ def sdn_hebergeur() -> dict:
|
|||
return data.get("proxmox_sdn") or {}
|
||||
|
||||
|
||||
def _admin_dans_le_supernet(nom_instance: str, supernet: str) -> list[str]:
|
||||
"""Les reseaux d'administration de ce tenant qui tombent DANS son propre supernet.
|
||||
|
||||
CEUX-LA SEULEMENT ONT BESOIN D'UNE ROUTE, et c'est tout le point. Un plan
|
||||
d'administration pose hors du supernet (10.0.0.0/24 du site, une adresse de VPN) est
|
||||
joint par la route par defaut du VRF : il n'a jamais rencontre le puits. Celui qui vit
|
||||
DANS le supernet, lui, tombe dedans.
|
||||
|
||||
D-77 place justement l'underlay d'un site dans la BANDE BASSE de son propre supernet —
|
||||
`10.<index>.0-15.x`. Chez un hebergeur qui est aussi son propre tenant, le plan
|
||||
d'administration est donc a l'interieur du /16 que le puits couvre. Les deux regles
|
||||
sont justes separement et se contredisent ensemble ; cette fonction nomme
|
||||
l'intersection.
|
||||
"""
|
||||
try:
|
||||
from devis_reseau import admin_de
|
||||
reseau_super = ipaddress.ip_network(supernet, strict=False)
|
||||
dedans = []
|
||||
for cidr in admin_de(nom_instance):
|
||||
try:
|
||||
n = ipaddress.ip_network(str(cidr).strip(), strict=False)
|
||||
except ValueError:
|
||||
continue
|
||||
if n.subnet_of(reseau_super) and n != reseau_super:
|
||||
dedans.append(str(n))
|
||||
return sorted(set(dedans))
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
||||
sdn = sdn_hebergeur()
|
||||
u = underlay_mod.charger()
|
||||
|
|
@ -140,6 +171,7 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
|||
"tenant": nom_instance,
|
||||
"index": index,
|
||||
"supernet": supernet_de(index),
|
||||
"admin_interne": _admin_dans_le_supernet(nom_instance, supernet_de(index)),
|
||||
"zone": zone_de(index),
|
||||
"vrf_vxlan": index,
|
||||
"mtu": mtu,
|
||||
|
|
@ -200,7 +232,29 @@ def strophe_frr(devis: dict) -> str:
|
|||
# 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",
|
||||
]
|
||||
# LE PUITS AVALAIT LE PLAN D'ADMINISTRATION (mesure du 2026-08-28).
|
||||
#
|
||||
# Il couvre tout le supernet du tenant. Les VNets s'en sortent : leurs /24 sont
|
||||
# connectes, donc plus specifiques. Mais D-77 place l'underlay d'un site dans la
|
||||
# BANDE BASSE de ce meme supernet — et rien ne l'y rendait plus specifique.
|
||||
#
|
||||
# Consequence, invisible parce qu'elle porte sur le RETOUR : un paquet venu du plan
|
||||
# d'administration arrive bien sur l'hyperviseur, et le noyau refuse de l'acheminer
|
||||
# parce que sa source est injoignable depuis le VRF.
|
||||
#
|
||||
# ip route get 10.17.19.41 from 10.0.31.11 iif vlan40 -> dev vrf_t17
|
||||
# ip route get 10.17.19.41 from 10.17.0.17 iif vlan40 -> Invalid cross-device link
|
||||
#
|
||||
# La frontiere, elle, laissait passer : ses regles `administration -> tenant`
|
||||
# etaient donc VRAIES et INAPPLICABLES a la fois — un devis vert sur un chemin
|
||||
# qui ne pouvait pas aboutir.
|
||||
#
|
||||
# On ne touche pas au puits, qui a ses raisons (voir plus haut). On rend l'admin
|
||||
# plus specifique que lui, vers la frontiere — la ou ce trafic doit aller.
|
||||
for _adm in b.get("admin_interne") or []:
|
||||
lignes.append(f" ip route {_adm} {devis['frontiere']} nexthop-vrf default")
|
||||
lignes += [f" ip route {b['supernet']} blackhole",
|
||||
"exit-vrf"]
|
||||
return "\n".join(lignes) + "\n"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue