docs : l'autorisation devient une doctrine (D-65, D-66)

L'authentification etait resolue et gardee ; l'autorisation n'existait nulle
part. `ou=groups` est cree depuis le debut et AUCUN des 29 roles ne le lit —
toute personne authentifiee obtient le defaut du service.

D-65 : les groupes LDAP portent l'autorisation, Keycloak les projette.
L'argument est mecanique : Dovecot et Postfix ne savent pas lire un role
Keycloak. L'y loger rendrait la moitie courriel aveugle et imposerait deux
modeles de permissions.

D-66 : un service nomme un GROUPE, jamais une personne. Un depart devient une
ligne au plan, sans toucher un service.

Le vocabulaire d'habilitation reste celui du service : une echelle commune
devrait etre traduite partout, et la traduction est ou l'habilitation se perd.

Rien n'est construit : registre, role, meta/acces.yml et P31 restent a ecrire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-07 13:05:48 -04:00
parent 8260f2f914
commit 84afa6a2af
4 changed files with 150 additions and 0 deletions

View file

@ -2,6 +2,37 @@
## 2026-08-06 — le chemin nord-sud devient dérivable
### L'autorisation devient une doctrine (D-65, D-66)
L'authentification était résolue et gardée (P29) ; l'**autorisation** n'existait nulle part.
Constat mesuré : `ou=groups` est créé par `serveur_openldap` depuis le début, et **aucun des
29 rôles ne le lit** — pas un `memberOf`, pas un filtre de groupe. Toute personne authentifiée
obtient le défaut du service : tout, ou rien, jamais ce qu'on avait décidé — puisqu'on n'avait
rien décidé.
**D-65 — les groupes LDAP portent l'autorisation, Keycloak les projette.** L'argument est
mécanique, pas doctrinal : Dovecot et Postfix ne savent pas lire un rôle Keycloak. L'y loger
rendrait la moitié courriel du système aveugle et imposerait deux modèles de permissions —
donc leur divergence.
**D-66 — un service nomme un groupe, jamais une personne.** C'est ce qui rend une arrivée ou
un départ instantané : une ligne au plan, aucun service touché. Nommer quelqu'un crée une dette
qu'on découvre le jour du départ, service par service.
Le vocabulaire d'habilitation reste **celui du service** (`Admin` chez Grafana, `owner` chez
Forgejo) : inventer une échelle commune obligerait à la traduire partout, et la traduction est
précisément l'endroit où une habilitation se perd.
Deux points tranchés en écrivant. Le groupe `personnel` mérite d'exister plutôt que d'être
sous-entendu — sans lui, « avoir un compte » et « avoir une boîte aux lettres » sont la même
chose, et on ne peut désactiver l'un sans l'autre. Et le mot de passe initial n'est pas *le*
mot de passe de quelqu'un : généré, en voûte, **changement forcé** à la première ouverture —
sans quoi l'exploitant connaîtrait celui de chacun.
**Rien n'est construit.** Le registre `plan/acces.yml`, le rôle qui peuple LDAP, les
`meta/acces.yml` et la preuve P31 restent à écrire.
### Le courriel interne, et l'annuaire qui ne désignait personne
`infra-mail-01` (Dovecot) et `edge-mta-01` (Postfix + rspamd) déployées — **dix-neuf

116
docs/autorisation.md Normal file
View file

