devis switch : le port vers la frontière, et l'ordre d'application

Deux omissions que le devis faisait porter à l'opérateur.

Le VLAN de transit était étiqueté sur le trunk `<PORT-VERS-PROXMOX>`, et
aucune interface vers le pare-feu n'était émise. Les hyperviseurs n'ont pas
d'interface sur le transit, et le pare-feu n'a rien à faire des VLAN
tenants — il route vers eux, il ne les étiquette pas. Le transit sort du
trunk Proxmox et prend son propre port (section 4b), dérivé de l'underlay.

Les routes de la section 5 déplacent la sortie du switch, y compris celle de
ses propres réponses. Tant que la frontière ne répond pas, elles coupent
l'accès d'administration AU SWITCH LUI-MÊME — le mécanisme qui a rendu une
VM muette le 2026-07-29, appliqué à l'équipement depuis lequel on travaille.
Le devis les crachait à la suite des autres comme si elles étaient
équivalentes ; il énonce maintenant les préalables et l'ordre.

Sans transit déclaré, les deux sections s'affichent en clair comme
manquantes plutôt que de disparaître.

Preuves : 24 OK, 0 échec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-01 19:52:50 -04:00
parent 069e549160
commit 0d64d28402
3 changed files with 65 additions and 1 deletions

View file

@ -1,5 +1,25 @@
# CHANGELOG — Set-OPS # CHANGELOG — Set-OPS
## 2026-08-01 (suite 2) — le port vers la frontière, et l'ordre d'application
### Ajouté — section 4b : le port du switch vers la frontière
Le devis étiquetait le VLAN de transit sur le trunk `<PORT-VERS-PROXMOX>` et n'émettait
aucune interface vers le pare-feu. Deux erreurs en une : les hyperviseurs n'ont pas
d'interface sur le transit, et le pare-feu n'a rien à faire des VLAN tenants — il route vers
eux, il ne les étiquette pas. Le VLAN de transit sort donc du trunk Proxmox et prend son
propre port, dérivé du transit déclaré.
### Ajouté — avertissement d'ordre en tête de la section 5
Les routes de la section 5 déplacent la sortie du switch, **y compris celle de ses propres
réponses**. Tant que l'adresse de la frontière ne répond pas, elles coupent l'accès
d'administration au switch lui-même — le mécanisme qui a rendu une VM muette le 2026-07-29,
appliqué cette fois à l'équipement depuis lequel on travaille.
Le devis crachait ces lignes à la suite des autres comme si elles étaient équivalentes.
Il énonce désormais les préalables (boîtier câblé, adressé, joignable depuis le switch,
session console ouverte) — inacceptable autrement pour un outil censé être exploitable sans
IA.
## 2026-08-01 (suite) — le prochain saut dérive du transit ## 2026-08-01 (suite) — le prochain saut dérive du transit
`opnsense_prochain_saut` n'est plus un intrant du panneau : il **dérive** du réseau de `opnsense_prochain_saut` n'est plus un intrant du panneau : il **dérive** du réseau de

View file

@ -95,6 +95,17 @@ ip route 192.168.255.0 255.255.255.0 10.0.4.2 # retour (nftables_admin_ssh)
**L'aller** est la sortie générale : sans elle, aucun hôte de la flotte n'atteint quoi que ce **L'aller** est la sortie générale : sans elle, aucun hôte de la flotte n'atteint quoi que ce
soit hors de sa zone. soit hors de sa zone.
### L'ordre d'application, qui n'est pas indifférent
Les sections 0 à 4b du devis switch sont sûres et posables à tout moment. **La section 5 ne
l'est pas.** Ces routes déplacent la sortie du switch — y compris celle de ses propres
réponses — vers l'adresse de la frontière. Tant que cette adresse ne répond pas, elles
coupent l'accès d'administration **au switch lui-même**.
C'est le même mécanisme que le piège ci-dessous, mais appliqué à l'équipement depuis lequel
on travaille. Préalables, rappelés dans le devis : boîtier câblé, adressé sur le lien de
transit, joignable depuis le switch, et une session console ouverte pendant l'opération.
**Le retour** est le piège proprement dit. Un paquet d'administration entre par la frontière **Le retour** est le piège proprement dit. Un paquet d'administration entre par la frontière
et atteint la VM ; la réponse part de la VM vers sa passerelle — un SVI **de switch**. Si le et atteint la VM ; la réponse part de la VM vers sa passerelle — un SVI **de switch**. Si le
switch n'a pas de route vers le réseau d'administration, ou s'il l'atteint par un autre chemin switch n'a pas de route vers le réseau d'administration, ou s'il l'atteint par un autre chemin

View file

