docs : Set-OPS amorce les acces, le sysadmin gouverne (D-65 a D-67)

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 ;
`ou=people` est vide.

D-67 contredit DELIBEREMENT la doctrine du depot : partout ailleurs un ecart est
un defaut a corriger, ici il est legitime — c'est le sysadmin qui travaille.
Set-OPS cree UN acces puis se retire. Idempotence par EXISTENCE, pas par
conformite : compte present, aucune action quel que soit son etat. Reconcilier
effacerait le compte cree la veille pour un nouvel employe.

D-65 : les groupes LDAP portent l'autorisation, Keycloak les projette. Dovecot
et Postfix ne savent pas lire un role Keycloak — un seul endroit a administrer.

D-66 : un service nomme un groupe, jamais une personne : revoquer quelqu'un ne
demande pas un deploiement.

Le §6 est un runbook de reprise. Il dit aussi ce qu'il faut regenerer pour que
la livraison soit un vrai transfert : les comptes de secours ont ete generes
pendant le deploiement, et leur auteur y a eu acces.

Sans registre de personnes, aucune donnee personnelle n'entre dans git.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-07 13:27:00 -04:00
parent 84afa6a2af
commit be20ab43a9
4 changed files with 156 additions and 95 deletions

View file

@ -2,36 +2,50 @@
## 2026-08-06 — le chemin nord-sud devient dérivable
### L'autorisation devient une doctrine (D-65, D-66)
### Set-OPS amorce les accès, le sysadmin gouverne (D-65 → D-67)
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é.
29 rôles ne le lit** — pas un `memberOf`, pas un filtre. `ou=people` est vide aussi : personne
ne peut entrer autrement que par les comptes de secours en voûte.
**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.
**La décision principale contredit délibérément la doctrine du dépôt (D-67).** Partout
ailleurs, un écart entre le déclaré et le réel est un défaut à corriger : les devis
réconcilient, les applicateurs retirent ce qui n'est plus demandé. Pour les habilitations,
**l'écart est légitime** — c'est le sysadmin qui fait son travail.
**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.
Deux régimes, et la frontière entre eux :
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.
| | Qui décide | Régime |
|---|---|---|
| quel groupe accorde quoi dans un service | Set-OPS | réconcilié, comme le reste |
| qui appartient à quel groupe | **une personne** | **amorcé une fois, jamais réconcilié** |
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.
Set-OPS crée **un** accès — celui du sysadmin — puis se retire. **Idempotence par existence,
pas par conformité** : compte absent, on le crée ; compte présent, aucune action *quel que soit
son état*. Il a pu être renommé, promu, déplacé. Un dépôt qui réconcilierait les appartenances
effacerait le compte créé la veille pour un nouvel employé — exactement la « correction » qu'un
agent zélé ferait sans y penser, d'où la nécessité de l'écrire.
**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.
**D-65 — les groupes LDAP portent l'autorisation, Keycloak les projette.** Argument mécanique :
Dovecot et Postfix ne savent pas lire un rôle Keycloak. L'y loger rendrait la moitié courriel
aveugle et imposerait au sysadmin **deux** modèles de permissions. Conséquence pratique : un
seul endroit à administrer.
**D-66 — un service nomme un groupe, jamais une personne.** C'est ce qui rend la reprise
possible : révoquer quelqu'un ne demande pas un déploiement.
**Le §6 est un runbook de reprise**, et c'est la partie utile : récupérer le mot de passe
d'amorçage, où administrer quoi, comment se rouvrir si on se ferme dehors, et ce qu'il faut
changer en priorité. Ce dernier point mérite d'être dit : les comptes de secours ont été
générés pendant le déploiement, et **l'auteur du déploiement y a eu accès**. Les régénérer
n'est pas une formalité — c'est ce qui transforme une livraison en transfert.
Effet secondaire du cadrage : sans registre de personnes, **aucune donnée personnelle n'entre
dans l'historique git**.
**Rien n'est construit.** Le rôle d'amorçage, les `meta/acces.yml` et la preuve restent à
écrire.
### Le courriel interne, et l'annuaire qui ne désignait personne

View file

@ -1,8 +1,8 @@
# Autorisation : le groupe LDAP est l'unité d'habilitation
# Accès et habilitations : Set-OPS amorce, le sysadmin gouverne
> **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.
> (`authentification.md`). Ce document dit *ce que tu peux faire* — et surtout **qui en
> décide**. La réponse n'est pas « le dépôt ».
## 1. Le constat, mesuré
@ -10,107 +10,153 @@
**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é.
Toute personne qui s'authentifie obtient donc le défaut du service — tout, ou rien. Et
aucun humain n'a de compte : `ou=people` est vide aussi. L'écosystème est déployé et
personne ne peut y entrer autrement que par les comptes de secours en voûte.
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. Deux régimes, et la frontière entre eux
## 2. La décision : LDAP porte les habilitations, pas Keycloak
C'est la décision principale, et elle **contredit délibérément la doctrine du dépôt**.
**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.
Partout ailleurs, un écart entre le déclaré et le réel est un défaut à corriger : les devis
réconcilient, les applicateurs retirent ce qui n'est plus demandé. **Ici, l'écart est
légitime** — c'est le sysadmin qui fait son travail.
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.
| | Qui décide | Régime |
|---|---|---|
| **Infrastructure** — quel groupe accorde quoi dans un service | Set-OPS | réconcilié à chaque déploiement, comme le reste |
| **Habilitations** — qui appartient à quel groupe | **une personne** | **amorcé une fois, jamais réconcilié** |
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**.
Le mapping « le groupe `sysadmin` vaut `Admin` dans Grafana » est de la configuration
d'infrastructure : il se déclare, se dérive et se corrige. Savoir **qui** est dans
`sysadmin` ne l'est pas — c'est une décision d'exploitation, prise par un humain, souvent
en urgence, et le dépôt n'a pas à la défaire au prochain `make deployer`.
| 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 |
Un dépôt qui réconcilierait les appartenances effacerait le compte créé la veille pour un
nouvel employé. C'est exactement le genre de « correction » qu'il ne faut pas faire.
## 3. Un service nomme un groupe, jamais une personne
## 3. L'amorçage est un one-shot
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.
Set-OPS crée **un** accès : celui du sysadmin, pour qu'il puisse prendre la main. Rien de
plus.
Chaque rôle déclare donc son modèle dans `meta/acces.yml` — même patron que `meta/flux.yml`
et `meta/authentification.yml` :
**Idempotence par non-intervention.** La condition n'est pas la conformité, c'est
l'**existence** : si le compte est là, on n'y touche pas — ni son mot de passe, ni ses
groupes, ni ses attributs. Il a pu être renommé, promu, déplacé ; c'est le droit de celui
qui exploite.
```
compte sysadmin absent → créé, mot de passe généré en voûte, changement forcé
compte sysadmin présent → aucune action, quel que soit son état
```
C'est la différence entre `state: present` et une réconciliation. Le second serait un
défaut ici.
**Le premier mot de passe n'est pas *le* mot de passe de quelqu'un.** Généré, déposé en
voûte, **changement forcé à la première ouverture** : c'est un jeton d'amorçage à usage
unique. Sans ce changement, l'auteur du déploiement connaîtrait le mot de passe du
sysadmin — ce qui contredit « une identité, une personne ».
## 4. Où vivent les habilitations : LDAP, pas Keycloak
**Les groupes LDAP sont la source unique.** Keycloak les *projette* en rôles pour le web ;
les services non-OIDC les lisent directement.
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 au
sysadmin de tenir deux modèles de permissions — donc de les voir diverger.
Conséquence pratique pour l'exploitant : **un seul endroit à administrer**. Ajouter
quelqu'un au groupe `sysadmin` dans LDAP lui ouvre Grafana, Forgejo, Icinga et le courriel,
sans toucher à un seul service.
## 5. Un service nomme un groupe, jamais une personne
C'est la règle qui rend la reprise possible. Chaque rôle déclare le **groupe** qu'il
reconnaît, dans son `meta/acces.yml` :
```yaml
acces:
- groupe: obs-admin # groupe LDAP, déclaré au plan
- groupe: sysadmin # groupe LDAP
accorde: Admin # ce que ça vaut DANS ce service
raison: >-
Édition des tableaux de bord et des sources de données.
Administration : sources de données, tableaux de bord, utilisateurs.
```
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.
Le champ `accorde` est **le vocabulaire du service**, pas un niveau abstrait : `Admin` chez
Grafana, `owner` chez Forgejo. 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 service qui nommerait une personne créerait une dette qu'on découvre le jour du
départ, service par service — et il faudrait un déploiement pour révoquer quelqu'un.
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.
## 6. Prendre la main — le runbook
**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.
Ce qui suit est destiné au sysadmin le jour de la livraison. **C'est la partie utile de ce
document.**
| Groupe | Qui | Ce que ça ouvre |
### 6.1 Récupérer le mot de passe d'amorçage
```
ansible-vault view instance/inventories/principal/group_vars/all/vault.yml \
| grep vault_sysadmin_amorcage
```
Il ne servira qu'une fois : la première connexion impose son changement.
> Le mot de passe de la voûte est lu depuis `ANSIBLE_VAULT_PASSWORD_FILE`
> (`~/.config/setops-vault-pass` par défaut). **Sans ce fichier, rien de ce qui suit n'est
> possible** — c'est la clé de voûte au sens propre, et la première chose à sauvegarder
> hors de la machine.
### 6.2 Où administrer quoi
| Ce que tu veux faire | Où | Comment |
|---|---|---|
| `sysadmin` | l'exploitant | administration des services, supervision |
| `personnel` | toute personne de l'organisation | courriel, nuage, espace de travail |
| `dev` | qui écrit du code | forge |
| créer/désactiver une personne | **LDAP** (`ou=people`) | via Keycloak, ou `ldapmodify` |
| donner ou retirer un accès | **LDAP** (`ou=groups`) | ajouter/retirer du groupe |
| changer ce qu'un groupe *vaut* dans un service | **le plan** (`meta/acces.yml`) | c'est de l'infrastructure : éditer, redéployer |
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.
La troisième ligne est la seule qui passe par Set-OPS. Les deux premières t'appartiennent
et **le dépôt ne les touchera plus jamais**.
## 5. Le premier mot de passe
### 6.3 Si tu te fermes dehors
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.
Les comptes locaux de chaque service existent, sont en voûte, et leur formulaire n'est pas
annoncé (`authentification.md` §3). L'accès de secours passe par `sudo` sur l'hôte :
**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 ».
```
grafana-cli admin reset-admin-password
forgejo admin user change-password
occ user:resetpassword
```
## 6. La garde
Et si LDAP lui-même est en panne, ces trois commandes fonctionnent quand même : elles ne
dépendent ni de Keycloak, ni de l'annuaire. C'est le sens de D-40.
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 :
### 6.4 Ce que tu dois changer en priorité
- 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.
1. **Le mot de passe d'amorçage** — dès la première connexion, imposé.
2. **Les comptes de secours** générés au déploiement : ils sont en voûte, et l'auteur du
déploiement y a eu accès. Les régénérer transfère réellement le contrôle.
3. **Le mot de passe de la voûte** elle-même, si le dépôt change de mains.
## 7. Ce que ce document ne couvre pas
**La gestion courante des comptes.** Créer, suspendre, réaffecter : c'est le travail du
sysadmin, pas du dépôt. Set-OPS ne fournit ni registre de personnes, ni synchronisation —
volontairement. Un registre versionné mettrait des données personnelles dans l'historique
git, de façon permanente et difficile à retirer.
**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.
service et non à une personne.
**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
**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 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.
**Rien n'est construit.** Ce document fixe la direction ; le rôle d'amorçage, les
`meta/acces.yml` et la preuve restent à écrire.

View file

@ -50,7 +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` |
| Accès & habilitations | Set-OPS **amorce** un accès sysadmin puis se retire ; les appartenances aux groupes ne sont **jamais réconciliées** — c'est une personne qui gouverne | **décidé, non construit** : `ou=people` et `ou=groups` existent et restent vides | `autorisation.md` (§6 = runbook de reprise) |
| 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,8 +79,9 @@ 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-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 aveugle et imposerait au sysadmin **deux** modèles de permissions — donc leur divergence. Un seul endroit à administrer | `autorisation.md` §4 | — |
| **D-66** | Un service nomme un **groupe**, jamais une personne | c'est ce qui rend la reprise possible : révoquer quelqu'un ne demande pas un déploiement. Nommer une personne crée une dette qu'on découvre le jour du départ, service par service | `autorisation.md` §5 | — |
| **D-67** | Les **appartenances aux groupes ne sont jamais réconciliées** : Set-OPS amorce **un** accès sysadmin, puis se retire | contredit délibérément la doctrine du dépôt, où un écart est un défaut. Ici l'écart **est** l'exploitation : réconcilier effacerait le compte créé la veille pour un nouvel employé. Idempotence par **existence**, pas par conformité | `autorisation.md` §2-3 | — |
| **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