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:
parent
8ac7909d49
commit
1653319ef1
5 changed files with 136 additions and 21 deletions
26
CHANGELOG.md
26
CHANGELOG.md
|
|
@ -1,5 +1,31 @@
|
|||
# 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`
|
||||
|
||||
Le vocabulaire des plans dit **vigie** pour Icinga Web 2 (`vigie.<domaine>`) et **console**
|
||||
|
|
|
|||
|
|
@ -113,6 +113,11 @@ serveur_icingaweb2_bpm_processes: {}
|
|||
# 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"
|
||||
|
||||
# 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 ------------------------------------
|
||||
#
|
||||
# UN SEUL COMPTE EST POSE PAR LE MOTEUR, et c'est voulu : celui qui permet d'entrer la
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@
|
|||
sondes:
|
||||
- nom: vigie
|
||||
ttl: 5400
|
||||
raison: '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.'
|
||||
raison: '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.'
|
||||
|
|
|
|||
|
|
@ -1,29 +1,112 @@
|
|||
#!/bin/bash
|
||||
# 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
|
||||
# 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,
|
||||
# `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
|
||||
# `monitoring-plugins-basic` : il parle deja l'API des greffons Nagios, gere les delais et
|
||||
# les redirections. On lui passe l'adresse et on rend SON code de sortie tel quel.
|
||||
#
|
||||
# SUR LA BOUCLE LOCALE, ET SANS S'AUTHENTIFIER. En mode `external`, l'ecoute est restreinte
|
||||
# a 127.0.0.1 et une passerelle SSO se tient devant : la sonde frappe donc la ou
|
||||
# l'application ecoute vraiment, et se contente de la reponse que rend une requete non
|
||||
# authentifiee. On mesure « la pile PHP sert encore », pas « l'annuaire accepte encore ».
|
||||
# Mise en defaut PAR PARAMETRE : `serveur_icingaweb2_sonde_codes` pour la premiere etape,
|
||||
# `serveur_icingaweb2_sonde_table` (table inexistante) pour la seconde.
|
||||
set -uo pipefail
|
||||
|
||||
# PREMIERE ETAPE — LA PILE WEB SERT. `check_http` vient de `monitoring-plugins-basic` : il
|
||||
# parle deja l'API des greffons Nagios. Sur la boucle locale et sans s'authentifier : en
|
||||
# mode `external`, l'ecoute est restreinte a 127.0.0.1 et une passerelle SSO se tient
|
||||
# devant ; la sonde frappe la ou l'application ecoute vraiment.
|
||||
#
|
||||
# `-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
|
||||
# sonde sur le comportement NORMAL d'une vigie saine — la pire sorte de fausse alerte,
|
||||
# celle qu'on finit par ignorer.
|
||||
# sonde sur le comportement NORMAL d'une vigie saine.
|
||||
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`.
|
||||
exec /usr/lib/nagios/plugins/check_http \
|
||||
-H 127.0.0.1 -p {{ serveur_icingaweb2_http_port }} -u '/' \
|
||||
-e '{{ serveur_icingaweb2_sonde_codes }}' \
|
||||
-t 10
|
||||
# ON LIT LA CONFIGURATION DEPLOYEE, PAS CELLE QU'ON CROIT AVOIR POSEE : chaque ressource
|
||||
# que la vigie UTILISE (module icingadb, authentification, groupes) est eprouvee avec SES
|
||||
# identifiants, par le MEME pilote PHP (PDO pgsql, ext-ldap). Un tenant (annuaire) et le
|
||||
# site (base locale) passent par le meme code sans qu'on ait a le lui dire.
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ graph LR
|
|||
|
||||
| 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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue