Set-OPS-Public/docs/authentification.md
Daniel Allaire 22ef279464 authentification : chaque rôle déclare sa position, gardé par P29
Une règle qu'aucune garde ne vérifie finit par ne plus être vraie — c'est ce qui
était arrivé aux 28 lignes d'intégration recopiées. Chaque rôle serveur_* porte
un meta/authentification.yml, confronté à son code par P29.

web-sso 5, socle-identite 2 (keycloak/openldap : ils SONT la chaîne d'identité),
ldap-direct 2, interne-sans-auth 2, sans-auth-humaine 12.

La preuve refuse l'oubli ET le mensonge. Éprouvée par sabotage sur sept cas :
déclaration supprimée, portée inventée, secours retiré, posture de formulaire
retirée, raison retirée, ldap-direct mensonger, réglage retiré des defaults.

Les deux derniers passaient dans la première version :

- le mensonge passait à cause d'un commentaire. Je cherchais le mot « ldap » dans
  le rôle, et serveur_grafana/defaults/main.yml contient « désactiver quelqu'un
  dans LDAP » : de la prose validait une déclaration fausse. La preuve exige
  maintenant un indice nommé — variable <rôle>_oidc / <rôle>_ldap, ou URI ldap://
- le réglage retiré passait parce que le gabarit citait encore la variable alors
  que plus rien ne lui donnait de valeur. La preuve lit defaults/main.yml en YAML
  et exige que la clé y soit définie, pas mentionnée.

Elle a aussi forcé une valeur : oauth2-proxy était déclaré « formulaire local
fermé » alors qu'il n'a aucun compte local. D'où formulaire_local: aucun, qui
distingue « il n'y en a jamais eu » de « il y en a un, il est fermé ».

Correction d'une note de la veille : Prometheus et Loki ne sont PAS exposés
publiquement (aucun expose au plan). Seuls six groupes le sont. Le risque est
intra-tenant, pas frontalier. Les deux lacunes sont comptées à chaque exécution,
pas masquées.

AFF-111, D-42. 29 preuves OK, ansible-lint (production) sur 375 fichiers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 17:05:43 -04:00

127 lines
7.1 KiB
Markdown

# Authentification : Keycloak devant, LDAP dessous, `sudo` en secours
> **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.
1. **Toute authentification web passe par Keycloak.** Aucun service ne tient son propre
répertoire de comptes.
2. **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.
3. **Chaque service garde un accès de secours**, atteint par `sudo` sur 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_form` **masque** le
> formulaire ; `…/login?direct=1` y 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 | 12 |
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.
> **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éclaration `ldap-direct` mensongère. La preuve exige
> désormais une **variable du namespace du rôle** (`<rôle>_oidc`, `<rôle>_ldap`) ou une URI
> `ldap://` — 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.