@ -82,6 +82,26 @@ def section_underlay(underlay: dict | None) -> list[str]:
return out return out
def section_frontiere(transit: dict | None) -> list[str]:
"""Port du switch vers le pare-feu de bordure. Porte le VLAN de transit, et lui seul.
Distinct du trunk vers Proxmox : les hyperviseurs n'ont aucune interface sur le
transit, et le pare-feu n'a rien a faire des VLAN tenants — il route vers eux, il
ne les etiquette pas.
"""
if not transit:
return ["! ----- 4b. Port vers la frontiere -----",
"! Aucun reseau de transit declare dans underlay.yml (cle 'passerelle_sortie')."]
return [
"! ----- 4b. Port vers la frontiere nord/sud -----",
f"! Porte le seul VLAN {transit['vlan']} ({transit['nom']}). En lien dedie,",
f"! remplacer par : switchport access vlan {transit['vlan']}.",
"interface <PORT-VERS-FRONTIERE>",
" switchport mode trunk",
f" switchport trunk allowed vlan add {transit['vlan']}",
]
def section_routes(underlay: dict | None) -> list[str]: def section_routes(underlay: dict | None) -> list[str]:
"""Routes du switch vers la frontiere nord/sud. Vide si aucun transit declare. """Routes du switch vers la frontiere nord/sud. Vide si aucun transit declare.
@ -104,6 +124,14 @@ def section_routes(underlay: dict | None) -> list[str]:
"!"] "!"]
sortie = transit["passerelle_sortie"] sortie = transit["passerelle_sortie"]
out = ["! ----- 5. Routes vers la frontiere nord/sud -----", out = ["! ----- 5. Routes vers la frontiere nord/sud -----",
"! /!\\ A APPLIQUER EN DERNIER, ET SEULEMENT UNE FOIS LA FRONTIERE VIVANTE.",
f"! Ces routes deplacent la sortie du switch — y compris celle de ses propres",
f"! reponses — vers {sortie}. Tant que cette adresse ne repond pas, elles",
"! coupent l'acces d'administration AU SWITCH LUI-MEME.",
"! Prealables : boitier cable, adresse sur le lien de transit, et joignable",
f"! depuis le switch (ping {sortie} depuis Vlan{transit['vlan']}).",
"! Garder une session console ouverte pendant l'operation.",
"!",
f"! Transit '{transit['nom']}' (VLAN {transit['vlan']}) : SVI {transit['passerelle']}" f"! Transit '{transit['nom']}' (VLAN {transit['vlan']}) : SVI {transit['passerelle']}"
f" <-> frontiere {sortie}", f" <-> frontiere {sortie}",
"! Aller : sortie generale de la flotte.", "! Aller : sortie generale de la flotte.",
@ -189,7 +217,11 @@ def generer(tenants: list[tuple[str, str, dict]], dialecte: str | None = None) -
out += remarque(dialecte, "Reste (Internet / inter-tenant controle) -> passerelle OPNsense") out += remarque(dialecte, "Reste (Internet / inter-tenant controle) -> passerelle OPNsense")
out.append(f" permit ip {reseau} {m} any") out.append(f" permit ip {reseau} {m} any")
out += ["!", "! ----- 4. Trunk vers Proxmox + inter-switch (a adapter) -----"] out += ["!", "! ----- 4. Trunk vers Proxmox + inter-switch (a adapter) -----"]
vlans_underlay = [str(r["vlan"]) for r in underlay_mod.reseaux(underlay)] transit = underlay_mod.reseau_transit(underlay)
# Le VLAN de transit ne va PAS vers Proxmox : aucun hyperviseur n'a d'interface
# dessus. Il sort par le port de la frontiere (ci-dessous), et lui seul.
vlans_underlay = [str(r["vlan"]) for r in underlay_mod.reseaux(underlay)
if not (transit and r["vlan"] == transit["vlan"])]
vlans_tenants = [ vlans_tenants = [
str(vlan_de(n["index"], zone)) str(vlan_de(n["index"], zone))
for _, _, n in tenants for _, _, n in tenants
@ -199,6 +231,7 @@ def generer(tenants: list[tuple[str, str, dict]], dialecte: str | None = None) -
out.append("interface <PORT-VERS-PROXMOX>") out.append("interface <PORT-VERS-PROXMOX>")
out.append(" switchport mode trunk") out.append(" switchport mode trunk")
out.append(f" switchport trunk allowed vlan add {vlans}") out.append(f" switchport trunk allowed vlan add {vlans}")
out += ["!"] + section_frontiere(transit)
out += ["!"] + section_routes(underlay) out += ["!"] + section_routes(underlay)
out += ["end", "write memory"] out += ["end", "write memory"]
return "\n".join(out) return "\n".join(out)