- 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>
65 lines
2.6 KiB
Python
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:]))
|