README.md ouvre desormais sur « par ou entrer, selon ce que tu viens faire » :
monter (QUICKSTART), heriter (wiki Reprendre l'ecosysteme), modifier
(carte-set-ops), apprendre (wiki Home). On n'arrive pas avec un sujet, on
arrive avec une situation.
carte-set-ods.md et wiki/Home.md declarent leur lecteur — le mainteneur et
l'apprenant — et renvoient aux deux autres portes. C'est la convention qui
empeche la rechute : un document qui declare son lecteur se range tout seul.
La regle du miroir est ecrite aux trois endroits ou elle se lit : le wiki est
publie DEPUIS le depot, une page modifiee dans l'interface de la forge est
detruite a la publication suivante.
Corrige au passage les comptes perimes de la carte (26 docs + 7 audits + 21
unites -> 34 + 15 + 23, et un README pour chacun des 54 roles).
Le lien vers la page accentuee est percent-encode : aucun precedent de lien
accentue hors du wiki dans ce depot, et le rendu du depot n'est pas celui du
wiki.
Verifie : les cinq liens relatifs du README resolvent, chaque porte declare
son lecteur, prouver.py 0 (33 OK), plan-recette inchange.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Trois des cinq pieges restants avaient deja une page depuis que la semaine a
avance : le connect() vers le vide (frontiere-opnsense.md + le controle porte
par frontiere-mesurer), le `make prouver` vert (wiki « Verifier le deploye »),
et le banner exchange. Les deux orphelins — kcadm -s sur une map, grafana-cli
dans une base fantome — s'adressent a qui ECRIT du code, pas a qui reprend
l'exploitation : ils n'ont rien a faire dans un chemin de reprise.
Une page separee aurait redit ce que trois autres disent deja, et aurait donc
vieilli mal — le reproche exact qu'on faisait a sa version longue.
La section ⑤ de « Reprendre l'ecosysteme » porte desormais la REGLE qui les
relie (verifier l'instrument avant d'accuser le composant) et un tableau de
trois lignes qui renvoie chacune a son domicile. Plus de promesse en attente.
Et le banner exchange n'avait AUCUN domicile : le savoir vivait dans un
commentaire du Makefile et trois entrees du CHANGELOG — nulle part ou on le
cherche, exactement le mal que la refonte traite. Ecrit en runbook §3, avec
ses trois causes par frequence et ce qui tranche dans l'ordre.
runbooks-exploitation.md declare aussi son lecteur, comme le veut la
convention validee.
Verifie : chaque renvoi controle un a un, prouver.py 0 (33 OK), plan-recette
inchange.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'exploitant a retire la derniere regle heritee. `make frontiere-mesurer`
rend CONFORME (code 0) : tout ce qui est declare est livre, tout le reste est
refuse — y compris collab-01:9980, le seul qui livrait vraiment un HTTP 200
depuis le poste.
Et mon instrument avait tort, pas la frontiere. Il comptait 38 ecarts en
concluant depuis le client : « connexion etablie => la bordure a relaye ».
Faux, verifie A LA DESTINATION : pendant que le poste tenait une connexion
« etablie » vers idm-01:389, idm-01 n'en voyait aucune ; collab-01 n'en
voyait aucune sur 9980. La frontiere repond a la poignee TCP sans relayer.
Le devis raisonne desormais sur la LIVRAISON seule, et le controle ne rend le
releve NUL que s'il LIVRE des donnees — qu'il ressorte AMBIGU est attendu ici
et le rapport le dit a chaque execution. Cette relaxation rend aussi le sens
sortant mesurable : il etait declare NUL en permanence.
Nouveau mot-cle `poste: false` dans meta/flux.yml : un service publie n'est
pas forcement fait pour un poste de travail. Le 25 entrant de Postfix est un
flux serveur a serveur ; la frontiere l'etendait au VLAN d'administration ou
le nftables de l'hote le refusait. Deux regles retirees. Le mot-cle vit avec
le role qui sait ce que son port veut dire ; le generateur ne connait
toujours aucun numero de port.
Verifie : frontiere-plan sans ecart (41 regles, 12 routes), flotte 14/14,
frontiere-mesurer CONFORME, prouver.py 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
devis_expositions pose la question positive (une exposition declaree
repond-elle). Celle-ci manquait, et ce n'est pas la meme : un pare-feu peut
servir tout ce qu'on lui demande ET laisser passer tout le reste.
Rien n'y est saisi. Les cibles sont les ports REELLEMENT en ecoute (sonder un
port ferme ne prouverait rien du pare-feu) ; la politique attendue est lue
dans devis_opnsense.py, la source qui configure la frontiere.
scripts/sonde_tcp.py refuse de conclure : un CONTROLE (adresse ou personne
n'ecoute) avant tout verdict — s'il repond, le releve est declare NUL. Et
etablir n'est pas livrer : banniere, sinon requete HTTP, sinon poignee TLS ;
sans reponse le verdict est AMBIGU, jamais « ouvert ».
Deux pieges rencontres en le construisant, corriges et commentes sur place :
la sonde « tenant » s'executait sur le poste (dans un play connection: local,
delegate_to herite de cette connexion) ; et 2 s de lecture faisaient ressortir
un Postfix sain en AMBIGU (postscreen retarde sa banniere expres) — porte a
8 s, compense par du parallelisme.
Premier verdict, frontiere en l'etat : 38 ecarts, dont collab-01:9980 qui
livre vraiment un HTTP 200 depuis le poste. Releve sortant declare NUL.
Verifie : ansible-lint Passed, prouver.py 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Validation a l'instrument de l'exigence « aucun trafic impertinent », par de
vraies requetes applicatives contre des destinations interdites.
Le transit tenant etait deja correct : hyperviseur, frontiere, poste et
Proxmox tous muets ; le 25 sortant passe depuis edge-mta-01 (banniere
220 mx.google.com) et est refuse depuis infra-dns-01.
Trou 1, a nous : les flux sortants visaient `any`, donc n'excluaient ni le
plan de gestion ni le voisin — https://10.0.0.1/ repondait depuis une VM.
Ils visent desormais !SETOPS_INTERNES, destination NIEE valant les trois
blocs prives RFC 1918. Pas la liste de nos reseaux : elle laissait dehors
192.168.11.0/24, le plan de gestion herite.
Trou 2, pas a nous : la regle d'usine « Default allow LAN to any » privait
notre defaut-deny de tout effet (mesure : le 443 d'un nginx repondait depuis
le poste alors que seul le 22 est declare). Aucune API ne l'expose. Set-OPS
declare donc les flux d'administration legitimes vers les services publies,
pour que la desactiver ne coupe pas l'exploitant de ses consoles web.
Verifie apres application : 10.0.0.1:443 bloque depuis le tenant, sortie web
+ DNS + SMTP public toujours passants, curl vers le nginx du tenant -> 302,
flotte 14/14, frontiere-plan sans ecart, prouver.py 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Retire les deux routes /16 (les douze /24 attribues suffisent) et retrecit les
alias SETOPS_TENANT_* du supernet aux memes /24 : nos propres regles
autorisaient jusqu'ici « admin -> tout le /16:22 ».
Ajoute la garde qui l'aurait attrape : verifier() exige que l'ensemble des
reseaux routes et l'ensemble des reseaux autorises coincident exactement.
Attachee a P24, qui ne verifiait que la traduction NAT.
Fait entrer les routes dans appliquer_opnsense.py — elles etaient posees a la
main, donc reconciliees par rien : identite portee par la description
(setopsroute:<tenant>:<reseau>-><saut>), creation avant retrait, perimetre
strict. Le nom de passerelle est resolu depuis l'adresse du prochain saut.
Le symptome du connect() qui aboutit toujours subsiste et n'est pas de notre
fait : l'etat pf porte la regle d'usine « Default allow LAN to any rule ».
Mesure qui tranche : depuis une VM du tenant, 172.31.99.99 « s'etablit » en
1 ms sans rendre de banniere.
Verifie : frontiere-plan sans ecart (12 routes), flotte 14/14, prouver.py 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Router 10.27.0.0/16 entier faisait porter a la frontiere des destinations qui
n'existent nulle part. Ces paquets atteignaient le noeud de sortie, y
arrivaient dans la table PRINCIPALE — pas dans le VRF, qui n'est atteint que
par les /24 annonces en BGP — et repartaient vers la passerelle du reseau
d'ADMINISTRATION. Mesure : ip route get 10.27.99.99 rendait via 192.168.11.254.
C'est aussi ce qui faisait reussir tout connect() depuis le VLAN
d'administration, y compris vers des adresses inexistantes — symptome attribue
pendant deux jours a une fonction d'anti-usurpation de la frontiere, alors que
c'etait un routage trop large.
Le devis emet desormais une route par sous-reseau attribue (12 au lieu de 2).
Le NAT reste sur le supernet : il porte sur la SOURCE, qui contient tous les
sous-reseaux — la garde P24 itere sur les alias et reste satisfaite.
Verifie avant de livrer : les 14 hotes du plan sont tous dans les six /24,
aucun ne serait coupe. L'applicateur ne gere pas les routes (0 mention) : le
devis prescrit, l'exploitant applique — D-23/D-24, et ce chemin est celui de
son administration.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P22 garde la synchronisation entre le wiki et docs/audit/plan-de-recette.md.
En ajoutant l'unite « Verifier le deploye », j'ai rendu le plan perime et la
preuve l'a vu aussitot — une garde que le depot avait deja et que j'avais
oubliee.
Et j'ai POUSSE le commit precedent avec cette preuve en echec, pour la
DEUXIEME fois, avec la meme cause : mon garde-fou etait
grep -E '^CONFORME|^NON CONFORME', qui reconnait « NON CONFORME » comme une
correspondance et laisse passer la chaine &&. Je l'avais documente hier sans
changer l'habitude. Desormais : le CODE DE SORTIE de prouver.py.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le wiki datait du 3 aout. Verifie avant d'y toucher : toutes les cibles make
citees existent, aucune commande morte.
La-preuve.md annoncait P01-P21 ; le harnais est a P33. Les trois nouvelles
ajoutees, et surtout la page dit maintenant ce que ces preuves NE FONT PAS :
elles sont statiques, elles lisent le depot, et c'est dans cet angle mort
qu'une AC est restee expiree huit heures sous un harnais vert.
Infra-as-Code-et-idempotence.md enseignait l'idempotence comme acquise. Vrai
role par role, faux a l'echelle de la flotte. La page enseigne desormais
depuis les chiffres (924 -> 17 -> 0), raconte ce que les 17 cachaient, et
explique pourquoi il faut verifier le zero lui-meme.
Nouvelle unite « Verifier le deploye » : la difference entre valider du code
et verifier un systeme, avec les trois regles qui separent un devis utile d'un
devis decoratif.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
924 (rejeu depuis zero) -> 17 (2e passage) -> 0 (3e). Zero tache changed,
zero echec sur les quatorze hotes. Le depot n'avait jamais fait ce test a
l'echelle de la flotte.
Verification du zero, parce qu'un zero peut signifier que les roles ne font
plus rien : PLUS de taches se sont executees au passage a vide qu'au rejeu
(2266 contre 2152). Elles ont toutes tourne et toutes trouve le systeme
conforme. Un zero obtenu avec MOINS de taches aurait dit l'inverse.
Ce que le test a rapporte : trois defauts, dont deux n'etaient pas des defauts
d'idempotence mais des PANNES SILENCIEUSES — node_exporter mourait a chaque
renouvellement de certificat sur les 14 hotes, et chaque deploiement
invalidait les jetons OAuth2 de la forge.
Le chiffre devient la ligne de base : un deploiement futur qui rapporte
changed sur une flotte non modifiee signale desormais quelque chose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le groupe etait TOUJOURS vide et rien ne le signalait. instancier l'emet comme
squelette, les etats d'un serveur ne connaissent que actif et planifie — aucun
chemin ne permettait d'y faire entrer une machine. Les trois cibles recevaient
« skipping: no hosts matched », qui n'est pas une erreur.
Le gabarit ne PEUT PAS venir du plan : sa config Proxmox le place sur le
reseau de fabrication (192.168.12.99/24), pas dans le supernet. Ce n'est pas
un hote de l'ecosysteme, c'est la matrice dont il est tire. Le forcer dans
plan/serveurs.yml aurait ete le mettre dans un registre qui n'est pas le sien.
MODELE_HOTE=<ip> le designe ; l'inventaire d'un seul hote sert les trois
playbooks, prerequis d'acces et de privileges compris. Sans lui, elles
REFUSENT en expliquant au lieu de ne rien faire.
Meme famille que le reste de la journee : une capacite declaree dont personne
ne verifiait qu'elle est branchee — a ceci pres qu'elle ne se manifestait par
aucun symptome. Elle ne faisait rien, poliment.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le test unitaire a rejete mon ajout d'export — c'est exactement son role : il
epingle le contrat de parametres-proxmox, et une variable de plus est un
changement de contrat qui doit etre declare, pas subi.
J'ai pousse le commit precedent AVEC cette preuve en echec. Cause : mon
garde-fou etait « grep -E 'CONFORME|ECART' », qui correspond aussi a « NON
CONFORME ». Un motif qui ne sait pas distinguer le succes de l'echec ne garde
rien — meme famille que les criteres creux de P31.
Harnais de nouveau a 33 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Question « on peut l'optimiser ? » — mesure avant de repondre. Rien a gagner
cote performance (UEFI/q35, virtio-scsi-single + iothread, discard+ssd,
x86-64-v2-AES, agent, balloon 0) ni cote paquets : le socle est deja cuit dans
l'image.
Ce que le gabarit transporte, c'est son lieu de naissance. searchdomain
chezlepro.ca — le domaine PUBLIC — etait herite par les 14 VM, faute de
proxmox_clone_domaines_recherche defini. Desormais derive de domaine_interne
via SETOPS_DOMAINE, par le mecanisme qui existait deja pour le DNS.
Honnetement : ca ne reparait pas de panne. serveur_debian reecrit resolv.conf
au deploiement sans ligne search — verifie sur la flotte. C'etait faux et ca
ne tenait que par chance.
template_cleanup vide desormais /etc/resolv.conf a la capture : un gabarit ne
transporte aucune identite de reseau.
Notes : mtu 9000 est un reglage MORT (les clones tournent en 1500) ; et le
groupe modeles_vm est VIDE, donc preparer/verifier/nettoyer-modele n'ont
aucune cible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deuxieme des trois chantiers ouverts par la reconstruction. Retrouve son
defaut n6 a froid, sans machine.
Le SASL de Dovecot et l'interface d'Alloy se disputaient le 12345 sur
infra-mail-01 depuis le premier jour, et c'est Dovecot qui perdait EN SILENCE.
Il a fallu inverser l'ordre de demarrage — ce que fait un rejeu depuis zero —
pour que ca devienne audible.
Le controle n'etait possible qu'apres avoir DECLARE le port d'Alloy : un port
SUBI (defaut amont d'un logiciel qu'on n'a pas choisi) n'existe pour aucun
registre, donc aucune preuve ne peut le voir. Il faut l'imposer pour le
verifier.
partage: true — nouveau mot du registre — distingue « j'ouvre cette ecoute »
de « je decris celle d'un autre » (serveur_backup empruntant le sshd de
serveur_debian). Sans lui, la seule co-location legitime de la flotte serait
signalee a tort, et une preuve qui crie sur un cas sain finit par etre ignoree.
Verifie dans les deux sens : 32 revendications sans collision sur le reel ;
en remettant Alloy a 12345, le defaut n6 est nomme, code 1.
Harnais : 33 preuves, 0 echec, 0 sautee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Premier des trois chantiers rendus evidents par la reconstruction. Il aurait
trouve son defaut n1 — amorcage_acces_courriel — SANS RIEN DETRUIRE.
Un assert de role declare un contrat ; rien ne verifiait que l'instance
l'honore, et le manque ne se voit qu'au moment ou la garde s'execute — donc,
pour un intrant d'amorcage, seulement en repartant de rien.
Satisfait par : defaut non vide (vault_* compris, gardes par P18), set_fact de
resolveur, ou declaration de l'inventaire. Aucune voute dechiffree : la preuve
reste statique.
Deux fois mon instrument a accuse le composant a sa place, avant meme sa
premiere execution utile : il criait au manque sur
serveur_postfix_mailstore_hote, pourtant fourni — je ne lisais pas le fichier
d'inventaire, puis je n'y cherchais que les blocs vars: alors qu'instancier
ecrit sous le nom d'hote.
Verifie dans les deux sens : 30 exigences satisfaites sur le reel ; sur un
double sans la declaration d'hier, le defaut n1 est nomme, code 1.
Harnais : 32 preuves, 0 echec, 0 sautee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ecosysteme chezlepro detruit (14 VM, disques compris) puis rejoue depuis le
plan seul, sans restaurer aucune sauvegarde. Les cinq devis rendent le meme
verdict qu'avant la destruction.
Premiere fois que le depot peut affirmer que le SYSTEME RECONSTRUIT est celui
que le plan decrit, au lieu d'affirmer que le depot est coherent avec lui-meme.
Six defauts trouves, tous invisibles autrement : trois d'ordre, deux courses de
premier demarrage, un conflit de port. Ils dormaient tous derriere un etat
preexistant — un compte deja la, des roles crees par un passage anterieur, des
clients existants, un service qui tournait depuis toujours, un port deja tenu.
Le rejeu n'a rien casse : il a retire l'etat qui masquait.
Refait a la main : la seule base de Grafana, dont le schema etait reste a
moitie migre apres l'interruption du defaut n5. Rien d'autre.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sixieme et dernier arret de la reconstruction from-zero, et le seul qui ne soit
ni un ordre ni une course : un vrai conflit de port.
infra-mail-01 : dovecot ecoute 0.0.0.0:12345 (serveur_dovecot_sasl_port,
choix delibere de Set-OPS pour la soumission :587)
alloy : defaut amont 12345 -> bind: address already in use
Le premier demarre gagne. En exploitation courante le conflit DORMAIT : Alloy
tenait le port depuis toujours et c'est l'ecoute SASL de Dovecot qui echouait,
en silence. L'ordre des couches d'une reconstruction inverse les roles et le
rend visible.
Le vrai defaut n'est pas le numero : c'est qu'un port SUBI ne se declare nulle
part, donc aucun controle ne peut voir la collision. Le port est desormais
IMPOSE (--server.http.listen-addr) et DECLARE dans meta/flux.yml avec
pair: localhost — une revendication de port, pas un flux entre hotes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contrainte de l'exploitant apres avoir vu backup-01 et collab-01 crees avant
l'AC et le DNS. Ma premiere reponse etait incomplete : un clone est bien
inerte, mais un echec a la 12e VM coute 40 minutes sans rien deployer, et le
journal donne l'impression que le moteur ignore ses couches.
Mesure avant de coder :
- enregistrements A : DEJA derives du plan (zone generee depuis hotes_actifs)
- zone inverse / PTR : n'existe NULLE PART, aucun role ne touche in-addr.arpa
- ordre d'amorcage : aucun, deployer-tout est par couches
Le premier point a reduit le travail de moitie — j'allais ecrire un enrolement
DNS par hote alors que la zone directe etait deja correcte.
Zone inverse derivee du supernet (27.10.in-addr.arpa), PTR issus de la MEME
source que les A : pas d'endroit ou elles puissent diverger. Vide si le
supernet n'est pas un /16.
_amorcer-socle monte l'AC puis le DNS completement avant deployer-tout ; les
deux derives de applications.<app>.hote, dans un ordre causal et non
alphabetique. Deux exceptions assumees : l'AC s'auto-signe, le DNS pose son
propre enregistrement.
P10 a attrape un handler que je venais d'inventer (Recharger PowerDNS au lieu
de Validate and reload PowerDNS) avant tout deploiement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
« Pourquoi pas make myDay ? » — la cible existe (alias strict de reconstruire)
mais n'avait aucun texte d'aide, et P31 la declarait conforme. Le motif etait
^[a-z][a-z0-9_-]*: — toute majuscule echappait au controle. myDay est citee
dans l'aide du Makefile et dans la GUI, et n'apparaissait dans aucun
recensement.
Une preuve ne vaut que ce que vaut son motif. Celle-ci a ete ecrite avec la
conviction d'etre rigoureuse et testee dans les deux sens le jour meme. Le
trou a ete trouve par une question, pas par un test.
Troisieme fois sur la meme preuve en une journee, apres le rapport genere qui
se citait lui-meme et l'inventaire genere qui l'aurait satisfaite par
construction. La difficulte n'est pas d'ecrire un test, c'est de delimiter
honnetement ce qu'il regarde.
87 cibles documentees, 36 scripts, 54 roles.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ajoutee pour rendre la reconstruction from-zero REPETABLE — un test qu'on ne
peut jouer qu'une fois n'est pas une recette.
Verrous, tous eprouves avant usage :
- VMID derives du plan uniquement (le gabarit dore est structurellement exclu)
- le NOM doit correspondre : un VMID du plan sous un autre nom fait refuser
l'operation ENTIERE. Pas theorique — le 2026-08-07 une VM heritee portait un
VMID du plan sous le nom web-frontal-01 et proxmox_kvm rapportait ok.
- il faut NOMMER l'ecosysteme (INSTANCE=) : le symlink instance/ peut pointer
n'importe ou ; taper le nom distingue le POC de la production.
- CONFIRMER=true ; sans lui, inventaire et rien d'autre.
Le verrou du nom est le seul qu'on ne peut pas eprouver sur le vrai cluster
sans y fabriquer une collision : scripts/tests/test_raser.py l'isole derriere
un faux cluster, rattache a P02. Harnais : 31 preuves, 0 sautee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les 14 VM sont un POC, pas de la prod (recadrage de l'exploitant). Et
reconstruire Chezlepro est un MEILLEUR test que construire Technolibre : on a
un etat de reference — les 5 devis y sont CONFORME. Toute divergence apres
rejeu sera un defaut reel, mesurable. Sur Technolibre, qui n'a jamais tourne,
un echec serait ambigu.
Fige : les 5 verdicts, les 14 hotes (adresse + services), et ce qui sera perdu
et devra etre refait a la main (cle racine de l'AC, donc la racine installee
dans le navigateur ; mot de passe sysadmin). Pour que « identique » soit
prouvable plutot que ressenti.
Verifie que rien de necessaire au rejeu ne vit dans les 14 VM : les voutes et
le mot de passe de voute sont hors cluster, et origin est sur eregion
(192.168.12.201), machine distincte du tenant.
Constat : le moteur n'a AUCUN chemin de destruction. make reconstruire cree et
deploie, il ne rase rien.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Question de l'exploitant : « Set-OPS trichait ? ». Non, et c'est le depot qui
le prouve : onze de ses propres promesses publiques marquees FAUSSES, un
perimetre declare (« aucune VM / Proxmox / reseau touche »), et D-25 qui en
fait une regle. Un systeme qui triche n'ecrit aucune de ces trois choses.
L'angle mort etait ailleurs, et il est ferme depuis ce matin : les 30 preuves
sont statiques. « CONFORME : 30 preuves » se lit comme « le systeme
fonctionne » alors que ca veut dire « le depot est coherent avec lui-meme ».
C'est ainsi que le certificat de l'AC a pu expirer 8 h sous un harnais vert.
Les 11 rejugees, chacune reconfrontee au depot : toutes resolues. Preuve
consignee ligne par ligne.
Le rejugement a trouve mieux qu'un registre oublie : les resolutions etaient
DEJA documentees en Phase 3, mais le tableau de synthese annoncait encore
« fausse : 8 ». Deux representations du meme fait, une corrigee et l'autre
non, rien qui verifie qu'elles se rejoignent — le defaut que ce registre
existe pour traquer, applique a lui-meme. Il penchait du bon cote, ce qui l'a
rendu invisible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Directive de l'exploitant : la doc dit et explique tout ce que Set-OPS fait.
Une exigence seulement enoncee pourrit en silence — trois exemples le jour
meme dans la carte.
Ecart mesure : 66 cibles make sur 85 sans texte d'aide (make aide en montrait
19), 11 scripts sur 35 cites nulle part. Les 66 cibles ont recu leur aide :
85 commandes documentees.
P31 garde le couvert. Le chemin pour l'ecrire a ete instructif : deux fois mon
critere s'est revele creux. D'abord « le nom apparait dans un document » — le
rapport d'audit GENERE recopiait les noms manquants dans son message d'echec.
Puis j'ai failli refaire le trou en plus grand : generer un inventaire de
l'outillage aurait satisfait le critere par construction. Un critere qu'on
peut satisfaire en generant du texte ne prouve rien.
P31 teste donc que chaque script porte une docstring qui l'explique et reste
ATTEIGNABLE (cible make ou autre outil), que chaque cible porte son aide (sauf
les internes prefixees _, exemption nommee), que chaque role a son README.
Verifiee dans les deux sens.
Ce qu'elle ne garde pas, et c'est dit dans son code : que l'explication soit
bonne. Le pourquoi se juge en revue.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pas de refonte : 54 roles / 54 README, 34 documents, une carte, un registre de
decisions. Le retard etait ailleurs — le travail du jour vivait dans son coin,
les cinq devis n'existant que dans deux fichiers. Donc decouvrables seulement
par qui connait le Makefile, ce qui contredit « exploitable sans IA ».
Tisses dans les quatre points d'entree : ligne « Conformite du deploye » dans
la carte, section « Ecrire, puis relire (D-68) » dans AGENTS.md, §6.0 du
runbook (le premier reflexe), vue Reconstruction de la GUI.
Le tissage a fait tomber trois affirmations perimees :
- la carte annoncait 28 decisions, il y en a 66 en vigueur (D-01 -> D-69) ;
- elle disait les acces « decides, non construits, ou=people et ou=groups
restent vides » — mesure : un compte, un groupe, chaine exercee de bout en
bout sur Icinga Web 2 le jour meme ;
- la GUI parlait des « deux » devis d'infrastructure ; il y en a quatre.
Formation et wiki differes : la reconstruction from-zero est le test de cette
documentation, et enseigner une procedure que personne n'a executee serait
enseigner une hypothese.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Question de l'exploitant apres deux pannes causees par kcadm. La reponse est
non, et elle se fonde sur la mesure : sur six familles de defauts du jour,
deux seulement viennent d'un CLI ; un module Ansible (ldap_entry) a commis la
meme faute, et trois autres viennent d'un grep de fichier, de la precedence
Ansible et de mon propre comparateur.
D-68 — ecrire, puis relire et comparer, quelle que soit l'interface ; choisir
celle dont le chemin de lecture parle le meme langage que celui d'ecriture.
Une API est souvent preferable parce qu'elle rend la ressource ENTIERE, ce qui
permet le patron de chaque devis. Mais la plupart de la flotte n'a pas d'API,
et postconf -h / -e sont parfaitement symetriques.
D-69 — sur Keycloak : l'API pour toute map ou collection, kcadm ailleurs
(vocabulaire de la doc du produit, donc lisible sans IA).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Arbitrage rendu : le courrier local est route par uid, plus par l'attribut
mail. Dovecot le faisait DEJA (mail_home = /var/vmail/%{user | username}) ;
les deux moities ne s'accordaient que par coincidence, tant que mail valait
uid@<domaine_interne>. Cout assume : l'adresse interne est derivee et ne se
choisit plus ; en echange mail redevient libre de porter la vraie adresse de
la personne.
Puis la preuve de bout en bout a revele bien pire : toute livraison interne
etait DIFFEREE.
SSL_connect error to infra-mail-01:24: Connection timed out
status=deferred (Cannot start TLS: handshake failure)
Postfix etait durci (lmtp_tls_security_level = verify), le port LMTP de
Dovecot ecoutait en clair — son ssl = required global ne concerne que les
services de connexion. Les deux cotes d'un meme flux avaient ete traites
separement. Corrige et accorde : ssl = yes sur l'inet_listener (TLS implicite)
et lmtp_tls_wrappermode = yes cote client ; l'un sans l'autre ne marche pas.
Livraison prouvee : status=sent (250 ... Saved), message dans
/var/vmail/sysadmin/Maildir/.INBOX/new/.
Le devis disait CONFORME pendant ce temps : il verifiait la resolution et les
dialectes, jamais si le courrier BOUGE. Un devis qui ne regarde que les
reglages ne dit pas si le service rend son service. Il releve desormais la
file d'attente et ses raisons ; test negatif : la panne est nommee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PostgreSQL — rend visibles deux defauts deja vecus ici : un reseau ECRIT dans
pg_hba au lieu d'etre derive, et une ligne host en clair la ou il faut hostssl
(verrou qui saute sans bruit, les clients verify-full continuant de marcher).
Interroge pg_settings, jamais le fichier : un grep de postgresql.conf annonce
ssl_cert_file=snakeoil alors que le serveur sert le certificat de l'AC — la
valeur vient d'un conf.d que le grep ne voyait pas. L'instrument etait
incomplet, pas la configuration.
Courriel — chaque maillon interroge la ou il dit la verite : postmap -q pour
la resolution LDAP de Postfix, doveadm user pour celle de Dovecot (le maillon
exact ou la livraison avait bloque), vraies conversations SMTP/IMAP. Verifie
aussi qu'une adresse INEXISTANTE ne resout pas — sinon la boite est un
fourre-tout et le devis ne mesure plus rien.
Trouve immediatement une divergence reelle : Dovecot connait la boite de
sysadmin@chezlepro.internal, Postfix ne sait pas y router (query_filter
(mail=%s), et l'attribut mail porte sysadmin@chezlepro.ca). Collision de
roles : une identite ne porte qu'une adresse mail et on lui en demande deux —
notification joignable hors du systeme, et cle de routage local. Arbitrage a
rendre avant correction.
Tests negatifs : PostgreSQL 3 ecarts nommes, code 1. Une variable morte
trouvee dans une branche que le cas nominal n'emprunte jamais.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Troisieme devis de service. Chaque expose: du plan repond-il, et si non, ou
ca casse.
- requete HTTPS complete avec la racine de l'AC, jamais un connect() : a
travers l'OPNsense (anti-spoofing) toute connexion TCP reussit, et en TLS
le silence apres connect() ne distingue pas un service sain d'un trou.
- deux points de vue : depuis l'edge (edge + dorsal) et depuis le poste
(DNS + frontiere + edge + dorsal). Leur difference diagnostique.
- un code n'est pas un verdict : mon premier comparateur laissait passer un
502 des deux cotes. Trouve par le test negatif, pas par la relecture.
Etat : les 6 expositions repondent des deux cotes. Test negatif (frontiere
qui bloque + dorsal tombe) : 2 ecarts nommes distinctement, code 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deuxieme application du patron devis/applicateur aux services. Trouve a la
premiere execution : sur infra-pki-01 — l'autorite elle-meme — le certificat
etait expire depuis plus de 8 h et le renouvellement echouait toutes les 14
minutes sur « 'step ca renew' requires the '--ca-url' flag ». Rien ne le
signalait.
Cause : sur l'hote de l'AC, /etc/step est le STEPPATH du SERVEUR, pas un
amorcage client — pas de defaults.json, et l'unite de renouvellement en
dependait. La lecon etait deja ecrite dans le commentaire de la tache
d'emission (« l'autorite ne bootstrape pas »), jamais reportee sur l'unite.
Le role ne pouvait pas non plus se soigner : la re-emission ne regardait que
la FORME (cert absent ou SAN manquant), jamais la validite. client_pki
verifie desormais l'echeance (client_pki_marge_renouvellement).
Ce qu'il a fallu desapprendre : les certificats vivent 24 h et se renouvellent
toutes les ~14 min ; « empreinte servie != empreinte disque » est l'etat
NORMAL. Comparer les empreintes aurait donne un verificateur qui crie en
permanence. Le signal est l'echeance de ce qui est SERVI, plus l'absence de
client_pki_reload_services.
Le devis a d'abord menti, du defaut meme qu'il traque : include_vars au niveau
du play prime sur les group_vars. Et le premier correctif a PARU marcher —
set_fact accepte un dictionnaire entier en argument libre sans erreur et n'en
fait rien. Il faut reimposer cle par cle. Les deux devis sont corriges et le
piege est consigne dans docs/devis-services.md avant d'ecrire le prochain.
Verifie dans les deux sens : CONFORME sur 14 hotes ; sur un releve ou l'on
rejoue une copie perimee en memoire, 2 ecarts et code de sortie 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Constat de l'exploitant : « ca fait beaucoup de trucs incoherents qu'on
debusque ensemble ». Il y a une raison mesurable — les 30 preuves de
prouver.py sont STATIQUES (0 appel reseau, 0 ssh, 0 ansible). Elles montrent
que le depot est coherent avec lui-meme ; aucune ne demande au systeme
deploye s'il ressemble a ce que le depot annonce. Les quatre defauts du jour
vivaient tous la.
La classe statique est presque epuisee : recensement des motifs « cree mais
ne reconcilie jamais » -> amorcage_acces (delibere, D-67), serveur_openldap
(corrige le matin), et un seul reste reel (rbac-oidc.yml). Une preuve
statique de plus aurait rapporte une ligne.
Le patron devis/applicateur (D-23/D-24) existait deja pour les quatre
pare-feu, jamais pour les services. make identite-plan l'y porte :
- playbooks/maintenance/devis-identite.yml RELEVE le declare et le reel
- scripts/devis_identite.py COMPARE (le raisonnement n'a rien a faire en
Jinja ; le depot a deja cette forme pour les devis reseau)
- le declare n'est jamais recopie : defauts du role + resolveurs. Un devis
qui redeclare ce qu'il verifie ne verifie rien.
Verifie dans les deux sens : CONFORME sur le systeme reel ; sur un releve ou
les quatre defauts du jour sont rejoues plus deux regressions, 6 divergences
listees et code de sortie 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Quatre defauts mesures dans l'integration Keycloak/LDAP, meme famille : une
valeur declaree d'un cote, consommee de l'autre, rien qui verifie la jonction.
1. Aucune regle ne s'appliquait sur le chemin d'un vrai utilisateur. Sonde :
« abcd » refuse par l'operation etendue LDAP, accepte par Keycloak (204),
puis actif pour l'authentification. Keycloak ecrivait userPassword en
direct (ppolicy aveugle) et le realm n'avait aucune passwordPolicy.
2. ldap_entry ne fait que CREER : la politique etait figee a sa creation. Le
depot disait pwdMustChange TRUE, le serveur FALSE — une reconstruction
from-zero aurait ressuscite la boucle du 2026-08-07. ldap_attrs state=exact
reconcilie la politique et l'overlay (DN lu, pas devine).
3. syncRegistrations absent : un compte cree dans Keycloak n'atteignait jamais
ou=people — acces web, aucune boite, invisible du modele de groupes.
4. Le prenom pointait sur cn (nom complet) : « Administrateur systeme systeme ».
Ajoute roles/resoudre_politique_mdp : LA declaration, traduite en pwdPolicy,
passwordPolicy et anti-force-brute. Les deux roles la consomment sans la
redeclarer.
usePasswordModifyExtendedOp ET validatePasswordPolicy : la seconde est
porteuse, Keycloak se liant en rootDN et slapd n'appliquant pas ses controles
de qualite au rootDN. La premiere seule aurait paru juste sans tenir.
Verification : abcd -> 400 « minimum length 12 » et absent de LDAP ; mot de
passe conforme -> 204 puis ldapwhoami accepte ; POST users -> 201 ET present
dans ou=people. Second passage des deux playbooks : changed=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le realm portait une politique d'acces complete et aucun moyen d'ecrire a qui
que ce soit. Tout oubli remontait donc a l'exploitant, qui n'avait d'autre
choix que de manipuler le mot de passe d'autrui.
- serveur_keycloak/tasks/courriel-realm.yml : reconcilie smtpServer et
resetPasswordAllowed ; hote du relais DERIVE de applications.postfix.hote,
et refus explicite si le plan ne declare pas de MTA.
- passe par l'API d'administration : kcadm.sh accepte les deux formes -s sur
une map, sort en succes et n'ecrit rien (smtpServer reste vide).
- amorcage_acces_courriel redevient a declarer : cette adresse designe une
personne, hors du systeme qu'on amorce ; une boite interne serait illisible
tant qu'on n'a pas l'acces qu'on cherche justement a recuperer.
- autorisation.md §6.6 : le mecanisme, ses deux conditions, et l'ecart
d'adresse laisse par l'ancien mode READ_ONLY de la federation.
Preuve : banniere SMTP lue depuis idm-01, RCPT TO accepte, execute-actions-email
declenche, MTA en starttls -> relay=mx.chezlepro.ca status=sent (250). Second
deploiement changed=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ldappasswd` ne peut pas le changer : `cn=admin` n'est pas une entree de la base
mais le rootDN declare dans cn=config. Le mot de passe vit dans `olcRootPW` et se
modifie par un bind EXTERNAL. L'echec etait sans degat — l'ancien fonctionnait
toujours, verifie avant de continuer.
Keycloak stocke le mot de passe de LIAISON dans sa base et le masque : la
reconciliation d'hier couvrait l'URL, les DN et le mode, pas `bindCredential`.
Tourner le secret aurait coupe Keycloak de l'annuaire. Comme la valeur est
masquee, la reconciliation passe par une empreinte.
Verifie consommateur par consommateur : synchro LDAP de Keycloak (qui prouve la
liaison), carte LDAP de Postfix, Dovecot actif. Les trois rejouent a changed=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ce compte est le moyen de se changer lui-meme : regenerer la voute d'abord
l'aurait rendu inapplicable — plus rien n'aurait pu s'authentifier pour poser la
nouvelle valeur. L'ordre est inverse : s'authentifier avec l'actuelle, poser la
nouvelle, verifier, PUIS ecrire la voute.
C'est une procedure, pas un redeploiement. Meme contrainte pour
`vault_openldap_admin`, qui reste a faire. Consigne au runbook §6.7, avec le
rappel qu'une verification n'est pas un message de succes.
Verifie : admin (voute) OK, groupe sysadmin et role grafana-admin intacts,
rejeu a changed=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`realm-admin` (role du client `realm-management`) est attache au GROUPE sysadmin.
Administrer le realm ne passe plus par le compte local. Portee : CE realm, jamais
`master` — le compte de secours reste hors d'atteinte du groupe, et c'est le sens
meme d'un acces de secours (D-40).
Les roles de CLIENT sont un espace de noms distinct : la declaration gagne
`roles_client`. L'API attend l'UUID du client, pas son clientId — interroger par
le nom rendait une erreur, la verification echouait toujours et la tache se
declarait `changed` a chaque passage. Deux passages consecutifs a changed=0.
La boucle de changement de mot de passe : `pwdMustChange: TRUE` signifie « quand
un ADMINISTRATEUR pose un mot de passe, l'utilisateur doit le changer ». Keycloak
ecrit en tant qu'administrateur — chaque changement relaye etait vu comme une
reinitialisation. Incompatible par construction avec un IdP qui relaie.
La contrainte est deplacee la ou l'utilisateur la voit : pwdMustChange FALSE cote
annuaire, action `UPDATE_PASSWORD` posee par Keycloak. J'avais eprouve pwdReset
au niveau LDAP, ou il marche, sans parcourir le chemin complet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La premiere connexion du sysadmin echouait sur « invalid username or password ».
Ni le jeton ni le compte : `https://auth.<domaine>/` redirige vers `/admin/`, la
console du realm `master`, ou `sysadmin` n'existe pas — il vit dans le realm
applicatif.
Le runbook disait « se connecter a Keycloak » SANS donner d'URL, et l'URL
evidente est la mauvaise. Il nomme desormais les deux consoles :
/realms/<realm>/account/ ton compte sysadmin + jeton
/admin/ Keycloak lui-meme admin + vault_keycloak_admin
L'absence de `pwdFailureTime` cote LDAP etait le vrai indice : aucune tentative
n'atteignait l'annuaire, donc le probleme etait en amont de la validation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le sysadmin ne pouvait atteindre AUCUNE interface web de la flotte qu'il
administre : le pare-feu de l'hyperviseur n'autorisait le 443 de l'edge que
depuis `+t17-flotte`. Le reseau d'administration est dans `t17-admin`.
L'exploitant arrive par un TROISIEME chemin que rien ne declarait : ni `externe`
(Internet, affaire de la frontiere), ni `flotte` (le tenant). Le runbook de
reprise supposait pourtant qu'on ouvre Keycloak dans un navigateur.
`admin` devient un pair declarable — les reseaux de `nftables_admin_ssh`, deja
source unique de la garde anti-lockout. `serveur_nginx` le declare pour son 443.
L'edge SEUL : ouvrir les services en direct elargirait la surface pour rien.
Erreur de methode de ma part : mon premier test utilisait `/dev/tcp` et concluait
« atteignable ». Faux — la frontiere repond au SYN a la place de la cible. Je
l'avais consigne le matin meme.
`make ca-racine` / `make ca-empreinte` : l'hote de l'AC est derive du groupe
`serveur_step_ca`, et la sortie insiste sur la comparaison d'empreinte. La racine
est un certificat PUBLIC — hors voute, dans le magasin de confiance.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le module etait sur disque mais jamais charge (seul back_mdb l'etait). Depuis
OpenLDAP 2.5 son schema est INTEGRE au module : aucun .ldif a charger.
La contrainte mord, mesuree sur un compte fraichement amorce :
Insufficient access (50)
Operations are restricted to bind/unbind/abandon/StartTLS/modify password
Le sysadmin peut se connecter et RIEN d'autre que changer son mot de passe. Ce
que la doctrine promettait est garanti techniquement, plus seulement demande.
`pwdMustChange` est ce qui donne son effet a `pwdReset` : sans lui, marquer une
entree n'oblige a rien. La politique apporte aussi longueur minimale 12,
verrouillage apres 5 echecs, historique. `olcPPolicyUseLockout` reste FALSE :
annoncer « compte verrouille » renseignerait un attaquant sur son existence.
Le DN de la base est LU, pas suppose : olcDatabase={1}mdb est l'usage mais
l'index n'est pas garanti.
La detection du role d'amorcage s'est verifiee d'elle-meme : rejoue apres le
chargement, il annonce « Changement FORCE » la ou il disait l'inverse une heure
plus tot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cree uid=sysadmin et cn=sysadmin dans LDAP. Prouve sur idm-01 : le compte
s'authentifie, et le second passage ne touche a rien (changed=0, 7 taches
sautees) — idempotence par EXISTENCE, pas par conformite (D-67).
Il suit l'annuaire au lieu de se declarer au plan : il ecrit par `ldapi:///` et
doit tourner sur cet hote. Le declarer comme groupe obligerait chaque instance a
le poser sur le bon hote, et elles ne le nomment pas pareil (idm-01 ici,
id-ldap-01 chez Technolibre) — je l'ai d'abord pose sur infra-pki-01 par erreur.
Trois defauts trouves en le construisant :
1. `pwdReset` n'existe pas dans ce schema (overlay ppolicy non charge) :
l'entree entiere etait rejetee et `no_log` masquait la cause. J'avais suppose
un mecanisme sans verifier. Le role le DETECTE maintenant, et la doctrine ne
promet plus un changement force qui n'a pas lieu.
2. Le mot de passe aurait ete stocke EN CLAIR : `ldap_entry` ecrit userPassword
litteralement. Hache par `slappasswd -h {SSHA}` desormais.
3. `voute.py` ne scannait que roles/<groupe> : un role applique par un playbook
sans etre un groupe echappait au recensement, ce que D-20 interdit. Il suit
maintenant les listes `roles:` des playbooks.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'authentification etait resolue et gardee ; l'autorisation n'existait nulle
part. `ou=groups` est cree depuis le debut et AUCUN des 29 roles ne le lit ;
`ou=people` est vide.
D-67 contredit DELIBEREMENT la doctrine du depot : partout ailleurs un ecart est
un defaut a corriger, ici il est legitime — c'est le sysadmin qui travaille.
Set-OPS cree UN acces puis se retire. Idempotence par EXISTENCE, pas par
conformite : compte present, aucune action quel que soit son etat. Reconcilier
effacerait le compte cree la veille pour un nouvel employe.
D-65 : les groupes LDAP portent l'autorisation, Keycloak les projette. Dovecot
et Postfix ne savent pas lire un role Keycloak — un seul endroit a administrer.
D-66 : un service nomme un groupe, jamais une personne : revoquer quelqu'un ne
demande pas un deploiement.
Le §6 est un runbook de reprise. Il dit aussi ce qu'il faut regenerer pour que
la livraison soit un vrai transfert : les comptes de secours ont ete generes
pendant le deploiement, et leur auteur y a eu acces.
Sans registre de personnes, aucune donnee personnelle n'entre dans git.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'authentification etait resolue et gardee ; l'autorisation n'existait nulle
part. `ou=groups` est cree depuis le debut et AUCUN des 29 roles ne le lit —
toute personne authentifiee obtient le defaut du service.
D-65 : les groupes LDAP portent l'autorisation, Keycloak les projette.
L'argument est mecanique : Dovecot et Postfix ne savent pas lire un role
Keycloak. L'y loger rendrait la moitie courriel aveugle et imposerait deux
modeles de permissions.
D-66 : un service nomme un GROUPE, jamais une personne. Un depart devient une
ligne au plan, sans toucher un service.
Le vocabulaire d'habilitation reste celui du service : une echelle commune
devrait etre traduite partout, et la traduction est ou l'habilitation se perd.
Rien n'est construit : registre, role, meta/acces.yml et P31 restent a ecrire.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le generateur emettait `{protocole} dport {port}` pour tout flux. L'ICMP a un
type et un code, pas un port : `icmp dport frag-needed` produit un jeu que `nft`
rejette, et un jeu rejete ne se charge PAS — l'hote perd sa barriere au lieu
d'en gagner une.
Le defaut touchait les 14 hotes. La seconde barriere de D-31 n'avait jamais pu
demarrer nulle part ; personne ne l'avait vu parce qu'aucune VM tenant n'avait
encore ete deployee.
`_selecteur_nft()` traduit : `icmp frag-needed` devient
`icmp type destination-unreachable icmp code frag-needed`. Un code inconnu est
refuse a la generation, avec le nom du role fautif. La garde tourne aussi a la
verification, pour attraper un flux declare mais pas encore porte.
Mesure : nftables actif sur infra-pki-01 et infra-dns-01, 16 et 17 regles, dont
la garde anti-lockout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Une VM ne peut pas s'installer sans resoudre des noms. PowerDNS repond
UNIQUEMENT pour la zone souveraine et ne recurse pour personne : il manquait un
resolveur recursif. `client_unbound` est l'outil ecrit pour ca — il rejoint
`client_journal` et `client_metrique` parmi les integrations universelles.
`infra-dns-01` est exempte (PowerDNS occupe son port 53), et
`serveur_powerdns_listen_addresses` passe de 0.0.0.0 a l'adresse de l'hote pour
laisser 127.0.0.1:53 libre. Mais l'appartenance au groupe est AUSSI ce qui ouvre
le port 53 a la frontiere : en exemptant la machine, je lui retirais le droit de
resoudre. `serveur_powerdns` declare donc son propre flux sortant — il ne
recurse pour personne, mais doit resoudre pour lui-meme.
Nouvel intrant `dns_amorcage`, derive jusqu'a `make creer-vm`. Cloud-init
l'ecrit bien mais sans effet : `dns-nameservers` exige `resolvconf`, absent du
gabarit, et installer resolvconf demande apt, qui demande la resolution.
`serveur_debian` pose donc le resolveur en pre_tasks, avant le premier apt, avec
une garde qui respecte la bascule ulterieure de client_unbound.
Defaut corrige en chemin : `_intrants_communs()` lisait `instance/` en dur ; le
chemin derive maintenant de l'inventaire recu, et un test le prouve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le devis enseignait « politique d'entree DROP au datacenter ». Or policy_in y
est la politique par defaut de TOUTE VM dont le pare-feu s'active — 37 machines
heritees sans regles, sur ce cluster. `policy_in` existe aussi par VM : le devis
et l'applicateur le posent la. Meme isolation, sans falaise, et l'applicateur
n'a plus a refuser une partie de son devis.
Trois verrous, pas un : datacenter enable=1, `enable` de la VM (defaut 0), et
`firewall=1` sur la carte. C'est le verrou du milieu que j'avais manque en
annoncant que huit VM en production tomberaient.
`enable=1` au datacenter bascule et verifie : pve-firewall running, 12 chaines
cadres, AUCUNE chaine par VM, 0 regle visant roxanne, 15 VM toujours en marche,
hyperviseurs et frontiere joignables, sortie tenant 2/2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deux obstacles, aucun n'etait celui qu'on croyait.
Proxmox n'installe AUCUN defaut dans le VRF du tenant : `default-originate`
annonce une route aux autres noeuds, il n'en pose pas chez lui. Les deux zones
etaient dans cet etat. La sortie vient d'une strophe frr.conf.local, que Proxmox
fusionne a chaque regeneration (verifie : survit a `pvesh set /cluster/sdn` et a
un redemarrage de FRR).
`nexthop-vrf default` emprunte UNE adresse au lieu d'importer la table
principale : la route par defaut des hyperviseurs ne gouverne pas la sortie des
tenants. `import vrf default` l'aurait fait contourner la frontiere et aurait
fuite le transport VXLAN, la gestion et les VLAN herites dans le VRF.
Le NAT sortant en mode automatique ne couvre que les reseaux directement
attaches ; un supernet joint par route statique en sort en silence. L'etat
montrait `nat_addr` absent : le filtre passait, la traduction manquait.
`devis_opnsense` emet le NAT (section 2bis), le reconciliateur l'applique et le
retire, et P24 refuse tout supernet route mais non traduit.
Mesure : tenant -> frontiere 3/3, -> passerelle FAI 3/3, -> Internet 2/2 pour
les deux tenants.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le devis rangeait tout flux entrant sur le WAN, en supposant que
l'administration revenait par l'adresse publique. Vrai pour 192.168.255.0/24,
faux depuis que c'est 10.0.0.0/24 — directement attache sur `lan`. Les deux
regles SSH etaient mortes deux fois : mauvaise interface, et « Block private
networks » les aurait filtrees. Le devis conseillait meme de decocher ce filtre
sur le WAN, ce qui aurait affaibli l'interface publique pour rien.
`reseaux_locaux_frontiere()` derive de l'underlay les sous-reseaux ou la
frontiere porte une adresse, hors transit. Un alias par interface : Technolibre
a les deux cotes, Chezlepro seulement la gestion. Sans underlay, tout retombe
sur le WAN — comportement inchange.
P24 confronte desormais chaque regle d'administration a l'attachement de sa
source, et refuse l'ancien comportement.
Nouvel intrant `opnsense_if_gestion`, au catalogue du GUI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`nftables_admin_ssh` est la source unique du « d'où administre-t-on » : il
alimente les alias de la frontière, le devis du commutateur et le jeu nftables
de chaque VM. Chezlepro déclarait encore l'ancien 192.168.255.0/24.
`make flux` échouait par ailleurs sur un tri mêlant ports numériques et
symboliques — défaut latent réveillé par les deux flux ICMP frag-needed, qui
sont dans les deux sens. `_cle_port()` rend la clé homogène.
Aperçus régénérés : les 14 hôtes actifs de Chezlepro portent 10.0.0.0/24.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les deux commutateurs deviennent bifrost-3 et bifrost-4, avec 10.0.0.3 et
10.0.0.4. Tout l'ensemble de bordure porte un seul nom, et son numéro est son
adresse : bifrost-1 = .1, bifrost-4 = .4. Plus de table de correspondance.
D-12 disait « bifrost aux frontières, sleipnir à la fabric » — le nom portait le
type de la machine. C'est le champ `role` qui le fait, et lui seul pilote le
devis : aucune logique ne dépendait du nom, seulement des données et un
commentaire. Le devis a suivi seul, jusqu'aux marqueurs de ports.
Inconvénient assumé : bifrost-3 ne dit plus « commutateur », il faut lire `role`.
La partie B du devis s'en charge à l'affichage.
30 preuves OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le pont n'était pas seulement non portable, il était faux. proxmox_clone_pont
faisait naître les VM sur vmbr1 avec une étiquette VLAN — l'ancien monde. En SDN
une VM appartient à son VNet ; c'est ce qu'il a fallu corriger à la main sur
infra-pki-01, et les treize suivantes auraient suivi.
deriver_nomenclature() expose désormais la zone de sécurité, instancier en dérive
proxmox_pont et une étiquette VIDE — le VNet porte déjà le tag, en poser un
second donnerait un double étiquetage. La chaîne va jusqu'à make creer-vm :
SETOPS_PONT='t11appl', SETOPS_VLAN=''.
Trois pièges. Un doublon dans le Makefile passait PONT_PROXMOX deux fois dans la
même cible, la seconde vide aurait écrasé la valeur dérivée. Un repli naïf sur
proxmox_vlan aurait fait revenir l'étiquette en SDN : le repli ne s'applique que
si la clé est ABSENTE, jamais si elle est présente et vide. Et le test unitaire
est tombé, à raison — il couvre maintenant cette distinction.
D-55 : le dépôt réseau porte le contrat entre l'Alliance et ses hébergeurs, et
abstrait le matériel en encapsulant chaque tenant dans sa zone EVPN. Mesuré : un
tenant est à deux valeurs de la portabilité complète (noeud, stockage).
D-57 : l'interface sysadmin d'un hyperviseur (vmbr0, 10.0.0.41/.43/.47) n'a pas
de route par défaut ; celle-ci vit sur vlan40, vers la frontière. On n'atteint
l'administration que depuis son propre domaine de diffusion. Ça tranche la
question de la sortie des nœuds laissée ouverte ce matin — option A, mais sur une
interface dédiée, ce qui lève l'objection qui la bloquait.
D-58 : un hôte déclare par quelle interface (`via`) chaque réseau lui arrive ; le
devis en dérive un port par interface et son type — trunk 11,40 sur bond3, accès
VLAN 10 sur vmbr0. Sans ça, ajouter le VLAN 10 le remettait sur le trunk du
transport, soit le domaine qu'on venait d'en sortir.
D-59 : un VLAN qui ne porte que des adresses d'hôte n'a pas besoin de pont.
Régression créée puis corrigée : le modèle public, qui ne déclare aucun
hyperviseur, n'émettait plus rien pour ce port. Il émet maintenant tout
l'underlay en disant que c'est un repli.
30 preuves OK, 4 tests unitaires.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Il valait partout ; il ne vaut que dans l'adressage dérivé des tenants, où
passerelle_de(index, zone) produit le même .1 dans les treize sous-réseaux d'un
tenant. C'est une propriété de la dérivation, pas une loi universelle.
Dans l'underlay il produisait deux effets pervers. Un seuil arbitraire : les
sous-réseaux plus étroits qu'un /24 étaient exemptés, donc élargir un /29
changeait la validité du fichier sans que rien d'autre bouge. Et une couture
entre propriétaires : l'octet attendu venait de la nomenclature d'un tenant,
appliquée à la fabric de l'hébergeur — la validité de l'underlay aurait dépendu
du tenant actif.
Ce qui reste est plus fort et suffit (D-52) : une passerelle doit être l'adresse
d'un hôte déclaré sur ce réseau. Elle attrape les passerelles fantômes, ce que le
comptage d'octets ne faisait pas. Vérifié : la garde mord toujours.
À noter, parce que l'ordre était mauvais : le ré-adressage de l'OPNsense en .1 a
été demandé au nom de cette règle, deux messages avant qu'elle soit recadrée. Pas
perdu — .1 est la position conventionnelle d'une passerelle — mais la portée
aurait dû être questionnée avant de faire changer une adresse en service.
D-53 : le réseau et l'underlay de l'hébergeur méritent leur propre dépôt.
underlay.yml décrit une infrastructure, le dépôt de tenant une organisation ; un
tenant peut déménager, une fabric non. Consigné, non fait.
D-54 : 10.0.0.0/24 est réservé à l'IPAM, la gestion des équipements et l'OOB,
accès sysadmin. Aucun hyperviseur, aucune VM, aucun trafic tenant. C'est la
raison d'être des VLAN 11 et 40.
30 preuves OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>