Commit graph

324 commits

Author SHA1 Message Date
3ef470be76 frontiere : ne declarer que ce qui existe, et reconcilier les routes
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>
2026-08-09 16:44:58 -04:00
df05d90059 frontiere : ne router que les sous-reseaux REELLEMENT attribues
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>
2026-08-09 16:07:10 -04:00
78f2f7fd6f SDN : un puits sur le supernet du tenant dans son propre VRF
« Je ne trouve pas ca normal » — l'exploitant avait raison. Pendant deux jours
nous avons attribue ce comportement a l'anti-usurpation de la frontiere, et je
l'avais consigne comme tel.

Mesure depuis DEUX points de vue : depuis le poste, quatre connexions sur
quatre s'etablissaient ; depuis l'interieur du tenant, le comportement etait
correct partout ou un VNet existe. L'anomalie ne touchait que les portions de
supernet sans VNet.

Cause : vrf_t17 portait les six /24 et un defaut, RIEN pour le reste du /16.
Une adresse non attribuee sortait du VRF, atteignait la frontiere, revenait a
l'hyperviseur dans la table PRINCIPALE — pas dans le VRF — et repartait vers
192.168.11.254, la passerelle du reseau d'ADMINISTRATION.

strophe_frr pose desormais un puits sur le supernet du tenant, moins specifique
que ses /24 donc invisible au trafic legitime. Derive du seed.

Verifie : depuis le tenant, 10.27.99.99 et 10.27.18.99 refusees, 10.27.18.21
etablie. Une machine du tenant ne peut plus atteindre le reseau de gestion par
une faute de frappe.

Reste, sans arbitrage : depuis le VLAN d'administration le connect() reussit
encore, ce trafic n'entrant jamais dans le VRF. Deux remedes possibles, dont
l'un touche la table qui porte l'administration des hyperviseurs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:01:24 -04:00
91d9bd19f4 recette : regenerer le plan apres l'ajout de l'unite de wiki (P22)
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>
2026-08-09 15:22:02 -04:00
85fed974a4 wiki : rattraper la reconstruction, et une unite sur la verification du deploye
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>
2026-08-09 15:11:43 -04:00
c7fe250520 idempotence de la flotte : zero changed, et le zero est verifie
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>
2026-08-09 15:00:43 -04:00
ffb515cf3c idempotence : Prometheus trie ses cibles, Forgejo conserve son secret JWT
Prometheus : intersect rend un ENSEMBLE, dont l'ordre d'iteration n'est pas
stable d'un processus a l'autre. Le fichier se rendait differemment a chaque
passage — memes cibles, ordre different — et le service redemarrait pour rien.
Trie sur les hotes.

Forgejo : JWT_SECRET est genere par le service et ajoute par lui a la fin
d'app.ini. Le gabarit ne le portait pas, donc chaque rendu l'EFFACAIT et
Forgejo en generait un nouveau. Ce n'etait pas du bruit : chaque deploiement
invalidait les jetons OAuth2 emis par la forge. Le role le relit et le repose ;
meme empreinte avant/apres, changed=0 aux 2e et 3e passages.

Trois erreurs de methode de ma part dans cette enquete :
- conclu « diff vide donc contenu identique » alors que no_log masquait le diff
- applique un replace sur le gabarit SANS verifier qu'il avait pris (la section
  [oauth2] n'existait pas), affiche un succes, et interprete trois passages sur
  cette base
- garde une expression Jinja indiagnosticable en place a cause de no_log

Verifier l'effet, pas l'intention — un replace qui ne trouve rien reussit
silencieusement, exactement comme kcadm -s sur une map.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 14:44:03 -04:00
fba0df26c3 metriques : node_exporter mourait a chaque renouvellement de certificat
Le passage d'idempotence a trouve une PANNE, pas une imperfection. 17 taches
changed au second passage contre 924 au rejeu depuis zero — mais 13 d'entre
elles etaient « Activer et demarrer node_exporter », et Ansible ne le
redemarrait pas par maladresse : il le trouvait ARRETE.

Le dump du module : ActiveState inactive, SubState dead, ExecStart code=killed
status=1/HUP.

Cause : le script de synchro du certificat faisait try-reload-or-restart, qui
RECHARGE si l'unite declare un ExecReload — et Debian en declare un
(kill -HUP). node_exporter ne sait pas se recharger : il meurt sur SIGHUP. Le
commentaire du script affirmait le contraire ; c'est l'hypothese qui etait
fausse, pas le code.

Consequence : a chaque renouvellement (24 h), la collecte de metriques
s'arretait sur toute la flotte, en silence. Elle repartait au deploiement
suivant, ce qui rendait la panne invisible a qui deploie souvent.

Mesure sur backup-01 : reload alloy -> active, reload loki -> active, reload
node_exporter -> INACTIVE. Seul lui est concerne.

Corrige en restart. Preuve : synchro declenchee sur les 14, quatorze active.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 14:14:16 -04:00
a0dc3da4a6 get_url : plus aucun sans garde — la dependance externe tombe a zero
Arbitrage de l'exploitant : garder aussi les cles de signature. Zero get_url
sans garde dans le depot, contre neuf ce matin.

Avant : 6 serveurs tiers x 14 hotes recontactes a chaque deploiement. Apres :
zero. Un deploiement de flotte ne depend plus d'aucun serveur etranger pour ce
que la machine possede deja.

Consequence assumee et ecrite dans chaque role : une rotation de cle amont
n'est plus recuperee seule. Elle ne passe pas inapercue pour autant — apt
refuse le depot, bruyamment — et le remede tient en une ligne. C'est un defaut
SONORE, pas silencieux ; toute la journee a consiste a transformer les seconds
en premiers.

Verifie sur backup-01 : changed=0, trois taches sautees. Reste a eprouver sur
un hote neuf, ou la garde doit laisser passer le telechargement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 14:05:25 -04:00
26dc0469be get_url : garder les artefacts epingles a une version
Recensement apres l'echec du passage d'idempotence : 9 get_url sans aucune
garde, 1 avec.

Les quatre artefacts epingles (forgejo, keycloak, nextcloud, oauth2-proxy)
sont immuables PAR CONSTRUCTION — leur chemin de destination porte la version.
Les retelecharger n'a aucun sens, les recontacter encore moins. Gardes par une
verification d'existence.

Restent cinq cles de signature apt et un trousseau .deb, recuperes a chaque
passage : cinq serveurs externes x quatorze hotes = 70 allers-retours par
deploiement. Les garder supprimerait la dependance au prix de ne plus detecter
une rotation ; une cle tournee casse apt bruyamment, donc l'oubli se voit.
Arbitrage a rendre — pas a moi.

Sur une plateforme souveraine la question merite d'etre posee : combien de
serveurs tiers doivent etre joignables pour redeployer ce qu'on possede deja ?

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 14:01:12 -04:00
2ec9cec76a forgejo : ne pas retelecharger un binaire deja pose
Trouve par le passage d'idempotence : « Connection failure: The read operation
timed out » sur le telechargement du binaire — 106 Mo deja presents sur la
machine.

get_url n'avait aucune garde (ni checksum, ni condition d'existence), alors
que le chemin de destination PORTE la version : forgejo-10.0.0 ne peut pas
designer un autre contenu demain. Chaque deploiement recontactait donc un
serveur tiers pour un fichier immuable.

Un deploiement de flotte echouait parce qu'un serveur externe etait lent. Sur
une plateforme qui se veut souveraine, c'est une dependance de trop sur le
chemin critique.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 13:58:33 -04:00
9feaf212c7 reconstruction complete sans echec, et correction : ssh_hardening declarait deja
Deuxieme reconstruction from-zero : zero echec, zero injoignable sur les 14
hotes, d'un seul trait — creation, amorcage du socle, trente couches. La
premiere avait demande six corrections. Les cinq devis : CONFORME.

D-71 se lit dans les chiffres : infra-pki-01 changed=3, infra-dns-01
changed=1, deja montes par _amorcer-socle.

CORRECTION. J'ai ecrit hier que MaxStartups/MaxSessions « ne viennent d'aucun
role, elles sont dans le gabarit ». C'est FAUX : j'avais grepe ssh_baseline
seul. ssh_hardening les pose depuis toujours, en dur dans son template. Le
depot declarait bien son durcissement ; ce qu'il ne faisait pas, c'est
l'exposer — des valeurs ecrites dans un fichier de rendu sont invisibles a qui
lit les defaults.

Et ma premiere correction avait EMPIRE les choses : ajouter ces cles a
ssh_baseline creait deux fichiers, deux roles, une meme directive et deux
valeurs. Retire. Elles sont maintenant des variables de ssh_hardening.

Mesure finale sur les 14 : logingracetime 20, maxsessions 10, maxstartups
10:30:60 — identique partout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 13:45:24 -04:00
ed6087d5de ansible : pipelining + ControlPersist — la course « banner exchange » comprise
Cette fois j'ai garde le journal, et la cause est etablie.