@ -0,0 +1,116 @@
# Autorisation : le groupe LDAP est l'unité d'habilitation
> **Directive d'architecture du 2026-08-07.** L'authentification dit *qui tu es*
> (`authentification.md`). Ce document dit *ce que tu peux faire*. Les deux sont
> distincts, et le second n'existait pas.
## 1. Le constat, mesuré
`ou=groups` est créé par `serveur_openldap` depuis le début. Au 2026-08-07, c'est un
**conteneur vide qu'aucun service ne lit** : pas un `memberOf`, pas un filtre de groupe
dans les 29 rôles du catalogue.
Conséquence : toute personne qui s'authentifie obtient ce que le service accorde par
défaut. Selon le service, c'est *tout* ou *rien* — jamais ce qu'on a décidé, puisqu'on
n'avait rien décidé.
Ce n'est pas une négligence : l'authentification était le problème à résoudre d'abord.
Mais une chaîne d'identité sans habilitations n'est qu'une porte d'entrée.
## 2. La décision : LDAP porte les habilitations, pas Keycloak
**Les groupes LDAP sont la source unique de l'autorisation.** Keycloak les *projette* en
rôles pour le web ; les services non-OIDC les lisent directement.
L'argument n'est pas doctrinal, il est mécanique : **Dovecot et Postfix ne savent pas lire
un rôle Keycloak.** Si l'habilitation vivait dans Keycloak, la moitié courriel du système
y serait aveugle, et il faudrait tenir deux modèles de permissions — donc les voir diverger.
C'est le prolongement de ce qui était déjà décidé pour les comptes (D-39), étendu aux
habilitations : une identité, un mot de passe, **et un seul endroit qui dit ce qu'elle
peut**.
| Ce que porte LDAP | Ce qui le consomme |
|---|---|
| l'identité (`ou=people`) | Keycloak (fédération), Dovecot, Postfix |
| l'habilitation (`ou=groups`) | Keycloak → rôles OIDC ; Dovecot/Postfix directement |
## 3. Un service nomme un groupe, jamais une personne
C'est la règle qui rend une arrivée ou un départ instantané : une ligne au plan, **aucun
service touché**. Un service qui nomme quelqu'un crée une dette qu'on découvre le jour du
départ, service par service.
Chaque rôle déclare donc son modèle dans `meta/acces.yml` — même patron que `meta/flux.yml`
et `meta/authentification.yml` :
```yaml
acces:
- groupe: obs-admin # groupe LDAP, déclaré au plan
accorde: Admin # ce que ça vaut DANS ce service
raison: >-
Édition des tableaux de bord et des sources de données.
```
Le champ `accorde` est **le vocabulaire du service**, pas un niveau abstrait : `Admin`
chez Grafana, `owner` chez Forgejo, `admin` chez Nextcloud. Inventer une échelle commune
obligerait à la traduire partout, et la traduction est exactement l'endroit où une
habilitation se perd.
## 4. Les groupes se déclarent au plan
Un registre `instance/plan/acces.yml` déclare les personnes et les groupes, et le rôle qui
peuple LDAP le dérive — comme les VMID, les adresses et le supernet. Le plan reste la
seule chose qu'un opérateur édite.
**Trois ou quatre groupes, pas un modèle RBAC.** Chezlepro a un sysadmin et quatorze VM ;
une hiérarchie fine coûterait plus qu'elle ne protège. Affiner plus tard est facile,
l'inverse ne l'est pas.
| Groupe | Qui | Ce que ça ouvre |
|---|---|---|
| `sysadmin` | l'exploitant | administration des services, supervision |
| `personnel` | toute personne de l'organisation | courriel, nuage, espace de travail |
| `dev` | qui écrit du code | forge |
Le groupe `personnel` mérite d'être nommé plutôt que sous-entendu : sans lui, « avoir un
compte » et « avoir une boîte aux lettres » seraient la même chose, et on ne saurait pas
désactiver l'un sans l'autre.
## 5. Le premier mot de passe
Une personne est un **tiers** : elle choisit son mot de passe, le dépôt ne l'invente pas.
La règle du dépôt est constante — un secret se **génère** quand le dépôt en est la source,
et se **saisit** quand un tiers l'est. Mais il faut bien un secret pour la première connexion.
**Généré, déposé en voûte, changement forcé à la première ouverture.** Le secret généré
n'est donc jamais *le* mot de passe de quelqu'un : c'est un jeton d'amorçage à durée de vie
d'un usage. La distinction compte — sans le changement forcé, l'exploitant connaîtrait le
mot de passe de chacun, ce qui contredit « une identité, une personne ».
## 6. La garde
Une directive qu'aucune garde ne vérifie finit par ne plus être vraie — c'est ce que
`authentification.md` §5 a déjà appris. **P31** confronterait les deux sens :
- tout groupe nommé dans un `meta/acces.yml` **existe** au plan ;
- toute personne déclarée appartient à **au moins un** groupe ;
- aucun rôle `web-sso` n'accorde d'accès sans déclarer son modèle ;
- aucun `meta/acces.yml` ne nomme une personne.
Le dernier point est le plus important : c'est la règle du §3, et c'est celle qu'on
enfreint sans le remarquer, un service à la fois.
## 7. Ce que ce document ne couvre pas
**L'autorisation machine-à-machine.** Les comptes de service (PostgreSQL, Redis, jetons
d'API) ne sont pas des humains et ne passent pas par LDAP — ils vivent en voûte, liés à un
service et non à une personne. Les mêler aux groupes humains rendrait les deux illisibles.
**Les permissions internes à un service.** Qui peut écrire dans quel dépôt Forgejo, qui
voit quel dossier Nextcloud : ça se règle dans le service, à partir du groupe qu'il a reçu.
Set-OPS accorde l'entrée et le niveau ; il ne réimplémente pas le modèle interne de chaque
application.
**Rien n'est construit.** Ce document fixe la direction ; le registre, le rôle qui peuple
LDAP, les `meta/acces.yml` et la preuve P31 restent à écrire.

