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>
10 KiB
La frontière OPNsense (nord/sud)
Le pare-feu de bordure de l'écosystème. Ce document fixe les décisions d'architecture prises le 2026-07-29, et décrit le devis dérivé qui en découle (
make devis-opnsense). Il complètedocs/flux-conception.md(le modèle des flux) etdocs/registre-flux.md(la matrice d'audit).
1. Le partage des rôles
Trois pare-feux coexistent dans le modèle, et chacun voit une chose différente :
| Où | Quoi | Généré par |
|---|---|---|
| Sur chaque hôte | nftables policy drop, moindre privilège par IP source |
make flux (resoudre_flux.py) |
| Sur les switches L3 | ACL d'isolation inter-tenant, appliquées in sur les SVI |
make devis-reseau |
| À la frontière (OPNsense) | ce qui entre et sort de l'écosystème | make devis-opnsense |
Décision : OPNsense est une frontière nord/sud, pas la passerelle des zones. Les SVI restent sur les switches L3, qui continuent d'assurer le routage inter-zone au débit ligne. OPNsense ne porte aucun SVI de tenant et ne voit pas le trafic est-ouest.
Conséquence assumée : l'isolation inter-zone repose sur les ACL de switch, moins expressives 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.
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
volontairement le pair externe (scripts/resoudre_flux.py:184) : ces flux-là ne
concernent pas le pare-feu d'hôte, ils relèvent de la bordure. Plusieurs raison le disent
déjà noir sur blanc — « Frontière publique gérée à l'OPNsense ».
Autrement dit : la politique de la frontière était déjà écrite dans le registre, et il ne
restait qu'à la dériver. C'est ce que fait scripts/devis_opnsense.py, à partir de :
roles/*/meta/flux.yml— les fluxpair: externe, qui donnent les règles ;- l'inventaire de l'instance active — quels hôtes portent quel rôle, donc les destinations ;
../*/plan/nomenclature.yml— les supernets des tenants fédérés, donc les routes ;- l'intrant
nftables_admin_ssh— les réseaux d'administration.
Aucun port, aucune adresse et aucun nom d'hôte n'est écrit dans le générateur.
3. La garde anti-lockout
Une seule règle entrante ne vient pas d'Internet : le SSH de gestion. Sa source est
l'alias SETOPS_ADMIN, alimenté par l'intrant nftables_admin_ssh — le même qui nourrit
la garde des nftables d'hôte. Source unique, donc pas de divergence possible entre « ce que
le pare-feu d'hôte laisse passer » et « ce que la bordure laisse entrer ».
make devis-opnsense-verifier (preuve P24) refuse un devis dont cet intrant est vide :
sans lui, la règle SSH n'aurait aucune source et le block in final fermerait l'accès
d'administration. Le trou ne peut plus passer inaperçu.
4. Le lien de transit, et les deux routes
C'est le piège qui nous a coûté une passe de déploiement le 2026-07-29.
Le lien vit dans l'underlay, pas dans un tenant
La frontière route vers tous les supernets tenants (10.21.0.0/16, 10.27.0.0/16…) par
le même prochain saut. Le lien qui la relie au routeur est-ouest est donc partagé : il
n'appartient à aucun tenant et ne peut dériver d'aucun index. Sa place est l'underlay,
cluster-global, au même titre que le management, l'iSCSI et Ceph.
Il se déclare dans underlay.yml par la clé passerelle_sortie — l'adresse du pare-feu
sur ce lien :
- nom: transit-frontiere
vlan: 40
sous_reseau: 10.0.4.0/29
passerelle: 10.0.4.1 # SVI du switch L3
passerelle_sortie: 10.0.4.2 # la frontière = sortie par défaut de la flotte
Un /29 plutôt qu'un /30 : pendant une transition, deux pare-feux cohabitent sur le lien,
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 :
ip route 0.0.0.0 0.0.0.0 10.0.4.2 # aller
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 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 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 (typiquement sa passerelle de management), la réponse revient au pare-feu par une autre interface que celle où l'état a été créé. Elle est alors jetée en silence : ni réponse, ni ICMP unreachable.
Le symptôme est déroutant : la passerelle de zone répond au ping (elle, sa pile de management sait revenir), mais aucun hôte derrière elle n'est joignable. On croit à une règle de pare-feu ; c'est une route manquante à l'autre bout.
Les réseaux d'administration ne sont pas saisis ici : ils viennent de l'intrant
nftables_admin_ssh, la même source unique qui alimente la garde des nftables d'hôte et
l'alias SETOPS_ADMIN de la frontière. Les trois pare-feux et les routes de retour ne peuvent
donc pas diverger.
5. Appliquer le devis
OPNsense expose une API REST de première classe, authentifiée par clé + secret
(System → Access → Users → l'utilisateur → API keys). C'est ce qui permettra d'appliquer le
devis sans clics : devis_opnsense.py --json produit déjà la structure destinée à cet usage
(alias, routes, règles), et les endpoints visés sont /api/firewall/alias/*,
/api/firewall/filter/* et /api/routes/*, avec le motif habituel « on prépare puis on
applique ».
Où vivent les identifiants
Même partage que Proxmox — l'anodin en clair, le secret dans la voûte :
| Quoi | Où | Réglable |
|---|---|---|
| URL de gestion, interfaces, prochain saut | group_vars/opnsense.yml (en clair) |
panneau « Intrants de base » du GUI, section Frontière |
| Clé et secret d'API | voûte unique de l'instance, sous vault_opnsense_api_key / vault_opnsense_api_secret |
ansible-vault edit |
Les valeurs non sensibles sont de vrais intrants : opnsense_api_url,
opnsense_api_verifier_certs, opnsense_if_wan, opnsense_if_transit et
opnsense_prochain_saut figurent au schéma du panneau (INTRANTS_SCHEMA) et s'éditent sans
toucher au YAML — un opérateur règle la frontière depuis la console, sans IA.
Tant qu'elles sont vides, le devis affiche des marqueurs (<IF-TRANSIT>,
<PROCHAIN-SAUT-SWITCH>) ; il se complète tout seul dès qu'elles sont renseignées.
Garde-fou :
opnsense_api_keyetopnsense_api_secretsont dansINTRANTS_CLES_INTERDITES— le GUI refuse de les écrire. Impossible de coller un secret d'API dans le panneau par mégarde. Ils n'apparaissent qu'en lecture seule, par leur nom, dans le rappelSECRETS_ATTENDUS.
Le fichier en clair ne porte que des références par nom ({{ vault_opnsense_api_key }}).
scripts/voute.py les recense automatiquement et la preuve P18 vérifie qu'elles figurent
au gabarit vault.yml.example — le gabarit est passé de 23 à 25 secrets sans intervention.
Pour renseigner les vraies valeurs, une seule commande, jamais de secret sur disque en clair :
ansible-vault edit instance/inventories/principal/group_vars/all/vault.yml
Le secret d'API n'est affiché qu'à sa création dans OPNsense. S'il est perdu, il faut révoquer la clé et en générer une nouvelle.
pfSense CE, lui, n'a pas d'API officielle (celle de Netgate n'existe que sur pfSense Plus) : seul un paquet tiers en fournit une. C'est l'une des raisons du choix d'OPNsense.
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. 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. - L'application par l'API —
--jsonproduit la structure ; le client d'application reste à écrire. - 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
outputdes nftables estacceptet que l'ACL de switch se termine parpermit ip <tenant> any. À trancher : la déclarer explicitement, ou l'assumer comme politique par défaut de la bordure.