108 lines
4.4 KiB
Python
108 lines
4.4 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Ce qu'un locataire chiffre par ses group_vars, le site le chiffre aussi.
|
||
|
|
|
||
|
|
POURQUOI (2026-10-12). Trois fois le meme ecart : un interrupteur TLS que chaque locataire
|
||
|
|
pose dans ses `group_vars`, et que l'inventaire du site — dynamique, sans group_vars — ne
|
||
|
|
posait nulle part. PostgreSQL servait le certificat auto-signe de Debian (2026-09-12),
|
||
|
|
l'edge aussi (meme jour), et node_exporter et Loki servaient en HTTP (2026-10-12) alors
|
||
|
|
que le registre des flux les dit `tls`. Chaque fois, le certificat etait la, inutilise.
|
||
|
|
|
||
|
|
Le test lit les `group_vars/<groupe>.yml` des locataires montes a cote du depot, retient
|
||
|
|
chaque cle qui contient `tls` et vaut vrai, et exige la meme valeur dans les hostvars des
|
||
|
|
machines du site qui portent ce groupe.
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import yaml
|
||
|
|
|
||
|
|
RACINE = Path(__file__).resolve().parents[2]
|
||
|
|
sys.path.insert(0, str(RACINE / "scripts"))
|
||
|
|
|
||
|
|
import site_inventaire as si # noqa: E402
|
||
|
|
|
||
|
|
# CE QUI RESTE A FAIRE, NOMME — pas une tolerance muette. Chaque entree dit pourquoi le
|
||
|
|
# site differe encore, et disparait le jour ou l'ecart se ferme.
|
||
|
|
RESTE = {
|
||
|
|
("client_metrique", "client_metrique_tls_actif", "hyperviseurs"):
|
||
|
|
"node_exporter des hyperviseurs : il leur faudrait un certificat a eux "
|
||
|
|
"(pas de client_pki sur la fabric) — collecte encore en HTTP, chantier suivant.",
|
||
|
|
}
|
||
|
|
|
||
|
|
# CE QUE LE SITE OBTIENT AUTREMENT, avec la CONDITION qui le prouve dans ses hostvars.
|
||
|
|
# Pas une dispense : si la condition tombe, l'ecart redevient un echec.
|
||
|
|
AUTREMENT = {
|
||
|
|
("serveur_icinga", "serveur_icinga_db_tls"): (
|
||
|
|
lambda v: v.get("serveur_postgresql_tls_force") is True,
|
||
|
|
"derive par le role de `serveur_postgresql_tls_force` (resoudre_base), vrai au site"),
|
||
|
|
("serveur_postfix", "serveur_postfix_lmtp_tls_verify"): (
|
||
|
|
lambda v: v.get("serveur_postfix_mode") == "relais",
|
||
|
|
"sans objet : le relais du site ne remet rien par LMTP (`serveur_postfix_mode: relais`)"),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def interrupteurs_des_locataires() -> dict[tuple[str, str], list[str]]:
|
||
|
|
"""{(groupe, cle): [locataires]} pour chaque cle *tls* vraie d'un group_vars."""
|
||
|
|
vus: dict[tuple[str, str], list[str]] = {}
|
||
|
|
for gv in sorted(RACINE.parent.glob("OPS-*/inventories/principal/group_vars/*.yml")):
|
||
|
|
locataire = gv.parents[3].name
|
||
|
|
if locataire.endswith("-lab"):
|
||
|
|
continue
|
||
|
|
try:
|
||
|
|
donnees = yaml.safe_load(gv.read_text(encoding="utf-8")) or {}
|
||
|
|
except yaml.YAMLError:
|
||
|
|
continue
|
||
|
|
if not isinstance(donnees, dict):
|
||
|
|
continue
|
||
|
|
for cle, val in donnees.items():
|
||
|
|
if "tls" in str(cle) and val is True:
|
||
|
|
vus.setdefault((gv.stem, str(cle)), []).append(locataire)
|
||
|
|
return vus
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
attendus = interrupteurs_des_locataires()
|
||
|
|
if not attendus:
|
||
|
|
print("OK : aucun locataire monte a cote du depot — rien a comparer.")
|
||
|
|
return 0
|
||
|
|
try:
|
||
|
|
inv = si.inventaire()
|
||
|
|
except SystemExit as e: # plan du site refuse ou absent
|
||
|
|
print(f"OK : aucun site monte ({e}) — rien a comparer.")
|
||
|
|
return 0
|
||
|
|
except Exception as e: # noqa: BLE001
|
||
|
|
print(f"OK : aucun site monte ({type(e).__name__}) — rien a comparer.")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
hv = inv["_meta"]["hostvars"]
|
||
|
|
hyperviseurs = set((inv.get("hyperviseurs") or {}).get("hosts") or [])
|
||
|
|
echecs, restes = [], []
|
||
|
|
for (groupe, cle), qui in sorted(attendus.items()):
|
||
|
|
for hote in (inv.get(groupe) or {}).get("hosts") or []:
|
||
|
|
if hv.get(hote, {}).get(cle) is True:
|
||
|
|
continue
|
||
|
|
autre = AUTREMENT.get((groupe, cle))
|
||
|
|
if autre and autre[0](hv.get(hote, {})):
|
||
|
|
continue
|
||
|
|
classe = "hyperviseurs" if hote in hyperviseurs else "vm"
|
||
|
|
raison = RESTE.get((groupe, cle, classe))
|
||
|
|
if raison:
|
||
|
|
restes.append(f"{hote} : {cle} — {raison}")
|
||
|
|
else:
|
||
|
|
echecs.append(f"{hote} ({groupe}) : `{cle}` vaut {hv.get(hote, {}).get(cle)!r} au site, "
|
||
|
|
f"true chez {', '.join(qui)}")
|
||
|
|
|
||
|
|
for r in sorted(set(restes)):
|
||
|
|
print(f"RESTE : {r}")
|
||
|
|
for e in echecs:
|
||
|
|
print(f"ECHEC : {e}")
|
||
|
|
if not echecs:
|
||
|
|
print(f"OK : les {len(attendus)} interrupteurs TLS des locataires valent aussi au site.")
|
||
|
|
return 1 if echecs else 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|