flux : le mot voisins_site, et l'aveu de ce qui manque pour s'en servir
Le registre ne savait pas dire « les autres tenants de ma fabric ». ingress + externe signifie DEPUIS L'INTERNET : declarer ainsi un cache partage l'aurait publie au monde. voisins_site rend les supernets des tenants que CE SITE heberge, en reutilisant devis_reseau.decouvrir_du_site() plutot qu'en ecrivant un second recensement. Ma premiere version lisait un `federe` absent comme « non federe » et excluait Chezlepro et Technolibre en silence -- la decouverte canonique dit l'inverse. LE CHAINAGE DES CACHES N'EST PAS LIVRE. Aucun generateur ne rend ce mot : zero regle 3142 au devis de frontiere. Les declarations ont donc ete RETIREES plutot que laissees a moitie -- un flux declare que personne n'applique est le piege que ce depot traque. La difficulte est structurelle : les regles OPNsense s'evaluent sur l'interface d'ARRIVEE, donc une regle inter-tenant doit etre posee sur l'interface du VOISIN. Le generateur construit tenant par tenant, sur les interfaces de ce tenant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
12d938cdb0
commit
6547eb2fac
4 changed files with 113 additions and 6 deletions
39
CHANGELOG.md
39
CHANGELOG.md
|
|
@ -1,5 +1,44 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-24 — `voisins_site` : le vocabulaire d'un flux entre tenants, et le mur qu'il révèle
|
||||
|
||||
Le registre des flux connaissait `flotte` (mon écosystème), `edge`, `admin` et `externe`.
|
||||
Il ne savait pas dire **« les autres tenants de ma fabric »** — et `ingress` + `externe`
|
||||
signifie *depuis l'Internet* : déclarer ainsi un cache partagé l'aurait **publié au monde**.
|
||||
|
||||
`voisins_site` existe maintenant dans `resoudre_flux`. Il rend des CIDR — les supernets
|
||||
des tenants que **ce site** héberge — et réutilise `devis_reseau.decouvrir_du_site()`
|
||||
plutôt que d'en écrire un second recensement. Deux listes de tenants finiraient par
|
||||
diverger, et la divergence se lirait « tout va bien ».
|
||||
|
||||
Vérifié : depuis patient 0, il rend `10.17.0.0/16` et `10.23.0.0/16`. Le lab, `federe:
|
||||
false`, est correctement exclu.
|
||||
|
||||
**Une faute évitée de justesse.** Ma première version lisait un `federe` absent comme
|
||||
« non fédéré » et excluait Chezlepro et Technolibre — leur nomenclature est antérieure à
|
||||
cette clé. `devis_reseau` dit `n.get("federe", True)` : **l'absence vaut fédéré**. Réutiliser
|
||||
sa découverte plutôt que d'en réécrire une a fermé le piège au passage.
|
||||
|
||||
### Ce qui n'est PAS fait, et pourquoi
|
||||
|
||||
Le chaînage des caches — chaque écosystème garde le sien, qui prend celui de l'hébergeur
|
||||
comme amont, pour que Debian ne soit téléchargé qu'une fois par site — **n'est pas livré**.
|
||||
|
||||
Le vocabulaire est accepté, mais **aucun générateur ne le rend** : `make frontiere-plan`
|
||||
produit zéro règle pour le port 3142 entre tenants. Un flux déclaré que personne
|
||||
n'applique est précisément le piège que ce dépôt traque, alors les déclarations ont été
|
||||
retirées plutôt que laissées à moitié.
|
||||
|
||||
La difficulté est structurelle, pas cosmétique. Les règles d'OPNsense s'évaluent sur
|
||||
l'**interface d'arrivée** : un paquet venant de Chezlepro vers le cache de patient 0 arrive
|
||||
sur l'interface *de Chezlepro*, donc la règle doit être posée **là**. Or le générateur
|
||||
construit ses règles tenant par tenant, sur les interfaces de ce tenant. Émettre des règles
|
||||
croisées change la forme de ce qu'il produit — et ses propres commentaires répètent qu'une
|
||||
règle posée sur la mauvaise interface ne correspond jamais à un paquet.
|
||||
|
||||
Ce qui reste en place : le mot `voisins_site`, éprouvé ; et `serveur_artefacts_amont`, la
|
||||
capacité de chaînage côté rôle. Il manque le rendu, dans les deux générateurs de pare-feu.
|
||||
|
||||
## 2026-08-24 — Un résolveur par tenant, et non plus un par machine
|
||||
|
||||
`client_unbound` posait un Unbound sur **chaque VM**. C'était étanche, et c'était N démons
|
||||
|
|
|
|||
|
|
@ -45,3 +45,21 @@ serveur_artefacts_hors_ligne: false
|
|||
# Journalisation : apt-cacher-ng tient un journal des requêtes servies. C'est lui qui
|
||||
# permet de répondre à « ce paquet, l'avons-nous servi ou est-il venu du dehors ? ».
|
||||
serveur_artefacts_journal: "/var/log/apt-cacher-ng"
|
||||
|
||||
# --- LE CHAINAGE DES CACHES (2026-08-24) --------------------------------------
|
||||
#
|
||||
# Chaque écosystème garde SON cache, et celui-ci prend le cache de l'hébergeur comme
|
||||
# amont. Debian n'est alors téléchargé qu'UNE FOIS pour tout le site :
|
||||
#
|
||||
# VM du tenant -> cache du tenant -> cache du SITE -> Debian
|
||||
#
|
||||
# POURQUOI PAS UN CACHE UNIQUE OÙ TOUTES LES VM TAPENT. Il aurait fallu ouvrir un flux de
|
||||
# chaque machine de chaque tenant vers un hôte d'un autre tenant — N×M chemins à travers
|
||||
# le default-deny. Avec le chaînage il n'y en a qu'UN par écosystème, entre deux caches.
|
||||
#
|
||||
# Et l'étanchéité y gagne : le cache du site ne voit que des requêtes AGRÉGÉES, jamais
|
||||
# quelle machine installe quoi.
|
||||
#
|
||||
# Vide = ce cache va directement à l'amont Debian. C'est le cas du cache du SITE lui-même,
|
||||
# et de tout écosystème qui s'est émancipé (docs/filiation-emancipation.md).
|
||||
serveur_artefacts_amont: ""
|
||||
|
|
|
|||
|
|
@ -17,3 +17,9 @@ Offlinemode: 1
|
|||
{% else %}
|
||||
Offlinemode: 0
|
||||
{% endif %}
|
||||
|
||||
{% if serveur_artefacts_amont %}
|
||||
# CHAÎNAGE : ce cache passe par celui de l'hébergeur plutôt que d'aller chez Debian.
|
||||
# Debian n'est ainsi téléchargé qu'une fois pour tout le site.
|
||||
Proxy: {{ serveur_artefacts_amont }}
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,20 @@ CHIFFREMENT = {"tls-requis", "tls", "starttls", "ssh", "tls-cible", "clair", "n-
|
|||
# `admin` : les RESEAUX d'administration (intrant `nftables_admin_ssh`), pas des
|
||||
# hotes. C'est le troisieme chemin d'arrivee, distinct de `flotte` (le tenant) et
|
||||
# d'`externe` (Internet) — l'exploitant n'est ni l'un ni l'autre.
|
||||
MOTS_PAIR = {"edge", "flotte", "externe", "localhost", "expositions", "derive", "admin"}
|
||||
# `voisins_site` : les autres tenants FEDERES de la meme fabric. Ni `flotte` (mon
|
||||
# ecosysteme), ni `externe` (l'Internet) — un quatrieme chemin d'arrivee, celui du
|
||||
# VOISINAGE.
|
||||
#
|
||||
# POURQUOI CE MOT EXISTE (2026-08-24). Le cache d'artefacts se CHAINE : chaque ecosysteme
|
||||
# garde le sien, et celui-ci prend le cache de l'hebergeur comme amont. Debian n'est alors
|
||||
# telecharge qu'une fois pour tout le site. Sans ce mot, la seule facon de declarer ce flux
|
||||
# aurait ete `ingress` + `externe` — qui signifie « depuis l'Internet » et aurait PUBLIE le
|
||||
# cache au monde entier.
|
||||
#
|
||||
# Il rend des CIDR (les supernets des voisins), pas des adresses d'hotes : il est donc
|
||||
# traite a part, comme `admin`.
|
||||
MOTS_PAIR = {"edge", "flotte", "externe", "localhost", "expositions", "derive", "admin",
|
||||
"voisins_site"}
|
||||
GROUPE_EDGE = "serveur_nginx"
|
||||
|
||||
|
||||
|
|
@ -205,10 +218,10 @@ def _resoudre_sources(data: dict, pair, actifs_noms: set[str], ip_par_hote: dict
|
|||
for p in (pair if isinstance(pair, list) else [pair]):
|
||||
if p in ("localhost", "externe", "expositions", "derive"):
|
||||
continue # lo (auto), frontiere OPNsense, ou hors perimetre noeud
|
||||
if p == "admin":
|
||||
# `admin` n'est pas un groupe d'hotes mais des RESEAUX : l'intrant
|
||||
# `nftables_admin_ssh`, deja source unique de la garde anti-lockout.
|
||||
# Il est traite a part parce qu'il rend des CIDR, pas des adresses.
|
||||
if p in ("admin", "voisins_site"):
|
||||
# Ni l'un ni l'autre n'est un groupe d'hotes : ce sont des RESEAUX. `admin`
|
||||
# vient de l'intrant `nftables_admin_ssh` ; `voisins_site` des supernets des
|
||||
# autres tenants federes. Traites a part, plus bas.
|
||||
continue
|
||||
if p == "flotte":
|
||||
noms |= actifs_noms
|
||||
|
|
@ -216,11 +229,42 @@ def _resoudre_sources(data: dict, pair, actifs_noms: set[str], ip_par_hote: dict
|
|||
groupe = GROUPE_EDGE if p == "edge" else p
|
||||
noms |= {h for h in _hotes_du_groupe(data, groupe) if h in actifs_noms}
|
||||
srcs = {ip_par_hote[h] for h in noms if h in ip_par_hote}
|
||||
if "admin" in (pair if isinstance(pair, list) else [pair]):
|
||||
demandes = pair if isinstance(pair, list) else [pair]
|
||||
if "admin" in demandes:
|
||||
srcs |= set(_sources_admin_ssh())
|
||||
if "voisins_site" in demandes:
|
||||
srcs |= set(_supernets_voisins())
|
||||
return sorted(srcs)
|
||||
|
||||
|
||||
def _supernets_voisins() -> list[str]:
|
||||
"""Les supernets des AUTRES tenants que CE SITE heberge.
|
||||
|
||||
ON REUTILISE `devis_reseau.decouvrir_du_site()`, on n'en ecrit pas une deuxieme
|
||||
version. C'est la lecon de P41 appliquee ici : deux recensements de tenants finiraient
|
||||
par diverger, et la divergence se lirait « tout va bien ».
|
||||
|
||||
Elle porte aussi la bonne semantique. « Voisin » veut dire *sur ma fabric*, pas
|
||||
*quelque part dans la federation* : un tenant pose sur un autre site n'est joignable
|
||||
par aucune regle de cette frontiere-ci, et lui ouvrir un flux serait un chemin qui ne
|
||||
correspondra jamais a un paquet.
|
||||
|
||||
Elle porte enfin la regle du `federe` ABSENT = federe (devis_reseau, ligne 568). Ma
|
||||
premiere version lisait l'absence comme « non federe » et excluait Chezlepro et
|
||||
Technolibre en silence — leur nomenclature est anterieure a cette cle.
|
||||
"""
|
||||
try:
|
||||
import devis_reseau
|
||||
from inventory_rules import supernet_de
|
||||
tenants = devis_reseau.decouvrir_du_site()
|
||||
except Exception:
|
||||
# Sans underlay designe (ex. `make flux` hors site), il n'y a pas de voisinage a
|
||||
# resoudre. Rendre [] plutot qu'echouer : l'absence de voisins n'est pas une faute.
|
||||
return []
|
||||
moi = INSTANCE.resolve().name
|
||||
return sorted({supernet_de(int(n["index"])) for nom, _p, n in tenants if nom != moi})
|
||||
|
||||
|
||||
# Codes ICMP de destination inatteignable qu'un flux peut nommer dans `port`. L'ICMP n'a
|
||||
# PAS de port : il a un type et un code. Emettre `icmp dport frag-needed` produit un jeu
|
||||
# que `nft` refuse — et le service ne demarre plus du tout, donc l'hote perd sa barriere
|
||||
|
|
|
|||
Loading…
Reference in a new issue