From 069e5491608ab3ec0c22be53749a818d8ac25657 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sat, 1 Aug 2026 19:38:17 -0400 Subject: [PATCH] =?UTF-8?q?fronti=C3=A8re=20:=20le=20prochain=20saut=20d?= =?UTF-8?q?=C3=A9rive=20du=20transit,=20il=20n'est=20plus=20saisi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `opnsense_prochain_saut` quitte le panneau Intrants. Il dérive du réseau de transit de l'underlay — la `passerelle` du réseau portant `passerelle_sortie`. Un seul bloc alimente les deux devis : 10.0.4.1 est le SVI côté switch ET le prochain saut des routes tenants côté frontière ; 10.0.4.2 est la route par défaut du switch. Le saisir en doublon rouvrait la possibilité de deux valeurs contradictoires pour un seul lien — le mode de panne qu'on venait de fermer pour les réseaux d'administration. Le devis frontière expose le bloc `transit` (JSON compris) et cesse de réclamer en section 0 les routes que `devis-reseau` émet désormais : il dit lesquelles sont déjà émises, ou signale l'absence de transit déclaré. Sans underlay, le marqueur revient — le repli reste explicite. Preuves : 24 OK, 0 échec. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 19 ++++++++++++++++ docs/frontiere-opnsense.md | 13 ++++++++--- scripts/devis_opnsense.py | 45 ++++++++++++++++++++++++++++++++++++-- scripts/inventory_gui.py | 4 +++- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 834949d..c057b62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # CHANGELOG — Set-OPS +## 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 +transit de l'underlay (la `passerelle` du réseau portant `passerelle_sortie`). + +Un seul bloc de six lignes alimente désormais les deux devis : `10.0.4.1` devient le SVI +côté switch **et** le prochain saut des routes tenants côté frontière ; `10.0.4.2` devient +la route par défaut du switch. Le saisir en doublon dans le panneau rouvrait la possibilité +de deux valeurs contradictoires pour un seul lien — précisément le mode de panne qu'on venait +de fermer pour les réseaux d'administration. + +Le devis frontière gagne au passage un bloc `transit` (JSON compris) et cesse de réclamer en +section 0 des routes que `make devis-reseau` émet maintenant : il dit lesquelles sont **déjà +émises**, ou signale l'absence de transit déclaré. Sans underlay, le marqueur +`` revient — le repli reste explicite. + +Reste un seul intrant à figer au câblage : `opnsense_if_transit`, le nom de l'interface qui +porte le VLAN 40 sur le boîtier — la seule valeur que rien ne peut deviner. + ## 2026-08-01 — le lien de transit et les deux routes (la boucle est fermée) ### Ajouté — réseau de transit dans l'underlay (clé `passerelle_sortie`) diff --git a/docs/frontiere-opnsense.md b/docs/frontiere-opnsense.md index ac326f5..84f3b5e 100644 --- a/docs/frontiere-opnsense.md +++ b/docs/frontiere-opnsense.md @@ -77,6 +77,12 @@ Un `/29` plutôt qu'un `/30` : pendant une transition, deux pare-feux cohabitent et un `/30` n'offre que deux adresses. `make underlay` (**preuve P23**) refuse une sortie hors du lien, confondue avec le SVI, sans SVI, ou déclarée deux fois. +Ce seul bloc alimente **les deux devis** : le `passerelle` (`10.0.4.1`) devient le SVI côté +switch *et* le prochain saut des routes tenants côté frontière ; le `passerelle_sortie` +(`10.0.4.2`) devient la route par défaut du switch. Le prochain saut n'est donc **pas** un +intrant du panneau — le saisir en doublon rouvrirait la possibilité de deux valeurs +contradictoires pour un seul et même lien. + ### Les deux routes, et pourquoi il en faut deux `make devis-reseau` émet alors, en section 5 : @@ -155,9 +161,10 @@ ansible-vault edit instance/inventories/principal/group_vars/all/vault.yml ## 6. Ce qui reste ouvert - **Le câblage** — le lien de transit est *décidé* (VLAN 40, `10.0.4.0/29`) et le devis - switch émet déjà son SVI et ses routes. Restent à figer, une fois le boîtier raccordé, les - deux intrants côté frontière : `opnsense_if_transit` (l'interface qui porte le VLAN 40) et - `opnsense_prochain_saut` (`10.0.4.1`). + switch émet déjà son SVI et ses routes. Reste à figer, une fois le boîtier raccordé, un + seul intrant : `opnsense_if_transit`, le nom de l'interface qui porte le VLAN 40 sur le + boîtier — la seule valeur que rien ne peut deviner. Le prochain saut, lui, **dérive** du + transit (voir §4). - **Le second VPN** — pendant la transition, le VPN d'administration actuel (pfSense) reste en service et OPNsense en montera un second. Son sous-réseau devra être **ajouté à `nftables_admin_ssh`**, sans quoi il sera muet de la même façon. diff --git a/scripts/devis_opnsense.py b/scripts/devis_opnsense.py index 5da0618..3faf3fb 100644 --- a/scripts/devis_opnsense.py +++ b/scripts/devis_opnsense.py @@ -47,6 +47,9 @@ from resoudre_flux import ( # noqa: E402 _sources_admin_ssh, ) from devis_reseau import decouvrir, prefixe # noqa: E402 +# Le lien de transit (et donc le prochain saut) vit dans l'underlay : il est PARTAGE +# par tous les tenants, il ne peut deriver d'aucun index. Source unique, pas un intrant. +import underlay as underlay_mod # noqa: E402 # Marqueurs de repli, tant que les intrants de frontiere ne sont pas renseignes # (meme idiome que devis_reseau). Le panneau « Intrants de base » du GUI les ecrit @@ -70,6 +73,22 @@ def _ou_marqueur(intrants: dict, cle: str, marqueur: str) -> str: valeur = str(intrants.get(cle) or "").strip() return valeur or marqueur + +def transit_underlay() -> dict | None: + """Le reseau de transit declare dans l'underlay, ou None.""" + return underlay_mod.reseau_transit(underlay_mod.charger()) + + +def prochain_saut(transit: dict | None) -> str: + """Prochain saut des routes tenants = le SVI du switch sur le lien de transit. + + DERIVE, jamais saisi : c'est le meme lien que celui d'ou `devis_reseau` tire la + route par defaut et les routes de retour. Le declarer deux fois (une fois ici, une + fois dans l'underlay) ouvrirait la porte a deux valeurs contradictoires — c'est + exactement le mode de panne qu'on ferme. Marqueur tant qu'aucun transit n'existe. + """ + return str((transit or {}).get("passerelle") or "").strip() or PROCHAIN_SAUT + # Le socle est porte par TOUS les hotes : sa destination n'est pas une poignee # de machines mais le supernet du tenant. Evite un alias de 14 adresses. ROLE_SOCLE = "serveur_debian" @@ -177,7 +196,8 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict: }) intrants = intrants_frontiere() - saut = _ou_marqueur(intrants, "opnsense_prochain_saut", PROCHAIN_SAUT) + transit = transit_underlay() + saut = prochain_saut(transit) routes = [ { "reseau": supernet_de(n["index"]), @@ -198,6 +218,13 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict: "routes": routes, "regles": regles, "admin": admin, + "transit": { + "nom": (transit or {}).get("nom"), + "vlan": (transit or {}).get("vlan"), + "sous_reseau": (transit or {}).get("sous_reseau"), + "svi_switch": saut, + "adresse_frontiere": (transit or {}).get("passerelle_sortie"), + } if transit else None, "reciproque_switch": [ f"ip route {cidr} -> {saut} (retour vers l'administration)" for cidr in admin @@ -214,17 +241,31 @@ def rendre(devis: dict) -> str: "# Les passerelles de zone restent sur les switches L3 : aucun SVI ici.", "# ============================================================", "", - "## 0. Prerequis reciproques — A FAIRE SUR LES SWITCHES", + "## 0. Prerequis reciproques — COTE SWITCHES", "# Sans ces routes de retour, le trafic part mais ne revient pas : la", "# reponse sort par une autre interface et le pare-feu la jette (hors etat).", ] out += [f"# {r}" for r in devis["reciproque_switch"]] or ["# (aucun reseau d'administration declare)"] + tr = devis.get("transit") + if tr: + out.append(f"# DEJA EMIS par `make devis-reseau` (section 5), derive du transit" + f" '{tr['nom']}' de l'underlay.") + else: + out.append("# A EMETTRE : aucun reseau de transit n'est declare dans underlay.yml") + out.append("# (cle `passerelle_sortie`). Sans lui, ni sortie ni retour ne sont derivables.") out += [ "", "## 1. Interfaces", f"# Gestion : {devis['api_url'] or '(intrant opnsense_api_url a renseigner)'}", f"# WAN : {devis['if_wan']}", f"# TRANSIT : {devis['if_transit']} (trunk 802.1Q vers les switches)", + ] + if tr: + out += [ + f"# lien '{tr['nom']}' {tr['sous_reseau']} — VLAN {tr['vlan']} a etiqueter", + f"# switch {tr['svi_switch']} <-> frontiere {tr['adresse_frontiere']}", + ] + out += [ "", "## 2. Routes statiques (vers les supernets tenants)", ] diff --git a/scripts/inventory_gui.py b/scripts/inventory_gui.py index c81f303..000bdc4 100644 --- a/scripts/inventory_gui.py +++ b/scripts/inventory_gui.py @@ -115,7 +115,9 @@ INTRANTS_SCHEMA = [ ("opnsense_api_verifier_certs", "frontiere", "defaut", "Frontière", "Valider le certificat TLS de la frontière", "bool"), ("opnsense_if_wan", "frontiere", "defaut", "Frontière", "Interface WAN", "str"), ("opnsense_if_transit", "frontiere", "defaut", "Frontière", "Interface/trunk vers les switches (transit)", "str"), - ("opnsense_prochain_saut", "frontiere", "defaut", "Frontière", "IP du switch L3 qui porte les SVI (prochain saut des routes tenants)", "str"), + # `opnsense_prochain_saut` n'est PLUS un intrant : il derive du reseau de transit de + # l'underlay (`passerelle` du reseau portant `passerelle_sortie`). Le saisir ici en + # doublon ouvrirait la porte a deux valeurs contradictoires pour un seul lien. ] # Cles secretes : interdites a l'ecriture par le GUI (garde-fou). INTRANTS_CLES_INTERDITES = {