Set-OPS-Public/scripts/verifier_flux_site.py
Daniel Allaire ca731eedc4 site : l'ordre d'un site neuf, le depot avant l'autorite et le cache avant tous
- Couche depot_etat juste apres le socle (serveur_backup, serveur_backup_site) : l'AC, les
  bases et la supervision trouvent un depot qui repond quand elles naissent.
- site-deployer-tout amorce d'abord la machine du cache, seule (socle, serveur_artefacts,
  serveur_cache_site) : aucune couche ne peut exprimer cet ordre par machine.
- verifier_flux_site.py refuse le deploiement si une machine active n'a pas ses regles
  nftables generees ; test_verifier_flux_site.py l'eprouve.

Simulation complete contre le site de TechnoLibre : aucun echec sur les neuf machines.
95 preuves (make verifier), make test passe. CHANGELOG 147.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:31:56 -04:00

65 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Chaque machine du site a-t-elle ses regles nftables generees, AVANT de deployer ?
POURQUOI (2026-10-13, premier site construit depuis zero). `nftables_baseline` cherche le
jeu de regles DERIVE des flux (`flux-genere/<machine>.nft`, a cote de la carte du site) ;
absent, il pose en silence son GABARIT GENERIQUE, qui ne connait aucun flux reel. Au site de
TechnoLibre, `make flux` n'avait jamais tourne : les neuf machines seraient nees avec un pare-
feu qui refuse leurs propres services, et rien ne l'aurait dit avant le premier refus.
Lit l'inventaire du site (donc `SETOPS_UNDERLAY` s'il est pose) et refuse si une machine
active qui arme son pare-feu n'a pas son fichier. `--inventaire FICHIER` lit un inventaire
deja rendu (JSON de `site_inventaire.py --list`) — c'est ainsi que le test l'eprouve.
"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from pathlib import Path
RACINE = Path(__file__).resolve().parents[1]
def manquants(inv: dict) -> list[str]:
hv = (inv.get("_meta") or {}).get("hostvars") or {}
actifs = ((inv.get("hotes_actifs") or {}).get("hosts")) or []
out = []
for h in sorted(actifs):
v = hv.get(h) or {}
if not v.get("nftables_baseline_enabled", False):
continue
f = str(v.get("nftables_baseline_ruleset_genere") or "")
if not f or not Path(f).is_file():
out.append(f"{h} : {f or '(aucun chemin)'}")
return out
def main(argv: list[str]) -> int:
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--inventaire", default="")
a = ap.parse_args(argv)
if a.inventaire:
inv = json.loads(Path(a.inventaire).read_text(encoding="utf-8"))
else:
r = subprocess.run([sys.executable, str(RACINE / "scripts" / "site_inventaire.py"),
"--list"], capture_output=True, text=True, cwd=RACINE)
if r.returncode != 0:
print("Inventaire du site illisible :\n" + r.stderr.strip()[:400], file=sys.stderr)
return 2
inv = json.loads(r.stdout)
m = manquants(inv)
if m:
print("Refus : ces machines du site n'ont pas leurs regles nftables generees — leur "
"pare-feu retomberait sur le gabarit generique :", file=sys.stderr)
for x in m:
print(f" - {x}", file=sys.stderr)
print("Lancer `make flux` (avec le meme SETOPS_UNDERLAY), puis relancer.", file=sys.stderr)
return 2
print("OK : chaque machine active du site a ses regles nftables generees.")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))