Publication du wiki depuis le depot (source: c9d31e9)

Daniel Allaire 2026-09-08 12:47:26 -04:00
parent b6167f2cf4
commit 4da68c7ecd
23 changed files with 923 additions and 123 deletions

@ -41,9 +41,18 @@ Déclaratif dans Set-OPS : `serveur_keycloak_realm_roles`, `_role_mapper_clients
**zéro coupure SSO**. Une identité (`testmail`), et c'est **le rôle** — pas la connexion — qui décide
d'Explore.
> **Raffinement souverain** : ici le rôle est assigné *explicitement* à testmail. L'idéal est de le
> piloter par un **groupe d'annuaire** (LDAP → Keycloak → claim), pour que *l'appartenance* gouverne
> l'autorisation. C'est le vrai « l'annuaire gouverne l'accès ».
> **Ce « raffinement » est construit depuis le 2026-08-08.** Cette unité le présentait comme
> un idéal à atteindre ; c'est le mécanisme en vigueur. Les **groupes LDAP** sont projetés
> dans Keycloak et émis en claim (`roles/serveur_keycloak/tasks/groupes-ldap.yml` et
> `claim-groupes.yml`), et chaque service déclare le groupe qu'il reconnaît dans son
> `meta/acces.yml` — `sysadmin ⇒ Admin`, `personnel ⇒ Viewer` chez Grafana. C'est donc bien
> **l'appartenance** qui gouverne, pas une assignation nominative.
>
> La règle qui va avec : **un service nomme un groupe, jamais une personne**. Nommer
> quelqu'un 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. L'assignation explicite à `testmail` de l'exemple
> ci-dessus reste utile pour *comprendre* la mécanique du claim ; ce n'est pas la façon dont
> on donne un accès. Voir `docs/autorisation.md` §5.
---

@ -32,7 +32,10 @@ Registre ──> PostgreSQL crée la base + le compte propriétaire
Le **secret** vit dans la **voûte** (Ansible Vault), déréférencé **au déploiement**, jamais en clair
dans le plan. C'est une **liaison** *app → base*, de modalité **requise** (Keycloak sans sa base ne
démarre pas). Consommateurs prouvés : Keycloak, Forgejo, IcingaDB.
démarre pas). Le registre porte aujourd'hui **quatre bases** — `keycloak`, `forgejo`,
`icingadb`, `nextcloud` — et **cinq rôles** incluent `resoudre_base` pour bâtir leur
connexion : `serveur_keycloak`, `serveur_forgejo`, `serveur_icinga`, `serveur_icingaweb2`,
`serveur_nextcloud`.
---

@ -54,10 +54,13 @@ Tu as appris **MTA/MDA/MUA, SMTP vs soumission, IMAP, la déliverabilité** —
1. **Envoie via la soumission `:587`** (client authentifié) :
```bash
swaks --server edge-mta-01.lab.chezlepro.internal:587 --tls \
--auth LOGIN --auth-user testmail@lab.chezlepro.internal --auth-password MotDePasseTest123 \
--from testmail@lab.chezlepro.internal --to testmail@lab.chezlepro.internal --header 'Subject: essai'
swaks --server edge-mta-01.chezlepro.internal:587 --tls \
--auth LOGIN --auth-user testmail@chezlepro.internal --auth-password "$MDP_ESSAI" \
--from testmail@chezlepro.internal --to testmail@chezlepro.internal --header 'Subject: essai'
```
*(`MDP_ESSAI` se saisit à la main — `read -rs MDP_ESSAI`. Un mot de passe écrit ici
partirait dans l'historique du shell et dans le dépôt public : ce document en portait un
en clair jusqu'au 2026-09-06.)*
`235 Authentication successful` puis `250 queued` = ① en action.
2. **Lis la boîte** (le MDA) : `doveadm search -u testmail mailbox INBOX all | wc -l` sur infra-mail-01.
3. **Casse & répare.** Coupe l'annuaire (arrête OpenLDAP), renvoie un courriel : Postfix **rejette le

@ -7,12 +7,12 @@
## ① Le concept *(générique)*
Les machines se parlent par **adresses IP** ; les humains (et les configs) utilisent des **noms**.
La **résolution de noms** fait le pont : `keycloak.lab…` → `192.168.15.21`.
La **résolution de noms** fait le pont : `keycloak.chezlepro.internal` → `10.17.17.11`.
Plusieurs couches, de la plus locale à la plus globale :
- **Fichier hosts** (`/etc/hosts`) : une table statique, locale, **consultée en premier**, **sans
aucun réseau**. Increvable, mais manuelle.
- **DNS autoritatif** : le serveur qui **détient la vérité** d'une zone (ex. `lab.chezlepro.internal`)
- **DNS autoritatif** : le serveur qui **détient la vérité** d'une zone (ex. `chezlepro.internal`)
et répond pour ses noms (enregistrements **A**, **SOA**…).
- **DNS récursif (résolveur)** : celui que tes machines interrogent ; il **cherche pour toi**
(cache local, puis interne, puis Internet).
@ -28,14 +28,21 @@ chercher* la réponse pour toi »).
|---|---|---|
| **1. Le plancher** | `hosts_statiques` (socle) → `/etc/hosts` sur **chaque** nœud | résout **même DNS éteint**, dès le bootstrap. Le filet en dessous de tout. |
| **2. Autoritatif** | `serveur_powerdns` (PowerDNS) | la zone interne, les enregistrements **A**. |
| **3. Récursif local** | `client_unbound` (**opt-in**) | résolveur local : stub-zone → PowerDNS pour l'interne, récursion pour le reste. |
| **3. Récursif** | `serveur_resolveur` — **un** Unbound pour tout le tenant | récurse depuis la racine, délègue la zone souveraine à PowerDNS. |
| *(l'intégration)* | `client_resolveur` — **universelle**, sur chaque nœud | n'installe rien : écrit `/etc/resolv.conf` pour désigner le résolveur ci-dessus. |
> **Le récursif n'est plus « local », et il n'est plus optionnel.** Avant le 2026-08-24,
> `client_resolveur` posait un Unbound sur *chaque* VM — N démons identiques pour un service
> unique. Il n'installe plus rien, et son intégration est **universelle, sans aucune
> exemption** : pas même l'hôte qui porte le résolveur, qui se sert lui-même.
Le **plancher** est le cœur pédagogique : parce que chaque nœud connaît *tout l'écosystème* par
`/etc/hosts`, **rien ne dépend du DNS pour démarrer** — PowerDNS devient une *commodité*, pas un
point de défaillance unique. On construit la robustesse **de bas en haut**.
C'est aussi ce que **tu** utilises depuis ta machine : mettre `192.168.15.21 keycloak.lab…` dans
ton `/etc/hosts`, c'est exactement le même « plancher ».
C'est aussi ce que **tu** utilises depuis ta machine : mettre
`10.17.16.11 keycloak.chezlepro.internal` dans ton `/etc/hosts`, c'est exactement le même
« plancher ».
---
@ -56,23 +63,28 @@ Tu as appris **la hiérarchie de résolution** (hosts → récursif → autorita
1. **Le plancher, sans DNS.** Sur un nœud :
```bash
getent hosts keycloak.lab.chezlepro.internal # répond via /etc/hosts, zéro DNS
getent hosts keycloak.chezlepro.internal # répond via /etc/hosts, zéro DNS
grep chezlepro /etc/hosts | head
```
2. **Interroge l'autoritatif.** Demande à PowerDNS directement :
```bash
dig @infra-dns-01.lab.chezlepro.internal keycloak.lab.chezlepro.internal A +short
dig @infra-dns-01.lab.chezlepro.internal lab.chezlepro.internal SOA +short
dig @infra-dns-01.chezlepro.internal keycloak.chezlepro.internal A +short
dig @infra-dns-01.chezlepro.internal chezlepro.internal SOA +short
```
3. **Vois les couches.** Compare `getent hosts` (plancher) et `dig` (DNS) : **deux chemins**, même IP.
4. **Casse & répare.** Sur un nœud **sans** `client_unbound`, vide `/etc/hosts` de ses entrées
`chezlepro` (garde une sauvegarde !) et coupe l'accès au DNS : la résolution interne **échoue**.
Restaure `/etc/hosts` : ça remarche **sans DNS**. Tu viens de *sentir* pourquoi le plancher est le
filet de sécurité.
4. **Casse & répare.** Vide `/etc/hosts` de ses entrées `chezlepro` (garde une sauvegarde !)
**et** pointe `/etc/resolv.conf` ailleurs : la résolution interne **échoue**. Restaure
`/etc/hosts` seul : ça remarche **sans DNS**. Tu viens de *sentir* pourquoi le plancher
est le filet de sécurité.
5. **Le piège du récursif.** Demande un nom qui n'existe pas sous `internal.`, puis
redemande un nom qui existe. Si le résolveur répond NXDOMAIN aux deux, tu viens de
reproduire la panne de deux jours du 2026-09-02 : la racine étant signée, elle *prouve*
que `internal.` n'existe pas, et Unbound étend ce « non » à tout ce qui est dessous —
sans jamais interroger la stub-zone. Remède : `harden-below-nxdomain: no`.
---
## Pour aller plus loin *(dépôt)*
- Rôles : `roles/hosts_statiques` (le plancher), `roles/serveur_powerdns`, `roles/client_unbound`.
- Rôles : `roles/hosts_statiques` (le plancher), `roles/serveur_powerdns`, `roles/client_resolveur`.
- Conception des 3 couches + frontière publique : `docs/dns-interne.md`.
- Note : `client_unbound` est une **liaison optionnelle** (opt-in) — voir l'unité *Liaisons*.
- Note : `client_resolveur` est une **intégration universelle** (elle suit l'existence de `serveur_resolveur`) — voir l'unité *Liaisons* et `docs/integrations-vm.md`.

@ -0,0 +1,139 @@
# Filiation, signatures et témoins
> **Unité d'apprentissage.** Moule : ① concept → ② Set-OPS → ③ transférable → ④ à toi de jouer.
Un écosystème Set-OPS peut en **engendrer** un autre. Dès qu'il y a une descendance, trois
questions apparaissent, et aucune n'est technique au départ :
- **de qui** cet écosystème descend-il ?
- **qui** a le droit de modifier ce dont il descend ?
- comment le savoir **sans faire confiance** à celui qui héberge le code ?
---
## ① Le concept
### Git est déjà une chaîne de hachage
C'est la chose la plus utile à comprendre, et la moins connue. Chaque commit contient
l'**empreinte** de son parent. Changer une ligne d'il y a trois mois change l'empreinte de
ce commit — donc celle du suivant, et de tous les autres jusqu'à aujourd'hui.
```
A ←── B ←── C ←── D modifier B casse C et D
```
C'est un **arbre de Merkle** : la même structure de données qu'une blockchain. L'intégrité
est déjà là, gratuitement, depuis 2005.
### Ce que la chaîne ne dit pas
**Qui a écrit.** `user.name` est purement déclaratif : n'importe qui peut signer « Daniel
Allaire » et pousser. Rien ne lie ce nom à une personne.
C'est ce que corrigent les **signatures**. Avec une clé — la même clé SSH que pour se
connecter — on scelle une étiquette ou un commit. Qui possède la **clé publique** peut
vérifier ; qui ne possède pas la **clé privée** ne peut pas contrefaire.
Reste alors une question : *quelles clés ont le droit de signer ?* La réponse doit vivre
**avec le code**, pas chez l'hébergeur — sinon on remplace la confiance en une personne par
la confiance en une plateforme.
### Les témoins, ou pourquoi la fractale est le registre
Une signature prouve l'auteur, pas que l'histoire n'a pas été **remplacée**. Celui qui tient
la forge peut réécrire et re-signer.
Contre ça, un seul remède : la **multiplicité**. Si chaque écosystème enfant porte une copie
du code dont il descend, réécrire l'histoire suppose de convaincre **tous les descendants**.
C'est exactement ce qu'une blockchain achète au prix d'une machinerie considérable — et
qu'une lignée produit comme **effet secondaire de sa forme**.
> **Pourquoi pas une blockchain, alors ?** Elle résout un problème précis : *qui écrit
> ensuite, quand personne ne fait confiance à personne et qu'il y a de l'argent en jeu.*
> Ici : les membres sont connus, il n'y a pas d'argent dans la boucle, et une ancre de
> confiance existe déjà. Une chaîne publique ajouterait une dépendance à un réseau
> extérieur ; une chaîne privée, une base de données distribuée exigeant plusieurs
> opérateurs — l'inverse de « un humain doit pouvoir la faire tourner ».
---
## ② Dans Set-OPS
### Les quatre dépôts du génome
Un écosystème ne se reproduit pas depuis ses machines. Il se reproduit depuis **quatre
dépôts** — et `make genome` les nomme :
| Rôle | Ce qu'il porte |
|---|---|
| **moteur** | Set-OPS : les rôles, les preuves, le Makefile |
| **instance** | le plan du tenant, sa voûte chiffrée |
| **hébergeur** | l'`underlay.yml` du site : sa fabric physique |
| **modèles** | les offres dont les instances sont tirées |
Perdre les VM coûte du temps. Perdre ces quatre-là coûte l'écosystème.
### Le registre des signataires
`.git-allowed-signers`, **versionné dans le dépôt** :
```
allaire.dan@chezlepro.ca ssh-ed25519 AAAAC3NzaC1lZDI1...
```
Qui clone peut vérifier sans rien demander à la forge. Retirer une ligne **révoque** le
signataire pour la suite ; ce qui est déjà signé reste vérifiable — on ne réécrit pas le
passé, on cesse d'accepter l'avenir.
### La parenté, inscrite
`make genome-inscrire` écrit `parente.yml` dans l'instance : de quel moteur elle descend,
à quel **commit**, sous quelle **étiquette signée**.
```yaml
moteur:
remote: ssh://…/Set-OPS-Public.git
commit: 742bcbf4b0e1322bfa5e0dee14c94bcad6898c53
etiquette: v2026.08.21
```
Sans ce fichier, un écosystème fabriqué aujourd'hui ne sait plus, dans un an, d'où il
vient. Cinq enfants sans registre font cinq moteurs différents que plus personne ne sait
rapprocher. **C'est la mutation sans mémoire, et c'est le seul vrai risque d'une lignée.**
La preuve **P40** vérifie que la parenté est inscrite, que chaque dépôt nommé se retrouve,
que chaque commit inscrit **existe encore** — une histoire réécrite se voit là — et que
chacun porte un *remote* : un dépôt qui n'existe qu'ici n'est pas une lignée, c'est un
point unique de défaillance.
---
## ③ Transférable
Rien ici n'appartient à Set-OPS :
- **signature par clé SSH** : git 2.34+, partout, sans infrastructure ;
- **`allowed_signers`** : le mécanisme natif d'OpenSSH ;
- **journal de transparence** (Certificate Transparency, Sigstore) : la réponse mûre quand
plusieurs organisations doivent constater les mêmes faits sans se faire confiance ;
- **horodatage RFC 3161** : prouver qu'un document existait à une date.
Le jour où l'Alliance certifiera des écosystèmes, c'est un **journal de transparence**
qu'il faudra — un registre append-only, auditable par un tiers — et non une blockchain.
---
## ④ À toi de jouer
1. `make genome` — quatre dépôts ? Lequel n'a pas de *remote* ? Celui-là n'existe qu'ici.
2. `git verify-tag v2026.08.21` — que se passe-t-il si tu retires ta ligne de
`.git-allowed-signers` ? (remets-la ensuite)
3. `make genome-inscrire`, puis ouvre `parente.yml`. Dans un an, qu'est-ce que ce fichier
te dira que ta mémoire ne dira plus ?
4. Demande-toi où sont les **témoins** aujourd'hui. Combien de copies vivantes du moteur
existent, sur combien de machines distinctes ? C'est la vraie mesure de la résistance de
la lignée — pas la longueur des clés.
**Voir aussi** : [Multi-instance & fédération](Multi-instance-et-fédération),
[La preuve](La-preuve), [Sauvegardes](Sauvegardes).

@ -1,64 +1,366 @@
# Glossaire
Les concepts-clés de Set-OPS, en une phrase chacun. Les mots *en italique* renvoient à une autre
entrée. Le détail vit dans les **unités d'apprentissage** et le dépôt (`docs/`).
Tout mot que Set-OPS te met sous les yeux se trouve ici. Chaque entrée dit **ce que c'est**,
et pourquoi ce dépôt s'en sert — pas seulement sa définition.
Les mots *en italique* renvoient à une autre entrée ; les liens mènent à l'**unité
d'apprentissage** qui développe la notion.
> **Règle de ce glossaire.** Un terme employé par le dépôt et absent d'ici est un défaut.
> La preuve **P39** le vérifie. Ce qu'elle ne peut pas voir : un mot que personne n'a
> pensé à déclarer — elle garde une liste, elle ne devine pas.
---
**Adressage dérivé** — Les IP, VLAN, VMID, sous-réseaux ne sont pas saisis : ils se **calculent**
depuis le *seed*. Cf. [Le plan & l'adressage dérivé](Le-plan-et-l-adressage-dérivé).
## Le plan, et ce qui en dérive
**Binding (liaison)** — Relation déclarative entre entités du plan (app→app, app→base) résolue par
le moteur, sans coder de variables à la main. Cf. [Liaisons (bindings)](Liaisons-bindings).
**Plan** — La source unique de vérité (`instance/plan/` : serveurs, applications, bases,
domaines, nomenclature). On l'édite ; l'*inventaire* en est **généré**, jamais l'inverse.
**DIFF VIDE** — État sain où le *plan* reproduit **exactement** l'inventaire généré : la source de
vérité et l'artefact concordent. Vérifié par `make instancier`.
**Index (seed)** — Le **seul** intrant d'adressage d'une *instance*. Tout en descend :
supernet `10.<index>.0.0/16`, *VLAN* `1000+index×10+zone`, *VMID*, *VNet*. Unique dans la
*fédération*. Cf. [Le plan & l'adressage dérivé](Le-plan-et-l-adressage-dérivé).
**Fédération** — Ensemble des *instances* qui cohabitent sur une infrastructure partagée, chacune
avec son *index* unique. Cf. [Multi-instance & fédération](Multi-instance-et-fédération).
**Adressage dérivé** — Les adresses ne sont pas saisies, elles se **calculent**. Deux
valeurs ne peuvent donc pas se contredire : il n'y en a qu'une, et le reste en découle.
**`federe: false`** — Drapeau marquant un *bac à sable* local, **exclu** du réseau convergé (il ne
provisionne pas ses VLAN sur les switches de production).
**Nomenclature** — Le registre du modèle réseau : *zones*, placement des fonctions, et
l'*index*. Ne contient **aucune** adresse (preuve P20).
**Hôte fantôme** — Erreur de plan : une application posée sur un hôte **non déclaré** dans les
serveurs. Refusé par la preuve **P06** et par la création de modèle.
**Zone** — Un domaine de sécurité : un `/24` et un *VLAN*. Frontière, Identité, Données,
Services-infra, Observabilité, Applications. Un pare-feu les sépare.
**Idempotence** — Rejouer la même description N fois donne le même résultat (`changed=0` après la
1ʳᵉ fois). Cf. [Infra as Code & idempotence](Infra-as-Code-et-idempotence).
**Supernet** — Le grand bloc d'adresses d'un tenant (`10.29.0.0/16`), dont ses *zones*
découpent des tranches.
**Index (seed)** — Le **seul** intrant d'adressage d'une instance. Détermine supernet
(`10.(10+index)`), VLAN (`1000+index×10+zone`), VMID. Unique par instance fédérée.
**CIDR** — La notation `10.29.16.0/24` : une adresse, puis le nombre de bits **figés**.
`/24` fige les trois premiers nombres — 254 machines possibles.
**Instance** — Un écosystème réel (un *tenant*) : `../OPS-<nom>`, avec ses valeurs concrètes
(domaine, *voûte*, index). L'active est celle que pointe le symlink `instance/`.
**DIFF VIDE** — État sain : le *plan* reproduit **exactement** l'*inventaire* appliqué.
Vérifié par `make instancier`.
**ip-miroir** — Schéma de VMID à 9 chiffres où le numéro **contient** l'IP et le tenant
(`VLAN·octet-hôte·séquence`) — lisible d'un coup d'œil.
**Instance** — Un écosystème réel (`../OPS-<nom>`) : son domaine, son *index*, sa *voûte*.
L'active est celle que pointe le lien `instance/`.
**Modèle** — Un *plan* **générique** réutilisable (valeurs *placeholder*, sans *voûte*), dans le
dépôt privé `Set-OPS-Modeles`. Le socle public en est la preuve libre. On en crée une instance
(`instance-creer`) ; on en fabrique un (`model-creer`).
**Tenant** — Le même objet, vu depuis la *fédération* : un écosystème isolé parmi d'autres.
**Nomenclature** — Le registre du **modèle réseau** : zones, placement des fonctions, et le *seed*
`index`. Ne contient **aucun** adressage (il en dérive — preuve P20).
**Fédération** — L'ensemble des *instances* qui cohabitent sur une infrastructure
partagée, chacune avec son *index*. Cf. [Multi-instance & fédération](Multi-instance-et-fédération).
**Le plancher** — La première couche de résolution de noms : `/etc/hosts` posé par le socle, avant
même que le DNS soit debout. Cf. [DNS & résolution](DNS-et-résolution).
**`federe: false`** — Drapeau qui **exclut** une instance des devis du site : ni *VLAN*, ni
règles de frontière, ni *VNet* ne lui sont réservés. Pour un bac à sable local, ou pour un
écosystème encore à l'état de plan.
**Plan** — La source unique de vérité (`instance/plan/` : serveurs, applications, bases, domaines,
nomenclature). On l'édite ; l'inventaire en est **généré**, jamais l'inverse.
**Modèle** — Un *plan* générique réutilisable, sans *voûte*. Le dépôt public en fournit
**un** (`exemples/modeles/socle`) ; les modèles assemblés par offre — `forge`, `identite`,
`collaboration`… — vivent dans le dépôt privé `Set-OPS-modeles`.
**Preuve (Pxx)** — Une vérification rejouable du harnais `make prouver` qui garde une classe
**Socle** — Le modèle minimal public, **quatre VM** : DNS interne (PowerDNS), PKI
(step-ca), edge TLS (nginx) et **magasin courriel** (Dovecot). C'est un magasin de boîtes,
pas un relais : le MTA s'ajoute ensuite.
**Liaison (binding)** — Relation déclarée entre entités du plan (app→base, app→app),
résolue par le moteur. Cf. [Liaisons (bindings)](Liaisons-bindings).
**Hôte fantôme** — Erreur de plan : une application posée sur un hôte non déclaré. Refusée
par la preuve P06.
---
## Les machines
**VM** — Machine virtuelle : un ordinateur complet simulé par un *hyperviseur*.
**Hyperviseur** — La machine physique qui fait tourner les *VM*. Ici : Proxmox.
**Gabarit (template)** — Une VM **figée** qui sert de moule. Chaque machine de la flotte
en est un clone. Le gabarit doré de Set-OPS s'appelle `modeleSetOPS`.
**Clone** — La copie du *gabarit* qui devient une machine réelle. Deux minutes, contre une
installation complète.
**VMID** — Le numéro d'une VM sur le cluster. Set-OPS le **dérive** (schéma *ip-miroir*) au
lieu de le tirer au hasard.
**ip-miroir** — Schéma où le *VMID* **contient** l'adresse IP et le tenant : le numéro se
lit d'un coup d'œil.
**cloud-init** — Le mécanisme qui configure une VM **à sa toute première seconde** :
adresse IP, nom, clé SSH. Sans lui, un clone naîtrait identique à son moule, jumeau de
tous les autres.
**Snapshot** — Photo d'une VM à un instant. Utile pour revenir en arrière ; ce n'est
**pas** une sauvegarde (elle vit sur le même stockage).
**systemd** — Le chef d'orchestre des services d'une machine Linux : il démarre, arrête et
surveille. « Un service » veut dire « une unité systemd ».
---
## Ansible : décrire au lieu d'exécuter
**Ansible** — L'outil qui applique une **description** à des machines, par SSH. Cf.
[Infra as Code & idempotence](Infra-as-Code-et-idempotence).
**Playbook** — Un fichier qui dit **quoi appliquer, à qui**. Ici : un par groupe
(`playbooks/groupes/<groupe>.yml`).
**Rôle** — Le paquet qui sait installer et configurer **une** chose (`serveur_nginx`,
`client_backup`). C'est l'unité réutilisable.
**Inventaire** — La liste des machines et de leurs groupes (`hosts.yml`). **Généré** depuis
le plan : on ne l'édite jamais à la main.
**group_vars** — Les valeurs d'un groupe de machines, rangées par fichier. C'est là que
vivent les *intrants* non secrets.
**Handler** — Une action qui ne se déclenche **que si** quelque chose a changé — typiquement
recharger un service quand sa configuration a bougé.
**Idempotence** — Rejouer la même description dix fois donne le même résultat. La deuxième
exécution ne change rien (`changed=0`). C'est ce qui rend une reconstruction sûre.
**Voûte (vault)** — Le fichier **chiffré** des secrets d'une instance. Jamais dans un
modèle, jamais en clair. Seul le gabarit `vault.yml.example` — des **noms**, pas des
valeurs — est versionné.
---
## Le réseau
→ Unité complète : [Le réseau des tenants](Le-réseau-des-tenants)
**Underlay** — Le réseau **physique** : câbles, commutateurs, adresses des hyperviseurs. Il
ne connaît aucun tenant.
**Overlay** — Les réseaux **virtuels** des tenants, transportés par-dessus l'*underlay*.
**VLAN** — Une étiquette posée sur chaque trame pour séparer des réseaux qui partagent un
câble. Limite : 4094 étiquettes, et chaque commutateur doit les connaître.
**VXLAN** — L'encapsulation : la trame d'un tenant voyage **dans une enveloppe**. Le réseau
physique ne voit que des colis. L'enveloppe coûte **50 octets** — d'où le *MTU* 1450.
**EVPN** — Le mécanisme par lequel les hyperviseurs s'annoncent les machines qu'ils
hébergent. Sans lui, la table des destinations serait tenue à la main.
**SDN** — *Software-Defined Networking* : le réseau se **décrit** et se pose par API, au
lieu de se câbler à la main.
**VNet** — Le réseau virtuel d'**une zone** d'un tenant (`t29fron`) : le point où la carte
d'une VM se branche.
**VRF** — Une table de routage **étanche**. Dans celle du tenant A, les réseaux du tenant B
n'existent pas. Ce n'est pas une interdiction, c'est une ignorance.
**Strophe (FRR)** — Le bloc de configuration qu'on pose par tenant dans
`/etc/frr/frr.conf.local`, sur chaque hyperviseur : sa **sortie** et son **puits**.
**FRR** — Le démon de routage des hyperviseurs (*FRRouting*).
**`nexthop-vrf`** — Emprunter **une seule** adresse à une autre table de routage, au lieu
d'importer celle-ci en entier. La différence entre une porte et un mur abattu.
**Blackhole (puits)** — Une route qui **absorbe** le trafic vers les adresses non
attribuées d'un tenant, pour qu'il ne parte pas errer ailleurs.
**MTU** — La plus grosse trame qu'un lien accepte. 1500 par défaut ; 1450 dans l'*overlay*
(l'enveloppe VXLAN prend 50). Se tromper suspend les grosses réponses sans casser les
petites — la panne la plus déroutante du domaine.
**SVI** — L'adresse de passerelle portée par un **commutateur** pour un VLAN. Set-OPS ne
s'en sert plus : le routage des tenants vit sur l'hyperviseur (*VRF*).
**Pont (bridge)** — Le commutateur **virtuel** d'un hyperviseur (`vmbr1`). Les VM de la
flotte se branchent sur leur *VNet*, pas sur un pont nu.
**IPAM** — Le registre qui attribue les adresses. Ici, c'est la **dérivation** : personne
ne tient de liste.
**Spanning-tree (STP)** — Le protocole qui empêche une boucle de commutateurs de saturer le
réseau. Il élit une racine ; c'est ce que désigne `routeur` dans l'*underlay*.
**MLAG** — Deux commutateurs qui se font passer pour un seul. Set-OPS n'en a pas — d'où
les chemins doublés ailleurs (MPIO côté stockage, `active-backup` côté flotte). *(Cette
entrée concluait « d'où un seul commutateur qui route » : plus aucun ne route. Le routage
des tenants vit sur l'hyperviseur, cf. l'entrée* SVI *ci-dessus.)*
**nftables** — Le pare-feu **de chaque machine** Linux. Set-OPS le dérive du registre des
flux.
**Policy drop** — Politique par défaut : *tout ce qui n'est pas autorisé est refusé*. Sans
règles chargées, elle mure la machine — d'où l'importance de `make flux`.
**Frontière** — Le pare-feu de bordure (OPNsense), entre l'écosystème et l'extérieur. Hors
flotte Ansible : piloté par API.
**FQDN** — Le nom complet d'une machine, `forge.genese.internal` — pas seulement `forge`.
---
## Les noms
→ Unité complète : [DNS & résolution](DNS-et-résolution)
**DNS** — L'annuaire qui traduit un nom en adresse.
**Le plancher** — La première couche de résolution : `/etc/hosts`, posé par le socle
**avant** que le DNS existe. Sans lui, rien ne peut s'amorcer.
**Résolveur** — Le service qui pose les questions au DNS pour une machine (ici Unbound,
local à chaque hôte).
**Reverse proxy** — Le portier : il reçoit toutes les requêtes web et les distribue au bon
service derrière. Cf. [Reverse-proxy & TLS](Reverse-proxy-et-TLS).
**vhost** — La configuration d'**un site** dans le *reverse proxy* : quel nom, vers quel
service.
**WAF** — Filtre qui inspecte les requêtes web et bloque les attaques connues.
**Edge** — La machine de bordure qui publie les services web (ici nginx).
---
## La confiance
→ Unité complète : [PKI & confiance](PKI-et-confiance)
**PKI** — L'ensemble qui fabrique et gère les certificats.
**CA (autorité de certification)** — Celle qui **signe** les certificats. Ici, `step-ca`,
interne : l'écosystème est sa propre autorité.
**Certificat** — Une pièce d'identité pour une machine : ce nom, cette clé, signé par la
*CA*.
**ACME** — Le protocole qui **automatise** la demande et le renouvellement d'un certificat.
Personne ne le fait à la main.
**SAN** — Les noms qu'un certificat couvre. Set-OPS les **dérive** du plan.
**TLS** — Le chiffrement d'une connexion (le « s » de https).
**mTLS** — TLS **des deux côtés** : le client prouve aussi son identité. C'est le
zéro-confiance entre serveurs.
---
## L'identité
→ Unité complète : [Identité & SSO](Identité-et-SSO)
**LDAP** — L'annuaire des personnes et des groupes : la source de vérité des identités.
**LDAPS** — LDAP chiffré.
**SSO** — *Single Sign-On* : une seule authentification pour toutes les applications.
**OIDC** — Le protocole moderne du *SSO* sur le web. C'est lui derrière « Se connecter
avec… ».
**SAML** — L'ancêtre d'*OIDC*, encore répandu en entreprise.
**Realm** — Le « royaume » d'un serveur d'identité : un espace de comptes, de groupes et de
règles. Un par écosystème.
**RBAC** — Donner des droits à des **rôles**, pas à des personnes. Cf.
[Autorisation & RBAC](Autorisation-et-RBAC).
---
## Le courriel
→ Unité complète : [Courriel (SMTP/IMAP)](Courriel)
**SMTP** — Le protocole qui **transporte** un courriel d'un serveur à l'autre.
**MTA** — Le serveur qui fait ce transport (ici Postfix).
**IMAP** — Le protocole par lequel **tu lis** ta boîte, depuis ton téléphone ou ton client.
**LMTP** — Le dernier mètre : le MTA remet le message au serveur qui **détient** les boîtes
(ici Dovecot).
**DKIM** — La signature qui prouve qu'un courriel vient bien de ton domaine.
---
## L'état, et sa preuve
**SQLite** — Une base de données qui tient dans **un fichier**, sans serveur ni compte.
Suffisante pour une petite forge — c'est ce que portait patient 0, dont les machines
n'existent plus. À l'inverse de
*PostgreSQL*, qui est un service à part entière — un serveur, une zone, un secret.
**restic** — L'outil de sauvegarde chiffrée et dédupliquée. Cf. [Sauvegardes](Sauvegardes).
**Sauvegarde vs snapshot** — L'infrastructure se **reconstruit** depuis le code ; seul
l'**état** (annuaire, bases, courriel, forge) se sauvegarde. Cf. [Sauvegardes](Sauvegardes).
**Devis** — Une commande qui **interroge le système réel** et montre l'écart avec le plan,
sans rien écrire (`make sdn-plan`, `make placement-plan`).
**Preuve (Pxx)** — Une vérification rejouable de `make prouver` qui garde une classe
d'erreur. Cf. [La preuve](La-preuve).
**Socle** — L'infrastructure de base souveraine (DNS, PKI, edge TLS, relais courriel) — le modèle
minimal public, extensible.
**Source unique** — Le principe qui traverse tout le dépôt : une information vit à **un**
endroit, et tout le reste en dérive. Neuf copies d'une même règle ne vieillissent pas
ensemble — et la divergence ne se voit jamais de l'intérieur d'une copie.
**Tenant** — Synonyme d'*instance* dans le contexte de la *fédération* : un écosystème isolé parmi
d'autres.
**Résolution d'instance** — Comment un script sait **quel écosystème** il regarde : la
variable `SETOPS_INSTANCE`, sinon le lien `instance`. Une seule fonction y répond pour tout
le moteur (`inventory_rules.instance_courante`) ; la preuve P41 refuse qu'on en réécrive
une deuxième.
**Voûte (vault)** — Le fichier chiffré des secrets d'une instance (`vault.yml`, Ansible Vault).
**Jamais** dans un *modèle* ni versionné ; seul le gabarit `vault.yml.example` l'est.
**Harnais** — L'ensemble des preuves. Un `make prouver` vert veut dire : aucune des classes
d'erreur connues n'est présente.
**Zone** — Un domaine de sécurité (un `/24` + un VLAN) : Frontière, Identité, Données,
Services-infra, Observabilité, Applications. Un pare-feu les sépare.
**Chèque vert sur un périmètre vide** — Le piège central de ce dépôt : une vérification qui
réussit **sans rien avoir mesuré**. Une sauvegarde de zéro fichier, un devis qui lit un
intrant périmé, une preuve qui ne peut pas échouer. À chaque « ✅ », se demander **sur
quoi** il a porté.
**GUI** — La console web d'exploitation (`make inventaire-ui`). Cf.
[Le GUI](Le-GUI-console-d-exploitation).
**CI** — La vérification automatique à chaque poussée (`make ci` la rejoue à l'identique).
---
## La filiation
→ Unité complète : [Filiation, signatures & témoins](Filiation-signatures-et-témoins)
**Génome** — Les **quatre dépôts** sans lesquels un écosystème ne peut pas être refabriqué :
le *moteur*, l'*instance*, le dépôt de l'hébergeur, les *modèles*. `make genome` les nomme.
**Parenté** — Le fichier `parente.yml` d'une instance : de quel moteur elle descend, à quel
*commit*, sous quelle *étiquette signée*. Sans lui, un écosystème ne sait plus d'où il vient.
**Commit** — Un instantané du dépôt, qui porte l'**empreinte** du précédent. Modifier le
passé change toutes les empreintes suivantes : c'est ce qui rend une histoire vérifiable.
**Empreinte (hachage)** — Une courte signature calculée d'un contenu. Deux contenus
différents n'ont pas la même — c'est ce qui permet de détecter une modification.
**Arbre de Merkle** — La structure où chaque élément porte l'empreinte du précédent. Git en
est un ; une blockchain aussi. L'intégrité vient de là, pas d'une chaîne de blocs.
**Étiquette (tag)** — Un nom posé sur un état précis du dépôt (`v2026.08.21`). Signée, elle
devient un point de repère **vérifiable** : « c'est de cet état-là que je descends ».
**Signature** — Le scellé posé avec une clé privée. Qui a la clé **publique** peut vérifier ;
qui n'a pas la privée ne peut pas contrefaire. Sans elle, un nom d'auteur est déclaratif.
**`.git-allowed-signers`** — Le registre, **versionné**, des clés autorisées à signer cette
lignée. Retirer une ligne révoque pour la suite ; le passé signé reste vérifiable.
**Témoin** — Une copie vivante et indépendante du *génome*. Réécrire l'histoire suppose de
convaincre tous les témoins : c'est la lignée elle-même qui protège la lignée.
**Journal de transparence** — Un registre append-only, auditable par un tiers (Certificate
Transparency, Sigstore). La réponse mûre quand plusieurs organisations doivent constater les
mêmes faits sans se faire confiance — et l'alternative sobre à une blockchain.
**Horodatage (RFC 3161)** — Prouver qu'un document **existait** à une date, par un tiers.

@ -24,7 +24,7 @@ des **méthodes 100 % génériques**.
| Section | Contenu |
|---|---|
| **Unités d'apprentissage** | Un fondamental TIC par page, toujours selon le même moule (ci-dessous). Des *services* (identité, PKI, courriel…) **et** de la *méthode* (le plan, le multi-instance, la preuve). |
| **Opérations (runbooks)** | Procédures : ajouter un service, déployer un nœud, restaurer une sauvegarde… |
| **Opérations (runbooks)** | Les procédures ne vivent **pas** ici : elles sont dans le dépôt, `docs/runbooks-exploitation.md`. Le wiki y pointe (même règle que la référence technique, ci-dessous). |
| **[Glossaire](Glossaire)** | Les concepts-clés en une phrase (seed, bindings, le plancher, hôte fantôme, voûte…). |
| **Référence technique** | Le *détail du « comment »* vit **dans le dépôt** (`docs/`, README des rôles) — ce wiki y **pointe**, ne le **recopie pas** (pour éviter la dérive). |

@ -33,7 +33,7 @@ Trois pièces, un rôle chacun :
| Pièce | Rôle | Concept incarné |
|---|---|---|
| **OpenLDAP** (`serveur_openldap`) | l'**annuaire** — la source de vérité. `testmail` y vit. | annuaire |
| **Keycloak** (`serveur_keycloak`) | le **fournisseur d'identité** (IdP) SSO. Il **fédère** OpenLDAP (lit les comptes en LDAP, lecture seule). | SSO, OIDC, fédération |
| **Keycloak** (`serveur_keycloak`) | le **fournisseur d'identité** (IdP) SSO. Il **fédère** OpenLDAP en mode **`WRITABLE`** : il écrit *à travers*, vers l'annuaire. | SSO, OIDC, fédération |
| **oauth2-proxy** (`serveur_oauth2_proxy`) | une **passerelle** OIDC pour les apps **sans** OIDC natif. | motif *proxy d'authentification* |
Les applications se branchent de deux façons :
@ -52,6 +52,16 @@ Grafana ──> crée la session : "tu es testmail" ✔
Résultat : **une identité** (`testmail`, un seul mot de passe LDAP) ouvre **le courriel, Grafana,
Forgejo et Icinga**. Change le mot de passe une fois, il change partout.
> **Pourquoi `WRITABLE` et pas « lecture seule ».** Cette unité a écrit « lecture seule »
> jusqu'au 2026-09-06 ; ce serait plus prudent en apparence, et c'est un cul-de-sac. En
> `READ_ONLY`, un changement de mot de passe échoue sur *« Federated storage is not
> writable »* — or l'annuaire *exige* ce changement à la première connexion : la reprise du
> sysadmin butait dessus (constaté le 2026-08-07). Et `UNSYNCED` serait pire : Keycloak
> écrirait dans **sa** base, Dovecot et Postfix continueraient de valider l'ancien depuis
> LDAP — une identité, deux mots de passe, exactement ce que la doctrine interdit.
> `WRITABLE` écrit *à travers* : l'annuaire reste la source unique, Keycloak n'en est qu'un
> client.
---
## ③ Pourquoi c'est transférable
@ -74,14 +84,14 @@ Change les produits, le **schéma reste**. C'est ça, un savoir générique.
> Prérequis : accès au lab (VPN), `/etc/hosts` pointant les services sur l'edge.
1. **Vis le SSO.** Ouvre `https://grafana.lab.chezlepro.internal` → « *Se connecter avec
1. **Vis le SSO.** Ouvre `https://grafana.chezlepro.internal` → « *Se connecter avec
Chezlepro* » → `testmail`. Puis ouvre Forgejo, puis Icinga : **tu n'es reconnecté nulle part**.
2. **Observe le flux.** Rouvre Grafana en navigation privée, ouvre les outils dév (F12 → Réseau) :
repère la **redirection vers Keycloak**, puis le **retour avec un `code=`**. C'est ① en action.
3. **Interroge l'annuaire (la source de vérité).** Sur un nœud avec `ldap-utils` :
```bash
ldapsearch -x -H ldaps://id-ldap-01.lab.chezlepro.internal \
-b ou=people,dc=lab,dc=chezlepro,dc=internal '(uid=testmail)'
ldapsearch -x -H ldaps://idm-01.chezlepro.internal \
-b ou=people,dc=chezlepro,dc=internal '(uid=testmail)'
```
Tu vois l'entrée que Keycloak **fédère** — il ne l'a pas recopiée.
4. **Casse & répare (la fédération).** Dans la console admin Keycloak → *User Federation* →

