From 7b2ba07ffc2ad580aed87426cede0ae40ec0ecca Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sat, 1 Aug 2026 19:59:03 -0400 Subject: [PATCH] ACL de switch : les tenants n'atteignent plus la fabric physique MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'ACL d'isolation bloquait l'autre tenant, puis se terminait par `permit ip any`. Ce `any` autorisait 10.27.x -> 10.0.0.0/24 : le management des switches, celui de Proxmox et l'OOB/IPMI, plus iSCSI et Ceph. Une VM compromise atteignait la console physique des hyperviseurs. Le commentaire du générateur disait « Reste -> passerelle OPNsense », ce qui est faux pour l'underlay : ce trafic est routé LOCALEMENT par le switch et ne passe jamais par la frontière, donc elle ne le filtre jamais. Un `deny` par sous-réseau underlay est désormais émis avant le `permit` final, dérivé de underlay.yml, dialecte respecté (masque normal ou wildcard). Vérifié qu'aucun flux du registre ne vise l'underlay : rien de déclaré ne casse. Sans underlay déclaré, l'ACL retrouve sa forme d'avant. Consigné en §6 : le registre n'a pas de mot-clé `underlay` (un besoin légitime, superviser l'hyperviseur, ne pourrait pas être déclaré), et le devis émet un jeu unique de SVI pour trois switches sans MLAG. Preuves : 24 OK, 0 échec. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 21 +++++++++++++++++++++ docs/frontiere-opnsense.md | 15 +++++++++++++++ scripts/devis_reseau.py | 9 +++++++++ 3 files changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41860e9..852529b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # CHANGELOG — Set-OPS +## 2026-08-01 (suite 3) — les tenants n'atteignent plus l'underlay + +### Corrigé — ACL de switch : `deny` vers la fabric physique +L'ACL d'isolation bloquait l'autre tenant, puis se terminait par `permit ip any`. +Ce `any` autorisait `10.27.x` → `10.0.0.0/24` : le management des switches, celui de Proxmox +et l'**OOB/IPMI**, plus les réseaux iSCSI et Ceph. Une VM compromise atteignait la console +physique des hyperviseurs. + +Le commentaire du générateur disait « Reste → passerelle OPNsense », mais c'est faux pour +l'underlay : ce trafic est routé **localement** par le switch et ne passe jamais par la +frontière, donc il n'est jamais filtré par elle. + +`devis_reseau.py` émet désormais un `deny` par sous-réseau underlay avant le `permit` final, +dérivé de `underlay.yml` — dialecte respecté (masque normal ou wildcard). Aucun flux du +registre ne vise l'underlay : le blocage ne casse rien de déclaré. + +Deux limites consignées dans `docs/frontiere-opnsense.md` §6 : le registre des flux n'a pas +de mot-clé `underlay`, donc un besoin légitime (superviser l'hyperviseur) ne pourrait pas +être déclaré ; et `devis_reseau.py` émet un jeu unique de SVI pour trois switches sans MLAG, +ce qui reste une décision d'architecture ouverte. + ## 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 diff --git a/docs/frontiere-opnsense.md b/docs/frontiere-opnsense.md index 5cb81f3..f0daf7e 100644 --- a/docs/frontiere-opnsense.md +++ b/docs/frontiere-opnsense.md @@ -23,6 +23,13 @@ Conséquence assumée : l'isolation inter-zone repose sur les ACL de switch, moi que le registre des flux. Le zéro-confiance est-ouest reste porté par les nftables d'hôte et le TLS mutuel — pas par le pare-feu de bordure. +Corollaire, qui n'est pas anodin : **le trafic d'un tenant vers l'underlay est routé +localement par le switch et ne passe jamais par la frontière.** Il n'est donc jamais filtré +par elle. Sans précaution, le `permit ip any` final de l'ACL laisserait une VM +atteindre le management des switches, celui de Proxmox et l'OOB/IPMI. `make devis-reseau` +émet pour cette raison un `deny` par sous-réseau underlay **avant** le `permit` final, +dérivé de `underlay.yml`. + ## 2. Ce que la frontière décide (et pourquoi rien n'est saisi à la main) Le registre des flux distingue les pairs par **mot-clé**. Or `resoudre_flux.py` **saute @@ -181,6 +188,14 @@ ansible-vault edit instance/inventories/principal/group_vars/all/vault.yml `nftables_admin_ssh`**, sans quoi il sera muet de la même façon. - **L'application par l'API** — `--json` produit la structure ; le client d'application reste à écrire. +- **Pas de mot-clé `underlay` dans le registre des flux** — l'underlay est aujourd'hui + interdit en bloc aux tenants. Si un besoin légitime apparaît (superviser l'hyperviseur + depuis `obs-01`, par exemple), il ne peut pas être *déclaré* : il faudrait ajouter un pair + `underlay` au registre plutôt qu'une exception manuelle dans l'ACL. +- **Trois switches, un seul devis** — `devis_reseau.py` émet un jeu unique de SVI sans + désigner le switch qui les porte. Sans MLAG, il reste à trancher entre « un seul routeur, + les deux autres en L2 pur » et « SVI répliqués en VRRP ». Appliquer le devis tel quel sur + les trois créerait des conflits d'adresses. - **La sortie générale** (mises à jour apt, ACME) n'est pas déclarée dans le registre : les hôtes sortent aujourd'hui parce que la politique `output` des nftables est `accept` et que l'ACL de switch se termine par `permit ip any`. À trancher : la déclarer diff --git a/scripts/devis_reseau.py b/scripts/devis_reseau.py index 8a729f1..97ea029 100644 --- a/scripts/devis_reseau.py +++ b/scripts/devis_reseau.py @@ -214,6 +214,15 @@ def generer(tenants: list[tuple[str, str, dict]], dialecte: str | None = None) - a_reseau, a_m = masque_acl(supernet_de(autre["index"]), dialecte) out += remarque(dialecte, f"Bloquer le tenant {autre_nom}") out.append(f" deny ip {reseau} {m} {a_reseau} {a_m}") + # L'underlay est la fabric physique : mgmt des switches, mgmt Proxmox, OOB/IPMI, + # iSCSI, Ceph. Le trafic d'un tenant vers ces reseaux est route LOCALEMENT par le + # switch : il ne passe jamais par la frontiere, donc il n'est jamais filtre. Sans + # ce deny, le `permit any` final l'autorise — une VM atteindrait la console + # physique des hyperviseurs. Aucun flux du registre ne vise l'underlay. + for r in underlay_mod.reseaux(underlay): + u_reseau, u_m = masque_acl(r["sous_reseau"], dialecte) + out += remarque(dialecte, f"Bloquer l'underlay {r['nom']} (fabric physique)") + out.append(f" deny ip {reseau} {m} {u_reseau} {u_m}") out += remarque(dialecte, "Reste (Internet / inter-tenant controle) -> passerelle OPNsense") out.append(f" permit ip {reseau} {m} any") out += ["!", "! ----- 4. Trunk vers Proxmox + inter-switch (a adapter) -----"]