Set-OPS-Public/scripts/tests/test_frontiere_refus.py
Daniel Allaire 6a8fc53084 frontiere : les interfaces internes refusent a voix haute, le WAN reste muet
Un reject final, journalise, sur chaque interface interne (zones du site,
gestion, transit, WireGuard) : un flux mal declare entre deux zones echoue
au lieu d'expirer. P53 affinee, P50 ne compte que les block, test du devis.
La sonde classe ECONNREFUSED en avertissement : le RST de la frontiere est
indiscernable d'un port ferme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:39:46 -04:00

58 lines
2.5 KiB
Python

#!/usr/bin/env python3
"""La frontiere refuse A VOIX HAUTE a l'interieur, et se tait sur l'Internet.
POURQUOI (2026-10-01). Les machines refusaient deja en le disant (nftables, Proxmox) ; la
frontiere jetait en silence sur TOUTES ses interfaces. Entre deux zones, un flux mal declare
expirait au lieu d'echouer — la sonde `connectivite` l'a mesure au site. Chaque interface
interne recoit desormais un `reject` final. Le WAN, lui, reste muet : y repondre servirait
les balayages d'Internet (P53).
Sur le VRAI devis (il faut voir le site : sans `underlay.yml`, le test se saute) :
- aucun `reject` sur le WAN ;
- chaque interface interne qui porte des regles porte un `reject` ;
- ce `reject` vient APRES toute autre regle de son interface (OPNsense evalue en `quick`).
"""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
RACINE = Path(__file__).resolve().parents[2]
def main() -> int:
r = subprocess.run([sys.executable, "scripts/devis_opnsense.py", "--json"],
capture_output=True, text=True, cwd=RACINE)
if r.returncode != 0 or not r.stdout.strip().startswith("{"):
print("SAUTE le devis de la frontiere ne se genere pas ici (site invisible)")
return 0
regles = json.loads(r.stdout).get("regles") or []
wan = str((json.loads(r.stdout).get("interfaces") or {}).get("wan") or "wan")
echecs = []
refus = [x for x in regles if x.get("action") == "reject"]
if any(x.get("interface") == wan for x in refus):
echecs.append("un `reject` sur le WAN : la bordure repondrait aux balayages")
interfaces = {x.get("interface") for x in regles} - {wan}
for itf in sorted(i for i in interfaces if i):
siens = [x for x in regles if x.get("interface") == itf]
rj = [x for x in siens if x.get("action") == "reject"]
if not rj:
echecs.append(f"{itf} : aucun refus audible")
continue
haut = max([int(x.get("sequence") or 1) for x in siens if x.get("action") != "reject"] or [0])
if int(rj[0].get("sequence") or 1) <= haut:
echecs.append(f"{itf} : le `reject` ({rj[0].get('sequence')}) ne vient pas apres "
f"toutes les autres regles ({haut})")
for e in echecs:
print("ECHEC " + e)
if echecs:
return 1
print(f"OK {len(refus)} interface(s) interne(s) refusent a voix haute, chacune en dernier ; "
f"WAN muet.")
return 0
if __name__ == "__main__":
sys.exit(main())