sonde vigie : elle eprouve ce que la vigie lit, pas seulement qu elle repond

Apres check_http, la sonde lit la configuration deployee d Icinga Web 2 et eprouve
chaque ressource utilisee avec ses identifiants : base du moteur (hotes), annuaire ou
base des comptes, Redis. Au vert sur les trois ; quatre mises en defaut critiques.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-09-28 23:55:58 -04:00
parent 8ac7909d49
commit 1653319ef1
5 changed files with 136 additions and 21 deletions

View file

@ -1,5 +1,31 @@
# CHANGELOG — Set-OPS # CHANGELOG — Set-OPS
## 2026-09-28 (28) — `vigie` vérifie ce que la vigie lit, pas seulement qu'elle répond
Quatrième des cinq sondes « répond » de la revue (25).
**Avant.** `check_http` sur la boucle locale : un 302 vers le formulaire de connexion
suffisait. Une vigie dont la base est injoignable (mot de passe tourné d'un seul côté,
`pg_hba`, certificat), dont l'annuaire refuse la liaison, ou dont le Redis est tombé, sert
très bien ce formulaire — puis une erreur, des tableaux vides, ou un compte connecté sans
aucun droit parce que ses groupes ne se lisent plus.
**Maintenant.** Après `check_http`, la sonde lit la configuration DÉPLOYÉE de la vigie
(`resources.ini`, `authentication.ini`, `groups.ini`, `modules/icingadb/`) et éprouve chaque
ressource qu'elle utilise, avec SES identifiants et le même pilote PHP : la base du moteur
(compte des hôtes, zéro = critique), l'annuaire (liaison + lecture de la racine) ou la base
des comptes, et Redis (`PING`). Un tenant (annuaire) et le site (base locale) passent par
le même code. Les secrets restent dans les fichiers (0640), rien n'est affiché.
**Éprouvé.** Au vert sur les trois : Chezlepro et Technolibre (13 hôtes, soit ceux du plan ;
annuaire ; Redis), site (23 hôtes, base des comptes et groupes, Redis) ; 51 ms. Mises en
défaut sur Technolibre, sur une copie de la configuration : table absente
(`serveur_icingaweb2_sonde_table`), Redis sur un port fermé, liaison à l'annuaire refusée,
mot de passe de la base faux — les quatre critiques, chacune nommant sa cause. Au site,
codes HTTP attendus changés : critique, « ne sert plus ses pages ».
Reste de la revue : `passerelle` (oauth2-proxy atteint-il Keycloak ?).
## 2026-09-28 (27) — La sonde d'Icinga Web 2 s'appelle `vigie`, plus `console` ## 2026-09-28 (27) — La sonde d'Icinga Web 2 s'appelle `vigie`, plus `console`
Le vocabulaire des plans dit **vigie** pour Icinga Web 2 (`vigie.<domaine>`) et **console** Le vocabulaire des plans dit **vigie** pour Icinga Web 2 (`vigie.<domaine>`) et **console**

View file

@ -113,6 +113,11 @@ serveur_icingaweb2_bpm_processes: {}
# bloque — et l'absence de reponse, qui est le nginx local tombe. # bloque — et l'absence de reponse, qui est le nginx local tombe.
serveur_icingaweb2_sonde_codes: "HTTP/1.1 200,HTTP/1.1 302,HTTP/1.1 401,HTTP/1.1 403" serveur_icingaweb2_sonde_codes: "HTTP/1.1 200,HTTP/1.1 302,HTTP/1.1 401,HTTP/1.1 403"
# CE QUE LA VIGIE LIT, EPROUVE AVEC SES PROPRES IDENTIFIANTS (2026-09-28). La table que la
# sonde interroge dans la base du moteur, au travers de la ressource que la vigie utilise.
# Mise en defaut PAR PARAMETRE : une table qui n'existe pas.
serveur_icingaweb2_sonde_table: "host"
# --- MODE `db` : le compte d'amorcage de la console ------------------------------------ # --- MODE `db` : le compte d'amorcage de la console ------------------------------------
# #
# UN SEUL COMPTE EST POSE PAR LE MOTEUR, et c'est voulu : celui qui permet d'entrer la # UN SEUL COMPTE EST POSE PAR LE MOTEUR, et c'est voulu : celui qui permet d'entrer la

