Set-OPS-Public/playbooks/proxmox/supprimer_vm_debian.yml

142 lines
6.2 KiB
YAML
Raw Normal View History

---
# DESTRUCTIF : supprime des VM Proxmox par VMID. Réservé à un from-zero explicite.
# Sécurités : n'agit QUE sur les VMID passés en `cibles_vmid` ET présents dans le
# cluster ; refuse si le modèle (proxmox_clone_vmid_modele) est dans la liste.
- name: Supprimer des VM Debian du cluster Proxmox (from-zero)
hosts: localhost
connection: local
gather_facts: false
become: false
vars:
setops_inventaires: [lab, principal, production]
proxmox : pools créés, jeton normalisé, reliquat de voûte supprimé Reconnaissance en lecture seule de l'API du cluster, avec le jeton de la voûte. Trois valeurs devinées étaient fausses, et deux défauts bloquants sont apparus. Corrigé d'après le cluster - stockages : truenas-dbsql manquait ; le catalogue ne garde que ceux qui portent `images` (PBS, cephFS, local et truenas iSCSI n'accueillent pas de disque de VM) ; - ponts : vmbr0 avait été omis, et l'uniformité sur les trois nœuds n'avait pas été vérifiée — un pont partiel empêche la VM de démarrer sur certains nœuds. Pools Chezlepro-17 et Technolibre-11 créés, dérivés comme le reste. Les pools Env.Tenant antérieurs (Prod.Chezlepro, Lab.KBR...) sont l'ancien monde : on n'y touche pas et on n'y verse pas la flotte générée. Diff réel : 2 pools ajoutés, 0 retiré, 1 VM sur 38 déplacée — infra-pki-01, qui n'avait aucun pool. Défaut bloquant : make creer-vm aurait échoué en 401 proxmoxer recompose `utilisateur!nom` à partir d'api_user et d'api_token_id. La voûte stocke la forme complète, que les playbooks passaient telle quelle, d'où un 401 muet — alors que le même jeton fonctionne en curl. Mesuré des deux côtés avec un module en lecture seule : forme complète = 401, forme courte = OK. Normalisation par split('!') | last, qui accepte les deux écritures. Reliquat proxmox.vault.yml supprimé Toléré « en compatibilité », il restait le seul porteur du jeton chez Technolibre — et comme *.vault.yml est gitignoré, ce jeton ne voyageait avec aucun dépôt : une voûte unique (D-19) qui ne l'était pas. Migration faite en mémoire, avec relecture et aller-retour de chiffrement vérifiés avant écriture ; fichier supprimé, listes de chargement des playbooks nettoyées, validé par un appel API réel ne chargeant que all/vault.yml. La garde qui manquait voute.py verifier ne comparait que le gabarit — il disait « complet » pendant qu'un secret vivait ailleurs. Il contrôle maintenant aussi la voûte réelle quand ANSIBLE_VAULT_PASSWORD_FILE la rend déchiffrable : noms de clés seulement, jamais de valeur, et vérification sautée sans mot de passe. Elle a trouvé un second trou dès son premier passage : la voûte réelle de Technolibre n'a ni vault_nextcloud_admin ni vault_nextcloud_oidc, que le plan exige. Non corrigé — générer ces secrets est une décision, et celui d'OIDC doit correspondre à ce que Keycloak connaîtra. 27 preuves OK. --syntax-check et ansible-lint (production) sur les playbooks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:52:44 -04:00
# Voute UNIQUE de l'instance : all/vault.yml porte TOUS les secrets, jeton
# d'hyperviseur compris (D-19). `proxmox.vault.yml` a ete retire le 2026-08-03 :
# tolere "en compatibilite", il restait le SEUL porteur du jeton chez un tenant,
# et il etait gitignore — une voute unique qui ne l'etait pas, dans un fichier
# qui ne voyageait avec aucun depot. P18 ne pouvait pas le voir : il verifie le
# gabarit, pas le contenu reel.
# `proxmox.local.yml` reste une surcharge locale volontaire, non versionnee.
setops_gv_proxmox: [proxmox.yml, proxmox.local.yml, all/vault.yml]
intégrations : le rôle déclare sa politique ; le cluster passe à l'hébergeur Deux corrections de propriété, l'une dans le plan, l'autre dans les intrants. 1. Intégrations universelles (D-33/D-34, P26) Le plan portait 57 lignes d'intégration écrites à la main, dont 28 disaient oui à quelque chose de vrai pour tous les hôtes. Elles n'existaient que pour être oubliées — et elles l'avaient été : dans Chezlepro, backup-01 et infra-pki-01 n'étaient ni supervisés, ni journalisés, ni certifiés. Le rôle déclare désormais sa politique une fois, dans meta/integration.yml ; le plan ne garde que les vrais choix et refuse la recopie. Les exemptions se dérivent du service rendu (sauf_role), jamais d'un nom d'hôte : l'AC ne s'enrôle pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace. Une seule fonction de résolution — integrations_de() — lue par l'inventaire, la voûte et le panneau. Sans le passage par la voûte, les secrets des intégrations universelles auraient cessé d'être exigés et P18 serait passé au vert sur une voûte incomplète. Vérifié : diff vide sur Technolibre (la politique reproduit exactement les 41 lignes retirées) ; sur Chezlepro, exactement les groupes manquants, et pas client_pki sur infra-pki-01. 2. Vue Intégrations : la matrice La fiche montrait les intégrations d'UN serveur ; le trou de Chezlepro n'a pas été trouvé par le panneau mais par le devis de pare-feu. Matrice serveurs x intégrations : colonnes de politique en lecture seule, facultatives cochables sur place, ligne de couverture n/N qui rend le motif visible sans le juger. 3. Propriété des intrants (D-35/D-36, P27) Le cluster Proxmox appartient à l'hébergeur, comme sa fabric et sa frontière. Recopié chez chaque tenant, son inventaire avait déjà divergé : deux listes de stockages contradictoires pour le même matériel. API/nœuds/stockages/ponts vont dans proxmox-hebergeur.yml, à côté d'underlay.yml, dont le chemin se dérive — l'hébergeur reste non déclaré (D-17). Restent au tenant son golden template et ses défauts de placement. Le panneau nomme désormais le propriétaire de chaque section : éditer une section « hébergeur » vaut pour tous ses tenants, et l'écran ne le disait pas. 26 preuves OK, 0 échec. --syntax-check des deux playbooks Proxmox. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:09:22 -04:00
# Le CLUSTER appartient a l'HEBERGEUR (API, noeuds, stockages, ponts). Son fichier
# vit dans le depot de l'hebergeur, que le symlink `underlay.yml` designe deja
# (D-14/D-17) : on en DERIVE le chemin plutot que de redeclarer l'hebergeur. Absent
# (depot sans underlay monte), le `stat` echoue simplement et rien n'est charge.
setops_proxmox_hebergeur: "{{ (playbook_dir ~ '/../../underlay.yml') | realpath | dirname }}/proxmox-hebergeur.yml"
# cibles_vmid : liste d'entiers, passée en -e (sinon dérivée des hôtes actifs).
cibles_vmid: "{{ groups['hotes_actifs'] | map('extract', hostvars, 'proxmox_vmid') | map('int') | list }}"
tasks:
- name: Localiser les fichiers de variables Proxmox
ansible.builtin.stat:
path: "{{ playbook_dir }}/../../instance/inventories/{{ item.0 }}/group_vars/{{ item.1 }}"
loop: "{{ setops_inventaires | product(setops_gv_proxmox) | list }}"
loop_control:
label: "{{ item.0 }}/{{ item.1 }}"
register: proxmox_fichiers_variables
- name: Charger les variables Proxmox non sensibles
ansible.builtin.include_vars:
file: "{{ item.stat.path }}"
loop: "{{ proxmox_fichiers_variables.results }}"
when:
- item.stat.exists
- item.stat.path is not search('vault')
intégrations : le rôle déclare sa politique ; le cluster passe à l'hébergeur Deux corrections de propriété, l'une dans le plan, l'autre dans les intrants. 1. Intégrations universelles (D-33/D-34, P26) Le plan portait 57 lignes d'intégration écrites à la main, dont 28 disaient oui à quelque chose de vrai pour tous les hôtes. Elles n'existaient que pour être oubliées — et elles l'avaient été : dans Chezlepro, backup-01 et infra-pki-01 n'étaient ni supervisés, ni journalisés, ni certifiés. Le rôle déclare désormais sa politique une fois, dans meta/integration.yml ; le plan ne garde que les vrais choix et refuse la recopie. Les exemptions se dérivent du service rendu (sauf_role), jamais d'un nom d'hôte : l'AC ne s'enrôle pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace. Une seule fonction de résolution — integrations_de() — lue par l'inventaire, la voûte et le panneau. Sans le passage par la voûte, les secrets des intégrations universelles auraient cessé d'être exigés et P18 serait passé au vert sur une voûte incomplète. Vérifié : diff vide sur Technolibre (la politique reproduit exactement les 41 lignes retirées) ; sur Chezlepro, exactement les groupes manquants, et pas client_pki sur infra-pki-01. 2. Vue Intégrations : la matrice La fiche montrait les intégrations d'UN serveur ; le trou de Chezlepro n'a pas été trouvé par le panneau mais par le devis de pare-feu. Matrice serveurs x intégrations : colonnes de politique en lecture seule, facultatives cochables sur place, ligne de couverture n/N qui rend le motif visible sans le juger. 3. Propriété des intrants (D-35/D-36, P27) Le cluster Proxmox appartient à l'hébergeur, comme sa fabric et sa frontière. Recopié chez chaque tenant, son inventaire avait déjà divergé : deux listes de stockages contradictoires pour le même matériel. API/nœuds/stockages/ponts vont dans proxmox-hebergeur.yml, à côté d'underlay.yml, dont le chemin se dérive — l'hébergeur reste non déclaré (D-17). Restent au tenant son golden template et ses défauts de placement. Le panneau nomme désormais le propriétaire de chaque section : éditer une section « hébergeur » vaut pour tous ses tenants, et l'écran ne le disait pas. 26 preuves OK, 0 échec. --syntax-check des deux playbooks Proxmox. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:09:22 -04:00
- name: Localiser le fichier Proxmox de l'hebergeur
ansible.builtin.stat:
path: "{{ setops_proxmox_hebergeur }}"
register: proxmox_fichier_hebergeur
- name: Charger le cluster de l'hebergeur (autoritaire sur ses propres cles)
ansible.builtin.include_vars:
file: "{{ setops_proxmox_hebergeur }}"
when: proxmox_fichier_hebergeur.stat.exists
- name: Charger les secrets Proxmox (Vault)
ansible.builtin.include_vars:
file: "{{ item.stat.path }}"
loop: "{{ proxmox_fichiers_variables.results }}"
when:
- item.stat.exists
- item.stat.path is search('vault')
no_log: true
- name: Résoudre les identifiants d'API
ansible.builtin.set_fact:
api_host: "{{ proxmox_api_host | default(lookup('env', 'PROXMOX_API_HOST'), true) }}"
api_port: "{{ proxmox_api_port | default('8006', true) }}"
api_user: "{{ proxmox_api_user | default(lookup('env', 'PROXMOX_API_USER'), true) }}"
proxmox : pools créés, jeton normalisé, reliquat de voûte supprimé Reconnaissance en lecture seule de l'API du cluster, avec le jeton de la voûte. Trois valeurs devinées étaient fausses, et deux défauts bloquants sont apparus. Corrigé d'après le cluster - stockages : truenas-dbsql manquait ; le catalogue ne garde que ceux qui portent `images` (PBS, cephFS, local et truenas iSCSI n'accueillent pas de disque de VM) ; - ponts : vmbr0 avait été omis, et l'uniformité sur les trois nœuds n'avait pas été vérifiée — un pont partiel empêche la VM de démarrer sur certains nœuds. Pools Chezlepro-17 et Technolibre-11 créés, dérivés comme le reste. Les pools Env.Tenant antérieurs (Prod.Chezlepro, Lab.KBR...) sont l'ancien monde : on n'y touche pas et on n'y verse pas la flotte générée. Diff réel : 2 pools ajoutés, 0 retiré, 1 VM sur 38 déplacée — infra-pki-01, qui n'avait aucun pool. Défaut bloquant : make creer-vm aurait échoué en 401 proxmoxer recompose `utilisateur!nom` à partir d'api_user et d'api_token_id. La voûte stocke la forme complète, que les playbooks passaient telle quelle, d'où un 401 muet — alors que le même jeton fonctionne en curl. Mesuré des deux côtés avec un module en lecture seule : forme complète = 401, forme courte = OK. Normalisation par split('!') | last, qui accepte les deux écritures. Reliquat proxmox.vault.yml supprimé Toléré « en compatibilité », il restait le seul porteur du jeton chez Technolibre — et comme *.vault.yml est gitignoré, ce jeton ne voyageait avec aucun dépôt : une voûte unique (D-19) qui ne l'était pas. Migration faite en mémoire, avec relecture et aller-retour de chiffrement vérifiés avant écriture ; fichier supprimé, listes de chargement des playbooks nettoyées, validé par un appel API réel ne chargeant que all/vault.yml. La garde qui manquait voute.py verifier ne comparait que le gabarit — il disait « complet » pendant qu'un secret vivait ailleurs. Il contrôle maintenant aussi la voûte réelle quand ANSIBLE_VAULT_PASSWORD_FILE la rend déchiffrable : noms de clés seulement, jamais de valeur, et vérification sautée sans mot de passe. Elle a trouvé un second trou dès son premier passage : la voûte réelle de Technolibre n'a ni vault_nextcloud_admin ni vault_nextcloud_oidc, que le plan exige. Non corrigé — générer ces secrets est une décision, et celui d'OIDC doit correspondre à ce que Keycloak connaîtra. 27 preuves OK. --syntax-check et ansible-lint (production) sur les playbooks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:52:44 -04:00
# `split('!')[-1]` : proxmoxer recompose `utilisateur!nom` a partir d'api_user
# et d'api_token_id. La forme COMPLETE donne un 401 muet (verifie contre le
# cluster le 2026-08-03). Sans effet si la voute stocke deja le nom seul.
api_tid: "{{ (proxmox_api_token_id | default(lookup('env', 'PROXMOX_API_TOKEN_ID'), true)) | string | split('!') | last }}"
api_secret: "{{ proxmox_api_token_secret | default(lookup('env', 'PROXMOX_API_TOKEN_SECRET'), true) }}"
no_log: true
- name: Interroger l'inventaire du cluster
ansible.builtin.uri:
url: "https://{{ api_host }}:{{ api_port }}/api2/json/cluster/resources?type=vm"
method: GET
headers:
Authorization: "PVEAPIToken={{ api_user }}!{{ api_tid }}={{ api_secret }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
register: cluster_vms
check_mode: false
no_log: true
- name: Établir la carte VMID -> nœud pour nos cibles présentes
ansible.builtin.set_fact:
vm_a_supprimer: >-
{{ cluster_vms.json.data
| selectattr('vmid', 'in', cibles_vmid | map('int') | list)
| list }}
- name: "SÉCURITÉ — le modèle n'est PAS ciblé (VMID {{ proxmox_clone_vmid_modele }})"
ansible.builtin.assert:
that: "(proxmox_clone_vmid_modele | int) not in (cibles_vmid | map('int') | list)"
fail_msg: "REFUS : le VMID du modèle est dans la liste à supprimer."
- name: Récapitulatif avant suppression
ansible.builtin.debug:
msg: >-
Suppression de {{ vm_a_supprimer | length }} VM :
{{ vm_a_supprimer | map(attribute='name') | list }}
(VMID {{ vm_a_supprimer | map(attribute='vmid') | list }})
- name: Arrêter chaque VM (force)
community.general.proxmox_kvm:
api_host: "{{ api_host }}"
api_port: "{{ api_port | int }}"
api_user: "{{ api_user }}"
api_token_id: "{{ api_tid }}"
api_token_secret: "{{ api_secret }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ item.node }}"
vmid: "{{ item.vmid | int }}"
state: stopped
force: true
timeout: 120
loop: "{{ vm_a_supprimer }}"
loop_control:
label: "{{ item.name }} ({{ item.vmid }})"
no_log: true
failed_when: false
- name: Supprimer chaque VM
community.general.proxmox_kvm:
api_host: "{{ api_host }}"
api_port: "{{ api_port | int }}"
api_user: "{{ api_user }}"
api_token_id: "{{ api_tid }}"
api_token_secret: "{{ api_secret }}"
validate_certs: "{{ proxmox_validate_certs | default(false) | bool }}"
node: "{{ item.node }}"
vmid: "{{ item.vmid | int }}"
state: absent
timeout: 120
loop: "{{ vm_a_supprimer }}"
loop_control:
label: "{{ item.name }} ({{ item.vmid }})"
no_log: true