@ -26,7 +26,7 @@ Trois idées la portent :
- **Le registre** : `docs/audit/affirmations.md` — chaque affirmation du dépôt (README, docs,
aide `make`, GUI) reliée à une preuve et un statut (✅/🟡/❌/⚪).
- **Le harnais** : `make prouver` rejoue les preuves automatisables (**P01–P34**) et écrit
- **Le harnais** : `make prouver` rejoue les preuves automatisables (**P01–P60**, sans trou dans la série) et écrit
`docs/audit/preuve-<date>.md`. `make verifier` les inclut : il **échoue** si une preuve échoue.
- **Chaque preuve garde une classe d'erreur.** Extrait :
@ -42,6 +42,7 @@ Trois idées la portent :
| P31 | une capacité du dépôt **non expliquée** (script muet, cible sans aide, rôle sans README) |
| P32 | un intrant qu'un rôle **exige** et que l'instance ne fournit pas |
| P33 | deux rôles co-localisés qui **revendiquent le même port** |
| P35 | une application dont le rôle **exige une base** sans entrée au plan — sinon l'écart n'apparaît qu'après quarante minutes de déploiement |
| P34 | un document qui ne **déclare pas son lecteur** — il finirait rangé par sujet, donc introuvable |
> **Ce que ces preuves ne font pas, et il faut le savoir avant de leur faire confiance.** Elles
@ -86,6 +87,50 @@ harnais de Set-OPS ».
---
## Le défaut le plus dangereux n'est pas l'erreur, c'est la **copie**
Set-OPS pilote plusieurs écosystèmes. Avant d'agir, chaque script doit donc savoir
**lequel il regarde** — par le lien `instance`, ou par la variable `SETOPS_INSTANCE`.
En août 2026, **neuf scripts avaient chacun écrit leur propre réponse** à cette question.
Trois lignes chacun. Aucune n'était fausse en soi.
Le problème n'est pas l'erreur : c'est que **neuf copies ne vieillissent pas ensemble**.
Quand on améliore l'une, les huit autres ne le savent pas. Et personne ne peut le voir,
parce que de l'intérieur d'un fichier, la copie locale a toujours l'air correcte.
### Ce que ça donnait
`make placement-plan` visait patient 0 et répondait :
```
Devis du placement — tenant « instance »
noeud asgard · stockage TrueNAS · gabarit 99998 → CONFORME
```
C'était vrai — **sur l'autre écosystème**. Sa copie lisait le lien au lieu de la variable.
Comme les deux tenants portaient les mêmes valeurs de placement, le verdict semblait
juste. C'est très exactement la circonstance où une erreur ne se voit pas.
Cinq défauts de cette famille sont sortis en cinq jours. Tous dans des outils qui
**constatent** — preuves et devis — jamais dans ceux qui agissent. C'est moins grave et
plus insidieux : un outil qui agit mal, on le voit ; un outil qui mesure mal dit
« conforme », et on passe à la suite.
### La réponse
Une **source unique** : une fonction, dans un fichier, que tous appellent. Si elle est
fausse, elle l'est partout d'un coup — donc visible, donc corrigée une fois.
Et une preuve, **P41**, qui refuse la prochaine copie. Elle en a trouvé une dixième le
jour de son écriture, dans le fichier des preuves lui-même.
> **C'est la même leçon que `proxmox-hebergeur.yml`** : les nœuds et stockages du cluster
> recopiés chez chaque tenant avaient divergé. Une source, pas N copies — pour les données
> comme pour le code.
---
## Pour aller plus loin *(dépôt)*
- Le mode d'emploi : `docs/audit/README.md`.
- Le registre : `docs/audit/affirmations.md` ; le harnais : `scripts/prouver.py`.

