devis_opnsense prend dans la face publiee les adresses des groupes, l'intrant d'administration, les verdicts, le tunnel et les zones publiees. Devis identique a l'octet pres, a HEAD comme par le repli. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
17 KiB
Contextes : un tronc commun, deux classes (SITE et LOCATAIRE)
Pour qui : le mainteneur — comment le moteur sait s'il sert un site ou un locataire, et ce que les deux s'apprennent l'un à l'autre.
Statut : arrêtée avec l'exploitant le 2026-10-04. Rien n'est encore construit ; les décisions sont au §7, le chemin au §6.
1. Le problème, mesuré
Le moteur ne sait pas dans quel contexte il tourne : chaque script le devine. Relevé du 2026-10-04 : 33 scripts font leur propre déduction, à partir de cinq indices différents.
| Indice | Ce qu'on en déduit | Scripts |
|---|---|---|
lien instance/ ou SETOPS_INSTANCE |
« un locataire est monté » | 23 |
lien underlay.yml ou SETOPS_UNDERLAY |
« un site est monté » ; son plan est à côté | 8 |
SETOPS_INVENTAIRE |
quel inventaire de locataire lire | 8 |
../*/plan/nomenclature.yml |
« la fédération », les locataires frères | 11 |
../SITE-*/underlay.yml |
« les sites » | 2 |
Les indices ne concordent pas toujours, et chaque désaccord a déjà produit un défaut silencieux :
- 2026-08-14 :
frontiere-planvoulait poser sur la frontière de Technolibre les règles de Chezlepro. « La fédération » valait « les locataires de ce site », jusqu'au second site. - 2026-09-16 : la console du runner du site affichait zéro machine, sans erreur. Elle cherchait un inventaire de locataire là où il n'y en a pas.
- 2026-10-04 :
make ci, sur le poste, mélangeait le modèle public et les écosystèmes réels. P74 lisaitSETOPS_UNDERLAY(le modèle) ; P82 lisait le lienunderlay.ymlet les dossiers frères (le site réel).
Les rôles Ansible, eux, ne posent pas ce problème : ils sont déjà le tronc commun. Un même
serveur_postgresql sert au site et chez un locataire.
2. Le modèle
Ecosysteme (tronc commun)
/ \
Site Locataire
\ /
`-- contrat --' (associations : un site A des locataires,
un locataire A un site)
2.1 Le tronc commun : Ecosysteme
Ce que tout écosystème possède, quel que soit son contexte :
- un nom et un dépôt (
SITE-Chezlepro,OPS-Technolibre) ; - une voûte et sa clé (
~/.config/setops-vault-<dépôt>) ; - un plan (
<dépôt>/plan/) et un index, dont dérive son adressage (le site aussi depuis le 2026-09-20) ; - des machines, déployées par les mêmes rôles : socle, durcissement, PKI, journaux, métriques, supervision, sauvegarde de son propre état ;
- une filiation : le moteur et le commit dont il descend ;
- les preuves communes : lint, rendu des gabarits, adressage dérivé, etc.
Méthodes abstraites, que chaque classe surcharge : inventaire(), machines(),
preuves(), verbes(), console().
2.2 Site(Ecosysteme)
- Déclaration :
underlay.yml(le matériel, les réseauxsiteetfabric) etplan/(10-intrants.yml, serveurs, applications, domaines, bases). - Inventaire : dynamique (
site_inventaire.py). Le site ne dérive rien d'un plan de services ; sa déclaration est sa forme finale. - Ce qu'il porte en propre : le matériel (hyperviseurs, commutateurs, frontière), la matérialisation des VM (Proxmox), le SDN, le pare-feu Proxmox, la frontière OPNsense, le DNS public, le dépôt des sauvegardes des locataires, le cache et les artefacts, la forge du génome.
- Relation :
locataires(), la liste deunderlay.tenantsrésolue en objetsLocataire. Le site n'en lit que la face réseau (§2.4).
2.3 Locataire(Ecosysteme)
- Déclaration :
plan/(nomenclature, serveurs, applications, bases, domaines). - Inventaire : généré (
instancier.py→hosts.yml). C'est la méta-classe demeta-classe.md: une définition qui engendre toute la flotte. - Ce qu'il porte en propre : la configuration de ses services, la remise au client.
- Relation :
site(), l'hébergeur que nommeparente.yml, résolu en objetSite. Le locataire n'en lit que les intrants exposés (§2.4).
2.4 Ce que le site et le locataire s'apprennent l'un à l'autre
Les deux entités s'informent mutuellement. Relevé du 2026-10-04 : qui décide de chaque information, où elle vit, et comment elle parvient à l'autre.
Ce que chacun a sous la main. Le runner du site porte le moteur, son dépôt et ceux de ses locataires (sans leurs voûtes). Le runner d'un locataire ne porte que le moteur et son propre dépôt. Le poste porte tout.
Le site informe le locataire, par trois canaux
Canal 1 : des copies écrites à la main dans le dépôt du locataire.
| Information | Décidée par | Tenue chez le site dans | Copiée chez le locataire dans | Contrôle |
|---|---|---|---|---|
| son index | le site | underlay.yml → tenants |
plan/nomenclature.yml (index) |
underlay valider |
| son adresse publique | le site | opnsense.yml → opnsense_ips_publiques |
10-intrants.yml (ip_publique) |
— |
| les 10 intrants de service : résolveur, cache, binaires, forge du génome, cible de sauvegarde, DNS public, plan d'administration, passerelle | le site (dérivés de son plan, par site_intrants.py) |
son plan | 10-intrants.yml, et serveur_ops.yml pour la forge |
site_intrants.py --verifier, seulement là où les deux dépôts sont présents (le poste) |
| sa racine de confiance | le site | ac-racine-site.crt |
le même fichier, copié | — |
| hors contrat : un dépôt de la forge du site désigné par son adresse | — | — | serveur_web_dorsal.yml (Chezlepro) |
aucun |
Canal 2 : une lecture directe, au moment de générer l'inventaire. instancier.py ouvre
l'underlay.yml et le plan du site pour écrire le hosts.yml du locataire. Mesuré sur
Technolibre, inventaire généré avec puis sans le site monté : quatre variables changent.
| Variable du locataire | Avec le site monté | Sans le site |
|---|---|---|
chrony_serveurs |
10.0.4.1 (la frontière) |
absente |
proxmox_pont |
t23appl (le VNet SDN) |
absente |
proxmox_etiquette_vlan |
aucune (le SDN étiquette) | 1236 |
serveur_resolveur_zones_deleguees |
genese.internal → 10.37.34.11 |
absente |
Canal 3 : le réseau. Le runner du locataire tire son génome de la forge du site ; il est né de l'insémination par le runner du site.
Le locataire informe le site : le site lit et recalcule
| Information | Décidée par | Tenue chez le locataire dans | Parvient au site par |
|---|---|---|---|
| ses zones et son adressage | dérivés de l'index | plan/nomenclature.yml |
le site lit le fichier |
| les VM à matérialiser | le locataire | plan/serveurs.yml → hosts.yml |
le site lit les fichiers (placement, clonage, pools, SDN) |
| ses flux | ses rôles et son plan | meta/flux.yml des rôles (moteur), croisés avec son hosts.yml |
le site recalcule lui-même, avec sa version du moteur et la totalité de l'inventaire du locataire → frontière, NAT, pare-feu Proxmox |
| ses domaines publics | le locataire | plan/domaines.yml (+ applications.yml, serveurs.yml) |
le site lit les fichiers → DNS public secondaire |
| sa clé de sauvegarde | le locataire | inventories/*/group_vars/serveur_backup.yml |
le site lit le fichier → compte Unix sur le dépôt |
| ses accès d'administration | le locataire | plan/acces.yml, nftables_admin_ssh |
le site lit les fichiers → pairs WireGuard, règles d'administration |
Le SDN, lui, ne prend aucun flux : il ne filtre pas. Il ne reçoit que l'index, dont il dérive la zone, les 6 VNets et les 6 sous-réseaux.
À l'exécution, entre machines
Ces échanges-là passent par le réseau, pas par les dépôts. Ils sont déjà déclarés en flux : le locataire dépose ses sauvegardes chez le site (SFTP), tire ses paquets, ses binaires et son génome, entre par le tunnel d'administration du site ; le site réplique les zones publiques du locataire (AXFR signé TSIG).
Ce que le relevé montre
- Le site fouille l'intérieur du locataire. Six fichiers de son plan et de son inventaire,
group_varscompris. Rien ne dit ce que le locataire accepte de montrer. Renommer un champ chez le locataire casse le site sans bruit. - Les flux sont calculés deux fois, par le locataire pour ses
nftableset par le site pour la frontière et Proxmox, chacun avec sa version du moteur. Ils concordent tant que les deux runners tiennent le même commit (c'était le cas le 2026-10-04), mais rien ne l'impose. - Le locataire vit de copies : douze valeurs et un certificat, recopiés à la main, plus une valeur hors contrat. La garde qui compare ne tourne que sur le poste ; le runner du locataire ne peut pas savoir que sa copie a vieilli.
- L'inventaire d'un locataire dépend du site monté au moment de le générer. Généré sur le runner du locataire, qui n'a pas le dépôt du site, il perdrait son serveur de temps, son SDN et sa délégation DNS. Ça ne s'est jamais vu, parce que l'inventaire est toujours généré sur le poste puis versionné.
- Deux décisions du site (l'index, l'adresse publique) vivent en double.
Proposition : deux fiches, une dans chaque sens
Chacun publie ce qu'il donne à l'autre, dans une fiche générée par le moteur, jamais écrite à la main. Chacun ne lit que la fiche que l'autre lui destine. Plus aucune lecture croisée, plus aucun recalcul.
- La fiche du site pour un locataire. Tout ce que le site lui attribue (index, adresse publique) et lui offre : les 10 intrants, sa racine de confiance, et ce que l'instancier allait lire en douce (serveur de temps, délégation DNS, mode SDN et nom des VNets). Une fiche par locataire : aucun ne voit le plan du site ni ses voisins. L'instancier ne lit plus que cette fiche, et l'inventaire devient identique où qu'on le génère.
- La face réseau du locataire. Ce qu'il demande au site : VM à matérialiser, zones,
domaines publics, clé de sauvegarde publique, accès d'administration, et ses flux déjà
résolus (adresses, ports, protocoles), ceux avec l'extérieur pour la frontière et ceux de
chaque VM pour Proxmox. Le locataire génère déjà ses flux résolus (
flux-genere/*.nft,*.connectivite.json) : la face réseau en est la partie destinée au site. Le site ne recalcule plus rien : il applique ce que le locataire publie, après l'avoir confronté à sa propre politique. - Chaque fiche porte l'empreinte de sa source, et une preuve de chaque côté vérifie que la fiche reçue correspond à ce que l'autre a publié.
Où en est l'étape 2 (2026-10-04). La fiche du site (P84), les faits de la face réseau
(P85) et les flux de chaque machine (P86) existent, et disent exactement ce que les lectures
croisées produisent. La première mesure des flux a trouvé une information que le locataire
jetait : les clients nommés d'un port aussi public, que Proxmox doit admettre nommément. Il
les publie désormais (sources_declarees). La frontière, en trois temps : les identités
(P87), les entrées publiques (P88), l'administration (P89) et les sorties (P90) sont faites.
L'étape 2 est terminée (2026-10-05). Étape 3 : l'instancier lit la fiche déposée par le site, et
l'inventaire d'un locataire se génère sans son site, à l'octet près (P91). Le locataire publie sa face
réseau (P92) ; les comptes de sauvegarde, le DNS public, le pare-feu Proxmox et la frontière du site
la lisent.
Méthodes du contrat : site.fiche_pour(locataire), locataire.face_reseau(). Une classe
n'ouvre jamais les fichiers de l'autre ; une preuve vérifiera la règle.
Ce que les fiches donnent : la portabilité
Un locataire qui change de site, pour un déménagement, un plan de reprise ou une émancipation, n'a plus qu'à recevoir la fiche de son nouveau site. Son dépôt ne contient plus rien d'interne à l'ancien : ni copie d'adresse, ni inventaire généré avec l'ancien site monté. En face, le nouveau site n'a qu'à lire sa face réseau. Aujourd'hui, la même bascule demande de corriger des copies dans plusieurs fichiers, puis de régénérer l'inventaire avec le nouveau site monté sur le poste.
3. Le poste : un sélecteur
Aujourd'hui, le poste monte les deux contextes en même temps (instance/ + underlay.yml),
et ConsolePoste hérite de ConsoleLocataire. Désormais :
- Le poste choisit un contexte actif : un site ou un locataire. La console ouvre celui-là, et seulement celui-là.
- Une opération qui traverse les deux nomme ses objets au lieu de les deviner.
reconstruire-locataireen est l'exemple : le site matérialise, puis le locataire monte. L'orchestration devientsite.materialiser(locataire)puislocataire.monter(). - Les runners ne changent pas : celui du site n'a qu'un
Site, celui d'un locataire qu'unLocataire. Leur contexte est désormais dit, plus déduit.
4. Qui surcharge quoi
| Méthode | Tronc commun | Site | Locataire |
|---|---|---|---|
inventaire() |
abstraite | script dynamique (underlay.yml) |
hosts.yml généré du plan |
machines() |
abstraite | VM du site + équipements | VM du plan |
adressage() |
dérivé de l'index | zones site dérivées, liens fabric écrits |
6 zones dérivées |
sauvegardes() |
son propre état, vérifié par restauration | + héberge les dépôts des locataires | dépose chez son site |
supervision() |
sondes déclarées par les rôles | + matériel, fabric, frontière | — |
raser() / reconstruire() |
— | site_raser.py |
raser.py, reconstruire_locataire.py |
preuves() |
preuves communes | + preuves de site (P23, P74, P82…) | + preuves de locataire |
verbes() |
verifier, publier… |
site-*, frontiere-*, proxmox-* |
appliquer, instancier, remise-* |
console() |
— | console SITE | console LOCATAIRE |
5. Ce qui ne change pas
- Les rôles Ansible, déjà communs.
- Les formats de plan : pas dans ce chantier.
- La doctrine d'
AGENTS.md.
6. Le chemin, chaque pas prouvé avant le suivant
scripts/contexte.py:Ecosysteme,Site,Locataire,contexte_actif(),Site.charger(nom),Locataire.charger(nom). Tests unitaires. Rien ne l'utilise encore.- Les deux fiches, générées à côté de l'existant sans rien remplacer :
site.fiche_pour(locataire)etlocataire.face_reseau(). Une preuve vérifie que chaque fiche dit exactement ce que les lectures croisées d'aujourd'hui produisent. - Les consommateurs basculent sur les fiches, un par un : l'instancier sur la fiche du site (l'inventaire généré doit rester identique, octet pour octet) ; la frontière, Proxmox, le DNS public et les comptes de sauvegarde sur la face réseau (chaque devis doit rester inchangé).
prouver.py: chaque preuve déclare son contexte (commun, site, locataire) et reçoit son écosystème du module.- Les autres scripts, un par un, vérifiés par
make verifieret par un devis inchangé. - Une preuve « aucune devinette, aucune lecture croisée » : les indices du §1, et toute
ouverture d'un fichier de l'autre contexte, interdits hors de
contexte.py. - Les verbes du Makefile rangés par contexte.
- Les consoles : le sélecteur et deux consoles (chantier suivant).
- OPS-Modele : un locataire modèle, et sans doute un site modèle, vérifiés chacun dans son contexte.
Après les étapes 3 et 5, une reconstruction prouve que la flotte n'a pas bougé.
7. Décisions et questions ouvertes
Tranché par l'exploitant
-
Deux classes,
SiteetLocataire, qui héritent d'un tronc commun (2026-10-04). -
Le poste est un sélecteur : un contexte actif à la fois (2026-10-04).
-
On commence par le moteur, les consoles viennent ensuite (2026-10-04).
-
Le site dépose sa fiche dans le dépôt du locataire (2026-10-04), comme il y amorce déjà son runner. Le runner du locataire n'a besoin d'aucun accès au dépôt du site, et ne voit ni le plan du site ni ses voisins.
-
Le contexte actif se nomme dans un fichier
contexteexplicite (2026-10-04), une seule valeur :site:SITE-Chezleprooulocataire:OPS-Technolibre. Un sélecteur qui monte deux liens à la fois contredirait sa propre règle. Les liensinstance/etunderlay.ymlrestent le temps de la bascule, lus par le seulcontexte.py. -
Un modèle SITE public,
SITE-Modele, à côté d'OPS-Modele(2026-10-04). Sans site, la CI ne peut exercer ni les preuves de site (P74, P81, P82) ni la fiche que le site dépose chez le locataire. -
Le site a aussi son
parente.yml(2026-10-04) : la filiation est dans le tronc commun.