frontière : ses intrants appartiennent à l'hébergeur, pas au tenant actif
Un hébergeur sert plusieurs tenants et n'a qu'une frontière. Ses intrants étaient lus chez le tenant actif : basculer sur un invité — Technolibre, qui n'a pas de frontière à lui — faisait perdre au devis l'URL de gestion, l'adresse publique et les deux interfaces. Il repartait en marqueurs, comme si le boîtier n'existait pas. Vérifié en simulant la bascule. Même famille que le défaut de l'underlay corrigé plus tôt ; c'est la distinction hébergeur/tenant qui le fait apparaître. Le devis et le panneau lisent maintenant la frontière chez l'hébergeur, qui n'est pas déclaré pour autant : le symlink `underlay.yml` le désigne déjà. Repli sur l'instance active sans underlay monté. Vérifié : devis identique avec l'hébergeur actif, intrants conservés avec un invité actif. Preuves : 24 OK, 0 échec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
7256486c9c
commit
517119c7ac
4 changed files with 97 additions and 13 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
|
@ -1,5 +1,24 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-02 (suite 7) — la frontière appartient à l'hébergeur, pas au tenant actif
|
||||
|
||||
Un hébergeur sert **plusieurs tenants** et n'a qu'**une** frontière. Or ses intrants
|
||||
(`group_vars/opnsense.yml`) étaient lus chez le **tenant actif** : basculer sur un invité —
|
||||
Technolibre, qui n'a pas de frontière à lui — faisait perdre au devis l'URL de gestion,
|
||||
l'adresse publique et les deux interfaces. Il repartait en marqueurs, comme si le boîtier
|
||||
n'existait pas. Vérifié en simulant la bascule : `AUCUN` intrant lu.
|
||||
|
||||
Même famille que le défaut de l'underlay corrigé plus tôt, et c'est la distinction
|
||||
hébergeur/tenant qui le fait apparaître.
|
||||
|
||||
Le devis **et** le panneau lisent désormais la frontière chez l'hébergeur. Celui-ci n'est pas
|
||||
déclaré pour autant : le symlink `underlay.yml` le **désigne déjà**, et une seconde
|
||||
déclaration ouvrirait la porte à deux valeurs contradictoires. Repli sur l'instance active
|
||||
quand aucun underlay n'est monté — un site sans fabric déclarée continue de fonctionner.
|
||||
|
||||
Vérifié : devis identique avec l'hébergeur actif, et intrants **conservés** avec un invité
|
||||
actif. `docs/frontiere-opnsense.md` gagne un §2 qui pose qui possède quoi.
|
||||
|
||||
## 2026-08-02 (suite 6) — l'underlay devient modélisable
|
||||
|
||||
Un modèle décrivait jusqu'ici un **tenant** : ses services, ses zones, ses bases. Or tous les
|
||||
|
|
|
|||
|
|
@ -38,7 +38,27 @@ atteindre le management des switches, celui de Proxmox et l'OOB/IPMI. `make devi
|
|||
é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)
|
||||
## 2. Hébergeur et tenants — qui possède quoi
|
||||
|
||||
Un **hébergeur** possède le matériel et sert **plusieurs tenants** ; il a en général son
|
||||
propre tenant par défaut. Chezlepro est les deux à la fois, ce qui masque la distinction —
|
||||
mais elle décide de l'emplacement de chaque chose :
|
||||
|
||||
| Objet | Appartient à | Vit dans |
|
||||
|---|---|---|
|
||||
| plan des services, inventaire | le **tenant** | `OPS-<tenant>` |
|
||||
| `underlay.yml` (fabric physique) | l'**hébergeur** | `OPS-<hébergeur>`, monté par symlink |
|
||||
| `group_vars/opnsense.yml` (frontière) | l'**hébergeur** | idem — **une** frontière pour tous ses tenants |
|
||||
|
||||
Conséquence pratique : `make instance-utiliser` bascule le **tenant** actif, jamais
|
||||
l'hébergeur. Le devis lit donc la fabric et les intrants de frontière chez l'hébergeur, quel
|
||||
que soit le tenant actif — sans quoi basculer sur un invité ferait disparaître l'URL de
|
||||
gestion, l'adresse publique et les interfaces, et le devis repartirait en marqueurs.
|
||||
|
||||
L'hébergeur n'est **pas déclaré** : le symlink `underlay.yml` le désigne déjà, et une seconde
|
||||
déclaration ouvrirait la porte à deux valeurs contradictoires.
|
||||
|
||||
## 3. Ce que la frontière décide (et pourquoi rien n'est saisi à la main)
|
||||
|
||||
**La frontière est un équipement partagé, comme les switches.** Elle route vers tous les
|
||||
tenants fédérés, elle porte donc aussi **leurs règles** — pas seulement celles de l'instance
|
||||
|
|
@ -70,7 +90,7 @@ restait qu'à la dériver. C'est ce que fait `scripts/devis_opnsense.py`, à par
|
|||
|
||||
Aucun port, aucune adresse et aucun nom d'hôte n'est écrit dans le générateur.
|
||||
|
||||
## 3. L'interface de chaque règle, et l'invariant du dernier octet
|
||||
## 4. L'interface de chaque règle, et l'invariant du dernier octet
|
||||
|
||||
**Dans OPNsense, une règle est toujours `in` sur l'interface d'arrivée** — celle par laquelle
|
||||
le paquet pénètre le pare-feu. Posée ailleurs, elle ne s'applique jamais, et le trafic est
|
||||
|
|
@ -102,7 +122,7 @@ Seule exception, assumée : les liens plus étroits qu'un `/24`. Sur le `/29` de
|
|||
l'adressage est dicté par les participants du lien — les deux frontières occupent `.1` et
|
||||
`.2`, le switch prend `.6`.
|
||||
|
||||
## 4. La garde anti-lockout
|
||||
## 5. 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
|
||||
|
|
@ -113,7 +133,7 @@ le pare-feu d'hôte laisse passer » et « ce que la bordure laisse entrer ».
|
|||
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.
|
||||
|
||||
## 5. Le lien de transit, et les deux routes
|
||||
## 6. 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.**
|
||||
|
||||
|
|
@ -205,7 +225,7 @@ Les réseaux d'administration ne sont pas saisis ici : ils viennent de l'intrant
|
|||
l'alias `SETOPS_ADMIN` de la frontière. Les trois pare-feux et les routes de retour ne peuvent
|
||||
donc pas diverger.
|
||||
|
||||
## 6. Appliquer le devis
|
||||
## 7. 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
|
||||
|
|
@ -252,12 +272,12 @@ ansible-vault edit instance/inventories/principal/group_vars/all/vault.yml
|
|||
> 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.
|
||||
|
||||
## 7. Ce qui reste ouvert
|
||||
## 8. 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`, 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 (§5).
|
||||
seule valeur que rien ne peut deviner. Le prochain saut, lui, **dérive** du transit (§6).
|
||||
|
||||
**Trois noms désignent le même port dans OPNsense**, et l'intrant en veut un seul :
|
||||
`igb1` est le périphérique FreeBSD, `TENANTS` (ou tout autre libellé) est la description
|
||||
|
|
|
|||
|
|
@ -63,12 +63,32 @@ IF_TRANSIT = "<IF-TRANSIT>"
|
|||
IF_WAN = "<IF-WAN>"
|
||||
|
||||
|
||||
def depot_hebergeur() -> Path | None:
|
||||
"""Depot de l'HEBERGEUR — celui qui possede le materiel, donc la frontiere.
|
||||
|
||||
Derive du symlink `underlay.yml` : il pointe vers le depot de l'hebergeur, ce qui
|
||||
le DESIGNE deja. Le declarer une seconde fois ouvrirait la porte a deux valeurs
|
||||
contradictoires. None si aucun underlay (site sans fabric declaree).
|
||||
"""
|
||||
c = underlay_mod.chemin()
|
||||
return c.resolve().parent if c else None
|
||||
|
||||
|
||||
def intrants_frontiere() -> dict:
|
||||
"""Intrants NON sensibles de la frontiere (group_vars/opnsense.yml). {} si absent."""
|
||||
for nom in ("principal", "production", "lab"):
|
||||
p = _inventaire().parent.parent / nom / "group_vars" / "opnsense.yml"
|
||||
if p.is_file():
|
||||
return yaml.safe_load(p.read_text(encoding="utf-8")) or {}
|
||||
"""Intrants NON sensibles de la frontiere (group_vars/opnsense.yml). {} si absent.
|
||||
|
||||
Lus chez l'HEBERGEUR, pas chez le tenant actif : un hebergeur sert plusieurs
|
||||
tenants et n'a qu'une frontiere. Basculer l'instance active sur un invite ne doit
|
||||
pas faire perdre au devis l'URL de gestion, l'adresse publique et les interfaces.
|
||||
Repli sur l'instance active quand aucun underlay ne designe d'hebergeur.
|
||||
"""
|
||||
for base in (depot_hebergeur(), _inventaire().parent.parent.parent):
|
||||
if base is None:
|
||||
continue
|
||||
for nom in ("principal", "production", "lab"):
|
||||
p = base / "inventories" / nom / "group_vars" / "opnsense.yml"
|
||||
if p.is_file():
|
||||
return yaml.safe_load(p.read_text(encoding="utf-8")) or {}
|
||||
return {}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -87,12 +87,37 @@ INTRANTS_PROXMOX = INVENTAIRE_MODELE.parent / "group_vars/proxmox.yml"
|
|||
# reformater le fichier (cf. _ecrire_index_nomenclature).
|
||||
# Frontiere nord/sud (OPNsense) : parametres NON sensibles du pare-feu de bordure.
|
||||
# La cle/secret d'API n'y entrent JAMAIS — voute uniquement (cf. INTRANTS_CLES_INTERDITES).
|
||||
INTRANTS_FRONTIERE = INVENTAIRE_DEFAUT.parent / "group_vars/opnsense.yml"
|
||||
# Fabric physique : `underlay.yml`, monte par symlink a la racine du moteur depuis le
|
||||
# depot de l'HEBERGEUR (ses switches, ses cables). Distinct de l'instance : il ne suit
|
||||
# pas `make instance-utiliser`. Imbrique sous `underlay:` et riche en commentaires :
|
||||
# on l'ecrit chirurgicalement, jamais par un safe_dump qui les effacerait.
|
||||
FICHIER_UNDERLAY = Path(os.environ.get("SETOPS_UNDERLAY") or (RACINE / "underlay.yml"))
|
||||
|
||||
|
||||
def _depot_hebergeur() -> Path | None:
|
||||
"""Depot de l'HEBERGEUR, derive du symlink `underlay.yml` qui le designe deja.
|
||||
|
||||
Un hebergeur sert PLUSIEURS tenants et n'a qu'une frontiere : ses intrants ne
|
||||
peuvent pas vivre chez le tenant actif, sinon basculer sur un invite les ferait
|
||||
disparaitre du panneau. None si aucun underlay n'est monte.
|
||||
"""
|
||||
if not FICHIER_UNDERLAY.exists():
|
||||
return None
|
||||
return FICHIER_UNDERLAY.resolve().parent
|
||||
|
||||
|
||||
def _fichier_frontiere() -> Path:
|
||||
"""group_vars/opnsense.yml de l'hebergeur ; repli sur l'instance active."""
|
||||
base = _depot_hebergeur()
|
||||
if base:
|
||||
for nom in ("principal", "production", "lab"):
|
||||
p = base / "inventories" / nom / "group_vars" / "opnsense.yml"
|
||||
if p.is_file():
|
||||
return p
|
||||
return INVENTAIRE_DEFAUT.parent / "group_vars/opnsense.yml"
|
||||
|
||||
|
||||
INTRANTS_FRONTIERE = _fichier_frontiere()
|
||||
FICHIERS_INTRANTS = {"identite": INTRANTS_IDENTITE, "proxmox": INTRANTS_PROXMOX,
|
||||
"reseau": FICHIER_NOMENCLATURE, "frontiere": INTRANTS_FRONTIERE,
|
||||
"fabric": FICHIER_UNDERLAY}
|
||||
|
|
|
|||
Loading…
Reference in a new issue