P79 : une derivation qui ne trouve rien ne passe plus pour un succes
Sept replis des deux derniers jours, un temoin chacun, lu hors de la derivation qu'il juge : jumeaux d'amorcage, patte de zone, edge d'exposition, certificat bouchon, rechargement, sortie vers l'Internet, regles d'hote par instance. Eprouvee par reinjection de chaque faute ; a trouve deux flux-genere perimes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ae212c0252
commit
85c7f9710b
5 changed files with 296 additions and 5 deletions
|
|
@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente
|
|||
## Écrire, puis relire (D-68)
|
||||
|
||||
`--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 78 preuves de `make prouver` : elles lisent le dépôt, sans le
|
||||
C'est aussi ce que font les 79 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
|
||||
dépôt annonce.**
|
||||
|
||||
|
|
|
|||
57
CHANGELOG.md
57
CHANGELOG.md
|
|
@ -1,5 +1,62 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-15 (7) — P79 : une derivation qui ne trouve rien ne passe plus pour un succes
|
||||
|
||||
En deux jours, pour monter l'edge du site puis les trois consoles, le meme defaut est
|
||||
revenu sous sept noms. Chaque fois un repli rendait un SUCCES au lieu d'un refus :
|
||||
deploiement vert, devis muet, harnais vert. *Une derivation qui ne trouve rien ne se
|
||||
distinguait pas d'une derivation qui n'a rien a trouver.*
|
||||
|
||||
### Ce que P79 regarde
|
||||
|
||||
| Temoin | Le repli qu'il attrape |
|
||||
|---|---|
|
||||
| jumeaux | `dns_amorcage` derive sans `artefacts_amorcage` : le gabarit garde son ancien proxy apt |
|
||||
| patte | une zone occupee absente de `opnsense_if_zones` : regles posees sur la patte plate |
|
||||
| edge | une exposition qui retombe sur son propre groupe dans un ecosysteme qui A un edge |
|
||||
| bouchon | l'edge du site sert `ssl-cert-snakeoil.pem`, faute de group_vars |
|
||||
| rechargement | un edge qui renouvelle son certificat sans recharger nginx |
|
||||
| internet | une sortie vers un role absent traduite en sortie vers l'Internet |
|
||||
| flux | les regles d'hote rejouees pour CHAQUE locataire, pas seulement l'instance montee |
|
||||
|
||||
Chaque temoin lit sa source directement — inventaire, plan, table ecrite, `meta/flux.yml`
|
||||
— et ne passe jamais par la derivation qu'il juge. La lecon de P43 et de P67 : une garde
|
||||
qui reproduit le raisonnement qu'elle verifie ne verifie rien.
|
||||
|
||||
Le temoin des flux rejoue `resoudre_flux.py nftables` dans un dossier temporaire qui
|
||||
reprend l'instance par liens : rien n'est ecrit dans les depots.
|
||||
|
||||
### Eprouvee en lui remettant chaque faute sous les yeux
|
||||
|
||||
Chaque temoin prend ses donnees en parametre. Les sept fautes ont ete reinjectees une a
|
||||
une dans les donnees reelles du site (zone retiree de la table, `edge` retire de
|
||||
`domaines.yml`, certificat, rechargement et jumeau retires de l'inventaire, regle
|
||||
`!SETOPS_INTERNES` sur 636 ajoutee au devis) : sept refus, et zero sur les donnees saines.
|
||||
Sans edge, le repli « le service se sert lui-meme » reste juste, et la garde se tait.
|
||||
|
||||
### Et elle a trouve deux ecarts reels des sa premiere execution
|
||||
|
||||
OPS-Chezlepro-lab 14 regles de machines retirees du plan, aucune pour ops-01
|
||||
OPS-Patient0 4 regles perimees (admin en 10.17.0.0/24, sans le refus
|
||||
`admin-prohibited`), aucune pour ops-01
|
||||
|
||||
Les deux dataient d'avant le renumerotage du site : `make flux` n'avait jamais ete relance
|
||||
avec eux montes. Regeneres par `SETOPS_INSTANCE`, sans basculer l'instance. Ce sont des
|
||||
apercus, rien n'est applique a une machine.
|
||||
|
||||
### Ce qu'elle ne couvre pas
|
||||
|
||||
La collision des noms publics par groupe (P67 la garde). `client_pki` qui rend
|
||||
`changed=0` sans comparer ses SAN : cela se mesure sur la machine (`make
|
||||
certificats-plan`). Un registre facultatif exige par `include_vars` : celui-la echouait
|
||||
bruyamment.
|
||||
|
||||
### Au passage
|
||||
|
||||
Les gabarits de voute de `OPS-Patient0` et `OPS-Chezlepro-lab` portaient
|
||||
`vault_setops_console_oidc` depuis la session precedente, jamais commite, et le premier
|
||||
avait un commentaire coupe de sa cle. Remis en forme et commites.
|
||||
|
||||
## 2026-09-15 (6) — Les trois consoles repondent, chacune derriere sa propre serrure
|
||||
|
||||
console.genese.internal 401 vestibule HTTP Basic (pas d'annuaire)
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (77 OK · 0 echec · 1 saute)
|
||||
- **Verdict** : ✅ CONFORME (78 OK · 0 echec · 1 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
| # | Preuve | Affirmations | Statut | Detail |
|
||||
|---|---|---|---|---|
|
||||
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | } \| to_nice_json }}`. |
|
||||
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | [0m[0m |
|
||||
| P02 | Tests unitaires (inventaire, raser, ecritures du plan, rendu du GUI) | — | ✅ OK | OK |
|
||||
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 5 instance(s) verifiee(s) — instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
|
||||
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
|
||||
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
|
||||
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
|
||||
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (78 preuves, 68 roles, 41 groupes). |
|
||||
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (79 preuves, 68 roles, 41 groupes). |
|
||||
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
|
||||
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
|
||||
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `392da4d` (publie le 2026-09-14). |
|
||||
|
|
@ -91,6 +91,7 @@
|
|||
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 155 gabarits de role : tous se rendent. |
|
||||
| P77 | Panneaux declares : assemblables, et gradues | — | ✅ OK | 8 panneau(x) declare(s) dans 2 role(s), tous avec titre, expression, raison et une unite que la table sait traduire. |
|
||||
| P78 | Un consommateur de base suit le verrou TLS de son serveur | — | ✅ OK | 3 consommateur(s) suivent la posture de leur serveur ; 2 sans reglage TLS (serveur_icingaweb2, serveur_nextcloud). |
|
||||
| P79 | Replis silencieux : une derivation vide ne passe pas pour un succes | — | ✅ OK | 6 ecosysteme(s) (instance-ci-1646753, OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0, SITE-Chezlepro) : pattes, edges, certificats, rechargemen |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met
|
|||
|
||||
## Le trou qu'il comble
|
||||
|
||||
`scripts/prouver.py` porte 78 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
||||
`scripts/prouver.py` porte 79 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
|
||||
cohérent **avec lui-même** — que les handlers existent, que les intrants ont un
|
||||
propriétaire, que rien n'est codé en dur.
|
||||
|
|
|
|||
|
|
@ -3928,6 +3928,237 @@ def preuve_amorcage_suit_le_site() -> tuple[bool, str]:
|
|||
return True, (f"{verifies} adresse(s) d'amorcage designent bien une machine du site.")
|
||||
|
||||
|
||||
# --- LES REPLIS SILENCIEUX (P79) -----------------------------------------------------
|
||||
#
|
||||
# Chaque temoin ci-dessous prend ses donnees en PARAMETRE : on peut lui remettre la faute
|
||||
# sous les yeux sans toucher au depot, et c'est ainsi qu'ils ont ete eprouves.
|
||||
|
||||
def _yaml_ou_vide(chemin: Path) -> dict:
|
||||
"""Le contenu d'un fichier YAML, ou {} s'il n'existe pas (registre facultatif)."""
|
||||
if not chemin.is_file():
|
||||
return {}
|
||||
return yaml.safe_load(chemin.read_text(encoding="utf-8")) or {}
|
||||
|
||||
|
||||
def _repli_pattes(serveurs: dict, if_zones: dict) -> list[str]:
|
||||
"""Chaque zone OCCUPEE par une machine active du site a sa patte a la frontiere.
|
||||
|
||||
Un site qui n'a pas encore de table par zone est plat, et la patte unique est alors
|
||||
juste. Des que la table existe, une zone qui y manque retombe sur la patte plate.
|
||||
"""
|
||||
if not if_zones:
|
||||
return []
|
||||
occupees: dict[str, list[str]] = {}
|
||||
for nom, s in (serveurs or {}).items():
|
||||
if isinstance(s, dict) and str(s.get("etat", "actif")) == "actif" and s.get("reseau"):
|
||||
occupees.setdefault(str(s["reseau"]), []).append(str(nom))
|
||||
return [f"patte : la zone `{z}` ({', '.join(sorted(n))}) manque a `opnsense_if_zones` "
|
||||
f"— ses regles tomberaient sur la patte plate, justes et jamais rencontrees"
|
||||
for z, n in sorted(occupees.items()) if z not in if_zones]
|
||||
|
||||
|
||||
def _repli_edge(nom: str, applications: dict, domaines: dict, a_un_edge: bool) -> list[str]:
|
||||
"""Dans un ecosysteme qui A un edge, aucune exposition ne retombe sur son propre groupe.
|
||||
|
||||
Le temoin n'est pas la derivation : la presence d'un edge vient de l'inventaire, et les
|
||||
edges legitimes de ce que `domaines.yml` ECRIT. Le repli « le service se sert lui-meme »
|
||||
n'est juste que la ou il n'y a personne pour le servir.
|
||||
"""
|
||||
if not a_un_edge:
|
||||
return []
|
||||
from inventory_rules import expositions_des_applications
|
||||
doms = (domaines or {}).get("domaines_publics") or {}
|
||||
ecrits = {str(c["edge"]) for c in doms.values() if isinstance(c, dict) and c.get("edge")}
|
||||
return [f"edge : {nom} — `{e['fqdn']}` retombe sur son propre groupe (`{e['edge']}`) "
|
||||
f"alors que l'ecosysteme a un edge : ni vhost ni SAN ne le retiendront"
|
||||
for e in expositions_des_applications(applications, domaines)
|
||||
if e["edge"] != "serveur_nginx" and str(e["edge"]) not in ecrits]
|
||||
|
||||
|
||||
def _repli_hotes_du_site(enfants: dict, hv: dict) -> list[str]:
|
||||
"""Ce que l'inventaire DYNAMIQUE du site donne a ses machines, sans group_vars pour
|
||||
rattraper un defaut de role."""
|
||||
def membres(g: str) -> set[str]:
|
||||
return set(((enfants.get(g) or {}).get("hosts")) or {})
|
||||
|
||||
fautes: list[str] = []
|
||||
pki = membres("client_pki")
|
||||
for h in sorted(membres("serveur_nginx")):
|
||||
v = hv.get(h) or {}
|
||||
cert = str(v.get("serveur_nginx_certificat") or "")
|
||||
if not cert or "snakeoil" in cert:
|
||||
fautes.append(f"bouchon : {h} sert `{cert or 'le defaut du role, snakeoil'}` "
|
||||
f"— du TLS qui ressemble a du TLS")
|
||||
if h in pki and "nginx" not in (v.get("client_pki_reload_services") or []):
|
||||
fautes.append(f"rechargement : {h} renouvelle son certificat sans recharger "
|
||||
f"nginx — le nouveau reste sur disque, l'ancien est servi")
|
||||
if membres("serveur_artefacts"):
|
||||
for h, v in sorted(hv.items()):
|
||||
if (v or {}).get("dns_amorcage") and not (v or {}).get("artefacts_amorcage"):
|
||||
fautes.append(f"jumeaux : {h} recoit `dns_amorcage` sans "
|
||||
f"`artefacts_amorcage` — le gabarit garde son ancien proxy apt")
|
||||
return fautes
|
||||
|
||||
|
||||
def _repli_devis_internet(flux: dict, roles_presents: set[str], regles: list[dict]) -> list[str]:
|
||||
"""Une sortie qui NOMME un role absent du site ne devient pas une sortie vers l'Internet.
|
||||
|
||||
Le temoin lit les `meta/flux.yml` : les ports qu'un role n'emploie QUE vers des roles
|
||||
absents ne doivent porter aucune regle `!SETOPS_INTERNES`. Un port qu'il emploie aussi
|
||||
vers un pair lointain (`externe`, un depot) y a sa place, et il est ecarte.
|
||||
"""
|
||||
connus = {d.name for d in (RACINE / "roles").iterdir() if d.is_dir()}
|
||||
fautes: set[str] = set()
|
||||
for role in sorted(roles_presents):
|
||||
absents, lointains = set(), set()
|
||||
for fl in flux.get(role, []):
|
||||
if fl.get("sens") != "egress":
|
||||
continue
|
||||
pairs = fl.get("pair") if isinstance(fl.get("pair"), list) else [fl.get("pair")]
|
||||
ports = fl["port"] if isinstance(fl.get("port"), list) else [fl.get("port")]
|
||||
nommes = {str(p) for p in pairs} & connus
|
||||
if nommes and not nommes & roles_presents:
|
||||
absents.update(str(p) for p in ports)
|
||||
elif not nommes:
|
||||
lointains.update(str(p) for p in ports)
|
||||
suspects = absents - lointains
|
||||
for r in regles:
|
||||
if (str(r.get("tenant")) == "SITE" and r.get("role") == role
|
||||
and str(r.get("destination")) == "!SETOPS_INTERNES"):
|
||||
for port in sorted(suspects & {str(p) for p in (r.get("ports") or [])}):
|
||||
fautes.add(f"internet : {role} sort vers l'Internet sur {port}, un port "
|
||||
f"qu'il n'emploie que vers un role absent du site")
|
||||
return sorted(fautes)
|
||||
|
||||
|
||||
def _flux_genere_perime(base: Path) -> list[str]:
|
||||
"""Les regles d'hote d'un ecosysteme, rejouees pour LUI — pas pour l'instance montee.
|
||||
|
||||
`make flux` n'ecrit que pour l'instance montee. On rejoue la generation dans un dossier
|
||||
temporaire qui reprend l'instance par liens, et l'on compare : rien n'est ecrit dans le
|
||||
depot.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
reel = base / "flux-genere"
|
||||
with tempfile.TemporaryDirectory(prefix="p79-") as tmp:
|
||||
for e in base.iterdir():
|
||||
if e.name not in ("flux-genere", ".git"):
|
||||
os.symlink(e, Path(tmp) / e.name)
|
||||
r = subprocess.run([sys.executable, str(RACINE / "scripts" / "resoudre_flux.py"), "nftables"],
|
||||
env=dict(os.environ, SETOPS_INSTANCE=tmp), capture_output=True,
|
||||
text=True, cwd=RACINE, timeout=300)
|
||||
if r.returncode != 0:
|
||||
derniere = (r.stderr.strip().splitlines() or ["sans message"])[-1]
|
||||
return [f"flux : {base.name} — la generation echoue ({derniere[:100]})"]
|
||||
gen = Path(tmp) / "flux-genere"
|
||||
a = {p.name for p in reel.glob("*.nft")} if reel.is_dir() else set()
|
||||
b = {p.name for p in gen.glob("*.nft")} if gen.is_dir() else set()
|
||||
differents = sorted(n for n in a & b if (reel / n).read_bytes() != (gen / n).read_bytes())
|
||||
fautes = []
|
||||
if b - a:
|
||||
fautes.append(f"flux : {base.name} — aucune regle generee pour {', '.join(sorted(b - a))}")
|
||||
if a - b:
|
||||
fautes.append(f"flux : {base.name} — regles d'une machine retiree du plan : "
|
||||
f"{', '.join(sorted(a - b))}")
|
||||
if differents:
|
||||
fautes.append(f"flux : {base.name} — perimees par rapport au plan : {', '.join(differents)}")
|
||||
return fautes
|
||||
|
||||
|
||||
def preuve_une_derivation_vide_se_dit() -> tuple[bool, str]:
|
||||
"""Une derivation qui ne trouve rien ne passe pas pour une derivation qui n'a rien a trouver.
|
||||
|
||||
POURQUOI (2026-09-14 et 15). En deux jours, pour monter l'edge du site puis les trois
|
||||
consoles, les memes defauts sont revenus sous des noms differents. Chaque fois, un
|
||||
repli a rendu un SUCCES au lieu d'un refus : deploiement vert, devis muet, harnais vert.
|
||||
|
||||
1. `site_inventaire` derivait `dns_amorcage` sans son jumeau `artefacts_amorcage` : les
|
||||
machines nees avant le renumerotage gardaient le proxy apt du gabarit.
|
||||
2. Une zone manquait a `opnsense_if_zones` : `_if_de()` retombait sur la patte plate,
|
||||
20 regles posees et jamais rencontrees.
|
||||
3. L'edge d'une exposition retombait sur le groupe de l'application : vhost de 43
|
||||
octets, certificat sans les noms publies.
|
||||
4. L'edge du site servait `ssl-cert-snakeoil.pem`, le defaut du role, faute de
|
||||
group_vars dans un inventaire dynamique.
|
||||
5. Un certificat renouvele sans `client_pki_reload_services` : servi perime.
|
||||
6. Une sortie vers un role absent du site devenait une sortie vers l'Internet.
|
||||
7. `make flux`, lance avec un locataire monte, n'a rien regenere pour l'autre : 502
|
||||
derriere un TLS parfait.
|
||||
|
||||
Chaque temoin lit sa source DIRECTEMENT (inventaire, plan, registre, table ecrite) et
|
||||
ne passe pas par la derivation qu'il juge : une garde qui reproduit le raisonnement
|
||||
qu'elle verifie ne verifie rien (cf. P43, P67).
|
||||
|
||||
CE QU'ELLE NE COUVRE PAS. La collision des noms publics par groupe (P67 la garde).
|
||||
`client_pki` qui rend `changed=0` sans comparer ses SAN a ceux qu'il derive : cela se
|
||||
mesure sur la machine (`make certificats-plan`), pas dans le depot. Et un registre
|
||||
facultatif exige par `include_vars` : celui-la echouait bruyamment, ce n'etait pas un
|
||||
repli.
|
||||
"""
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
import instances as mod_instances
|
||||
|
||||
fautes: list[str] = []
|
||||
couverts: list[str] = []
|
||||
|
||||
for i in mod_instances.decouvrir():
|
||||
if i.get("site"):
|
||||
continue
|
||||
base = RACINE.parent / i["nom"]
|
||||
inv = next((base / "inventories" / x / "hosts.yml" for x in ("principal", "production")
|
||||
if (base / "inventories" / x / "hosts.yml").is_file()), None)
|
||||
if inv is None:
|
||||
continue
|
||||
enfants = ((_yaml_ou_vide(inv).get("all") or {}).get("children")) or {}
|
||||
a_un_edge = bool(((enfants.get("serveur_nginx") or {}).get("hosts")) or {})
|
||||
fautes += _repli_edge(i["nom"], _yaml_ou_vide(base / "plan" / "applications.yml"),
|
||||
_yaml_ou_vide(base / "plan" / "domaines.yml"), a_un_edge)
|
||||
gv_nginx = inv.parent / "group_vars" / "serveur_nginx.yml"
|
||||
if a_un_edge and "nginx" not in (_yaml_ou_vide(gv_nginx).get("client_pki_reload_services") or []):
|
||||
fautes.append(f"rechargement : {i['nom']} — l'edge renouvelle son certificat sans "
|
||||
f"recharger nginx")
|
||||
if (base / "flux-genere").is_dir():
|
||||
fautes += _flux_genere_perime(base)
|
||||
couverts.append(i["nom"])
|
||||
|
||||
lien = RACINE / "underlay.yml"
|
||||
if lien.exists():
|
||||
site = lien.resolve().parent
|
||||
inventaire = _inventaire_du_site()
|
||||
if inventaire is None:
|
||||
fautes.append(f"site : l'inventaire dynamique de {site.name} ne s'execute pas — "
|
||||
f"rien de ce qu'il derive ne peut etre juge")
|
||||
else:
|
||||
enfants, hv = inventaire["enfants"], inventaire["hv"]
|
||||
fautes += _repli_edge(site.name, _yaml_ou_vide(site / "plan" / "applications.yml"),
|
||||
_yaml_ou_vide(site / "plan" / "domaines.yml"),
|
||||
bool(((enfants.get("serveur_nginx") or {}).get("hosts")) or {}))
|
||||
fautes += _repli_hotes_du_site(enfants, hv)
|
||||
fautes += _repli_pattes(_yaml_ou_vide(site / "plan" / "serveurs.yml").get("serveurs") or {},
|
||||
_yaml_ou_vide(site / "opnsense.yml").get("opnsense_if_zones") or {})
|
||||
import contextlib
|
||||
import io
|
||||
import devis_opnsense as D
|
||||
import resoudre_flux as RF
|
||||
with contextlib.redirect_stderr(io.StringIO()):
|
||||
regles = D.construire(D.decouvrir_du_site()).get("regles") or []
|
||||
presents = {g for g, v in enfants.items() if (v.get("hosts") or {})}
|
||||
fautes += _repli_devis_internet(RF.charger_flux(), presents, regles)
|
||||
couverts.append(site.name)
|
||||
|
||||
if fautes:
|
||||
return False, (f"{len(fautes)} repli(s) silencieux — une derivation vide rend un "
|
||||
f"succes :\n - " + "\n - ".join(fautes[:10])
|
||||
+ ("\n - …" if len(fautes) > 10 else ""))
|
||||
if not couverts:
|
||||
return True, "Aucun ecosysteme decouvert : aucune derivation a juger."
|
||||
return True, (f"{len(couverts)} ecosysteme(s) ({', '.join(couverts)}) : pattes, edges, "
|
||||
f"certificats, rechargements, jumeaux d'amorcage, sorties du devis et regles "
|
||||
f"d'hote par instance — aucune derivation vide ne passe pour un succes.")
|
||||
|
||||
|
||||
PREUVES: list[dict] = [
|
||||
{"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"],
|
||||
"cmds": [["ansible-lint", "-q"]]},
|
||||
|
|
@ -4104,6 +4335,8 @@ PREUVES: list[dict] = [
|
|||
"refs": [], "func": preuve_panneaux_assembles},
|
||||
{"id": "P78", "titre": "Un consommateur de base suit le verrou TLS de son serveur",
|
||||
"refs": [], "func": preuve_consommateurs_suivent_le_verrou_tls},
|
||||
{"id": "P79", "titre": "Replis silencieux : une derivation vide ne passe pas pour un succes",
|
||||
"refs": [], "func": preuve_une_derivation_vide_se_dit},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue