contexte : etape 2, les flux de chaque machine (P86)
La face reseau porte les flux resolus par le locataire ; verifier_flux les confronte au devis Proxmox, port par port. resoudre_flux publie les sources_declarees d un port aussi public, que le locataire jetait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
077c6c91b4
commit
3d01208a68
9 changed files with 249 additions and 12 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 85 preuves de `make prouver` : elles lisent le dépôt, sans le
|
C'est aussi ce que font les 86 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.**
|
||||||
|
|
||||||
|
|
|
||||||
39
CHANGELOG.md
39
CHANGELOG.md
|
|
@ -1,5 +1,44 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-10-04 (82) — Étape 2 : les flux de chaque machine, et une information que le locataire jetait
|
||||||
|
|
||||||
|
**Le chemin** (`docs/conception-contextes.md` §6), étape 2, sens locataire → site : les flux.
|
||||||
|
La face réseau porte désormais les flux de **chaque machine**, ceux que consomme le pare-feu
|
||||||
|
Proxmox. Ceux de la **frontière** (interfaces, alias, traduction d'adresses, flux entre site
|
||||||
|
et locataires) restent à faire.
|
||||||
|
|
||||||
|
**Fait** (`scripts/contexte.py`) :
|
||||||
|
- la face réseau reprend, pour chaque machine, ce que le locataire a **déjà résolu** pour ses
|
||||||
|
propres pare-feux (`flux-genere/<hôte>.connectivite.json`) : chaque entrée admise et ses
|
||||||
|
réseaux d'administration ;
|
||||||
|
- `verifier_flux(site, locataire)` compare, machine par machine et port par port, les sources
|
||||||
|
admises par **nftables** (le locataire) et par **Proxmox** (le devis du site). Les deux
|
||||||
|
écrivent la même politique autrement : règle par groupe contre sources réunies, ensembles
|
||||||
|
nommés contre adresses en clair, « Internet » (`0.0.0.0/0` contre « tout sauf RFC 1918 »,
|
||||||
|
doublé d'une règle « administration (poste) »), l'administration à part, l'ICMP nommé
|
||||||
|
différemment. Ces différences sont neutralisées, et décrites dans le code.
|
||||||
|
|
||||||
|
**Ce que la première mesure a trouvé** : 3 écarts par locataire, tous sur `edge-mta-01`
|
||||||
|
(25, 465, 587). Ce n'était pas une divergence de politique, mais une **information perdue** :
|
||||||
|
quand un port est à la fois public et ouvert à des clients internes (`[externe, client_smtp]`),
|
||||||
|
`resoudre_flux.py` écrivait `0.0.0.0/0` et **jetait** la liste des clients nommés. Juste pour
|
||||||
|
l'hôte, qui accepte de partout ; insuffisant pour Proxmox, dont l'Internet exclut les plages
|
||||||
|
privées et qui doit admettre ces clients nommément. Telle quelle, la face réseau n'aurait pas
|
||||||
|
suffi au site.
|
||||||
|
|
||||||
|
**Corrigé à la source** (décision de l'exploitant) : `resoudre_flux.py` publie, à côté de
|
||||||
|
`0.0.0.0/0`, les `sources_declarees`. Flux régénérés chez les deux locataires : seul
|
||||||
|
`edge-mta-01.connectivite.json` change, **aucun `.nft` ne bouge**. La sonde `connectivite`,
|
||||||
|
qui lit ces fichiers sur les machines, ignore le champ nouveau ; le fichier sera recopié au
|
||||||
|
prochain passage de `nftables_baseline`, sans effet.
|
||||||
|
|
||||||
|
**Éprouvé** :
|
||||||
|
- **P86**, « Machine par machine, le locataire et Proxmox admettent les mêmes entrées » :
|
||||||
|
aucun écart chez les deux locataires.
|
||||||
|
- `test_contexte.py`, 66 contrôles : des flux altérés de **quatre façons** (une source retirée,
|
||||||
|
un port ajouté, les sources déclarées effacées, l'administration retirée) sont vus en écart.
|
||||||
|
- `make verifier` conforme, **86/86**. Les trois documents comptent 86 preuves.
|
||||||
|
|
||||||
## 2026-10-04 (81) — Étape 2, seconde moitié (les faits) : la face réseau du locataire
|
## 2026-10-04 (81) — Étape 2, seconde moitié (les faits) : la face réseau du locataire
|
||||||
|
|
||||||
**Le chemin** (`docs/conception-contextes.md` §6), étape 2, sens locataire → site. La face
|
**Le chemin** (`docs/conception-contextes.md` §6), étape 2, sens locataire → site. La face
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,12 @@ croisée, plus aucun recalcul.
|
||||||
- **Chaque fiche porte l'empreinte de sa source**, et une preuve de chaque côté vérifie que la
|
- **Chaque fiche porte l'empreinte de sa source**, et une preuve de chaque côté vérifie que la
|
||||||
fiche reçue correspond à ce que l'autre a publié.
|
fiche reçue correspond à ce que l'autre a publié.
|
||||||
|
|
||||||
|
**Où en est l'étape 2 (2026-10-04).** La fiche du site (P84), les faits de la face réseau
|
||||||
|
(P85) et les flux de chaque machine (P86) existent, et disent exactement ce que les lectures
|
||||||
|
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
|
||||||
|
les publie désormais (`sources_declarees`). Reste la frontière.
|
||||||
|
|
||||||
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 85 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
`scripts/prouver.py` porte 86 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 (85 preuves, zéro réseau)
|
make prouver le dépôt est-il cohérent avec lui-même (86 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ù
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,8 @@ class Locataire(Ecosysteme):
|
||||||
re-derivait, sequence 1 par defaut), sa cle de sauvegarde de SON dossier d'inventaire
|
re-derivait, sequence 1 par defaut), sa cle de sauvegarde de SON dossier d'inventaire
|
||||||
(le site ecrivait `principal` en dur).
|
(le site ecrivait `principal` en dur).
|
||||||
|
|
||||||
LES FLUX RESOLUS sont la seconde moitie de l'etape 2 ; ils ne sont pas encore ici.
|
LES FLUX de chaque machine viennent de ce que le locataire a deja resolu pour ses
|
||||||
|
propres pare-feux (`flux-genere/*.connectivite.json`). Ceux de la frontiere suivront.
|
||||||
"""
|
"""
|
||||||
from inventory_rules import charger_acces, est_groupe_operationnel, tunnel_admin_de
|
from inventory_rules import charger_acces, est_groupe_operationnel, tunnel_admin_de
|
||||||
from resoudre_flux import _enfants, _hotes_du_groupe, _ip_par_hote
|
from resoudre_flux import _enfants, _hotes_du_groupe, _ip_par_hote
|
||||||
|
|
@ -418,10 +419,21 @@ class Locataire(Ecosysteme):
|
||||||
"port": 5300 if colocalise else 53} if zones else {})
|
"port": 5300 if colocalise else 53} if zones else {})
|
||||||
cle_sauvegarde = str(_yaml(self.dossier_inventaire() / "group_vars" / "serveur_backup.yml")
|
cle_sauvegarde = str(_yaml(self.dossier_inventaire() / "group_vars" / "serveur_backup.yml")
|
||||||
.get("serveur_backup_pubkey") or "").strip()
|
.get("serveur_backup_pubkey") or "").strip()
|
||||||
|
# LES FLUX DE CHAQUE MACHINE, tels que le locataire les a deja resolus pour ses propres
|
||||||
|
# pare-feux (`make flux` -> `flux-genere/<hote>.connectivite.json`) : chaque entree
|
||||||
|
# admise (protocole, port, sources), plus ses reseaux d'administration.
|
||||||
|
flux = {}
|
||||||
|
for h in sorted(ips):
|
||||||
|
c = self.depot / "flux-genere" / f"{h}.connectivite.json"
|
||||||
|
if c.is_file():
|
||||||
|
d = json.loads(c.read_text(encoding="utf-8"))
|
||||||
|
flux[h] = {"entrees": d.get("entrees") or [], "administration": d.get("admin_ssh") 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",
|
||||||
self.dossier_inventaire() / "group_vars" / "serveur_backup.yml")}
|
self.dossier_inventaire() / "group_vars" / "serveur_backup.yml")}
|
||||||
|
sources.update({f"flux-genere/{h}.connectivite.json":
|
||||||
|
_empreinte(self.depot / "flux-genere" / f"{h}.connectivite.json") for h in flux})
|
||||||
return {
|
return {
|
||||||
"locataire": self.nom,
|
"locataire": self.nom,
|
||||||
"hebergeur": self.nom_hebergeur(),
|
"hebergeur": self.nom_hebergeur(),
|
||||||
|
|
@ -434,6 +446,7 @@ class Locataire(Ecosysteme):
|
||||||
for n, c in sorted(pairs.items()) if isinstance(c, dict)}},
|
for n, c in sorted(pairs.items()) if isinstance(c, dict)}},
|
||||||
"dns_public": dns_public,
|
"dns_public": dns_public,
|
||||||
"sauvegarde": {"cle_publique": cle_sauvegarde},
|
"sauvegarde": {"cle_publique": cle_sauvegarde},
|
||||||
|
"flux": flux,
|
||||||
}
|
}
|
||||||
|
|
||||||
def decrire(self) -> dict:
|
def decrire(self) -> dict:
|
||||||
|
|
@ -603,6 +616,104 @@ def verifier_face(site: "Site", locataire: "Locataire", face: dict | None = None
|
||||||
return ecarts
|
return ecarts
|
||||||
|
|
||||||
|
|
||||||
|
# LES DEUX PARE-FEUX ECRIVENT LA MEME POLITIQUE AUTREMENT. On les ramene a une forme commune :
|
||||||
|
# pour chaque machine, pour chaque (protocole, port), l'ENSEMBLE des sources admises, chacune
|
||||||
|
# etant une adresse, `EXTERNE` ou `ADMIN`. Les differences d'ecriture, toutes neutralisees ici :
|
||||||
|
# - Proxmox ecrit une regle par groupe source ; le locataire reunit les sources d'un port ;
|
||||||
|
# - Proxmox nomme un ensemble (`+t23-srv-nginx`) ou ecrit les adresses en clair, separees
|
||||||
|
# par des virgules (la fabric vers Loki, le DNS public du site) ;
|
||||||
|
# - « Internet » : `0.0.0.0/0` chez le locataire, l'ensemble « tout sauf RFC 1918 » chez
|
||||||
|
# Proxmox, double d'une regle « administration (poste) » sur le meme port ;
|
||||||
|
# - l'administration : une liste a part (`admin_ssh`) et des entrees ordinaires chez le
|
||||||
|
# locataire, l'ensemble « reseaux d'administration » chez Proxmox ;
|
||||||
|
# - l'ICMP : `frag-needed` d'un cote, `fragmentation-needed` de l'autre.
|
||||||
|
# Un port ouvert a tous ET a des clients nommes porte ces derniers dans `sources_declarees`
|
||||||
|
# (depuis le 2026-10-04) : sans eux, Proxmox ne saurait pas qui admettre en plus d'Internet.
|
||||||
|
EXTERNE, ADMIN = "EXTERNE", "ADMIN"
|
||||||
|
_ICMP = {"frag-needed": "fragmentation-needed"}
|
||||||
|
|
||||||
|
|
||||||
|
def _entrees_locataire(face: dict) -> dict[str, dict]:
|
||||||
|
out: dict[str, dict] = {}
|
||||||
|
for h, f in (face.get("flux") or {}).items():
|
||||||
|
admin = set(f.get("administration") or [])
|
||||||
|
par_port = out.setdefault(h, {})
|
||||||
|
for e in f.get("entrees") or []:
|
||||||
|
cle = (str(e.get("proto")), _ICMP.get(str(e.get("port")), str(e.get("port"))))
|
||||||
|
atomes = par_port.setdefault(cle, set())
|
||||||
|
# `sources_declarees` : les clients nommes d'un port aussi ouvert a tous (`0.0.0.0/0`).
|
||||||
|
for s in list(e.get("sources") or []) + list(e.get("sources_declarees") or []):
|
||||||
|
atomes.add(EXTERNE if s == "0.0.0.0/0" else ADMIN if s in admin else str(s))
|
||||||
|
if admin:
|
||||||
|
par_port.setdefault(("tcp", "22"), set()).add(ADMIN)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _entrees_proxmox(bloc: dict) -> dict[str, dict]:
|
||||||
|
ens = bloc.get("ipsets") or {}
|
||||||
|
regles = {g["nom"]: g.get("regles") or [] for g in bloc.get("groupes") or []}
|
||||||
|
out: dict[str, dict] = {}
|
||||||
|
for a in bloc.get("affectations") or []:
|
||||||
|
par_port = out.setdefault(a["hote"], {})
|
||||||
|
for g in a.get("groupes") or []:
|
||||||
|
for r in regles.get(g, []):
|
||||||
|
if r.get("action") != "ACCEPT" or r.get("sens") != "IN":
|
||||||
|
continue
|
||||||
|
if str(r.get("via", "")) == "administration (poste)":
|
||||||
|
continue # le double d'une regle `internet` sur le meme port
|
||||||
|
source = str(r.get("source", ""))
|
||||||
|
if source.startswith("+"):
|
||||||
|
e = ens.get(source[1:]) or {}
|
||||||
|
role = str(e.get("role", ""))
|
||||||
|
atomes = ({EXTERNE} if role.startswith("Internet") else
|
||||||
|
{ADMIN} if role.startswith("reseaux d'admin") else
|
||||||
|
{str(m) for m in e.get("membres") or []})
|
||||||
|
else:
|
||||||
|
atomes = {s.strip() for s in source.split(",") if s.strip()}
|
||||||
|
for p in str(r.get("dport") or r.get("icmp_type")).split(","):
|
||||||
|
cle = (str(r.get("proto")), _ICMP.get(p.strip(), p.strip()))
|
||||||
|
par_port.setdefault(cle, set()).update(atomes)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def verifier_flux(site: "Site", locataire: "Locataire", face: dict | None = None) -> list[str]:
|
||||||
|
"""Machine par machine : le pare-feu du locataire (nftables, resolu par lui) et celui de
|
||||||
|
Proxmox (devise par le site) admettent-ils les memes entrees ?
|
||||||
|
|
||||||
|
C'est le constat 2 de la conception : les flux etaient calcules DEUX FOIS, chacun avec sa
|
||||||
|
version du moteur. Vide = les deux pare-feux disent la meme chose, et le site pourra, a
|
||||||
|
l'etape 3, deviser Proxmox depuis la face reseau sans rien recalculer.
|
||||||
|
"""
|
||||||
|
import devis_proxmox_fw
|
||||||
|
import devis_reseau
|
||||||
|
f = face if face is not None else locataire.face_reseau()
|
||||||
|
with _monter(site):
|
||||||
|
bloc = next((b for b in devis_proxmox_fw.construire(devis_reseau.decouvrir_du_site())["blocs"]
|
||||||
|
if b.get("tenant") == locataire.nom), None)
|
||||||
|
if bloc is None:
|
||||||
|
return ["le devis Proxmox n'a aucun bloc pour ce locataire"]
|
||||||
|
loc, pve = _entrees_locataire(f), _entrees_proxmox(bloc)
|
||||||
|
ecarts: list[str] = []
|
||||||
|
for h in sorted(set(loc) | set(pve)):
|
||||||
|
if h not in loc:
|
||||||
|
ecarts.append(f"{h} : filtre par Proxmox, sans flux publie par le locataire")
|
||||||
|
continue
|
||||||
|
if h not in pve:
|
||||||
|
ecarts.append(f"{h} : flux publies par le locataire, aucune affectation Proxmox")
|
||||||
|
continue
|
||||||
|
|
||||||
|
for cle in sorted(set(loc[h]) | set(pve[h])):
|
||||||
|
l, p = loc[h].get(cle, set()), pve[h].get(cle, set())
|
||||||
|
if l == p:
|
||||||
|
continue
|
||||||
|
port = f"{cle[0]}/{cle[1]}"
|
||||||
|
if l - p:
|
||||||
|
ecarts.append(f"{h} {port} : le locataire admet {sorted(l - p)}, Proxmox non")
|
||||||
|
if p - l:
|
||||||
|
ecarts.append(f"{h} {port} : Proxmox admet {sorted(p - l)}, le locataire non")
|
||||||
|
return ecarts
|
||||||
|
|
||||||
|
|
||||||
def couples_hebergeur() -> list[tuple["Site", "Locataire"]]:
|
def couples_hebergeur() -> list[tuple["Site", "Locataire"]]:
|
||||||
"""Chaque (site, locataire) dont ce site est l'hebergeur ACTIF — les seuls qui ont une fiche
|
"""Chaque (site, locataire) dont ce site est l'hebergeur ACTIF — les seuls qui ont une fiche
|
||||||
a verifier : un site de reprise ne depose rien tant que la bascule n'a pas eu lieu."""
|
a verifier : un site de reprise ne depose rien tant que la bascule n'a pas eu lieu."""
|
||||||
|
|
@ -699,10 +810,12 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
return 0
|
return 0
|
||||||
total = 0
|
total = 0
|
||||||
for site, loc in couples:
|
for site, loc in couples:
|
||||||
e = verifier_face(site, loc)
|
face = loc.face_reseau()
|
||||||
total += len(e)
|
for quoi, e in (("faits", verifier_face(site, loc, face)),
|
||||||
print(f"{'OK ' if not e else 'ECART'} {loc.nom} -> {site.nom}"
|
("flux ", verifier_flux(site, loc, face))):
|
||||||
+ "".join(f"\n - {x}" for x in e))
|
total += len(e)
|
||||||
|
print(f"{'OK ' if not e else 'ECART'} {loc.nom} -> {site.nom} ({quoi})"
|
||||||
|
+ "".join(f"\n - {x}" for x in e))
|
||||||
return 1 if total else 0
|
return 1 if total else 0
|
||||||
if args.fiche:
|
if args.fiche:
|
||||||
loc = Locataire.charger(args.fiche)
|
loc = Locataire.charger(args.fiche)
|
||||||
|
|
|
||||||
|
|
@ -4715,6 +4715,31 @@ def preuve_face_reseau() -> tuple[bool, str]:
|
||||||
f"(decouverte, devis Proxmox, frontiere, DNS public, sauvegarde).")
|
f"(decouverte, devis Proxmox, frontiere, DNS public, sauvegarde).")
|
||||||
|
|
||||||
|
|
||||||
|
def preuve_flux_concordent() -> tuple[bool, str]:
|
||||||
|
"""Le pare-feu de chaque machine (nftables, resolu par le locataire) et celui de Proxmox
|
||||||
|
(devise par le site) admettent-ils les memes entrees ?
|
||||||
|
|
||||||
|
POURQUOI (2026-10-04, `docs/conception-contextes.md` §2.4, constat 2). Les flux etaient
|
||||||
|
calcules DEUX FOIS, par le locataire et par le site, chacun avec sa version du moteur. La
|
||||||
|
face reseau publie ceux du locataire (`flux-genere/*.connectivite.json`). Machine par
|
||||||
|
machine et port par port, les sources admises doivent etre les memes, une fois neutralisees
|
||||||
|
les differences d'ecriture (decrites dans `contexte.py`). Premiere mesure : 3 ecarts par
|
||||||
|
locataire, tous sur le courriel — le locataire jetait les clients internes d'un port aussi
|
||||||
|
public ; il les publie desormais (`sources_declarees`).
|
||||||
|
"""
|
||||||
|
sys.path.insert(0, str(RACINE / "scripts"))
|
||||||
|
import contexte as _ctx
|
||||||
|
couples = _ctx.couples_hebergeur()
|
||||||
|
if not couples:
|
||||||
|
return True, "Aucun site frere : aucun flux a confronter."
|
||||||
|
ecarts = [f"{l.nom} : {e}" for s, l in couples for e in _ctx.verifier_flux(s, l)]
|
||||||
|
if ecarts:
|
||||||
|
return False, (f"{len(ecarts)} ecart(s) entre le pare-feu des machines et celui de Proxmox : "
|
||||||
|
+ " | ".join(ecarts[:4]))
|
||||||
|
return True, (f"{len(couples)} locataire(s) : machine par machine et port par port, nftables et "
|
||||||
|
f"Proxmox admettent les memes sources.")
|
||||||
|
|
||||||
|
|
||||||
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"]]},
|
||||||
|
|
@ -4905,6 +4930,8 @@ PREUVES: list[dict] = [
|
||||||
"refs": [], "func": preuve_fiche_du_site},
|
"refs": [], "func": preuve_fiche_du_site},
|
||||||
{"id": "P85", "titre": "La face reseau du locataire dit exactement ce que le site en tire",
|
{"id": "P85", "titre": "La face reseau du locataire dit exactement ce que le site en tire",
|
||||||
"refs": [], "func": preuve_face_reseau},
|
"refs": [], "func": preuve_face_reseau},
|
||||||
|
{"id": "P86", "titre": "Machine par machine, le locataire et Proxmox admettent les memes entrees",
|
||||||
|
"refs": [], "func": preuve_flux_concordent},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -867,9 +867,16 @@ def generer_nftables(flux_par_role: dict[str, list[dict]], site: bool = False) -
|
||||||
continue # lo deja accepte
|
continue # lo deja accepte
|
||||||
regles.append(f" {saddr}{_selecteur_nft(fl['protocole'], port)} accept"
|
regles.append(f" {saddr}{_selecteur_nft(fl['protocole'], port)} accept"
|
||||||
f" # {g}: {fl.get('raison','')}")
|
f" # {g}: {fl.get('raison','')}")
|
||||||
entrees.setdefault(hote, []).append(
|
entree = {"proto": proto, "port": str(port), "role": g,
|
||||||
{"proto": proto, "port": str(port), "role": g,
|
"sources": list(srcs) if saddr else ["0.0.0.0/0"]}
|
||||||
"sources": list(srcs) if saddr else ["0.0.0.0/0"]})
|
# « TOUT LE MONDE » NE DOIT PAS EFFACER QUI ETAIT NOMME (2026-10-04). L'hote
|
||||||
|
# accepte de partout, et c'est juste pour lui ; mais le pare-feu de Proxmox,
|
||||||
|
# dont l'Internet exclut les plages privees, doit admettre les clients
|
||||||
|
# internes NOMMEMENT (le 25 de Postfix : `client_smtp`). Les jeter ici
|
||||||
|
# privait la face reseau du locataire de ce que le site doit en lire.
|
||||||
|
if not saddr and srcs:
|
||||||
|
entree["sources_declarees"] = sorted(str(s) for s in srcs)
|
||||||
|
entrees.setdefault(hote, []).append(entree)
|
||||||
# Testable depuis la source : TCP, et l'ICMP « echo » (ping). L'UDP ne se
|
# Testable depuis la source : TCP, et l'ICMP « echo » (ping). L'UDP ne se
|
||||||
# « connecte » pas ; les autres types ICMP ne se provoquent pas.
|
# « connecte » pas ; les autres types ICMP ne se provoquent pas.
|
||||||
if proto == "tcp" or (proto == "icmp" and str(port) == "echo-request"):
|
if proto == "tcp" or (proto == "icmp" and str(port) == "echo-request"):
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ Puis, si les vrais depots sont la : chaque locataire declare par un site porte l
|
||||||
site lui attribue, et nomme un hebergeur qui existe ; la fiche de chaque hebergeur dit
|
site lui attribue, et nomme un hebergeur qui existe ; la fiche de chaque hebergeur dit
|
||||||
exactement ce que son locataire porte, et une fiche alteree (sept facons) est vue en ecart ; la face
|
exactement ce que son locataire porte, et une fiche alteree (sept facons) est vue en ecart ; la face
|
||||||
reseau de chaque locataire dit ce que les consommateurs du site en tirent, et une face alteree
|
reseau de chaque locataire dit ce que les consommateurs du site en tirent, et une face alteree
|
||||||
(huit facons) est vue en ecart.
|
(huit facons) est vue en ecart ; machine par machine, le pare-feu du locataire et celui de
|
||||||
|
Proxmox admettent les memes entrees, et des flux alteres (quatre facons) sont vus en ecart.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
@ -238,6 +239,49 @@ def faces() -> None:
|
||||||
verifier(bool(C.verifier_face(site, loc, f)), f"une face reseau alteree ({nom}) est vue en ecart")
|
verifier(bool(C.verifier_face(site, loc, f)), f"une face reseau alteree ({nom}) est vue en ecart")
|
||||||
|
|
||||||
|
|
||||||
|
def flux() -> None:
|
||||||
|
"""Machine par machine, le pare-feu du locataire et celui de Proxmox admettent les memes
|
||||||
|
entrees — et une face dont les flux sont alteres se voit."""
|
||||||
|
import copy
|
||||||
|
couples = C.couples_hebergeur()
|
||||||
|
if not couples:
|
||||||
|
print("SAUTE aucun site frere : aucun flux a confronter")
|
||||||
|
return
|
||||||
|
for site, loc in couples:
|
||||||
|
e = C.verifier_flux(site, loc)
|
||||||
|
verifier(not e, f"flux {loc.nom} : le locataire et Proxmox admettent les memes entrees"
|
||||||
|
+ (f" ({e[0]})" if e else ""))
|
||||||
|
site, loc = couples[0]
|
||||||
|
juste = loc.face_reseau()
|
||||||
|
mta = next(h for h, f in juste["flux"].items()
|
||||||
|
if any(x.get("sources_declarees") for x in f["entrees"]))
|
||||||
|
autre = next(h for h, f in juste["flux"].items()
|
||||||
|
if any(len(x.get("sources") or []) > 1 for x in f["entrees"]))
|
||||||
|
|
||||||
|
def retirer_une_source(f):
|
||||||
|
e = next(x for x in f["flux"][autre]["entrees"] if len(x.get("sources") or []) > 1)
|
||||||
|
e["sources"] = e["sources"][1:]
|
||||||
|
|
||||||
|
def ajouter_un_port(f):
|
||||||
|
f["flux"][autre]["entrees"].append({"proto": "tcp", "port": "6666", "role": "x",
|
||||||
|
"sources": ["192.0.2.66"]})
|
||||||
|
|
||||||
|
def effacer_declarees(f):
|
||||||
|
for x in f["flux"][mta]["entrees"]:
|
||||||
|
x.pop("sources_declarees", None)
|
||||||
|
|
||||||
|
def retirer_admin(f):
|
||||||
|
f["flux"][autre]["administration"] = []
|
||||||
|
|
||||||
|
for nom, alterer in (("une source retiree", retirer_une_source),
|
||||||
|
("un port ajoute", ajouter_un_port),
|
||||||
|
(f"sources declarees effacees sur {mta}", effacer_declarees),
|
||||||
|
("administration retiree", retirer_admin)):
|
||||||
|
f = copy.deepcopy(juste)
|
||||||
|
alterer(f)
|
||||||
|
verifier(bool(C.verifier_flux(site, loc, f)), f"des flux alteres ({nom}) sont vus en ecart")
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
d = Path(tmp)
|
d = Path(tmp)
|
||||||
|
|
@ -248,6 +292,7 @@ def main() -> int:
|
||||||
monter_restaure()
|
monter_restaure()
|
||||||
fiches()
|
fiches()
|
||||||
faces()
|
faces()
|
||||||
|
flux()
|
||||||
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