Metriques et journaux (CHANGELOG 142). Le site pose les quatre interrupteurs TLS que chaque locataire pose dans ses group_vars : node_exporter et Loki servaient en HTTP sur les neuf VM, le certificat etait la, inutilise. test_parite_tls_site.py compare desormais le site aux locataires. Les hyperviseurs poussent leurs journaux en HTTPS verifie, avec la racine du site comme simple fichier. L'exportateur PostgreSQL sert en HTTPS partout, sur l'interrupteur de PostgreSQL lui-meme, que Prometheus lit pour choisir son schema. La frontiere (CHANGELOG 143). Une autorite subordonnee signee par la racine du site, bornee (pathlen 0, noms sous le domaine et adresses du site, contrainte critique), cle nee sur la boite (scripts/frontiere_pki.py). Syslog vers Loki en TLS mutuel (seule la frontiere peut ecrire), exportateur en HTTPS, interface web sur le certificat de services ; les clients d'API verifient la frontiere contre la racine du site. La lecture du magasin passe par config.xml : l'API de recherche renvoie les cles privees. Deploye et mesure au site, chez les deux locataires et sur la frontiere ; 95 preuves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
4 KiB
Python
83 lines
4 KiB
Python
#!/usr/bin/env python3
|
|
"""L'exportateur PostgreSQL sert en TLS, et Prometheus le lit en HTTPS verifie.
|
|
|
|
POURQUOI (2026-10-12). Le 9187 etait le seul port de metriques en clair, chez les
|
|
locataires comme au site — dette inscrite au registre le 2026-09-14. Un seul interrupteur
|
|
decide des deux bouts : `serveur_postgresql_tls_actif`, que la declaration de l'exportateur
|
|
nomme (`tls_si`) et que Prometheus lit dans les hostvars de chaque cible.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import jinja2
|
|
import yaml
|
|
|
|
RACINE = Path(__file__).resolve().parents[2]
|
|
PG = RACINE / "roles/serveur_postgresql"
|
|
|
|
|
|
def rendre_prometheus(tls_par_hote: dict[str, bool]) -> dict:
|
|
meta = PG / "meta/metriques.yml"
|
|
# LES FILTRES D'ANSIBLE LUI-MEME (intersect, extract, regex_replace, b64decode...) :
|
|
# un rendu avec des imitations prouverait les imitations.
|
|
from ansible.plugins.filter.core import FilterModule as Coeur
|
|
from ansible.plugins.filter.mathstuff import FilterModule as Maths
|
|
env = jinja2.Environment(extensions=["jinja2.ext.do"])
|
|
env.filters.update(Coeur().filters())
|
|
env.filters.update(Maths().filters())
|
|
hotes = sorted(tls_par_hote)
|
|
hostvars = {h: {"ansible_host": f"10.0.0.{i + 1}", "serveur_postgresql_tls_actif": v}
|
|
for i, (h, v) in enumerate(sorted(tls_par_hote.items()))}
|
|
rendu = env.from_string((RACINE / "roles/serveur_prometheus/templates/prometheus.yml.j2")
|
|
.read_text(encoding="utf-8")).render(
|
|
serveur_prometheus_intervalle="15s", serveur_prometheus_metriques_tls=True,
|
|
serveur_prometheus_metriques_ca="/etc/step/certs/root_ca.crt",
|
|
serveur_prometheus_groupe_metriques="client_metrique", serveur_prometheus_port_node=9100,
|
|
serveur_prometheus_cibles_supplementaires=[],
|
|
groups={"client_metrique": hotes, "hotes_actifs": hotes, "serveur_postgresql": hotes},
|
|
hostvars=hostvars,
|
|
serveur_prometheus_declarations={"results": [{
|
|
"item": {"path": str(meta)},
|
|
"content": base64.b64encode(meta.read_bytes()).decode()}]},
|
|
)
|
|
return {j["job_name"]: j for j in yaml.safe_load(rendu)["scrape_configs"]}
|
|
|
|
|
|
def main() -> int:
|
|
echecs: list[str] = []
|
|
|
|
expo = (yaml.safe_load((PG / "meta/metriques.yml").read_text(encoding="utf-8")) or {}).get("exportateur") or {}
|
|
if expo.get("tls_si") != "serveur_postgresql_tls_actif":
|
|
echecs.append(f"meta/metriques.yml : tls_si = {expo.get('tls_si')!r}, attendu serveur_postgresql_tls_actif")
|
|
|
|
flux = yaml.safe_load((PG / "meta/flux.yml").read_text(encoding="utf-8"))["flux"]
|
|
f9187 = next((f for f in flux if str(f.get("port")) == "9187"), {})
|
|
if f9187.get("chiffrement") != "tls":
|
|
echecs.append(f"le registre dit 9187 `{f9187.get('chiffrement')}`, attendu `tls`")
|
|
|
|
taches = (PG / "tasks/main.yml").read_text(encoding="utf-8")
|
|
if "--web.config.file=" not in taches or "serveur_postgresql_exportateur_web_config" not in taches:
|
|
echecs.append("l'exportateur ne recoit pas `--web.config.file`")
|
|
if "setops-pg-exportateur-cert-sync" not in taches:
|
|
echecs.append("l'exportateur n'est pas abonne au renouvellement du certificat")
|
|
|
|
jobs = rendre_prometheus({"data-sql-01": True, "data-sql-02": True})
|
|
pg = jobs.get("postgresql", {})
|
|
if pg.get("scheme") != "https" or (pg.get("tls_config") or {}).get("ca_file") != "/etc/step/certs/root_ca.crt":
|
|
echecs.append(f"toutes cibles TLS : job postgresql = {pg}, attendu https + ca_file")
|
|
jobs = rendre_prometheus({"data-sql-01": True, "data-sql-02": False})
|
|
if jobs.get("postgresql", {}).get("scheme") == "https":
|
|
echecs.append("une cible sans TLS : le job ne doit pas passer en https (il la perdrait)")
|
|
|
|
for e in echecs:
|
|
print(f"ECHEC : {e}")
|
|
if not echecs:
|
|
print("OK : l'exportateur PostgreSQL sert en TLS ; Prometheus le lit en HTTPS verifie sur le meme interrupteur.")
|
|
return 1 if echecs else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|