La machine : un seul demarrage toujours en cours, aucune coupure reseau, aucun
redemarrage de sshd. Le « trou » de 72 s dans son journal n'en etait pas un —
l'entree qui le referme est ma propre commande de diagnostic. L'hote n'a rien
fait parce que plus personne ne lui parlait.

La cause : maxstartups 5:30:20 et maxsessions 2 (defauts Debian 10:30:100 et
10). Au-dela de 5 connexions non authentifiees simultanees, sshd en refuse une
partie SANS BANNIERE — et le client rapporte « timed out during banner
exchange », qui accuse le reseau pour un refus applicatif.

Ces valeurs ne viennent d'aucun role : elles sont dans le GABARIT. Un reglage
de securite qui ne vit que dans une image disque est invisible du depot et des
preuves, et gouverne pourtant la voie d'administration.

Corrige cote client : ansible.cfg n'avait AUCUNE section [ssh_connection].
pipelining + ControlPersist + control_path_dir court (un chemin trop long
depasse la limite des sockets UNIX et ferait retomber Ansible sur une
connexion par tache).

Mesure sur le meme play de 30 taches : avant, une session par seconde ; apres,
ZERO nouvelle session.

Reste ouvert : MaxStartups/MaxSessions devraient etre declares par
ssh_baseline plutot que dormir dans le gabarit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 12:35:01 -04:00
b08130dfdd gabarit : ancien supprime, le nouveau porte son nom
Le cluster ne porte plus qu'un gabarit : modeleChezlepro, VMID 99998.

Trois verifications avant de supprimer : le nouveau avait deja produit une VM
deployee et prouvee (web-dorsal-01, cinq devis CONFORME) ; aucune VM ne
dependait du disque de 99999 (recherche de base-99999 dans tous les disques du
cluster — un clone lie aurait rendu la suppression destructrice pour la flotte
entiere) ; et le nom de 99999 confirme avant le DELETE, meme verrou que raser.

L'ordre n'etait pas indifferent : supprimer d'abord, renommer ensuite. Deux
modeleChezlepro sur le cluster auraient rendu le clonage PAR NOM ambigu —
exactement la collision qui avait fait rapporter ok a proxmox_kvm sans rien
faire le 2026-08-07.

Ce que le nouveau n'a plus : resolv.conf du reseau de fabrication, searchdomain
public, et une cle PRIVEE d'hote SSH.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 11:59:46 -04:00
0de5294a20 client_unbound : survivre a la perte de connexion sans la masquer
Le deploiement s'interrompait autour du redemarrage d'Unbound (« banner
exchange »), et Ansible abandonnait alors TOUTES les couches suivantes de
l'hote — alors qu'il repondait de nouveau une minute plus tard.

Ma premiere explication etait fausse et je l'ai verifiee avant de coder :
sshd -T dit usedns no, il n'y a pas de resolution inverse. Et la cause reste
INCONNUE — j'avais ecrase le journal du deploiement rate en relancant. Faute
de methode, pas de raisonnement.

Ce que le depot sait de ce symptome est deja dans le Makefile : a travers la
frontiere le TCP s'etablit par proxy SYN, et l'echec se lit « banner exchange »
meme quand l'hote n'est pas la. Le message accuse SSH pour un probleme
d'accessibilite.

Corrige sans pretendre connaitre la cause : ignore_unreachable sur le handler,
puis wait_for_connection qui EXIGE le retour (180 s). Si l'hote ne revient
pas, la tache suivante echoue franchement — on ne masque rien.

Regle pour moi : ne plus ecraser le journal d'un echec avant de l'avoir lu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 11:55:41 -04:00
9cdfcc5deb bascule du plan sur le gabarit recapture, prouvee par une VM reelle
proxmox_clone_* pointent sur modeleChezlepro-travail (99998). L'ancien reste
sur le cluster — il porte encore un resolv.conf et une cle privee d'hote SSH
du reseau de fabrication ; a supprimer quand plusieurs VM seront nees du
nouveau.

Preuve par une machine, pas par lecture de config : web-dorsal-01 rase puis
recree par le chemin normal. Herite du nouveau gabarit un resolv.conf reduit a
ses commentaires, un machine-id neuf, une cle d'hote regeneree. Deploiement
complet sans echec, cinq devis CONFORME.

