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