diff --git a/CHANGELOG.md b/CHANGELOG.md index b28cca8..9882603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # CHANGELOG — Set-OPS +## 2026-08-24 — Deux runners, deux pouvoirs, aucun omnipotent + +Le travail d'un runner se divise en trois, et la ligne de partage est **celle des voûtes** : + +``` +calculer plan -> inventaire aucune voûte portée TENANT +configurer rôles sur ses machines voûte du TENANT portée TENANT +matérialiser créer/détruire des VM voûte du SITE portée FABRIC +``` + +Un runner **par tenant** qui matérialiserait mettrait la voûte du SITE en N exemplaires — +le secret le plus dangereux du système, recopié autant de fois qu'il y a de locataires. + +Un runner **unique** qui ferait tout devrait entrer en SSH chez tous les tenants, donc +traverser le default-deny inter-tenant — et rendrait l'**émancipation impossible** : un +écosystème dont le runner appartient à l'hébergeur ne peut plus se rebâtir sans lui. + +D'où `serveur_ops_site`, additif : il crée des VM vides et n'entre **jamais** chez un +tenant ; `serveur_ops` habille des machines et ne touche **jamais** la fabric. Réservé à +l'écosystème de l'hébergeur — un tenant ordinaire qui le déclarerait s'arrogerait un +pouvoir sur ses voisins. + +### La voûte, de droit plutôt que par emprunt + +La cérémonie du prêt que j'avais bricolée en ligne de commande masquait une pièce +manquante. Le rôle dépose maintenant la voûte du SITE, **chiffrée**, en `0600` — et le mot +de passe n'est toujours pas stocké : le Makefile ajoute `--ask-vault-pass` quand aucun +fichier n'est défini. + +### Une garde née d'une faute + +`decrypt: false` est obligatoire sur la copie : sans lui, Ansible **déchiffre** la source +quand il détient le mot de passe. Mesuré le jour même, en la déposant à la main — 776 +octets en clair au lieu de 3465 chiffrés, sur une machine où ils n'avaient rien à faire. + +Le rôle **relit l'en-tête après avoir écrit** et refuse si le fichier n'est pas chiffré. +Contrôle négatif fait : pointé sur un fichier en clair, il échoue ; rétabli ensuite, la +voûte déposée est bien `$ANSIBLE_VAULT`, 3465 octets, `0600`. + +Une fuite silencieuse aurait été le pire des cas : le fichier existe, le rôle se dit +satisfait, et les clés du cluster dorment en clair. + ## 2026-08-24 — Filiation, mutualisation, émancipation : nommer un motif que le moteur avait déjà Un écosystème ne naît pas autoportant. Il lui faut une forge pour lire son génome, une diff --git a/docs/audit/preuve-2026-08-24.md b/docs/audit/preuve-2026-08-24.md index ee82aa5..d9e2f93 100644 --- a/docs/audit/preuve-2026-08-24.md +++ b/docs/audit/preuve-2026-08-24.md @@ -20,8 +20,8 @@ | P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | | | P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. | | P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). | -| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 33 groupes classes, aucun cycle, aucune arete en arriere. | -| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 31 rôles, 82 flux, schéma + matrice OK. | +| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 34 groupes classes, aucun cycle, aucune arete en arriere. | +| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 32 rôles, 83 flux, schéma + matrice OK. | | P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). | | P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml | | P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. | @@ -36,21 +36,21 @@ | P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. | | P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). | | P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 7 reseau(x), aucune collision avec la plage tenant. | -| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 54 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,192.168.254.2/32,192.168.255.2/32. | +| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 55 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,192.168.254.2/32,192.168.255.2/32. | | P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 48 groupe(s), 77 regle(s). | | P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 15 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). | | P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. | | P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 35 VM placee(s), aucun nom ni VMID en collision. | -| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 25 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 14, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv | +| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 26 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 15, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv | | P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. | -| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 47 scripts expliques et atteignables, 98 cibles make documentees, 57 roles avec README. | +| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 47 scripts expliques et atteignables, 98 cibles make documentees, 58 roles avec README. | | P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 34 exigence(s) de role, toutes satisfaites (121 cle(s) declaree(s) par l'instance). | | P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (34 groupes). | | P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (22 genere(s) exempte(s)). | | P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). | | P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). | | P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. | -| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 32 role(s) serveur/client tous nommes, 33 groupe(s) cite(s) en table existent tous. | +| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 33 role(s) serveur/client tous nommes, 34 groupe(s) cite(s) en table existent tous. | | P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. | | P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. | | P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 43 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). | diff --git a/docs/catalogue-services.md b/docs/catalogue-services.md index 68493db..8cd4bbb 100644 --- a/docs/catalogue-services.md +++ b/docs/catalogue-services.md @@ -51,6 +51,7 @@ Ce que la reconstruction couvre, par capacité : | Sauvegardes | `serveur_backup`, `client_backup` | restic hors-nœud, **restauration éprouvée** (2026-08-12 : la donnée revient) | | Exploitation | `serveur_ops` | le poste depuis lequel l'ecosysteme se reconstruit : Ansible epingle, genome clone depuis **sa propre forge**, cle SSH propre — **sans** la voute ni son mot de passe | | Source d'artefacts | `serveur_artefacts`, `client_artefacts` | cache apt de l'ecosysteme (apt-cacher-ng) : les paquets viennent de chez soi, pas de six serveurs etrangers — **mode hors ligne** pour prouver ce que le cache detient vraiment | +| Runner de site | `serveur_ops_site` | le pouvoir de **materialiser** : creer et detruire des VM sur la fabric. Detient la voute du SITE, chiffree, et n'entre JAMAIS chez un tenant — reserve a l'ecosysteme de l'hebergeur | | Agents de flotte | `client_metrique`, `client_journal`, `client_smtp` | collecte et relais sur toute la flotte | *Rôles retirés (2026-07-04, supersédés ou hors conception)* : `serveur_sendmail` diff --git a/docs/couches-deploiement.yml b/docs/couches-deploiement.yml index 1f9c0d5..98a62bc 100644 --- a/docs/couches-deploiement.yml +++ b/docs/couches-deploiement.yml @@ -67,6 +67,9 @@ couches: # Le poste d'exploitation vient APRES la forge : il clone le genome depuis # elle. Le placer plus tot le laisserait sans source. - serveur_ops + # Le runner de SITE vient APRES le runner de tenant : il suppose les depots + # clones. Il n'ajoute qu'un pouvoir -- celui de materialiser sur la fabric. + - serveur_ops_site - nom: agents raison: "Les intégrations clientes qui expédient vers les services centraux (métriques, journaux, courriel, sauvegardes, résolution locale). Déployées en dernier, quand leurs cibles sont debout." diff --git a/docs/dependances-groupes.yml b/docs/dependances-groupes.yml index eefef31..60d11f8 100644 --- a/docs/dependances-groupes.yml +++ b/docs/dependances-groupes.yml @@ -118,6 +118,15 @@ groupes: raison: "Un cache apt ne depend d'aucun service de l'ecosysteme : il ne fait que relayer et retenir." surveillance: "Verifier l'ecoute sur 3142, le taux de service depuis le journal, et l'espace du cache." + serveur_ops_site: + requiert_groupes_actifs: + - serveur_ops + # LE RUNNER DE SITE EST ADDITIF : il suppose le poste d'exploitation en place, dont il + # reutilise la racine, l'utilisateur et les depots clones. Seul, il n'aurait ni carte + # de la fabric ni moteur pour agir. + raison: "Le runner de site n'ajoute qu'un pouvoir a un poste d'exploitation existant : sans lui, rien a quoi l'attacher." + surveillance: "Verifier que la voute du site est presente ET CHIFFREE, et que la carte de la fabric est lisible." + serveur_nextcloud: requiert_groupes_actifs: - serveur_postgresql diff --git a/playbooks/groupes/serveur_ops_site.yml b/playbooks/groupes/serveur_ops_site.yml new file mode 100644 index 0000000..2031b5b --- /dev/null +++ b/playbooks/groupes/serveur_ops_site.yml @@ -0,0 +1,15 @@ +--- +- name: Appliquer le groupe serveur_ops_site + hosts: serveur_ops_site + become: true + gather_facts: true + + pre_tasks: + - name: Vérifier que la cible est Debian + ansible.builtin.assert: + that: + - ansible_facts.distribution == "Debian" + fail_msg: "Ce playbook est prévu pour Debian." + + roles: + - serveur_ops_site diff --git a/roles/serveur_ops_site/README.md b/roles/serveur_ops_site/README.md new file mode 100644 index 0000000..46df520 --- /dev/null +++ b/roles/serveur_ops_site/README.md @@ -0,0 +1,62 @@ +# serveur_ops_site + +**Le runner de SITE** : celui qui matérialise. Il crée des VM sur la fabric, et rien d'autre. + +## Pourquoi ce rôle existe séparément + +Le travail d'un runner se divise en trois, et la ligne de partage est celle des voûtes : + +| | voûte requise | portée | rôle | +|---|---|---|---| +| calculer — plan → inventaire | aucune | tenant | `serveur_ops` | +| configurer — rôles sur ses machines | **tenant** | tenant | `serveur_ops` | +| matérialiser — créer/détruire des VM | **SITE** | fabric | **ce rôle** | + +**Un runner par tenant qui matérialiserait** mettrait la voûte du SITE en N exemplaires — +le secret le plus dangereux du système, recopié autant de fois qu'il y a de locataires. + +**Un runner unique qui ferait tout** devrait entrer en SSH chez tous les tenants, donc +traverser le default-deny inter-tenant. Et il rendrait l'**émancipation impossible** : un +écosystème dont le runner appartient à l'hébergeur ne peut pas se rebâtir sans lui. + +Deux pouvoirs, deux rôles, **aucun omnipotent**. Ce rôle crée des VM vides et n'entre +jamais chez un tenant ; `serveur_ops` habille des machines et ne touche jamais la fabric. + +## Qui a le droit de le déclarer + +**L'écosystème de l'hébergeur**, celui qui exploite la fabric. Un tenant ordinaire qui le +déclarerait s'arrogerait un pouvoir sur ses voisins. + +## Ce qu'il dépose, et comment + +La voûte du SITE, **chiffrée**, en `0600`. Le mot de passe n'est jamais stocké : le +Makefile ajoute `--ask-vault-pass` quand aucun fichier de mot de passe n'est défini, et +l'exploitant le tape au moment d'agir. + +`decrypt: false` est **obligatoire** sur la copie. Sans lui, Ansible déchiffre la source +quand il détient le mot de passe — constaté le 2026-08-24 : 776 octets en clair au lieu de +3465 chiffrés, sur une machine où ils n'avaient rien à faire. Le rôle **relit l'en-tête** +après avoir écrit et refuse si le fichier n'est pas chiffré. + +## Colocalisation + +Rien n'oblige à lui donner sa propre machine : chez l'hébergeur, les deux pouvoirs +résident légitimement au même endroit, et une machine de plus dans un écosystème +volontairement maigre se paie. Le déclarer séparément rend le pouvoir **visible**, ce qui +est l'essentiel. + +Chez un tenant qui n'est pas l'hébergeur, la question ne se pose pas : le rôle n'a pas à +y être. + +## Variables + +| Variable | Rôle | +|---|---| +| `serveur_ops_site_depot` | dossier du dépôt SITE chez le runner (cloné par `serveur_ops`) | +| `serveur_ops_site_voute_source` | chemin de `underlay.vault.yml` sur le contrôleur | +| `serveur_ops_site_voute_deposer` | `false` pour un runner qui lit la carte sans détenir les clés | + +## Ce que ce rôle ne fait pas + +Il n'installe rien, n'ouvre aucun port, ne sauvegarde rien. Il ne fait qu'**attribuer un +pouvoir** — et le rendre lisible dans le plan. diff --git a/roles/serveur_ops_site/defaults/main.yml b/roles/serveur_ops_site/defaults/main.yml new file mode 100644 index 0000000..e080cc9 --- /dev/null +++ b/roles/serveur_ops_site/defaults/main.yml @@ -0,0 +1,43 @@ +--- +# LE RUNNER DE SITE — celui qui MATÉRIALISE. +# +# Le travail d'un runner se divise en trois, et les deux premières portées appartiennent +# au TENANT tandis que la troisième appartient à la FABRIC : +# +# calculer plan -> inventaire aucune voûte portée OPS (serveur_ops) +# configurer rôles sur ses machines voûte du TENANT portée OPS (serveur_ops) +# matérialiser créer/détruire des VM voûte du SITE portée SITE (ce rôle) +# +# POURQUOI LA SÉPARATION. Si chaque écosystème portait le pouvoir de matérialiser, la +# voûte du SITE se retrouverait en N exemplaires — le secret le plus dangereux du système, +# recopié autant de fois qu'il y a de locataires. C'est précisément ce que la séparation +# des voûtes (2026-08-22) cherchait à éviter. +# +# Et l'inverse serait pire : un runner UNIQUE qui ferait tout devrait entrer en SSH chez +# tous les tenants, donc traverser le default-deny inter-tenant — et rendrait toute +# émancipation impossible, puisqu'un écosystème ne pourrait plus se rebâtir sans son hôte. +# +# Deux pouvoirs, deux rôles, aucun omnipotent : ce rôle crée des VM vides et n'entre +# jamais chez un tenant ; `serveur_ops` habille des machines et ne touche jamais la fabric. +# +# QUI PEUT LE DÉCLARER : l'écosystème de l'HÉBERGEUR, celui qui exploite la fabric. Un +# tenant ordinaire qui le déclarerait s'arrogerait un pouvoir sur ses voisins. + +serveur_ops_site_utilisateur: "setops" +serveur_ops_site_racine: "/opt/setops" + +# Le dépôt du SITE, tel qu'il atterrit chez le runner. Il doit avoir été cloné par +# `serveur_ops` (entrée `role: hebergeur` de `serveur_ops_depots`). +serveur_ops_site_depot: "" + +# --- LA VOÛTE DU SITE -------------------------------------------------------- +# +# Déposée CHIFFRÉE, jamais en clair. Le mot de passe n'est pas stocké : le Makefile +# ajoute `--ask-vault-pass` quand aucun fichier de mot de passe n'est défini, et +# l'exploitant le tape au moment d'agir. +# +# `decrypt: no` EST OBLIGATOIRE sur la copie. Sans lui, Ansible DÉCHIFFRE la source quand +# il détient le mot de passe — mesuré le 2026-08-24, 776 octets en clair au lieu de 3465 +# chiffrés, sur une machine où ils n'avaient rien à faire. +serveur_ops_site_voute_source: "" +serveur_ops_site_voute_deposer: true diff --git a/roles/serveur_ops_site/meta/authentification.yml b/roles/serveur_ops_site/meta/authentification.yml new file mode 100644 index 0000000..7d3e211 --- /dev/null +++ b/roles/serveur_ops_site/meta/authentification.yml @@ -0,0 +1,12 @@ +--- +# Position de ce role dans la directive d'authentification (D-38..D-41). +# Voir docs/authentification.md. Gardee par la preuve P29. +authentification: + portee: sans-auth-humaine + mecanisme: aucun + formulaire_local: sans-objet + secours: "Acces SSH a l'hote, puis `sudo -iu setops`" + raison: >- + Ce role n'expose aucune interface : il DETIENT un pouvoir au lieu d'en offrir un. + Ce qui le protege n'est pas une authentification mais le fait que la voute deposee + reste CHIFFREE et que son mot de passe soit tape a l'execution, jamais stocke. diff --git a/roles/serveur_ops_site/meta/empreinte.yml b/roles/serveur_ops_site/meta/empreinte.yml new file mode 100644 index 0000000..bd1634d --- /dev/null +++ b/roles/serveur_ops_site/meta/empreinte.yml @@ -0,0 +1,7 @@ +--- +# Empreinte ressources — ce rôle n'ajoute qu'un fichier et des appels d'API. Il se +# colocalise avec `serveur_ops`, dont il partage la racine et l'utilisateur. +setops_empreinte: + coeurs: 0 + memoire_mo: 0 + disque_go: 0 diff --git a/roles/serveur_ops_site/meta/flux.yml b/roles/serveur_ops_site/meta/flux.yml new file mode 100644 index 0000000..cf9627d --- /dev/null +++ b/roles/serveur_ops_site/meta/flux.yml @@ -0,0 +1,15 @@ +--- +# Flux réseau du runner de SITE. Voir docs/flux-conception.md. +# +# Un seul flux, et c'est tout son sens : l'API de l'hyperviseur. Ce rôle n'entre JAMAIS +# chez un tenant — c'est `serveur_ops` qui configure les machines, une fois qu'elles +# existent. +flux: + - sens: egress + port: 8006 + protocole: tcp + pair: externe + chiffrement: tls-requis + raison: >- + API de l'hyperviseur : créer, cloner et détruire les VM de la fabric. Le seul flux + par lequel un écosystème peut en matérialiser un autre. diff --git a/roles/serveur_ops_site/tasks/main.yml b/roles/serveur_ops_site/tasks/main.yml new file mode 100644 index 0000000..4638d4b --- /dev/null +++ b/roles/serveur_ops_site/tasks/main.yml @@ -0,0 +1,64 @@ +--- +- name: Exiger les intrants du runner de site + ansible.builtin.assert: + that: + - serveur_ops_site_depot | length > 0 + - (not (serveur_ops_site_voute_deposer | bool)) or (serveur_ops_site_voute_source | length > 0) + fail_msg: >- + serveur_ops_site exige `serveur_ops_site_depot` (le dossier du dépôt SITE chez le + runner, cloné par serveur_ops) et, si le dépôt de la voûte est demandé, + `serveur_ops_site_voute_source` (le chemin de `underlay.vault.yml` sur le contrôleur). + +- name: Le dépôt du SITE est-il bien là ? + ansible.builtin.stat: + path: "{{ serveur_ops_site_racine }}/{{ serveur_ops_site_depot }}/underlay.yml" + register: serveur_ops_site_carte + +- name: Refuser si la carte de la fabric manque + ansible.builtin.assert: + that: + - serveur_ops_site_carte.stat.exists + fail_msg: >- + {{ serveur_ops_site_racine }}/{{ serveur_ops_site_depot }}/underlay.yml est absent. + Déclarer ce dépôt dans `serveur_ops_depots` (role: hebergeur) et rejouer serveur_ops : + un runner de site sans carte ne sait pas sur quoi il matérialise. + when: not ansible_check_mode + +# LES CLÉS DU MONDE PHYSIQUE, DE DROIT ET NON PAR EMPRUNT. +# +# `decrypt: no` : voir defaults/main.yml. Le fichier reste chiffré ; seul le mot de passe, +# tapé à l'exécution, l'ouvre. +- name: Déposer la voûte du SITE (chiffrée) + ansible.builtin.copy: + src: "{{ serveur_ops_site_voute_source }}" + dest: "{{ serveur_ops_site_racine }}/{{ serveur_ops_site_depot }}/underlay.vault.yml" + owner: "{{ serveur_ops_site_utilisateur }}" + group: "{{ serveur_ops_site_utilisateur }}" + mode: "0600" + decrypt: false + when: + - serveur_ops_site_voute_deposer | bool + - not ansible_check_mode + +# ÉCRIRE, PUIS RELIRE (D-68). Une voûte déchiffrée par accident est une fuite silencieuse : +# le fichier existe, le rôle se dit satisfait, et les clés du cluster dorment en clair. +- name: Relire l'en-tête de la voûte déposée + ansible.builtin.command: + cmd: "head -c 21 {{ serveur_ops_site_racine }}/{{ serveur_ops_site_depot }}/underlay.vault.yml" + register: serveur_ops_site_entete + changed_when: false + when: + - serveur_ops_site_voute_deposer | bool + - not ansible_check_mode + +- name: Exiger que la voûte déposée soit CHIFFRÉE + ansible.builtin.assert: + that: + - "'$ANSIBLE_VAULT' in serveur_ops_site_entete.stdout" + fail_msg: >- + La voûte déposée n'est PAS chiffrée — elle a été déchiffrée en transit. + Vérifier `decrypt: false` sur la copie. Détruire le fichier au shred et + considérer les justificatifs du cluster comme exposés. + when: + - serveur_ops_site_voute_deposer | bool + - not ansible_check_mode