runner : separer le pouvoir de configurer de celui de materialiser

La ligne de partage est celle des voutes. serveur_ops calcule et configure --
voute du tenant, SSH chez lui. serveur_ops_site materialise -- voute du SITE,
API de l'hyperviseur, jamais de SSH chez un tenant.

Un runner par tenant qui materialiserait mettrait la voute du SITE en N
exemplaires. Un runner unique qui ferait tout traverserait le default-deny
inter-tenant et rendrait l'emancipation impossible.

Le role depose la voute CHIFFREE et relit l'en-tete apres avoir ecrit : sans
`decrypt: false`, Ansible dechiffre la source quand il detient le mot de passe
-- constate le jour meme, 776 octets en clair au lieu de 3465. Controle negatif
fait, la garde mord.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-08-24 15:30:03 -04:00
parent f248bb084f
commit 2b55761fba
12 changed files with 279 additions and 6 deletions

View file

@ -1,5 +1,47 @@
# CHANGELOG — Set-OPS # 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à ## 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 Un écosystème ne naît pas autoportant. Il lui faut une forge pour lire son génome, une

View file

@ -20,8 +20,8 @@
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | | | 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. | | 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). | | 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. | | 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 : 31 rôles, 82 flux, schéma + matrice OK. | | 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). | | 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 | | 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. | | 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. | | 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). | | 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. | | 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). | | 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). | | 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. | | 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. | | 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. | | 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). | | 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). | | 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)). | | 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). | | 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). | | 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. | | 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. | | 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. | | 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). | | P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 43 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |

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) | | 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 | | 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 | | 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 | | 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` *Rôles retirés (2026-07-04, supersédés ou hors conception)* : `serveur_sendmail`

View file

@ -67,6 +67,9 @@ couches:
# Le poste d'exploitation vient APRES la forge : il clone le genome depuis # Le poste d'exploitation vient APRES la forge : il clone le genome depuis
# elle. Le placer plus tot le laisserait sans source. # elle. Le placer plus tot le laisserait sans source.
- serveur_ops - 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 - 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." 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."

View file

@ -118,6 +118,15 @@ groupes:
raison: "Un cache apt ne depend d'aucun service de l'ecosysteme : il ne fait que relayer et retenir." 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." 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: serveur_nextcloud:
requiert_groupes_actifs: requiert_groupes_actifs:
- serveur_postgresql - serveur_postgresql

View file

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

View file

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

View file

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

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

View file

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

View file

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