From 20a5e4480347be3cb14cd3051dd079df598a3ab1 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Wed, 30 Sep 2026 17:08:55 -0400 Subject: [PATCH] client_backup : le premier rapport a Icinga part d'un marqueur a lui ; le pare-feu ne compte que ses critiques La copie de l'AC d'Icinga est deposee aussi par client_sante, plus tot : sur une flotte neuve elle ne changeait jamais ici, et rien ne partait (reconstruction de Chezlepro). client_backup retient desormais l'empreinte de l'Icinga qui l'a entendu, apres le rapport. eprouver_parefeu ne s'arrete plus sur les critiques presents avant l'activation. Co-Authored-By: Claude Opus 5.5 --- roles/client_backup/defaults/main.yml | 2 ++ roles/client_backup/handlers/main.yml | 11 +++++- roles/client_backup/tasks/verifier.yml | 48 ++++++++++++++++++++------ scripts/eprouver_parefeu.py | 12 +++++-- scripts/tests/test_restauration.py | 16 +++++---- 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/roles/client_backup/defaults/main.yml b/roles/client_backup/defaults/main.yml index cde91f8..88fe549 100644 --- a/roles/client_backup/defaults/main.yml +++ b/roles/client_backup/defaults/main.yml @@ -113,6 +113,8 @@ client_backup_icinga_hote: "{{ (groups['serveur_icinga'] | default([]) | first) client_backup_icinga_utilisateur: "setops-depot" client_backup_icinga_motdepasse: "{{ vault_icinga_api_depot | default('') }}" client_backup_ca_verification: "/etc/setops/icinga-ca.crt" +# L'Icinga auquel ce noeud a deja rapporte (empreinte de son AC) — voir tasks/verifier.yml. +client_backup_marque_icinga: "/etc/setops/client_backup-icinga-entendu" client_backup_icinga_ca_source: "/var/lib/icinga2/ca/ca.crt" # `ttl` du resultat passif : au-dela, Icinga perime le service de lui-meme — c'est ce qui diff --git a/roles/client_backup/handlers/main.yml b/roles/client_backup/handlers/main.yml index 41aff23..4f0163a 100644 --- a/roles/client_backup/handlers/main.yml +++ b/roles/client_backup/handlers/main.yml @@ -4,7 +4,7 @@ daemon_reload: true # PREMIER RAPPORT A UN ICINGA QUI NE NOUS A JAMAIS ENTENDUS — voir `tasks/verifier.yml`. -# Trois temps, dans cet ordre (les gestionnaires s'executent dans l'ordre de ce fichier) : +# Trois temps, puis la trace, dans cet ordre (les gestionnaires s'executent dans l'ordre de ce fichier) : # deposer, pour que le depot porte un instantane de CETTE incarnation ; verifier le depot ; # verifier qu'il se restaure. Les unites sont `oneshot` : chacune rend la main une fois son # rapport parti, et echoue — bruyamment — si la sauvegarde refuse (etat d'avant non remis) @@ -26,3 +26,12 @@ name: setops-verification-restauration.service state: started listen: Premier rapport a Icinga + +- name: Retenir l'Icinga qui nous a entendus + ansible.builtin.copy: + content: "{{ client_backup_icinga_empreinte }}\n" + dest: "{{ client_backup_marque_icinga }}" + owner: root + group: root + mode: "0600" + listen: Premier rapport a Icinga diff --git a/roles/client_backup/tasks/verifier.yml b/roles/client_backup/tasks/verifier.yml index a9c12e5..f65639a 100644 --- a/roles/client_backup/tasks/verifier.yml +++ b/roles/client_backup/tasks/verifier.yml @@ -37,16 +37,6 @@ delegate_to: "{{ client_backup_icinga_hote }}" register: client_backup_ca_icinga -# UN ICINGA QUI NE NOUS A JAMAIS ENTENDUS (2026-09-30). Ses services `sauvegarde` et -# `restauration` sont CRITIQUES tant qu'aucun rapport n'est arrive — c'est voulu, le silence -# doit alerter. Mais les minuteurs ne rapportent que toutes les 4 h (depot) et le DIMANCHE -# (restauration) : apres chaque reconstruction, Icinga restait rouge jusqu'a une semaine, -# et on declenchait les controles a la main. -# -# Le noeud ne peut pas demander a Icinga s'il l'a deja entendu : son compte ne sait que -# DEPOSER. Le signal fiable est ICI — l'AC d'Icinga que le noeud recopie. Un Icinga refait a -# une AC neuve ; un noeud refait n'en a pas encore de copie. Dans les deux cas cette tache -# change, et le noeud rapporte aussitot (voir les gestionnaires). - name: Déposer l'AC d'Icinga pour la vérification du pair when: client_backup_ca_presente.stat.exists ansible.builtin.copy: @@ -55,6 +45,44 @@ owner: root group: root mode: "0644" + +# UN ICINGA QUI NE NOUS A JAMAIS ENTENDUS (2026-09-30). Ses services `sauvegarde` et +# `restauration` sont CRITIQUES tant qu'aucun rapport n'est arrive — c'est voulu, le silence +# doit alerter. Mais les minuteurs ne rapportent que toutes les 4 h (depot) et le DIMANCHE +# (restauration) : apres chaque reconstruction, Icinga restait rouge jusqu'a une semaine. +# +# Le noeud ne peut pas demander a Icinga s'il l'a deja entendu : son compte ne sait que +# DEPOSER. Il retient donc LUI-MEME a quel Icinga il a rapporte — l'empreinte de son AC. Un +# Icinga refait a une AC neuve ; un noeud refait n'a pas de marqueur. +# +# UN MARQUEUR A LUI, PAS LA COPIE DE L'AC. La premiere version se fiait au changement de +# `icinga-ca.crt` — or `client_sante` depose le MEME fichier, plus tot dans le deploiement : +# sur une flotte neuve il etait deja la, « ok », et rien n'etait notifie. Vu a la +# reconstruction de Chezlepro du 2026-09-30 : `restauration` rouge sur sept noeuds, et le +# pare-feu arrete. L'epreuve precedente avait joue `client_backup` SEUL, et ne pouvait pas +# le voir. +# +# ECRIT APRES LE RAPPORT (dernier gestionnaire), jamais avant : un rapport rate laisserait +# sinon le noeud convaincu d'avoir ete entendu, et la passe suivante ne retenterait rien. +- name: Retenir l'empreinte de l'Icinga destinataire + when: client_backup_ca_presente.stat.exists + ansible.builtin.set_fact: + client_backup_icinga_empreinte: "{{ client_backup_ca_icinga.content | b64decode | hash('sha256') }}" + +- name: A quel Icinga ce noeud a-t-il deja rapporte ? + when: client_backup_ca_presente.stat.exists + ansible.builtin.slurp: + src: "{{ client_backup_marque_icinga }}" + register: client_backup_icinga_entendu + failed_when: false + +- name: Cet Icinga ne nous a jamais entendus — premier rapport + when: + - client_backup_ca_presente.stat.exists + - (client_backup_icinga_entendu.content | default('') | b64decode | trim) != client_backup_icinga_empreinte + ansible.builtin.debug: + msg: "Icinga {{ client_backup_icinga_empreinte[:12] }} n'a encore rien recu de {{ inventory_hostname }} : rapport immediat." + changed_when: true notify: Premier rapport a Icinga - name: Installer curl pour le rapport passif diff --git a/scripts/eprouver_parefeu.py b/scripts/eprouver_parefeu.py index 7ff4bb8..687a84b 100644 --- a/scripts/eprouver_parefeu.py +++ b/scripts/eprouver_parefeu.py @@ -299,6 +299,11 @@ def eprouver(I: str, H: str, args: argparse.Namespace) -> int: if hors or echecs: print(" REFUS D'ACTIVER : un flux reel hors des regles, ou un echec a comprendre d'abord.") return 2 + # LES CRITIQUES D'AVANT NE SONT PAS LES NOTRES (2026-09-30). Tout critique d'Icinga + # arretait la procedure — y compris ceux qui existaient avant l'activation : a la + # reconstruction de Chezlepro, des `restauration` rouges (rien a voir avec le pare-feu) + # l'ont arretee deux fois. On releve donc AVANT, et seul ce qui apparait apres compte. + crit_avant = set(critiques_icinga(I)) if activer(I, vmid, args) != 0: print(" ECHEC de l'activation.") return 3 @@ -307,8 +312,11 @@ def eprouver(I: str, H: str, args: argparse.Namespace) -> int: change = sorted(k for k in avant if avant[k] != apres.get(k)) print(f" apres : {sum(v == 'ok' for v in apres.values())}/{len(apres)} ok ; " f"changements : {', '.join(change) or 'aucun'}") - crit = critiques_icinga(I) - print(f" Icinga : {len(crit)} critique(s)" + (f" — {', '.join(crit)}" if crit else "")) + crit_apres = critiques_icinga(I) + crit = sorted(set(crit_apres) - crit_avant) + print(f" Icinga : {len(crit)} critique(s) apparu(s)" + (f" — {', '.join(crit)}" if crit else "") + + (f" ; {len(crit_avant)} deja present(s) avant, sans rapport avec le pare-feu" + if crit_avant else "")) if change: print(f" RETOUR ARRIERE : remettre `enable=0` sur la VM {vmid} " f"(/nodes//qemu/{vmid}/firewall/options).") diff --git a/scripts/tests/test_restauration.py b/scripts/tests/test_restauration.py index a9abced..82e31e5 100644 --- a/scripts/tests/test_restauration.py +++ b/scripts/tests/test_restauration.py @@ -60,15 +60,19 @@ def couverture() -> None: # UN ICINGA NEUF ENTEND LE NOEUD TOUT DE SUITE (2026-09-30) : sinon `restauration` reste # rouge jusqu'au dimanche apres chaque reconstruction. + # PAS SUR LA COPIE DE L'AC : `client_sante` la depose aussi, plus tot — sur une flotte + # neuve elle ne change donc jamais ici (reconstruction de Chezlepro, 2026-09-30). taches = yaml.safe_load((RACINE / "roles/client_backup/tasks/verifier.yml").read_text()) - depot_ac = [t for t in taches if "AC d'Icinga pour la" in t.get("name", "")] - verifier(bool(depot_ac) and depot_ac[0].get("notify") == "Premier rapport a Icinga", - "la copie de l'AC d'Icinga (Icinga neuf ou noeud neuf) declenche le premier rapport") + notifiants = [t.get("name", "") for t in taches if t.get("notify") == "Premier rapport a Icinga"] + verifier(notifiants and all("AC d'Icinga pour la" not in n for n in notifiants) + and any("jamais entendus" in n for n in notifiants), + "le premier rapport part d'un marqueur propre a client_backup, pas de la copie partagee de l'AC") gest = yaml.safe_load((RACINE / "roles/client_backup/handlers/main.yml").read_text()) - ordre = [g["ansible.builtin.systemd"]["name"] for g in gest if g.get("listen") == "Premier rapport a Icinga"] + ecoute = [g for g in gest if g.get("listen") == "Premier rapport a Icinga"] + ordre = [g.get("ansible.builtin.systemd", {}).get("name", "marqueur") for g in ecoute] verifier(ordre == ["setops-sauvegarde.service", "setops-verification-depot.service", - "setops-verification-restauration.service"], - f"premier rapport : deposer, puis verifier le depot, puis la restauration ({ordre})") + "setops-verification-restauration.service", "marqueur"], + f"premier rapport : deposer, verifier le depot, la restauration, PUIS retenir ({ordre})") # --- 2. L'outil de noeud, avec des doublures ------------------------------------------