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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Le runbook promettait cette rotation ; le code ne savait pas la faire. Les trois
roles ne posaient le mot de passe qu'a la CREATION — regenerer la voute aurait
produit le mensonge silencieux corrige toute la journee.
Chaque role sait desormais CHANGER un mot de passe existant, idempotent par
empreinte du secret applique. Pour Nextcloud le secret passe par l'environnement,
pas par la ligne de commande ou il serait visible dans la table des processus.
`grafana-cli` ecrivait dans une base FANTOME en annoncant « changed successfully »
a chaque fois : il prend `paths.data` a `<homepath>/data`, le paquet Debian range
la base dans /var/lib/grafana. Demasque par le champ `updated` du compte, reste a
l'heure du deploiement initial malgre quatre reinitialisations « reussies ». Un
message de succes n'est pas une preuve ; l'etat l'est.
Verifie par authentification reelle : Grafana 200, Nextcloud 200. Forgejo ferme
l'API par doctrine (D-41) — seule preuve disponible : le retour de la commande.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le dernier `porte_par: liste-uid` du catalogue. `roles.ini` porte desormais
`groups = "sysadmin"` ; `serveur_icingaweb2_admins` devient un repli de
depannage, VIDE par defaut.
Le mode SSO complique le montage : les membres d'un `groupOfNames` sont des DN,
alors que `REMOTE_USER` est une chaine. Un backend LDAP supplementaire est
declare — jamais utilise pour authentifier — uniquement pour que `groups.ini`
resolve le nom vers son DN. Sans ce pont, l'habilitation par groupe est
impossible en SSO.
NON PROUVE : la resolution REMOTE_USER -> DN -> appartenance est interne a
Icinga Web 2 ; seule une connexion reelle par le SSO la confirmera.
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>
Le changement de mot de passe imposé echouait sur « Federated storage is not
writable » : `editMode=READ_ONLY` etait code en dur. Keycloak lisait l'annuaire
sans pouvoir y ecrire — `pwdReset` devenait un cul-de-sac, LDAP exigeant un
changement que Keycloak ne pouvait pas faire.
Trois modes, un seul tient avec la doctrine. `UNSYNCED` ferait ecrire Keycloak
dans SA base : Dovecot et Postfix, qui se lient directement a LDAP (D-39),
valideraient encore l'ancien mot de passe. Ca aurait « marche » a l'ecran en
cassant le courriel en silence. `WRITABLE` ecrit A TRAVERS : l'annuaire reste la
source unique, Keycloak n'en est qu'un client.
Le mode devient une variable et il est RECONCILIE : un provider cree en
READ_ONLY le serait reste a vie.
Deux fautes de ma part au passage : extraction ancree sur `$` alors que la ligne
finit par un guillemet, et pas de `|| true` — sous `pipefail`, un grep vide tue
le script. Masque par `no_log`, pour la troisieme fois aujourd'hui.
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>
Quatre meta/empreinte.yml declaraient leurs valeurs A LA RACINE, sans la cle
`setops_empreinte:` : collabora, nextcloud, web_dorsal, web_frontal. Le lecteur
les voyait vides et rendait {0,0,0} — en silence. collab-01 s'est retrouvee avec
1 coeur / 1 Go pour porter Nextcloud ET Collabora, et a cesse de repondre en SSH
faute de memoire. Corrigee : 4c/5632Mo. Une garde refuse desormais cette forme.
Un fichier qui existe mais ne dit rien est pire qu'un fichier absent : le repli
aurait donne des valeurs sensees.
Deux courses de premier demarrage :
- le clone rend la main avant que son .conf existe -> attente active sur l'API ;
- le verrou dpkg frappait hors de `common_packages` -> `lock_timeout` pose en
module_defaults sur les 30 playbooks de groupe, une declaration au lieu de 30.
Au passage, j'ai failli livrer pire que le defaut : une URL coupee avec `>-`
inserait une ESPACE en son milieu. Le lint passait, la requete non.
Enfin : `proxmox_kvm` identifie une VM par son NOM. Une VM heritee homonyme lui
a fait rapporter `ok` sans rien cloner — un deploiement peut donc PARAITRE
reussi alors qu'aucune VM n'existe. Touche D-37 directement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`forge-01` a echoue sur une URL de decouverte pointant
`https://keycloak.<domaine>` — le nom que rien ne publie. J'avais corrige
exactement ca dans `serveur_oauth2_proxy` quelques heures plus tot, en croyant
regler un cas isole.
Il etait dans quatre roles : forgejo, grafana, nextcloud, oauth2-proxy. Chacun
fabriquait le meme nom par la meme convention. Une cinquieme correction a la main
aurait diverge comme les quatre autres.
`roles/resoudre_idp` lit l'exposition declaree au plan et rend hote, base et
discovery. Les roles n'en gardent qu'un REPLI nomme, jamais la valeur de travail.
Verifie en base sur forge-01 : discovery = auth.chezlepro.internal,
GroupClaimName = groups, AdminGroup = sysadmin.
Meme lecon que `resoudre_annuaire` hier : corriger la valeur la ou elle echoue ne
corrige que la. Ce sont les copies silencieuses qui coutent la journee suivante.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les deux services n'etaient pas encore deployes : les cabler maintenant vaut
mieux que les corriger apres. `porte_par` passe de `aucun` a `claim-groupe`.
Forgejo recoit --group-claim-name + --admin-group, et sa tache passe de « creer
si absent » a add-oauth OU update-oauth : le meme defaut que la federation
Keycloak — cree une fois, jamais corrige — l'attendait sinon.
Nextcloud recoit --mapping-groups + --group-provisioning, plus une tache qui
verse les membres du groupe d'habilitation dans le groupe interne `admin` : etre
dans un groupe projete ne donne aucun pouvoir en soi.
Le maillon qui manquait aux deux : AUCUN mapper de protocole n'emettait le claim.
Les groupes existaient dans le realm et n'apparaissaient dans aucun jeton — un
cablage correct des deux cotes et rien au milieu. `oidc-group-membership-mapper`
pose sur les trois clients, `full.path=false` pour que le claim porte `sysadmin`
et non `/sysadmin`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La chaine est complete : LDAP cn=sysadmin -> groupe de realm -> role
grafana-admin -> Admin dans Grafana. Ajouter quelqu'un au groupe dans l'annuaire
lui ouvre Grafana sans deploiement et sans que personne ne soit nomme (D-66).
Rejoue : changed=0.
Defaut de fond trouve en chemin : la federation LDAP n'etait JAMAIS reconciliee.
`federation-ldap.yml` creait le provider s'il manquait puis ne le corrigeait
plus — il pointait encore `ldaps://id-ldap-01...`, le nom errone corrige le matin
meme dans `resoudre_annuaire`. La synchronisation echouait sur `UnknownHost`.
C'est le revers de D-67 au mauvais endroit : l'INFRASTRUCTURE se reconcilie,
seules les appartenances ne le sont pas. URL, usersDn et bindDn sont desormais
corriges a chaque passage.
Et j'avais masque l'echec avec `|| true` : le mapper existait, le realm restait
vide, rien ne disait pourquoi. Retire — l'erreur remonte avec son message.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chaque role web declare le GROUPE qu'il reconnait et ce qu'il lui accorde. Un
troisieme champ s'est impose en ecrivant : `porte_par` — le mecanisme qui
transporte reellement l'habilitation. Sans lui, les declarations auraient decrit
une chaine inexistante.
Etat mesure : grafana `role-realm` (reel) ; forgejo, nextcloud et keycloak
`aucun` ; icingaweb2 `liste-uid` — il NOMME DES PERSONNES, ce que D-66 interdit.
Le maillon manquant est chez Keycloak : `role_assignments` assigne un role a un
UTILISATEUR, et son propre commentaire l'admettait (« en prod, preferer
l'assignation via groupe d'annuaire »). Sans group-ldap-mapper, les groupes LDAP
n'atteignent jamais les services. Sa meta a donc une autre forme,
`acces_projection` : Keycloak projette au lieu de consommer (D-65).
Defaut corrige : `serveur_icingaweb2_admins` valait "testmail", un compte de
test code en dur dans le moteur qu'aucune instance ne surchargeait — le seul
administrateur declare de la supervision etait un utilisateur inexistant. Il
suit desormais l'uid d'amorcage. Verifie sur mon-01 : users = "sysadmin".
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>