raser accepte --hote : les quatre verrous restent en vigueur, on ne fait que
RESTREINDRE la liste derivee du plan.

Releve, non corrige : le premier deploiement s'est interrompu sur
client_unbound, a l'instant de la bascule du resolveur vers 127.0.0.1 — sshd
fige sa resolution a l'ouverture de session (« banner exchange »). L'hote
repondait une minute plus tard et la reprise est passee integralement. C'est
une COURSE, meme famille que celles de la reconstruction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 11:50:34 -04:00
3618dfc129 gabarit recapture : copie de travail preparee, nettoyee, convertie
modeleChezlepro (99999) clone en modeleChezlepro-travail (99998). L'original
n'a pas ete touche.

Deux defauts de mon propre outillage, trouves en l'utilisant :
- l'inventaire d'un seul hote ne porte aucun group_vars, donc aucun utilisateur
  de connexion : Ansible tentait le compte local. MODELE_HOTE passe desormais
  ansible_user.
- les playbooks ciblent hosts: modeles_vm, et un inventaire d'un seul hote
  place la machine dans all. La commande etait juste et la cible introuvable
  (« skipping: no hosts matched »). J'avais verifie l'affichage, pas l'effet.
  Les trois acceptent maintenant cible_modele.

Le nettoyage vide /etc/resolv.conf (il portait l'identite du reseau de
fabrication) et supprime les CLES D HOTE SSH — le gabarit transportait une cle
privee. Sur que parce que cloud-init les recree au premier demarrage, verifie
avant de l'ecrire.

Reste a faire, et ce n'est pas a moi : basculer proxmox_clone_* sur le nouveau
une fois qu'une VM en sera nee et aura fonctionne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 11:29:51 -04:00
ff3ca3bf26 modeles_vm : trois commandes qui ne pouvaient rien faire
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>
2026-08-09 10:40:49 -04:00
711676ca69 test : epingler SETOPS_DOMAINE dans le contrat de parametres-proxmox
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>
2026-08-09 10:26:44 -04:00
70a0698ba7 gabarit : deriver le domaine de recherche, et vider resolv.conf a la capture
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>
2026-08-09 10:25:03 -04:00
28e0c45696 P33 / D-73 : aucune collision de port entre roles co-localises
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>
2026-08-09 09:46:17 -04:00
d52f256366 P32 / D-72 : tout intrant exige par un role est fourni par l'instance
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>
2026-08-09 09:40:25 -04:00
cbb186d2fa reconstruction from-zero PROUVEE : cinq devis sur cinq, identiques a la reference
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>
2026-08-09 09:13:51 -04:00
57e3bd3d01 client_journal : imposer et DECLARER le port d'Alloy — collision avec le SASL de Dovecot
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>
2026-08-09 09:04:25 -04:00
043ca6480f grafana : rendre patiente la pose du mot de passe de secours
Cinquieme arret de la reconstruction from-zero, meme classe que le quatrieme :
une COURSE de premier demarrage. La commande suit de peu le premier demarrage
de grafana-server, qui cree encore sa base ; la CLI echoue sur une base absente
ou verrouillee. Rejouee seule quelques minutes plus tard, elle passe.

retries/until, comme pour l'ecriture Keycloak qui suit la synchro LDAP : on
attend une condition, pas une duree.

Note : la sonde de diagnostic a change le mot de passe admin. Verifie avant de
poursuivre que le marqueur d'empreinte etait ABSENT — la tache se rejoue donc
et repose la valeur de la voute. Aucune trace laissee.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 23:47:34 -04:00
059953e621 keycloak : rendre patiente l'ecriture qui suit la synchronisation LDAP
Quatrieme arret de la reconstruction from-zero, et le premier qui ne soit pas
un defaut d'ordre mais une COURSE.

  ModelException: Database operation failed
  Caused by: PSQLException: This statement has been closed
  TransactionReaper::doCancellations ... ActionStatus.ABORTED

L'ecriture des actions requises arrive juste apres la synchronisation complete
de la federation ; sur un realm neuf, la synchro tient encore des transactions
et le collecteur annule le PUT. Rejouee seule deux minutes plus tard, la meme
ecriture passe en 76 ms — et le groupe entier repasse avec failed=0.

retries/until plutot qu'un delai fixe : on attend une condition, pas une
duree. Un echec transitoire n'a pas a faire tomber un deploiement d'une heure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:17:26 -04:00
8b0eaaec0a keycloak : le claim de groupes exige les clients — troisieme defaut d'ordre
Meme motif que les roles de realm, une etape plus loin. groupes-ldap.yml
posait aussi un oidc-group-membership-mapper sur les CLIENTS, alors que
clients-oidc.yml s'execute apres. Invisible tant que les clients existaient
d'un passage precedent.

