Une revision de documentation vieillit comme le reste. Ce qui tient, c est ce
qu une machine verifie — et trois lacunes etaient nommees sans etre gardees.
P58 HABILITATIONS. autorisation.md posait la regle (un service nomme un
GROUPE, jamais une personne, D-66) et meta/acces.yml la portait ; rien ne
la verifiait. P29 gardait les POSITIONS d authentification, personne ne
gardait les DROITS.
Le controle qui porte la preuve est un croisement : une entree
porte_par: role-realm affirme que l habilitation voyage par un role de
realm projete depuis un groupe LDAP. P58 le confronte a serveur_keycloak.
Sans ca, un service annonce une habilitation que rien ne transporte, et l
ecran reste vide sans que personne sache pourquoi.
CE QU ELLE N EXIGE PAS, et c est le point le plus important : que les
groupes nommes existent dans l annuaire. Ce serait contredire le regime du
paragraphe 2 — le depot AMORCE un acces et se retire, les appartenances
appartiennent a une personne. dev et personnel n existent dans aucun code,
et ce n est pas un defaut.
P59 ENUMERATIONS ANNONCEES. Les deux ecarts trouves a la main pendant la
tournee — cinq portes annoncees devant une table de six, huit lignes
renvoyees vers une fiche qui en compte dix — etaient d une forme que P57
ne voit pas.
Ma premiere version a signale CINQ ecarts, et les cinq etaient du bruit :
dans « reprise dans les deux devis : », le nombre qualifie autre chose que
la liste. Cent pour cent de faux positifs — la preuve qui crie sur un cas
sain et qu on apprend a ignorer. Resserree aux deux formes ou le nombre ne
peut compter rien d autre. Etroite et vraie plutot que large et devineuse.
P60 WIKI PUBLIE. Le wiki est publie DEPUIS le depot ; rien ne mesurait l ecart,
et il s est creuse de VINGT-SEPT JOURS en silence. Deux unites jamais
publiees, vingt et une differentes : pour qui lit la forge plutot que le
depot, toute la revision n existait pas.
Le harnais est STATIQUE, zero appel reseau — cloner la forge romprait la
seule propriete qui fasse qu une preuve vaille hors de ce poste. La mesure
passe donc par un TEMOIN que make wiki-publier depose. Amorce avec la
valeur MESUREE : le wiki d eregion porte b6167f2, dont le message dit
source: ac85278.
Ce qu elle ne prouve pas : un temoin dit ce qui est PARTI, jamais ce qui
est ARRIVE.
LES TROIS SONT EPROUVEES DANS LES DEUX SENS
Douze essais negatifs, douze refus : groupe non projete, acces.yml disparu,
personne au lieu d un groupe, mecanisme invente, raison manquante, compte
revenu a cinq, septieme porte ajoutee sans toucher au compte, renvoi croise
fausse, temoin absent, temoin d un autre depot. Une garantie qu on n a jamais vu
dire non n est pas une garantie, c est une habitude.
ETAT : NON CONFORME, 58 OK, 1 echec, 1 saute.
P60 est rouge, et c est le comportement voulu : le registre a le droit de
perdre. Le retard qu elle signale est reel et anterieur a elle. Une commande le
ferme, et elle vient ensuite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
7.8 KiB
Authentification : Keycloak devant, LDAP dessous, sudo en secours
Pour qui : le mainteneur — la doctrine d'authentification, avant de brancher un service.
Directive d'architecture du 2026-08-03. Trois règles, décidées ensemble et volontairement indissociables — chacune crée le problème que la suivante résout.
- Toute authentification web passe par Keycloak. Aucun service ne tient son propre répertoire de comptes.
- LDAP est la source de vérité unique des comptes d'un tenant. Keycloak le fédère ; les protocoles qui ne parlent pas OIDC s'y lient directement.
- Chaque service garde un accès de secours, atteint par
sudosur l'hôte.
1. Pourquoi les trois vont ensemble
La chaîne est en série : service → Keycloak → LDAP. C'est ce qui donne « une identité, un
mot de passe » — et c'est aussi trois points de panne alignés. Si LDAP tombe, plus personne
n'entre nulle part, y compris pour réparer.
La règle 3 n'est donc pas une entorse au SSO : c'est ce qui rend les deux premières tenables. Sans elle, la panne d'un maillon devient une exclusion totale.
2. Portée : le web, et seulement le web
| Protocole | Chemin d'authentification | Pourquoi |
|---|---|---|
| HTTP(S) — interfaces humaines | Keycloak (OIDC natif, ou oauth2-proxy devant) |
c'est là que la directive s'applique |
| IMAP / SMTP | LDAP direct (Dovecot, Postfix) | ces protocoles ne parlent pas OIDC ; le flux est prouvé de bout en bout |
| SSH | clé publique (PasswordAuthentication no) |
pas de mot de passe du tout, donc rien à fédérer |
| PostgreSQL, Redis | secrets applicatifs en voûte | comptes de service, pas d'humains |
Ce qui est interdit partout, c'est qu'un service tienne son propre répertoire d'humains. Le chemin varie ; la source ne varie pas.
3. La posture retenue : la porte de secours n'est pas annoncée
Le compte local de chaque service existe — il ne peut pas dépendre de Keycloak, sinon il tomberait avec lui. Mais son formulaire de connexion n'est pas proposé au repos.
Ce n'est pas une préférence esthétique. Un formulaire local ouvert en permanence :
- contourne la politique de mot de passe et le MFA ;
- contourne surtout la révocation centrale — désactiver quelqu'un dans LDAP laisserait son compte local valide, sans que rien ne le signale ;
- offre une cible de bourrage d'identifiants sur
admin, en continu, pour un usage qui devrait être exceptionnel.
Le coût de la fermeture est nul depuis qu'on a tranché que sudo suffit : sudo est
le mécanisme de réouverture.
Ce que chaque service sait vraiment faire
Vérifié auprès de l'amont le 2026-08-03, puis par rendu réel des gabarits dans les deux postures — pas seulement lu.
| Service | Réglage | Effet réel | Secours par sudo |
|---|---|---|---|
| Grafana | GF_AUTH_DISABLE_LOGIN_FORM=true |
ferme le formulaire | grafana-cli admin reset-admin-password |
| Forgejo ≥ 10 | ENABLE_INTERNAL_SIGNIN=false + ENABLE_BASIC_AUTHENTICATION=false |
ferme la connexion interne et l'API en Basic | forgejo admin user change-password |
| Nextcloud | hide_login_form=true |
masque seulement | occ user:resetpassword, ou …/login?direct=1 |
Nextcloud est une exception, et il faut la dire.
hide_login_formmasque le formulaire ;…/login?direct=1y accède encore, et l'amont le documente comme voulu — c'est ainsi qu'un administrateur entre. La protection est donc de ne plus l'annoncer, pas de l'interdire. Le présenter comme équivalent à Grafana ou Forgejo donnerait un faux confort.
Le réglage s'appelle <rôle>_connexion_locale dans les trois rôles, et vaut false par
défaut. Le passer à true rouvre la porte, explicitement.
Garde de version. ENABLE_INTERNAL_SIGNIN n'existe que depuis Forgejo v10 (suivi
amont, ticket 7476 : « This option was added to Forgejo v10 »). Sur une version
antérieure il serait ignoré sans erreur — le rôle croirait avoir fermé la porte. Un
assert refuse donc connexion_locale: false sous 10.0.0 plutôt que de laisser croire.
4. La chaîne de secours
Trois maillons, aucun ne dépendant d'une porte web ouverte :
1. SSO Keycloak → LDAP ← la voie normale
2. sudo via SSH occ / grafana-cli / forgejo admin ← Keycloak ou LDAP en panne
3. console Proxmox de la VM ← SSH inaccessible
Un accès de secours qu'on ne sait pas emprunter n'existe pas : les commandes exactes sont dans le tableau ci-dessus, et les mots de passe locaux vivent dans la voûte unique de l'instance.
5. La garde : chaque rôle déclare sa position
Une directive qu'aucune garde ne vérifie finit par ne plus être vraie. Chaque rôle
serveur_* porte donc un meta/authentification.yml, et P29 le confronte à son code.
| Portée | Sens | Rôles |
|---|---|---|
web-sso |
authentification humaine web → Keycloak (mecanisme: natif ou oauth2-proxy) |
5 |
socle-identite |
est la chaîne d'identité, ne peut pas se déléguer à elle-même | keycloak, openldap |
ldap-direct |
protocole non-OIDC lié à LDAP | dovecot, postfix |
interne-sans-auth |
interface joignable dans le tenant sans authentification, non exposée | prometheus, loki |
sans-auth-humaine |
aucun point d'authentification humaine | 21 |
La preuve refuse l'oubli et le mensonge : un rôle sans déclaration, une portée
inventée, un web-sso sans accès de secours ou sans posture de formulaire, un web-sso natif sans réglage <rôle>_connexion_locale défini dans defaults, et une
déclaration que le code contredit.
Et depuis le 2026-09-07, sa sœur P58 garde les habilitations — ce que P29 ne fait
pas : elle tient les positions (qui s'authentifie comment), pas les droits (qui obtient
quoi). Voir autorisation.md §5.
Depuis le 2026-09-06, elle refuse aussi que ce tableau mente. Les chiffres et les listes
de la troisième colonne sont confrontés aux déclarations réelles. Ils avaient cessé d'être
vrais sans que rien ne le signale : la ligne sans-auth-humaine annonçait 12 rôles, il y en
avait 21 — la preuve lisait les déclarations depuis le début, mais ne regardait pas ce que
le document en disait.
Les indices doivent être nommés. Une première version cherchait les mots « ldap » et « oidc » dans le rôle. Le mot LDAP, présent dans un commentaire de
serveur_grafana, suffisait alors à valider une déclarationldap-directmensongère. La preuve exige désormais une variable du namespace du rôle (<rôle>_oidc,<rôle>_ldap) ou une URIldap://— ce qu'une phrase en prose ne produit pas.
Une valeur mérite d'être expliquée : formulaire_local: aucun. Elle distingue un service
qui n'a pas de compte local du tout — oauth2-proxy est une passerelle — de celui qui
en a un et l'a fermé. Sans elle, on écrirait « fermé », ce qui laisserait croire qu'une
porte a été close alors qu'il n'y en a jamais eu.
Les lacunes sont comptées, pas masquées
interne-sans-auth ne fait pas échouer la preuve : c'est une lacune assumée et nommée.
Mais elle est comptée à chaque exécution, donc visible dans chaque rapport. La refuser
bloquerait le harnais sur une décision déjà prise ; la taire la ferait oublier.
Prometheus et Loki sont dans ce cas. À noter, contre une idée reçue de la veille : ils ne
sont pas exposés publiquement — aucun expose au plan. Seuls six groupes le sont
(collabora, forgejo, grafana, keycloak, nextcloud, oauth2-proxy). Le risque est donc
intra-tenant, pas frontalier — plus petit qu'annoncé, réel quand même. oauth2-proxy est
déjà éprouvé devant Icinga Web 2 : le patron existe, il reste à l'appliquer.