Portabilité from-zero : 4 correctifs débusqués par la reconstruction
Reconstruction complète prouvée (14 VM + sauvegardes + AC supprimées, puis `make myDay` rebâtit tout ; `make valider` entièrement vert). Bugs corrigés : - client_pki : empreinte du root CA dérivée dynamiquement de l'autorité (au lieu d'une valeur figée en Vault) — une AC régénérée a une empreinte neuve. - serveur_keycloak : assignation de rôle tolérante aux utilisateurs absents (annuaire vide sur un from-zero). - serveur_prometheus : ne scrute que les hôtes ACTIFS (client_metrique ∩ hotes_actifs). - nftables résolu : compatible Docker — remplace la seule table setops_flux (pas de flush ruleset, préserve les tables Docker) + forward autorise docker0/established. Sans ça, forward policy drop coupait Collabora (conteneur). Ajoute playbooks/proxmox/supprimer_vm_debian.yml (suppression par VMID, garde-fous). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
89d0e4e821
commit
72a5fdbbc4
6 changed files with 180 additions and 6 deletions
27
CHANGELOG.md
27
CHANGELOG.md
|
|
@ -1,5 +1,32 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-07-07 — Reconstruction from-zero PROUVÉE (preuve de portabilité « sans réserve »)
|
||||
|
||||
Les 14 VM du lab (+ sauvegardes + AC) supprimées, puis `make myDay` a reconstruit l'écosystème
|
||||
POC **de rien** : 13 hôtes déployés (0 échec), et `make valider` **entièrement vert** (Prometheus
|
||||
6/6 UP, 6 vhosts HTTPS, courriel bout-en-bout remis, 6 dépôts restic restaurés) — le tout sous
|
||||
pare-feu actif. 4 bugs de portabilité débusqués et corrigés dans le moteur :
|
||||
|
||||
### Corrigé
|
||||
- **client_pki : empreinte du root CA dérivée dynamiquement** (au lieu d'une valeur figée en Vault).
|
||||
Une AC régénérée (from-zero) a une empreinte neuve ; le rôle la lit désormais de l'autorité
|
||||
elle-même (`step certificate fingerprint`, délégué au nœud step-ca), source de vérité.
|
||||
`client_pki_ca_fingerprint_override` permet un épinglage explicite.
|
||||
- **serveur_keycloak : assignation de rôle tolérante aux utilisateurs absents.** Sur un annuaire
|
||||
vide (from-zero), assigner un rôle à un user inexistant échouait ; on vérifie désormais son
|
||||
existence (Keycloak fédère LDAP à la demande) et on saute proprement sinon.
|
||||
- **serveur_prometheus : ne scrute que les hôtes ACTIFS** (`client_metrique ∩ hotes_actifs`). Un
|
||||
hôte planifié (non déployé) n'est plus une cible morte.
|
||||
- **Pare-feu nftables compatible Docker.** Le ruleset résolu (a) remplace **uniquement la table
|
||||
`setops_flux`** au lieu de `flush ruleset` (préserve les tables Docker : DNAT/forward des
|
||||
conteneurs) et (b) autorise `docker0` + `ct established,related` dans la chaîne `forward`. Sans
|
||||
ça, `forward policy drop` coupait Collabora (conteneur). Diagnostic prouvé au niveau paquet.
|
||||
|
||||
### Ajouté
|
||||
- **`playbooks/proxmox/supprimer_vm_debian.yml`** — suppression de VM par VMID (from-zero), avec
|
||||
garde-fous : n'agit que sur les VMID présents dans le cluster, refuse si le modèle est ciblé,
|
||||
secrets `no_log`.
|
||||
|
||||
## 2026-07-07
|
||||
|
||||
### Ajouté
|
||||
|
|
|
|||
116
playbooks/proxmox/supprimer_vm_debian.yml
Normal file
116
playbooks/proxmox/supprimer_vm_debian.yml
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
# 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]
|
||||
setops_gv_proxmox: [proxmox.yml, proxmox.vault.yml, proxmox.local.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')
|
||||
|
||||
- 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 }}
|
||||
|
||||
- name: "SÉCURITÉ — le modèle ({{ proxmox_clone_vmid_modele }}) n'est PAS ciblé"
|
||||
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
|
||||
|
|
@ -1,12 +1,27 @@
|
|||
---
|
||||
- name: Exiger les secrets PKI (Vault)
|
||||
# L'empreinte du root CA est la SOURCE DE VÉRITÉ de l'autorité elle-même : on la
|
||||
# dérive à chaud (robuste au from-zero — une AC régénérée a une empreinte neuve).
|
||||
# `client_pki_ca_fingerprint_override` permet d'épingler explicitement si besoin.
|
||||
- name: Dériver l'empreinte du root CA depuis l'autorité
|
||||
ansible.builtin.command:
|
||||
cmd: "step certificate fingerprint {{ serveur_step_ca_steppath | default('/etc/step-ca') }}/certs/root_ca.crt"
|
||||
delegate_to: "{{ groups['serveur_step_ca'][0] }}"
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
register: client_pki_fingerprint_ac
|
||||
|
||||
- name: Retenir l'empreinte (dérivée de l'AC, sauf override explicite)
|
||||
ansible.builtin.set_fact:
|
||||
client_pki_ca_fingerprint: "{{ client_pki_ca_fingerprint_override | default(client_pki_fingerprint_ac.stdout | trim, true) }}"
|
||||
|
||||
- name: Exiger l'empreinte AC et le mot de passe provisioner (Vault)
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- client_pki_ca_fingerprint | length > 0
|
||||
- client_pki_provisioner_password | length > 0
|
||||
fail_msg: >-
|
||||
client_pki_ca_fingerprint et client_pki_provisioner_password
|
||||
sont requis (a fournir via Ansible Vault).
|
||||
Empreinte du root CA indisponible (step-ca joignable et déployé ?) ;
|
||||
client_pki_provisioner_password requis (via Ansible Vault).
|
||||
|
||||
- name: Assurer le repertoire des trousseaux apt
|
||||
ansible.builtin.file:
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@
|
|||
KC={{ serveur_keycloak_home }}/bin/kcadm.sh
|
||||
"$KC" config credentials --server http://localhost:8080 --realm master \
|
||||
--user {{ serveur_keycloak_admin_user }} --password "$KC_ADMIN_PW" >/dev/null
|
||||
# L'utilisateur doit exister dans le realm (Keycloak fédère LDAP à la demande).
|
||||
# from-zero : l'annuaire peut être vide -> on n'échoue pas ; l'assignation
|
||||
# s'appliquera quand l'utilisateur existera (synchro / 1re connexion).
|
||||
if ! "$KC" get users -r {{ serveur_keycloak_realm }} -q username={{ item.user }} --fields username 2>/dev/null \
|
||||
| grep -q '"{{ item.user }}"'; then
|
||||
echo SETOPS_SKIP
|
||||
exit 0
|
||||
fi
|
||||
if "$KC" get-roles -r {{ serveur_keycloak_realm }} --uusername {{ item.user }} 2>/dev/null \
|
||||
| grep -q '"{{ item.role }}"'; then
|
||||
echo SETOPS_OK
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ scrape_configs:
|
|||
ca_file: {{ serveur_prometheus_metriques_ca }}
|
||||
{% endif %}
|
||||
static_configs:
|
||||
- targets: {{ groups.get(serveur_prometheus_groupe_metriques, [])
|
||||
- targets: {{ (groups.get(serveur_prometheus_groupe_metriques, []) | intersect(groups.get('hotes_actifs', [])))
|
||||
| map('extract', hostvars)
|
||||
| selectattr('ansible_host', 'defined')
|
||||
| map(attribute='ansible_host')
|
||||
|
|
|
|||
|
|
@ -232,7 +232,10 @@ def _rendre_nft(hote: str, regles: list[str]) -> str:
|
|||
f"#!/usr/sbin/nft -f\n"
|
||||
f"# GENERE par scripts/resoudre_flux.py depuis les meta/flux.yml — hote {hote}.\n"
|
||||
f"# APERCU inspectable — NON active. L'activation reste un geste dedie, teste par noeud.\n\n"
|
||||
f"flush ruleset\n\n"
|
||||
f"# Remplace UNIQUEMENT la table setops (pas de `flush ruleset` : préserve les\n"
|
||||
f"# tables de Docker — DNAT/forward des conteneurs, ex. Collabora).\n"
|
||||
f"table inet setops_flux {{}}\n"
|
||||
f"delete table inet setops_flux\n\n"
|
||||
f"table inet setops_flux {{\n"
|
||||
f" chain input {{\n"
|
||||
f" type filter hook input priority 0; policy drop;\n\n"
|
||||
|
|
@ -243,7 +246,12 @@ def _rendre_nft(hote: str, regles: list[str]) -> str:
|
|||
f" ip6 nexthdr icmpv6 accept\n\n"
|
||||
f"{corps}\n"
|
||||
f" }}\n\n"
|
||||
f" chain forward {{ type filter hook forward priority 0; policy drop; }}\n"
|
||||
f" chain forward {{\n"
|
||||
f" type filter hook forward priority 0; policy drop;\n"
|
||||
f" ct state established,related accept\n"
|
||||
f" iifname \"docker0\" accept\n"
|
||||
f" oifname \"docker0\" accept\n"
|
||||
f" }}\n"
|
||||
f" chain output {{ type filter hook output priority 0; policy accept; }}\n"
|
||||
f"}}\n"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue