La sortie 80/443 que le socle donnait a toute machine ne servait plus a apt (tout passe par le cache du site) mais a de la telemetrie et a des sorties non declarees. Elle devient conditionnelle (seulement_si artefacts_amorcage vide) ; la telemetrie de Grafana, Loki, Alloy et la verification de version de Collabora sont coupees a la source ; rspamd et la sonde du genome declarent leur sortie. Le devis du site evalue enfin seulement_si, ce qui retire aussi le 465/587 vers un Postfix du site qui n'ecoute que sur 25. Garde : test_conditions_site.py. Journal (125). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
3.7 KiB
Python
81 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Aucune sortie vers Internet n'est donnee a TOUTE machine, et le site lit les conditions.
|
|
|
|
POURQUOI (2026-10-10). Le socle donnait a chaque machine une sortie 80/443 vers tout
|
|
Internet, « pour apt ». Apt passait deja par le cache du site : la sortie ne servait plus
|
|
qu'a ce qu'aucun role ne declarait — telemetrie de Grafana, d'Alloy et de Loki, verification
|
|
de version de Collabora, cartes de rspamd. Elle est devenue conditionnelle
|
|
(`seulement_si: artefacts_amorcage` vide), et deux defauts sont apparus :
|
|
|
|
1. un role porte par toute machine (le socle, les `client_*`) peut rouvrir Internet a
|
|
toute la flotte sans que rien ne le signale ;
|
|
2. le devis de la frontiere n'evaluait `seulement_si` que chez les locataires : au site,
|
|
tout flux conditionnel etait emis. `make flux` retirait la sortie des machines du site,
|
|
la frontiere la gardait.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
RACINE = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(RACINE / "scripts"))
|
|
|
|
import devis_opnsense as dv # noqa: E402
|
|
from resoudre_flux import charger_flux # noqa: E402
|
|
|
|
UNIVERSELS = {"serveur_debian", "serveur_durci"}
|
|
|
|
|
|
def main() -> int:
|
|
echecs: list[str] = []
|
|
flux = charger_flux()
|
|
|
|
# 1. Les roles universels n'ouvrent pas Internet sans condition (hors ICMP du PMTUD).
|
|
for role, liste in flux.items():
|
|
if role not in UNIVERSELS and not role.startswith("client_"):
|
|
continue
|
|
for fl in liste:
|
|
pairs = fl.get("pair") if isinstance(fl.get("pair"), list) else [fl.get("pair")]
|
|
if (fl.get("sens") == "egress" and "externe" in pairs
|
|
and str(fl.get("protocole")).lower() in ("tcp", "udp")
|
|
and not fl.get("seulement_si")):
|
|
echecs.append(f"{role} : sortie {fl.get('protocole')}/{fl.get('port')} vers "
|
|
f"`externe` sans condition — donnee a TOUTE machine")
|
|
|
|
# 2. L'evaluation au site, sur des variables d'hote connues.
|
|
socle = [fl for fl in flux.get("serveur_debian", [])
|
|
if fl.get("sens") == "egress" and fl.get("seulement_si")
|
|
and (fl.get("seulement_si") or {}).get("variable") == "artefacts_amorcage"]
|
|
if not socle:
|
|
echecs.append("serveur_debian : la sortie apt n'est plus conditionnee a `artefacts_amorcage`")
|
|
else:
|
|
fl = socle[0]
|
|
dv._HOSTVARS_SITE = {"avec": {"artefacts_amorcage": "10.0.0.1:3142"}, "sans": {}}
|
|
attendus = [(["avec"], False), (["sans"], True), (["avec", "sans"], True)]
|
|
for machines, attendu in attendus:
|
|
obtenu = dv.applicable_au_site(fl, "serveur_debian", machines)
|
|
if obtenu is not attendu:
|
|
echecs.append(f"applicable_au_site({machines}) = {obtenu}, attendu {attendu}")
|
|
dv._HOSTVARS_SITE = {}
|
|
if dv.applicable_au_site(fl, "serveur_debian", ["avec"]) is not True:
|
|
echecs.append("inventaire du site illisible : la regle doit rester emise (statu quo)")
|
|
dv._HOSTVARS_SITE = None
|
|
|
|
# 3. La boucle du site appelle bien l'evaluation, avant tout rendu de regle.
|
|
source = (RACINE / "scripts" / "devis_opnsense.py").read_text(encoding="utf-8")
|
|
boucle = re.search(r"for _fl in flux_par_role\.get\(_role, \[\]\):(.{0,600})", source, re.S)
|
|
if not boucle or "applicable_au_site(" not in boucle.group(1):
|
|
echecs.append("devis_opnsense : la boucle des flux du site n'evalue plus `seulement_si`")
|
|
|
|
for e in echecs:
|
|
print(f"ECHEC {e}")
|
|
if echecs:
|
|
return 1
|
|
print("OK — aucun role universel n'ouvre Internet ; le site lit les conditions.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|