@ -37,8 +37,8 @@ Le **plan** (`instance/plan/`) est la source unique. Cinq registres :
| Élément | Formule (modèle 6 zones) |
|---|---|
| Supernet | `10.(10+index).0.0/16` |
| Sous-réseau de zone | `10.(10+index).(15+zone).0/24` |
| Supernet | `10.<index>.0.0/16` |
| Sous-réseau de zone | `10.<index>.(15+zone).0/24` |
| Passerelle | `…(15+zone).1` |
| VLAN sur le trunk | `1000 + index×10 + zone` |
| VMID | `VLAN·octet-hôte·séquence` (9 chiffres) |

165
Le-réseau-des-tenants.md Normal file

@ -0,0 +1,165 @@
# Le réseau des tenants — du câble au VRF
> **Unité d'apprentissage.** Moule : ① concept → ② Set-OPS → ③ transférable → ④ à toi de jouer.
C'est la partie de Set-OPS qui emploie le plus de mots obscurs — *underlay*, *VXLAN*,
*VNet*, *VRF*, *strophe FRR*. Aucun n'est là par goût du jargon : chacun répond à un
problème précis, et on peut les rencontrer dans l'ordre où ils sont apparus.
---
## ① Le concept
### Le problème de départ : deux clients sur le même fil
Deux organisations hébergées sur le même matériel ne doivent pas se voir. Or leurs
machines partagent des câbles, des commutateurs, des hyperviseurs. Il faut donc
**séparer ce qui est physiquement mélangé**.
### Première réponse : le VLAN
Un **VLAN** colle une étiquette (un nombre) sur chaque trame. Deux machines n'échangent
que si leurs étiquettes correspondent. C'est simple, ça marche, et c'est vieux de trente
ans.
Deux limites :
- l'étiquette tient sur 12 bits — **4094 VLAN**, pas un de plus ;
- chaque commutateur du chemin doit connaître chaque VLAN. Ajouter un client, c'est
toucher à la configuration de tout le monde.
### Deuxième réponse : l'encapsulation
**VXLAN** prend la trame d'un tenant, la met **dans une enveloppe** et l'expédie comme
un colis ordinaire d'un hyperviseur à l'autre. Le réseau physique ne voit passer que des
colis — il n'a plus besoin de connaître les clients.
Deux vocabulaires en découlent, et c'est la distinction la plus utile de cette page :
| | |
|---|---|
| **underlay** | le réseau **physique** : les câbles, les commutateurs, les adresses des hyperviseurs. Il ne connaît aucun tenant. |
| **overlay** | les réseaux **virtuels** des tenants, transportés dans des enveloppes par-dessus l'underlay. |
L'enveloppe coûte **50 octets**. C'est toute l'explication du `mtu_overlay: 1450` :
1450 + 50 = 1500, la taille standard d'une trame. Se tromper là ne casse rien
franchement — les petites requêtes passent, les grosses réponses restent suspendues.
C'est la panne la plus déroutante du domaine.
### Qui distribue les enveloppes : EVPN
Pour expédier un colis, il faut savoir **où** l'envoyer. **EVPN** est le mécanisme par
lequel les hyperviseurs s'annoncent mutuellement les machines qu'ils hébergent. Sans
lui, il faudrait tenir cette table à la main.
### Le VRF : une table de routage étanche
Un routeur ordinaire a **une** table de routage. Un **VRF** lui en donne plusieurs,
hermétiques : dans la table du tenant A, les réseaux du tenant B **n'existent pas**. Ce
n'est pas une règle de pare-feu qu'on pourrait oublier — c'est une ignorance
structurelle.
C'est la différence entre *« je t'interdis d'y aller »* et *« la route n'existe pas »*.
---
## ② Dans Set-OPS
### Ce que tu écris, et ce qui se calcule
Tu écris **un nombre** : `index`. Tout le reste en découle.
```
index: 29
↓
supernet 10.29.0.0/16
VLAN 1000 + 29×10 + zone → 1291, 1292, 1293…
zone SDN t29
VNet t29fron, t29serv, t29donn, t29appl
sous-réseau 10.29.16.0/24, 10.29.17.0/24 …
```
Un **VNet** est le réseau virtuel d'**une zone** d'un tenant : le point où les cartes
réseau des VM se branchent. Une VM de la zone Frontière du tenant 29 se branche sur
`t29fron`, et nulle part ailleurs.
### Trois commandes, trois questions
| Commande | Question à laquelle elle répond |
|---|---|
| `make underlay` | mon réseau **physique** est-il cohérent, et n'empiète-t-il pas sur les tenants ? |
| `make sdn-plan` | ce que le cluster porte **diffère-t-il** de ce que le plan décrit ? (aucune écriture) |
| `make sdn-appliquer` | pose la différence — et **retire ce qui est périmé** |
`sdn-plan` avant `sdn-appliquer`, toujours. La seconde écrit sur le cluster et sur les
hyperviseurs ; la première ne fait que regarder.
### La strophe FRR
**FRR** est le démon de routage des hyperviseurs. Set-OPS lui pose un bloc par tenant —
ce que le dépôt appelle une **strophe** — dans `/etc/frr/frr.conf.local`, sur chaque
nœud de sortie :
```
vrf vrf_t29
ip route 0.0.0.0/0 10.0.4.1 nexthop-vrf default
ip route 10.29.0.0/16 blackhole
exit-vrf
```
**La première ligne, c'est la sortie.** Tout ce qui ne concerne pas le tenant part vers
la frontière. `nexthop-vrf default` **emprunte une seule adresse** à la table principale
au lieu de l'importer en entier : importer aurait fait entrer dans le VRF le transport
VXLAN, le plan de gestion et les VLAN hérités — et permis de contourner la frontière.
**La deuxième ligne, c'est un puits.** Elle attrape les adresses **non attribuées** du
tenant. Elle est moins précise que les `/24` des VNets, donc le trafic légitime ne la
voit jamais.
Sans ce puits (mesuré le 2026-08-09) : une adresse inexistante ne trouvait aucune route
locale, sortait par le défaut, revenait de la frontière vers l'hyperviseur, atterrissait
dans la table **principale** — et repartait vers la passerelle du réseau
d'**administration**. Deux conséquences : le trafic d'un tenant pouvait atteindre le
plan de gestion **par une faute de frappe**, et `connect()` réussissait vers n'importe
quelle adresse inexistante. Ce qu'on avait longtemps pris pour une protection de la
frontière n'était qu'une route manquante ici.
> **Le fichier ne se sauvegarde pas : il se régénère.** Il porte son avertissement en
> tête — *« NE PAS ÉDITER À LA MAIN »*. La source de vérité est le plan.
---
## ③ Transférable
Rien de tout ça n'appartient à Set-OPS. Ce sont les briques de n'importe quel réseau
multi-locataire :
- **underlay / overlay** : le vocabulaire de tous les centres de données depuis 2015 ;
- **VXLAN + EVPN** : la même paire chez tous les hébergeurs, du garage à AWS ;
- **VRF** : présent sur tout routeur professionnel, et sur Linux depuis 2016 ;
- **le puits (`blackhole`)** : une pratique standard d'anti-fuite.
Ce que Set-OPS ajoute n'est pas de la technologie, c'est une **dérivation** : ailleurs,
ces objets se saisissent à la main, un par un, dans quatre interfaces différentes. Ici,
ils descendent tous d'un seul nombre — donc ils ne peuvent pas se contredire.
---
## ④ À toi de jouer
1. **Lis ton réseau physique** : `make underlay`. Repère les VLAN sous 1000 (l'underlay)
et les MTU. Pourquoi le transport doit-il être à 1500 quand l'overlay est à 1450 ?
2. **Regarde sans écrire** : `make sdn-plan`. Si le cluster dit déjà ce que le plan dit,
la sortie tient en une ligne.
3. **Trouve la sortie d'un tenant** : dans `make devis-sdn`, repère la strophe FRR et
l'adresse du prochain saut. À quel équipement appartient-elle ?
4. **Change `index` dans un modèle** (jamais en production) et régénère : combien de
valeurs ont bougé ? C'est la mesure exacte de ce que la dérivation t'épargne.
**Pour aller plus loin** : `docs/sdn-evpn.md` dans le dépôt (référence technique),
[Le plan & l'adressage dérivé](Le-plan-et-l-adressage-dérivé),
[Multi-instance & fédération](Multi-instance-et-fédération).
> *Le lien vers `docs/` était relatif (`../docs/…`) : il fonctionne dans le dépôt et
> **casse une fois le wiki publié**, la forge ne recevant que le contenu de `wiki/`. C'est
> pourquoi le corpus cite `docs/` en **texte**, jamais en lien — une seule page dérogeait.*

@ -38,8 +38,16 @@ Les **résolveurs partagés** font le pont : `resoudre_base` (app→base) et `re
**dans le rôle**, jamais en clair.
La **modalité** structure la robustesse : une liaison **requise** absente → le moteur **refuse
d'instancier** (ex. une base sans serveur SQL). Une **optionnelle** absente → silence (ex. un nœud
sans `client_journal` marche très bien).
d'instancier** (ex. une base sans serveur SQL). Une **optionnelle** absente → silence (ex. un
nœud sans `client_backup` : il ne détient pas d'état, il n'a rien à sauvegarder).
> **Attention à une troisième catégorie, qui n'est ni l'une ni l'autre : l'universelle.**
> `client_pki`, `client_metrique`, `client_journal`, `client_resolveur` et
> `client_artefacts` ne se déclarent **pas** — tout hôte les reçoit par dérivation, et
> **P26** refuse qu'un hôte y échappe. Cette unité citait `client_journal` en exemple
> d'optionnelle jusqu'au 2026-09-06 : c'est l'inverse. Le plan portait 28 lignes qui
> disaient « oui » à quelque chose de vrai pour tous ; elles n'existaient que pour être
> oubliées, et quatre l'avaient été.
C'est **le** concept de Set-OPS : *tu déclares les liaisons, le moteur câble.*
@ -64,9 +72,11 @@ produit. C'est un **modèle mental** qui vaut de NetScaler à Kubernetes.
*app→domaine*), et `serveurs.yml` : un nœud avec `integrations:` (liaison *nœud→service*).
2. **Vois-la se résoudre.** Après `make instancier`, regarde l'inventaire généré : la cible est
devenue une **valeur concrète** (FQDN, groupe) — le moteur a câblé.
3. **Requise vs optionnelle.** Compare : retirer `client_journal` d'un nœud → aucun problème
(optionnelle). Déclarer une base **sans** serveur → `make instancier`/la validation **échoue**
(requise). Tu *sens* la différence de modalité.
3. **Requise, optionnelle, universelle.** Compare les trois : retirer `client_backup` d'un
nœud sans état → aucun problème (**optionnelle**). Déclarer une base **sans** serveur →
`make instancier` **échoue** (**requise**). Essayer de recopier `client_metrique` dans
`serveurs.yml` → le plan **refuse** (**universelle** : elle est dérivée, la recopier
créerait une seconde source qui finirait par diverger).
4. **Casse & répare.** Casse une liaison requise (ex. réfère une base à un serveur inexistant),
relance l'instanciation : **échec clair** *avant* tout déploiement. Corrige : ça passe. Le moteur
attrape le câblage manquant à ta place.

@ -30,7 +30,10 @@ rien à inscrire nulle part. Déposer une instance à côté des autres suffit.
**Le seed garantit l'unicité.** Chaque tenant a un `index` distinct → adressage dérivé sans
chevauchement possible : VLAN `1000+index×10+zone` (les VLAN de deux index différents ne peuvent
*mathématiquement* pas se croiser). Plafond : **245** écosystèmes fédérés (borne IPv4).
*mathématiquement* pas se croiser). Plafond : le **2ᵉ octet IPv4**, soit un index de **0 à
255** — en évitant 0, où vivent les réseaux de service du site. *(Cette unité annonçait 245,
un reste de l'époque où l'adressage valait `10.<10+index>` ; ce décalage a été retiré le
2026-08-12, justement pour qu'on lise l'index directement dans l'adresse.)*
**Gérer la flotte :**
- `make instances` — la vue d'ensemble : qui existe, l'active (★), index, VLAN, **collision** ;
@ -91,15 +94,23 @@ d'aucun `index`. On la décrit dans `underlay.yml`, qui vit dans le dépôt de *
plan par `instance/`. Ce lien ne suit pas `make instance-utiliser` : la fabric reste celle de
l'hébergeur, quel que soit le tenant actif. Gabarit : `underlay.yml.example`.
| Underlay | VLAN | Sous-réseau | Fabric |
|---|---|---|---|
| management (commutateurs, Proxmox, OOB) | 10 | `10.0.0.0/24` | principale |
| **transit** vers la frontière | 40 | `10.0.4.0/29` | principale |
| stockage / iSCSI | 20 | `10.0.1.0/24` | stockage |
| ceph-public | 30 | `10.0.2.0/24` | stockage |
| ceph-cluster | 31 | `10.0.3.0/24` | stockage |
> **Ne pas recopier de table ici.** Cette unité en portait une, figée, et **aucune de ses
> cinq lignes n'était encore vraie** après la bascule d'adressage de la fabric (D-77/D-78,
> terminée le 2026-08-22). Un underlay n'est pas un modèle : c'est la description d'un
> matériel particulier, propre à un hébergeur. On le **demande** :
>
> ```bash
> make underlay # affiche l'underlay monté, et le valide
> ```
>
> Ce qu'on y trouve chez l'hébergeur de référence, à titre d'illustration seulement : un
> plan d'**administration** sans VLAN (segment physique, `10.17.0.0/24`), le **contrôle de
> la grappe** à part (`vmbr0`, `192.168.11.0/24`), un **transit** vers la frontière, un
> réseau de **transport VXLAN**, trois réseaux de **stockage** sur leur propre fabric en
> MTU 9000, et les **zones du site lui-même** — l'hébergeur est aussi un exploitant, ses
> machines vivent là.
Deux choses à retenir de cette table.
Deux choses à retenir, qui ne dépendent d'aucune table.
**Les fabrics.** Tous les réseaux ne partagent pas les mêmes câbles. Le stockage jumbo peut
vivre sur ses propres commutateurs ; le devis de l'une ne déclare alors rien de l'autre, et le
@ -116,13 +127,19 @@ route vers tous les tenants par ce même saut, il ne peut donc dériver d'aucun
> elle n'est joignable. On cherche une règle de pare-feu ; c'est une route manquante à l'autre
> bout.
En mode `sdn`, ces réseaux transportent du VXLAN : leur **MTU doit atteindre 1550** au minimum,
sinon le ping passe et les transferts échouent. `make underlay` le refuse.
En mode `sdn`, le transport porte du VXLAN : le **MTU des réseaux de la fabric du routeur**
doit valoir l'overlay déclaré **plus 50 octets** d'encapsulation. `make underlay` le
**refuse** en dessous, et le dit — sous ce seuil, le ping passe et les transferts échouent,
la panne la plus coûteuse à diagnostiquer de cette couche.
`make underlay` l'affiche et le **valide** : VLAN < 1000 et sous-réseaux hors des supernets
tenant (`10.(10+index).0.0/16`) — **aucune collision possible** avec les overlays. `make
devis-reseau` en émet la config (section 0) et l'ajoute au trunk. La **preuve P23** garde la
règle ; elle est *sautée* si aucun `underlay.yml` n'est défini.
`make underlay` l'affiche et le **valide** : VLAN < 1000, et pas de chevauchement
**accidentel** avec un supernet de tenant. « Accidentel », parce qu'il en existe un
**voulu** : le plan d'administration de l'hébergeur occupe la **bande basse** du `/16` d'un
tenant (D-77). Les zones d'un tenant commencent au 3ᵉ octet 16 ; la bande 0-15 lui est
libre. Ce n'est pas une collision, mais il faut le **déclarer** (`bande_basse_de:`) — sinon
le validateur ne peut pas faire la différence, et il refuse. `make devis-reseau` en émet la
config (section 0) et l'ajoute au trunk. La **preuve P23** garde la règle ; elle est
*sautée* si aucun `underlay.yml` n'est défini.
---

@ -28,9 +28,19 @@ ou expédie des logs (*shipper*). Le serveur central agrège ; un tableau de bor
| **Loki** (`serveur_loki`) | **PUSH** | reçoit les journaux `journald` expédiés par `client_journal`. |
| **Grafana** (`serveur_grafana`) | — | tableaux de bord au-dessus des **deux** (au SSO). |
Le point-clé prouvé cette session : un serveur d'observabilité **sans agents** ne voit que
lui-même. En déployant `client_metrique`/`client_journal` (des **liaisons nœud, optionnelles**),
Grafana voit **toute la flotte**. *Serveur ≠ agent* : les deux sont nécessaires.
Le point-clé : un serveur d'observabilité **sans agents** ne voit que lui-même. Avec
`client_metrique`/`client_journal` sur les nœuds, Grafana voit **toute la flotte**.
*Serveur ≠ agent* : les deux sont nécessaires.
> **Ces deux liaisons ne sont PAS optionnelles**, contrairement à ce que cette unité a dit
> jusqu'au 2026-09-06. Elles portent `universelle: true` dans leur `meta/integration.yml` :
> tout hôte les reçoit **par dérivation**, sans qu'on écrive une ligne au plan, et **P26**
> refuse qu'un hôte y échappe. C'est exactement la leçon du paragraphe ci-dessus, tirée à
> son terme : le plan portait 28 lignes qui disaient « oui » à quelque chose de vrai pour
> tous — elles n'existaient que pour être oubliées, et quatre l'avaient été. Deux serveurs
> n'étaient alors ni supervisés ni journalisés, et **une machine non supervisée ne
> proteste pas**. Aujourd'hui on ne peut plus oublier : il faut **exempter**, et dire
> pourquoi.
---
@ -55,7 +65,7 @@ Tu as appris **métriques vs logs, pull vs push, le motif agent** — pas « Pro
```
2. **Vois les journaux** : `curl -s http://localhost:3100/loki/api/v1/label/host/values` → les nœuds
qui expédient leurs logs.
3. **Ouvre Grafana** (`https://grafana.lab.chezlepro.internal`) — métriques *et* logs au même endroit.
3. **Ouvre Grafana** (`https://grafana.chezlepro.internal`) — métriques *et* logs au même endroit.
4. **Casse & répare.** Arrête `prometheus-node-exporter` sur un nœud : dans Prometheus, sa cible passe
**`up=0`** (DOWN). Redémarre : elle repasse UP. Tu *sens* que c'est **l'agent** qui nourrit le
serveur (modèle pull).
@ -65,4 +75,4 @@ Tu as appris **métriques vs logs, pull vs push, le motif agent** — pas « Pro
## Pour aller plus loin *(dépôt)*
- Rôles : `roles/serveur_prometheus`, `roles/serveur_loki`, `roles/serveur_grafana`.
- Agents : `roles/client_metrique` (node_exporter), `roles/client_journal` (→ Loki).
- Ces agents sont des **liaisons optionnelles** : unité **Liaisons**.
- Ces agents sont des **intégrations universelles** (posées par dérivation, gardées par P26) : unités **Liaisons** et `docs/integrations-vm.md`.

@ -9,7 +9,7 @@
**Chiffrement asymétrique** : une **paire de clés** — une **privée** (secrète) et une **publique**
(partageable). Ce que l'une chiffre, l'autre le déchiffre. La privée **signe**, la publique **vérifie**.
**Un certificat** = une clé publique + une identité (« ce serveur est `id-ldap-01` »), le tout
**Un certificat** = une clé publique + une identité (« ce serveur est `idm-01` »), le tout
**signé** par une autorité. Il répond à : *« à qui est-ce que je parle, vraiment ? »*
**L'autorité de certification (AC / CA)** signe les certificats. On lui fait confiance, donc on fait
@ -29,7 +29,7 @@ confiance à toute la chaîne.
| Pièce | Rôle | Concept incarné |
|---|---|---|
| **step-ca** (`serveur_step_ca`) | l'**autorité de certification** interne (racine + intermédiaire, base des émissions). | AC, chaîne de confiance |
| **client_pki** (`client_pki`) | intégration : un nœud **obtient un certificat** de l'AC (via ACME) et **fait confiance à la racine**. | émission ACME, magasin de confiance |
| **client_pki** (`client_pki`) | intégration **universelle** : tout nœud **obtient un certificat** de l'AC (via ACME) et **fait confiance à la racine**. Elle ne se déclare pas — elle est dérivée, et **P26** refuse qu'un hôte y échappe. Seule exemption, dérivée : l'hôte qui *est* l'AC, qui ne s'enrôle pas auprès d'elle-même. | émission ACME, magasin de confiance |
Le motif : chaque service qui doit prouver son identité (LDAPS d'OpenLDAP, HTTPS de l'edge…)
reçoit un **certificat d'hôte** signé par step-ca ; la **racine** est déposée dans le **magasin de
@ -68,7 +68,7 @@ Encrypt, Vault, une AC d'entreprise : le schéma est **identique**.
`OK` = la racine valide bien le certificat du serveur. C'est ① en action.
3. **Vois-le servir en vrai.** Le LDAPS d'OpenLDAP utilise ce certificat :
```bash
openssl s_client -connect id-ldap-01.lab.chezlepro.internal:636 \
openssl s_client -connect idm-01.chezlepro.internal:636 \
-CAfile /etc/step/certs/root_ca.crt </dev/null 2>/dev/null | grep -E 'Verify return code'
```
`0 (ok)` = confiance vérifiée.
@ -92,13 +92,25 @@ fiable. Et attention à un piège vécu en vrai :
Diagnostic-réflexe : **comparer le cert servi au cert fichier**.
```bash
echo | openssl s_client -connect EDGE:443 -servername keycloak.lab… 2>/dev/null | openssl x509 -noout -enddate # SERVI
echo | openssl s_client -connect EDGE:443 -servername keycloak.chezlepro.internal 2>/dev/null | openssl x509 -noout -enddate # SERVI
openssl x509 -in /etc/step/certs/EDGE.crt -noout -enddate # FICHIER
```
Dans Set-OPS, `client_pki_reload_services` (par nœud) fait recharger les **vrais** consommateurs
après chaque renouvellement — edge→nginx, mail→postfix/dovecot, annuaire→slapd. Fix d'urgence si
ça arrive : `systemctl reload nginx` sur l'edge.
Ce diagnostic est **outillé** depuis le 2026-08-08 : `make certificats-plan` compare, pour
toute la flotte, ce que le disque porte à ce que la mémoire sert. Il a trouvé quelque chose
à sa première exécution — sur `infra-pki-01`, **l'autorité elle-même**, le certificat était
expiré depuis plus de huit heures et le renouvellement échouait toutes les quatorze minutes
sur `'step ca renew' requires the '--ca-url' flag`. Rien ne le signalait, et le harnais
était entièrement vert.
> **Ne compare pas les empreintes, regarde l'échéance de ce qui est SERVI.** Avec des certs
> de 24 h renouvelés toutes les ~14 minutes, une empreinte servie *différente* de celle sur
> disque est l'état **normal** : un contrôle par empreinte crierait en permanence, et on
> apprendrait à l'ignorer.
**Casse & répare** : sur l'edge, arrête le timer `cert-renewer@…`, laisse le cert expirer (ou
force une horloge), observe le login SSO casser (échec TLS de l'échange OIDC), puis recharge nginx
→ tout revient. Tu *sens* que la PKI ne vit que si le renouvellement **et** le rechargement tournent.

@ -47,11 +47,24 @@ Détail de ce que chaque devis vérifie — et de ce qu'il ne vérifie pas : `do
Un prérequis et trois pièges, tous à la première connexion. Le runbook complet est dans
`docs/autorisation.md` **§6** ; voici l'ordre et la raison de chaque geste.
**1. La clé de voûte.** 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 n'est possible** — ni les devis ci-dessus, ni un déploiement. C'est la première chose à
sauvegarder hors de la machine, avec les `vault.yml` de chaque instance, qui ne sont pas
versionnés.
**1. La clé de voûte — une voûte, une clé.** Chaque dépôt a **sa** clé, nommée d'après lui :
`~/.config/setops-vault-<dépôt-en-minuscules>`. Il n'y a **rien à exporter** — le `Makefile`
les rassemble seul (`scripts/voutes.py` → `ANSIBLE_VAULT_IDENTITY_LIST`). Pour voir ce que
cette machine peut ouvrir :
```
python3 scripts/voutes.py etat
```
**Sans la clé de ton écosystème, rien n'est possible** — ni les devis ci-dessus, ni un
déploiement. C'est la première chose à sortir de la machine (`make cles-exporter`, cf.
`docs/sortir-les-cles-du-poste.md`), avec les `vault.yml` de chaque instance, qui ne sont
pas versionnés.
> Ce paragraphe a désigné un `ANSIBLE_VAULT_PASSWORD_FILE` unique jusqu'au 2026-09-06. Ce
> n'est plus le mécanisme, et la raison compte : un seul mot de passe ouvrait alors *toutes*
> les voûtes de la flotte, celle de l'hébergeur comprise. Compromettre le plus petit
> locataire, c'était obtenir les secrets de tous.
**2. Le mot de passe d'amorçage, à changer avant tout le reste** (§6.1). L'annuaire refuse
toute opération tant qu'il n'est pas changé, sauf le changement lui-même. Ce n'est pas

@ -19,7 +19,7 @@ Un seul endroit gère les certificats → simple et cohérent.
## ② Comment Set-OPS le fait
`serveur_nginx` déployé sur l'**edge** (`infra-edge`) est le reverse-proxy. Le point élégant :
l'**exposition est auto-dérivée**. Déclarer `expose: [icinga.lab.chezlepro.internal]` sur une app
l'**exposition est auto-dérivée**. Déclarer `expose: [icinga.chezlepro.internal]` sur une app
génère **tout** :
```
@ -49,14 +49,14 @@ Tu as appris **le reverse-proxy, le routage par nom, la terminaison TLS** — pa
## ④ À toi de jouer
1. **Route par nom.** Deux noms, un seul edge (`192.168.15.21`) :
1. **Route par nom.** Deux noms, un seul edge (`10.17.16.11`) :
```bash
curl -sI --resolve grafana.lab.chezlepro.internal:443:192.168.15.21 \
--cacert /etc/step/certs/root_ca.crt https://grafana.lab.chezlepro.internal/ | head -1
curl -sI --resolve grafana.chezlepro.internal:443:10.17.16.11 \
--cacert /etc/step/certs/root_ca.crt https://grafana.chezlepro.internal/ | head -1
```
Change `grafana` en `forge` : même IP, **backend différent**. C'est le routage par SNI.
2. **Vois la terminaison TLS.** Le certificat présenté est celui de l'**edge** (avec les SAN des
exposés) : `openssl s_client -connect 192.168.15.21:443 -servername grafana.lab… | openssl x509 -noout -text | grep -A1 'Subject Alternative'`.
exposés) : `openssl s_client -connect 10.17.16.11:443 -servername grafana.chezlepro.internal | openssl x509 -noout -text | grep -A1 'Subject Alternative'`.
3. **Casse & répare.** Arrête le backend (ex. `systemctl stop grafana-server` sur obs-01) et rouvre
Grafana : l'edge répond **502 Bad Gateway** (le proxy est là, le service non). Redémarre : ça
remarche. Tu distingues **le proxy** de **ce qu'il sert**.

@ -32,8 +32,16 @@ Choix fondateur : **sauvegarder la donnée** (l'infra est reconstructible par le
| Pièce | Rôle |
|---|---|
| **restic** | l'outil : chiffrement côté client, déduplication, rétention. |
| **`serveur_backup`** | la **cible** hors-nœud (un dépôt restic par nœud). |
| **`client_backup`** | intégration par nœud : **jobs déclaratifs** (dump + chemins), timer quotidien, rétention. |
| **`serveur_backup`** | la **cible** hors-nœud (un dépôt restic par nœud) — depuis le 2026-09-01, chez l'**hébergeur** (`site-backup-01`), un compte Unix par écosystème. |
| **`client_backup`** | intégration par nœud : **jobs déclaratifs** (dump + chemins), timer quotidien, rétention — et, depuis le 2026-09-02, **vérification de son propre dépôt distant**. |
> **Qui vérifie a changé.** Le dépôt était le seul à voir ce qui arrivait vraiment : un
> nœud sait qu'il a *lancé* sa sauvegarde, pas qu'elle a *abouti*. C'était juste tant que
> le dépôt vivait dans l'écosystème. Depuis que les écosystèmes déposent chez leur
> **hébergeur** — qui héberge du chiffré côté client et ne peut rien juger — la
> vérification revient au seul qui détient la clé : **le nœud lui-même**. Il interroge son
> dépôt *distant*, et non le fait d'avoir lancé un timer. Une unité verte sur un dépôt vide
> est exactement ce qui a menti pendant un mois.
Ce qu'on protège, par **tiers** :
- **Tier 0 (vital)** : les **clés de la CA** step-ca (`/etc/step-ca`) — l'ancre de confiance, irremplaçable.
@ -64,12 +72,15 @@ Tu as appris **quoi sauvegarder, la règle 3-2-1, logique vs image, et surtout r
```bash
/usr/local/sbin/setops-sauvegarder.sh
```
2. **Liste les instantanés** (le dépôt vit hors-nœud) :
2. **Liste les instantanés** (le dépôt vit hors-nœud, et hors de l'écosystème) :
```bash
export RESTIC_REPOSITORY=sftp:restic@backup-01.lab.chezlepro.internal:$(hostname)
export RESTIC_PASSWORD_FILE=/etc/setops/restic.pass
# NE PAS RECOPIER UNE ADRESSE ICI : la cible est une valeur du plan, et elle a
# deja change. Le noeud la porte deja, gravee dans son propre script.
eval "$(grep -E '^export RESTIC_' /usr/local/sbin/setops-sauvegarder.sh)"
restic snapshots
```
*Le même dépôt est interrogé chaque nuit par `setops-verifier-mon-depot.sh`, qui
rapporte à Icinga : c'est le nœud, seul détenteur de la clé, qui juge.*
3. **Restaure — le vrai test.** Restaure dans un dossier temporaire et **compare** :
```bash
restic restore latest --target /tmp/rst
@ -85,4 +96,9 @@ Tu as appris **quoi sauvegarder, la règle 3-2-1, logique vs image, et surtout r
## Pour aller plus loin *(dépôt)*
- Rôles : `roles/serveur_backup`, `roles/client_backup`.
- Philosophie « donnée, pas VM » + les tiers : voir le CHANGELOG (entrée sauvegardes) et les jobs en host_vars.
- Complément 3-2-1 (offsite) : reste à faire — c'est l'**Étape B** (frontière publique).
- Le **1 hors-site** de la règle 3-2-1 : `make depot-hors-site VERS=<répertoire>`
(`playbooks/maintenance/depot-hors-site.yml`). Il emporte le dépôt du site — celui de
l'hébergeur **et** ceux des locataires, puisque c'est l'hébergeur qui a pris cette
promesse. La copie est opaque, et l'empreinte est prise **à la source** puis recalculée
sur la copie : sans cela on rentre chez soi avec un répertoire, pas avec une sauvegarde.
Manœuvre exécutée le 2026-09-05 — 233 fichiers, 95,7 Mo, empreintes identiques une à une.

@ -49,7 +49,7 @@ Tu as appris **supervision active vs observabilité passive, l'état, l'alerting
## ④ À toi de jouer
1. **Ouvre Icinga Web 2** (`https://icinga.lab.chezlepro.internal`, via le SSO) : la liste des hôtes
1. **Ouvre Icinga Web 2** (`https://icinga.chezlepro.internal`, via le SSO) : la liste des hôtes
et services **supervisés**, avec leur **état** (vert/jaune/rouge).
2. **Vois l'impact.** Menu *Business Processes* → « **Supervision Chezlepro** » : un processus qui
**agrège** des checks (load, procs, ping…) en un état roulé. C'est l'impact, pas une case.

@ -31,14 +31,28 @@ pas en option :
| Audit | `auditd` |
| Anti-force-brute SSH | `fail2ban_ssh` |
| Durcissement noyau | `sysctl_hardening` |
| Pare-feu | `nftables_baseline` (*installé et préparé, désactivé par défaut*) |
| Pare-feu | `nftables_baseline` — **règles dérivées du registre des flux**, pas écrites à la main |
| Mises à jour | `unattended_upgrades` |
| Journaux | `journald` (rétention, persistance) |
| Vidages mémoire | `core_dumps` (désactivés — un *core* peut contenir des secrets) |
| Paquets | `hardening_packages` |
| SSH | `ssh_baseline` + `ssh_hardening` (clé d'abord, `PermitRootLogin no`…) |
| Moindre privilège | compte technique `ansible` + `sudo_ansible` (pas de root direct) |
Nuance importante : le **pare-feu** est *préparé mais pas activé* dans le template — on l'active sur
un **clone/serveur final** avec des règles adaptées à son rôle (couper l'accès à l'aveugle = un
risque). Prudence par conception.
Le groupe `serveur_durci` compose **dix** de ces rôles en un seul geste ; `ssh_baseline` et
`sudo_ansible` viennent du socle `serveur_debian`, en amont.
**Le pare-feu : préparé dans le gabarit, armé sur la flotte.** `nftables_baseline` vaut
`false` par défaut *dans le rôle* — couper l'accès à l'aveugle pendant la construction d'un
gabarit serait un risque gratuit. Mais l'instance le met à `true` pour `hotes_actifs` : sur
une machine réelle, **le pare-feu tourne**.
Et ses règles ne s'écrivent pas à la main. Chaque rôle déclare dans son `meta/flux.yml` ce
qu'il écoute et ce à quoi il se connecte ; `scripts/resoudre_flux.py` en dérive le jeu de
règles, en `policy drop` par défaut. Le **même** registre alimente le pare-feu de
l'hyperviseur et la frontière OPNsense : trois couches qui ne *peuvent pas* se contredire,
parce qu'elles descendent d'une seule décision. C'est la défense en profondeur du §① prise
au mot — sans le coût habituel, qui est de tenir trois politiques cohérentes à la main.
---

@ -23,8 +23,11 @@ Idée-force : la VM devient **jetable/reconstructible**. Ce qui est précieux, c
## ② Comment Set-OPS le fait
- **Proxmox** = l'hyperviseur (cluster).
- Un **golden template** (`basiqueChezlepro`) : Debian minimal, durci, avec le compte technique
`ansible`, qemu-guest-agent, cloud-init… — préparé **une fois**.
- Un **golden template** (`modeleSetOPS`) : Debian minimal, durci, avec le compte technique
`ansible`, qemu-guest-agent, cloud-init… — préparé **une fois**. *(Cette unité l'appelait
`basiqueChezlepro` jusqu'au 2026-09-06 : c'est l'ancien nom. Le nom en vigueur est celui
qu'enregistre `make config` sous `proxmox_clone_source_nom`, et il doit correspondre au
nom réel du template dans Proxmox — sinon le clonage ne trouve pas sa source.)*
- Chaque nœud = un **clone** du template. `cloud-init` pose l'identité (hostname, IP dérivée de la
nomenclature, clé SSH).
- **Puis** Set-OPS/Ansible fait la **vraie** configuration (les rôles).

@ -45,6 +45,20 @@ make postgresql-plan le chiffrement est-il imposé, et à quels réseaux
make courriel-plan Postfix → LDAP → LMTP → Dovecot → IMAP, file d'attente comprise
```
Le même patron a ensuite été porté **sous** les services, au monde physique — mêmes pièces,
même refus d'écrire :
```
make frontiere-plan les règles de la frontière OPNsense contre leur devis
make proxmox-fw-plan le pare-feu est-ouest de l'hyperviseur contre le registre des flux
make sdn-plan la zone EVPN, ses VNets et la sortie des VRF
make underlay-plan l'underlay déclaré contre ce que le cluster porte vraiment
make placement-plan chaque VM est-elle là où le plan la met
```
*(Deux mesures voisines ne comparent à rien et ne portent donc pas le suffixe `-plan` :
`make mtu-mesurer` et `make versions-mesurer` relèvent un état, sans devis en face.)*
| Pièce | Rôle |
|---|---|
| un **playbook** (`playbooks/maintenance/devis-*.yml`) | **relève** le déclaré et le réel, dépose un JSON |
@ -86,8 +100,9 @@ l'éprouver dans les deux sens, en cassant volontairement ce qu'il surveille.
## ④ À toi de jouer
1. Lance les cinq devis sur ta flotte. Note le temps que ça prend : quelques minutes pour ce qui
demandait une journée d'enquête à la main.
1. Lance les **dix** devis sur ta flotte — les cinq de service, puis les cinq
d'infrastructure. Note le temps que ça prend : quelques minutes pour ce qui demandait une
journée d'enquête à la main.
2. **Casse quelque chose exprès** — arrête un service publié, change un port — et relance le
devis concerné. S'il ne dit rien, c'est *lui* qu'il faut réparer, pas le service.
3. Cherche, dans ton propre outillage, une vérification qui n'a **jamais** échoué. Demande-toi si

@ -13,6 +13,7 @@
- [DNS & résolution de noms](DNS-et-résolution)
*Communication*
- [Le réseau des tenants](Le-réseau-des-tenants)
- [Reverse-proxy & TLS](Reverse-proxy-et-TLS)
- [Courriel (SMTP/IMAP)](Courriel)
@ -36,6 +37,7 @@
*Flotte & preuve*
- [Multi-instance & fédération](Multi-instance-et-fédération)
- [La preuve](La-preuve)
- [Filiation, signatures & témoins](Filiation-signatures-et-témoins)
- [Vérifier le déployé](Vérifier-le-déployé)
**Opérations**