pare-feu Proxmox : un tenant retire sort du perimetre, ses objets aussi

Les IPSets et groupes t29- restaient poses : le perimetre ne retenait que
les prefixes des tenants presents. Les etiquettes des zones retirees
(ANCIEN_NOMMAGE du SDN) y entrent, pour etre retirees. Et un cluster muet
arrete le plan au lieu de se lire vide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-09-28 09:58:42 -04:00
parent fde604c2a5
commit badbcd3971
2 changed files with 38 additions and 4 deletions

View file

@ -37,6 +37,12 @@ frères (`decouvrir()`), alors que la frontière et le SDN s'en tiennent à
proposait donc de recréer ses groupes ; le poste comptait un dossier de CI comme tenant.
Les deux devis suivent maintenant la même liste : 2 tenants, et non 3.
Filtré ainsi, le devis ne voyait plus du tout `t29` — ni à créer, ni à RETIRER : son
périmètre ne retient que les préfixes des tenants présents. 3 IPSets et 6 groupes `t29-`
restaient posés sur le cluster. `appliquer_proxmox_fw.py` ajoute à son périmètre les
étiquettes des zones de `ANCIEN_NOMMAGE` (une liste, pas deux), et reçoit le même garde
que le SDN contre un cluster muet.
Wiki republié (P60) : deux de ses pages nommaient patient 0. `make verifier` : 82 OK, 1 échec — P02,
`test_ecriture_plan.py` sur `domaines.yml` (`'list' object has no attribute 'get'`),
présent avant ce changement.

View file

@ -83,8 +83,36 @@ def _regle_cle(r: dict) -> tuple:
str(r.get("dport") or ""))
def _prefixes_retires() -> tuple[str, ...]:
"""Les etiquettes des tenants RETIRES : `t29-` pour la zone `t29`.
UN TENANT RETIRE SORT DU PERIMETRE, ET SES OBJETS AVEC (2026-09-28). Le perimetre ne
retient que les prefixes du devis ; un index libere n'y est plus, donc ses IPSets et
ses groupes n'existaient plus pour ce script — ni a creer, ni a retirer. Ils restaient
poses. La liste n'est pas ecrite ici : c'est celle du SDN (`ANCIEN_NOMMAGE`), dont les
zones `t<index>` portent la meme etiquette que les IPSets. Une liste, pas deux.
"""
import devis_sdn
return tuple(f"{z}-" for z in devis_sdn.ANCIEN_NOMMAGE
if z.startswith("t") and z[1:].isdigit())
def _lire(api: Cluster, chemin: str):
"""Une lecture du cluster, ou l'arret : un cluster muet n'est pas un cluster vide.
Meme defaut, meme correctif que `appliquer_sdn._lire` (2026-09-28) : `rep or []`
parcourait le dict d'erreur comme une liste vide, et le plan proposait de tout creer.
"""
rep = api(chemin)
if api.rate(rep):
raise SystemExit(f"Le cluster {api.hote} ne repond pas sur `{chemin}` : "
f"{api.rate(rep)}\n (un cluster injoignable serait lu comme un "
f"cluster VIDE, et le plan proposerait de tout recreer)")
return rep
def plan(api: Cluster, devis: dict) -> dict:
prefixes = tuple(_prefixe(b) for b in devis["blocs"] if _prefixe(b))
prefixes = tuple(_prefixe(b) for b in devis["blocs"] if _prefixe(b)) + _prefixes_retires()
ipsets_voulus, groupes_voulus, affect_voulues = {}, {}, {}
for b in devis["blocs"]:
@ -98,20 +126,20 @@ def plan(api: Cluster, devis: dict) -> dict:
# --- etat du cluster, restreint a nos prefixes -----------------------------
ipsets_poses = {}
for s in (api("/cluster/firewall/ipset") or []):
for s in (_lire(api, "/cluster/firewall/ipset") or []):
if isinstance(s, dict) and str(s.get("name", "")).startswith(prefixes):
membres = [str(m.get("cidr")) for m in (api(f"/cluster/firewall/ipset/{s['name']}") or [])
if isinstance(m, dict)]
ipsets_poses[s["name"]] = sorted({_norm(m) for m in membres})
groupes_poses = {}
for g in (api("/cluster/firewall/groups") or []):
for g in (_lire(api, "/cluster/firewall/groups") or []):
if isinstance(g, dict) and str(g.get("group", "")).startswith(prefixes):
regles = [r for r in (api(f"/cluster/firewall/groups/{g['group']}") or [])
if isinstance(r, dict)]
groupes_poses[g["group"]] = regles
vm_du_cluster = {int(r["vmid"]): r["node"] for r in (api("/cluster/resources?type=vm") or [])
vm_du_cluster = {int(r["vmid"]): r["node"] for r in (_lire(api, "/cluster/resources?type=vm") or [])
if isinstance(r, dict) and r.get("vmid")}
affect_posees, vm_absentes = {}, []