View file

@ -50,6 +50,7 @@ Ce que je re-découvre sinon. **Consulter avant de concevoir un nouveau mécanis
| Exposition → edge | app expose un FQDN public servi par un edge | `plan/domaines.yml` + `expose` (applications) | `bindings-conception.md` §4 |
| Exploitation de l'hébergeur | ses **opérations** (supervision de la fabric, sauvegarde des configs, DNS d'underlay) n'appartiennent à aucun tenant et restent **hors overlay** | décidé, **non construit** : aucun équipement d'hébergeur n'est encore dans un inventaire | `hebergeur-exploitation.md` |
| Authentification | web → Keycloak ; LDAP source unique ; secours par `sudo`, formulaire local non annoncé | `<rôle>_connexion_locale: false` (grafana, forgejo, nextcloud) ; garde de version Forgejo ≥ 10 | `authentification.md` |
| Autorisation | les **groupes LDAP** sont l'unité d'habilitation ; un service nomme un groupe, jamais une personne | **décidé, non construit** : `ou=groups` existe et reste vide, aucun rôle ne le lit | `autorisation.md` |
| SDN EVPN | ajouter un tenant implique **1 zone + 6 VNets + 6 sous-réseaux**, tous dérivés du seed | `scripts/devis_sdn.py` (`make devis-sdn`) ; nommage dérivé du tenant (`CHEZ17`, `chez174`), ≤ 8 caractères ; garde **P30** | `sdn-evpn.md` §2 |
| Pools Proxmox | un pool par tenant : les noms courts de VM sont **volontairement identiques** d'un tenant à l'autre (même fonction, même nom), et seule la console Proxmox en souffrait | `scripts/devis_proxmox_pools.py` (`make devis-proxmox-pools`) ; nom dérivé de l'`index` ; garde de collision = preuve **P28** | `decisions-architecture.md` D-37 |
| Routage | **aucun commutateur ne route** : la frontière est le seul équipement L3 ; les switches commutent | `passerelle` dit qui porte la passerelle, le SVI se dérive du rôle du porteur | `decisions-architecture.md` D-49/50 |

View file

@ -79,6 +79,8 @@ sont les seules vérifiables.
| **D-40** | L'accès de secours passe par **`sudo` sur l'hôte**, pas par un compte web permanent | `service → Keycloak → LDAP` est en série : sans secours, une panne exclut tout le monde, y compris pour réparer | `authentification.md` §4 | — |
| **D-41** | Le formulaire de connexion locale n'est **pas proposé au repos** (`<rôle>_connexion_locale: false`) | il contourne la politique de mot de passe, le MFA et surtout la **révocation centrale** ; `sudo` est le mécanisme de réouverture | `authentification.md` §3 | — |
| **D-65** | Les **groupes LDAP** portent l'autorisation ; Keycloak les *projette*, il ne les détient pas | Dovecot et Postfix ne savent pas lire un rôle Keycloak : l'y loger rendrait la moitié courriel du système aveugle, et imposerait deux modèles de permissions — donc leur divergence | `autorisation.md` §2 | P31 |
| **D-66** | Un service nomme un **groupe**, jamais une personne | une arrivée ou un départ devient une ligne au plan, sans toucher un seul service ; nommer quelqu'un crée une dette qu'on découvre le jour du départ | `autorisation.md` §3 | P31 |
| **D-42** | Chaque rôle **déclare sa position** d'authentification (`meta/authentification.yml`), gardée par une preuve | une directive qu'aucune garde ne vérifie finit par ne plus être vraie — c'était le cas des 28 lignes d'intégration recopiées | `authentification.md` §5 | P29 |
## 3. Les secrets