diff --git a/docs/audit/preuve-2026-09-14.md b/docs/audit/preuve-2026-09-14.md index e459c48..1cc0988 100644 --- a/docs/audit/preuve-2026-09-14.md +++ b/docs/audit/preuve-2026-09-14.md @@ -76,7 +76,7 @@ | P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp | | P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). | | P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s | -| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 26 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ | +| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 27 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ | | P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. | | P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (6 exposition(s)). | | P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 6 service(s) expose(s) portent le nom du plan (6 groupe(s) derive(s)). | diff --git a/roles/serveur_redis/defaults/main.yml b/roles/serveur_redis/defaults/main.yml index 43bd4fd..5c073ae 100644 --- a/roles/serveur_redis/defaults/main.yml +++ b/roles/serveur_redis/defaults/main.yml @@ -16,3 +16,18 @@ serveur_redis_maxmemory_policy: "allkeys-lru" # Mot de passe OBLIGATOIRE (expose sur le reseau interne). Ansible Vault. serveur_redis_password: "{{ vault_redis | default('') }}" # rempli depuis la voute (vault_redis) + +# --- SONDE DE SUPERVISION ------------------------------------------------------------ +# +# COMBIEN D'EVICTIONS ENTRE DEUX PASSAGES AVANT DE CRIER. Le compteur de Redis est +# CUMULATIF depuis le demarrage ; la sonde en fait un DEBIT, sinon une seule mauvaise +# journee laisserait le voyant rouge pour toujours. +# +# 500 par quart d'heure : un cache qui travaille evicte un peu, c'est sa nature avec +# `allkeys-lru`. Ce seuil vise le moment ou l'eviction cesse d'etre un ajustement pour +# devenir une perte — des sessions qui disparaissent plus vite qu'on ne les cree. +# +# C'EST AUSSI LA MISE EN DEFAUT DE LA SONDE : le poser a 0 rend CRITIQUE des la premiere +# eviction, sans rien casser. Une sonde se prouve deux fois — verte sur le sain, rouge +# sur le casse — et ce parametre est ce qui rend la seconde preuve REJOUABLE. +serveur_redis_sonde_evictions_crit: 500 diff --git a/roles/serveur_redis/meta/supervision.yml b/roles/serveur_redis/meta/supervision.yml new file mode 100644 index 0000000..fadd42b --- /dev/null +++ b/roles/serveur_redis/meta/supervision.yml @@ -0,0 +1,24 @@ +--- +# Supervision derivee du role. Voir docs/supervision-conception.md. +# +# UNE SEULE SONDE, ET C'EST LA REGLE « une par CAUSE D'ACTION ». Redis peut cesser de +# servir de deux facons, et les deux appellent le meme geste — aller voir Redis : +# +# 1. IL NE REPOND PAS — service mort, mot de passe rejete, ecoute perdue. +# 2. IL REPOND ET N'A RIEN A DIRE — borne a `maxmemory` avec `allkeys-lru`, un Redis +# plein n'echoue JAMAIS : il evicte. Les sessions disparaissent, les gens sont +# deconnectes au hasard, et le service reste vert. +# +# LE SECOND CAS EST CELUI QUI MANQUAIT, et c'est le plus couteux. La panne se presente +# comme un probleme d'application : « on me deconnecte tout le temps ». Personne ne +# regarde le cache, puisqu'il va bien. +# +# TTL de 5400 s pour un porteur qui passe aux 15 min : trois passages manques avant la +# peremption. Le silence alerte autant que l'echec. +sondes: + - nom: cache + ttl: 5400 + raison: 'Le cache repond-il, et sert-il encore ? Un Redis borne ne tombe pas quand il + est plein — il evicte. Les sessions se perdent une a une, les gens sont deconnectes + au hasard, et le service reste vert : la panne se presente comme un defaut + d''application.' diff --git a/roles/serveur_redis/tasks/main.yml b/roles/serveur_redis/tasks/main.yml index d5c0d6d..bcb53d1 100644 --- a/roles/serveur_redis/tasks/main.yml +++ b/roles/serveur_redis/tasks/main.yml @@ -36,3 +36,21 @@ name: "{{ serveur_redis_service }}" enabled: true state: started + +# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et +# pousse le resultat ; il n'a pas a savoir ce qu'elle mesure, ni ou vit le mot de passe. +- name: Assurer le repertoire des sondes de supervision + ansible.builtin.file: + path: /usr/local/lib/setops/sondes + state: directory + owner: root + group: root + mode: "0755" + +- name: Deposer la sonde « cache » + ansible.builtin.template: + src: sonde-cache.sh.j2 + dest: /usr/local/lib/setops/sondes/cache.sh + owner: root + group: root + mode: "0750" diff --git a/roles/serveur_redis/templates/sonde-cache.sh.j2 b/roles/serveur_redis/templates/sonde-cache.sh.j2 new file mode 100644 index 0000000..67a3037 --- /dev/null +++ b/roles/serveur_redis/templates/sonde-cache.sh.j2 @@ -0,0 +1,70 @@ +#!/bin/bash +# GENERE par Set-OPS (role serveur_redis). Ne pas editer a la main. +# +# SONDE « cache » — le cache repond-il, et sert-il encore a quelque chose ? +# +# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2. +# Mise en defaut PAR PARAMETRE : `serveur_redis_sonde_evictions_crit` (seuil impossible). +# +# UNE SEULE SONDE, PARCE QU'IL N'Y A QU'UNE CAUSE D'ACTION : « le cache ne sert plus ». +# Elle s'atteint par deux chemins, et le verdict appelle le meme geste — aller voir Redis. +# +# 1. IL NE REPOND PAS. Service mort, mot de passe rejete, ecoute perdue. +# 2. IL REPOND ET N'A RIEN A DIRE. Borne a `maxmemory` avec `allkeys-lru`, un Redis +# plein n'echoue jamais : il EVICTE. Les sessions disparaissent, les gens sont +# deconnectes au hasard, et le service reste vert. C'est la panne la plus trompeuse +# de ce composant — elle se presente comme un probleme d'application. +# +# CE QU'ON NE MESURE PAS ICI, ET C'EST LA DOCTRINE : l'occupation memoire, le taux de +# succes du cache, la latence. Ce sont des SERIES ; elles appartiennent a Prometheus. +# Icinga repond a une seule question — est-ce casse ? +# +# LE PORTEUR TOURNE EN root ; `redis-cli` lit le mot de passe dans la configuration que +# le role a posee, jamais dans un argument de ligne de commande (il serait visible dans +# `ps` de toute la machine). +set -uo pipefail + +CONF={{ serveur_redis_config }} +CRIT={{ serveur_redis_sonde_evictions_crit }} +SERVICE={{ serveur_redis_service }} + +systemctl is-active --quiet "${SERVICE}" || { echo "Redis n'est pas actif."; exit 2; } + +mdp=$(grep -oP '(?<=^requirepass ).*' "${CONF}" 2>/dev/null) +cli=(redis-cli --no-auth-warning) +[[ -n "${mdp}" ]] && cli+=(-a "${mdp}") + +# UN `PING`, PAS UN `connect()`. Le port peut repondre alors que l'authentification +# echoue : le client obtient une connexion et rien d'autre. `PING` traverse l'auth. +pong=$("${cli[@]}" ping 2>&1) +[[ "${pong}" == "PONG" ]] || { echo "Redis ne repond pas a PING : ${pong:0:70}"; exit 2; } + +lire() { "${cli[@]}" info "$1" 2>/dev/null | grep -oP "(?<=^$2:)[0-9]+" | tr -d '\r'; } + +evictions=$(lire stats evicted_keys) +[[ "${evictions}" =~ ^[0-9]+$ ]] || { echo "Redis vivant, compteur d'evictions illisible."; exit 1; } + +# LE COMPTEUR EST CUMULATIF DEPUIS LE DEMARRAGE — on mesure donc un DEBIT, pas un total, +# sinon la sonde vire au rouge pour toujours apres une seule mauvaise journee. +etat=/var/lib/setops/sonde-redis-evictions +prec=$(cat "${etat}" 2>/dev/null || echo "") +mkdir -p "$(dirname "${etat}")" && echo "${evictions}" > "${etat}" + +memoire=$(lire memory used_memory) +plafond=$("${cli[@]}" config get maxmemory 2>/dev/null | tail -1 | tr -d '\r') +perf="evictions=${evictions}c memoire=${memoire:-0}B;;;0;${plafond:-0}" + +if [[ "${prec}" =~ ^[0-9]+$ ]] && (( evictions >= prec )); then + depuis=$(( evictions - prec )) + if (( depuis >= CRIT )); then + echo "Redis evicte : ${depuis} cles depuis le dernier passage — des sessions se perdent en silence.|${perf}" + exit 2 + fi + echo "Cache sain — ${depuis} eviction(s) depuis le dernier passage.|${perf}" + exit 0 +fi + +# Premier passage, ou compteur remis a zero par un redemarrage : on ne juge pas un debit +# qu'on ne peut pas calculer. Vert, et on le DIT — un « inconnu » muet serait pire. +echo "Cache sain — premier releve, debit d'eviction mesurable au prochain passage.|${perf}" +exit 0