diff --git a/docs/audit/preuve-2026-09-14.md b/docs/audit/preuve-2026-09-14.md index 3148163..5fe8eb4 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 | 30 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 | 31 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_backup/defaults/main.yml b/roles/serveur_backup/defaults/main.yml index 8716281..ca15df0 100644 --- a/roles/serveur_backup/defaults/main.yml +++ b/roles/serveur_backup/defaults/main.yml @@ -57,3 +57,13 @@ serveur_backup_noeuds_attendus: >- {{ (client_backup_groupes_etat | default([]) | map('extract', groups) | select('defined') | flatten | unique | list) | intersect(groups['client_backup'] | default([])) }} + +# --- SONDE DE SUPERVISION ------------------------------------------------------------- +# +# L'ENDROIT QUE LA SONDE ESSAIE D'ECRIRE. C'est la racine du depot par defaut — celle que +# les clients atteignent en sftp. +# +# C'EST AUSSI LA MISE EN DEFAUT : pointer un chemin non inscriptible (ou inexistant) rend +# CRITIQUE sans toucher au vrai depot, ce qui rend la seconde preuve REJOUABLE. Une sonde +# se prouve deux fois — verte sur le sain, rouge sur le casse. +serveur_backup_sonde_racine: "{{ serveur_backup_racine }}" diff --git a/roles/serveur_backup/meta/supervision.yml b/roles/serveur_backup/meta/supervision.yml new file mode 100644 index 0000000..22a5ecf --- /dev/null +++ b/roles/serveur_backup/meta/supervision.yml @@ -0,0 +1,28 @@ +--- +# Supervision derivee du role. Voir docs/supervision-conception.md. +# +# UNE SEULE SONDE, ET ELLE NE JUGE RIEN DU CONTENU. +# +# Les instantanes sont chiffres cote client : ce serveur heberge des octets qu'il ne peut +# pas ouvrir, donc qu'il ne peut pas juger. « Cet instantane est-il recent, complet, +# restaurable ? » se demande a qui detient la cle — et `client_backup` le fait deja depuis +# chaque noeud, avec son propre minuteur. LA VERIFICATION SUIT LA CLE. +# +# CE QUE CELLE-CI AJOUTE, ET POURQUOI ELLE N'EST PAS UN DOUBLON : elle voit TOUT DE SUITE, +# et depuis la cause, ce que les clients ne decouvriront qu'a leur prochaine execution, +# chacun de son cote. Un systeme de fichiers remonte en lecture seule, un volume plein, +# des droits derives — le depot refuse alors tout le monde. +# +# NEUF ROUGES EPARS NE DESIGNENT PAS UNE CAUSE COMMUNE. C'est sa seule raison d'etre : +# dire UNE fois, ici, ce que neuf machines diront plus tard sans savoir qu'elles disent +# la meme chose. +# +# 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: depot + ttl: 5400 + raison: 'Le depot accepte-t-il encore une ecriture ? Il ne juge pas le contenu — chiffre + cote client, il ne peut pas l''ouvrir. Il constate que l''endroit prend encore : en + lecture seule, plein ou aux droits derives, il refuse tout le monde, et neuf clients + rougiront un par un sans designer la cause commune.' diff --git a/roles/serveur_backup/tasks/main.yml b/roles/serveur_backup/tasks/main.yml index c70ee2e..c4baa7c 100644 --- a/roles/serveur_backup/tasks/main.yml +++ b/roles/serveur_backup/tasks/main.yml @@ -42,3 +42,21 @@ - name: Vérification locale des dépôts (là où ce dépôt peut les ouvrir) ansible.builtin.include_tasks: verification.yml when: serveur_backup_verification_locale | bool + +# 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. +- 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 « depot » + ansible.builtin.template: + src: sonde-depot.sh.j2 + dest: /usr/local/lib/setops/sondes/depot.sh + owner: root + group: root + mode: "0750" diff --git a/roles/serveur_backup/templates/sonde-depot.sh.j2 b/roles/serveur_backup/templates/sonde-depot.sh.j2 new file mode 100644 index 0000000..9ea3c84 --- /dev/null +++ b/roles/serveur_backup/templates/sonde-depot.sh.j2 @@ -0,0 +1,50 @@ +#!/bin/bash +# GENERE par Set-OPS (role serveur_backup). Ne pas editer a la main. +# +# SONDE « depot » — le depot accepte-t-il encore qu'on y depose ? +# +# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2. +# Mise en defaut PAR PARAMETRE : `serveur_backup_sonde_racine` (chemin non inscriptible). +# +# CE QU'ELLE NE MESURE PAS, ET C'EST LA DOCTRINE LA PLUS STRICTE DE CE DEPOT. +# +# Elle ne juge RIEN du contenu. Les instantanes sont chiffres cote client : ce serveur +# heberge des octets qu'il ne peut pas ouvrir, donc qu'il ne peut pas juger. « Cet +# instantane est-il recent, complet, restaurable ? » se demande a qui detient la cle, et +# `client_backup` le fait deja depuis chaque noeud. LA VERIFICATION SUIT LA CLE. +# +# CE QU'ELLE MESURE, ET QUE PERSONNE D'AUTRE NE VOIT A TEMPS : que l'endroit accepte une +# ECRITURE. Un systeme de fichiers remonte en lecture seule apres une erreur disque, un +# volume plein, des droits qui ont derive — dans les trois cas le depot refuse tout, et +# les clients ne l'apprennent qu'a leur prochaine execution, chacun de son cote. +# +# NEUF ROUGES EPARS NE DESIGNENT PAS UNE CAUSE COMMUNE. C'est la seule raison d'etre de +# cette sonde : dire UNE fois, ici, ce que neuf machines diront plus tard sans savoir +# qu'elles disent la meme chose. +# +# ON ECRIT VRAIMENT, ET SOUS L'IDENTITE QUI DEPOSE. Un `test -w` ment sur un montage en +# lecture seule et sur un quota atteint. Seule une ecriture reelle, faite par le compte +# que les clients empruntent, prouve que le depot prend encore. +set -uo pipefail + +RACINE={{ serveur_backup_sonde_racine }} +UTIL={{ serveur_backup_utilisateur }} + +[[ -d "${RACINE}" ]] || { echo "Le depot ${RACINE} n'existe pas."; exit 2; } + +temoin="${RACINE}/.setops-temoin-sonde" +if ! sudo -u "${UTIL}" install -m 0600 /dev/null "${temoin}" 2>/dev/null; then + echo "Le depot ${RACINE} refuse l'ecriture pour ${UTIL} — plein, en lecture seule, ou droits derives." + exit 2 +fi +sudo -u "${UTIL}" rm -f "${temoin}" 2>/dev/null + +# CE CHIFFRE EST UN CONSTAT, PAS UN SEUIL. L'occupation appartient a Prometheus ; on la +# publie ici en donnee de performance pour que le graphe existe, sans jamais en faire un +# verdict. Icinga repond a une seule question — est-ce casse ? +lisible=$(df -h --output=avail "${RACINE}" 2>/dev/null | tail -1 | tr -d ' ') +octets=$(df -B1 --output=avail "${RACINE}" 2>/dev/null | tail -1 | tr -d ' ') +depots=$(sudo -u "${UTIL}" find "${RACINE}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l) + +echo "Le depot accepte l'ecriture — ${depots} depot(s) heberge(s), ${lisible} libres.|libre=${octets:-0}B depots=${depots}" +exit 0 diff --git a/roles/serveur_icinga/templates/setops-sauvegardes.conf.j2 b/roles/serveur_icinga/templates/setops-sauvegardes.conf.j2 index 8985451..7d4dd4c 100644 --- a/roles/serveur_icinga/templates/setops-sauvegardes.conf.j2 +++ b/roles/serveur_icinga/templates/setops-sauvegardes.conf.j2 @@ -10,7 +10,15 @@ * fichiers qui definissent le meme `object Host` font rejeter TOUTE la configuration. * Ce fichier n'attache que des services. * - * Les resultats sont POUSSES par `backup-01`, qui est le seul a pouvoir lire ses depots. + * QUI POUSSE : CHAQUE NOEUD, POUR SON PROPRE DEPOT. Cette ligne a dit le contraire + * jusqu'au 2026-09-14 — elle nommait `backup-01`, « le seul a pouvoir lire ses depots ». + * Or `backup-01` a ete RETIRE le 2026-09-02, et c'est tout le sujet : le site heberge des + * octets chiffres cote client, il ne peut pas les ouvrir, donc pas les juger. + * + * LA VERIFICATION SUIT LA CLE. Chaque noeud verifie SON depot distant et rapporte + * lui-meme, par `client_backup` et son minuteur. Le code de ce fichier avait suivi la + * decision — il attache les services aux vrais noeuds — mais l'en-tete decrivait encore + * un acteur disparu, a celui-la meme qui l'ouvrirait pour comprendre qui pousse. * Le `ttl` porte dans chaque envoi fait la fraicheur : sans nouvelle, Icinga bascule tout * seul en « expire ». C'est le SILENCE qui doit alerter, pas seulement l'echec — le * silence est precisement ce qui n'a alerte personne.