Set-OPS-Public/docs/audit/plan-de-recette.md
Daniel Allaire 58015f56e0 Plan de recette (P22) : le pendant manuel de make prouver
Les 78 exercices « À toi de jouer » du wiki forment un plan de tests d'acceptation.
Formalisé sans dupliquer :

- scripts/plan_recette.py + make plan-recette : GÉNÈRE docs/audit/plan-de-recette.md
  depuis les exercices du wiki. Grille auto-contenue par unité, colonnes : ce qu'on
  éprouve · le geste · type (observe/casse-répare) · Preuve auto (le Pxx extrait du
  texte -> quels gestes manuels sont AUSSI gardés par la machine). Générée -> ne peut
  pas dériver du wiki.
- Preuve P22 : plan_recette.py --verifier échoue si le fichier committé est périmé.
  Le plan de recette devient auto-gardé.
- Honnêteté de couverture assumée : « — » = manuel seul ; pas d'exhaustivité au-delà
  des exercices du wiki.

Pendant humain de make prouver (le harnais prouve le moteur P01-P21, la recette valide
l'exploitation) ; checklist du protocole-operateur-independant (« exploitable sans IA »).

Validé : 78 gestes / 19 unités, 5 doublés d'un Pxx ; P22 détecte une dérive (testé) ;
make verifier -> CONFORME 22/22 (instance cohérente).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 15:49:16 -04:00

230 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plan de recette — tests d'acceptation manuels
> **GÉNÉRÉ** depuis les exercices du wiki (`wiki/*.md`, section « ④ À toi de jouer »)
> par `scripts/plan_recette.py` — **ne pas éditer à la main** ; régénérer avec
> `make plan-recette`. La preuve **P22** échoue si ce fichier n'est plus à jour.
Ce plan est le **pendant manuel** de `make prouver` : là où le harnais prouve le
*moteur* par machine (P01P21), ces gestes valident l'*exploitation* — ce qu'un
**humain** fait, voit, casse et répare. Ensemble, ils couvrent les deux moitiés ;
le côté humain **est** la preuve de l'affirmation « exploitable sans IA » (cf.
`protocole-operateur-independant.md`).
**78 gestes** sur **19 unités** · **18** en « casse-répare »
· **5** doublés d'un garde-fou machine (colonne *Preuve auto*).
> **Honnêteté de couverture.** La colonne *Preuve auto* n'est remplie que lorsqu'une
> preuve `Pxx` de `make prouver` couvre AUSSI le geste. Un « — » signifie **manuel
> seul** : aucune machine ne le garde, seul l'œil de l'opérateur le valide. Ce plan
> ne prétend pas à l'exhaustivité au-delà des exercices présents dans le wiki.
## Autorisation & RBAC
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Sens le défaut | Avant tout rôle, un nouvel utilisateur SSO est Viewer dans Grafana : il voit le dashboard *Journaux de la flotte*, mais pas l'icône Explore. | 👁 observe | — |
| 2 | Donne Editor | testmail a le rôle grafana-editor → déconnecte/reconnecte-le (Grafana applique le rôle *à la connexion*) → Explore apparaît. | 👁 observe | — |
| 3 | Observe le claim | Dans Keycloak (console admin) → Clients → grafana → *Client scopes**Evaluate* pour testmail : le jeton contient "roles": ["grafana-editor"]. C'est ② en vrai. | 👁 observe | — |
| 4 | Casse & répare | Retire grafana-editor de testmail (ou renomme-le en grafana-viewer), reconnecte : Explore disparaît. Remets-le : il revient. Tu *sens* que c'est le rôle, pas l'identité, qui ouvre la porte. | 🔨 casse-répare | — |
*Source : [Autorisation & RBAC](Autorisation-et-RBAC) · § À toi de jouer.*
## Bases de données
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Liste les bases | (sur data-sql-01) : « commande » | 👁 observe | — |
| 2 | Vois l'isolation | Chaque app a sa base et son compte — pas de compte partagé. | 👁 observe | — |
| 3 | Suis une liaison | Ouvre plan/bases-donnees.yml : l'entrée keycloak (serveur, base, propriétaire, secret) est *exactement* ce que le rôle serveur_keycloak va lire pour se connecter. | 👁 observe | — |
| 4 | Casse & répare | Change le mot de passe d'un compte dans PostgreSQL (garde l'ancien !) sans mettre à jour la voûte : l'app ne se connecte plus. Restaure : ça repart. Tu *sens* que la connexion = *identité + secret cohérents des deux côtés*. | 🔨 casse-répare | — |
*Source : [Bases de données](Bases-de-données) · § À toi de jouer.*
## Cache
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Écris/lis avec authentification | (sur data-sql-01) : « commande » | 👁 observe | — |
| 2 | Teste le TTL | : redis-cli -a … SET court 1 EX 5 puis GET court avant/après 5 s — il expire. | 👁 observe | — |
| 3 | Vois la borne | : redis-cli -a … CONFIG GET maxmemory et … maxmemory-policy (LRU). | 👁 observe | — |
| 4 | Casse & répare | Interroge sans mot de passe : redis-cli GET essai:1 → NOAUTH (refusé). Puis vide le cache (FLUSHALL) : rien ne casse dans l'écosystème — la vraie donnée est en base. Tu *sens* qu'un cache est jetable. | 🔨 casse-répare | — |
*Source : [Cache](Cache) · § À toi de jouer.*
## Courriel (SMTP / IMAP)
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Envoie via la soumission `:587` | (client authentifié) : « commande » 235 Authentication successful puis 250 queued = ① en action. | 👁 observe | — |
| 2 | Lis la boîte | (le MDA) : doveadm search -u testmail mailbox INBOX all \| wc -l sur infra-mail-01. | 👁 observe | — |
| 3 | Casse & répare | Coupe l'annuaire (arrête OpenLDAP), renvoie un courriel : Postfix **rejette le destinataire** (il ne peut plus valider en LDAP). Rallume OpenLDAP : ça repart. Tu *sens* la dépendance requise MTA → annuaire. | 🔨 casse-répare | — |
*Source : [Courriel (SMTP / IMAP)](Courriel) · § À toi de jouer.*
## DNS & résolution de noms
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Le plancher, sans DNS | Sur un nœud : « commande » | 👁 observe | — |
| 2 | Interroge l'autoritatif | Demande à PowerDNS directement : « commande » | 👁 observe | — |
| 3 | Vois les couches | Compare getent hosts (plancher) et dig (DNS) : deux chemins, même IP. | 👁 observe | — |
| 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 planc… | 🔨 casse-répare | — |
*Source : [DNS & résolution de noms](DNS-et-résolution) · § À toi de jouer.*
## Identité & SSO
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Vis le SSO | Ouvre https://grafana.lab.chezlepro.internal → « *Se connecter avec Chezlepro* » → testmail. Puis ouvre Forgejo, puis Icinga : tu n'es reconnecté nulle part. | 👁 observe | — |
| 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. | 👁 observe | — |
| 3 | Interroge l'annuaire (la source de vérité) | Sur un nœud avec ldap-utils : « commande » Tu vois l'entrée que Keycloak fédère — il ne l'a pas recopiée. | 👁 observe | — |
| 4 | Casse & répare (la fédération) | Dans la console admin Keycloak → *User Federation* → désactive le fournisseur LDAP. Reconnecte-toi : échec (l'IdP ne voit plus l'annuaire). Réactive : ça remarche. Tu viens de *sentir* la dépendance requise entre l'IdP et l'annuaire. | 🔨 casse-répare | — |
*Source : [Identité & SSO](Identité-et-SSO) · § À toi de jouer.*
## Infra as Code & idempotence
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Sens l'idempotence | Redéploie un rôle déjà en place (ex. via la GUI ou make deployer HOTE=…) : la 2ᵉ fois, changed=0. Rien à faire = rien n'est touché. | 👁 observe | — |
| 2 | Le flux déclaratif | Édite le plan (un serveur dans la GUI), ⚙ Appliquer le plan (instancier), puis Vérifier (dry-run) : tu prévisualises avant d'appliquer. | 👁 observe | — |
| 3 | La réversibilité | git diff / git checkout sur le plan : l'état est du code, donc annulable. | 👁 observe | — |
| 4 | Casse & répare | Modifie à la main un fichier géré par un rôle (ex. un .conf), puis redéploie : Ansible rétablit l'état voulu (le code gagne sur la dérive manuelle). Tu *sens* que la source de vérité, c'est le code. | 🔨 casse-répare | — |
*Source : [Infra as Code & idempotence](Infra-as-Code-et-idempotence) · § À toi de jouer.*
## La preuve — prouver, pas affirmer
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Produis une preuve | make prouver (voûte exportée). Lis docs/audit/preuve-<date>.md : chaque preuve, son verdict, l'affirmation couverte. | 👁 observe | — |
| 2 | Fais échouer une preuve — exprès | Introduis un hôte fantôme : dans applications.yml, pointe une appli vers un hôte qui n'existe pas dans serveurs.yml. make prouver : **P06 échoue**, en nommant l'hôte. Corrige (ou via le <select> de la GUI) : vert. | 👁 observe | **P06** |
| 3 | Une autre | Remets de l'adressage dans une nomenclature (vlan: 42), make prouver : P20 échoue. Retire-le : vert. | 👁 observe | **P20** |
| 4 | Lis le registre | Ouvre docs/audit/affirmations.md : trouve une affirmation ⚪ (*non prouvable localement*) — vois comment elle est assumée comme intention, jamais présentée comme prouvée. | 👁 observe | — |
| 5 | Comprends la valeur | Demande-toi : *quelle promesse est-ce que je fais sans preuve ?* C'est exactement ce que ce registre force à regarder en face. | 👁 observe | — |
*Source : [La preuve — prouver, pas affirmer](La-preuve) · § À toi de jouer.*
## Le GUI (console d'exploitation)
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Lance-la | make inventaire-ui, ouvre l'URL affichée. Repère l'inventaire actif et l'état de la flotte (en haut), les onglets, « Appliquer le plan », « Sauvegarder ». | 👁 observe | — |
| 2 | Édite → prévisualise | Vue Serveurs, change la mémoire d'un hôte, Sauvegarder, Appliquer le plan, puis Vérifier (dry-run) : tu vois ce qui *changerait* avant d'agir. | 👁 observe | — |
| 3 | Répare un hôte fantôme (l'erreur impossible) | Si une appli pointe un hôte inexistant, ouvre la vue Applications, sélectionne-la : le <select> « Hôte » ne montre **que des hôtes réels. Choisis le bon, Sauvegarder, Appliquer**. Tu ne *peux pas* re-saisir le fantôme. | 👁 observe | — |
| 4 | Bascule d'instance | Vue Réseau, bouton « Activer » sur une autre instance. Toutes les vues suivent, sans redémarrer. | 👁 observe | — |
| 5 | Sens le garde-fou | Essaie de sauvegarder un plan incohérent (ex. une base dont le consommateur n'existe pas) : la console refuse avec une raison. L'invalide ne passe pas. | 👁 observe | — |
| 6 | Casse & répare | Édite hosts.yml à la main, reviens dans la GUI, « Appliquer le plan » : ta modification est écrasée par le plan. La source de vérité, c'est le plan — pas l'inventaire. | 🔨 casse-répare | — |
*Source : [Le GUI (console d'exploitation)](Le-GUI-console-d-exploitation) · § À toi de jouer.*
## Le plan & l'adressage dérivé
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Observe la dérivation | Vue Serveurs de la GUI : chaque carte montre VMID · IP · VLAN. Aucun n'a été saisi — tous viennent du index. Note l'IP d'un hôte. | 👁 observe | — |
| 2 | Change le seed, regarde tout suivre | Panneau Intrants → Réseau, change index (ex. de 1 à 7), « Appliquer le plan ». Toutes les IP basculent de 10.11.x à 10.17.x, les VLAN de 101x à 107x — d'un seul chiffre. Puis remets ta valeur. | 👁 observe | — |
| 3 | Sens la source unique | make instancier (diff), puis make instancier-appliquer : *« DIFF VIDE : le plan reproduit exactement l'inventaire »* — le plan est la vérité. | 👁 observe | — |
| 4 | Casse & répare | Édite hosts.yml à la main (change une IP). Relance make instancier : il signale l'écart. Ré-applique : le plan écrase ta modification. Tu *sens* que hosts.yml n'est pas la vérité — le plan l'est. | 🔨 casse-répare | — |
| 5 | Éprouve le garde-fou | Ajoute une ligne supernet: 10.99.0.0/16 dans une nomenclature, puis make prouver : P20 échoue (« adressage stocké »). Retire-la : vert. La règle se *prouve*. | 👁 observe | **P20** |
*Source : [Le plan & l'adressage dérivé](Le-plan-et-l-adressage-dérivé) · § À toi de jouer.*
## Liaisons (bindings)
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Lis une liaison | Ouvre instance/plan/applications.yml : une app avec expose: (liaison *app→domaine*), et serveurs.yml : un nœud avec integrations: (liaison *nœud→service*). | 👁 observe | — |
| 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é. | 👁 observe | — |
| 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é. | 👁 observe | — |
| 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. | 🔨 casse-répare | — |
*Source : [Liaisons (bindings)](Liaisons-bindings) · § À toi de jouer.*
## Multi-instance & fédération
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Vois la flotte | make instances : l'active (★), les index, les VLAN, le statut fédéré/local. Repère qui est en production. | 👁 observe | — |
| 2 | Bascule | GUI, vue Réseau, bouton « Activer » sur une autre instance (ou make instance-utiliser NOM=…). Toutes les vues suivent — sans redémarrer. | 👁 observe | — |
| 3 | Crée une instance | make instance-modeles (les modèles dispo), puis make instance-creer NOM=OPS-Test MODELE=socle INDEX=4. Un écosystème neuf, en une commande. | 👁 observe | — |
| 4 | Éprouve le garde-fou de collision | Essaie de créer une instance avec un index **déjà pris : refus *avant* toute copie. Puis make prouver → P21** veille sur la fédération. | 👁 observe | **P21** |
| 5 | Casse & répare | Donne à deux instances fédérées le même index (édite une nomenclature), make instances : la bannière de collision s'allume ; make prouver : P21 échoue. Corrige l'index : tout redevient vert. | 🔨 casse-répare | **P21** |
| 6 | (Avancé) Promeus un produit | Une instance qui *tourne et se prouve* peut devenir un modèle vendable : make model-creer MODE=instance SOURCE=OPS-… NOM=… — elle est généralisée (identité → exemple.*, secrets retirés) et validée. | 👁 observe | — |
*Source : [Multi-instance & fédération](Multi-instance-et-fédération) · § À toi de jouer.*
## Métriques & journaux
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Interroge les métriques | (sur obs-01) — combien de nœuds scrapés, tous UP ? « commande » | 👁 observe | — |
| 2 | Vois les journaux | : curl -s http://localhost:3100/loki/api/v1/label/host/values → les nœuds qui expédient leurs logs. | 👁 observe | — |
| 3 | Ouvre Grafana | (https://grafana.lab.chezlepro.internal) — métriques *et* logs au même endroit. | 👁 observe | — |
| 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). | 🔨 casse-répare | — |
*Source : [Métriques & journaux](Métriques-et-journaux) · § À toi de jouer.*
## PKI & confiance
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Regarde un certificat | Sur un nœud avec client_pki : « commande » Repère le sujet (l'identité), l'émetteur (l'intermédiaire) et la validité. | 👁 observe | — |
| 2 | Suis la chaîne de confiance | « commande » OK = la racine valide bien le certificat du serveur. C'est ① en action. | 👁 observe | — |
| 3 | Vois-le servir en vrai | Le LDAPS d'OpenLDAP utilise ce certificat : « commande » 0 (ok) = confiance vérifiée. | 👁 observe | — |
| 4 | Casse & répare | Retire la racine du magasin système, refais un curl HTTPS interne : avertissement de certificat (plus de confiance). Réinstalle la racine : ça remarche. Tu viens de *sentir* pourquoi « faire confiance à la racine » est la clé de voûte. | 🔨 casse-répare | — |
*Source : [PKI & confiance](PKI-et-confiance) · § À toi de jouer.*
## Reverse-proxy & TLS
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Route par nom | Deux noms, un seul edge (192.168.15.21) : « commande » Change grafana en forge : même IP, backend différent. C'est le routage par SNI. | 👁 observe | — |
| 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'. | 👁 observe | — |
| 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. | 🔨 casse-répare | — |
*Source : [Reverse-proxy & TLS](Reverse-proxy-et-TLS) · § À toi de jouer.*
## Sauvegardes (3-2-1)
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Lance une sauvegarde | Sur un nœud avec client_backup : « commande » | 👁 observe | — |
| 2 | Liste les instantanés | (le dépôt vit hors-nœud) : « commande » | 👁 observe | — |
| 3 | Restaure — le vrai test | Restaure dans un dossier temporaire et compare : « commande » *(sur infra-pki-01 ; ailleurs, compare le dump correspondant.)* | 👁 observe | — |
| 4 | Casse & répare | Supprime un fichier de donnée (une copie de test !), restaure-le depuis l'instantané, vérifie qu'il est identique. Tu viens de *sentir* que la valeur d'une sauvegarde est la restauration, pas la sauvegarde. | 🔨 casse-répare | — |
*Source : [Sauvegardes (3-2-1)](Sauvegardes) · § À toi de jouer.*
## Supervision & impact
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Ouvre Icinga Web 2 | (https://icinga.lab.chezlepro.internal, via le SSO) : la liste des hôtes et services supervisés, avec leur état (vert/jaune/rouge). | 👁 observe | — |
| 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. | 👁 observe | — |
| 3 | Casse & répare | Provoque l'échec d'un check (ex. arrête un service surveillé) : l'état passe CRITICAL, et le processus BPM qui en dépend rougit (l'impact remonte). Répare : tout reverdit. Tu *sens* la différence entre *mesurer* et *superviser/alerter*. | 🔨 casse-répare | — |
*Source : [Supervision & impact](Supervision-et-impact) · § À toi de jouer.*
## Sécurité & durcissement
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Constate les couches | (sur n'importe quel nœud) : « commande » | 👁 observe | — |
| 2 | Moindre privilège | : PermitRootLogin est à no, l'accès se fait par le compte ansible + clé SSH. Vérifie : sshd -T \| grep -E 'permitrootlogin\|passwordauthentication'. | 👁 observe | — |
| 3 | Casse & répare (avec prudence, en lab) | Assouplis un réglage sysctl, observe, puis remets-le. Tu *sens* que chaque ligne de durcissement ferme une porte précise. | 🔨 casse-répare | — |
*Source : [Sécurité & durcissement](Sécurité-et-durcissement) · § À toi de jouer.*
## Virtualisation & clonage
| # | Ce qu'on éprouve | Le geste (avec l'attendu) | Type | Preuve auto |
|---|---|---|---|---|
| 1 | Regarde un clone | Dans la GUI (make inventaire-ui), un serveur *actif* a été cloné du template (bouton « 🖥 Créer la VM »). Son VMID/IP sont dérivés de la nomenclature. | 👁 observe | — |
| 2 | Vois l'identité cloud-init | Sur un nœud : cloud-init query hostname, hostname -f, et l'agrandissement du disque racine (df -h /). | 👁 observe | — |
| 3 | Sépare les deux couches | cloud-init a posé *l'identité* ; tout le reste (paquets, services, durcissement) vient d'Ansible. Le template, lui, ne contient aucune donnée de clone. | 👁 observe | — |
| 4 | Casse & répare (mentalement + lab) | Supprime un nœud non critique et reclone-le depuis le template, puis redéploie : il revient à l'identique. Tu *sens* que la machine est reconstructible. | 🔨 casse-répare | — |
*Source : [Virtualisation & clonage](Virtualisation-et-clonage) · § À toi de jouer.*