View file

@ -28,7 +28,8 @@
sondes: sondes:
- nom: vigie - nom: vigie
ttl: 5400 ttl: 5400
raison: 'La vigie (Icinga Web 2) repond-elle encore ? Si elle meurt, le moteur raison: 'La vigie (Icinga Web 2) repond-elle, et lit-elle ce qu''elle doit montrer
continue de collecter et tous les verdicts restent verts : le systeme est en (base du moteur, annuaire ou base des comptes, Redis) ? Si elle meurt, ou si elle
parfaite sante et l''exploitant est aveugle. C''est une panne sans symptome, qu''on sert des pages vides, le moteur continue de collecter et tous les verdicts restent
decouvre en cherchant a diagnostiquer autre chose.' verts : le systeme est en parfaite sante et l''exploitant est aveugle. C''est une
panne sans symptome, qu''on decouvre en cherchant a diagnostiquer autre chose.'

View file

@ -1,29 +1,112 @@
#!/bin/bash #!/bin/bash
# GENERE par Set-OPS (role serveur_icingaweb2). Ne pas editer a la main. # GENERE par Set-OPS (role serveur_icingaweb2). Ne pas editer a la main.
# #
# SONDE « vigie » — la vigie (Icinga Web 2) repond-elle encore ? # SONDE « vigie » — la vigie (Icinga Web 2) repond-elle, et PEUT-ELLE MONTRER quelque chose ?
# #
# « vigie », PAS « console » (2026-09-28). Elle s'appelait `console`, alors que le # « vigie », PAS « console » (2026-09-28). Elle s'appelait `console`, alors que le
# vocabulaire des plans reserve « console » a la console d'exploitation Set-OPS # vocabulaire des plans reserve « console » a la console d'exploitation Set-OPS
# (`serveur_ops`, sonde `console-ops`) et nomme ce service-ci `vigie`. Dans Icinga, # (`serveur_ops`, sonde `console-ops`) et nomme ce service-ci `vigie`. Dans Icinga,
# `mon-01!console` parlait donc de la vigie : le nom dit ce qu'on vient y faire. # `mon-01!console` parlait donc de la vigie : le nom dit ce qu'on vient y faire.
# #
# UNE ENVELOPPE, PAS UNE SONDE ECRITE A LA MAIN. `check_http` vient de # Mise en defaut PAR PARAMETRE : `serveur_icingaweb2_sonde_codes` pour la premiere etape,
# `monitoring-plugins-basic` : il parle deja l'API des greffons Nagios, gere les delais et # `serveur_icingaweb2_sonde_table` (table inexistante) pour la seconde.
# les redirections. On lui passe l'adresse et on rend SON code de sortie tel quel. set -uo pipefail
#
# SUR LA BOUCLE LOCALE, ET SANS S'AUTHENTIFIER. En mode `external`, l'ecoute est restreinte # PREMIERE ETAPE — LA PILE WEB SERT. `check_http` vient de `monitoring-plugins-basic` : il
# a 127.0.0.1 et une passerelle SSO se tient devant : la sonde frappe donc la ou # parle deja l'API des greffons Nagios. Sur la boucle locale et sans s'authentifier : en
# l'application ecoute vraiment, et se contente de la reponse que rend une requete non # mode `external`, l'ecoute est restreinte a 127.0.0.1 et une passerelle SSO se tient
# authentifiee. On mesure « la pile PHP sert encore », pas « l'annuaire accepte encore ». # devant ; la sonde frappe la ou l'application ecoute vraiment.
# #
# `-e` PLUTOT QU'UN 200 IMPLICITE : Icinga Web 2 renvoie une REDIRECTION vers son # `-e` PLUTOT QU'UN 200 IMPLICITE : Icinga Web 2 renvoie une REDIRECTION vers son
# formulaire de connexion quand personne n'est authentifie. Exiger un 200 ferait crier la # formulaire de connexion quand personne n'est authentifie. Exiger un 200 ferait crier la
# sonde sur le comportement NORMAL d'une vigie saine — la pire sorte de fausse alerte, # sonde sur le comportement NORMAL d'une vigie saine.
# celle qu'on finit par ignorer. http=$(/usr/lib/nagios/plugins/check_http -H 127.0.0.1 -p {{ serveur_icingaweb2_http_port }} -u '/' \
-e '{{ serveur_icingaweb2_sonde_codes }}' -t 10 2>&1)
rc=$?
if (( rc != 0 )); then
echo "La vigie ne sert plus ses pages : ${http:0:160}"
exit "${rc}"
fi
# SECONDE ETAPE — CE QU'ELLE LIT (2026-09-28). « La pile PHP repond » n'etait pas la
# question : une vigie dont la base est injoignable (mot de passe tourne d'un seul cote,
# `pg_hba`, certificat), dont l'annuaire refuse sa liaison, ou dont le Redis est tombe,
# sert tres bien son formulaire de connexion — puis une page d'erreur, ou des tableaux
# vides, ou un compte connecte SANS AUCUN DROIT parce que ses groupes ne se lisent plus.
# Meme lecon que `tableaux` chez Grafana.
# #
# Mise en defaut PAR PARAMETRE : `serveur_icingaweb2_sonde_codes`. # ON LIT LA CONFIGURATION DEPLOYEE, PAS CELLE QU'ON CROIT AVOIR POSEE : chaque ressource
exec /usr/lib/nagios/plugins/check_http \ # que la vigie UTILISE (module icingadb, authentification, groupes) est eprouvee avec SES
-H 127.0.0.1 -p {{ serveur_icingaweb2_http_port }} -u '/' \ # identifiants, par le MEME pilote PHP (PDO pgsql, ext-ldap). Un tenant (annuaire) et le
-e '{{ serveur_icingaweb2_sonde_codes }}' \ # site (base locale) passent par le meme code sans qu'on ait a le lui dire.
-t 10 # Les secrets restent dans les fichiers (0640 root:icingaweb2) ; rien n'est affiche.
TABLE='{{ serveur_icingaweb2_sonde_table }}' php -d display_errors=stderr -- '{{ serveur_icingaweb2_config_dir }}' <<'PHP'
<?php
$dir = $argv[1];
$lire = function ($f) use ($dir) { return @parse_ini_file("$dir/$f", true, INI_SCANNER_RAW) ?: []; };
$res = $lire('resources.ini');
$utilisees = []; // ressource => a quoi elle sert (moteur, comptes, groupes)
foreach (['modules/icingadb/config.ini' => 'moteur', 'authentication.ini' => 'comptes', 'groups.ini' => 'groupes'] as $f => $usage) {
foreach ($lire($f) as $s) { if (!empty($s['resource'])) $utilisees[$s['resource']][$usage] = $usage; }
}
if (!array_filter($utilisees, fn($u) => isset($u['moteur']))) {
echo "La vigie sert ses pages, mais aucune base du moteur n'est configuree (modules/icingadb/config.ini).\n"; exit(2);
}
$ko = []; $vu = []; $perf = '';
foreach ($utilisees as $nom => $usages) {
$role = implode(' et ', $usages);
$r = $res[$nom] ?? null;
if ($r === null) { $ko[] = "$nom ($role) : ressource absente de resources.ini"; continue; }
if (($r['type'] ?? '') === 'db') {
$dsn = sprintf('pgsql:host=%s;port=%s;dbname=%s', $r['host'], $r['port'] ?? 5432, $r['dbname']);
if (!empty($r['ssl_mode'])) $dsn .= ';sslmode=' . $r['ssl_mode'];
if (!empty($r['ssl_ca'])) $dsn .= ';sslrootcert=' . $r['ssl_ca'];
try {
$db = new PDO($dsn, $r['username'], $r['password'] ?? '', [PDO::ATTR_TIMEOUT => 5, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
if (isset($usages['moteur'])) {
$n = (int) $db->query('SELECT count(*) FROM ' . preg_replace('/[^a-z_]/', '', getenv('TABLE')))->fetchColumn();
if ($n === 0) { $ko[] = "$nom : la base du moteur ne contient AUCUN hote"; continue; }
$vu[] = "base du moteur ($n hotes)"; $perf = " | hotes=$n";
} else {
$db->query('SELECT 1')->fetchColumn();
$vu[] = "base des $role";
}
} catch (Throwable $e) {
$ko[] = "$nom ($role, {$r['host']}) : " . substr(preg_replace('/\s+/', ' ', $e->getMessage()), 0, 110);
}
} elseif (($r['type'] ?? '') === 'ldap') {
$enc = $r['encryption'] ?? '';
$url = ($enc === 'ldaps' ? 'ldaps://' : 'ldap://') . $r['hostname'] . ':' . ($r['port'] ?? 389);
$l = @ldap_connect($url);
ldap_set_option($l, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($l, LDAP_OPT_NETWORK_TIMEOUT, (int) ($r['timeout'] ?? 5));
if ($enc === 'starttls' && !@ldap_start_tls($l)) { $ko[] = "$nom ($role) : STARTTLS refuse — " . ldap_error($l); continue; }
if (!@ldap_bind($l, $r['bind_dn'] ?? null, $r['bind_pw'] ?? null)) {
$ko[] = "$nom ($role, {$r['hostname']}) : liaison refusee — " . ldap_error($l); continue;
}
if (!@ldap_read($l, $r['root_dn'], '(objectClass=*)', ['dn'])) {
$ko[] = "$nom ($role) : liee, mais {$r['root_dn']} illisible — " . ldap_error($l); continue;
}
$vu[] = "annuaire ($role)";
}
}
// L'ETAT EN DIRECT : le module icingadb lit les etats volatils dans Redis. Sans lui, la
// vigie affiche une banniere d'erreur et des resultats de controle perimes.
foreach ($lire('modules/icingadb/redis.ini') as $sec => $r) {
if (empty($r['host'])) continue;
$s = @fsockopen($r['host'], (int) ($r['port'] ?? 6380), $errno, $errstr, 3);
if (!$s) { $ko[] = "Redis $sec ({$r['host']}:{$r['port']}) : $errstr"; continue; }
stream_set_timeout($s, 3);
fwrite($s, "PING\r\n");
$rep = trim((string) fgets($s));
fclose($s);
if ($rep !== '+PONG') { $ko[] = "Redis $sec : rend « " . substr($rep, 0, 40) . " » au lieu de PONG"; continue; }
$vu[] = 'Redis';
}
if ($ko) {
echo "La vigie sert ses pages, mais NE PEUT PLUS MONTRER ce qu'elle doit : " . implode(' ; ', $ko) . $perf . "\n";
exit(2);
}
echo "Vigie : pages servies ; lit " . implode(', ', $vu) . '.' . $perf . "\n";
exit(0);
PHP

View file

@ -23,7 +23,7 @@ graph LR
| sonde | TTL | ce qu'elle voit | | sonde | TTL | ce qu'elle voit |
|---|---|---| |---|---|---|
| `vigie` | 5400 s | La vigie (Icinga Web 2) repond-elle encore ? Si elle meurt, le moteur continue de collecter et tous les verdicts restent verts : le systeme est en parfaite sante et l'exploitant est aveugle. C'est une panne sans symptome, qu'on decouvre en cherchant a diagnostiquer autre chose. | | `vigie` | 5400 s | La vigie (Icinga Web 2) repond-elle, et lit-elle ce qu'elle doit montrer (base du moteur, annuaire ou base des comptes, Redis) ? Si elle meurt, ou si elle sert des pages vides, le moteur continue de collecter et tous les verdicts restent verts : le systeme est en parfaite sante et l'exploitant est aveugle. C'est une panne sans symptome, qu'on decouvre en cherchant a diagnostiquer autre chose. |
## Ce qu'il expose en séries ## Ce qu'il expose en séries