From 77cf63f1cfe746149a1e2df4aeb192f8048aec5e Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sun, 13 Sep 2026 20:03:58 -0400 Subject: [PATCH] flux : un dossier genere doit aussi savoir supprimer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flux-genere/ ecrivait sans jamais retirer. Un hote sorti du plan y laissait son ruleset complet, indiscernable des vivants. Le dossier cessait d'etre une image du plan pour devenir le cumul de tous les plans successifs. P53 voyait l'orphelin — un ruleset perime n'a pas de refus audible — mais designait le fichier, pas la cause. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q --- CHANGELOG.md | 18 ++++++++++++++++++ docs/audit/preuve-2026-09-13.md | 8 ++++---- scripts/resoudre_flux.py | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6839f6..2de5399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # CHANGELOG — Set-OPS +## 2026-09-13 (7) — Un hote retire du plan laissait son pare-feu derriere lui + +En retirant `backup-01` du plan de TechnoLibre, `flux-genere/backup-01.nft` est reste sur +le disque : un ruleset complet, pour une machine qui n'existe plus, indiscernable des +autres au premier coup d'oeil. La generation ECRIVAIT sans jamais RETIRER. + +Ce que ca coute : `flux-genere/` cesse d'etre une IMAGE du plan pour devenir le cumul de +tous les plans successifs. Qui lit le dossier pour savoir ce qu'un ecosysteme expose lit +alors un etat qui n'a jamais existe. + +**P53 le voyait deja** — un ruleset perime n'a pas de refus audible — mais il designait le +fichier, pas la cause. La generation supprime desormais les orphelins **et le dit** : + + note : backup-01.nft retire — cet hote n'est plus au plan. + +C'est le meme patron que les neuf resolutions d'instance : un dossier GENERE doit etre le +reflet exact de sa source, donc la generation doit aussi supprimer. + ## 2026-09-13 (6) — `balloon: 0` ne desactive pas le ballooning : il retire le peripherique Soixante-et-un gigaoctets de RAM etaient **declares** a vingt-et-une machines. Mesure a diff --git a/docs/audit/preuve-2026-09-13.md b/docs/audit/preuve-2026-09-13.md index ba977af..b253b36 100644 --- a/docs/audit/preuve-2026-09-13.md +++ b/docs/audit/preuve-2026-09-13.md @@ -30,7 +30,7 @@ | P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. | | P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). | | P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. | -| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 27 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) | +| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 29 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) | | P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. | | P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. | | P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 5 instance(s) federee(s), aucun index en collision. | @@ -44,7 +44,7 @@ | P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv | | P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. | | P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 63 scripts expliques et atteignables, 121 cibles make documentees, 68 roles avec README. | -| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (140 cle(s) declaree(s) par l'instance). | +| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (146 cle(s) declaree(s) par l'instance). | | P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). | | P34 | Chaque document declare son lecteur | — | ✅ OK | 45 document(s) declarent leur lecteur (39 genere(s) exempte(s)). | | P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). | @@ -65,9 +65,9 @@ | P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 236 regles `pass`), tous non consignes et tous motives. | | P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 | | P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. | -| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b | +| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 14 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b | | P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. | -| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. | +| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 0. | | P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. | | P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (74 preuves, 68 roles, 41 groupes). | | P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. | diff --git a/scripts/resoudre_flux.py b/scripts/resoudre_flux.py index 5cc6117..8d559b2 100644 --- a/scripts/resoudre_flux.py +++ b/scripts/resoudre_flux.py @@ -685,6 +685,25 @@ def generer_nftables(flux_par_role: dict[str, list[dict]], site: bool = False) - chemin = sortie_dir / f"{hote}.nft" chemin.write_text(contenu, encoding="utf-8") ecrits.append(chemin) + # UN HOTE RETIRE DU PLAN LAISSAIT SON PARE-FEU DERRIERE LUI (2026-09-13). + # + # Cette boucle ECRIVAIT sans jamais RETIRER. En retirant `backup-01` du plan d'un + # locataire, son `backup-01.nft` est reste sur le disque — un ruleset complet pour une + # machine qui n'existe plus, impossible a distinguer des autres au premier coup d'oeil. + # + # CE QUE CA COUTE : `flux-genere/` cesse d'etre une IMAGE du plan pour devenir un + # cumul de tous les plans successifs. Quiconque lit le dossier pour savoir ce que + # l'ecosysteme expose lit alors un etat qui n'a jamais existe. + # + # C'est le meme patron que les neuf resolutions d'instance et que les listes qui se + # suivent : un dossier GENERE doit etre le reflet exact de sa source, donc la + # generation doit aussi SUPPRIMER. La garde est posee en meme temps que le remede — + # **P53** voyait deja l'orphelin, puisqu'un ruleset perime n'a pas de refus audible. + for orphelin in sorted(sortie_dir.glob("*.nft")): + if orphelin not in ecrits: + orphelin.unlink() + print(f"note : {orphelin.name} retire — cet hote n'est plus au plan.", + file=sys.stderr) return ecrits