runner : serveur_ops_tenant — le runner d'un tenant recoit enfin sa voute
Some checks are pending
verifier / verifier (push) Waiting to run

La doctrine des runners decrit trois portees depuis le 2026-08-22 :

  calculer      plan -> inventaire        aucune voute        serveur_ops
  configurer    roles sur ses machines    voute du TENANT     <- revendiquee, jamais recue
  materialiser  creer/detruire des VM     voute du SITE       serveur_ops_site

La deuxieme ligne etait un trou. RIEN ne deposait jamais la voute d'un tenant sur
son runner. Un runner pouvait deriver son inventaire et ne rien pouvoir en faire :
chaque role qui demande un secret echouait sur son assertion, et l'echec ne disait
pas qu'il manquait un FICHIER, seulement que les valeurs etaient vides.

Decouvert en preparant la reconstruction de Chezlepro. Le runner du SITE peut
materialiser ses quinze machines ; il ne peut pas les configurer, parce que
Chezlepro a sa PROPRE autorite de certification — donc `client_pki` y reclame un
secret de Chezlepro. Le runner du site ne l'a pas, et NE DOIT PAS l'avoir : c'est
la ligne qui rend l'hebergement mutualise defendable.

`serveur_ops_tenant` est le symetrique exact de `serveur_ops_site` : un MARQUEUR,
pas un installateur. Il depose la voute `decrypt: false`, puis RELIT l'en-tete du
fichier depose — une voute dechiffree par accident est une fuite silencieuse. Le
dossier d'inventaire (`principal` ou `production`) se DECOUVRE sur la machine
plutot que d'etre ecrit.

Pourquoi un role a part et non une option de `serveur_ops` : donner sa voute a un
runner est un POUVOIR, pas un reglage. Le declarer au plan force a repondre a
« cette machine a-t-elle le droit de configurer cet ecosysteme ? » — une option
activee par defaut y repondrait a notre place.

CHEZLEPRO LISAIT ENCORE SON GENOME CHEZ PATIENT 0.

Trouve au passage, et bloquant pour la reconstruction : `serveur_ops_forge_amont`
pointait sur `10.29.16.11` — la forge de patient 0, l'amont d'avant que le site
ait la sienne. D-81 a tranche depuis. Laisse tel quel, Chezlepro se serait
reconstruit depuis un moteur perime, sur un reseau que le decoupage en zones a de
toute facon deplace.

Corrige vers la forge du site, PAR SON IP — `forge.genese.internal` appartient a
la zone souveraine du site, et un tenant ne resout que la sienne ; le certificat
SERVI porte bien `IP Address:10.0.33.11`. La racine de l'AC du site est desormais
versionnee a cote de la carte de la fabric a laquelle elle appartient, et son
chemin se DERIVE du symlink `underlay.yml` : il vaut depuis le poste comme depuis
un runner.

Le role est inscrit dans les quatre registres qui l'exigeaient — couches de
deploiement, graphe des dependances, catalogue des services, carte d'orientation.
Ce sont les preuves P08, P38 et P48 qui l'ont reclame, chacune a son tour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-26 15:30:51 -04:00
parent 9e8679215d
commit 8890c997af
13 changed files with 329 additions and 7 deletions

View file

@ -1,5 +1,48 @@
# CHANGELOG — Set-OPS
## 2026-08-26 — `serveur_ops_tenant` : le runner d'un tenant reçoit enfin sa voûte
**47 preuves.** La doctrine des runners décrit trois portées depuis le 2026-08-22 :
```
calculer plan → inventaire aucune voûte serveur_ops
configurer rôles sur ses machines voûte du TENANT ← revendiquée, jamais reçue
matérialiser créer/détruire des VM voûte du SITE serveur_ops_site
```
La deuxième ligne était un trou. **Rien ne déposait jamais la voûte d'un tenant sur son
runner.** Un runner pouvait dériver son inventaire et ne rien pouvoir en faire : chaque
rôle qui demande un secret échouait sur son assertion, et l'échec ne disait pas qu'il
manquait un *fichier*, seulement que les valeurs étaient vides.
Découvert en préparant la reconstruction de Chezlepro. Le runner du site peut matérialiser
ses quinze machines ; il ne peut pas les configurer, parce que Chezlepro a sa **propre**
autorité de certification — donc `client_pki` y réclame un secret de Chezlepro. Le runner
du site ne l'a pas, et **ne doit pas l'avoir** : c'est la ligne qui rend l'hébergement
mutualisé défendable.
`serveur_ops_tenant` est le symétrique exact de `serveur_ops_site` : un **marqueur**, pas
un installateur. Il dépose la voûte `decrypt: false`, puis **relit l'en-tête** du fichier
déposé — une voûte déchiffrée par accident est une fuite silencieuse. Le dossier
d'inventaire (`principal` ou `production`) se **découvre** sur la machine.
*Pourquoi un rôle à part et non une option : donner sa voûte à un runner est un pouvoir,
pas un réglage. Le déclarer au plan force à répondre à « cette machine a-t-elle le droit de
configurer cet écosystème ? » — une option activée par défaut y répondrait à notre place.*
### Chezlepro lisait encore son génome chez patient 0
Trouvé au passage, et bloquant pour la reconstruction : `serveur_ops_forge_amont` pointait
sur `10.29.16.11` — la forge de patient 0, l'amont d'avant que le site ait la sienne.
**D-81** a tranché depuis. Laissé tel quel, Chezlepro se serait reconstruit depuis un
moteur périmé, sur un réseau que le découpage en zones a de toute façon déplacé.
Corrigé vers la forge du site, **par son IP** — `forge.genese.internal` appartient à la
zone souveraine du site, et un tenant ne résout que la sienne ; le certificat servi porte
bien `IP Address:10.0.33.11`. La racine de l'AC du site est désormais versionnée à côté de
la carte de la fabric à laquelle elle appartient, et son chemin se **dérive** du symlink
`underlay.yml` — il vaut donc depuis le poste comme depuis un runner.
## 2026-08-26 — Le runner du site travaille, et le génome remonte chez lui
**46 preuves.** `serveur_ops` et `serveur_ops_site` sont déployés sur `site-ops-01` : le

View file

