Le site de TechnoLibre n'a qu'une adresse DHCP. Trois adresses fixes sont louees sur un VPS (le relais), qui repond a l'ARP pour elles et les route en /32 dans un tunnel WireGuard que la frontiere initie. Aucune traduction : l'adresse du client arrive intacte. - underlay.py : `tunnel: wireguard` dispense de VLAN ; role d'hote `relais`. - devis_opnsense.py : `opnsense_face_publique`. Les entrees publiques s'y posent avec reply-to ; les sorties des locataires du pool passent par le relais ; une redirection n'y vise plus `wanip`. Le devis de Chezlepro est identique au caractere pres. - appliquer_opnsense.py : replyto et gateway transmis et dans l'identite (seulement s'ils existent) ; le NAT sortant en mode automatic arrete l'application au lieu de dupliquer. - role serveur_relais, inventorie depuis la carte comme un hyperviseur ; cle privee dans la voute du site. Deploye, idempotent. - site_inventaire.py : les hyperviseurs ne sont plus filtres sur 192.168.11. ; site-appliquer charge la voute designee par SETOPS_UNDERLAY ; voute.py n'exige plus d'un locataire un secret du site. - prouver.py : P67 lit le plan par underlay.plan_site() ; P53 couvre la face publique. - Tests : test_underlay_tunnel, test_face_publique_relais, test_mode_nat_sortant. 95 preuves (make verifier), make test passe. CHANGELOG 144 et 145. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
2.7 KiB
Python
71 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Un lien de l'underlay peut etre un TUNNEL : ni VLAN, ni port physique.
|
|
|
|
POURQUOI (2026-10-13). Le site de TechnoLibre n'a qu'une adresse DHCP ; ses adresses fixes
|
|
sont louees sur un VPS (le relais) et lui arrivent par un tunnel WireGuard. Ce lien porte un
|
|
sous-reseau de liaison mais aucune etiquette — la validation exigeait un `vlan` ou
|
|
`segment_physique`, et l'aurait refuse. `tunnel: wireguard` le dit.
|
|
|
|
Sur un underlay SYNTHETIQUE (aucun site requis) :
|
|
- un tunnel declare passe ;
|
|
- un type de tunnel inconnu est refuse (faute de frappe, pas un tunnel) ;
|
|
- `tunnel` et `vlan` ensemble sont refuses ;
|
|
- sans `tunnel` ni `vlan`, le lien est refuse comme avant (temoin : la garde n'a pas
|
|
ete relachee pour tout le monde) ;
|
|
- le role d'hote `relais` est connu, un role mal ecrit ne l'est pas.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
import underlay as U # noqa: E402
|
|
|
|
BASE = {
|
|
"index": 31,
|
|
"reseaux": [
|
|
{"nom": "management", "nature": "fabric", "segment_physique": True,
|
|
"sous_reseau": "10.31.0.0/24", "passerelle": "10.31.0.1"},
|
|
{"nom": "relais", "nature": "fabric", "fabric": "relais", "tunnel": "wireguard",
|
|
"sous_reseau": "10.31.60.0/30", "mtu": 1420},
|
|
],
|
|
"hotes": [
|
|
{"nom": "requin", "reseau": "management", "ip": "10.31.0.1", "role": "frontiere"},
|
|
{"nom": "requin", "reseau": "relais", "ip": "10.31.60.2", "role": "frontiere"},
|
|
{"nom": "relais", "reseau": "relais", "ip": "10.31.60.1", "role": "relais"},
|
|
],
|
|
}
|
|
|
|
|
|
def _erreurs(modif=None, sur="relais") -> list[str]:
|
|
u = copy.deepcopy(BASE)
|
|
if modif:
|
|
modif(next(r for r in u["reseaux"] if r["nom"] == sur), u)
|
|
return [e for e in U.valider(u) if "relais" in e or "relai" in e]
|
|
|
|
|
|
def main() -> int:
|
|
cas = [
|
|
("tunnel declare", None, False),
|
|
("type de tunnel inconnu", lambda r, u: r.__setitem__("tunnel", "wireguardd"), True),
|
|
("tunnel et vlan ensemble", lambda r, u: r.__setitem__("vlan", 60), True),
|
|
("ni tunnel ni vlan (temoin)", lambda r, u: r.pop("tunnel"), True),
|
|
("role relais mal ecrit",
|
|
lambda r, u: next(h for h in u["hotes"] if h["nom"] == "relais").__setitem__("role", "relai"),
|
|
True),
|
|
]
|
|
echecs = 0
|
|
for nom, modif, doit_refuser in cas:
|
|
e = _erreurs(modif)
|
|
if bool(e) != doit_refuser:
|
|
echecs += 1
|
|
print(f"ECHEC {nom} : {'accepte' if not e else e}")
|
|
else:
|
|
print(f"OK {nom}" + (f" — refuse : {e[0][:70]}…" if e else ""))
|
|
return 1 if echecs else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|