2026-07-07 06:45:12 -04:00
|
|
|
---
|
|
|
|
|
# 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]
|
frontière nord/sud : devis dérivé, lien de transit et les deux routes
La bordure devient un artefact dérivé, comme le devis switch — et le chemin
qui y mène est enfin déclaré.
`make devis-opnsense` (+ preuve P24) dérive la politique de bordure du
registre des flux : les flux `pair: externe`, que `resoudre_flux.py` saute
volontairement parce qu'ils relèvent de la frontière et non du pare-feu
d'hôte. Aucun port, aucune adresse, aucun nom d'hôte dans le générateur.
Le lien manquait dans tous les fichiers : le devis switch ne contenait pas
une seule `ip route`. Un réseau underlay portant `passerelle_sortie` le
déclare — il vit dans l'underlay et non dans un tenant parce que la
frontière route vers TOUS les supernets tenants par le même saut, donc il
ne peut dériver d'aucun `index`. `devis-reseau` en tire deux routes :
l'aller (sortie générale) et le retour vers l'administration, dont l'absence
a coûté la passe de déploiement du 2026-07-29 — la réponse revient au
pare-feu par une autre interface que celle où l'état a été créé, et se fait
jeter en silence.
Les réseaux d'administration viennent de l'intrant `nftables_admin_ssh` :
même source unique que la garde anti-lockout des nftables et l'alias
SETOPS_ADMIN. Les trois pare-feux et les routes ne peuvent plus diverger.
La frontière est réglable depuis la console (section « Frontière » du
panneau Intrants) ; les identifiants d'API restent interdits d'écriture par
le GUI et vivent dans la voûte.
Correctifs de la même passe :
- le panneau refusait d'enregistrer les intrants de la frontière : le
garde-fou confondait une référence de voûte `{{ vault_* }}` préservée
avec un secret soumis. Il regarde désormais la valeur, pas le nom.
- `supprimer_vm_debian.yml` ne chargeait que `proxmox.vault.yml` pour ses
secrets ; retirer ce reliquat aurait cassé `make detruire`. Aligné sur le
playbook de clonage, voûte unique en dernier.
- documentation : la voûte est unique, `proxmox.vault.yml` n'est qu'un
reliquat de compatibilité.
Preuves : 24 OK, 0 échec. Cas de rejet du validateur d'underlay exercés un
par un ; résolution du jeton Proxmox vérifiée en exécution réelle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 19:32:04 -04:00
|
|
|
# Voute Proxmox : la voute UNIQUE de l'instance (all/vault.yml) est autoritaire ;
|
|
|
|
|
# `proxmox.vault.yml` reste accepte en compat legacy pour les instances qui en
|
|
|
|
|
# ont encore une. all/vault.yml en dernier => il l'emporte sur les memes cles.
|
|
|
|
|
setops_gv_proxmox: [proxmox.yml, proxmox.vault.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"
|
2026-07-07 06:45:12 -04:00
|
|
|
# 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
|
|
|
|
|
|
2026-07-07 06:45:12 -04:00
|
|
|
- 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) }}"
|
|
|
|
|
api_tid: "{{ proxmox_api_token_id | default(lookup('env', 'PROXMOX_API_TOKEN_ID'), true) }}"
|
|
|
|
|
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 }}
|
|
|
|
|
|
Mise en conformité prouvable : registre d'affirmations + make prouver
Le dépôt fait / explique / prouve ce qu'il affirme, vérifiable en une commande.
- Phase 1 : docs/audit/affirmations.md — 54 affirmations publiques tracées vers
une commande de preuve et un statut (✅/🟡/❌/⚪).
- Phase 2 : CLAUDE.md réduit à un pointeur mince ; contradiction SSH levée (le
code applique déjà PasswordAuthentication no + AuthenticationMethods publickey,
conforme à AGENTS.md) ; section AGENTS « Codex » → « agents IA ».
- Phase 3 : parcours démarrage réparé (QUICKSTART renvoyait à un modèle absent,
chemins de voûte faux, commandes make périmées) ; make verifier vert
(ansible-lint 33 → 0 : site.yml généré nommé, pipefail, name[template]) ;
voûte Proxmox unifiée lue par le clonage (all/vault.yml).
- Phase 4 : make prouver → docs/audit/preuve-<date>.md, harnais rejouable qui
rappelle l'outillage existant (aucune validation réimplémentée).
- Phase 5 : parcours QUICKSTART prouvé hors-ligne sur le socle ; modèle socle
rendu valide (autorite interne → auto-heberge) ; split-brain d'inventaire
corrigé (repli sur le répertoire existant, pas principal/).
make prouver : 15 OK, 0 échec, 1 sautée (voûte). ansible-lint : 0 failure.
Écarts découverts en cours de traitement (AFF-097..100) : tous résolus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 19:53:18 -04:00
|
|
|
- name: "SÉCURITÉ — le modèle n'est PAS ciblé (VMID {{ proxmox_clone_vmid_modele }})"
|
2026-07-07 06:45:12 -04:00
|
|
|
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
|