@ -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 : 36 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 34 rôles, 92 flux, schéma + matrice OK. |
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 37 groupes classes, aucun cycle, aucune arete en arriere. |
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 35 rôles, 92 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 | serveur_oauth2_proxy |
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
@ -41,16 +41,16 @@
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 5 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (0 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 | 28 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 17, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 29 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 18, 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 | 50 scripts expliques et atteignables, 104 cibles make documentees, 60 roles avec README. |
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 50 scripts expliques et atteignables, 104 cibles make documentees, 61 roles avec README. |
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 14 exigence(s) de role, toutes satisfaites (39 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 (16 groupes). |
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (24 genere(s) exempte(s)). |
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 0 application(s) exigeant une base l'ont toutes (0 entree(s) au registre). |
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 2 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 : 35 role(s) serveur/client tous nommes, 36 groupe(s) cite(s) en table existent tous. |
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 36 role(s) serveur/client tous nommes, 37 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 : 46 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |

View file

@ -23,8 +23,8 @@ README de rôles). Cette page comble ces deux trous.
| Ce qu'on compte | Combien | Comment on le mesure |
|---|---|---|
| rôles | 60 | `roles/*/` |
| README de rôles | 60 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette |
| rôles | 61 | `roles/*/` |
| README de rôles | 61 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette |
| documents | 38 | `docs/*.md` |
| pièces d'audit | 27 | `docs/audit/*` |
| unités de wiki | 27 | `wiki/*.md` |

View file

@ -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 tenant | `serveur_ops_tenant` | le pouvoir de **configurer** : la voute de l'ecosysteme, deposee CHIFFREE sur son propre runner. Sans elle un runner calcule son inventaire et ne peut rien en faire — chaque role qui demande un secret echoue sur son assertion. N'atteint ni la fabric ni la frontiere |
| 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 |
| Resolution | `serveur_resolveur`, `client_resolveur` | UN resolveur recursif par tenant (Unbound), qui recurse depuis la racine et delegue la zone souveraine a PowerDNS. Remplace les N demons locaux d'avant le 2026-08-24 |
| Cache du site | `serveur_cache_site` | designe LE cache que les ecosystemes voisins prennent comme amont : Debian telecharge une fois pour toute la fabric, et le cache ne voit que des requetes agregees. Reserve a l'ecosysteme de l'hebergeur |

View file

@ -76,6 +76,10 @@ 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 TENANT vient APRES le poste : il suppose les depots clones et le lien
# `instance` pose. Il n'ajoute qu'un pouvoir -- celui de CONFIGURER cet ecosysteme,
# par sa voute deposee chiffree.
- serveur_ops_tenant
# 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

View file

@ -118,6 +118,16 @@ 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_tenant:
requiert_groupes_actifs:
- serveur_ops
# LE RUNNER DE TENANT EST ADDITIF, comme celui du site : il suppose le poste
# d'exploitation en place, dont il reutilise la racine, l'utilisateur, les depots
# clones et le lien `instance`. Seul, il ne ferait que deposer un secret sur une
# machine qui n'a pas le plan qu'il ouvre.
raison: "Le runner de tenant n'ajoute qu'un pouvoir a un poste d'exploitation existant : sans lui, rien a quoi l'attacher."
surveillance: "Verifier que la voute de l'ecosysteme est presente ET CHIFFREE sous son dossier d'inventaire."
serveur_ops_site:
requiert_groupes_actifs:
- serveur_ops

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_ops_tenant
hosts: serveur_ops_tenant
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_tenant

View file

@ -0,0 +1,48 @@
# serveur_ops_tenant — donner à un runner la voûte de SON écosystème
> **Pour qui :** l'exploitant qui monte le runner d'un tenant.
Marqueur, pas installateur. `serveur_ops` installe le poste — Ansible, les collections, les
dépôts du génome, les symlinks. Ce rôle-ci y ajoute **un pouvoir** : la voûte de
l'écosystème, déposée **chiffrée**, sans laquelle le runner peut tout calculer et ne rien
configurer.
## Les trois portées d'un runner
```
calculer plan → inventaire aucune voûte serveur_ops
configurer rôles sur ses machines voûte du TENANT serveur_ops_tenant
matérialiser créer/détruire des VM voûte du SITE serveur_ops_site
```
Aucun runner n'est omnipotent. Le runner d'un **site** matérialise le terrain et n'entre
jamais chez un tenant ; le runner d'un **tenant** configure son écosystème et ne touche
jamais la fabric.
## Pourquoi un rôle à part
Donner sa voûte à un runner est un **pouvoir**, pas un réglage. Le déclarer au plan force à
répondre à « cette machine a-t-elle le droit de configurer cet écosystème ? » — une option
activée par défaut y répondrait à notre place.
## Ce qu'il exige
| Variable | Ce que c'est |
|---|---|
| `serveur_ops_tenant_depot` | le dossier de cet écosystème chez le runner ; vaut `serveur_ops_instance` par défaut |
| `serveur_ops_tenant_voute_source` | le chemin du `vault.yml` **sur le contrôleur** |
Le dossier d'inventaire (`principal` ou `production`) se **découvre** sur la machine : il
n'est écrit nulle part.
## Ce qu'il garantit
`decrypt: false` sur la copie, puis **relecture de l'en-tête** du fichier déposé. Une voûte
déchiffrée par accident est une fuite silencieuse — le fichier existe, le rôle se dirait
satisfait, et les secrets dormiraient en clair sur une machine. Le rôle refuse plutôt.
## Voir aussi
- `roles/serveur_ops/README.md` — l'installateur
- `roles/serveur_ops_site/README.md` — le pouvoir symétrique, côté fabric
- `docs/implanter-un-tenant-sur-un-site.md` — la séquence complète

View file

@ -0,0 +1,58 @@
---
# LE RUNNER D'UN TENANT — celui qui CONFIGURE.
#
# Le travail d'un runner se divise en trois portées, et chacune a son rôle :
#
# calculer plan -> inventaire aucune voûte `serveur_ops`
# configurer rôles sur ses machines voûte du TENANT CE RÔLE
# matérialiser créer/détruire des VM voûte du SITE `serveur_ops_site`
#
# CE QUI MANQUAIT (2026-08-26). La portée « configurer » était attribuée à `serveur_ops`
# dans la doctrine — mais rien ne déposait jamais la voûte du tenant sur son runner. Un
# runner pouvait donc dériver son inventaire et ne rien pouvoir en faire : chaque rôle qui
# demande un secret échouait sur son assertion, et l'échec ne disait pas qu'il manquait un
# FICHIER, seulement que les valeurs étaient vides.
#
# Découvert en préparant la reconstruction de Chezlepro : le runner du SITE peut
# matérialiser ses quinze machines, mais ne peut pas les configurer — Chezlepro a sa
# PROPRE autorité de certification, donc `client_pki` y réclame un secret de Chezlepro.
# Le runner du site ne l'a pas, et ne doit pas l'avoir : c'est la ligne qui rend
# l'hébergement mutualisé défendable.
#
# POURQUOI UN RÔLE À PART, et non une option de `serveur_ops`. Donner sa voûte à un runner
# est un POUVOIR, pas un réglage. Le déclarer explicitement au plan force à répondre à la
# question « cette machine a-t-elle le droit de configurer cet écosystème ? » — alors
# qu'une option activée par défaut y répondrait à notre place. C'est le même patron que
# `serveur_artefacts` + `serveur_cache_site` : un installateur, puis un marqueur qui
# ajoute un pouvoir.
#
# QUI PEUT LE DÉCLARER : l'écosystème lui-même, pour SA machine. Un runner de SITE ne le
# déclare jamais — il porte les plans des tenants en `role: tenant` précisément pour dire
# qu'il prépare leur terrain sans les piloter.
serveur_ops_tenant_utilisateur: "setops"
serveur_ops_tenant_racine: "/opt/setops"
# Le dossier du dépôt de CET écosystème chez le runner, cloné par `serveur_ops`
# (entrée `role: instance` de `serveur_ops_depots`). Par défaut : celui que le lien
# `instance` désigne déjà — le pilote et sa voûte ne peuvent pas viser deux écosystèmes.
serveur_ops_tenant_depot: "{{ serveur_ops_instance | default('') }}"
# --- LA VOÛTE DU TENANT ------------------------------------------------------
#
# Déposée CHIFFRÉE, jamais en clair. Le mot de passe n'est pas stocké : 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 — mesuré le 2026-08-24 sur la voûte du site, 776 octets
# en clair au lieu de 3465 chiffrés, sur une machine où ils n'avaient rien à faire.
serveur_ops_tenant_voute_source: ""
serveur_ops_tenant_voute_deposer: true
# LE DOSSIER D'INVENTAIRE SE DÉCOUVRE, il ne s'écrit pas. Le dépôt en connaît deux noms
# — `principal` et `production` — et l'ordre de préférence est celui de
# `inventory_rules.dossier_inventaire`. Écrire le nom en dur ici le ferait mentir pour
# l'écosystème qui emploie l'autre.
serveur_ops_tenant_inventaires_connus:
- principal
- production

View file

@ -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.

View file

@ -0,0 +1,7 @@
---
# Empreinte ressources — ce rôle n'ajoute qu'un fichier. Il se colocalise avec
# `serveur_ops`, dont il partage la racine et l'utilisateur.
setops_empreinte:
coeurs: 0
memoire_mo: 0
disque_go: 0

View file

@ -0,0 +1,11 @@
---
# Flux réseau du runner de TENANT. Voir docs/flux-conception.md.
#
# AUCUN FLUX PROPRE, ET C'EST LE POINT. Ce rôle ne fait que déposer un fichier ; ce qui
# consomme la voûte, c'est `serveur_ops` quand il configure les machines de l'écosystème,
# et ses flux sont déclarés chez lui. Ce rôle n'atteint NI la fabric NI la frontière —
# c'est exactement ce qui le sépare de `serveur_ops_site`.
#
# Le dire explicitement plutôt que d'omettre le fichier : une absence de déclaration se
# lit comme un oubli, une déclaration vide se lit comme une décision.
flux: []

View file

@ -0,0 +1,113 @@
---
- name: Exiger les intrants du runner de tenant
ansible.builtin.assert:
that:
- serveur_ops_tenant_depot | length > 0
- (not (serveur_ops_tenant_voute_deposer | bool))
or (serveur_ops_tenant_voute_source | length > 0)
fail_msg: >-
serveur_ops_tenant exige `serveur_ops_tenant_depot` (le dossier de CET écosystème
chez le runner, cloné par serveur_ops — il vaut `serveur_ops_instance` par défaut)
et, si le dépôt de la voûte est demandé, `serveur_ops_tenant_voute_source`
(le chemin de son `vault.yml` sur le contrôleur).
- name: Le plan de l'écosystème est-il bien là ?
ansible.builtin.stat:
path: "{{ serveur_ops_tenant_racine }}/{{ serveur_ops_tenant_depot }}/plan/serveurs.yml"
register: serveur_ops_tenant_plan
- name: Refuser si le plan de l'écosystème manque
ansible.builtin.assert:
that:
- serveur_ops_tenant_plan.stat.exists
fail_msg: >-
{{ serveur_ops_tenant_racine }}/{{ serveur_ops_tenant_depot }}/plan/serveurs.yml est
absent. Déclarer ce dépôt dans `serveur_ops_depots` (role: instance) et rejouer
serveur_ops : donner sa voûte à un runner qui n'a pas le plan qu'elle ouvre ne
configure rien — ça ne fait que déposer un secret.
when: not ansible_check_mode
# LE DOSSIER D'INVENTAIRE SE DÉCOUVRE. Deux noms existent dans le dépôt (`principal`,
# `production`) et l'écrire en dur le ferait mentir pour l'écosystème qui emploie l'autre.
- name: Chercher le dossier d'inventaire de cet écosystème
ansible.builtin.stat:
path: "{{ serveur_ops_tenant_racine }}/{{ serveur_ops_tenant_depot }}/inventories/{{ item }}"
register: serveur_ops_tenant_candidats
loop: "{{ serveur_ops_tenant_inventaires_connus }}"
loop_control:
label: "{{ item }}"
- name: Retenir le premier qui existe
ansible.builtin.set_fact:
serveur_ops_tenant_inventaire: >-
{{ (serveur_ops_tenant_candidats.results | selectattr('stat.exists')
| map(attribute='item') | list | first) | default('') }}
- name: Refuser si aucun inventaire n'est reconnu
ansible.builtin.assert:
that:
- serveur_ops_tenant_inventaire | length > 0
fail_msg: >-
Aucun dossier d'inventaire trouvé sous
{{ serveur_ops_tenant_racine }}/{{ serveur_ops_tenant_depot }}/inventories/ parmi
{{ serveur_ops_tenant_inventaires_connus | join(', ') }}. Sans lui, on ne saurait pas
où déposer la voûte — et la déposer au mauvais endroit revient à ne pas la déposer,
en laissant croire le contraire.
when: not ansible_check_mode
- name: Préparer le dossier des variables communes
ansible.builtin.file:
path: >-
{{ serveur_ops_tenant_racine }}/{{ serveur_ops_tenant_depot }}/inventories/{{
serveur_ops_tenant_inventaire }}/group_vars/all
state: directory
owner: "{{ serveur_ops_tenant_utilisateur }}"
group: "{{ serveur_ops_tenant_utilisateur }}"
mode: "0700"
when:
- serveur_ops_tenant_voute_deposer | bool
- not ansible_check_mode
# LES SECRETS DE L'ÉCOSYSTÈME, DE DROIT ET NON PAR EMPRUNT.
#
# `decrypt: false` : 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 de l'écosystème (chiffrée)
ansible.builtin.copy:
src: "{{ serveur_ops_tenant_voute_source }}"
dest: >-
{{ serveur_ops_tenant_racine }}/{{ serveur_ops_tenant_depot }}/inventories/{{
serveur_ops_tenant_inventaire }}/group_vars/all/vault.yml
owner: "{{ serveur_ops_tenant_utilisateur }}"
group: "{{ serveur_ops_tenant_utilisateur }}"
mode: "0600"
decrypt: false
when:
- serveur_ops_tenant_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 secrets de l'écosystème dorment en
# clair sur une machine.
- name: Relire l'en-tête de la voûte déposée
ansible.builtin.command:
cmd: >-
head -c 21 {{ serveur_ops_tenant_racine }}/{{ serveur_ops_tenant_depot
}}/inventories/{{ serveur_ops_tenant_inventaire }}/group_vars/all/vault.yml
register: serveur_ops_tenant_entete
changed_when: false
when:
- serveur_ops_tenant_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_tenant_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 secrets de cet écosystème comme exposés.
when:
- serveur_ops_tenant_voute_deposer | bool
- not ansible_check_mode