Extrait dans claim-groupes.yml, place APRES clients-oidc. J'avais d'abord
insere l'appel AVANT — le defaut meme que je corrigeais ; rattrape avant tout
deploiement.

La lecon vaut au-dela du role : un fichier de taches nomme d'apres un SUJET
(« les groupes ») rassemble des etapes aux dependances differentes, et l'ordre
qui en resulte n'est correct que par accident. Ce qui doit gouverner le
decoupage, c'est ce dont chaque etape a BESOIN, pas ce dont elle parle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 18:06:16 -04:00
3bf99fa7be keycloak : les roles de realm sont un prerequis de la projection des groupes
Second defaut trouve par la reconstruction from-zero. groupes-ldap.yml attache
grafana-admin a un groupe, mais c'est rbac-oidc.yml qui cree ce role — et il
s'executait APRES. Invisible tant que le realm existait avec ses roles crees
par un passage precedent ; sur un realm neuf, seuls les roles integres
existaient et l'attachement echouait.

Scinde plutot que deplace : rbac-oidc.yml fait trois choses aux dependances
DISTINCTES — creer les roles (ne depend de rien), poser un mapper sur les
clients (depend de clients-oidc), assigner des roles a des utilisateurs. Les
melanger etait le defaut. L'ordre est desormais roles -> groupes -> clients ->
mappers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 17:04:15 -04:00
a1accdd49e amorcage du socle eprouve : AC auto-signee, zones directe et inverse qui repondent
Premiere execution de _amorcer-socle sur une flotte fraichement clonee, aucun
pair debout. Les deux exceptions structurelles de D-71 se sont exercees :
l'AC s'auto-signe (subject == issuer sur la racine), le DNS pose ses zones.

Requetes reelles, pas lecture de fichier :
  10.27.19.21 -> infra-pki-01.chezlepro.internal.
  10.27.21.11 -> forge-01.chezlepro.internal.
  10.27.18.21 -> backup-01.chezlepro.internal.

forge-01 et backup-01 ne sont PAS deployes et leur PTR repond : demonstration
de l'arbitrage rendu la veille — la zone est GENEREE depuis le plan, pas
enrolee par la VM. Un enrolement aurait fait dependre le DNS de l'etat de
chaque machine.

Sonde fausse une fois de plus avant le systeme : dig @127.0.0.1 refusait la
connexion, PowerDNS ecoutant sur ansible_host.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 16:24:04 -04:00
1295eeaf4e D-71 : PKI et DNS debout avant tout, et la zone inverse manquait
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>
2026-08-08 15:29:57 -04:00
872d590031 P31 : le motif laissait passer toute cible contenant une majuscule
« 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>
2026-08-08 14:34:00 -04:00
a430edf014 make raser : la seule commande destructive du moteur, avec ses quatre verrous
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>
2026-08-08 14:22:04 -04:00
7dcbc6a0a4 audit : figer l'etat de reference avant la reconstruction from-zero
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>
2026-08-08 14:14:44 -04:00
107518d269 audit : rejuger les 11 affirmations fausses — onze sur onze resolues
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>
2026-08-08 13:41:49 -04:00
5308574730 D-70 / P31 : l'exigence de documentation devient une preuve
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>
2026-08-08 13:21:23 -04:00
c5fd3aa70b docs : tisser les devis dans les points d'entree, et corriger trois faits perimes
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>
2026-08-08 12:33:35 -04:00
c9d84e48d9 doctrine : D-68 et D-69 — relire ce qu'on ecrit, pas « toujours l'API »
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>
2026-08-08 11:45:31 -04:00
44ee6d4d36 keycloak : URI de retour apres deconnexion, derivees de web_origins
Nextcloud se connectait et echouait a la deconnexion — « invalid redirect
uri ». Keycloak valide ces URI SEPAREMENT des URI de rappel, et aucun des
quatre clients ne declarait l'attribut ; Nextcloud etait seulement le seul a
en envoyer une.

post.logout.redirect.uris derivee de web_origins, qui porte deja l'URL de base
du service. Posee par l'API : attributes est une map, et kcadm -s sur une map
accepte sans ecrire (meme leçon que smtpServer ce matin). Relu apres ecriture.

