Deux corrections, et la premiere est une faute que j ai ecrite ce soir.
La fonction appelait underlay_mod — un nom qui n existe pas dans ce fichier, le
module y est importe as U. Python levait un AttributeError a chaque appel, et
mon except Exception: return False le rendait MUET. La fonction repondait donc
toujours je ne suis pas une machine du site, le rebond etait toujours pose, et
rien ne le disait. Une garde qui se tait ne garde rien.
Le except ne couvre plus qu un plan illisible. Une faute de programmation
remonte.
Et cle_ssh subit le meme traitement que le rebond : il vaut le chemin de la cle
sur le POSTE. Impose au runner, il designe un fichier absent chez lui. Les deux
decrivent COMMENT ON ARRIVE, pas ce vers quoi on va.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Troisieme formulation, et les deux premieres etaient fausses.
Suis-je dans un RESEAU du site : le poste porte 10.37.0.17, donc il y est, et il
aurait perdu son rebond alors qu il en a besoin. Etre dans un reseau ne dit rien
de ce qu on atteint.
Mon adresse est-elle dans underlay.hotes : hotes ne contient que les EQUIPEMENTS.
Les machines du site vivent dans son PLAN, et le module le dit lui-meme. La
comparaison portait sur une liste vide et rendait toujours False, sans rien
signaler — une garde muette qui ne gardait rien.
Le runner s appelle site-ops-01, et c est une cle du plan du site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Deux defauts qui s additionnaient, et un message qui accusait le mauvais.
1. Le site n avait AUCUN moyen d autoriser une cle d administration. Un
locataire declare ssh_baseline_cles_admin ; cote site, rien ne transmettait
cette liste. Ses machines n autorisaient que la cle de cloud-init.
2. Le rebond etait pose sans condition. Depuis le poste c est juste — aucune
route directe. Depuis le runner du site, qui vit DANS le site, c est un
detour qui casse : il doit s authentifier aupres de la frontiere ou sa cle
n est pas autorisee.
Et OpenSSH rend alors Host key verification failed / Connection closed by
UNKNOWN port 65535 — un message qui accuse les cles d HOTE alors que l echec
est une AUTHENTIFICATION, et sur le SAUTEUR, pas sur la cible.
Le critere du rebond est desormais es-tu une MACHINE du site, pas es-tu dans un
RESEAU du site : le poste porte 10.37.0.17, donc il est dans le reseau
management, et il aurait perdu son rebond alors qu il en a besoin.
L amorcage est circulaire et se rompt par le poste : lui seul entrait, il pose
la cle, le runner est ensuite autonome. Verifie : 5/5 machines du site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
L entree du 2026-09-13 affirmait aucun maxmemory configure. Faux : le grep
visait /etc/redis/redis.conf alors que le role ecrit setops.conf. Interroge la
ou la verite vit, redis repond maxmemory 268435456 et allkeys-lru.
La conclusion ne change pas — redis reste hors de la table des planchers — mais
sa raison change : il est borne a 256 Mo, il ne peut pas prendre la machine.
Un fichier de configuration n est pas la configuration ; c est une de ses
sources.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Un runner tire ce qu on pousse, il ne le recoit pas. Trois fois ce soir un
genome perime a menace de rebatir un etat depasse — et aucun des deux runners
n aurait echoue : un deploiement depuis un genome perime REUSSIT. Il applique
fidelement un etat qui n a plus cours, et se presente en vert.
En retard : refus. En avance ou diverge : note et on continue. Forge
injoignable : note et on continue — une forge en panne ne doit pas immobiliser
une exploitation, le silence serait la faute.
FORCE=1 passe outre. Eprouvee dans les trois sens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Le controle rapportait CONFORME sur un locataire dont le plan d administration
pointait les bouts de WAN d un AUTRE site. nftables_admin_ssh et
passerelle_sortie n etaient compares par personne.
Quatorze machines debout, le runner insemine, et l exploitant bloque a la porte
sans qu aucune garde n ait rien eu a dire.
nftables_admin_ssh est une liste : la comparaison normalise, parce que l ordre
d une liste de sources ne porte aucun sens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
La cle d amorcage du plan est une transcription de la cle publique du runner du
site. Reconstruire le runner lui donne une paire neuve ; la transcription, elle,
ne bouge pas. Meme algorithme, meme commentaire, materiel different : rien ne
distingue les deux a l oeil.
La garde ne demande rien au reseau — le runner du site EST la machine qui
insemine, sa cle est sous sa main. Elle ne compare que si les deux cles nomment
le meme hote : lancee depuis le poste d un exploitant, elle se tait.
Elle dit aussi ce que la correction seule ne suffit pas a reparer : une machine
deja creee porte la cle perimee, il faut la RECREER.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
instancier le derivait, le playbook savait le poser, et la table qui les relie
ne transmettait rien. Quatorze machines allaient naitre avec le ballooning
desactive sans qu une seule etape echoue : variable vide, extra-var non passee,
garde when: qui saute. Trois silences en file.
P75 lit la cible creer-vm, releve chaque $SETOPS_X qu elle consomme et exige
que la table qui les emet le declare. Eprouvee dans les deux sens.
74 OK, 0 echec, 1 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
flux-genere/ ecrivait sans jamais retirer. Un hote sorti du plan y laissait son
ruleset complet, indiscernable des vivants. Le dossier cessait d'etre une image
du plan pour devenir le cumul de tous les plans successifs.
P53 voyait l'orphelin — un ruleset perime n'a pas de refus audible — mais
designait le fichier, pas la cause.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
61 Go declares a 21 machines, 12 reellement utilises. balloon valait 0 partout,
ce qui ne desactive pas le ballooning : cela RETIRE le peripherique de la ligne
de commande QEMU. Le moniteur repond "No balloon device has been activated".
Le moteur derive desormais un plancher par machine sur les deux chemins de
creation, et le playbook de clonage le pose a cote de memory.
La table des planchers a ete corrigee deux fois par la mesure : shared_buffers
vaut 128 Mo et non une part de la RAM, une JVM tient 605 Mo, Redis 16 Mo. Ces
machines tiennent du cache de pages, pas un jeu de donnees.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Le plan du site disait 9006 (modeleSetOPS-minimal), l underlay 99998 — que le plan
nomme lui-meme `precedent`. Le Makefile derive VMID_MODELE du PLAN : les locataires
clonaient 9006. `site_machines` lisait l autre : les machines du SITE clonaient 99998.
POURQUOI CA NE S EST JAMAIS VU. Les deux VMID designent un gabarit valide. Les deux
clonent, les deux demarrent, rien n echoue. La flotte etait issue de deux souches, et
aucun message ne pouvait le dire puisqu aucune operation n avait echoue.
`site_machines` lit desormais underlay.gabarit(), la meme source que tout le monde. Le
motif de la seconde declaration est preserve : cette source lit le PLAN DU SITE, jamais
celui du tenant actif, donc elle ne depend d aucun symlink `instance`.
materialisation.vmid_modele retire des deux cartes. `precedent:` reste au plan — il dit
d ou l on vient et n est jamais clone.
P74 refuse toute redeclaration. Eprouvee dans les deux sens.
SITE-Technolibre visait 99998, repris de la carte de Chezlepro au lieu de son plan.
Corrige avant son premier clonage.
make prouver : 73 OK, 0 echec, 1 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
`--support-chiffre` ecrit un tar clair quand le support est deja chiffre au repos.
Ajoute cote EXPORT le 12 septembre, et pas cote RESTAURATION : `restaurer_cles.py`,
qui est ce qui rend la cle USB autonome, ne savait lire que du GPG.
Constate en EPROUVANT l export, pas en le supposant reussi. Et le message etait le
pire possible :
gpg: aucune donnee OpenPGP valable n a ete trouvee.
ECHEC du dechiffrement. Phrase de passe erronee, ou archive abimee.
Il accusait une phrase de passe qu on n avait jamais posee, sur une archive saine. Il
envoyait chercher la faute la ou elle n est pas — au moment ou l on restaure, c est-a
-dire au moment ou l on sait le moins.
`tarfile.is_tarfile` reconnait la forme. Pas de drapeau a passer : ce serait une chose
de plus a savoir le jour ou tout a brule.
Verifie sur la cle reelle : l archive du 13 se liste, dix fichiers, chacun avec sa
destination. Le garde-fou a par ailleurs refuse d ecraser celle du 12 — « on n ecrase
pas une sauvegarde de cles : elle est peut-etre la seule » — donc la nouvelle est
datee et l ancienne reste.
make prouver : 72 OK, 0 echec, 1 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Un locataire ECRIT les adresses des services de son site — resolveur, cache, forge,
depot de sauvegarde, plan d administration, sortie. Une copie se perime, et deux
l avaient fait en deux jours avec la meme forme : `serveur_ops_forge_amont` visait
10.0.33.11 quand la forge sert en 10.37.33.11, et `ac-racine-site.crt` portait la
racine d avant la reconstruction du site. Rien ne les relisait.
`make site-intrants` lit le plan du site et rend le contrat — sept valeurs, toutes
derivees. `make site-intrants-verifier` les confronte a ce que le locataire declare,
et P73 en fait une preuve. Elle a trouve le defaut de la forge des sa premiere
execution.
Le contrat se DERIVE du plan du site, pas d une liste tenue a part : ajouter un
service prete au site l ajoute au contrat, sans qu on ait a y penser.
P69 RESTREINTE AU COUPLE MONTE. Elle balayait tous les depots OPS-* et les comparait
au site monte. Elle avait raison tant qu un seul site existait : une adresse en
10.x.3z ne pouvait designer que lui. Deux sites decoupent leurs zones de la meme
facon — c est le but, un locataire doit pouvoir habiter l un ou l autre sans se
renumeroter. Le troisieme octet a cesse de distinguer « mon site » d « un autre
site » : 10.31.34.11, juste pour un locataire de TechnoLibre, etait declare faux
parce que Chezlepro etait monte.
Un locataire n appartient a aucun site — il en habite un, choisi par le symlink au
deploiement. La seule paire jugeable est celle qui est montee. Meme portee que P73.
Une marche payee : la premiere version de site_intrants recopiait la resolution
d instance au lieu de la partager. P41 a mordu — neuf modules avaient deja porte
chacun leur copie, et cinq defauts en etaient sortis en cinq jours.
make prouver : 72 OK, 0 echec, 1 saute. ansible-lint : 0 failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Keycloak, Dovecot, Postfix et Icinga Web 2 se liaient TOUS avec cn=admin, le compte
d administration de la base. C est le rootDN : slapd lui fait contourner toutes les
ACL. Un seul secret, quatre services, tous les droits sur l arbre — pour ce qui est,
trois fois sur quatre, une simple lecture.
Et les droits livres par Debian etaient intacts : `to * by * read`. Sur ldap://, sans
s authentifier, une machine du reseau enumerait tous les comptes et toutes les
adresses. Des comptes a droits mesures n auraient rien valu tant que cette ligne
restait : on aurait ferme la porte en laissant la fenetre.
L indice etait deja dans le depot. `validatePasswordPolicy` existe parce que slapd
n applique pas ses controles de qualite au rootDN : la consequence etait compensee,
la cause intacte.
- ou=services, un compte par consommateur, secret propre en voute
- sept regles d acces posees EN ENTIER (state: exact) : l ordre est la regle, et
inserer c est parier sur ce que le paquet aura mis avant nous
- amorcage_acces garde le compte d administration, NOMME comme l exception : il ne
consomme pas l annuaire, il le provisionne depuis la socket locale
- la sonde passe de -x a -Y EXTERNAL : elle lisait en anonyme et aurait annonce un
annuaire VIDE sur un annuaire parfaitement sain
- la rotation du compte d administration devient possible (elle n etait posee qu a
l installation, par debconf : la voute et slapd divergeaient en silence)
Quatre marches payees en chemin :
1. un cinquieme appelant oublie, dont l echec etait masque par no_log — la garde
refuse desormais SANS no_log : elle nomme la cle absente, jamais son contenu
2. la federation Keycloak ne reecrivait son bindDn que si l URL ou le mode changeaient
— nouveau secret, ancien nom, error code 49
3. la rotation placee APRES les taches qui se lient en administrateur
4. ansible-vault et son tube : sortie non bloquante = echec silencieux, la voute
paraissait tournee et etait identique a l octet
P72 exige que tout role incluant resoudre_annuaire NOMME son compte, et qu aucun sauf
amorcage_acces ne nomme admin. Eprouvee dans les deux sens.
Verifie sur l infrastructure : chaque compte lit ce qu il doit, aucun ne voit les
autres, la lecture anonyme rend 0 entree, et les quatre services repondent (doveadm
user, postmap -q, decouverte OIDC 200, portier SSO 200).
vault_openldap_admin et vault_ldap_bind_postfix renouveles : les deux avaient transite
en clair par une session d exploitation. Les anciennes valeurs rendent Invalid
credentials (49).
make prouver : 71 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
`Chezlepro-17` contenait VINGT ET UNE VM : les quatorze du locataire ET les sept du
genome. `OPS-Chezlepro` et `OPS-Technolibre`, crees a la main, etaient vides.
La cause : `site-creer` appelle `cloner-vm`, qui derive son pool par `--pool-actif`,
c est-a-dire le pool du TENANT lie. Les machines du site heritaient du locataire
courant. Range a la main, ca se serait defait au prochain `site-creer` — sans un mot,
parce que la VM est bien creee, bien nommee, bien adressee. Seule son appartenance
est fausse, et rien ne la regarde.
- `--pool-site` rend le nom invariable du pool du genome. Option DISTINCTE, pas un
drapeau sur la premiere : une fonction qui repond aux deux questions finit par se
tromper d appelant.
- `cloner-vm` accepte une surcharge POOL= ; `site-creer` la nomme. Substitution au
niveau MAKE, pas shell : POOL arrive du sur-make comme variable make, et $${POOL}
ne l aurait jamais vue.
- P71 exige que `site-creer` nomme son pool. Eprouvee dans les deux sens : passe sur
le Makefile sain, tire des qu on retire l argument.
Applique au cluster : Site-OPS 9 VM (7 du site + 2 gabarits), OPS-Chezlepro 14,
OPS-Patient0 5. Chezlepro-17, Patient0-29 et Set-OPS supprimes une fois vides. Les
pools anterieurs a Set-OPS et les quinze VM hors pool n ont pas ete touches.
make prouver : 70 OK, 0 echec, 1 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Le cache du site couvrait apt ; quatre artefacts arrivaient autrement, parce qu ils
ne vivent dans aucun depot apt. Le controleur les tire puis les pousse par SSH.
Mesure du 2026-09-12 : le cache du runner du site est ABSENT. Un second locataire
monte depuis lui sortait chercher 570 Mo sur codeberg.org, github.com et
download.nextcloud.com, alors que le meme ecosysteme ne demandait plus un seul
paquet a Debian. Le poste du mainteneur les a depuis toujours : personne ne l avait vu.
Pas de relais transparent, et la mesure tranche : github.com redirige vers une URL
signee valable une heure, differente a chaque requete. Un cache qui la prend pour
cle ne fait jamais mouche. Le relais marcherait pour deux amonts sur quatre.
Donc un vrai depot, dans le service qui existe deja. LocalDirs d apt-cacher-ng publie
un repertoire du disque sous un prefixe, eprouve AVANT d ecrire le role. Aucun service,
aucun port, aucun certificat, aucun flux nouveaux : l ingress 3142 pair flotte couvre
exactement ce chemin.
Les versions ne sont pas recopiees : le role lit les defauts des quatre consommateurs.
Les quatre roles recoivent une tache AJOUTEE, placee avant leur stat de cache — si le
depot sert, le stat le voit et la tache amont se saute d elle-meme. Aucune tache
existante n a change.
P70 exige que tout dest ecrit sous un cache_local figure au depot. Une liste qui suit
une autre prend du retard ; celle-ci est nee avec sa garde.
Deux marches payees en chemin :
- failed_when: false REECRIT le verdict, donc la premiere garde de signature ne
gardait rien. Elles mesurent le fichier desormais.
- file: state=directory cree les parents en 0750 : apt-cacher-ng, qui ne tourne pas
en root, rendait 403 sur chaque fichier. Un chemin se traverse en entier.
Verifie sur l infrastructure : 6/6 artefacts servis (200/206) depuis le runner du site
ET depuis une machine du locataire a travers la frontiere ; les 6 empreintes SHA-256
sont identiques a celles qui ont construit Chezlepro ; second passage changed=0.
make prouver : 69 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production.
Inclut aussi force: true sur cinq telechargements de cles : une reprise conditionnelle
ne reprend rien (304 Not Modified, size 0, attempts 5).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
LE NOM D UN POOL EST CELUI DE SON DEPOT. Chezlepro-17 devient OPS-Chezlepro :
le seed se lisait dans le nom, ce qui obligeait a connaitre le codage — et
surtout le nom CHANGEAIT si l index changeait, ce que la renumerotation du
site a montre le jour meme.
Site-OPS ne derive de rien, et c est le point : les machines du genome ne
dependent d aucun index, elles sont l infrastructure SUR laquelle les index
vivent. Sans ce bloc elles restaient hors de tout pool.
P69 — l amorcage d un tenant designe-t-il le site REEL ? dns_amorcage et
artefacts_amorcage sont ecrits a la main, volontairement : au moment ou ils
servent la machine ne resout aucun nom. Mais ils designent des machines DU
SITE, et n ont pas suivi son renumerotage. La reconstruction du locataire
s est arretee sur Failed to update apt cache, a quinze couches de sa cause.
La preuve ne juge que les valeurs qui PRETENDENT designer le site : viser
9.9.9.9 est un choix, pas un oubli.
LES CLES SORTENT DU POSTE, EN CLAIR, ET C EST RAISONNE. Support perdu : LUKS
s en charge. Poste compromis : la seconde couche n aide pas, les originaux
sont dans ~/.config sur ce meme poste. Elle coutait une phrase de passe
stockee nulle part — le seul point que la procedure ne couvre pas. Option
--support-chiffre explicite ; le defaut reste GPG, parce qu un support non
chiffre est le cas le plus frequent.
Et ma note qui disait les cles sorties depuis le 5 septembre etait fausse :
le support ne portait que le depot hors site du 1er.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Tour 1 8 passages ~2 h 30 16 defauts
Tour 2 3 passages 53 min 2 defauts
Tour 3 2 passages 37 min 0
LE TOUR 2 EST CELUI QUI COMPTE LE PLUS. J avais annonce deux passages ; il en
fallait trois, parce que les deux blocages connus sont EN SERIE : on ne peut
pas amorcer une forge qui ne tourne pas, et elle ne tourne qu apres la
convergence de la cle. Un runbook ecrit d apres un recit de depannage
contenait une erreur qu aucune relecture n aurait montree — il fallait le
SUIVRE pour la voir.
DEUX MURS DE PLUS, invisibles au tour 1. site-creer rend la main avant que
les machines repondent (3 min 20 mesurees). Et les cles d hote changent a
chaque reconstruction : accept-new couvre la premiere rencontre, jamais un
changement.
LE CYCLE DE LA CLE EST DENOUE. Aucun ordre de couches n y pouvait rien — les
certificats doivent venir tot. C est la propriete du geste qui a change de
main : serveur_forgejo revendique la cle au moment ou il cree le groupe.
client_pki garde la sienne et la repose a chaque passage, parce que step
reecrit la cle a chaque renouvellement. Gain : un passage entier et 300
secondes d attente perdue.
forge-amorcer purge desormais l entree perimee de l hote que GIT va
contacter, lu dans git remote get-url — pas celle de l API, qui peut etre un
tunnel. Et il rend les quatre premieres lignes de stderr : n afficher que la
derniere m a coute un aller-retour, git terminant toujours par un message
generique.
Le runbook porte les six etapes du tour 3 avec leurs durees reelles et les
dix-huit murs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
SITE-Chezlepro n avait jamais ete rase. La limite qu on repetait partout —
l infrastructure d accueil n a jamais ete reconstruite depuis zero — se
lisait comme de la prudence. C etait seize defauts que rien d autre n aurait
pu reveler.
Un locataire naît dans un monde deja peuple : le site lui fournit paquets,
noms, genome, heure et depot. Un site n a personne au-dessus, sauf sa
frontiere. Onze des seize murs viennent de la.
DEUX CAPACITES QUI N EXISTAIENT PAS. make site-raser — rien ne detruisait les
machines du site, donc la limite etait un trou d outillage. make
forge-amorcer — la forge naît vide et le runner y clone ; l amorcage part du
poste, seul endroit qui detienne alors le genome.
TROIS GARDES QUI VERIFIAIENT LA FORME. La plus couteuse des familles : elles
donnent l apparence d une verification. Le resolveur comparait des adresses
au lieu de mesurer si la resolution aboutit, et protegeait ainsi l etat
casse. L administration etait reconnue a son port. Un flux a deux paires
n obtenait qu une branche.
UN ECART DE SECURITE. Le PostgreSQL du site servait le certificat auto-signe
de Debian, sans reseaux autorises ni hostssl — invisible tant qu aucun client
n exigeait la verification. Le defaut n a pas casse la construction : la
construction a revele le defaut.
UN ACCES ACCIDENTEL. Celui de l exploitant tenait au chevauchement d
adressage que le renumerotage a supprime. Separer les index n a pas cause le
probleme, il a retire le hasard qui le masquait.
La sequence du premier jour est ecrite : runbooks §9, avec les seize murs et
ce que chacun enseigne.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
SITE-Chezlepro passe de 10.0.31-36 (tapes a la main, derives de rien) et
10.17.0.0/24 (emprunte au supernet du locataire) a son PROPRE index : gestion
en 10.37.0.0/24, zones en 10.37.31-36. OPS-Chezlepro garde 17.
CE QUE L OPERATION A REVELE. Il n existait aucun moyen de raser le site :
raser.py ne vise que l instance active, un locataire. Ce n est donc pas que
personne n avait essaye de le reconstruire depuis zero — l outil n en offrait
pas le moyen, et la limite se lisait partout sans que sa cause soit nommee.
make site-raser reprend les quatre verrous de raser.py.
TROIS FOIS LE MEME DEFAUT, attrape par l exploitant. La declaration decrit la
CIBLE pendant que l outillage s en sert pour joindre l EXISTANT : renumeroter
le rebond avant de bouger la patte a rendu le site injoignable. Regle posee :
le renumerotage declaratif vient APRES la derniere operation qui a besoin de
l ancienne infrastructure.
nftables_admin_ssh etait une liste a la main qui devait suivre le plan
d administration. Elle a pris du retard le jour meme.
Le plan de frontiere n a propose AUCUNE creation ni suppression de regle —
seulement douze contenus d alias. Les regles visent des alias par leur nom :
un renumerotage complet se reduit a changer ce que les noms designent.
Routes des trois hyperviseurs refaites, declarees ET vives, avec sauvegarde
de /etc/network/interfaces.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Decision de l exploitant : sites et locataires se partagent la classe A,
chacun avec son propre index. L exception du guide — un site derive du meme
index que son tenant — disparait.
Ce qu elle cachait : le plan d administration du site vit dans 10.17.0.0/24,
a l interieur du supernet du locataire OPS-Chezlepro. Pas dangereux, mais
10.17.0.0/16 designait deux choses. Et les zones du site ne derivaient de
rien — le site etait la seule partie du systeme sans seed.
La seconde liste nait avec cette decision : un site et un locataire peuvent
desormais reclamer le meme nombre, et make instances ne voyait que les depots
OPS-*. La decouverte lit maintenant SITE-*/underlay.yml et son champ index.
Un site sans index declare reste hors du compte.
SITE-Technolibre : squelette du deuxieme site pour la visite du 14. Un seul
noeud Proxmox en version 9, meme forme que Chezlepro, adresse depuis l index
31. COLLECTE.md liste les 35 valeurs a relever.
Reste a l exploitant : OPS-Chezlepro passe a 37, ce qui libere 17 pour le
site qui l utilise deja.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Mesure sur les quatorze : aucune sortie TCP vers une adresse publique, apt
par le cache, noms autoritaires en local, unattended-upgrades masked. Et
quatre pairs NTP publics par machine. Le role chrony posait le fuseau et
installait le demon sans jamais toucher a ses sources : le defaut de Debian
tenait depuis le premier jour, herite et jamais choisi.
L autorite est la frontiere, par decision de l exploitant. Elle etait deja
stratum 2 et ecoutait en 123 ; il ne manquait que le passage. 9 anciennes
regles port 123 vers !SETOPS_INTERNES retirees, 9 regles nommees vers
SETOPS_FRONTIERE posees : le changement resserre autant qu il centralise.
Deux chemins parce que la topologie en a deux. Un tenant n atteint pas la
frontiere par sa passerelle de zone — tenue par le SDN — mais par le lien de
transit. Une machine du site a la frontiere pour passerelle directe. Les deux
valeurs sont derivees de l underlay, via reseau_transit() plutot que d un
prefixe d adresse qui aurait menti chez le prochain hebergeur.
La patte face aux tenants manquait a opnsense_if_zones, pour la meme raison
que grappe-controle la veille.
Sonde horloge ecrite en meme temps : synchronisee ET contre la source
DECLAREE. Une machine peut etre parfaitement a l heure contre quatre serveurs
publics — c est exactement l etat d avant.
14 tenant + 7 site, toutes disciplinees, sources publiques = 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Renommer une exposition touche cinq choses. Quatre suivent au deploiement ;
la cinquieme est celle que le navigateur regarde. serveur_nginx pose le vhost
et laisse le SAN en arriere — c est client_pki qui reemet, et rien ne le
reclame. Mesure deux fois le 2026-09-10.
Le controle compare trois listes : les expositions du plan, les noms du
certificat servi, et le code que rend le vhost. Dans les deux sens : un nom
reste dans le SAN apres avoir quitte le plan continue d etre authentifie.
Piege trouve en l ecrivant : client_pki met toujours le FQDN et le nom court
de la machine dans le SAN. Les compter comme vestiges faisait crier le
controle a chaque execution — et un controle qui crie toujours ne se lit plus.
Deux formes de terminaison, une seule question : le locataire termine sur un
edge, le site sur la machine elle-meme. Le porteur se derive de l une ou de
l autre.
Ce n est pas une preuve : rien de statique ne peut lire un certificat servi.
Les quatre controles a la demande n etaient nommes nulle part comme famille ;
ils ont maintenant leur section dans les runbooks.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Quatrieme reconstruction depuis zero, 32 min 14 s, 14/14. Elle avait un but
precis : trois roles dependent desormais d une variable que le generateur
pose. A froid, si instancier ne la posait pas, le defaut du role reprendrait
la main en silence — juste pour forge et cloud, faux pour observatoire.
Le certificat ne a froid porte observatoire et vigie, aucun ancien nom, et les
deux retours SSO derivent juste sans reprise.
Un seul echec, sans rapport : sur une machine des quatorze, get_url a rendu
0 octet SANS ERREUR — le cache a servi un 200 au corps vide. Le defaut s est
lu deux cents lignes plus loin, dans un apt qui accusait la signature. La
garde posee hier ne mordait pas : retirer une ressource vide ne vaut qu au
passage suivant, quand le fichier existe deja.
Les cinq roles qui telechargent une cle la mesurent maintenant dans la meme
execution. P68 garde le motif. infra-mail-01 redeployee, make valider a
0 echec sur 14 machines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Le renommage deploye, nginx servait observatoire, le certificat le portait,
les deux zones le publiaient — et Grafana fabriquait toujours son URL de
retour OIDC avec l ancien nom.
Quatre roles portaient en defaut une devinette du nom sous lequel ils sont
servis. Tant que le plan suit la meme convention, la devinette tombe juste et
rien ne revele qu il y a deux sources. Keycloak avait deja son remede, dans le
role — donc trois roles sans remede.
instancier derive desormais <groupe>_hostname de l exposition unique declaree
par le plan. Six services en heritent. P67 garde la derivation. Les quatre
instances sont regenerees, 67 preuves vertes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
grafana.chezlepro.internal et tableaux.genese.internal nommaient le meme
service de deux facons. Les deux deviennent observatoire.
Un nom de produit dans une URL se grave aussi dans les SAN du certificat et
dans les URI de redirection du SSO : remplacer Grafana obligerait alors a
renommer le service. Le nom dit desormais la fonction.
Le renommage a revele que les URI des clients Keycloak repetent a la main les
FQDN declares dans expose:. Rien ne les reliait. P66 garde ce lien, ecrite en
meme temps que le premier renommage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Doctrine posee par l exploitant : des lors que le site a son Loki, la
journalisation de la frontiere et des hyperviseurs doit y etre dirigee.
CE QUI ETAIT CASSE. La destination syslog d OPNsense pointait
10.17.20.11:3100 - une adresse de TENANT, sur le port de Loki, en UDP, ce
que Loki ne sait pas lire. La VM a ete rasee ; une cible morte a arrete
TOUTE la journalisation pendant dix jours. La destination a ete eteinte
pour reparer et jamais remplacee. Deux fautes en une : plus de journaux, et
une cible chez un locataire, ce que D-87 refuse dans l autre sens.
L intention etait juste - bonnes facilites, info et au-dessus. On l a
REPRISE telle quelle et change uniquement la destination : ces choix
avaient ete faits, ce n etait pas a nous de les redecider.
UN TRADUCTEUR, parce que Loki ne parle pas syslog : loki.source.syslog dans
l Alloy qui tourne DEJA sur le collecteur. Port 1514 et non 514, donc
ecoute sans privilege - un collecteur qui aurait besoin des droits du
systeme pour entendre un equipement serait un mauvais echange.
UNE REGLE DE RELABEL SANS GARDE N IGNORE PAS : ELLE EFFACE. Trois quarts d
heure sur un symptome absurde - la configuration deposee portait
host = "bifrost-1", visible dans le fichier, et le flux arrivait sans
etiquette. Sans regex, la regle correspond TOUJOURS, meme quand sa source n
existe pas, et pose host = "". Loki jette une etiquette vide, et celle de l
ecouteur disparaissait avec elle.
Et la verification finale a corrige une seconde erreur, la mienne :
label/host/values rendait une liste en CACHE. La requete directe rendait
bien un flux. Un index qui ne montre pas une chose ne prouve pas qu elle n
existe pas.
LA GARDE, qui manquait depuis l incident. journaux-frontiere demande a LOKI
ce qu il a RECU, pas a la frontiere ce qu elle croit avoir envoye : la
destination est le seul juge, et un emetteur qui parle a un trou noir se
porte tres bien. La fenetre est DERIVEE du debit observe - environ 1500
lignes par minute - pas choisie.
Trois etats eprouves sur une copie : frontiere muette rc=2, Loki
injoignable rc=3, debit normal rc=0. Le 3 compte autant que le 2 : « je ne
peux rien affirmer » n est pas « c est casse ».
Mesure : 46 681 lignes recues en 30 min, site a 68 services tous OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Les journaux de la fabric, et le defaut de classe que leur mise en place a
revele. 10 hotes dans Loki (7 VM du site + 3 hyperviseurs), site a 67
services tous OK contre 51 avant.
METRIQUES TIREES, JOURNAUX POUSSES. Un scrape part du collecteur et doit
REVENIR : il exige un chemin symetrique, que la route par defaut gelee
interdit. Un push part de la source et n attend qu un accuse.
Trois trous dans les generateurs. Le devis de la frontiere ne connaissait
fabric qu en DESTINATION - un flux entrant tombait dans « rien d autre n
entre » sans rien dire. La regle etait posee sur la patte de la destination
alors que D-61 raisonne en ARRIVEE, et opt7 manquait a la table des zones.
Et le plus grave : fabric etait un mot RECONNU mais NON RESOLU. Source
vide, donc regle sans saddr - tcp dport 3100 accept, ouvert a tous. Un mot
reconnu mais non resolu est pire qu un mot inconnu : celui-ci serait refuse
a la validation, celui-la produit une porte grande ouverte qui a l air d un
flux precis.
LA CLASSE ENTIERE EST REFERMEE. Le defaut n etait pas propre a fabric :
toute paire nommant un ensemble et ne resolvant rien ouvrait le port.
Trouve en lisant les fichiers generes, l API de supervision d un tenant
etait ouverte a tous parce que pair: serveur_backup ne resout rien - cet
ecosysteme depose chez le site. expositions et externe, eux, veulent bien
dire tout le monde : c est une intention. Ailleurs, pas de source, pas de
regle, et le generateur le DIT.
Trois regles se referment, chacune verifiee AVANT d appliquer. Deux etaient
sans effet ; la troisieme portait Grafana, qui ecoute bien sur 3000. Sa
declaration disait pair: edge - juste chez un tenant, faux au site qui n a
pas d edge. Ajout de admin : ce qui etait accidentel devient explicite.
Et la mesure a montre autre chose : Grafana etait DEJA injoignable depuis
le poste, la frontiere ne laissant pas passer le 3000. Le site a une
console deployee et sans chemin d acces. Ce n est pas corrige ici, mais
c est dit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
La supervision du site voyait ses sept VM et rien d autre. Au depart, depuis
site-mon-01, les trois hyperviseurs, les neuf pattes de la frontiere et sa
PROPRE passerelle par defaut rendaient tous 100 pourcent de perte au ping.
16 hotes UP sur 16 dont 9 pattes de frontiere en controle ACTIF
11 cibles Prometheus dont 3 hyperviseurs, job fabric separe
LE PARTAGE. Les hyperviseurs portent node_exporter - D-48 l autorise - et
exposent 1005 unites systemd avec leur etat, soit ce que la sonde sante
mesurait, plus la charge et le disque. Ils n entrent PAS dans le socle :
leur appliquer serveur_durci reecrirait le pare-feu, le SSH et les sysctl
de la machine qui tient tout le reste. La frontiere, elle, n accueille
aucun agent : controle actif, une entree par PATTE, parce qu une interface
eteinte coupe une zone pendant que les autres vont bien.
ON TIRE, ON NE POUSSE PAS. J avais propose du passif et il avait ete
valide ; la mesure a dit non. Un hyperviseur envoie vers un routeur qui ne
connait pas les reseaux du site, et sa route par defaut est GELEE (D-57).
Le porteur de sante y expirait en 20 s.
LE VRAI DEFAUT ETAIT UNE LISTE QUI N A PAS SUIVI. Le mecanisme de routage
existait deja sur vmbr0, avec un commentaire du 2026-08-26 tenant
exactement le raisonnement qu on venait de refaire. Sa liste s arretait a
10.0.34.0/24 quand le site en declare six : les zones sauvegarde et
supervision sont nees, les routes n ont pas suivi. Le symptome ne
ressemblait pas a une route manquante - il ressemblait a un pare-feu, puis
a un probleme de reseau chez l exploitant.
make routes-fabric-etat compare desormais TROIS choses : zones declarees,
routes declarees dans /etc/network/interfaces, routes vivantes dans le
noyau. Le cas le plus traitre est vivante mais non declaree : tout
fonctionne, la supervision est verte, et la panne attend la prochaine
maintenance. Eprouvee dans les deux sens.
Deux defauts trouves en construisant. bifrost-2 est un nom RESERVE, pas un
boitier - l underlay le disait en prose, illisible par le moteur ; il porte
desormais etat: reserve. Et un service passif n existe que pour un hote qui
peut POUSSER : l appartenance a un groupe sert deux choses qui ne
coincident pas toujours, a qui l on deploie et de qui l on attend un
rapport.
Les commutateurs restent dehors, choix de l exploitant, coherent avec D-48.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
LA GARDE FICHIER ENTIER, en deux corrections. Un telechargement
interrompu laisse un fichier de zero octet QUI EXISTE, et toutes les gardes
demandaient seulement s il etait la. infra-mail-01 a garde une cle
smallstep de 0 octet apres l epreuve hors ligne.
La premiere correction n a pas suffi. Ajouter le controle de taille faisait
bien s executer la tache - et le fichier faisait toujours 0 octet au
passage suivant. get_url sur une destination existante emet une requete
CONDITIONNELLE : l amont repond non modifie, le module rend ok, la ruine
reste. Le play etait vert et ne reparait rien. Il faut effacer avant de
redemander.
Controle negatif : 0 -> 1022 octets, 0 erreur apt. Cinq roles.
LA SONDE CORRECTIFS, 23e. Set-OPS desarme unattended-upgrades et applique
les correctifs au deploiement - choix defendable, le verrou dpkg a fait
decrocher une machine d une reconstruction entiere le matin meme. Mais rien
ne disait QUAND le geste etait du : une flotte pouvait deriver des mois en
restant verte.
Elle mesure les paquets de securite en attente ET depuis quand. Elle ne
lance pas apt-get update - une sonde qui rafraichit l index toutes les
quinze minutes deviendrait la cause de la panne qu elle surveille. Et le
seuil de 72 h est un choix d exploitation, pas une derivation : le
mecanisme qui applique les correctifs est un geste humain.
P64 REFUSAIT UNE DECLARATION CORRECTE. serveur_debian et serveur_durci sont
des roles de declaration pure, sans une tache ; le travail est fait par les
roles que leur playbook applique. La preuve exigeait declaration et depot
dans le meme role - vrai des vingt-deux premieres sondes, faux des qu une
sonde appartient au socle. Une garde qui force a contourner ce qu elle
protege est un defaut. Elle suit desormais le playbook du groupe.
Mesure : 21/21 machines vertes, 65 preuves, 23 sondes, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Deux mouvements d une seule doctrine : le site fournit tout ce dont un
tenant a besoin pour venir au monde.
1. LES DEPOTS TIERS. Grafana, Icinga, Smallstep et Collabora ne publient
qu en HTTPS ; apt-cacher-ng ne relaie pas un tunnel, et le socle pose donc
Acquire::https::Proxy DIRECT. Chaque machine sortait elle-meme sur
Internet. Le cache declare desormais un Remap par fournisseur, et chaque
role demande en {{ ..._depot_schema }}:// - http des qu un cache est
declare, https sinon. Le TLS n est rompu nulle part : il est TERMINE au
cache, qui est notre machine, et l integrite vient des signatures.
avant : 4 fournisseurs en HTTPS direct, 14 machines sortant seules
apres : 0 source en HTTPS direct, 0 erreur apt sur 14 machines
Trois lecons. Le remap appartient au cache qui SORT : pose sur un cache
chaine, il tente le HTTPS a travers son amont et rend 503. apt_repository
AJOUTE au lieu de remplacer, donc l ancienne ligne https sortait toujours.
Et la liste des fournisseurs ne se devine pas - j en avais trois, l audit
en a revele un quatrieme.
2. LE CACHE DU TENANT. Sa ligne portait son propre retrait depuis toujours
- service MUTUALISABLE, un ecosysteme au premier age peut pointer sur celui
de son hote. Retiree. Ce qu on perd, dit franchement : plus de trafic
inter-zone et plus de charge sur site-cache-01, contre un service de moins
a poser, superviser et reproduire.
3. UN ROLE QU ON RETIRE DOIT DEFAIRE CE QU IL A FAIT. Le retrait a montre
que rien ne nettoie derriere. Le fichier apt visait un cache eteint en
ecrasant le plancher qui fonctionnait - le defaut deja paye a quinze
machines. Et les sondes du cache restaient, le porteur poussant pour des
services qu Icinga ne definit plus (404). Le socle retire le premier,
client_sante derive les sondes attendues et retire les orphelines.
P65 refuse tout role visant un depot relaye en https ecrit en dur. Sa
limite est dite : elle empeche une regression sur ce qui est connu, elle ne
decouvre pas l inconnu.
Mesure : Icinga 87 OK sur 96, prouver 65 OK, lint 0 defaut.
Reste, et c est dit : apt-cacher-ng tourne toujours sur forge-01 que plus
aucun plan ne declare.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Quatorze machines detruites, quatorze refaites. 1 h 08, 0 injoignable.
D-86 TIENT, et la mesure le dit : 30 resultats de controle recus a
12:20:41 alors que la reconstruction ne s est terminee qu a 12:35:29.
Trente services rapportaient leur etat pendant que Keycloak, Forgejo et
Nextcloud montaient encore.
Sa limite, dite franchement : D-86 affirme que le DNS peut rester en
couche 6 grace au plancher /etc/hosts. Or _amorcer-socle monte
infra-dns-01 EN ENTIER d abord. Le DNS etait debout, le plancher n a rien
eu a porter, et la partie la plus audacieuse de la decision reste non
testee.
LE PLACEMENT DES VM N ETAIT DECLARE NULLE PART. Les quatorze vivaient sur
asgard depuis toujours, mais SETOPS_NOEUD valait le vide : ce placement
n existait que dans l etat d execution de Proxmox. Sans cible, le clone
reste sur le noeud du gabarit - vishnu, qui a 14 Go libres et heberge
eregion et site-forge-01. Le defaut avait survecu a la reconstruction du
2026-09-02 parce que les VM existaient deja et que le clone les sautait.
Il faut un from-zero VRAI pour voir ce genre de chose.
LE CLONAGE ETAIT 45 FOIS TROP LENT, et pas a cause du reseau : source et
destination etaient sur le meme stockage. C est le LVM epais qui interdit
a Proxmox tout clone autre que complet. Gabarit deplace sur CephNVMe,
mesure 480 s -> 8 s. On garde les clones complets : le clone lie descend
a 1 s mais enchaine chaque VM a l image de base pour toujours.
Un champ stockage: entre au gabarit, branche en quatre points - declare,
expose, consomme par le Makefile, garde par gabarit_etat. Declarer sans
consommer, c est decorer.
ICINGA NE POUVAIT PAS FAIRE SES PROPRES CONTROLES : icinga2 n apporte
aucun greffon, et la supervision etant passive, le manque etait masque.
Quatorze ping4 muets d une seule cause. monitoring-plugins-basic entre au
role. Le site l avait deja - pose A LA MAIN, jamais declare : troisieme
occurrence du jour d un etat qui ne survivrait pas a une reconstruction.
Trois defauts laisses ouverts : le cache apt attendu pendant l amorcage,
la regression D-85 sur /etc/cloud - qui a fait decrocher deux hotes de la
tache deposant icinga-ca.crt, rendant leurs sondes muettes en silence - et
auditd sans regles dans le gabarit.
Mesure finale : Icinga 93 OK sur 98, site 51/51, Prometheus 15/15,
prouver 64 OK, lint 0 defaut. Les quatre non-OK restants sont des
sauvegarde dont le minuteur nocturne n a pas encore visite une flotte nee
il y a deux heures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Application de ce que l entree precedente avait prepare : 49 regles creees,
5 retirees. Cibles Prometheus 2/8 -> 8/8, hotes dans Loki 1/7 -> 7/7.
Puis correction de ce que l application a revele. Cinq defauts, tous de la
meme famille : un reglage qui ne suit pas l interrupteur dont il depend.
- la sonde de Loki interrogeait en HTTPS un Loki servant en clair ;
- la sonde de la forge visait http:// sur un port 443 chiffre, puis
validait un certificat emis pour le FQDN en interrogeant 127.0.0.1 ;
- la sonde du runner comparait six depots a UNE branche quand le plan en
declare une par depot. Elle contredisait la declaration qu elle etait
censee verifier : elle n accusait pas la machine, elle s accusait
elle-meme.
Le tenant restait juste dans les cinq cas. C est le site, qui deploie ces
roles autrement, qui les a tous reveles d un coup.
La sonde des journaux, elle, avait deux defauts a la naissance. Elle criait
une perte deja reparee, faute de dire sur QUELLE fenetre - un ecart sans sa
fenetre n est pas une mesure, c est un nombre. Et elle alertait sur la
cicatrice plutot que sur la plaie : le compteur d Alloy est cumulatif, donc
six machines restaient orange pour toujours. Ce qui alerte est desormais la
CROISSANCE. En echange, un cas muet leve maintenant : zero envoi et zero
rejet n est pas la sante, c est un agent qui ne fait rien.
Enfin, le runner ne pouvait plus cloner le genome. Defaut PREEXISTANT,
revele par le redeploiement et non cause par lui - le diff des regles
generees le montre. generer_nftables(site=True) lisait le plan du TENANT :
derive se resolvait donc a 3000, le port que Forgejo ecoute derriere un
edge, alors que le plan du site dit 443 et explique pourquoi. La forge
ouvrait un port que personne n ecoute et laissait 443 ferme a toute la
flotte.
Mesure finale : Icinga 51/51, Prometheus 8/8, Loki 7/7, prouver 64 OK,
lint 0 defaut sur 82 fichiers. Controles negatifs sur des COPIES des
sondes : les quatre rendent CRITICAL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
D-87 disait que l hebergeur n a pas le droit de voir les journaux de ses
locataires. La decision avait une face cachee : a force de refuser de voir
ceux des autres, le site s etait prive des siens. Ses sept machines
n expediaient nulle part.
Il prend donc sa propre pile : prometheus, loki et grafana sur site-mon-01,
sans aucun lien avec ceux d un tenant. L exemption qui bloquait portait sa
propre condition de levee, ecrite cinq jours plus tot dans le plan.
Grafana au site n a pas de SSO, et le role l ignorait : il reclamait l IdP
avant de regarder s il en voulait un. L interrupteur existait, il n etait
pas honore. Le role refuse desormais SSO eteint ET formulaire local eteint
- la combinaison deploie un Grafana en sante ou personne ne peut entrer -
et le reglage du formulaire sort du if du SSO, ou il disparaissait en
laissant le defaut amont decider en silence.
Deux manques se cachaient l un l autre dans le devis de la frontiere, et
Prometheus voyait 1 cible sur 7 :
- le devis derivait les groupes d une machine du site de applications.yml
seul, et ne voyait donc aucune integration universelle - alors que
client_metrique ouvre un port d ecoute ;
- une sortie vers un role du site visait !SETOPS_INTERNES, qui exclut
precisement la machine nommee. Le devis autorisait a expedier les
journaux du site a n importe quel Loki du monde, et a nul autre endroit
qu a celui-la. Neuf flux dans ce cas.
Et deux declarations justes ne font qu une regle : appliquer_opnsense pose
tout en direction: in (D-61).
Les deux agents se supervisent enfin eux-memes. La sonde des journaux ne
demande pas si Alloy tourne, elle lit ce qu il a du JETER. Elle a fait ses
preuves le jour meme : Alloy actif, /-/ready a 200, et cinquante lignes
perdues sur six machines.
Mesure : metriques 7/7 vert, journaux 1/7 - les six autres attendent la
regle de frontiere, et le disent. prouver 64 OK, ansible-lint 0 defaut.
Reste a la main de l exploitant : make frontiere-appliquer CONFIRMER=true,
et le secret vault_grafana_admin a deposer dans underlay.vault.yml.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
J ai ecrit il y a une heure que la panne PMTUD « etait la, silencieuse,
sur les sept machines de l hebergeur ». La mesure ne le soutient pas :
site-mon-01 -> autre zone du site : MTU de chemin 1500
site-mon-01 -> Internet : MTU de chemin 1500
ops-01 (tenant, overlay EVPN) : MTU 1450, chemin vers Internet 1450
Le site est a 1500 de bout en bout. Aucun lien du chemin ne plafonne plus
bas, donc aucun message « fragmentation necessaire » n avait lieu d etre
emis, donc rien n etait casse entre zones du site. La contrainte a 1450
vit chez les TENANTS, sur l overlay — pas chez l hebergeur.
Les six regles restent un vrai manque : elles couvrent le site parlant
vers l EXTERIEUR, ou un correspondant distant peut plafonner plus bas, et
elles seraient necessaires si une zone du site passait sur l overlay. Mais
c etait une PROTECTION ABSENTE, pas une panne active.
Decrire l un comme l autre est l exageration que ce depot ne doit pas
contenir : une correction qu on ne mesure pas se raconte toujours plus
grande qu elle n est. La correction est portee au CHANGELOG et dans le
commentaire de devis_opnsense.py, la ou on la relira.
Ce qui reste vrai et mesure : l Icinga du site tenait 6 de ses 7 machines
pour mortes, et 7/7 apres.
make prouver : CONFORME, 64 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
LA CAUSE EXACTE. devis_opnsense traitait tout `port` non numerique comme
SYMBOLIQUE, a resoudre par le plan. Vrai pour `derive`, le port d un
service que seul le plan connait. FAUX pour l ICMP : echo-request et
frag-needed sont des litteraux. Ils tombaient dans la branche « le plan ne
resout pas » et aucune regle n etait emise. Une ligne de condition, et le
silence de toute une flotte.
CE QUE LA CORRECTION A REVELE EN PLUS. Huit regles a creer, zero a
retirer — et SIX sont du PMTUD entre zones du site, absent pour la meme
raison. Le depot dit de ces messages : bloques, la connexion s etablit,
les petites requetes passent et les grosses reponses restent suspendues —
la panne la plus couteuse a diagnostiquer. Elle etait la, silencieuse.
CE QUE LA REGLE AUTORISE, EXACTEMENT. appliquer_opnsense n envoie
destination_port que pour TCP et UDP : une regle ICMP ouvre le PROTOCOLE
entre deux pairs, pas le seul type. Le type reste porte par la regle d
HOTE, que resoudre_flux emet precisement. La frontiere dit qui peut parler
a qui, l hote dit ce qu il accepte d entendre. Emettre un icmptype a la
frontiere aurait demande de traduire frag-needed dans le vocabulaire de
pf, au risque de casser le PMTUD pour gagner de la precision sur une
barriere qui n est pas la derniere.
MESURE APRES APPLICATION :
ping site-mon-01 -> les six autres zones : 6/6 repondent
hotes UP : 1/7 -> 7/7 | ping4 : 1/7 -> 7/7 | certificat 7/7 | sante 7/7
tenant inchange : 14/14
make prouver : CONFORME, 64 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
« mon-01 tape dans l fond. » Il tapait : load 5,10 sur 4 coeurs, 8
processus check_disk a 70-99 % de CPU chacun, jusqu a 28 minutes de vie.
check_disk 2.4.0-3+deb13u1 ne rend jamais la main sur cet hote (etat R).
Icinga en relancait un a chaque intervalle pour le service `disk` de son
hote de DEMONSTRATION, et aucun ne mourait.
RETIRE : conf.d/hosts.conf, l hote NodeName livre par le paquet. Les apply
Service s y accrochaient — disk, http, swap, apt, load, procs, users — et
trois etaient rouges en permanence (swap sur une VM sans swap, http sur un
port ou rien n ecoute, apt pour un paquet). On retire l HOTE et non les
services : sans lui les apply ne s accrochent a rien, et on ne touche pas
a un fichier que le paquet remplacera. Ca garde ping4, qui vise nos hotes
et sert vraiment.
avant : load 5,10 — 8 check_disk — 3 alarmes rouges permanentes
apres : load 0,77 — 0 check_disk — certificat 14/14, sante 14/14, ping4 14/14
TROUVE EN VERIFIANT : l Icinga du SITE tenait 6 de ses 7 machines pour
MORTES (1/7 UP, contre 14/14 au tenant). hostalive est un ping, le site
est decoupe en zones, et l ICMP inter-zones n etait declare nulle part —
100 % de perte, mesure. Or Icinga SUPPRIME les notifications des services
d un hote DOWN : une supervision qui croit tout mort n alerte plus de
rien, tout en ayant l air de fonctionner.
Le flux est declare des DEUX cotes, et le registre a refuse la premiere
moitie seule — exactement sa raison d etre. CODES_ICMP apprend
echo-request. Regles d hote posees sur les 21 machines.
RESTE OUVERT : le generateur de la frontiere ne sait pas traduire un TYPE
ICMP pour un pair INTERNE — il le note et n emet rien. Le site reste a
1/7. Corriger devis_opnsense.py est le prochain geste.
make prouver : CONFORME, 64 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
LE CONSTAT. 39 roles declarent leurs flux, 32 leur empreinte, 32 leur
authentification — tous derives. Et 19 groupes sur 19 declaraient une
surveillance en prose que RIEN n executait ; Icinga en surveillait deux.
La carte disait ce qui etait surveille, et personne ne surveillait.
LE MECANISME. Un role declare ses sondes dans meta/supervision.yml et
depose lui-meme son script dans /usr/local/lib/setops/sondes/. Le porteur
client_sante les fait toutes tourner et pousse un resultat passif par
sonde, sans savoir ce qu elles mesurent. serveur_icinga derive les objets
Service ET le filtre de permission d API des memes declarations. Ajouter
une sonde ne demande de toucher ni au porteur ni a Icinga.
PREMIERE SONDE : client_pki/certificat. Heures restantes sur le certificat
reellement pose, chaine verifiee, et empreinte SERVIE comparee au disque
quand un service le consomme. 14/14 au tenant, 7/7 au site.
QUATRE OBSTACLES, ET TROIS SONT LA MEME LECON.
La sonde a rendu 14/14 en CRITIQUE sur une PKI saine : openssl verify
-CAfile racine ne trouve pas l intermediaire qui signe nos certificats.
step certificate verify, lui, repond VALIDE.
Deployee au site, elle a rendu 5/7 : le seuil d avertissement (12 h)
etait AU-DESSUS du point de renouvellement (8 h, le tiers restant). Elle
criait avant que le mecanisme ne soit cense agir. Seuils ramenes a 6 h et
3 h. Un seuil se DERIVE du moment ou le mecanisme surveille agit.
Une alarme toujours allumee ne vaut pas mieux qu une alarme jamais
allumee : elle apprend a ne plus regarder. Une sonde se prouve DEUX FOIS,
verte sur le sain et rouge sur le casse.
Le filtre d API etait ecrit avant la lecture des declarations : les
services auraient existe et Icinga aurait refuse leurs resultats.
Et mon controle negatif a casse un service reel : substituer le certificat
d hote a fait propager un cert sans sa clef vers node_exporter. Un controle
negatif se fait sur une COPIE.
P64 tient les deux bouts : declaree sans etre deposee, ou deposee sans
etre declaree. Trois controles negatifs rejoues.
make prouver : CONFORME, 64 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
cloud-init n est pas un logiciel d installation : c est une SOURCE DE
VERITE EXTERNE. Il se reveille a chaque demarrage et relit le lecteur
attache par l hyperviseur, qui peut redefinir comptes, cles SSH, mots de
passe et reseau. Sur une machine que le plan possede, c est un second
maitre — que le plan ne decrit pas, que make valider ne mesure pas, et
qui parle en premier.
Sa tache est finie a la premiere seconde : c est parce qu il a REUSSI a
poser l adresse et les cles qu Ansible a pu entrer.
TROIS MOITIES, ET ELLES SE DEFONT SEPAREMENT.
- le GABARIT le garde : sans lui un clone n a ni adresse ni nom ;
- le SOCLE ne l installe plus : le garder produisait un va-et-vient a
chaque deploiement, deux changed par passage, idempotence perdue ;
- le DURCISSEMENT le retire (roles/cloud_init_retrait, en dernier).
P63 garde les trois, plus le CONTENU du role : une coquille vide passerait
les trois premiers controles sans rien fermer. Quatre controles negatifs
rejoues.
CE QUI REND LE RETRAIT SUR EST MESURE, PAS SUPPOSE (obs-01, 2026-09-09) :
/etc/network/interfaces.d/50-cloud-init n appartient a aucun paquet — dpkg
-S ne le trouve pas — et le postrm ne le nomme jamais, meme en purge.
L adresse survit. Le role le verifie quand meme, avant et apres, et n
accuse que si le retrait l a emporte : une VM qui perd ce fichier ne se
plaint pas, elle repart sans adresse et plus personne ne peut entrer.
DEUX CHOIX DITS FRANCHEMENT. cloud-guest-utils reste (growpart : ni
service, ni port, ni source de donnees). Les ~29 paquets orphelins ne sont
pas retires par defaut : autoremove deciderait a partir des drapeaux dpkg,
et un durcissement ne doit pas pouvoir surprendre.
NON DEPLOYE : le code est ecrit, valide et prouve ; il n a pas ete
applique a la flotte. Essai a blanc sur obs-01 : cloud-init a retirer,
configuration reseau intacte.
make prouver : CONFORME, 62 OK, 0 echec, 1 saute (63 preuves).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
CHAMPS_ECRITS_A_LA_MAIN est vide. Serveurs et applications, les deux plus
gros, sont passes au generateur — chargement, rendu et sauvegarde.
L EPREUVE QUI COMPTE. Ouvrir chaque vue et enregistrer sans rien toucher
doit renvoyer exactement le plan qu on vient de lire : 14 serveurs, 25
applications, 2 domaines, 4 bases, IDENTIQUE partout. C est ce qui separe
un formulaire genere d un formulaire qui en a l air — un champ visible a
l ecran et perdu en silence a l enregistrement serait le pire des deux
mondes. test_rendu_gui.py le mesure a chaque make prouver.
TROIS DEFAUTS TROUVES EN CHEMIN.
Le formulaire annoncait des defauts INVENTES : 2048 Mo, 2 coeurs, 16G. Il
n existe aucun defaut fixe — deriver_ressources calcule depuis les roles
portes (1024 et 1 pour infra-pki-01, 5632 et 4 pour collab-01). Un repere
faux fait croire qu on connait la valeur. Le schema nomme le champ derive,
et l ecran montre la valeur reelle de cet hote. L option vide d un select
dit desormais ce qu elle produira : « (defaut : asgard) ».
Une SECONDE occurrence du defaut d hier dormait dans sourceDeValeurs :
elle lisait encore data.nomenclature. Elle n avait jamais leve parce que
la vue Serveurs, seule a emprunter cette source, avait un formulaire ecrit
a la main. Elle a leve a la seconde ou le generateur l a prise. Le banc ne
voit que les chemins vivants : verifier_gui.py fait donc aussi une
verification STATIQUE, qui voit ce qui dort.
La validation client s accrochait a data-v, pose a la main sur trois
champs. Le formulaire genere l aurait perdu et la validation serait passee
au vert sur ZERO champ. Le generateur marque chaque controle, et la
sauvegarde refuse si elle n en inspecte aucun.
DEUX CHAMPS GARDENT LEUR EDITEUR, et le schema le dit (x-editeur) : la
matrice des integrations montre les universelles et les exemptions, et
l editeur de liens contraint le role a meta/liens.yml. Le generateur s
efface plutot que de remplacer un editeur qui en sait plus que lui.
LIMITE : je n ai toujours pas ouvert ces pages dans un navigateur.
make prouver : CONFORME, 61 OK, 0 echec, 1 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
En voulant generer deux formulaires de plus, j ai trouve pire que ce que
je cherchais.
CE QUI ETAIT DEJA LA. Les quatre ecrivains de registre ecrasaient le
fichier au safe_dump. Mesure sur les fichiers reels : domaines.yml 6->3,
applications.yml 27->5, serveurs.yml 18->3. Quarante lignes, detruites
par n importe quel clic sur Sauvegarder dans les vues Serveurs,
Applications ou Domaines. Parmi elles, celle qui explique pourquoi
backup-01 a ete retire, et celle qui dit dans quel ordre les deux roles
du runner s appliquent. C etait l incident du 2026-08-18, jamais corrige
pour les registres du plan. Les quatre passent par _ecrire_registre :
aller-retour a vide identique a l octet, sur les quatre fichiers.
TROIS ECARTS DE SCHEMA, trouves en confrontant le schema aux VALIDATEURS
et non aux seuls plans :
- edge designe un GROUPE, pas un hote. Le schema disait serveurs : un
formulaire genere aurait offert une valeur qu aucun hote ne reconnait,
donc aucun SAN, donc la panne du 2026-08-25 reintroduite ;
- exposition, entierement valide par le moteur, manquait au schema ;
- liens etait items: {type: object} — une liste d objets sans forme.
Et mail, offert par la vue Domaines depuis sa creation, decrit ici comme
un booleen, saisi la-bas comme du texte, lu par rien : retire.
P62 garde tout ca. Elle separe l entite du reste mecaniquement : un
validateur lit son entite par des variables LOCALES, les autres registres
par ses PARAMETRES. Controle negatif rejoue.
LES FORMULAIRES. Serveurs de BD et Domaines sont generes, chargement et
sauvegarde compris. Quatre registres sur six. Le generateur a appris la
liste d objets.
LIMITE : restent serveurs et applications, les deux plus gros ; et je n ai
toujours pas ouvert ces pages dans un navigateur.
make prouver : CONFORME, 61 OK, 0 echec, 1 saute (62 preuves).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
LA VUE. La nomenclature etait le seul registre que le GUI ne savait pas
ecrire du tout : ajouter une fonction exigeait d ouvrir le YAML. Elle a
sa vue, et son formulaire est GENERE depuis le schema. Deuxieme registre
sur six. couverture_gui verifier passe : les 28 champs des plans reels
sont editables.
Elle n est pas un registre comme les autres : elle decrit la REGLE dont
VMID, VLAN, adresse et passerelle se derivent. Chaque fonction montre ce
qu elle derive et les VM qui la portent ; l index est montre mais pas
editable, parce qu il est alloue par le site ; valider_nomenclature
refuse de retirer une fonction encore portee, ou de designer une zone
non declaree.
DEUX FAUTES, ET POURQUOI MES BANCS NE LES VOYAIENT PAS.
Le formulaire des bases, livre la veille, etait casse dans un navigateur.
Il lisait data.schema, or il n existe aucun data global : c est une const
locale de charger(). ReferenceError a l ouverture, et zone morte dans
sauvegarderBases. Je l avais eprouve sous node EN LUI PASSANT data : le
banc reproduisait la fonction, pas sa portee. D ou test_rendu_gui.py, qui
charge le JS entier dans un DOM simule et dessine les douze vues, avec son
controle negatif.
Le schema decrivait reservations comme une table de zones ; le fichier
reel est un bloc plat. P61 comparait des NOMS aplatis, donc ne voyait
rien. Elle compare desormais aussi la FORME.
ECRIRE SANS DEPLACER UN COMMENTAIRE. _fusion_chirurgicale remplace le
bloc entier des qu une valeur change : quinze entrees compactes devenaient
42 lignes, et le commentaire du poste d exploitation se retrouvait en tete
du bloc, ou il affirmait que collab etait le poste d exploitation. Un
commentaire deplace n est pas laid, il est faux. _fusion_table edite les
tables ligne a ligne ; le diff fait trois lignes.
Au passage : sort_keys triait le schema, donc l ordre des cases a l ecran
(reserve_max avant reserve_min) ; et _ecrire_index_nomenclature ecrivait
encore par write_text, oubliee au passage des ecritures atomiques.
LIMITE : deux registres sur six sont generes, et je n ai toujours pas
ouvert cette page dans un navigateur.
make prouver : CONFORME, 60 OK, 0 echec, 1 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Deux dettes des commits 0eaceb1 et 03c628d.
La premiere se voyait a l ecran : le marqueur « requis » se collait au
libelle et se lisait « Base requis », « Secret (Vault) requis ». Il
devient une asterisque discrete portant l infobulle « Champ requis ».
La seconde est une entorse a la regle 5 : les deux commits precedents
ont livre le schema derive et le premier formulaire genere SANS entree
de CHANGELOG. Elle est ecrite ici, avec sa limite dite franchement — un
registre sur six est genere, et le trou de la nomenclature reste ouvert.
make prouver : CONFORME, 60 OK, 0 echec, 1 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Etape 3, sur un seul registre — `bases_donnees`, le plus simple et le seul ou
observe et editable coincidaient deja. Les cinq autres gardent leurs formulaires
ecrits a la main : on ne bascule pas six vues d un coup.
CE QUI DISPARAIT DU JAVASCRIPT
Huit `<label>` en dur, trois constructions de `<option>`, et la regle qui
choisissait la source du consommateur selon la portee. Cette derniere ne vivait
que dans le JS ; elle est desormais DECLAREE au schema (`x-source-selon`), donc
lisible et gardee.
Le formulaire rend exactement les memes huit champs qu avant — verifie en
EXECUTANT le moteur sous node avec le schema et des donnees reelles, pas
seulement en passant `node --check`.
LA BOUCLE EST FERMEE DES DEUX COTES
Le chemin de SAUVEGARDE enumerait lui aussi les sept champs en dur. Un champ
ajoute au registre serait apparu au formulaire genere et aurait disparu
SILENCIEUSEMENT a l enregistrement — le pire des deux mondes. Il derive
maintenant du schema, valeurs par defaut comprises (`default`).
LA SEPARATION FORME / COHERENCE, MONTREE
portee=groupe + consommateur APPLICATION -> REFUSE par valider_bases
portee=application + consommateur app -> ACCEPTE
secret absent -> REFUSE
Le schema a rempli la FORME (les defauts `groupe` et `principale` se sont
poses), le validateur a attrape l INCOHERENCE. Aucune de ces trois regles ne
s exprime en JSON Schema, et vouloir l y mettre creerait la seconde source de
verite que ce depot refuse.
CHAMPS_ECRITS_PAR_GUI COMMENCE A DISPARAITRE
Renomme CHAMPS_ECRITS_A_LA_MAIN, et `bases_donnees` en est SORTIE : sa
couverture se derive du schema. P19 lit desormais `champs_ecrits_par_gui()`, qui
reunit les deux. Le jour ou la table sera vide, elle gardera un mecanisme au
lieu d une liste.
UNE GARDE A CORRIGER AU PASSAGE
`declaration_derive()` verifiait que chaque champ declare apparait dans le
SOURCE du GUI. Pour un registre genere il n y apparait plus — c est le but.
Elle aurait crie sur precisement le progres qu elle devait constater. Les
registres generes en sont exemptes : c est P61 qui tient la promesse pour eux.
make prouver : CONFORME, 60 OK, 0 echec, 1 saute.
P07 (node --check), P19 (couverture), P61 (schema) : verts.
Reste : les cinq autres vues, et le trou de la nomenclature — que le passage au
generateur fermera par construction, puisque le schema decrit deja `categorie`
et `service`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Etape 2 du chantier « l UI reflete fidelement la structure ». Le GUI porte
CHAMPS_ECRITS_PAR_GUI, une liste tenue A LA MAIN de ce qu il sait ecrire, que
P19 confronte au reel. C est une copie — gardee, donc honnete, mais une copie :
quelqu un doit penser a l allonger.
`make schema` produit docs/audit/schema-plan.json : six registres, 42 champs,
leurs types, leurs enumerations et ce qui est requis.
CE QUE LE SCHEMA EST, ET CE QU IL N EST PAS
schema -> la FORME -> generera les champs du formulaire
validateurs -> la COHERENCE -> refusent une saisie incoherente
Un JSON Schema ne sait pas dire qu un `consommateur` designe une application
inexistante, ni qu une integration universelle recopiee au plan est un defaut.
Vouloir l y mettre creerait la seconde source de verite que tout ce depot
refuse. Les valider_* restent l autorite.
LES ENUMERATIONS SONT IMPORTEES, JAMAIS RECOPIEES
ETATS_SERVEUR, PORTEES_BD et AUTORITES_DNS viennent des constantes que les
validateurs appliquent. Une enumeration recopiee diverge — c est la lecon des
neuf resolutions d instance que P41 garde depuis.
CE QUE L ETAPE 1 AVAIT TROUVE, ET QUE P61 A CONFIRME
Le recensement montrait `categorie` et `service` presents dans TOUS les plans et
absents de CHAMPS_ECRITS_PAR_GUI, dont la ligne `nomenclature` est vide : le GUI
ne sait pas les editer, l operateur doit ouvrir le YAML. P61 a refuse le premier
schema pour cette raison exacte. Les trois tables imbriquees de la nomenclature
sont donc DECRITES et non resumees en « object ».
31 champs observes dans l instance courante, 42 decrits par le schema. La
difference n est pas du bavardage : observer une instance n est pas un schema.
`noeud`, `stockage` et `coeurs` sont legitimes et simplement inutilises ici — un
schema derive de l observation les INTERDIRAIT.
P61, EPROUVEE DANS LES DEUX SENS
fichier genere perime -> REFUSE
champ du plan absent du schema -> REFUSE
champ decrit mais inutilise au plan -> COMPTE, pas refuse
Le troisieme point est delibere : refuser obligerait a retirer du schema un
champ valide des que plus personne ne s en sert. Meme mesure que les lacunes
nommees de P29.
UN DEFAUT DE MON INSTRUMENT, PAYE EN ROUTE
P61 comparait des noms a plat quand couverture_gui aplatit les tables
imbriquees : elle criait sur un schema correct. L instrument mesurait autre
chose que la cible. On aplatit desormais des deux cotes.
make prouver : CONFORME, 60 OK, 0 echec, 1 saute.
Prochaine etape : generer les formulaires depuis ce schema, et retirer
CHAMPS_ECRITS_PAR_GUI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
`path.open("w")` TRONQUE avant d ecrire : entre les deux, le fichier est vide.
Une exception dans yaml.safe_dump, un disque plein, un Ctrl-C, et
instance/plan/serveurs.yml reste mutile.
L asymetrie fait la gravite : hosts.yml se regenere d un
make instancier-appliquer, le PLAN ne se regenere de rien. C est la source
unique de verite. Git est le filet, mais encore faut-il savoir qu on est tombe.
TREIZE SITES, UNE SEULE FONCTION
Le defaut n etait pas dans le GUI seul : douze sites dans sept fichiers, dont
les miroirs CLI des MEMES registres. Corriger le GUI seul aurait recree la
divergence que P41 garde depuis les neuf resolutions d instance. La fonction
vit donc dans inventory_rules.py, que les sept importaient deja. Une source,
pas douze.
TROIS DETAILS QUI FONT LA DIFFERENCE ENTRE « CA MARCHE » ET « CA TIENT »
temporaire dans le MEME dossier os.replace n est atomique qu au sein d un
meme systeme de fichiers ; un /tmp sur une
autre partition casserait la garantie sans
rien dire
fsync AVANT le rename sinon le renommage peut atteindre le disque
avant le contenu : au retour d une coupure
brutale, un fichier neuf et VIDE — le defaut
qu on ferme, deplace d un cran
report des droits mkstemp cree en 0600, le plan est en 0664 et
doit rester lisible par le groupe sur les
runners
LE TEST PORTE SON PROPRE CONTROLE NEGATIF
scripts/tests/test_ecriture_atomique.py rejoue D ABORD l ancienne forme et
verifie qu elle DETRUIT. Sans ce controle, « le fichier est intact » ne
prouverait rien — il pourrait l etre parce que rien n a ete ecrit du tout. Une
garantie qu on n a jamais vue echouer n est pas une garantie, c est une
habitude.
Branche sur P02, dont le titre annoncait « inventory_host » alors qu il lance
maintenant trois tests. Corrige au passage.
LA VOUTE DU GUI : VERIFIEE, PAS DE DEFAUT
Le soupcon etait qu executer_flux pose ANSIBLE_VAULT_PASSWORD_FILE (un seul mot
de passe) alors que creer une VM ouvre DEUX voutes depuis « une voute, une cle ».
Eprouve contre deux voutes JETABLES a mots de passe distincts — jamais les
vraies. Les deux variables se CUMULENT : Ansible essaie tous les secrets, et un
PASSWORD_FILE errone n empeche rien. Confirme en sondant l environnement qu une
recette make recoit reellement : le mot de passe saisi ET les cinq cles
calculees par voutes.py.
Ce qui sauve ce chemin n est donc pas le mot de passe saisi, c est
l IDENTITY_LIST que make pose par-dessus. Chacun couvre ce que l autre ne
couvre pas — le PASSWORD_FILE sert le runner qui n a que sa cle, l IDENTITY_LIST
le poste qui les a toutes. Ecrit au-dessus du code, pour que personne ne
« simplifie » en retirant l un des deux.
make prouver : CONFORME, 59 OK, 0 echec, 1 saute.
make instancier : DIFF VIDE, quatre registres relus, droits 664 preserves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Une revision de documentation vieillit comme le reste. Ce qui tient, c est ce
qu une machine verifie — et trois lacunes etaient nommees sans etre gardees.
P58 HABILITATIONS. autorisation.md posait la regle (un service nomme un
GROUPE, jamais une personne, D-66) et meta/acces.yml la portait ; rien ne
la verifiait. P29 gardait les POSITIONS d authentification, personne ne
gardait les DROITS.
Le controle qui porte la preuve est un croisement : une entree
porte_par: role-realm affirme que l habilitation voyage par un role de
realm projete depuis un groupe LDAP. P58 le confronte a serveur_keycloak.
Sans ca, un service annonce une habilitation que rien ne transporte, et l
ecran reste vide sans que personne sache pourquoi.
CE QU ELLE N EXIGE PAS, et c est le point le plus important : que les
groupes nommes existent dans l annuaire. Ce serait contredire le regime du
paragraphe 2 — le depot AMORCE un acces et se retire, les appartenances
appartiennent a une personne. dev et personnel n existent dans aucun code,
et ce n est pas un defaut.
P59 ENUMERATIONS ANNONCEES. Les deux ecarts trouves a la main pendant la
tournee — cinq portes annoncees devant une table de six, huit lignes
renvoyees vers une fiche qui en compte dix — etaient d une forme que P57
ne voit pas.
Ma premiere version a signale CINQ ecarts, et les cinq etaient du bruit :
dans « reprise dans les deux devis : », le nombre qualifie autre chose que
la liste. Cent pour cent de faux positifs — la preuve qui crie sur un cas
sain et qu on apprend a ignorer. Resserree aux deux formes ou le nombre ne
peut compter rien d autre. Etroite et vraie plutot que large et devineuse.
P60 WIKI PUBLIE. Le wiki est publie DEPUIS le depot ; rien ne mesurait l ecart,
et il s est creuse de VINGT-SEPT JOURS en silence. Deux unites jamais
publiees, vingt et une differentes : pour qui lit la forge plutot que le
depot, toute la revision n existait pas.
Le harnais est STATIQUE, zero appel reseau — cloner la forge romprait la
seule propriete qui fasse qu une preuve vaille hors de ce poste. La mesure
passe donc par un TEMOIN que make wiki-publier depose. Amorce avec la
valeur MESUREE : le wiki d eregion porte b6167f2, dont le message dit
source: ac85278.
Ce qu elle ne prouve pas : un temoin dit ce qui est PARTI, jamais ce qui
est ARRIVE.
LES TROIS SONT EPROUVEES DANS LES DEUX SENS
Douze essais negatifs, douze refus : groupe non projete, acces.yml disparu,
personne au lieu d un groupe, mecanisme invente, raison manquante, compte
revenu a cinq, septieme porte ajoutee sans toucher au compte, renvoi croise
fausse, temoin absent, temoin d un autre depot. Une garantie qu on n a jamais vu
dire non n est pas une garantie, c est une habitude.
ETAT : NON CONFORME, 58 OK, 1 echec, 1 saute.
P60 est rouge, et c est le comportement voulu : le registre a le droit de
perdre. Le retard qu elle signale est reel et anterieur a elle. Une commande le
ferme, et elle vient ensuite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
La revision a commence par un balayage par motifs — chemins morts, cibles make
absentes, comptes derives. Il a trouve une trentaine d ecarts et rate presque
tout le reste : un motif ne voit que ce qui s exprime en motif.
make hote-planifier en est l exemple. La cible EXISTE, donc le controle passait
au vert. C est une cible depreciee qui refuse et sort en 2, recommandee par
AGENTS.md, et qui contredit la REGLE D OR du meme fichier trois ecrans plus
haut. Il fallait lire pour la voir.
74 documents lus un par un. 66 corriges, 8 exacts.
CE QUI ETAIT FRANCHEMENT FAUX
AGENTS.md, la source d autorite, annoncait la flotte pas encore executee contre
des VM reelles. Elle a ete rasee et remontee depuis zero trois fois.
ecosysteme-chezlepro.md, le document montre a un client, portait la meme
phrase : il se sous-vendait gravement.
courriel-conception.md s ouvrait sur aucun role n est encore ecrit, au-dessus de
son propre paragraphe 1 qui les nomme. autorisation.md se terminait sur rien n
est construit alors qu il rapporte des mesures datees du role en fonctionnement.
hebergeur-exploitation.md disait rien n est fait d un depot qui existe.
filiation-emancipation.md se contredisait a deux ecrans de distance.
DES MODELES DECRITS D APRES UN MONDE ANTERIEUR
Le resolveur : cinq documents decrivaient un Unbound par VM en opt-in, trois le
donnaient en exemple d integration FACULTATIVE — il est universel depuis le
2026-08-24. L adressage de nomenclature-vm.md : reseau unique, VLAN 11-15, VMID
a cinq chiffres. Le nommage SDN de sdn-evpn.md contre le code : c est le wiki
qui avait raison.
CE QUI CASSE AU PREMIER ESSAI
Le nom du gabarit dore etait faux a quatre endroits, dont la procedure qui le
FABRIQUE et le critere R2 de l epreuve d operateur independant.
preparer-un-site-hebergeur.md avertissait qu une VM faite a la main serait
detruite : raser derive du plan, il ne la detruira jamais — le risque est l
inverse. Un mot de passe d essai en clair dans un depot public.
DEUX PREUVES ETENDUES, ET UNE QUI SE TROMPAIT ELLE-MEME
P57 couvre les groupes : elle a signale aussitot 29 groupes annonces au-dessus d
un tableau qui en cite 40. P29 confronte le tableau de authentification.md aux
declarations reelles : 12 annonces, 21 reels.
Et P57 imposait un chiffre faux — 56 preuves alors que le depot en porte 57, la
conditionnelle vivant hors de tout comptage. Un garde-fou qui fait respecter une
erreur ajoute l assurance a l erreur.
CE QUI RESTE, ET QU AUCUNE PREUVE NE TIENT
Deux comptes trouves a la main. Et une lacune reelle : rien ne garde les
meta/acces.yml — ni qu un service web-sso en porte un, ni que le groupe qu il
nomme existe. P29 tient les positions d authentification, personne ne tient les
habilitations.
make prouver : CONFORME, 56 OK, 0 echec, 1 saute. 0 lien mort.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
Le jour ou l on s en sert, le poste est mort — et cloner le depot demande la cle
SSH qui est dans l archive qu on essaie d ouvrir. Une procedure rangee dans le
depot serait inaccessible exactement quand elle sert.
L export depose donc restaurer_cles.py et un LISEZ-MOI a cote de l archive. La
cle ne demande plus que gpg, python3 et la phrase de passe.
Le script remet chaque fichier a sa place selon son NOM (machine neuve, parfois
autre compte), repose les droits a 0600 — ssh refuse une cle privee lisible par
d autres, et son message ne dit pas qu il s agit d un droit — et refuse d
ecraser une cle presente, en regardant AVANT d ecrire.
Le LISEZ-MOI porte les trois commandes manuelles. Un outil peut avoir un defaut ;
gpg et tar seront la.
EPROUVE : export vers une cle, poste neuf vide, restauration depuis la cle SEULE,
empreintes comparees — identiques 4 sur 4, droits 700/600, et le refus d ecraser
tire. Le filet manuel passe au meme test separement, identiques 4 sur 4.
make cles-restaurer refusera puisque les cles sont en place — et ce refus est la
preuve que l archive s ouvre et que la phrase de passe est la bonne.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Une cle USB est le plus souvent en FAT32 ou exFAT — pas de permissions Unix.
Le chmod de la derniere ligne y echoue, et le script serait mort APRES avoir
ecrit et verifie l archive : une trace d erreur sur un travail termine, au pire
moment pour semer un doute.
On le dit plutot que de le taire : sur un support sans droits, l archive est
lisible par qui branche la cle. C est le chiffrement qui protege, pas le support.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Le code est replique trois fois (eregion, forge du site, patient 0) et les
voutes chiffrees y sont aussi — le coffre est solide. Les CLES qui l ouvrent
vivaient dans neuf fichiers, 1644 octets, sans copie ailleurs.
Poste seul : les mots de passe restic restent lisibles sur les machines vivantes,
donc recuperable mais douloureux. Poste + une machine : l etat de cette machine
devient illisible. Poste + site : terminal.
make cles-recenser montre ce qui sortirait sans rien ecrire — nom, taille,
empreinte, JAMAIS le contenu. make cles-exporter chiffre en AES256 puis
REDECHIFFRE ce qu il vient d ecrire et compare les empreintes une a une : une
sauvegarde de cles qu on n a pas rouverte n est pas une sauvegarde.
A lancer par l exploitant lui-meme : gpg demande une phrase de passe, elle ne
doit passer ni par un journal ni par le contexte d un assistant.
Trois refus, eprouves en les faisant echouer : destination dans l infrastructure
(un coffre dont la cle est dedans), archive existante (elle est peut-etre la
seule), archive illisible (supprimee). Le premier essai du premier refus etait
faux — le shell developpait HOME avant que je le remplace, l instrument mesurait
ailleurs que la cible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Trois depots tiers etaient en HTTPS, et client_artefacts pose
Acquire::https::Proxy DIRECT — sans quoi le cache du site refuse les tunnels et
aucun depot tiers n est joignable. La ligne est juste ; sa consequence ne l avait
pas ete vue : ces trois depots CONTOURNENT le cache, et chaque VM neuve allait
les chercher sur Internet a sa naissance.
step-cli et alloy sont poses par des integrations UNIVERSELLES. Sans lien, une
machine neuve n obtenait ni son client d autorite ni ses metriques — elle n
entrait dans aucun flux chiffre. Dernier obstacle a une reconstruction hors
ligne, et il tenait dans un mot.
make cacher-paquets tire les 21 deb aux versions epinglees, empreinte SHA256
verifiee depuis l index du depot. Le role partage paquets_tiers les depose et les
installe EN UN SEUL appel a apt — il sait resoudre un ensemble de fichiers locaux
qui se dependent, la ou paquet par paquet echouerait sur l ordre (Icinga en
apporte dix-sept). Six roles branches ; chacun retombe sur le depot distant pour
ce que le cache n a PAS fourni, et rien d autre.
Eprouve pour de vrai : packages.smallstep.com renvoye vers 127.0.0.1, step-cli
desinstalle, cache local efface. Le role rejoue installe 0.30.6-1 depuis le cache
et la machine obtient ses certificats. Zero echec.
Deux defauts de mon propre outil, trouves en le construisant : Smallstep sert son
index NON COMPRESSE (404 sur .gz) donc step-cli n etait jamais mis en cache ; et
un depot injoignable faisait continue AVANT d incrementer le total, si bien que
le script rapportait 20 sur 20 alors qu il en manquait un. Une garde qui ne peut
pas echouer ne garde rien — troisieme fois cette semaine. Corrige puis eprouve en
le faisant echouer.
Reste hors ligne : NTP externe, et l expedition des alertes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Icinga livre son exemple avec root@localhost — une adresse que personne ne lit.
Une supervision qui voit tout et n en parle a personne a le meme effet qu aucune
supervision, en plus couteux : elle rassure.
serveur_icinga_destinataire cree un utilisateur dans le groupe auquel les
notifications sont deja assignees (Icinga refuse deux objets de meme nom, on ne
peut pas redefinir l exemple ; on en ajoute un autre, l exemple reste muet).
Vide, aucun destinataire n est pose ET LE DEPLOIEMENT LE DIT — plutot que de
laisser croire qu une alerte partira.
Eprouve : message expedie par le relais du site, status=sent (250 2.0.0 Ok),
livre directement a mx.chezlepro.ca sans intermediaire ni dependance envers un
locataire.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Le site a son temoin : site-mon-01 (VLAN 36) porte PostgreSQL, Icinga et un
relais. Le depot lui rapporte, et son premier verdict fut un vrai defaut —
site-mon-01 n avait jamais depose son propre etat. Les trois sont au vert.
serveur_backup_verification_locale repasse a true sur le depot : le drapeau ne
dit plus on renonce mais verifie ce que tu peux ouvrir . La liste des noeuds
attendus derive de l inventaire ou tourne le role, donc du site seul.
serveur_postfix gagne un mode relais : un site n heberge aucune boite, il
expedie. Directement par sa frontiere — emprunter le MTA d un locataire ferait
dependre l hebergeur d un ecosysteme qu il peut outvivre.
LA PANNE DE FOND : le modele de routes OPNsense lit enabled ; on lui envoyait
disabled=0, un champ ignore. enabled restait a son defaut, ETEINT. Chaque route
creee par Set-OPS depuis l origine l etait desactivee — invisible, parce qu une
route eteinte EXISTE dans le modele et compte posee . Le rechargement lance
pour activer la nouvelle patte a fait reprendre au noyau sa table depuis le
modele : 14 routes sur 15 disparues, 11 machines de Chezlepro injoignables le
lendemain de sa reconstruction, et le devis toujours vert.
Trois corrections empilees : la cause (enabled), la cecite (le plan lit la table
du NOYAU et signale absent ou eteint), l inaction (la reconfiguration ne se
declenchait que sur un changement du modele).
Aussi : un role du site peut enfin en appeler un autre a travers la frontiere —
le devis traitait tout pair nomme comme l exterieur . Et nftables_admin_ssh se
DERIVE de la carte : recopie a la main, il retardait d une zone, et m a
verrouille dehors de la machine que je venais de creer.
Reste ouvert : le destinataire des alertes est encore root@localhost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
L hebergeur protegeait l etat de tous ses locataires et pas le sien. Deux choses
qu il detient et que personne ne peut regenerer : la racine de son AC, et la
forge du genome. Le reste est reconstructible par le code.
Preuve faite, pas annoncee : sauvegarde reelle puis restic check et restitution.
13 fichiers pour l AC (root_ca_key et intermediate_ca_key compris), 835 pour la
forge.
Le site depose avec SON identite, sur le compte restic que serveur_backup lui
cree, separe des comptes des locataires par les memes permissions. La cible est
derivee du expose de l application qui porte serveur_backup_site : le nom du
service, pas une adresse — client_backup_repo la grave dans le chemin de chaque
instantane.
P36 ne lisait que le plan de l instance montee, donc jamais celui du site — qui
detient pourtant le plus. Elle lit desormais les deux. Verifiee en la faisant
echouer : integration retiree, la preuve tire.
Reste ouvert : personne ne verifie les sauvegardes du site. Le depot tourne avec
verification_locale a false (pose pour les locataires, dont il ne peut pas ouvrir
les depots) et le site n a pas de supervision. La sauvegarde existe et se
restaure ; c est son SILENCE qui n alerte pas encore.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Les six machines du SITE — racine de l AC, forge, cache, resolveur, depot de
sauvegarde, runner — ne recevaient que serveur_debian. Ni auditd, ni fail2ban,
ni apparmor, ni sysctl, ni pare-feu. Le commentaire au-dessus de GROUPE_SOCLE
affirmait pourtant le contraire, ce qui rendait l ecart invisible.
Le pare-feu du site n avait aucune des trois pieces qui le rendent applicable :
- aucune regle generee (resoudre_flux lit un hosts.yml ; le site a un inventaire
dynamique) -> commande nftables-site, branchee sur make flux
- le chemin du jeu de regles sortait du depot (inventory_dir vaut scripts/ pour
un inventaire dynamique) -> host_var explicite
- aucun nftables_admin_ssh : l exploitant administre PAR REBOND, la connexion
arrive avec l adresse de la patte de frontiere DANS LA ZONE VISEE. Le
generateur refuse desormais de produire des regles sans cet intrant.
Defaut attrape en LISANT le jeu de regles avant de l appliquer : la regle DNS de
site-dns-01 omettait 10.17.0.0/16. _supernets_voisins retire l instance montee —
juste chez un tenant, faux du cote du site, ou ca retire le locataire qu on
pilote. L appliquer aurait prive de DNS les quinze machines reconstruites la
veille.
MaxStartups du depot passe en host_var : dans le meta du role, il ne portait que
dans son play, et le passage de serveur_durci le remettait au defaut sans rien
dire. Un reglage qui depend de l ordre des plays revient en arriere.
Verifie depuis un locataire a travers le nouveau pare-feu : cache, forge, depot
(2 snapshots) et resolveur repondent ; l AC du site refuse, et c est correct.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Chezlepro detruite (15 VM, disques compris) et refaite depuis le gabarit
minimal. 15/15 hotes, 0 echec ; make valider passe, test de restitution
compris. Aucun defaut ne venait de la flotte ni du gabarit.
1. Un avertissement n est pas un echec. Proxmox rend WARNINGS: n pour une
tache ABOUTIE ; la garde n acceptait que OK et declarait perdues cinq VM
clonees a 100 pourcent. Le message parlait d etat stopped — celui de la
TACHE, pas de la VM.
2. client_artefacts se contredisait : son commentaire disait de degrader, son
code arretait. L autorite monte en premier, donc avant le cache du
locataire : aucun ordre ne pouvait satisfaire la garde.
3. harden-below-nxdomain etendait le NXDOMAIN signe de la racine pour le TLD
internal a toute la zone du site, sans jamais interroger l autoritatif.
Declencheur : toute question sur un nom absent sous internal, y compris la
zone d un autre locataire. Le cache contenait la bonne reponse ET un
message negatif ; c est le negatif qui etait servi.
aggressive-nsec: no avait semble marcher — c est le redemarrage qui vidait
le cache, pas le reglage.
4. Un locataire doit savoir a qui demander la zone de son hebergeur, sans quoi
il ne peut plus nommer son depot de sauvegarde. La derivation prenait
dns_amorcage pour le resolveur du site : faux chez Technolibre, dont
l amorcage est 9.9.9.9. P03 l a attrape avant tout deploiement.
Au passage : instancier tentait encore le mot de passe unique d avant la
separation des voutes ; comparer echouait en exit 4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Chezlepro rangeait ses instantanes sur une VM DE SA PROPRE FLOTTE. Raser
l ecosysteme pour le reconstruire, c etait raser le filet avec.
Le site a son depot ; les neuf detenteurs d etat y deposent ; une
restitution est sortie (annuaire LDAP lisible, hors flotte).
Isolation par compte Unix, pas par convention : home 0700, cle exclusive,
racine partagee a root en 0711 (traversable, non listable). Les deux refus
constates. Le site heberge du chiffre : il ne peut ni lire ni ouvrir, d ou
la verification deplacee chez le locataire qui detient la cle.
Quatre defauts reveles par ce deuxieme usage :
- la racine des depots ne peut etre le home de personne (StrictModes rendait
Permission denied publickey pour un refus de CHEMIN)
- la racine nie le TLD internal, et harden-below-nxdomain etendait ce non a
toute la zone sans jamais interroger l autoritatif : aucun locataire ne
pouvait nommer un service du site
- le gabarit transporte des fichiers de durcissement perimes, et les machines
du site ne recoivent jamais ssh_hardening
- MaxStartups compte les connexions non authentifiees : un depot de site en
voit la somme de ses locataires
Constat non corrige : les machines du site ne sont pas durcies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
CONSTAT DE L EXPLOITANT, PAYE EN ANOMALIES : convertir une machine deja installee
d `i440fx` a `q35` produit une serie de pannes dont chacune ressemble a autre chose
qu a sa cause. Ce n est pas une correction, c est une transplantation.
LE MECANISME, ECRIT POUR QU ON NE LE REDECOUVRE PAS : `i440fx` est un chipset PCI,
`q35` est PCIe. La topologie des bus change, donc les NOMS D INTERFACES
PREDICTIBLES changent avec le chemin PCI (enp0s3 -> enp1s0) et la machine perd le
reseau ; les chemins de disques bougent ; l ordre d enumeration suit.
C EST AUSSI POURQUOI SET-OPS N UTILISE PAS L IMAGE CLOUD OFFICIELLE DE DEBIAN :
`genericcloud` est livree configuree pour `i440fx`. Une machine nait `q35`, ou elle
ne le sera jamais proprement — et c est ce que l installation depuis l ISO garantit.
Ces deux lignes de la procedure n etaient qu une ligne de tableau. Elles portent
maintenant leur pourquoi, et le SITE les declare comme DONNEES (cle `gabarit`),
plus seulement comme prose.
`make gabarit-etat` compare le gabarit reel a ce que le site declare de lui.
ON VERIFIE LA SOURCE, PAS CHAQUE COPIE. Ma premiere version gardait le CLONAGE : la
propriete s herite, donc verifier chaque clone coute a chaque creation sans rien
dire de plus que verifier le gabarit une fois. Retiree.
TROIS FOIS J AI DEVINE LA FORME DE LA REPONSE AU LIEU DE LA REGARDER — regex_search
a groupe qui rend None, proxmox_vm_info sans `config: current` qui ne rend que
l etat. La garde a declare « ? » sur une VM parfaitement conforme : une garde qui
crie toujours est pire qu aucune, on apprend a l ignorer.
A la demande et non dans `make prouver` : ce controle exige le cluster, que le
harnais ne suppose pas joignable. Meme nature que genome-etat et underlay-plan.
Controle negatif verifie : declarer i440fx fait echouer, rc=1.
make verifier : vert. make prouver : CONFORME, 56 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
REFABRIQUE (VMID 9006, modeleSetOPS-minimal). Quatre roles au lieu de dix-sept :
qemu_guest_agent, cloud_init, sudo_ansible, ssh_baseline — des conditions
d existence, pas des choix d efficacite. Tout le reste vient du socle, et P56 refuse
qu un role retire ne soit repris par personne.
FABRIQUE CHEZ LE SITE, ET C EST LA DECISION QUI COMPTE. « Les ressources du SITE
font autorite pour tous ses artefacts ; elles servent les tenants jusqu a ce qu ils
s emancipent. »
Il se fabriquait DEHORS : `-i "<ip>,"` ne porte aucun group_vars, donc ni mandataire
ni resolveur. L ancien gabarit allait chercher ses paquets chez Debian et resolvait
chez l ancien LAN — 192.168.10.10, lu sur la VM 99998. Le site avait son cache et
son resolveur, et son propre artefact les ignorait.
Desormais la fabrication DERIVE ses ressources du plan du site (underlay --adresses)
et tourne sur le reseau du genome. PROUVE : dix requetes de 10.0.33.31 servies par
site-cache-01, resolveur pose a 10.0.34.11.
L IDENTITE DU GABARIT VIENT DU SITE, PLUS DU TENANT. `proxmox_clone_vmid_modele`
vivait dans les group_vars de l ecosysteme : deux tenants pouvaient cloner deux
gabarits differents sans que rien ne le dise, et un tenant decidait d un objet dont
descend chaque VM de chaque ecosysteme. Meme mouvement que l INDEX (2026-08-25) : le
site ALLOUE, le tenant RECOIT. Cle `gabarit` du plan du site, lue par
underlay --gabarit.
PIEGE FERME EN CHEMIN : creer-vm passait VMID_MODELE="$SETOPS_VMID_MODELE" a
cloner-vm, or inventory_host.py n emet PAS cette variable. Elle valait donc le VIDE,
et ce vide ECRASAIT la valeur derivee — le gabarit du tenant reprenait la main sans
bruit.
ET UN PIEGE DEJA DOCUMENTE, PAYE UNE TROISIEME FOIS : le chemin du controleur
contient une espace, et `lookup('pipe', ...)` le decoupait. Guillemets.
EPROUVE DE BOUT EN BOUT : VM clonee du gabarit minimal — nom, adresse, machine-id
neuf, cle d hote regeneree, agent invite actif ; puis socle applique dessus,
changed=5, 0 echec, auditd compris. VM d essai retiree.
make verifier : vert. make prouver : CONFORME, 56 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Il portait DIX-SEPT roles : exactement ceux du socle et du durcissement, que le
deploiement rejoue a l identique. C etait donc un CACHE — et comme tout cache, il
perimait sans le dire.
MESURE : derniere recapture le 2026-08-09, et SIX de ses roles avaient change depuis
— common_packages, cloud_init, ssh_baseline, ssh_hardening, auditd,
nftables_baseline. Rien ne le signalait : le deploiement masquait la derive en
reappliquant tout, donc personne ne pouvait la voir. Aucune preuve du harnais ne
regardait sa fraicheur.
IL NE GARDE QUE CE QUI DOIT EXISTER AVANT QU ANSIBLE PUISSE AGIR :
qemu_guest_agent l agent repond AVANT SSH — P52 s en sert
cloud_init le seul chemin vers la premiere seconde
sudo_ansible la porte par ou tout entre
ssh_baseline le serveur SSH
Ce ne sont pas des choix d efficacite, ce sont des conditions d existence.
CE QUE CA COUTE, ET QUI EST COUVERT : une VM neuve n est plus durcie a la naissance.
Elle nait cependant DERRIERE LE PARE-FEU DE L HYPERVISEUR, policy_in=REJECT arme au
clonage — verifie sur obs-01. La fenetre d exposition est fermee par la fabric, pas
par le gabarit. Mon objection initiale tombait devant la mesure.
P56 GARDE LES DEUX MOITIES. Qu il ne REGROSSISSE pas : un role ajoute recree le
cache, donc la peremption invisible. Et que rien de retire ne soit PERDU : un role
absent du gabarit ET du socle disparaitrait de toutes les machines neuves, sans
erreur ni trace, et la panne arriverait des mois plus tard sur une machine qu on
croyait durcie. Verifie : 14 retires, 14 repris, zero orphelin. Deux controles
negatifs.
make verifier : vert. make prouver : CONFORME, 56 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Le deploiement depuis ops-01 passe de 3 plays a 21 : douze machines sur quinze
deployees completement. Deux defauts restaient.
UN PORT SYMBOLIQUE ECRIT TEL QUEL.
/etc/nftables.conf:36: Could not resolve service: Servname not supported
ip saddr { ... } udp dport derive accept # serveur_powerdns
`port: derive` dit que le port depend du deploiement. Le devis de la frontiere le
resout depuis le 2026-08-25 ; le generateur nftables ecrivait le mot, et nft
refusait TOUT le fichier. Il resout desormais par le plan et n emet rien quand il
ne peut pas, en le DISANT — un flux tu en silence est une porte qu on croit
ouverte. Verifie que ca ne ferme rien : infra-dns-01 garde son 53 par
serveur_resolveur, et l omission de PowerDNS est juste puisqu il ecoute en loopback
derriere lui.
LA VALIDATION NE POSE PAS LA MEME QUESTION QUE L EMISSION. Ma premiere garde
refusait tout port non numerique et a fait echouer P09, qui valide les roles HORS
instance — la ou derive est legitime. PORTS_SYMBOLIQUES nomme le vocabulaire : le
mot passe a la validation, jamais dans un fichier, et un mot inconnu reste refuse
des deux cotes.
RECHARGER N APPLIQUE PAS UN CHANGEMENT D ECOUTE, deuxieme fois.
warning: to change inet_protocols, stop and start Postfix
fatal: :::submission: Address family for hostname not supported
main.cf porte inet_protocols, que Postfix refuse de changer a chaud. Le master garde
all, tente d ouvrir :::submission en IPv6 et meurt — APRES avoir accepte une
configuration valide. postfix check ne dit rien parce que la configuration EST
valide : c est la transition qui ne l est pas. Meme famille que nginx. main.cf
notifie desormais le redemarrage.
Diagnostic corrige en chemin : j ai d abord accuse postfix@-.service, dont
l assertion echouait — c est moi qui l avais declenche par un demarrage manuel,
postfix.service le declare en Conflicts.
make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Apres les paquets (serveur_cache_site) et le genome (serveur_forge_site), les NOMS.
Meme patron : un installateur, un marqueur.
CE QUE CA CORRIGE, mesure sur infra-pki-01, premiere machine a porter l autorite
de Chezlepro :
DNS BLOQUE un tenant resout chez lui, sa requete ne sort pas
443 sortant OK par adresse
apt via le cache OK le mandataire resout a sa place
apt s en sortait ; TOUT LE RESTE ETAIT AVEUGLE AUX NOMS. Versionner la cle de
signature Smallstep a fait passer une tache et l echec s est deplace d un cran : le
depot lui-meme devait etre joint par son nom.
POURQUOI PAS L UNBOUND DE LA FRONTIERE : il tourne sur le boitier sans etre un
service gere, et le plan du site l avait deja ecrit une fois — un processus n est
pas un service. site-dns-01 est deploye par le moteur et verifie par le harnais.
OU VIT LA DERIVATION : dans site_inventaire.py, qui connait le plan du site ET le
registre des tenants. Le role tourne sur une machine qui n a aucune raison de lire
la carte de l hebergeur — la derivation appartient a qui detient les deux sources.
PIEGE DE FILTRE, ATTRAPE EN LE BRANCHANT : _supernets_voisins() EXCLUT l instance
montee (personne n est son propre voisin). Le site sert TOUS ceux qu il heberge — y
compris celui qu on deploie. Reutilise tel quel, il privait de resolution le seul
tenant en cours de montage. On reutilise donc la DECOUVERTE partagee sans son
filtre : deux recensements divergent, deux filtres non.
Le marqueur verifie deux choses : que le resolveur ECOUTE, et que sa liste
d autorisation ADMET reellement les tenants. Sans la seconde, la panne chez le
locataire ressemblerait a un DNS mort alors que c est une ACL.
make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Le deploiement lance depuis ops-01 s est arrete au premier geste, sur les quinze
machines a la fois : Permission denied (publickey). Les VM neuves n acceptaient que
la cle de l exploitant. Celle du runner EST declaree au plan, mais c est le SOCLE
qui la depose — et le socle doit etre applique par quelqu un qui peut deja entrer.
Boucle fermee : le tenant recevait un terrain qu il ne pouvait pas occuper.
DEUX CLES, DEUX PORTEES :
la cle du SITE -> sur le SEUL runner du tenant l insemination
la cle du TENANT -> sur TOUTES ses machines il va les configurer
POSER UNE CLE AU CLONAGE N EST PAS ENTRER CHEZ LE TENANT. C est un parametre de
creation, au meme titre que l adresse ou le disque : le site ecrit les conditions
de NAISSANCE, il n ouvre aucune session. Le site n obtient aucun acces sur ces
machines ; seul le runner du tenant en obtient un.
Forme tranchee par l exploitant : le site renseigne le seul runner, qui se charge
de toute sa flotte. Le plancher /etc/hosts suit le meme chemin — ops-01 a le sien
depuis son insemination et resout ses quinze voisines par leur nom.
LA REVOCATION EST HONOREE A LA NAISSANCE : une entree a etat absent n est pas
reposee. Sans cette lecture, une cle retiree de la flotte serait ressuscitee sur
chaque VM creee ensuite — panne lente, silencieuse, invisible au plan.
P55 garde les deux moities : la cle du site ne nait que sur un porteur de
serveur_ops_tenant, et aucune machine ne reste sans celle de son tenant. Une
frontiere tenue a une seule couche n est pas tenue. Deux controles negatifs.
make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Sur le runner d un tenant, la cle du SITE DOIT manquer : il porte la carte de la
fabric et ne doit jamais pouvoir l ouvrir. Ma premiere version sortait en erreur
des qu une cle manquait — elle presentait une SEPARATION REUSSIE comme un defaut,
et a fait echouer une chaine parfaitement saine.
Seule l absence de la cle de l INSTANCE MONTEE empeche la machine de travailler.
C est elle, et elle seule, qui decide du code de sortie ; le reste s affiche.
Constate sur ops-01 le jour de son armement :
instance OPS-Chezlepro cle presente
hebergeur SITE-Chezlepro sans cle
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
CE QUE LA MESURE A ETABLI, saut par saut. Le poste de l exploitant atteint la
frontiere, qui LAISSE PASSER (rule 31/0 match, pass out vlan040). Asgard RECOIT le
paquet sur vlan40. La VM ne le voit jamais. Et le noyau dit pourquoi :
ip route get 10.17.19.41 from 10.0.31.11 iif vlan40 -> dev vrf_t17
ip route get 10.17.19.41 from 10.17.0.17 iif vlan40 -> Invalid cross-device link
LA SOURCE EST LE DISCRIMINANT, pas l interface. Le chemin de RETOUR, vu du VRF :
vers 10.0.31.11 -> via 10.0.4.1 dev vlan40
vers 10.17.0.17 -> Invalid argument <- le puits
LE PUITS A SES RAISONS et on n y touche pas : sans lui, une adresse non attribuee
du supernet sort par le defaut, revient par la frontiere dans la table PRINCIPALE
et repart vers le reseau de gestion (mesure du 2026-08-09). Le defaut n est pas
qu il existe, c est qu il est TROP LARGE : il couvre la bande basse ou D-77 place
justement l underlay d un site. Deux regles justes separement, contradictoires
ensemble.
LA CORRECTION EST DERIVEE, PAS ECRITE : pour chaque tenant, les reseaux
d administration qu il DECLARE (nftables_admin_ssh) et qui tombent dans son propre
supernet recoivent une route vers la frontiere, plus specifique que le puits. Ceux
qui vivent dehors n en ont pas besoin — la route par defaut les joint deja.
vrf_t17 ip route 10.17.0.0/24 ... l admin de Chezlepro
vrf_t23 (rien) le sien est hors de son supernet
vrf_t29 ip route 10.29.19.41/32 ... l admin de patient 0 : son ops-01
CE QUE CA REPARE AU-DELA DE L ACCES : les regles administration -> tenant de la
frontiere etaient VRAIES et INAPPLICABLES a la fois. Elles correspondaient, elles
laissaient passer, et le paquet mourait un saut plus loin. Un devis vert sur un
chemin qui ne pouvait pas aboutir.
make verifier : vert. make prouver : CONFORME, 54 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
L insemination avait ete conduite A LA MAIN depuis le runner du site — hors du
depot, donc sans preuve. Elle a maintenant sa cible :
make inseminer TENANT=OPS-Chezlepro
TENANT= PLUTOT QUE LE SYMLINK instance : le runner du SITE amorce PLUSIEURS
locataires ; pointer un lien global sur l un d eux le ferait se prendre pour ce
tenant. Il en NOMME un par commande. Ca borne aussi le couplage que creer-vm
imposait en silence — rien ne disait sur quels tenants ce lien pouvait pointer.
L HOTE SE DERIVE : celui qui porte serveur_ops_tenant. Meme critere que le flux
d insemination et que la cle SSH du runner — le meme mot borne les trois pouvoirs.
P54 GARDE LA LIGNE DE PARTAGE la ou elle glisserait sans bruit. Les deux couches
retenues sont les seules qui ne reclament aucun secret. Le jour ou l on en
ajouterait une, le deploiement echouerait chez le tenant sur une valeur vide, et ce
message ne dirait pas qu un POUVOIR a ete franchi. Controle negatif : ajouter
client_pki, la couche suivante, fait echouer la preuve.
UN GARDE-FOU EXISTANT A INTERCEPTE UNE INSEMINATION MAL DIRIGEE. Le make parent
exporte SETOPS_INVENTAIRE ; ma resolution en heritait et visait l inventaire d un
AUTRE ecosysteme. Le refus vient d inventory_rules, pas de la cible — exactement
l erreur qu un runner servant plusieurs locataires commettrait en silence.
make verifier : vert. make prouver : CONFORME, 54 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Trois faux diagnostics en une journee, tous dus a l instrument et aucun au
composant. curl et bash /dev/tcp ecrasent quatre causes incompatibles dans le meme
mot : ouvert, une POLITIQUE qui refuse, une machine ABSENTE, et la frontiere muette.
LE MEME CODE DIT DEUX CHOSES SELON LE DELAI, et c est la distinction qui a coute le
plus cher : EHOSTUNREACH immediat = pas de route ; le meme apres trois secondes =
il n y a pas de machine, c est l ARP qui renonce. Les confondre a fait appliquer un
pare-feu pour reparer un vide. est pure, donc gardee par un test qui
exige que les deux ne se lisent jamais pareil.
LA GARDE ECHOUAIT DU COTE SILENCIEUX. L outil dit par quelle SOURCE le paquet part
et refuse de conclure sur une passerelle anycast — le piege qui m a fait declarer
muet un REJECT qui emettait bien ses RST. Ma premiere version rendait « pas
anycast » quand inventory_rules manquait, c est-a-dire sur un hyperviseur ou l on a
copie le seul fichier : precisement la ou le piege se produit. Une garde qui echoue
doit crier, pas se taire.
ET « NON CONCLUANT » EST UN RESULTAT : sur une source anycast et un silence, l outil
ne tranche pas, il dit quoi faire — compter les paquets du cote qui refuse, ou
sonder depuis une VM.
TCP seulement, et c est dit : UDP n a pas de poignee.
Valide contre le reel depuis ops-01 : trois cas sur quatre, le quatrieme attendant
deux machines vivantes dans un meme tenant.
make verifier : vert. make prouver : CONFORME, 53 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Decision de l exploitant : block vers l Internet, reject a l interieur, parce que
c est prudent. Ce n est pas le refus qui informe, c est CE QU IL FAIT AU SILENCE :
sous drop partout, un timeout voulait dire aucune machine, aucune route, ou une
politique. Quand la politique parle, il n en reste qu une.
nftables par hote policy drop + reject with icmpx type admin-prohibited
pare-feu Proxmox policy_in = REJECT (POLITIQUE_VM, source unique)
frontiere OPNsense block — INCHANGE, et c est la condition
admin-prohibited ET NON tcp reset : un RST est indiscernable d un port ferme sans
service. La chaine forward reste muette : elle porte le trafic qui TRAVERSE l hote,
et y repondre ferait parler cette machine au nom d une destination qui n est pas
elle.
POURQUOI C EST PRUDENT : l obscurite etait deja nulle a l interieur (chaque machine
porte un /etc/hosts qui liste ses voisines), et la bordure protege le reject —
rien d indeclare ne franchit le perimetre, donc il ne repond jamais a l Internet.
982 000 entrees par jour a la frontiere, dont 82 % un balayage VNC.
LA MESURE A CORRIGE LA MESURE, DEUX FOIS.
Ma preuve interdisait le litteral DROP et a fait echouer un code JUSTE : la
detection d une politique posee AU DATACENTER, qui est un garde-fou. Une preuve qui
interdit un mot au lieu de mesurer une propriete finit par accuser ce qu elle
devrait proteger.
Et l absence parlait deja : EHOSTUNREACH en 3,05 s pour une machine inexistante,
timeout a 6 s pour un refus de la frontiere. Mes deux erreurs de diagnostic ne
venaient pas du drop mais de ma SONDE — curl et bash /dev/tcp ecrasent les deux
dans un meme echec.
Applique : 6 VM en REJECT, 0 creee, 0 retiree. Rien ne se ferme.
Trois controles negatifs verifies.
make verifier : vert. make prouver : CONFORME, 53 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Decision de l'exploitant : chaque runner est maitre de sa voute et en detient la
cle. C'est ce qui rend un runner autonome, donc ce qui rend l'emancipation
atteignable. Elle exigeait un prealable, mesure ce matin : UN SEUL mot de passe
ouvrait les SIX voutes de la flotte, celle de la fabric comprise.
DISTRIBUER AVANT DE SEPARER AURAIT ETE PIRE QUE LE STATU QUO : poser « la » cle
sur chaque runner rendait chaque runner capable d'ouvrir les autres. Compromettre
le plus petit locataire donnait les secrets de l'hebergeur.
Cinq cles, une par ecosysteme, sous ~/.config/setops-vault-<depot>. Verification
croisee apres rechiffrement : la diagonale, et rien qu'elle. L'ancienne cle
maitresse n'ouvre plus aucune des six.
UN SEUL MOT DE PASSE NE POUVAIT PLUS SUFFIRE, et pas pour la raison qu'on croit :
`cloner_vm_debian.yml` charge la voute du TENANT puis celle de l'UNDERLAY dans la
meme execution. `ANSIBLE_VAULT_IDENTITY_LIST` en porte plusieurs et les essaie
toutes — un seul export suffit pour les 28 appels a ansible-playbook, sans en
toucher un seul. La liste se derive dans scripts/voutes.py.
CE QUI BORNE LE POUVOIR N'EST PAS LA LISTE MAIS LA PRESENCE DES FICHIERS. Sur le
poste, toutes les cles sont la — c'est l'humain qui les detient toutes, et P03 lit
les inventaires de tous les freres. Sur un runner, une seule existe. Le code est
identique, le pouvoir ne l'est pas.
DEUX PREUVES ONT DIT CE QUI MANQUAIT. P03 est tombee des la separation : elle lit
les inventaires voisins, donc il lui faut leurs cles — c'est elle qui a etabli que
la liste devait couvrir le voisinage. P16 s'est mise a SAUTER : sa garde ne
connaissait que ANSIBLE_VAULT_PASSWORD_FILE. Une preuve sautee se lit trop
facilement comme une preuve passee.
COUT ASSUME : le chiffre et sa cle cohabiteront sur la meme machine des que les
runners recevront la leur. Le pari tient parce que le perimetre est borne.
Les six voutes sauvegardees avant rechiffrement. L'ancienne cle maitresse reste
sur le poste : la retirer est une decision, pas un nettoyage.
make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec, 0 saute — sans
ANSIBLE_VAULT_PASSWORD_FILE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
Le flux etait declare et applique ; il manquait l'IDENTITE. Le runner du SITE
atteignait la porte de ops-01 sans avoir de cle.
LE PIEGE COMPTE PLUS QUE LE CORRECTIF. La cle s'injecte au CLONAGE, et `creer-vm`
cree TOUTES les machines d'un tenant. L'injecter a chaque clonage aurait donne a
l'hebergeur un acces SSH a la flotte entiere de chaque locataire, en silence — ca
aurait defait a la couche IDENTITE ce que le pare-feu venait de borner a la couche
RESEAU. Le meme critere gouverne donc les deux : porter `serveur_ops_tenant`.
`SETOPS_CLES_AMORCAGE` est vide partout ailleurs. Elle S'AJOUTE a celle de
l'exploitant, elle ne la remplace pas : c'est l'humain qui arme.
La cle vient du PLAN DU SITE, pas du disque local : materialiser depuis le poste
et depuis le runner doit produire la meme VM.
DEUX COUCHES MANGEAIENT LES ESPACES. Proxmox rendait `SSH public key validation
error` — message muet sur la cause. Isole par un CONTROLE (rejouer sans la cle :
la tache passe), puis par la mesure de ce qui arrivait au module :
"sshkeys": "ssh-ed25519" <- le premier mot, rien d'autre
J'ai accuse `make` d'abord ; c'etait `ansible-playbook -e cle=valeur`, qui decoupe
AU SHLEX. D'ou l'environnement pour le transport et `-e '{...}'` en JSON pour
l'entree. (Un scalaire YAML plie ne produit pas non plus de saut de ligne.)
TROIS TESTS QUI NE GARDAIENT RIEN. `test_inventory_host` inscrit ses tests dans une
liste explicite ; mes deux nouveaux n'y etaient pas — definis, jamais joues. La
garde d'exhaustivite ajoutee en a trouve un TROISIEME le jour meme,
`test_etiquette_vlan_repli_et_vide_explicite`, jamais inscrit depuis sa creation :
inscrit, il levait un KeyError sur une fixture qu'il lisait mal. Un test non
inscrit est pire qu'un test absent : on croit l'avoir.
PREUVE, AVEC SON CONTROLE NEGATIF :
runner du SITE -> ops-01 ops-01 10.17.19.41/24 entre
runner du SITE -> 10.17.19.21 Connection timed out refuse
make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
L'insemination avait un nom depuis ce matin ; elle n'avait pas de flux. Deux
declarations, aux deux bouts, et rien d'autre :
serveur_ops_site egress 22/tcp -> serveur_ops_tenant
serveur_ops_tenant ingress 22/tcp <- runner_site partage: true
ETROIT PAR CONSTRUCTION : il vise le GROUPE `serveur_ops_tenant`, qu'un ecosysteme
ne pose que sur une machine. Au socle, il aurait ouvert le SSH du site vers toute
la flotte du tenant.
L'en-tete disait « ce role n'entre JAMAIS chez un tenant ». Frontiere intenable :
`creer-vm` exige `_instance-requise`, et le runner du site avait deja du basculer
son symlink `instance` sur OPS-Chezlepro pour materialiser ses VM. Declarer ne cree
pas ce pouvoir — ca rend limitable un pouvoir qui s'exercait sans borne. Ce qui
reste interdit n'est pas une regle mais un FAIT : il n'a pas la voute du tenant.
LA REGLE EST EMISE D'UN SEUL COTE, et pas celui qu'on croit. Le paquet penetre le
pare-feu par la patte du SITE, pas par le transit : la regle appartient au cote
site du devis. L'emettre aussi depuis l'`ingress` du tenant aurait produit une
seconde regle sur la mauvaise interface — jamais evaluee, indiscernable d'une regle
utile. La declaration du tenant pose sa regle nftables, et elle seule :
ip saddr { 10.0.31.11 } tcp dport 22 accept
L'adresse DERIVE du plan du site. Ecrite a la main, elle aurait survecu au prochain
deplacement du runner sans bruit — le site a deja deplace ses machines le 08-25.
Plan de la frontiere : 2 objets a creer, 0 a retirer, 126 inchanges. RIEN D'APPLIQUE.
P41 APPLIQUEE AU PLAN DU SITE : `resoudre_flux` en avait besoin a son tour ; les
trois lecteurs demenagent dans `underlay` et `devis_opnsense` delegue.
DEUX GARDES ONT TRAVAILLE : P33 a refuse `ingress 22` sur un hote portant deja le
sshd du socle (reponse : `partage: true`, comme `serveur_backup`), et le devis a
refuse d'emettre vers un alias vide.
UN TEST ROUGE DEPUIS TROIS JOURS. `test_adressage_derive` construisait un site avec
un `index` — or un SITE n'en a pas depuis add94f2 (08-25), remplace par
`bande_basse_de`. Invisible parce que le geste quotidien est `make prouver`, qui ne
joue pas les tests. Remis sur le contrat actuel, avec sa contrepartie : sans
`bande_basse_de`, aucun chevauchement n'est tolere.
make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
`creer-vm` confirmait son succes en attendant une reponse SSH. Le runner du SITE
materialise le terrain de TOUS les tenants, mais la frontiere lui refuse d'entrer
chez eux — c'est le sens meme de leur isolation. La premiere VM qu'il a creee a
donc ete declaree en echec apres 600 secondes alors qu'elle tournait, avec
l'adresse exacte que le plan lui destinait :
Attente de SSH sur ops-01 ............ ECHEC: injoignable apres 600s.
LA TENTATION ETAIT D'OUVRIR LE SSH du runner vers tous les tenants. Ca aurait
repare la mesure en detruisant ce qu'elle protege : une machine capable d'entrer
chez chaque locataire est precisement ce que cette architecture refuse d'avoir.
L'agent invite repond sans rien ouvrir — l'API des hyperviseurs est deja le flux
par lequel la VM vient d'etre creee, donc qui peut la creer peut la voir naitre —
et il PROUVE DAVANTAGE. « Quelque chose ecoute sur le port 22 » ne dit ni quel
systeme a demarre, ni si cloud-init a pose la bonne adresse. Sur ops-01 :
10.17.19.41 portee sur asgard — Debian GNU/Linux 13 (trixie) 6.12.101
Ses echecs distinguent deux causes tres differentes : « la machine demarre mais
rapporte une AUTRE adresse — cloud-init, ou le pont sur lequel elle est posee »,
et « introuvable sur la fabric ».
ATTENDRE LA DISPONIBILITE A CHANGE DE MAIN. Guetter cloud-init et la liberation de
dpkg appartient a qui va CONFIGURER : `deployer` le fait desormais, la ou il se
contentait d'un `ping` unique. Il y gagne ce qu'il n'avait pas — attendre le verrou
APT, faute de quoi la premiere couche echouait dessus. Contrepartie assumee : un
nom d'hote errone patiente au lieu d'echouer vite ; echouer vite interdirait de
chainer creation et deploiement, le geste central d'une reconstruction.
P52 garde le couplage ferme. Deux controles negatifs verifies.
make prouver : CONFORME, 52 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.
1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
11 a retire les modules Proxmox de cette collection.
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.
3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
`make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
mainteneur, ou les collections viennent du paquet systeme, toujours dans le
chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.
4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
`proxmoxer` SUR LE CONTROLEUR.
La bibliotheque Python proxmoxer est absente
Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
ce qui prouve que la ligne est au bon endroit.
P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.
RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.
Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Premiere materialisation de VM depuis le runner du SITE :
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
Meme depot, meme playbook, meme plan que chez le mainteneur. La difference tenait
a une seule chose que le moteur ne disait pas : la VERSION de ses collections.
Le poste porte `community.general` 10.3.0. Le runner, monte un jour plus tard, a
recu la 13.3.0 — et la version 11 a RETIRE les modules Proxmox de cette collection
(ils vivent desormais dans `community.proxmox`). `requirements.yml` nommait ses
collections sans dire lesquelles : chaque machine installait donc ce qui etait
courant le jour de son montage. Une dependance non epinglee n'est pas une
dependance, c'est un pari sur l'etat d'Internet a la date du deploiement.
TROIS CORRECTIONS.
`requirements.yml` epingle les trois collections aux versions eprouvees.
`serveur_ops` installe avec `--force`. Sans lui, ansible-galaxy laisse en place une
version SUPERIEURE a celle demandee : il ne retrograde pas. Un poste peut etre en
avance, pas seulement en retard, et le depot doit faire autorite dans les deux sens.
P51 garde les deux faiblesses de ce fichier — celle du 2026-08-24, une collection
utilisee sans etre declaree (`ansible.posix`), et celle d'aujourd'hui, declaree sans
version. La preuve ne lit que les fichiers de TACHES : un `defaults/main.yml` porte
`net.ipv4.ip_forward`, qu'un motif trop large prend pour un module.
MIGRATION CONNUE, PAS FAITE : passer les quatre modules Proxmox a
`community.proxmox` permettra de suivre `community.general` au-dela de la 11.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'outil ne savait qu'AUTORISER — `"action": "pass"` etait en dur dans l'emetteur.
Une regle de silence ne pouvait donc pas naitre du depot, et j'en avais pose deux
a la main sur le boitier : exactement ce que ce projet refuse.
CE QUI L'A MOTIVE. Le journal de la frontiere ecrivait 982 000 entrees par jour,
dont 82 % un balayage Internet contre le port VNC et le reste du bavardage de
decouverte du reseau local. Sa fenetre utile etait tombee a QUARANTE-QUATRE
SECONDES. J'y ai cherche la trace d'un flux du site vers les hyperviseurs, je n'ai
rien trouve, et j'en ai conclu a tort qu'aucune regle ne bloquait. Un journal noye
ment aussi surement qu'un journal mort. Mesure apres declaration : ~20 700/jour.
Une regle de silence ne change AUCUN comportement : ce qu'elle vise etait deja
refuse par le defaut. Elle ne supprime qu'une trace que personne ne lira.
TROIS PIECES.
`cle_regle` accepte une action sans changer d'un octet la cle des regles `pass`
deja posees. L'ajout naif d'un champ les aurait toutes detruites pour les recreer
a l'identique, sur la frontiere, en production. Le plan l'a confirme : 7 a creer,
0 a retirer, 121 inchangees.
`_corps_regle` lit l'action, la consignation et la SEQUENCE depuis le devis. La
sequence est ce qui rend un `block` sur : OPNsense evalue en `quick`, donc un
blocage large emis avant les `pass` fermerait courrier, web et acces distant.
`devis_opnsense` lit `opnsense_silences` et en fabrique regles et alias, motif
compris — une regle `block` muette sans raison ecrite est indiscernable d'un oubli.
P50 garde ces deux dangers. Controles negatifs verifies : un silence en sequence 1
echoue, un silence sans motif echoue.
Carte : 28 pieces d'audit (P48 l'avait vu juste).
make prouver : CONFORME, 50 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`docs/registre-flux.md` est GENERE depuis les `roles/*/meta/flux.yml`, et c'est le
document qu'un humain lit pour savoir ce que le pare-feu laisse passer. Son
EXISTENCE etait verifiee depuis longtemps ; sa FRAICHEUR ne l'etait pas.
Il avait derive : la garde d'administration y portait encore `10.0.0.0/24` alors
que le reseau d'administration vaut `10.17.0.0/24`, deux flux `client_resolveur`
ajoutes depuis n'y figuraient pas, et un hote manquait des listes de sources. Un
lecteur y aurait lu un pare-feu qui n'existe plus.
L'inventaire avait deja sa garde — P03, le diff-vide du plan. Le registre des flux
est le meme genre d'artefact : genere, versionne, lu par un humain. Il lui
manquait la meme. `generer_registre` etant une fonction PURE, P49 la rejoue en
memoire et compare — une preuve qui repare ce qu'elle mesure ne mesure plus rien.
Controle negatif ideal, et il ne s'invente pas : la version commitee elle-meme.
Restauree, la preuve echoue ; regeneree, elle passe.
CE QUE CETTE DECOUVERTE CORRIGE AUSSI DANS MA TETE. J'avais decrit le symptome
comme « le runner salit ses propres clones » — une contradiction structurelle
entre un depot-clone et un repertoire de travail. C'etait faux, et la question de
l'exploitant l'a mis au jour. Regenerer un artefact DOIT produire un diff quand
les sources ont change ; ce qui manquait n'etait pas une architecture, c'etait une
garde. Un symptome observe depuis un seul endroit ressemble toujours a une
propriete de cet endroit.
Ce commit emporte aussi la regeneration elle-meme : le registre du moteur, et les
quatorze fichiers nftables de Chezlepro, remis en accord avec leurs sources.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`docs/carte-set-ops.md` est l'index du MAINTENEUR : l'ordre de lecture du corpus,
et surtout le catalogue des MECANISMES TRANSVERSES avec, pour chacun, OU IL VIT
DANS LE CODE. Son but est ecrit en toutes lettres : « ne plus re-deterrer ce qui
existe ».
Elle n'avait aucune garde, alors que `catalogue-services.md` a la sienne depuis
P38. Ses sept chiffres etaient faux — 54 roles annonces contre 60, 34 documents
contre 38, 15 pieces d'audit contre 27, 70 decisions contre 78.
Le defaut couteux n'est pourtant pas la. C'est le POINTEUR MORT : la carte dit ou
vit un mecanisme, quelqu'un ne l'y trouve pas, et le reimplemente a cote —
exactement la panne qu'elle existe pour prevenir. Aucun de ces nombres ne fait
travailler personne ; mais un document dont les faits verifiables sont faux cesse
d'etre consulte, et c'est alors ses pointeurs qu'on perd.
P48 verifie les deux : 84 chemins cites existent, et 7 chiffres correspondent a
la mesure. Controle negatif verifie sur les DEUX moities — un chiffre fausse, un
pointeur casse, la preuve echoue dans les deux cas.
QUATRE DISTINCTIONS ont du etre ecrites pour qu'elle ne soit pas fausse dans
l'autre sens : un gabarit de nom (`preuve-<date>.md`) decrit une forme, pas un
fichier ; un chemin hors depot (`~/.config/setops-vault-pass`) vit sur le poste de
l'exploitant, et c'est tout l'interet de la doctrine des voutes ; un fragment
(`meta/acces.yml`) vaut comme SUFFIXE, parce qu'un index se lit ainsi ; et un
artefact GENERE (`hosts.yml`) n'a pas a exister dans le moteur. Les quatre sont
nommees dans le code plutot que sautees en silence.
Le tableau « Le depot en chiffres » remplace les comptes en prose : ce qu'on
n'entretient pas, on ne l'affirme pas — ou bien on le fait recompter.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Decision de l'exploitant : la forge du SITE fait autorite pour le genome. Toute
autre copie — y compris celle d'ou le moteur a ete pousse jusqu'ici — est un
MIROIR.
Un ecosysteme se reproduit depuis la forge de son site : c'est de la qu'il clone
son moteur, ses plans, ses modeles. Si l'autorite est ailleurs, cette forge
devient un cache qu'on croit a jour — et le 2026-08-26 elle etait quatre commits
en arriere sans que rien ne le signale, dont le correctif qui desarme le pare-feu
Proxmox.
UNE AUTORITE QU'ON NE VERIFIE PAS EST UNE AUTORITE QU'ON SUPPOSE.
`make genome-etat` confronte, depot par depot, ce que le poste porte a ce que la
forge porte. Il REFUSE en cas d'ecart plutot que de le signaler : un ecart connu
et tolere redevient un ecart oublie, et la commande qui le corrige tient en trois
mots. Il dit aussi quand la copie locale n'est pas propre — des commits pas
encore faits sont une autre forme de retard.
Mesure au passage, et traitee plutot qu'ignoree : le premier contact avec la
forge echoue une fois sur six — poignee TLS expiree, puis cinq reponses de suite.
Ce n'est pas le chemin, qui est prouve ; c'est l'acceptation TLS apres un temps
d'inactivite. Les deux cibles reessaient.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`serveur_ops` et `serveur_ops_site` sont deployes sur `site-ops-01` : le moteur,
les plans des trois tenants, les collections hors ligne, et la voute de
l'underlay deposee CHIFFREE. Le site a son runner.
DEUX FORMES DE RUNNER, ET UNE SEULE ETAIT PREVUE.
`serveur_ops` exigeait un depot `role: instance` et un `serveur_ops_instance` —
la forme d'un runner de TENANT, qui pilote un ecosysteme, monte son plan par le
lien `instance`, detient sa voute.
Un runner de SITE ne pilote rien : il MATERIALISE le terrain que d'autres
occuperont, et son inventaire est dynamique. Son plan declarait donc ses depots
de tenants en `role: tenant`, a dessein et avec ses raisons ecrites — et le role
le refusait au nom d'une exigence qui ne le concernait pas. Il exige desormais
que le runner pilote QUELQUE CHOSE, sans prescrire quoi.
Et il posait le lien quand meme : `serveur_ops_instance: ""` donnait
`instance -> /opt/setops/`, un repertoire qui n'est l'ecosysteme de personne. Un
lien qui existe et ne designe rien est pire qu'un lien absent : tout ce qui le
suit croit avoir trouve une instance.
LE CERTIFICAT COUVRE LES NOMS DU SERVICE.
`client_pki` ne mettait dans ses SAN que le FQDN, le nom court et l'IP. Le
clonage du genome a bute dessus : « certificate subject name
(site-forge-01.genese.internal) does not match target hostname
'forge.genese.internal' ». Le nom declare `expose:` etait publie partout —
plancher, zone DNS — et couvert nulle part.
Les expositions que cet hote SERT REELLEMENT entrent maintenant dans le
certificat. Un certificat qui revendiquerait le nom d'un service rendu ailleurs
serait une usurpation, pas une commodite.
`make genome-pousser` — LE RUNNER POUSSE, LE POSTE NE ROUTE PAS.
La forge du genome vit DANS le site, sur un reseau que le poste de l'exploitant
ne route pas. On ne perce pas un chemin pour lui : on lui retire le role. Le
poste emballe les commits dans un `git bundle` — un fichier, verifiable, qui ne
demande aucun reseau — et le runner verifie, avance EN AVANCE RAPIDE SEULEMENT,
puis pousse avec SA cle, autorisee en ecriture sur ces depots-la seulement.
Ce qui est pousse se DERIVE de `serveur_ops_depots`. `DEPOT=<nom>` en cible un
seul.
Trois pieges rencontres, tous inscrits dans le code : l'outil ne peut pas etre
supposé present sur le runner (il ne recevrait cette version qu'APRES la poussee
qu'elle sert a faire — le controleur le porte) ; la branche n'est pas toujours
`main` (deux depots vivent sur `master`, et le plan le declarait deja) ; le
dossier des depots freres contient un espace, d'ou `argv` et non `cmd`.
Le juge n'est pas le journal du playbook mais LA FORGE : on demande a son API ce
qu'elle porte, et on refuse si ca differe de ce que porte le runner. Six depots
verifies.
Pourquoi ca comptait : la forge du site etait restee quatre commits derriere le
poste, sans que rien ne le signale — dont le correctif qui desarme le pare-feu
Proxmox. Un ecosysteme qui se reproduit depuis une forge en retard reproduit ses
defauts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le site ne servait aucun PTR. `serveur_powerdns_zone_inverse` derivait d'un
supernet /16 — la forme d'un TENANT, qui tire tout de son index. Un site ne
derive pas : il declare plusieurs /24 et n'a pas de supernet unique, si bien que
la derivation rendait une chaine vide et qu'aucune zone n'etait generee.
Le site revendique desormais exactement ce qu'il occupe :
31.0.10.in-addr.arpa 32.0.10.in-addr.arpa
33.0.10.in-addr.arpa 34.0.10.in-addr.arpa
Revendiquer `0.10.in-addr.arpa` d'un seul geste aurait ete plus simple et faux :
cette zone couvre aussi la frontiere, le transit et les hyperviseurs, qui ne sont
pas a lui. Une autorite qu'on s'attribue sans l'exercer est une panne differee —
le resolveur repondrait NXDOMAIN pour des adresses qu'un autre sait nommer.
LA DERIVATION VIT DANS UN FILTRE (`zones_inverses`) parce qu'elle a DEUX
appelants : `serveur_powerdns` ecrit ces zones, `serveur_resolveur` les delegue a
l'autoritatif. Deux calculs separes finiraient par diverger, et la divergence ne
se verrait qu'au premier PTR interroge. Le nom d'une zone dit sa profondeur —
trois etiquettes numeriques valent un /24, deux valent un /16 — et le modele en
deduit seul la forme du PTR.
DEUX CHEMINS MORTS TROUVES EN ROUTE.
Les zones etaient servies, et personne ne les demandait. `serveur_resolveur`
deleguait la zone directe par une `stub-zone` mais pas les inverses : `dig -x`
rendait vide depuis les cinq machines alors que la meme requete posee directement
a l'autoritatif repondait juste. Un service correct derriere un chemin que rien
n'emprunte.
Puis, les stubs poses, Unbound repondait toujours NXDOMAIN avec le drapeau `aa` —
une reponse AUTORITAIRE, sans jamais consulter le stub. Il embarque des
`local-zone` pour tout l'espace RFC1918 inverse. `nodefault` n'y change rien : ce
mode n'agit que si le nom correspond EXACTEMENT a une zone par defaut, et la
sienne est `10.in-addr.arpa`, le /8 entier. C'est `transparent` qui laisse la
requete suivre son cours — pour nos quatre zones seulement, la ou
`unblock-lan-zones` aurait ouvert tout l'espace prive.
Rien ne distinguait ce blocage d'une absence : le meme NXDOMAIN qu'un nom qui
n'existe pas.
AUSSI : les zones inverses sont desormais validees par `named-checkzone` comme la
directe (un fichier mal forme etait refuse en silence par PowerDNS), et une zone
qu'un ecosysteme cesse de revendiquer est retiree du repertoire.
VERIFICATION. Les cinq PTR resolvent depuis les cinq hotes par le resolveur, les
huit noms directs par le plancher ET par le DNS, et les deux roles sont
idempotents (changed=0).
P47 evalue la derivation sur cinq cas — un site a quatre zones, un tenant a une,
deux machines d'un meme /24 qui n'en font qu'une, aucune adresse, une adresse
illisible. Controle negatif verifie.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le decoupage du site en quatre zones a deplace cinq machines. Ni le plancher
/etc/hosts ni la zone DNS n'avaient suivi. Quatre defauts, tous dans le moteur.
LE PLANCHER ETAIT EFFACE A CHAQUE DEMARRAGE — ET LE PREMIER CORRECTIF N'EN
ETAIT PAS UN.
`hosts_statiques` posait `99-setops-hosts.cfg` avec `manage_etc_hosts: false`,
pendant que `cloud_init` posait `99_setops.cfg` avec `true`. Dans `cloud.cfg.d`
l'ordre est LEXICAL et le dernier gagne : `-` vaut 0x2D, `_` vaut 0x5F. On a
donc retire la cle de `cloud_init` — le role qui POSSEDE le fichier decide —
puis renomme notre fragment `zz-` pour passer apres le `99_chezlepro.cfg` du
gabarit dore.
Et ca ne suffisait toujours pas. Redemarrage d'epreuve : plancher encore efface.
La cause reelle est ailleurs — Proxmox inscrit `manage_etc_hosts: true` dans la
USER-DATA de son lecteur cloud-init, et la user-data prime sur `cloud.cfg.d`
tout entier. Aucun fragment ne pouvait gagner ; renommer pour parler en dernier
ne servait a rien, le dernier mot n'appartenant pas a ce repertoire.
Ce que cloud-init regenere, il le regenere depuis `hosts.debian.tmpl` — le
gabarit le documente lui-meme. `hosts_statiques` le pose desormais avec le MEME
contenu que /etc/hosts, et une garde compare les deux a chaque passage.
Redemarrage d'epreuve : les neuf entrees sont la.
La garde precedente affirmait « conforme » en mesurant l'ordre lexical — vrai,
et sans rapport avec ce qui se passait. Une garde qui mesure la mauvaise chose
est pire qu'aucune.
LA ZONE DNS NE PUBLIAIT PAS LES NOMS DE SERVICE.
`forge.genese.internal` et `pki.genese.internal` — des noms que les certificats
portent et que les clients appellent — n'avaient aucun enregistrement. Seul le
plancher savait les resoudre. Trois causes empilees :
- le plan du site coupait `serveur_powerdns_publier_expositions`, au motif que
« le site n'a pas d'edge » : ca confondait PUBLIC et EXPOSE ;
- `expositions_des_applications` rendait `domaine: None` faute de
`domaines.yml`, et le modele de zone ecarte les expositions dont le domaine
n'est pas la zone. Repli ajoute, symetrique de celui deja ecrit pour `edge` :
sans domaine public declare, le domaine est celui que porte le FQDN ;
- `serveur_powerdns` exigeait les deux registres et echouait si `domaines.yml`
manquait — le meme tout-ou-rien que `hosts_statiques` a corrige le meme jour.
Puis `named-checkzone` a refuse la zone : `dns.genese.internal` heritait d'un
CNAME par defaut du role ET d'un A par exposition. La garde a bien joue son role
— elle a arrete une zone cassee avant qu'elle soit servie. Le plan l'emporte
desormais sur le defaut du role.
Un service ne doit pas dependre d'un plancher pour etre joignable : le plancher
est un filet, pas le sol.
VERIFICATION. Les huit noms — cinq machines, trois services — resolvent vers les
bonnes adresses depuis les cinq hotes, par le plancher ET par le DNS, et le
plancher survit au redemarrage.
P46 refuse desormais deux choses : plus d'un role ecrivant `manage_etc_hosts`,
et l'absence du gabarit maitre. Controle negatif verifie.
RESTE NOMME, PAS CORRIGE : la zone INVERSE. `serveur_powerdns_zone_inverse`
derive d'un supernet /16 — la forme d'un tenant. Un site declare plusieurs /24
et n'a pas de supernet unique : aucune zone inverse n'est generee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le decoupage du site en quatre zones a revele un defaut qui dormait dans le
moteur. `firewall=1` sur l'interface d'une VM fait passer tout le trafic ponte
par conntrack. Sur un VNet SDN c'est sans consequence : en EVPN le routage
inter-VNet se fait dans le VRF, SUR LE NOEUD, et le flux ne quitte jamais
l'hyperviseur. Sur un pont classique route par une frontiere externe, deux VM du
MEME noeud dans deux VLAN differents ne se parlent qu'en EPINGLE : la trame sort
par le lien physique, la frontiere la route, elle revient sur le meme pont. La
meme table conntrack voit alors les deux moities de la connexion, classe le
retour INVALID, et PVEFW-FORWARD le jette.
La mesure a tranche :
ops(asgard) -> pki(asgard) 0/8 dns(gandalf) -> cache(gandalf) 0/8
ops(asgard) -> forge(vishnu) 6/8 dns(gandalf) -> pki(asgard) 6/8
ops(asgard) -> cache(gandalf) 8/8 dns(gandalf) -> forge(vishnu) 8/8
Toutes les paires intra-noeud echouent, toutes les paires inter-noeuds passent.
Douze tentatives faisaient monter le compteur `ctstate INVALID` de +112 sur
asgard et +116 sur gandalf ; `firewall=0` pose, il ne bouge plus — 0 sur les
deux, pour le meme trafic.
Le defaut se deguise en panne reseau : la poignee TCP ABOUTIT, et ce sont les
paquets de DONNEES qui disparaissent. L'AC le disait dans son propre journal
(`TLS handshake error ... i/o timeout` : elle accepte et attend un ClientHello
qui n'arrive jamais). Ecartes un par un, par la mesure : regles identiques champ
par champ, alias corrects, assignation des interfaces confirmee, ARP et routes
saines, IPS desactive, shaper vide, NAT source limite a `wan`, MTU a 1500 de
bout en bout — et la taille sans effet, 100 octets se perdant comme 1460.
L'INTENTION DECLAREE NE SUFFIT PAS : un tenant declare
`proxmox_clone_parefeu_interface: true` avec `proxmox_clone_pont: vmbr1` comme
valeur PAR DEFAUT, chaque hote la remplacant par son VNet derive. L'hote qui
retombe sur `vmbr1` naitrait arme sur un pont classique. `cloner_vm_debian.yml`
croise donc l'intention avec le pont REELLEMENT utilise, et le dit quand il
desarme — un desarmement muet serait le meme piege, en silence.
P45 EVALUE l'expression du playbook sur quatre cas plutot que d'en lire le
texte, dont un qui DOIT rendre vrai : sans lui, une expression constamment
fausse passerait la preuve sans rien garantir. Controle negatif verifie — le
drapeau remis a plat fait echouer la preuve.
Ce commit emporte aussi le desarmement d'`unattended-upgrades` dans
`common_packages`, jusqu'ici applique sur les machines mais pas versionne : le
verrou dpkg tenu 48 minutes par une vague de maj automatiques a coute deux
deploiements.
Le site a revele ce defaut parce qu'il a ete le premier a porter plusieurs
zones. Ce n'est pas une particularite du site : un tenant derive les siennes du
meme principe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le site n'est plus un /24 plat. Une zone par nature d'autorite — pilotage, autorite,
genome, service — chacune son VLAN et sa patte sur la frontiere. L'inversion corrigee,
mesuree : les cinq VM du site n'avaient AUCUN filtrage est-ouest, contre policy_in=DROP
sur une machine de tenant. La plus autoritaire etait la moins protegee.
Filtrage nord-sud par choix de l'exploitant : un seul point de police, un seul devis.
90 -> 117 regles. Chaque flux `flotte` produit une regle par zone SOURCE, destination
nommee — ce qui etait gratuit devient police.
L'ORDRE FAIT PARTIE DE L'INTEGRATION. client_pki tournait en parallele sur tous les hotes ;
sur celui qui porte l'autorite il recharge step-ca, et les quatre autres echouaient dans
cette fenetre sur `TLS handshake timeout` — un message qui accuse le reseau. Chaque
integration declare desormais sa dependance dans meta/integration.yml, les playbooks en
sont le miroir genere, et P44 refuse l'ecart (4 controles negatifs). `serveur: ~` est une
reponse valable : client_metrique pose un exportateur qu'on vient LIRE.
Autres defauts du meme soir :
- le plancher /etc/hosts venait APRES le premier apt, qui vise le cache par son NOM :
boucle fermee des que les adresses changent. Il ne s'installe pas, il rend installable.
- dns_amorcage ecrit en dur a eu tort deux fois ; il se derive de serveur_resolveur.
- l'ACL du resolveur derivait d'un seul sous-reseau : trois zones refusees sur quatre.
ET UNE ERREUR A MOI : j'ai diagnostique un trou noir de MTU et declare 1450. Faux — pas de
VXLAN sur ce chemin, tout est a 1500 de bout en bout. Ma mesure etait reelle, mon
interpretation non : je venais de debrancher la carte a chaud, et chaque `ip link set mtu`
reconfigurait l'interface. C'est la reconfiguration qui debloquait, pas la valeur.
44 preuves vertes, ansible-lint profil production.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Couvre ce qui a failli coûter 36 objets ce soir : le devis de la frontiere avait cesse de
voir le site et proposait de retirer tous ses alias et toutes ses regles. Harnais vert,
lint vert — seule la lecture manuelle du plan avant application l'a attrape.
La premiere version etait inutile, et c'est instructif : elle lisait le plan par
`devis_opnsense._machines_du_plan_site()`, la fonction meme dont la panne etait a
detecter. Eprouvee sur la regression reelle, elle SE TAISAIT — les deux voyaient le vide,
et elle concluait « rien a prouver ». Une preuve qui partage la source de ce qu'elle
verifie ne verifie rien.
La version retenue lit plan/serveurs.yml et plan/applications.yml DIRECTEMENT et confronte
au devis produit. Eprouvee sur la regression reelle : elle refuse et nomme la cause.
43 preuves vertes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mise au point de l'exploitant : la frontiere n'a pas de service DNS actif et gere. Un
Unbound y tourne — il repondait, ce qui m'a induit en erreur — mais un processus n'est pas
un service. La delegation de zone que j'y avais posee est retiree.
Et le site a son propre DNS : `dns_amorcage` pointait encore sur la frontiere, valeur d'un
moment ou site-dns-01 n'existait pas. Elle vaut desormais 10.0.3.51.
Deux defauts revelés au passage :
- devis_opnsense lisait encore underlay.machines(), vide depuis le deplacement du plan.
Il proposait de RETIRER 36 objets — tous les alias et regles du site. Aucune preuve ne
couvre ce devis : c'est le plan avant application qui l'a attrape.
- le socle defaisait la bascule de client_resolveur a chaque passage. Sa garde ne
protegeait que l'hote du resolveur (127.0.0.1). Invisible chez un tenant, ou
dns_amorcage vaut l'adresse du resolveur : la coincidence masquait le defaut. Ca ne
s'est vu qu'en retirant la regle de pare-feu — les cinq machines ont perdu la
resolution d'un coup.
L'amorcage ne s'applique plus que si rien de sense n'est en place. Verifie : socle
`changed=0`, les cinq machines restent sur 10.0.3.51.
42 preuves vertes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
J'ai repete toute la journee qu'un SITE n'est pas un plan. C'etait faux : j'en
reconstruisais un morceau par morceau dans underlay.yml sans le nommer. Ce qui est vrai,
c'est qu'un site ne DERIVE pas — il declare ses adresses parce qu'il EST le terrain — et
ne passe donc pas par `instancier`. Mais ne pas deriver n'est pas ne pas avoir de plan.
SITE-Chezlepro/plan/{10-intrants,serveurs,applications}.yml ; underlay.yml ne garde que
la fabric. Ce qui est MESURE d'un cote, ce qui est VOULU de l'autre. Les groupes viennent
du registre des applications, les gardes ont suivi le plan (4 controles negatifs).
Huit defauts, tous invisibles chez un tenant parce qu'il a toujours tout :
- client_pki codait `infra-pki-01` EN DUR — vrai par coincidence de nomenclature ;
- aucun moyen pour un service non-root de lire la cle (Forgejo tourne en `git`) ;
- le certificat, PUBLIC par nature, restait en 0600 ;
- l'unite Forgejo n'avait pas d'ExecReload : un cert renouvele aurait ete servi perime ;
- le role ne savait pas servir TLS lui-meme (il y avait toujours un edge) ;
- le cert ne couvrait pas le nom de SERVICE, faute d'`expose:` ;
- les registres se chargeaient en tout-ou-rien : sans domaines.yml, plancher vide ;
- le flux declarait 3000 en dur.
Le message accusait presque toujours autre chose : le DNS quand c'etait un nom faux, une
permission de fichier quand c'etait un port privilegie, rien du tout quand le plancher
s'ecrivait vide.
ROOT_URL est gravee dans les URL de clonage : la forge sert desormais sur 443, avec
CAP_NET_BIND_SERVICE, et son URL n'a plus de port.
Verifie depuis le reseau : Verify return code 0, https://forge.genese.internal/ -> 200.
42 preuves vertes, flux coherents (34 roles, 92 flux).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`index: 17` ne disait pas « voici mon adressage » : un site ne derive rien, ses machines
vivent sur un reseau de fabric hors de toute derivation. Il disait UNE seule chose — quel
supernet de tenant est le mien — pour autoriser un reseau du site a en occuper la bande
basse (D-77). Un reste de la coincidence hebergeur<->tenant chez Chezlepro, qui est les
deux a la fois ; ailleurs il aurait fallu inventer un index a un hebergeur qui n'heberge
pas son propre ecosysteme.
L'exception se declare desormais sur le RESEAU qui en a besoin, et elle NOMME le tenant
dont elle occupe la bande basse (`bande_basse_de: OPS-Chezlepro`). Plus vrai : un seul
reseau est concerne. Plus verifiable : le nom se resout dans le registre `tenants:`, donc
une faute de frappe est refusee au lieu de passer.
Quatre controles negatifs, tous refuses — tenant inconnu, exception absente, debordement
sur la bande des zones, et le mauvais tenant nomme.
42 preuves vertes, les quatre devis du panneau OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le site portait trois services sur les sept du modele `origine`. Sa forge servait LE
GENOME EN CLAIR et aucune de ses machines n'avait de certificat — donc aucun chiffrement
est-ouest, ce que la doctrine zero-confiance interdit. site-pki-01 (step-ca) et
site-dns-01 (PowerDNS + resolveur colocalises) rejoignent les trois autres.
Quatre defauts que le site a fait tomber, chacun invisible chez un tenant :
- DNS bloque par notre propre default-deny. Un tenant a son resolveur DANS son reseau
et ne traverse jamais la frontiere ; le site interroge la sienne. La regle est derivee
de `site.dns_amorcage`, destination declaree, jamais `any`.
- serveur_cache_site n'installe rien : il marque un cache et lit les variables de
serveur_artefacts. Les defauts d'un role ne sont en portee que dans le play qui
l'inclut — une dependance de role regle l'ordre ET la portee.
- resoudre_idp partait meme avec OIDC desactive, et exigeait un plan. La resolution
suit desormais l'usage.
- le plancher /etc/hosts etait VIDE : `hotes_actifs` n'existait pas dans l'inventaire
du site. Un role qui reussit en n'ecrivant rien est la pire forme d'echec.
Une machine du site peut desormais se configurer (`variables:`), appliquee en dernier :
ce qu'une machine declare d'elle-meme prime sur ce que le site declare pour toutes.
Verifie et non suppose : systemd disait `active` mais rien n'ecoutait sur 443 — step-ca
sert sur 8443. La zone souveraine resout et la recursion marche, mesurees sur la machine.
42 preuves vertes, ansible-lint profil production.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SITE et OPS sont deux classes distinctes. Le site decide de la fabric et du reseau et
PRODUIT les intrants ; le tenant les consomme et n'a d'intelligence que sur ses
applications, leur configuration et leurs integrations. Une valeur reseau qu'un OPS decide
est une valeur mal placee.
L'index est l'intrant reseau par excellence — supernet, sous-reseaux de zone, VLAN, VMID,
noms de VNet en descendent. Il etait declare DEUX FOIS : dans la nomenclature du tenant et
dans l'underlay du site. Et le site ne declarait meme pas qui il heberge : la cle
`tenants:` existait dans le code, jamais dans la carte — la decouverte se faisait par
balayage des dossiers freres.
`tenants:` devient un registre d'allocation (nom -> index). Le site alloue, le tenant
recoit, la nomenclature n'est plus que la copie verifiable d'une decision prise ailleurs.
Quatre gardes neuves, chacune eprouvee par un controle negatif, sous P23.
Un tenant qui s'emancipe recoit un index NEUF de son nouveau site : l'index n'est pas une
propriete du tenant, c'est une place sur une fabric.
42 preuves vertes, quatre devis du panneau OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`segment_physique: true` retire la cle `vlan`. devis_reseau.py ecrivait `r['vlan']` en une
vingtaine d'endroits : KeyError, /api/devis-reseau en 500, et LA VUE RESEAU DU PANNEAU
RESTAIT VIDE — une panne a deux couches de sa cause.
Filtre a la source plutot que colmatage : devis_reseau definit son propre
`reseaux_de_fabric` qui ecarte les reseaux sans etiquette, et les dix appels y passent.
Colmater les vingt occurrences aurait laisse la vingt-et-unieme.
Le bloc de gestion d'un switch echappait au filtre (il lit son reseau directement). Sans
etiquette, aucune `interface VlanN` n'existe : l'adresse va sur l'interface de gestion
native du boitier. Le devis le DIT au lieu d'inventer une syntaxe.
Expose au passage que bifrost-3 et bifrost-4 declarent leur gestion sur un segment qui ne
les traverse pas, a des adresses qui n'ont jamais repondu.
Les quatre devis du panneau repassent. 42 preuves vertes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le commit dc92f01 justifiait le retrait des roles de site de patient 0 par « patient 0
est un tenant comme les autres, c'est meme tout ce qu'il prouve ». C'est FAUX.
Un tenant ordinaire existe POUR SES GENS : Chezlepro heberge de l'identite, du courriel,
de la collaboration. Patient 0 n'heberge que LA LIGNEE. Il est l'ecosysteme d'origine et
le detenteur du genome, et il le reste.
Ce qui le quitte, ce sont deux responsabilites de SITE qu'il tenait faute d'un SITE
capable de les porter — a l'epoque un site n'etait qu'un fichier de carte, sans machines.
Le SITE existe desormais comme objet a part entiere : le geste etait donc juste, seule sa
raison ne l'etait pas.
Le texte est corrige plutot qu'efface, dans le CHANGELOG comme dans les fichiers : une
doctrine juste appuyee sur une raison fausse finit toujours par se retourner.
42 preuves vertes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Toutes les VM naissaient sur le noeud du gabarit puis migraient. site-cache-01 est la
premiere a etre placee ailleurs, et elle a fait tomber quatre defauts enchaines :
1. l'URL de clonage visait le noeud de DESTINATION, alors que l'API veut celui qui DETIENT
le gabarit ; la destination se dit par `target`. D'ou un 500 sur tout clonage
inter-noeuds. Le noeud du gabarit se decouvre dans l'inventaire du cluster.
2. ce 500 etait avale par failed_when: false + no_log: true. Une assertion le releve
desormais, message de l'API compris.
3. l'attente interrogeait nodes/<destination>/tasks/<UPID> ; la tache vit sur le noeud du
gabarit. 60 tentatives x 10 s pour un clonage termine en 87 s, puis la suite qui reprend
sans un mot. Le noeud se lit dans l'UPID lui-meme.
4. la garde d'apres-attente retombait sur `exitstatus | default('OK')` : une attente qui
n'avait rien observe passait pour un succes. Elle exige d'avoir VU la tache s'arreter.
Et une taille de disque porte toujours son unite : `disque: 40` etait lu comme un
retrecissement, erreur toleree a raison — la machine naissait donc avec les 16 Go du
gabarit au lieu de 40, sans que rien ne le dise.
Le site declare enfin ce qu'il materialise (materialisation: gabarit, stockage,
clone_complet) au lieu de l'heriter du tenant actif.
Chrono : clonage reel 59 s et 87 s pour 3,3 Gio alloues.
42 preuves vertes, ansible-lint profil production.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un mot manquait au vocabulaire des flux. Le runner de SITE declarait son API Proxmox en
`externe`, qui se rend par !SETOPS_INTERNES — or les hyperviseurs SONT en RFC 1918 : la
regle les aurait exclus tout en ayant l'air d'ouvrir le flux. D'ou `fabric`.
Deux flux manquaient :
- serveur_cache_site n'avait aucun egress : la chaine de caches se terminait sur un
cache vide, et ca ne se serait vu qu'au premier apt update d'un ecosysteme neuf ;
- serveur_ops_site n'avait que l'API : le shell des noeuds et l'API de la frontiere
sont deux autres pouvoirs, declares a part.
Le devis ignorait les machines du site — dans aucun plan de tenant, donc invisibles a sa
boucle, zero regle rendue SANS RIEN SIGNALER. Il emet desormais SETOPS_SITE,
SETOPS_FABRIC, SETOPS_ADMIN_SITE et un alias par role, sur opt2 (arrivee reelle du
trafic) et lan pour le SSH, plus le NAT sortant du site.
Le socle s'applique au site : site_inventaire.py range ses machines dans serveur_debian.
Patient 0 ne declare plus serveur_ops_site ni serveur_cache_site : il est un tenant
comme les autres, c'est meme tout ce qu'il prouve. Inventaire regenere.
42 preuves vertes. Devis : 26 regles a creer, 5 a retirer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- site_machines.py : traduit une declaration d'underlay en SETOPS_*, que cloner-vm
consomme deja. Le clone reste le seul chemin eprouve.
- site_inventaire.py : inventaire DYNAMIQUE. Un site ne derivant de rien, sa declaration
est deja sa forme finale — un hosts.yml genere ne rendrait rien plus inspectable.
Les groupes sont les services : playbooks/groupes/<role>.yml trouve ses hotes seul.
- cibles site-decrire / site-inventaire / site-creer (CONFIRMER=true) / site-appliquer,
toutes independantes d'une instance montee : le site existe avant tout tenant.
- playbook de groupe manquant pour serveur_cache_site, et son classement en couche.
Le premier garde-fou de site-appliquer refusait un GROUPE vide : il ne pouvait jamais
se declencher (GROUPE a un defaut global). Le vrai risque, observe en le testant : un
playbook de tenant contre l'inventaire du site ne matche aucun hote et sort avec 0 — un
succes qui n'a rien fait. Refus desormais de tout groupe absent de cet inventaire.
42 preuves vertes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La branche « adressage declare » du generateur de tenants est retiree. Un tenant se
derive de son index ; un site n'a pas d'index et ne derive de rien. Les faire passer par
la meme moulinette donnait une nomenclature de site vide de sens, et un site exclu des
devis par ABSENCE d'index plutot que par nature.
Les machines de l'hebergeur se declarent desormais dans underlay.yml, a cote des switches
et des hyperviseurs qui les portent. Six gardes neuves, chacune eprouvee par un controle
negatif.
Mesures qui ont corrige la carte :
- 10.17.0.0/24 n'a pas d'etiquette VLAN (segment physique sur igb0 de la frontiere) ;
le VLAN 10 de vmbr1 est l'ancien plan 10.0.0.0/24, vide.
- aucun pont d'hyperviseur ne porte ce segment : trois sondes muettes, temoin positif
reussi. Une VM y naitrait sourde — le validateur le refuse.
- les machines du site vont donc sur grappe-controle (vmbr0), seul plan de l'hebergeur
porte par un pont reel, avec passerelle et sortie.
Role d'hote neuf : passerelle_amont — un routeur reel que nous n'administrons pas.
42 preuves vertes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un SITE decrit les machines de l'hebergeur : pas d'index, pas de cohabitation
avec les tenants, il vit dans le reseau d'administration. Rien ne peut donc
deriver d'un seed inexistant.
L'explicite (`ip`, `vmid`, `vlan`) gagne sur le derive, et les deux chemins se
rejoignent sur un seul jeu de hostvars. La garde reste entiere : une machine
sans adresse -- ni declaree ni derivable -- est toujours refusee.
Revele en preparant le plan du site : l'underlay ne dit PAS quel pont Proxmox
porte quel reseau. Les tenants ne s'en apercevaient pas, leur pont etant un VNet
derive de leur index. Un SITE n'en a pas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
verifier_ports traitait depuis toujours un port non numerique comme « pas une
ecoute fixe ». Le generateur est-ouest l'envoyait tel quel a l'API Proxmox :
« invalid port 'derive' », six regles refusees. Une meme notion, comprise d'un
cote et pas de l'autre.
Sauter est la bonne reponse : depuis que le resolveur est la seule porte,
PowerDNS n'ecoute que sur 127.0.0.1:5300 -- aucune regle est-ouest n'a d'objet
pour lui. Les trois groupes t*-srv-powerdns sont retires.
Mais un flux qu'on n'applique pas doit SE VOIR : le devis recense et affiche les
ports sautes avec leur raison. Sans cette note, sauter proprement serait devenu
un trou silencieux.
Les deux devis sont clos. Flotte verifiee : DNS, Internet, apt, cache joignable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Devis clos : 0 a creer, 0 a retirer, 65 inchange + 15 routes. Flotte verifiee
apres coup -- DNS interne, Internet et apt sans erreur sur les cinq hotes.
OPNsense refuse un alias de 32 caracteres ou plus. La contrainte n'etait ecrite
nulle part et se manifestait a l'APPLICATION, pas au devis. nom_alias abrege
desormais (SERVEUR_ -> SRV_, comme le pare-feu est-ouest) et REFUSE bruyamment
si le nom deborde encore : un devis qui promet un objet que la cible rejettera
n'est pas un devis. Le role devient serveur_cache_site.
« RIEN N'EST APPLIQUE » signifiait « pas encore recharge », pas « rien ecrit » :
les objets etaient dans la config, le pare-feu en marche les ignorait. J'avais
lu le message a l'envers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Une regle inter-tenant n'appartient ni a l'emetteur ni au recepteur : c'est le
site qui autorise un flux entre deux de ses tenants, et son runner qui prepare
le terrain.
Deux silences fermes dans le generateur de frontiere. flux_frontiere() ne
retenait que les flux `externe` -- or deux tenants vivent sur des VLAN routes
par la frontiere, leur trafic la traverse. Et un egress vers un voisin recevait
!SETOPS_INTERNES en destination : le port ouvert vers l'INTERNET. La destination
est desormais nommee.
Deux roles parce que meta/flux.yml est statique : un role unique aurait declare
l'ingress pour TOUS les caches -- maillage complet, visible au devis.
serveur_artefacts_site porte l'ingress, serveur_artefacts l'egress vers l'amont.
Debian est desormais telecharge une fois pour toute la fabric. Le cache du site
ne voit que des requetes agregees, jamais quelle machine installe quoi.
Rien n'est applique : le devis se lit avant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>