Keycloak, Dovecot, Postfix et Icinga Web 2 se liaient TOUS avec cn=admin, le compte
d administration de la base. C est le rootDN : slapd lui fait contourner toutes les
ACL. Un seul secret, quatre services, tous les droits sur l arbre — pour ce qui est,
trois fois sur quatre, une simple lecture.
Et les droits livres par Debian etaient intacts : `to * by * read`. Sur ldap://, sans
s authentifier, une machine du reseau enumerait tous les comptes et toutes les
adresses. Des comptes a droits mesures n auraient rien valu tant que cette ligne
restait : on aurait ferme la porte en laissant la fenetre.
L indice etait deja dans le depot. `validatePasswordPolicy` existe parce que slapd
n applique pas ses controles de qualite au rootDN : la consequence etait compensee,
la cause intacte.
- ou=services, un compte par consommateur, secret propre en voute
- sept regles d acces posees EN ENTIER (state: exact) : l ordre est la regle, et
inserer c est parier sur ce que le paquet aura mis avant nous
- amorcage_acces garde le compte d administration, NOMME comme l exception : il ne
consomme pas l annuaire, il le provisionne depuis la socket locale
- la sonde passe de -x a -Y EXTERNAL : elle lisait en anonyme et aurait annonce un
annuaire VIDE sur un annuaire parfaitement sain
- la rotation du compte d administration devient possible (elle n etait posee qu a
l installation, par debconf : la voute et slapd divergeaient en silence)
Quatre marches payees en chemin :
1. un cinquieme appelant oublie, dont l echec etait masque par no_log — la garde
refuse desormais SANS no_log : elle nomme la cle absente, jamais son contenu
2. la federation Keycloak ne reecrivait son bindDn que si l URL ou le mode changeaient
— nouveau secret, ancien nom, error code 49
3. la rotation placee APRES les taches qui se lient en administrateur
4. ansible-vault et son tube : sortie non bloquante = echec silencieux, la voute
paraissait tournee et etait identique a l octet
P72 exige que tout role incluant resoudre_annuaire NOMME son compte, et qu aucun sauf
amorcage_acces ne nomme admin. Eprouvee dans les deux sens.
Verifie sur l infrastructure : chaque compte lit ce qu il doit, aucun ne voit les
autres, la lecture anonyme rend 0 entree, et les quatre services repondent (doveadm
user, postmap -q, decouverte OIDC 200, portier SSO 200).
vault_openldap_admin et vault_ldap_bind_postfix renouveles : les deux avaient transite
en clair par une session d exploitation. Les anciennes valeurs rendent Invalid
credentials (49).
make prouver : 71 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
CORRECTION DE COMPTE D ABORD. J avais annonce cinq groupes sans sonde. La
mesure en donne VINGT-SEPT : j avais compte ceux que j avais en tete, pas
ceux que le depot contient. Il en reste vingt-deux.
LES CINQ, PAR ORDRE DE DEGAT SILENCIEUX.
autorite (step_ca) — la plus urgente : nos certificats vivent 24 h, une AC
muette ne casse rien aujourd hui et casse TOUT demain, d un coup, sur les
21 machines. Elle surveille aussi l expiration de la RACINE, que personne
ne regarde parce qu elle vit des annees (relevee a 3642 jours).
base (postgresql) — une VRAIE requete, pas pg_isready : celui-ci dit que
le port repond, pas que la base sert. Plus le compte des connexions : a
saturation, chaque application tombe sans que la base ait l air morte.
annuaire (openldap) — elle COMPTE les entrees. Un annuaire vide repond
success a tout : le mensonge des sauvegardes vides, vert et sans contenu.
zones (powerdns) — un autoritatif sans zone repond NXDOMAIN a tout, ce qui
se lit comme « ce nom n existe pas ».
edge (nginx) — elle valide la configuration SUR DISQUE : nginx sert la
derniere valide, et une configuration cassee ne se voit qu au prochain
demarrage, souvent des mois plus tard.
Les cinq eprouvees vertes sur le sain puis rouges PAR PARAMETRE, sans
toucher a un service.
Etat : 20 sondes sur 19 roles, 23 services distincts, 70 instances, 67 au
vert. Les trois autres sont connues : deux sauvegardes sans donnee a
emporter, et Loki en delai de stabilisation apres redeploiement.
make prouver : CONFORME, 64 OK, 0 echec, 0 saute.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
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 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>
Fin des dernières poches de 'codé en dur' liées à l'instance d'origine :
- realm SSO centralisé sur l'intrant identite_realm (defaut chezlepro,
retro-compatible) ; les 4 rôles (keycloak/forgejo/grafana/oauth2_proxy)
en dérivent. Expose dans la GUI (panneau Intrants).
- vars brandees renommees generiques : chezlepro_timezone -> fuseau_horaire,
chezlepro_organisation -> organisation (chrony, openldap, GUI, docs).
Aucune reference fonctionnelle aux anciens noms. Le moteur ne porte plus le
nom d'un tenant. Technolibre a son propre realm (technolibre).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- TLS LDAPS/STARTTLS : pont du cert step_ca (client_pki, root:root 600)
vers /etc/ldap/tls lisible par openldap ; script + unité path systemd
qui re-synchronise et recharge slapd au renouvellement ; olcTLS* dans
cn=config ; SLAPD_SERVICES expose ldaps://. Dégrade proprement sans cert.
- Organisation : intrant chezlepro_organisation (remplace « Exemple Inc »).
Validé statiquement (ansible-lint, syntax) ; déploiement réel à suivre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les rôles à secrets déclaraient serveur_X_password: "" avec la variable
de voûte seulement en commentaire — aucun mapping réel. Remplir la voûte
ne branchait donc rien (secret vide → assertion échoue).
Les 12 secrets des 8 rôles pointent maintenant vers leur source :
serveur_X_password: "{{ vault_X | default('') }}"
Comportement inchangé si la voûte est vide ; branché dès qu'elle a le
secret. Prouvé : assertion step_ca passe avec vault_step_ca_password.
Chaque instance remplit ses vault_* ; aucun mapping par instance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>