contexte : etape 2 terminee, les sorties (P90)
resoudre_flux publie les sorties vers l'Internet de chaque machine ; verifier_sorties les confronte aux regles de sortie de la frontiere. Le contrat site <-> locataire est entierement publie et prouve. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
92a6fb0ead
commit
d6591cb271
9 changed files with 157 additions and 7 deletions
|
|
@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente
|
||||||
## Écrire, puis relire (D-68)
|
## Écrire, puis relire (D-68)
|
||||||
|
|
||||||
`--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**.
|
`--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**.
|
||||||
C'est aussi ce que font les 89 preuves de `make prouver` : elles lisent le dépôt, sans le
|
C'est aussi ce que font les 90 preuves de `make prouver` : elles lisent le dépôt, sans le
|
||||||
moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le
|
moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le
|
||||||
dépôt annonce.**
|
dépôt annonce.**
|
||||||
|
|
||||||
|
|
|
||||||
32
CHANGELOG.md
32
CHANGELOG.md
|
|
@ -1,5 +1,37 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-10-05 (89) — Étape 2 terminée : les sorties, et le contrat entier tient
|
||||||
|
|
||||||
|
**Le chemin** (`docs/conception-contextes.md` §6), étape 2, frontière, dernier temps : ce qui
|
||||||
|
sort de chez un locataire vers l'Internet. Avec lui, **l'étape 2 est complète** : chaque chose
|
||||||
|
que le site lit chez un locataire est publiée par le locataire, et une preuve vérifie que la
|
||||||
|
publication dit exactement ce que le site en tire.
|
||||||
|
|
||||||
|
**Fait** :
|
||||||
|
- `resoudre_flux.py` relève, avec les mêmes conditions que les entrées (groupe opérationnel,
|
||||||
|
`flux_applicable`), les **sorties vers l'extérieur** de chaque machine, et les publie dans
|
||||||
|
son `*.connectivite.json` (`sorties_externes` : protocole, port, rôle). Ses machines
|
||||||
|
acceptent tout en sortie : il ne les relevait pas, la frontière les filtre ;
|
||||||
|
- flux régénérés chez les deux locataires : les 13 fichiers de connectivité de chacun gagnent
|
||||||
|
le champ, **aucun `.nft` ne bouge**. La sonde `connectivite` l'ignore ;
|
||||||
|
- la face réseau reprend ces sorties ; `verifier_sorties(site, locataire)` les confronte,
|
||||||
|
machine par machine, aux règles de sortie de la frontière vers « tout sauf l'interne », et
|
||||||
|
signale toute sortie vers une autre destination.
|
||||||
|
|
||||||
|
**Éprouvé** :
|
||||||
|
- **P90**, « Ce qui sort d'un locataire vers l'Internet, la frontière le tient de lui » : aucun
|
||||||
|
écart. Comparés chez Technolibre : 43 sorties publiées (machine × port), 7 règles de sortie
|
||||||
|
(le système en 80, 443 et ICMP ; Postfix en 25 ; PowerDNS et le résolveur en 53). Les deux
|
||||||
|
sorties fausses de `serveur_ops` étaient déjà retirées ((85), (87)).
|
||||||
|
- `test_contexte.py`, 91 contrôles : une sortie retirée ou ajoutée est vue à la frontière.
|
||||||
|
- `make verifier` conforme, **90/90**. Les trois documents comptent 90 preuves.
|
||||||
|
|
||||||
|
**Le bilan de l'étape 2** : sept preuves (P84 à P90) ; trois choses que le locataire
|
||||||
|
**jetait** ou **ne relevait pas** et publie désormais (les clients nommés d'un port public,
|
||||||
|
le drapeau `poste`, ses sorties) ; deux sorties fausses retirées de la frontière, dont un
|
||||||
|
passage entre locataires ; une garde sur la passerelle de zone. Prochaine étape (3) : les
|
||||||
|
consommateurs du site lisent les fiches au lieu des fichiers internes, à résultat identique.
|
||||||
|
|
||||||
## 2026-10-05 (88) — La passerelle n'est pas une porte : une garde sur l'API Proxmox
|
## 2026-10-05 (88) — La passerelle n'est pas une porte : une garde sur l'API Proxmox
|
||||||
|
|
||||||
**Le reste ouvert de (87)** : chez un locataire en SDN, la passerelle de chaque zone est une
|
**Le reste ouvert de (87)** : chez un locataire en SDN, la passerelle de chaque zone est une
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,8 @@ croisée, plus aucun recalcul.
|
||||||
croisées produisent. La première mesure des flux a trouvé une information que le locataire
|
croisées produisent. La première mesure des flux a trouvé une information que le locataire
|
||||||
jetait : les clients nommés d'un port aussi public, que Proxmox doit admettre nommément. Il
|
jetait : les clients nommés d'un port aussi public, que Proxmox doit admettre nommément. Il
|
||||||
les publie désormais (`sources_declarees`). La frontière, en trois temps : les identités
|
les publie désormais (`sources_declarees`). La frontière, en trois temps : les identités
|
||||||
(P87), les entrées publiques (P88) et l'administration (P89) sont faites ; restent les sorties.
|
(P87), les entrées publiques (P88), l'administration (P89) et les sorties (P90) sont faites.
|
||||||
|
**L'étape 2 est terminée** (2026-10-05).
|
||||||
|
|
||||||
Méthodes du contrat : `site.fiche_pour(locataire)`, `locataire.face_reseau()`. Une classe
|
Méthodes du contrat : `site.fiche_pour(locataire)`, `locataire.face_reseau()`. Une classe
|
||||||
n'ouvre jamais les fichiers de l'autre ; une preuve vérifiera la règle.
|
n'ouvre jamais les fichiers de l'autre ; une preuve vérifiera la règle.
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met
|
||||||
|
|
||||||
## Le trou qu'il comble
|
## Le trou qu'il comble
|
||||||
|
|
||||||
`scripts/prouver.py` porte 89 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
`scripts/prouver.py` porte 90 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
||||||
dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est
|
dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est
|
||||||
cohérent **avec lui-même** — que les handlers existent, que les intrants ont un
|
cohérent **avec lui-même** — que les handlers existent, que les intrants ont un
|
||||||
propriétaire, que rien n'est codé en dur.
|
propriétaire, que rien n'est codé en dur.
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ Nommer un point ouvert vaut mieux qu'une ligne rassurante sans mécanisme derri
|
||||||
Aucune de ces responsabilités n'est laissée à la parole :
|
Aucune de ces responsabilités n'est laissée à la parole :
|
||||||
|
|
||||||
```
|
```
|
||||||
make prouver le dépôt est-il cohérent avec lui-même (89 preuves, zéro réseau)
|
make prouver le dépôt est-il cohérent avec lui-même (90 preuves, zéro réseau)
|
||||||
make remise-verifier le second temps de la remise est-il fait, ou en retard ? (P80)
|
make remise-verifier le second temps de la remise est-il fait, ou en retard ? (P80)
|
||||||
make certificats-plan ce que le disque porte contre ce que la mémoire sert
|
make certificats-plan ce que le disque porte contre ce que la mémoire sert
|
||||||
make expositions-plan chaque service publié répond-il, et depuis où
|
make expositions-plan chaque service publié répond-il, et depuis où
|
||||||
|
|
|
||||||
|
|
@ -445,7 +445,8 @@ class Locataire(Ecosysteme):
|
||||||
d = json.loads(c.read_text(encoding="utf-8"))
|
d = json.loads(c.read_text(encoding="utf-8"))
|
||||||
entrees = [dict(e, poste=ouvert_au_poste(e)) if "0.0.0.0/0" in (e.get("sources") or [])
|
entrees = [dict(e, poste=ouvert_au_poste(e)) if "0.0.0.0/0" in (e.get("sources") or [])
|
||||||
else dict(e) for e in d.get("entrees") or []]
|
else dict(e) for e in d.get("entrees") or []]
|
||||||
flux[h] = {"entrees": entrees, "administration": d.get("admin_ssh") or []}
|
flux[h] = {"entrees": entrees, "administration": d.get("admin_ssh") or [],
|
||||||
|
"sorties_externes": d.get("sorties_externes") or []}
|
||||||
sources = {str(Path(*f.relative_to(self.depot).parts)): _empreinte(f) for f in (
|
sources = {str(Path(*f.relative_to(self.depot).parts)): _empreinte(f) for f in (
|
||||||
self.plan / "nomenclature.yml", self.inventaire(), self.plan / "acces.yml",
|
self.plan / "nomenclature.yml", self.inventaire(), self.plan / "acces.yml",
|
||||||
self.plan / "domaines.yml", self.plan / "applications.yml",
|
self.plan / "domaines.yml", self.plan / "applications.yml",
|
||||||
|
|
@ -894,6 +895,52 @@ def verifier_administration(site: "Site", locataire: "Locataire", face: dict | N
|
||||||
return ecarts
|
return ecarts
|
||||||
|
|
||||||
|
|
||||||
|
def verifier_sorties(site: "Site", locataire: "Locataire", face: dict | None = None) -> list[str]:
|
||||||
|
"""La frontiere, troisieme temps (seconde moitie) : ce qui SORT de chez le locataire vers
|
||||||
|
l'Internet.
|
||||||
|
|
||||||
|
Machine par machine, les regles de sortie vers « tout sauf l'interne » (`!SETOPS_INTERNES`)
|
||||||
|
doivent dire ce que la face reseau publie dans `sorties_externes` : les memes (machine,
|
||||||
|
protocole, port). Le locataire relevait ses entrees pour ses propres pare-feux ; ses sorties,
|
||||||
|
que ses machines acceptent toutes, il ne les relevait pas — il les publie depuis le
|
||||||
|
2026-10-05, pour la frontiere.
|
||||||
|
"""
|
||||||
|
import ipaddress
|
||||||
|
f = face if face is not None else locataire.face_reseau()
|
||||||
|
etq, devis = _devis_frontiere(site, locataire)
|
||||||
|
if not etq:
|
||||||
|
return ["la frontiere ne decouvre pas ce locataire"]
|
||||||
|
alias = devis.get("alias") or {}
|
||||||
|
ips = sorted({m["ip"] for m in f["machines"].values()})
|
||||||
|
|
||||||
|
def adresses(nom: str) -> set[str]:
|
||||||
|
a = alias.get(nom) or {}
|
||||||
|
if a.get("type") == "network":
|
||||||
|
reseaux = [ipaddress.ip_network(str(c), strict=False) for c in a.get("contenu") or []]
|
||||||
|
return {ip for ip in ips if any(ipaddress.ip_address(ip) in r for r in reseaux)}
|
||||||
|
return {str(c) for c in a.get("contenu") or []}
|
||||||
|
|
||||||
|
publiees = {(f["machines"][h]["ip"], str(s.get("proto")), _ICMP.get(str(s.get("port")), str(s.get("port"))))
|
||||||
|
for h, fl in (f.get("flux") or {}).items() for s in fl.get("sorties_externes") or []}
|
||||||
|
vues = {(ip, str(r.get("protocole")), _ICMP.get(str(p), str(p)))
|
||||||
|
for r in devis.get("regles") or []
|
||||||
|
if r.get("tenant") == locataire.nom and r.get("sens") == "out"
|
||||||
|
and str(r.get("destination")) == "!SETOPS_INTERNES"
|
||||||
|
for ip in adresses(str(r.get("source"))) for p in r.get("ports") or []}
|
||||||
|
ecarts: list[str] = []
|
||||||
|
for x in sorted(publiees - vues):
|
||||||
|
ecarts.append(f"{x[0]} {x[1]}/{x[2]} : le locataire sort vers l'Internet, la frontiere ne le laisse pas")
|
||||||
|
for x in sorted(vues - publiees):
|
||||||
|
ecarts.append(f"{x[0]} {x[1]}/{x[2]} : la frontiere laisse sortir, le locataire ne le publie pas")
|
||||||
|
autres = [r for r in devis.get("regles") or []
|
||||||
|
if r.get("tenant") == locataire.nom and r.get("sens") == "out"
|
||||||
|
and str(r.get("destination")) != "!SETOPS_INTERNES"]
|
||||||
|
for r in autres:
|
||||||
|
ecarts.append(f"sortie vers {r.get('destination')} ({r.get('protocole')}/{','.join(r.get('ports') or [])}, "
|
||||||
|
f"{r.get('role')}) : ni l'Internet, ni publiee par le locataire")
|
||||||
|
return ecarts
|
||||||
|
|
||||||
|
|
||||||
def verifier_frontiere(site: "Site", locataire: "Locataire", face: dict | None = None,
|
def verifier_frontiere(site: "Site", locataire: "Locataire", face: dict | None = None,
|
||||||
fiche: dict | None = None) -> list[str]:
|
fiche: dict | None = None) -> list[str]:
|
||||||
"""La frontiere, premier temps : les IDENTITES par lesquelles elle designe ce locataire.
|
"""La frontiere, premier temps : les IDENTITES par lesquelles elle designe ce locataire.
|
||||||
|
|
@ -1052,7 +1099,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
("flux ", verifier_flux(site, loc, face)),
|
("flux ", verifier_flux(site, loc, face)),
|
||||||
("frontiere (identites)", verifier_frontiere(site, loc, face)),
|
("frontiere (identites)", verifier_frontiere(site, loc, face)),
|
||||||
("frontiere (entrees publiques)", verifier_entrees_publiques(site, loc, face)),
|
("frontiere (entrees publiques)", verifier_entrees_publiques(site, loc, face)),
|
||||||
("frontiere (administration)", verifier_administration(site, loc, face))):
|
("frontiere (administration)", verifier_administration(site, loc, face)),
|
||||||
|
("frontiere (sorties)", verifier_sorties(site, loc, face))):
|
||||||
total += len(e)
|
total += len(e)
|
||||||
print(f"{'OK ' if not e else 'ECART'} {loc.nom} -> {site.nom} ({quoi})"
|
print(f"{'OK ' if not e else 'ECART'} {loc.nom} -> {site.nom} ({quoi})"
|
||||||
+ "".join(f"\n - {x}" for x in e))
|
+ "".join(f"\n - {x}" for x in e))
|
||||||
|
|
|
||||||
|
|
@ -4808,6 +4808,28 @@ def preuve_frontiere_administration() -> tuple[bool, str]:
|
||||||
f"l'administration atteint exactement ce que leur face reseau lui ouvre.")
|
f"l'administration atteint exactement ce que leur face reseau lui ouvre.")
|
||||||
|
|
||||||
|
|
||||||
|
def preuve_frontiere_sorties() -> tuple[bool, str]:
|
||||||
|
"""Les regles de sortie d'un locataire vers l'Internet disent-elles ce que sa face reseau
|
||||||
|
publie (`sorties_externes`) ?
|
||||||
|
|
||||||
|
POURQUOI (2026-10-05, `docs/conception-contextes.md` §2.4). Dernier temps de la frontiere.
|
||||||
|
Le locataire ne relevait pas ses sorties (ses machines les acceptent toutes) ; il les publie
|
||||||
|
desormais, et la frontiere doit les laisser passer, elles et rien d'autre. En chemin, deux
|
||||||
|
sorties de `serveur_ops` se sont revelees fausses et ont ete retirees (85, 87).
|
||||||
|
"""
|
||||||
|
sys.path.insert(0, str(RACINE / "scripts"))
|
||||||
|
import contexte as _ctx
|
||||||
|
couples = _ctx.couples_hebergeur()
|
||||||
|
if not couples:
|
||||||
|
return True, "Aucun site frere : aucune sortie a confronter."
|
||||||
|
ecarts = [f"{l.nom} : {e}" for s, l in couples for e in _ctx.verifier_sorties(s, l)]
|
||||||
|
if ecarts:
|
||||||
|
return False, (f"{len(ecarts)} ecart(s) entre la frontiere et ce que le locataire publie comme "
|
||||||
|
f"sorties : " + " | ".join(ecarts[:4]))
|
||||||
|
return True, (f"{len(couples)} locataire(s) : la frontiere laisse sortir exactement ce que leur "
|
||||||
|
f"face reseau publie.")
|
||||||
|
|
||||||
|
|
||||||
PREUVES: list[dict] = [
|
PREUVES: list[dict] = [
|
||||||
{"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"],
|
{"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"],
|
||||||
"cmds": [["ansible-lint", "-q"]]},
|
"cmds": [["ansible-lint", "-q"]]},
|
||||||
|
|
@ -5006,6 +5028,8 @@ PREUVES: list[dict] = [
|
||||||
"refs": [], "func": preuve_frontiere_entrees_publiques},
|
"refs": [], "func": preuve_frontiere_entrees_publiques},
|
||||||
{"id": "P89", "titre": "Ce que l'administration atteint chez un locataire, la frontiere le tient de lui",
|
{"id": "P89", "titre": "Ce que l'administration atteint chez un locataire, la frontiere le tient de lui",
|
||||||
"refs": [], "func": preuve_frontiere_administration},
|
"refs": [], "func": preuve_frontiere_administration},
|
||||||
|
{"id": "P90", "titre": "Ce qui sort d'un locataire vers l'Internet, la frontiere le tient de lui",
|
||||||
|
"refs": [], "func": preuve_frontiere_sorties},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -785,12 +785,24 @@ def generer_nftables(flux_par_role: dict[str, list[dict]], site: bool = False) -
|
||||||
hote_par_ip = {ip: h for h, ip in ip_par_hote.items()}
|
hote_par_ip = {ip: h for h, ip in ip_par_hote.items()}
|
||||||
sorties: dict[str, dict[tuple, dict]] = {}
|
sorties: dict[str, dict[tuple, dict]] = {}
|
||||||
entrees: dict[str, list[dict]] = {}
|
entrees: dict[str, list[dict]] = {}
|
||||||
|
# CE QUE CHAQUE VM DEMANDE A L'EXTERIEUR (2026-10-05). Ses machines acceptent tout en
|
||||||
|
# sortie : nftables n'en a pas besoin, et ce generateur ne les relevait donc pas. Mais la
|
||||||
|
# FRONTIERE, elle, filtre ces sorties, et la face reseau du locataire doit les lui dire
|
||||||
|
# (docs/conception-contextes.md). Relevees ICI, avec les memes conditions que les entrees.
|
||||||
|
sorties_externes: dict[str, set[tuple]] = {}
|
||||||
for hote in sorted(actifs_noms):
|
for hote in sorted(actifs_noms):
|
||||||
groupes = [g for g, membres in _enfants(data).items()
|
groupes = [g for g, membres in _enfants(data).items()
|
||||||
if hote in (membres.get("hosts") or {}) and est_groupe_operationnel(g)]
|
if hote in (membres.get("hosts") or {}) and est_groupe_operationnel(g)]
|
||||||
regles: list[str] = list(regles_admin)
|
regles: list[str] = list(regles_admin)
|
||||||
for g in sorted(groupes):
|
for g in sorted(groupes):
|
||||||
for fl in flux_par_role.get(g, []):
|
for fl in flux_par_role.get(g, []):
|
||||||
|
if fl.get("sens") == "egress" and "externe" in _pairs(fl):
|
||||||
|
if flux_applicable(fl, g, dossier_ecosysteme,
|
||||||
|
(_enfants(data).get(g, {}).get("hosts") or {}).get(hote)):
|
||||||
|
for pt in (fl["port"] if isinstance(fl["port"], list) else [fl["port"]]):
|
||||||
|
sorties_externes.setdefault(hote, set()).add(
|
||||||
|
(str(fl.get("protocole", "tcp")).lower(), str(pt), g))
|
||||||
|
continue
|
||||||
if fl.get("sens") != "ingress":
|
if fl.get("sens") != "ingress":
|
||||||
continue
|
continue
|
||||||
if not flux_applicable(fl, g, dossier_ecosysteme,
|
if not flux_applicable(fl, g, dossier_ecosysteme,
|
||||||
|
|
@ -914,6 +926,8 @@ def generer_nftables(flux_par_role: dict[str, list[dict]], site: bool = False) -
|
||||||
key=lambda x: (x["vers"], x["proto"], x["port"])),
|
key=lambda x: (x["vers"], x["proto"], x["port"])),
|
||||||
"entrees": entrees.get(hote, []),
|
"entrees": entrees.get(hote, []),
|
||||||
"admin_ssh": list(admin_ssh),
|
"admin_ssh": list(admin_ssh),
|
||||||
|
"sorties_externes": [{"proto": pr, "port": po, "role": ro}
|
||||||
|
for pr, po, ro in sorted(sorties_externes.get(hote, set()))],
|
||||||
}, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
|
}, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
|
||||||
ecrits.append(chemin)
|
ecrits.append(chemin)
|
||||||
for orphelin in sorted(list(sortie_dir.glob("*.nft")) + list(sortie_dir.glob("*.connectivite.json"))):
|
for orphelin in sorted(list(sortie_dir.glob("*.nft")) + list(sortie_dir.glob("*.connectivite.json"))):
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ Proxmox admettent les memes entrees, et des flux alteres (quatre facons) sont vu
|
||||||
la frontiere designe chaque locataire par les memes identites, et une identite alteree (cinq
|
la frontiere designe chaque locataire par les memes identites, et une identite alteree (cinq
|
||||||
facons) se voit ; ce qui entre depuis l'Internet concorde, et une entree alteree (quatre facons)
|
facons) se voit ; ce qui entre depuis l'Internet concorde, et une entree alteree (quatre facons)
|
||||||
se voit ; ce que l'administration atteint concorde, et une administration alteree (quatre
|
se voit ; ce que l'administration atteint concorde, et une administration alteree (quatre
|
||||||
facons) se voit.
|
facons) se voit ; ce qui sort vers l'Internet concorde, et une sortie alteree se voit.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
@ -395,6 +395,36 @@ def administration() -> None:
|
||||||
f"une administration alteree ({nom}) est vue a la frontiere")
|
f"une administration alteree ({nom}) est vue a la frontiere")
|
||||||
|
|
||||||
|
|
||||||
|
def sorties() -> None:
|
||||||
|
"""Ce qui sort de chez chaque locataire vers l'Internet : la frontiere dit ce que la face
|
||||||
|
publie — et chaque alteration se voit."""
|
||||||
|
import copy
|
||||||
|
couples = C.couples_hebergeur()
|
||||||
|
if not couples:
|
||||||
|
print("SAUTE aucun site frere : aucune sortie a confronter")
|
||||||
|
return
|
||||||
|
for site, loc in couples:
|
||||||
|
e = C.verifier_sorties(site, loc)
|
||||||
|
verifier(not e, f"sorties {loc.nom} : la frontiere laisse sortir exactement ce que la face publie"
|
||||||
|
+ (f" ({e[0]})" if e else ""))
|
||||||
|
site, loc = couples[0]
|
||||||
|
face = loc.face_reseau()
|
||||||
|
hote = next(h for h, f in face["flux"].items() if f.get("sorties_externes"))
|
||||||
|
verifier(sum(len(f.get("sorties_externes") or []) for f in face["flux"].values()) > 0,
|
||||||
|
"la face publie les sorties vers l'Internet de chaque machine")
|
||||||
|
|
||||||
|
def retirer(f):
|
||||||
|
f["flux"][hote]["sorties_externes"] = f["flux"][hote]["sorties_externes"][1:]
|
||||||
|
|
||||||
|
def ajouter(f):
|
||||||
|
f["flux"][hote]["sorties_externes"].append({"proto": "tcp", "port": "6667", "role": "x"})
|
||||||
|
|
||||||
|
for nom, alterer in (("une sortie retiree", retirer), ("une sortie ajoutee", ajouter)):
|
||||||
|
f = copy.deepcopy(face)
|
||||||
|
alterer(f)
|
||||||
|
verifier(bool(C.verifier_sorties(site, loc, f)), f"une sortie alteree ({nom}) est vue a la frontiere")
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
d = Path(tmp)
|
d = Path(tmp)
|
||||||
|
|
@ -409,6 +439,7 @@ def main() -> int:
|
||||||
frontiere()
|
frontiere()
|
||||||
entrees_publiques()
|
entrees_publiques()
|
||||||
administration()
|
administration()
|
||||||
|
sorties()
|
||||||
if ECHECS:
|
if ECHECS:
|
||||||
print(f"\n{len(ECHECS)} echec(s).")
|
print(f"\n{len(ECHECS)} echec(s).")
|
||||||
return 1
|
return 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue