Le runner fabriquait sa paire a sa naissance : reconstruit, il avait une identite que la flotte ne connaissait pas (refuse sur les 13 machines de Technolibre). La cle privee vit dans la voute du locataire ; serveur_ops_tenant la remet et exige que sa cle publique soit declaree au plan (garde eprouvee en defaut). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
254 lines
10 KiB
YAML
254 lines
10 KiB
YAML
---
|
|
- 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
|
|
|
|
# --- ARMER LE RUNNER ---------------------------------------------------------
|
|
#
|
|
# Ce qui suit ne se declenche que si un humain l'a demande (serveur_ops_tenant_cle_deposer).
|
|
|
|
- name: Exiger une source pour la cle de la voute
|
|
ansible.builtin.assert:
|
|
that:
|
|
- serveur_ops_tenant_cle_source | length > 0
|
|
fail_msg: >-
|
|
serveur_ops_tenant_cle_deposer est vrai, mais aucune
|
|
`serveur_ops_tenant_cle_source` n'est nommee. Armer un runner sans dire
|
|
avec QUELLE cle n'a pas de sens — et poser un fichier vide le laisserait croire
|
|
arme.
|
|
when: serveur_ops_tenant_cle_deposer | bool
|
|
|
|
- name: Preparer le dossier des cles du runner
|
|
ansible.builtin.file:
|
|
path: "{{ serveur_ops_tenant_racine }}/.config"
|
|
state: directory
|
|
owner: "{{ serveur_ops_tenant_utilisateur }}"
|
|
group: "{{ serveur_ops_tenant_utilisateur }}"
|
|
mode: "0700"
|
|
when:
|
|
- serveur_ops_tenant_cle_deposer | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Deposer la cle de la voute
|
|
ansible.builtin.copy:
|
|
src: "{{ serveur_ops_tenant_cle_source }}"
|
|
dest: "{{ serveur_ops_tenant_cle_destination }}"
|
|
owner: "{{ serveur_ops_tenant_utilisateur }}"
|
|
group: "{{ serveur_ops_tenant_utilisateur }}"
|
|
mode: "0600"
|
|
no_log: true
|
|
when:
|
|
- serveur_ops_tenant_cle_deposer | bool
|
|
- not ansible_check_mode
|
|
|
|
# ECRIRE, PUIS RELIRE — comme pour la voute, et pour la faute SYMETRIQUE : ici on ne
|
|
# craint pas un chiffre devenu clair, mais une cle qui serait en fait une VOUTE. Copier le
|
|
# mauvais fichier laisse un runner qui se croit arme et echoue au premier secret, tres
|
|
# loin d'ici. On mesure aussi qu'elle n'est pas vide : un fichier de mot de passe vide est
|
|
# accepte par Ansible et n'ouvre rien.
|
|
- name: Relire la cle deposee
|
|
ansible.builtin.stat:
|
|
path: "{{ serveur_ops_tenant_cle_destination }}"
|
|
checksum_algorithm: sha256
|
|
register: serveur_ops_tenant_cle_posee
|
|
when:
|
|
- serveur_ops_tenant_cle_deposer | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Exiger une cle non vide, en 0600, et qui ne soit pas une voute
|
|
ansible.builtin.assert:
|
|
that:
|
|
- serveur_ops_tenant_cle_posee.stat.exists
|
|
- serveur_ops_tenant_cle_posee.stat.size > 0
|
|
- serveur_ops_tenant_cle_posee.stat.mode == '0600'
|
|
fail_msg: >-
|
|
La cle deposee est absente, vide, ou lisible au-dela de son proprietaire.
|
|
Un runner arme d'une cle vide se croit arme et echoue au premier secret,
|
|
tres loin d'ici.
|
|
when:
|
|
- serveur_ops_tenant_cle_deposer | bool
|
|
- not ansible_check_mode
|
|
|
|
# --- L'IDENTITE SSH DU RUNNER (voir defaults/main.yml, 2026-09-30) ----------------------
|
|
- name: Remettre au runner sa cle SSH (conservee dans la voute)
|
|
ansible.builtin.copy:
|
|
content: "{{ serveur_ops_tenant_ssh_cle | trim }}\n"
|
|
dest: "{{ serveur_ops_tenant_ssh_chemin }}"
|
|
owner: "{{ serveur_ops_tenant_utilisateur }}"
|
|
group: "{{ serveur_ops_tenant_utilisateur }}"
|
|
mode: "0600"
|
|
no_log: true
|
|
when:
|
|
- serveur_ops_tenant_ssh_cle | length > 0
|
|
- not ansible_check_mode
|
|
|
|
- name: Relire la cle publique qui en decoule
|
|
ansible.builtin.command: ssh-keygen -y -f {{ serveur_ops_tenant_ssh_chemin }}
|
|
changed_when: false
|
|
register: serveur_ops_tenant_ssh_publique
|
|
when:
|
|
- serveur_ops_tenant_ssh_cle | length > 0
|
|
- not ansible_check_mode
|
|
|
|
- name: Poser la cle publique a cote de la privee
|
|
ansible.builtin.copy:
|
|
content: "{{ serveur_ops_tenant_ssh_publique.stdout.split()[:2] | join(' ') }} setops@{{ inventory_hostname }}.{{ domaine_interne }}\n"
|
|
dest: "{{ serveur_ops_tenant_ssh_chemin }}.pub"
|
|
owner: "{{ serveur_ops_tenant_utilisateur }}"
|
|
group: "{{ serveur_ops_tenant_utilisateur }}"
|
|
mode: "0644"
|
|
when:
|
|
- serveur_ops_tenant_ssh_cle | length > 0
|
|
- not ansible_check_mode
|
|
|
|
# LA GARDE QUI AURAIT VU L'ECART AVANT LE DEPLOIEMENT : la cle du runner doit etre une de
|
|
# celles que le plan declare, puisque c'est celle-la que chaque machine du locataire recoit
|
|
# a sa naissance. Sinon, le runner arme se verrait refuser toute la flotte, tres loin d'ici.
|
|
- name: Exiger que la cle du runner soit declaree au plan
|
|
ansible.builtin.assert:
|
|
that:
|
|
- (serveur_ops_tenant_ssh_publique.stdout.split()[:2] | join(' '))
|
|
in (ssh_baseline_cles_admin | default([]) | map(attribute='cle')
|
|
| map('split') | map('batch', 2) | map('first') | map('join', ' ') | list)
|
|
fail_msg: >-
|
|
La cle SSH du runner (issue de la voute) n'est PAS declaree dans
|
|
`ssh_baseline_cles_admin` : les machines du locataire, nees avec la cle declaree, le
|
|
refuseront toutes. Declarer sa cle publique au plan, ou corriger la voute.
|
|
when:
|
|
- serveur_ops_tenant_ssh_cle | length > 0
|
|
- not ansible_check_mode
|
|
|
|
- name: Dire qu'aucune identite n'est conservee
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
Aucune `vault_runner_cle_ssh` dans la voute : le runner garde la paire qu'il s'est
|
|
fabriquee. Sa reconstruction lui en donnera une autre, que la flotte refusera.
|
|
when: serveur_ops_tenant_ssh_cle | length == 0
|
|
|
|
# --- Sonde de supervision (docs/supervision-conception.md) --------------------------
|
|
# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la
|
|
# fait tourner et pousse le verdict, sans savoir ce qu'elle mesure.
|
|
- name: Assurer le repertoire des sondes de supervision
|
|
ansible.builtin.file:
|
|
path: /usr/local/lib/setops/sondes
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Deposer la sonde « voute »
|
|
ansible.builtin.template:
|
|
src: sonde-voute.sh.j2
|
|
dest: /usr/local/lib/setops/sondes/voute.sh
|
|
owner: root
|
|
group: root
|
|
mode: "0750"
|