contexte : etape 2b, la face reseau du locataire (les faits)
locataire.face_reseau() publie index, machines, groupes, administration, zones publiques et cle de sauvegarde ; verifier_face la confronte aux consommateurs du site (decouverte, devis Proxmox, frontiere, inventaire du site). P85 : aucun ecart ; huit alterations vues en ecart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8251022148
commit
077c6c91b4
7 changed files with 282 additions and 4 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 84 preuves de `make prouver` : elles lisent le dépôt, sans le
|
C'est aussi ce que font les 85 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.**
|
||||||
|
|
||||||
|
|
|
||||||
43
CHANGELOG.md
43
CHANGELOG.md
|
|
@ -1,5 +1,48 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 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
|
||||||
|
réseau se livre en deux temps vérifiés : d'abord les **faits** que le site lit chez le
|
||||||
|
locataire (ici), ensuite ses **flux résolus**, confrontés aux devis de la frontière et de
|
||||||
|
Proxmox (à suivre).
|
||||||
|
|
||||||
|
**Fait** (`scripts/contexte.py`) :
|
||||||
|
- `locataire.face_reseau()` publie, calculé **chez le locataire** : son index ; ses machines
|
||||||
|
(adresse, groupes, paramètres de matérialisation `proxmox_*`) ; ses groupes ; son
|
||||||
|
administration (l'intrant `nftables_admin_ssh`, son tunnel, ses pairs WireGuard avec leurs
|
||||||
|
clés **publiques**) ; ses zones publiques (zones, signées, adresse et port de son
|
||||||
|
primaire) ; la clé publique de sa sauvegarde. Elle porte l'empreinte de ses six sources.
|
||||||
|
- `verifier_face(site, locataire)` interroge **les consommateurs du site eux-mêmes**, le site
|
||||||
|
monté : la découverte des locataires (`devis_reseau`), le devis du pare-feu Proxmox (la
|
||||||
|
flotte, chaque groupe qu'il utilise, l'administration), la frontière (`admin_de`), et
|
||||||
|
l'inventaire du site (relations du DNS public, comptes de sauvegarde).
|
||||||
|
- `--face <locataire>` l'affiche ; `--verifier-faces` confronte chaque couple.
|
||||||
|
|
||||||
|
**Un faux écart, corrigé avant le commit** : la première comparaison exigeait un ensemble
|
||||||
|
d'adresses par groupe dans le devis Proxmox, et en signalait 19 absents par locataire. Le
|
||||||
|
devis ne garde que les ensembles que ses règles **utilisent** (13 chez chacun) ; la
|
||||||
|
vérification se fait désormais dans ce sens.
|
||||||
|
|
||||||
|
**Deux lectures fragiles du site, que la face réseau fera disparaître à l'étape 3** :
|
||||||
|
`site_inventaire.py` lit la clé de sauvegarde sous `inventories/principal/` écrit en dur, et
|
||||||
|
re-dérive l'adresse du primaire DNS avec la séquence 1 par défaut (juste aujourd'hui, les
|
||||||
|
primaires sont des `-01`). La face réseau prend l'une dans le dossier d'inventaire réel du
|
||||||
|
locataire, l'autre dans son inventaire.
|
||||||
|
|
||||||
|
**Ce que la vérification ne couvre pas encore** : la matérialisation est recopiée de
|
||||||
|
l'inventaire, que le clonage lit par Ansible, sans consommateur Python à confronter ; les pairs
|
||||||
|
WireGuard ne sont vérifiés qu'à travers le tunnel qu'ils ouvrent ; les flux arrivent dans le
|
||||||
|
second temps.
|
||||||
|
|
||||||
|
**Éprouvé** :
|
||||||
|
- **P85**, « La face réseau du locataire dit exactement ce que le site en tire » : 2 faces,
|
||||||
|
aucun écart.
|
||||||
|
- `test_contexte.py`, 60 contrôles : les deux faces réelles concordent, et une face altérée de
|
||||||
|
**huit façons** (index, adresse d'une machine, membres d'un groupe, administration déclarée,
|
||||||
|
tunnel, zones publiques, primaire DNS, clé de sauvegarde) est vue en écart à chaque fois.
|
||||||
|
- `make verifier` conforme, **85/85**. Les trois documents comptent 85 preuves.
|
||||||
|
|
||||||
## 2026-10-04 (80) — Étape 2, première moitié : la fiche du site, et la preuve qu'elle dit vrai
|
## 2026-10-04 (80) — Étape 2, première moitié : la fiche du site, et la preuve qu'elle dit vrai
|
||||||
|
|
||||||
**Le chemin** (`docs/conception-contextes.md` §6), étape 2 : générer les fiches **à côté** de
|
**Le chemin** (`docs/conception-contextes.md` §6), étape 2 : générer les fiches **à côté** de
|
||||||
|
|
|
||||||
|
|
@ -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 84 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
`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
|
||||||
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 (84 preuves, zéro réseau)
|
make prouver le dépôt est-il cohérent avec lui-même (85 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ù
|
||||||
|
|
|
||||||
|
|
@ -361,6 +361,81 @@ class Locataire(Ecosysteme):
|
||||||
return None
|
return None
|
||||||
return Site.charger(nom, self.depot.parent)
|
return Site.charger(nom, self.depot.parent)
|
||||||
|
|
||||||
|
# LE CONTRAT, SENS LOCATAIRE -> SITE (§2.4) ---------------------------------------------
|
||||||
|
def face_reseau(self) -> dict:
|
||||||
|
"""Ce que CE locataire demande a son site, en une fiche — les faits, pas encore les flux.
|
||||||
|
|
||||||
|
Le site lisait jusqu'ici six fichiers internes du locataire (relevé du 2026-10-04) :
|
||||||
|
sa nomenclature, son inventaire genere, ses `group_vars` (`nftables_admin_ssh`,
|
||||||
|
`serveur_backup_pubkey`), son `plan/acces.yml`, son `plan/domaines.yml` et ce qui
|
||||||
|
l'accompagne. La face reseau les publie, calcules ICI, chez le locataire, qui seul sait
|
||||||
|
lire ses fichiers : l'adresse de son primaire DNS vient de son inventaire (le site la
|
||||||
|
re-derivait, sequence 1 par defaut), sa cle de sauvegarde de SON dossier d'inventaire
|
||||||
|
(le site ecrivait `principal` en dur).
|
||||||
|
|
||||||
|
LES FLUX RESOLUS sont la seconde moitie de l'etape 2 ; ils ne sont pas encore ici.
|
||||||
|
"""
|
||||||
|
from inventory_rules import charger_acces, est_groupe_operationnel, tunnel_admin_de
|
||||||
|
from resoudre_flux import _enfants, _hotes_du_groupe, _ip_par_hote
|
||||||
|
data = _yaml(self.inventaire())
|
||||||
|
hv = _hostvars(self.inventaire())
|
||||||
|
ips = _ip_par_hote(data) if data else {}
|
||||||
|
groupes = {g: sorted(h for h in _hotes_du_groupe(data, g) if h in ips)
|
||||||
|
for g in sorted(_enfants(data)) if est_groupe_operationnel(g)} if data else {}
|
||||||
|
machines = {
|
||||||
|
h: {"ip": ip,
|
||||||
|
"groupes": sorted(g for g, membres in groupes.items() if h in membres),
|
||||||
|
"materialisation": {k: v for k, v in sorted((hv.get(h) or {}).items())
|
||||||
|
if k.startswith("proxmox_")}}
|
||||||
|
for h, ip in sorted(ips.items())}
|
||||||
|
# Les reseaux d'administration que le locataire DECLARE (l'intrant), et son tunnel.
|
||||||
|
intrant: list[str] = []
|
||||||
|
gv_all = self.dossier_inventaire() / "group_vars" / "all"
|
||||||
|
for fichier in sorted(gv_all.glob("*.yml")) if gv_all.is_dir() else []:
|
||||||
|
if "vault" in fichier.name:
|
||||||
|
continue
|
||||||
|
d = _yaml(fichier)
|
||||||
|
if isinstance(d, dict) and d.get("nftables_admin_ssh"):
|
||||||
|
src = d["nftables_admin_ssh"]
|
||||||
|
intrant = [str(s) for s in src] if isinstance(src, list) else [str(src)]
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
pairs = charger_acces(self.plan / "acces.yml").get("acces_admin_vpn") or {}
|
||||||
|
except (OSError, ValueError):
|
||||||
|
pairs = {}
|
||||||
|
# Ses zones publiques : celles qu'il fait servir par le site (`primaire-cache`).
|
||||||
|
doms = self.lire_plan("domaines.yml").get("domaines_publics") or {}
|
||||||
|
apps = self.lire_plan("applications.yml").get("applications") or {}
|
||||||
|
zones = sorted(z for z, c in doms.items()
|
||||||
|
if isinstance(c, dict) and str(c.get("autorite")) == "primaire-cache")
|
||||||
|
hote_dns = next((str(a.get("hote")) for a in apps.values()
|
||||||
|
if str(a.get("groupe")) == "serveur_powerdns"), "")
|
||||||
|
colocalise = any(str(a.get("groupe")) == "serveur_resolveur" and str(a.get("hote")) == hote_dns
|
||||||
|
for a in apps.values())
|
||||||
|
dns_public = ({"zones": zones,
|
||||||
|
"signees": sorted(z for z in zones if doms[z].get("dnssec") is True),
|
||||||
|
"primaire": ips.get(hote_dns, ""),
|
||||||
|
"port": 5300 if colocalise else 53} if zones else {})
|
||||||
|
cle_sauvegarde = str(_yaml(self.dossier_inventaire() / "group_vars" / "serveur_backup.yml")
|
||||||
|
.get("serveur_backup_pubkey") or "").strip()
|
||||||
|
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 / "domaines.yml", self.plan / "applications.yml",
|
||||||
|
self.dossier_inventaire() / "group_vars" / "serveur_backup.yml")}
|
||||||
|
return {
|
||||||
|
"locataire": self.nom,
|
||||||
|
"hebergeur": self.nom_hebergeur(),
|
||||||
|
"sources": sources,
|
||||||
|
"index": self.index,
|
||||||
|
"machines": machines,
|
||||||
|
"groupes": groupes,
|
||||||
|
"administration": {"intrant": intrant, "tunnel": tunnel_admin_de(self.depot),
|
||||||
|
"pairs": {n: {k: c.get(k) for k in ("cle_publique", "adresse", "etat")}
|
||||||
|
for n, c in sorted(pairs.items()) if isinstance(c, dict)}},
|
||||||
|
"dns_public": dns_public,
|
||||||
|
"sauvegarde": {"cle_publique": cle_sauvegarde},
|
||||||
|
}
|
||||||
|
|
||||||
def decrire(self) -> dict:
|
def decrire(self) -> dict:
|
||||||
d = super().decrire()
|
d = super().decrire()
|
||||||
d.update({"inventaire": str(self.inventaire()), "hebergeur": self.nom_hebergeur()})
|
d.update({"inventaire": str(self.inventaire()), "hebergeur": self.nom_hebergeur()})
|
||||||
|
|
@ -453,6 +528,81 @@ def verifier_fiche(site: "Site", locataire: "Locataire", fiche: dict | None = No
|
||||||
return ecarts
|
return ecarts
|
||||||
|
|
||||||
|
|
||||||
|
def verifier_face(site: "Site", locataire: "Locataire", face: dict | None = None) -> list[str]:
|
||||||
|
"""Les ecarts entre la face reseau du locataire et ce que les CONSOMMATEURS DU SITE en tirent
|
||||||
|
aujourd'hui, en lisant ses fichiers internes.
|
||||||
|
|
||||||
|
On interroge les consommateurs eux-memes, le site monte : la decouverte des locataires
|
||||||
|
(`devis_reseau`), le devis du pare-feu Proxmox (ses ensembles d'adresses par groupe et
|
||||||
|
d'administration), la frontiere (`admin_de`), et l'inventaire du site (relations du DNS
|
||||||
|
public, comptes de sauvegarde). Vide = chacun lirait la face reseau sans rien changer.
|
||||||
|
"""
|
||||||
|
import devis_proxmox_fw
|
||||||
|
import devis_reseau
|
||||||
|
import site_inventaire
|
||||||
|
f = face if face is not None else locataire.face_reseau()
|
||||||
|
ecarts: list[str] = []
|
||||||
|
with _monter(site):
|
||||||
|
decouverts = {nom: n for nom, _p, n in devis_reseau.decouvrir_du_site()}
|
||||||
|
bloc = next((b for b in devis_proxmox_fw.construire(devis_reseau.decouvrir_du_site())["blocs"]
|
||||||
|
if b.get("tenant") == locataire.nom), None)
|
||||||
|
admin_frontiere = sorted(devis_reseau.admin_de(locataire.nom))
|
||||||
|
inv = site_inventaire.inventaire() or {}
|
||||||
|
hv_site = (inv.get("_meta") or {}).get("hostvars") or {}
|
||||||
|
|
||||||
|
# 1. L'index, tel que le site le decouvre.
|
||||||
|
n = decouverts.get(locataire.nom)
|
||||||
|
if n is None:
|
||||||
|
ecarts.append("le site ne decouvre pas ce locataire")
|
||||||
|
elif n.get("index") != f["index"]:
|
||||||
|
ecarts.append(f"index : le site decouvre {n.get('index')}, la face dit {f['index']}")
|
||||||
|
|
||||||
|
# 2. Les machines et leurs groupes, tels que le pare-feu Proxmox les range.
|
||||||
|
ips = {h: m["ip"] for h, m in f["machines"].items()}
|
||||||
|
if bloc is None:
|
||||||
|
ecarts.append("le devis Proxmox n'a aucun bloc pour ce locataire")
|
||||||
|
else:
|
||||||
|
ens = bloc.get("ipsets") or {}
|
||||||
|
flotte = next((v["membres"] for v in ens.values() if str(v.get("role", "")).startswith("flotte")), [])
|
||||||
|
if sorted(flotte) != sorted(set(ips.values())):
|
||||||
|
ecarts.append(f"flotte : le devis Proxmox range {sorted(flotte)}, la face {sorted(set(ips.values()))}")
|
||||||
|
# Le devis ne garde que les ensembles que ses regles UTILISENT : chaque groupe qu'il range
|
||||||
|
# doit exister dans la face avec les memes adresses ; un groupe inutilise n'est pas un ecart.
|
||||||
|
speciaux = ("flotte", "Internet", "reseaux d'admin")
|
||||||
|
par_groupe = {str(v.get("role")): sorted(v.get("membres") or []) for v in ens.values()
|
||||||
|
if not str(v.get("role", "")).startswith(speciaux)}
|
||||||
|
for g, membres in sorted(par_groupe.items()):
|
||||||
|
if g not in f["groupes"]:
|
||||||
|
ecarts.append(f"groupe {g} : range par le devis Proxmox, absent de la face")
|
||||||
|
elif membres != sorted({ips[h] for h in f["groupes"][g]}):
|
||||||
|
ecarts.append(f"groupe {g} : le devis Proxmox range {membres}, "
|
||||||
|
f"la face {sorted({ips[h] for h in f['groupes'][g]})}")
|
||||||
|
adm = next((v["membres"] for v in ens.values() if str(v.get("role", "")).startswith("reseaux d'admin")), [])
|
||||||
|
attendu_adm = sorted(set(f["administration"]["intrant"]) | set(f["administration"]["tunnel"]))
|
||||||
|
if sorted(adm) != attendu_adm:
|
||||||
|
ecarts.append(f"administration (Proxmox) : le devis admet {sorted(adm)}, la face {attendu_adm}")
|
||||||
|
|
||||||
|
# 3. L'administration, telle que la frontiere la lit.
|
||||||
|
if admin_frontiere != sorted(f["administration"]["intrant"]):
|
||||||
|
ecarts.append(f"administration (frontiere) : le site lit {admin_frontiere}, "
|
||||||
|
f"la face {sorted(f['administration']['intrant'])}")
|
||||||
|
|
||||||
|
# 4. Le DNS public et la sauvegarde, tels que l'inventaire du site les porte.
|
||||||
|
relations = [r for v in hv_site.values() for r in (v.get("serveur_dns_public_relations") or [])
|
||||||
|
if r.get("ecosysteme") == locataire.nom]
|
||||||
|
rel = relations[0] if relations else {}
|
||||||
|
attendu_dns = f["dns_public"]
|
||||||
|
vu_dns = ({k: rel.get(k) for k in ("zones", "signees", "primaire", "port")} if rel else {})
|
||||||
|
if vu_dns != attendu_dns:
|
||||||
|
ecarts.append(f"DNS public : l'inventaire du site porte {vu_dns}, la face {attendu_dns}")
|
||||||
|
comptes = [c for v in hv_site.values() for c in (v.get("serveur_backup_site_locataires") or [])
|
||||||
|
if c.get("nom") == locataire.nom]
|
||||||
|
cle_site = comptes[0].get("pubkey") if comptes else ""
|
||||||
|
if (cle_site or "") != f["sauvegarde"]["cle_publique"]:
|
||||||
|
ecarts.append("sauvegarde : la cle que le site autorise n'est pas celle que le locataire publie")
|
||||||
|
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."""
|
||||||
|
|
@ -534,7 +684,26 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
ap.add_argument("--fiche", metavar="LOCATAIRE", help="la fiche que son hebergeur lui destine")
|
ap.add_argument("--fiche", metavar="LOCATAIRE", help="la fiche que son hebergeur lui destine")
|
||||||
ap.add_argument("--verifier-fiches", action="store_true",
|
ap.add_argument("--verifier-fiches", action="store_true",
|
||||||
help="chaque fiche dit-elle ce que le locataire porte ?")
|
help="chaque fiche dit-elle ce que le locataire porte ?")
|
||||||
|
ap.add_argument("--face", metavar="LOCATAIRE", help="la face reseau qu'il publie pour son site")
|
||||||
|
ap.add_argument("--verifier-faces", action="store_true",
|
||||||
|
help="chaque face reseau dit-elle ce que le site en tire ?")
|
||||||
args = ap.parse_args(argv)
|
args = ap.parse_args(argv)
|
||||||
|
if args.face:
|
||||||
|
print(yaml.safe_dump(Locataire.charger(args.face).face_reseau(), allow_unicode=True,
|
||||||
|
sort_keys=False), end="")
|
||||||
|
return 0
|
||||||
|
if args.verifier_faces:
|
||||||
|
couples = couples_hebergeur()
|
||||||
|
if not couples:
|
||||||
|
print("Aucun site frere : aucune face reseau a verifier.")
|
||||||
|
return 0
|
||||||
|
total = 0
|
||||||
|
for site, loc in couples:
|
||||||
|
e = verifier_face(site, loc)
|
||||||
|
total += len(e)
|
||||||
|
print(f"{'OK ' if not e else 'ECART'} {loc.nom} -> {site.nom}"
|
||||||
|
+ "".join(f"\n - {x}" for x in e))
|
||||||
|
return 1 if total else 0
|
||||||
if args.fiche:
|
if args.fiche:
|
||||||
loc = Locataire.charger(args.fiche)
|
loc = Locataire.charger(args.fiche)
|
||||||
site = loc.site()
|
site = loc.site()
|
||||||
|
|
|
||||||
|
|
@ -4692,6 +4692,29 @@ def preuve_fiche_du_site() -> tuple[bool, str]:
|
||||||
f"porte (copies, racine, inventaire genere).")
|
f"porte (copies, racine, inventaire genere).")
|
||||||
|
|
||||||
|
|
||||||
|
def preuve_face_reseau() -> tuple[bool, str]:
|
||||||
|
"""La face reseau que chaque locataire publie dit-elle ce que les consommateurs du site en
|
||||||
|
tirent aujourd'hui, en lisant ses fichiers internes ?
|
||||||
|
|
||||||
|
POURQUOI (2026-10-04, `docs/conception-contextes.md` §2.4). Le site lisait six fichiers
|
||||||
|
internes de ses locataires — nomenclature, inventaire, `group_vars`, acces, domaines. La
|
||||||
|
face reseau les publie, calculee chez le locataire. Avant que le site la lise a leur place
|
||||||
|
(etape 3), elle doit dire EXACTEMENT ce que la decouverte, le devis Proxmox, la frontiere
|
||||||
|
et l'inventaire du site en tirent. Les flux resolus suivront ; ils ne sont pas encore la.
|
||||||
|
"""
|
||||||
|
sys.path.insert(0, str(RACINE / "scripts"))
|
||||||
|
import contexte as _ctx
|
||||||
|
couples = _ctx.couples_hebergeur()
|
||||||
|
if not couples:
|
||||||
|
return True, "Aucun site frere : aucune face reseau a confronter."
|
||||||
|
ecarts = [f"{l.nom} -> {s.nom} : {e}" for s, l in couples for e in _ctx.verifier_face(s, l)]
|
||||||
|
if ecarts:
|
||||||
|
return False, (f"{len(ecarts)} ecart(s) entre la face reseau et ce que le site en tire : "
|
||||||
|
+ " | ".join(ecarts[:4]))
|
||||||
|
return True, (f"{len(couples)} face(s) reseau : chacune dit exactement ce que le site en tire "
|
||||||
|
f"(decouverte, devis Proxmox, frontiere, DNS public, sauvegarde).")
|
||||||
|
|
||||||
|
|
||||||
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"]]},
|
||||||
|
|
@ -4880,6 +4903,8 @@ PREUVES: list[dict] = [
|
||||||
"refs": [], "func": preuve_runbooks_suivent_le_makefile},
|
"refs": [], "func": preuve_runbooks_suivent_le_makefile},
|
||||||
{"id": "P84", "titre": "La fiche du site dit exactement ce que chaque locataire porte",
|
{"id": "P84", "titre": "La fiche du site dit exactement ce que chaque locataire porte",
|
||||||
"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",
|
||||||
|
"refs": [], "func": preuve_face_reseau},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ Sur une arborescence FABRIQUEE (un site, deux locataires, un depot ambigu), on v
|
||||||
- un contexte nomme qui ne designe rien est un REFUS, pas un contexte vide.
|
- un contexte nomme qui ne designe rien est un REFUS, pas un contexte vide.
|
||||||
Puis, si les vrais depots sont la : chaque locataire declare par un site porte l'index que ce
|
Puis, si les vrais depots sont la : chaque locataire declare par un site porte l'index que ce
|
||||||
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.
|
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
|
||||||
|
(huit facons) est vue en ecart.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
@ -198,6 +200,44 @@ def fiches() -> None:
|
||||||
f"une fiche alteree ({nom}) est vue en ecart")
|
f"une fiche alteree ({nom}) est vue en ecart")
|
||||||
|
|
||||||
|
|
||||||
|
def faces() -> None:
|
||||||
|
"""La face reseau dit ce que les consommateurs du site en tirent — et chaque ecart se voit."""
|
||||||
|
import copy
|
||||||
|
couples = C.couples_hebergeur()
|
||||||
|
if not couples:
|
||||||
|
print("SAUTE aucun site frere : aucune face reseau a confronter")
|
||||||
|
return
|
||||||
|
for site, loc in couples:
|
||||||
|
e = C.verifier_face(site, loc)
|
||||||
|
verifier(not e, f"face reseau {loc.nom} -> {site.nom} : identique a ce que le site en tire"
|
||||||
|
+ (f" ({e[0]})" if e else ""))
|
||||||
|
site, loc = couples[0]
|
||||||
|
juste = loc.face_reseau()
|
||||||
|
groupe = next(g for g in ("serveur_postfix", "serveur_nginx", "client_backup") if juste["groupes"].get(g))
|
||||||
|
hote = juste["groupes"][groupe][0]
|
||||||
|
|
||||||
|
def deplacer_machine(f):
|
||||||
|
f["machines"][hote]["ip"] = "192.0.2.10"
|
||||||
|
|
||||||
|
def retirer_du_groupe(f):
|
||||||
|
f["groupes"][groupe] = [h for h in f["groupes"][groupe] if h != hote]
|
||||||
|
|
||||||
|
alterations = {
|
||||||
|
"index": lambda f: f.__setitem__("index", (f["index"] or 0) + 1),
|
||||||
|
"adresse d'une machine": deplacer_machine,
|
||||||
|
f"membres du groupe {groupe}": retirer_du_groupe,
|
||||||
|
"administration declaree": lambda f: f["administration"].__setitem__("intrant", ["192.0.2.0/24"]),
|
||||||
|
"tunnel d'administration": lambda f: f["administration"].__setitem__("tunnel", ["192.0.2.128/25"]),
|
||||||
|
"zones publiques": lambda f: f["dns_public"].__setitem__("zones", ["exemple.invalid"]),
|
||||||
|
"primaire DNS": lambda f: f["dns_public"].__setitem__("primaire", "192.0.2.53"),
|
||||||
|
"cle de sauvegarde": lambda f: f["sauvegarde"].__setitem__("cle_publique", "ssh-ed25519 AAAA-fausse"),
|
||||||
|
}
|
||||||
|
for nom, alterer in alterations.items():
|
||||||
|
f = copy.deepcopy(juste)
|
||||||
|
alterer(f)
|
||||||
|
verifier(bool(C.verifier_face(site, loc, f)), f"une face reseau alteree ({nom}) est vue en ecart")
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
d = Path(tmp)
|
d = Path(tmp)
|
||||||
|
|
@ -207,6 +247,7 @@ def main() -> int:
|
||||||
vrais_depots()
|
vrais_depots()
|
||||||
monter_restaure()
|
monter_restaure()
|
||||||
fiches()
|
fiches()
|
||||||
|
faces()
|
||||||
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