From be20ab43a979908a17a6a22e2a3b79e735503564 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Fri, 7 Aug 2026 13:27:00 -0400 Subject: [PATCH] docs : Set-OPS amorce les acces, le sysadmin gouverne (D-65 a D-67) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 56 ++++++---- docs/autorisation.md | 188 ++++++++++++++++++++------------- docs/carte-set-ops.md | 2 +- docs/decisions-architecture.md | 5 +- 4 files changed, 156 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 127db95..d9a1705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/autorisation.md b/docs/autorisation.md index b98a4c6..90bcdfb 100644 --- a/docs/autorisation.md +++ b/docs/autorisation.md @@ -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. diff --git a/docs/carte-set-ops.md b/docs/carte-set-ops.md index fa4e0f9..422c457 100644 --- a/docs/carte-set-ops.md +++ b/docs/carte-set-ops.md @@ -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é | `_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 | diff --git a/docs/decisions-architecture.md b/docs/decisions-architecture.md index 816d4cc..d25c898 100644 --- a/docs/decisions-architecture.md +++ b/docs/decisions-architecture.md @@ -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** (`_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