Le second passage a revele un defaut de l'heure precedente : la tache de
journalisation se declarait changed a chaque deploiement — Jinja rendait True
et 1209600 en CHAINES, la comparaison au reel ne pouvait aboutir. Compose en
une seule expression, types natifs. Deux passages a changed=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:24:02 -04:00
eef44c2dce nginx : dimensionner les tampons d'en-tetes pour les sessions OIDC
oauth2-proxy journalisait AuthSuccess pendant que le navigateur recevait une
erreur de passerelle : le cookie de session porte le jeton d'identite, decoupe
en plusieurs Set-Cookie, et le tampon par defaut de nginx (4 Ko) ne peut pas
les contenir.

  upstream sent too big header while reading response header from upstream
  server: icinga.chezlepro.internal, request: GET /oauth2/callback

Pose sur TOUTES les expositions : c'est une propriete du proxy, pas de ce
service-la, et le prochain service derriere un IdP rencontrerait le meme mur.

Trouve grace au journal d'evenements active juste avant : LOGIN puis
CODE_TO_TOKEN reussis pour icingaweb2 ont ecarte l'identite d'un coup.

Laisse ouvert : trois upstream timed out vers Keycloak en 14 h, alors que
Keycloak repond en millisecondes depuis l'edge. Cause non etablie — et ma
sonde MTU ne valait rien, l'ICMP etant bloque par construction entre hotes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:06:05 -04:00
dd84ed011c keycloak : journaliser les evenements du realm
Deux services n'aboutissaient pas et tout etait correct cote serveur — clients
OIDC, URI de rappel, secret identique (meme empreinte), CA de confiance,
aucune restriction de domaine. Le premier saut rejoue au curl montrait des
parcours sains.

Et la, plus rien a examiner : eventsEnabled = False. Keycloak ne gardait
aucune trace, ni des connexions ni des echecs. Manque de diagnostic, mais
surtout d'exploitation : « un sysadmin l'exploite sans IA » suppose qu'il
puisse lire lui-meme ce qui s'est passe.

Journal (connexions + actions d'administration, retention 14 jours)
reconcilie par le role, pas active a la main dans une console.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 10:57:42 -04:00
a90dff7ba7 courriel : routage local par identifiant, et la livraison interne remise en marche
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>
2026-08-08 09:42:45 -04:00
4bfcf4944f devis PostgreSQL et courriel : la serie des devis de service est complete
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>
2026-08-08 08:53:49 -04:00
0cc04177fb devis des expositions : une vraie requete, depuis deux points de vue
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>
2026-08-08 07:33:49 -04:00
5fde136e9f devis des certificats : disque contre memoire, et l'AC etait expiree
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>
2026-08-08 07:20:56 -04:00
5aa5f2e479 devis d'identite : comparer le deploye au declare
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>
2026-08-08 06:55:06 -04:00
419ecaba63 identite : une declaration de politique de mot de passe, deux executants
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>
2026-08-08 06:28:10 -04:00
775df924cb identite : « mot de passe oublie » — raccord SMTP derive du plan
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>
2026-08-08 06:08:06 -04:00
4642ffec0a rotation : vault_openldap_admin, quatre consommateurs et deux pieges
`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>
2026-08-08 05:46:38 -04:00
35602546eb rotation : vault_forgejo_oidc, et la reconciliation des secrets OIDC
Le secret avait fui dans une sortie de diagnostic. Le faire tourner a d'abord
demande de rendre la rotation possible : ni Keycloak ni Forgejo ne reconciliaient
un secret OIDC existant. Le commentaire de clients-oidc.yml l'avouait
(« create-si-absent »), et `update-oauth` ne passait pas --secret.

Regenerer la voute aurait laisse les deux cotes sur l'ancienne valeur — ou un
seul des deux, et le SSO aurait casse sans que rien ne l'annonce.

Keycloak compare desormais le secret EN PLACE a celui voulu avant d'ecrire.
Verifie par empreinte aux trois endroits : voute, Keycloak, Forgejo — identiques.
Keycloak rejoue a changed=0.

Signale sans etre corrige : `Deployer app.ini` change a chaque passage. Forgejo
reecrit lui-meme ce fichier (il y persiste ses secrets generes) et le gabarit
l'ecrase. Prealable au correctif : decider quelles cles appartiennent au gabarit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 22:20:11 -04:00
b51933566b rotation : vault_keycloak_admin, et la contrainte d'ordre
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>
2026-08-07 21:16:51 -04:00