From 6e46ace4de89ee2a68ae76c9ba620118d077787f Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Thu, 10 Sep 2026 03:47:03 -0400 Subject: [PATCH] sondes : les cinq qui manquaient le plus (autorite, base, annuaire, zones, edge) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CORRECTION DE COMPTE D ABORD. J avais annonce cinq groupes sans sonde. La mesure en donne VINGT-SEPT : j avais compte ceux que j avais en tete, pas ceux que le depot contient. Il en reste vingt-deux. LES CINQ, PAR ORDRE DE DEGAT SILENCIEUX. autorite (step_ca) — la plus urgente : nos certificats vivent 24 h, une AC muette ne casse rien aujourd hui et casse TOUT demain, d un coup, sur les 21 machines. Elle surveille aussi l expiration de la RACINE, que personne ne regarde parce qu elle vit des annees (relevee a 3642 jours). base (postgresql) — une VRAIE requete, pas pg_isready : celui-ci dit que le port repond, pas que la base sert. Plus le compte des connexions : a saturation, chaque application tombe sans que la base ait l air morte. annuaire (openldap) — elle COMPTE les entrees. Un annuaire vide repond success a tout : le mensonge des sauvegardes vides, vert et sans contenu. zones (powerdns) — un autoritatif sans zone repond NXDOMAIN a tout, ce qui se lit comme « ce nom n existe pas ». edge (nginx) — elle valide la configuration SUR DISQUE : nginx sert la derniere valide, et une configuration cassee ne se voit qu au prochain demarrage, souvent des mois plus tard. Les cinq eprouvees vertes sur le sain puis rouges PAR PARAMETRE, sans toucher a un service. Etat : 20 sondes sur 19 roles, 23 services distincts, 70 instances, 67 au vert. Les trois autres sont connues : deux sauvegardes sans donnee a emporter, et Loki en delai de stabilisation apres redeploiement. make prouver : CONFORME, 64 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q --- CHANGELOG.md | 51 +++++++++++++++++++ docs/audit/preuve-2026-09-10.md | 2 +- roles/serveur_nginx/defaults/main.yml | 5 ++ roles/serveur_nginx/meta/supervision.yml | 8 +++ roles/serveur_nginx/tasks/main.yml | 19 +++++++ .../serveur_nginx/templates/sonde-edge.sh.j2 | 29 +++++++++++ roles/serveur_openldap/defaults/main.yml | 6 +++ roles/serveur_openldap/meta/supervision.yml | 8 +++ roles/serveur_openldap/tasks/main.yml | 19 +++++++ .../templates/sonde-annuaire.sh.j2 | 25 +++++++++ roles/serveur_postgresql/defaults/main.yml | 4 ++ roles/serveur_postgresql/meta/supervision.yml | 8 +++ roles/serveur_postgresql/tasks/main.yml | 19 +++++++ .../templates/sonde-base.sh.j2 | 34 +++++++++++++ roles/serveur_powerdns/defaults/main.yml | 4 ++ roles/serveur_powerdns/meta/supervision.yml | 8 +++ roles/serveur_powerdns/tasks/main.yml | 19 +++++++ .../templates/sonde-zones.sh.j2 | 24 +++++++++ roles/serveur_step_ca/defaults/main.yml | 7 +++ roles/serveur_step_ca/meta/supervision.yml | 8 +++ roles/serveur_step_ca/tasks/main.yml | 19 +++++++ .../templates/sonde-autorite.sh.j2 | 33 ++++++++++++ 22 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 roles/serveur_nginx/meta/supervision.yml create mode 100644 roles/serveur_nginx/templates/sonde-edge.sh.j2 create mode 100644 roles/serveur_openldap/meta/supervision.yml create mode 100644 roles/serveur_openldap/templates/sonde-annuaire.sh.j2 create mode 100644 roles/serveur_postgresql/meta/supervision.yml create mode 100644 roles/serveur_postgresql/templates/sonde-base.sh.j2 create mode 100644 roles/serveur_powerdns/meta/supervision.yml create mode 100644 roles/serveur_powerdns/templates/sonde-zones.sh.j2 create mode 100644 roles/serveur_step_ca/meta/supervision.yml create mode 100644 roles/serveur_step_ca/templates/sonde-autorite.sh.j2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 61513d6..16e93df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,56 @@ # CHANGELOG — Set-OPS +## 2026-09-10 (3) — Les cinq sondes qui manquaient le plus + +**20 sondes sur 19 roles. 23 services distincts, 70 instances, 67 au vert.** + +### D'abord une correction de compte + +J'avais annonce « cinq groupes sans sonde ». Mesure : **vingt-sept**. J'avais compte ceux +que j'avais en tete, pas ceux que le depot contient. Il en reste vingt-deux. + +### Les cinq posees, par ordre de degat silencieux + +**`autorite` (step_ca)** — la plus urgente de l'ecosysteme. Nos certificats vivent 24 h : +une AC muette ne casse rien aujourd'hui, elle casse TOUT demain, d'un coup, sur les +vingt-et-une machines a la fois. Elle surveille aussi **l'expiration de la RACINE**, que +personne ne regarde jamais parce qu'elle vit des annees — releve : 3642 jours. Le jour ou +elle expire, toute la confiance interne tombe d'un bloc, et aucun renouvellement de +certificat d'hote n'y change rien. + +**`base` (postgresql)** — une VRAIE requete, pas `pg_isready`. Celui-ci ouvre une connexion +et la ferme : il dit que le port repond, pas que la base sert. Une base en recuperation, en +lecture seule ou a court de connexions le passe et refuse tout travail. La sonde compte +aussi les connexions : a saturation, chaque application tombe en meme temps sans que la +base ait l'air morte. + +**`annuaire` (openldap)** — elle COMPTE les entrees. Un annuaire vide repond `success` a +tout, et plus personne ne s'authentifie nulle part : c'est exactement le mensonge des +sauvegardes vides, vert et sans contenu. + +**`zones` (powerdns)** — un autoritatif sans zone repond NXDOMAIN a tout, ce qui se lit +comme « ce nom n'existe pas ». La panne la plus trompeuse du DNS. + +**`edge` (nginx)** — elle valide la configuration SUR DISQUE. nginx garde la derniere +configuration valide et continue de servir ; une configuration cassee ne se voit qu'au +prochain demarrage, c'est-a-dire au pire moment, souvent des mois plus tard. + +Les cinq eprouvees vertes sur le sain, puis rouges PAR PARAMETRE — port ferme, seuil +impossible, port qui n'ecoute pas — sans toucher a un seul service. + +### Ce qui reste, et pourquoi ce n'est pas le meme genre + +Vingt-deux groupes. Ils ne sont pas de la meme nature : + + - **couverts ailleurs** : `client_metrique` (par `collecte` chez Prometheus), + `client_backup` (par `sauvegarde`), `client_sante` (sa fraicheur EST son `ttl`) ; + - **chemins de report** : `client_smtp`, `client_artefacts`, `client_journal`, + `client_resolveur` — leur panne se voit deja par le silence de ce qu'ils portent ; + - **du site** : `serveur_cache_site`, `serveur_forge_site`, `serveur_backup_site`, + `serveur_resolveur_site`, `serveur_ops_site` — a poser depuis l'instance du site ; + - **applications et socle** : `redis`, `rspamd`, `collabora`, `web_frontal`, + `web_dorsal`, `oauth2_proxy`, `icingaweb2`, `backup`, `debian`, `durci`. + ## 2026-09-10 (2) — `cache-apt` scindee, et le defaut que la scission a revele ### La scission diff --git a/docs/audit/preuve-2026-09-10.md b/docs/audit/preuve-2026-09-10.md index c8c6b30..897b082 100644 --- a/docs/audit/preuve-2026-09-10.md +++ b/docs/audit/preuve-2026-09-10.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 | 15 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_pki/certificat, serveur_artefacts/cache-apt, serveur_artefacts/cache-apt-vol | +| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 20 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_pki/certificat, serveur_artefacts/cache-apt, serveur_artefacts/cache-apt-vol | ## Couverture des affirmations ✅ du registre diff --git a/roles/serveur_nginx/defaults/main.yml b/roles/serveur_nginx/defaults/main.yml index 1ae9a30..a4d08a8 100644 --- a/roles/serveur_nginx/defaults/main.yml +++ b/roles/serveur_nginx/defaults/main.yml @@ -45,3 +45,8 @@ serveur_nginx_publier_expositions: true serveur_nginx_proxy_buffer_size: "16k" serveur_nginx_proxy_buffers: "8 16k" serveur_nginx_proxy_busy_buffers_size: "32k" + +# --- Sonde de supervision ----------------------------------------------------------- +# Les ports que l'edge DOIT servir. Variable, donc mise en defaut en nommant un port qui +# n'ecoute pas, sans toucher a nginx. +serveur_nginx_sonde_ports: "80 443" diff --git a/roles/serveur_nginx/meta/supervision.yml b/roles/serveur_nginx/meta/supervision.yml new file mode 100644 index 0000000..4537424 --- /dev/null +++ b/roles/serveur_nginx/meta/supervision.yml @@ -0,0 +1,8 @@ +--- +# Supervision derivee du role. Voir docs/supervision-conception.md. +sondes: + - nom: edge + ttl: 5400 + raison: 'L''edge ecoute-t-il, et sa configuration est-elle encore valide ? Un nginx dont la configuration + est cassee continue de servir l''ANCIENNE tant qu''on ne le recharge pas : il fonctionne, et le + prochain redemarrage le tuera.' diff --git a/roles/serveur_nginx/tasks/main.yml b/roles/serveur_nginx/tasks/main.yml index 7bf7218..ad4d340 100644 --- a/roles/serveur_nginx/tasks/main.yml +++ b/roles/serveur_nginx/tasks/main.yml @@ -94,3 +94,22 @@ name: "{{ serveur_nginx_service_name }}" enabled: true state: started + +# --- Sonde de supervision (docs/supervision-conception.md) -------------------------- +# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la +# fait tourner et pousse le verdict, sans 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 « edge » + ansible.builtin.template: + src: sonde-edge.sh.j2 + dest: /usr/local/lib/setops/sondes/edge.sh + owner: root + group: root + mode: "0750" diff --git a/roles/serveur_nginx/templates/sonde-edge.sh.j2 b/roles/serveur_nginx/templates/sonde-edge.sh.j2 new file mode 100644 index 0000000..0b7f7b9 --- /dev/null +++ b/roles/serveur_nginx/templates/sonde-edge.sh.j2 @@ -0,0 +1,29 @@ +#jinja2: comment_start_string:'{=#', comment_end_string:'#=}' +#!/bin/bash +# GENERE par Set-OPS (role serveur_nginx). Ne pas editer a la main. +# +# SONDE « edge » — l'edge ecoute et sa configuration tient au prochain demarrage +# +# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2. +# Mise en defaut PAR PARAMETRE : `serveur_nginx_sonde_ports` (port qui n'ecoute pas). +set -uo pipefail + +PORTS="{{ serveur_nginx_sonde_ports }}" + +systemctl is-active --quiet nginx || { echo "L'edge (nginx) n'est pas actif."; exit 2; } + +# LA CONFIGURATION SUR DISQUE, PAS SEULEMENT CELLE EN MEMOIRE. nginx garde la derniere +# configuration VALIDE et continue de servir ; une configuration cassee ne se voit qu'au +# prochain redemarrage — c'est-a-dire au pire moment, souvent des mois plus tard. +nginx -t >/dev/null 2>&1 \ + || { echo "L'edge sert, mais sa configuration sur disque est INVALIDE : le prochain demarrage echouera."; exit 2; } + +manquants=() +for p in ${PORTS}; do + ss -ltn 2>/dev/null | grep -qE "[:.]${p}[[:space:]]" || manquants+=("${p}") +done +if (( ${#manquants[@]} > 0 )); then + echo "L'edge n'ecoute pas sur : ${manquants[*]} — ce qui est publie derriere est injoignable." + exit 2 +fi +echo "Edge : configuration valide, ecoute sur ${PORTS}." diff --git a/roles/serveur_openldap/defaults/main.yml b/roles/serveur_openldap/defaults/main.yml index 208684c..09008b8 100644 --- a/roles/serveur_openldap/defaults/main.yml +++ b/roles/serveur_openldap/defaults/main.yml @@ -60,3 +60,9 @@ serveur_openldap_ppolicy_extra: {} # « identifiants invalides » et attend la duree de verrouillage. serveur_openldap_ppolicy_hash_cleartext: true serveur_openldap_ppolicy_use_lockout: false + +# --- Sonde de supervision ----------------------------------------------------------- +serveur_openldap_sonde_base: "{{ serveur_openldap_base_dn | default('dc=chezlepro,dc=internal') }}" +# UN MINIMUM, PAS UNE EGALITE : l'annuaire grandit. Ce chiffre dit « il reste peuple », +# pas « il contient exactement ceci ». Trop haut, il crierait a chaque menage. +serveur_openldap_sonde_minimum: 5 diff --git a/roles/serveur_openldap/meta/supervision.yml b/roles/serveur_openldap/meta/supervision.yml new file mode 100644 index 0000000..0a3430c --- /dev/null +++ b/roles/serveur_openldap/meta/supervision.yml @@ -0,0 +1,8 @@ +--- +# Supervision derivee du role. Voir docs/supervision-conception.md. +sondes: + - nom: annuaire + ttl: 5400 + raison: L'annuaire repond-il, et contient-il encore ses entrees ? Un annuaire vide repond parfaitement + a toutes les requetes — et plus personne ne s'authentifie nulle part. C'est le meme mensonge qu'une + sauvegarde vide. diff --git a/roles/serveur_openldap/tasks/main.yml b/roles/serveur_openldap/tasks/main.yml index e77b655..0e2bb92 100644 --- a/roles/serveur_openldap/tasks/main.yml +++ b/roles/serveur_openldap/tasks/main.yml @@ -288,3 +288,22 @@ - serveur_openldap_tls_actif | bool - serveur_openldap_cert_pont.stat.exists | default(false) notify: Redemarrer slapd + +# --- Sonde de supervision (docs/supervision-conception.md) -------------------------- +# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la +# fait tourner et pousse le verdict, sans 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 « annuaire » + ansible.builtin.template: + src: sonde-annuaire.sh.j2 + dest: /usr/local/lib/setops/sondes/annuaire.sh + owner: root + group: root + mode: "0750" diff --git a/roles/serveur_openldap/templates/sonde-annuaire.sh.j2 b/roles/serveur_openldap/templates/sonde-annuaire.sh.j2 new file mode 100644 index 0000000..1a08edf --- /dev/null +++ b/roles/serveur_openldap/templates/sonde-annuaire.sh.j2 @@ -0,0 +1,25 @@ +#!/bin/bash +# GENERE par Set-OPS (role serveur_openldap). Ne pas editer a la main. +# +# SONDE « annuaire » — l'annuaire repond ET contient ses entrees +# +# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2. +# Mise en defaut PAR PARAMETRE : `serveur_openldap_sonde_minimum` (seuil impossible). +set -uo pipefail + +BASE={{ serveur_openldap_sonde_base }} +MINIMUM={{ serveur_openldap_sonde_minimum }} + +systemctl is-active --quiet slapd || { echo "L'annuaire (slapd) n'est pas actif."; exit 2; } + +# ON COMPTE LES ENTREES, ON NE SE CONTENTE PAS D'UNE REPONSE. Un annuaire vide repond +# `success` a tout — c'est exactement le mensonge des sauvegardes vides : vert, et sans +# contenu. Sans ce comptage, la sonde dirait « tout va bien » sur un annuaire efface. +n=$(ldapsearch -x -H ldapi:/// -b "${BASE}" -s sub '(objectClass=*)' dn 2>/dev/null | grep -c '^dn:') +[[ "${n}" =~ ^[0-9]+$ ]] || { echo "L'annuaire ne repond pas sous ${BASE}."; exit 2; } + +if (( n < MINIMUM )); then + echo "Annuaire a ${n} entree(s) sous ${BASE}, moins que le minimum attendu (${MINIMUM}) — plus personne ne s'authentifiera.|entrees=${n};${MINIMUM};;0;" + exit 2 +fi +echo "Annuaire : ${n} entree(s) sous ${BASE}.|entrees=${n};${MINIMUM};;0;" diff --git a/roles/serveur_postgresql/defaults/main.yml b/roles/serveur_postgresql/defaults/main.yml index 4764a1b..f9a35d1 100644 --- a/roles/serveur_postgresql/defaults/main.yml +++ b/roles/serveur_postgresql/defaults/main.yml @@ -49,3 +49,7 @@ serveur_postgresql_tls_dir: "/var/lib/postgresql/tls" # Verrou zero-confiance : hostssl dans pg_hba => refuse toute connexion NON-TLS. # N'activer qu'apres avoir confirme que TOUS les clients sont en TLS (sinon coupure). serveur_postgresql_tls_force: false + +# --- Sonde de supervision ----------------------------------------------------------- +serveur_postgresql_sonde_pct_avert: 70 +serveur_postgresql_sonde_pct_crit: 90 diff --git a/roles/serveur_postgresql/meta/supervision.yml b/roles/serveur_postgresql/meta/supervision.yml new file mode 100644 index 0000000..affd762 --- /dev/null +++ b/roles/serveur_postgresql/meta/supervision.yml @@ -0,0 +1,8 @@ +--- +# Supervision derivee du role. Voir docs/supervision-conception.md. +sondes: + - nom: base + ttl: 5400 + raison: 'La base repond-elle a une VRAIE requete, et lui reste-t-il des connexions ? Un PostgreSQL + a court de connexions accepte encore le port et refuse tout le monde : chaque application tombe + en meme temps sans que la base ait l''air morte.' diff --git a/roles/serveur_postgresql/tasks/main.yml b/roles/serveur_postgresql/tasks/main.yml index 637ba9f..46d8305 100644 --- a/roles/serveur_postgresql/tasks/main.yml +++ b/roles/serveur_postgresql/tasks/main.yml @@ -215,3 +215,22 @@ loop_control: label: "{{ item.value.base }}" when: serveur_postgresql_registre | length > 0 + +# --- Sonde de supervision (docs/supervision-conception.md) -------------------------- +# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la +# fait tourner et pousse le verdict, sans 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 « base » + ansible.builtin.template: + src: sonde-base.sh.j2 + dest: /usr/local/lib/setops/sondes/base.sh + owner: root + group: root + mode: "0750" diff --git a/roles/serveur_postgresql/templates/sonde-base.sh.j2 b/roles/serveur_postgresql/templates/sonde-base.sh.j2 new file mode 100644 index 0000000..4f62cf3 --- /dev/null +++ b/roles/serveur_postgresql/templates/sonde-base.sh.j2 @@ -0,0 +1,34 @@ +#!/bin/bash +# GENERE par Set-OPS (role serveur_postgresql). Ne pas editer a la main. +# +# SONDE « base » — la base repond a une requete et garde des connexions libres +# +# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2. +# Mise en defaut PAR PARAMETRE : `serveur_postgresql_sonde_pct_avert` (seuil impossible). +set -uo pipefail + +AVERT={{ serveur_postgresql_sonde_pct_avert }} +CRIT={{ serveur_postgresql_sonde_pct_crit }} + +systemctl is-active --quiet postgresql || { echo "PostgreSQL n'est pas actif."; exit 2; } + +# UNE REQUETE, PAS UN `pg_isready`. `pg_isready` ouvre une connexion et la ferme : il dit +# que le port repond, pas que la base sert. Une base en recuperation, en lecture seule ou +# a court de connexions passe `pg_isready` et refuse tout travail. +util=$(sudo -u postgres psql -Atc 'select 1' 2>&1) +[[ "${util}" == "1" ]] || { echo "La base n'execute plus de requete : ${util:0:70}"; exit 2; } + +ouvertes=$(sudo -u postgres psql -Atc 'select count(*) from pg_stat_activity' 2>/dev/null) +max=$(sudo -u postgres psql -Atc 'show max_connections' 2>/dev/null) +[[ "${ouvertes}" =~ ^[0-9]+$ && "${max}" =~ ^[0-9]+$ ]] || { echo "Base vivante, comptes de connexions illisibles."; exit 1; } +pct=$(( ouvertes * 100 / max )) + +if (( pct >= CRIT )); then + echo "Connexions a ${pct}% (${ouvertes}/${max}) — les applications vont etre refusees.|conn=${ouvertes};;;0;${max}" + exit 2 +fi +if (( pct >= AVERT )); then + echo "Connexions a ${pct}% (${ouvertes}/${max}), seuil ${AVERT}%.|conn=${ouvertes};;;0;${max}" + exit 1 +fi +echo "Base : requete servie, ${ouvertes}/${max} connexions (${pct}%).|conn=${ouvertes};;;0;${max}" diff --git a/roles/serveur_powerdns/defaults/main.yml b/roles/serveur_powerdns/defaults/main.yml index 5f39c7a..8f8506b 100644 --- a/roles/serveur_powerdns/defaults/main.yml +++ b/roles/serveur_powerdns/defaults/main.yml @@ -80,3 +80,7 @@ serveur_powerdns_zones_inverses: >- {{ (serveur_powerdns_inventory_groups | map('extract', groups) | select | flatten | map('extract', hostvars, 'ansible_host') | select | list) | zones_inverses(setops_supernet | default('')) }} + +# --- Sonde de supervision ----------------------------------------------------------- +# La zone souveraine et son inverse : deux au minimum. +serveur_powerdns_sonde_minimum: 2 diff --git a/roles/serveur_powerdns/meta/supervision.yml b/roles/serveur_powerdns/meta/supervision.yml new file mode 100644 index 0000000..55463e4 --- /dev/null +++ b/roles/serveur_powerdns/meta/supervision.yml @@ -0,0 +1,8 @@ +--- +# Supervision derivee du role. Voir docs/supervision-conception.md. +sondes: + - nom: zones + ttl: 5400 + raison: 'L''autoritatif repond-il, et sert-il encore ses zones ? Un PowerDNS qui a perdu ses zones + repond NXDOMAIN a tout : la resolution interne s''effondre pendant que le service a l''air parfaitement + vivant.' diff --git a/roles/serveur_powerdns/tasks/main.yml b/roles/serveur_powerdns/tasks/main.yml index 090983d..37ffa8f 100644 --- a/roles/serveur_powerdns/tasks/main.yml +++ b/roles/serveur_powerdns/tasks/main.yml @@ -126,3 +126,22 @@ # n'existe pas encore et systemd echouerait. On saute en mode check seulement ; # en vrai deploiement, la tache s'execute normalement. when: not ansible_check_mode + +# --- Sonde de supervision (docs/supervision-conception.md) -------------------------- +# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la +# fait tourner et pousse le verdict, sans 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 « zones » + ansible.builtin.template: + src: sonde-zones.sh.j2 + dest: /usr/local/lib/setops/sondes/zones.sh + owner: root + group: root + mode: "0750" diff --git a/roles/serveur_powerdns/templates/sonde-zones.sh.j2 b/roles/serveur_powerdns/templates/sonde-zones.sh.j2 new file mode 100644 index 0000000..90cf7ef --- /dev/null +++ b/roles/serveur_powerdns/templates/sonde-zones.sh.j2 @@ -0,0 +1,24 @@ +#!/bin/bash +# GENERE par Set-OPS (role serveur_powerdns). Ne pas editer a la main. +# +# SONDE « zones » — l'autoritatif repond et sert ses zones +# +# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2. +# Mise en defaut PAR PARAMETRE : `serveur_powerdns_sonde_minimum` (seuil impossible). +set -uo pipefail + +MINIMUM={{ serveur_powerdns_sonde_minimum }} + +systemctl is-active --quiet pdns || { echo "L'autoritatif (pdns) n'est pas actif."; exit 2; } +[[ "$(pdns_control rping 2>&1 | head -1)" == "PONG" ]] \ + || { echo "L'autoritatif ne repond pas a son propre controle."; exit 2; } + +# LES ZONES, PAS SEULEMENT LE DEMON. Un PowerDNS sans zone repond NXDOMAIN a tout, ce qui +# se lit exactement comme « ce nom n'existe pas » — la panne la plus trompeuse du DNS. +n=$(pdnsutil list-all-zones 2>/dev/null | grep -vc '^All zonecount') +[[ "${n}" =~ ^[0-9]+$ ]] || { echo "Impossible de lister les zones."; exit 2; } +if (( n < MINIMUM )); then + echo "L'autoritatif ne sert que ${n} zone(s), moins que ${MINIMUM} — la resolution interne repondra NXDOMAIN.|zones=${n};${MINIMUM};;0;" + exit 2 +fi +echo "Autoritatif : ${n} zone(s) servie(s).|zones=${n};${MINIMUM};;0;" diff --git a/roles/serveur_step_ca/defaults/main.yml b/roles/serveur_step_ca/defaults/main.yml index 9c09b30..a3eade4 100644 --- a/roles/serveur_step_ca/defaults/main.yml +++ b/roles/serveur_step_ca/defaults/main.yml @@ -25,3 +25,10 @@ serveur_step_ca_acme: true # Secrets (Ansible Vault) — obligatoires pour l'initialisation. serveur_step_ca_password: "{{ vault_step_ca_password | default('') }}" # rempli depuis la voute (vault_step_ca_password) serveur_step_ca_provisioner_password: "{{ vault_step_ca_provisioner_password | default('') }}" # rempli depuis la voute (vault_step_ca_provisioner_password) + +# --- Sonde de supervision ----------------------------------------------------------- +serveur_step_ca_sonde_url: "https://127.0.0.1:8443" +serveur_step_ca_sonde_racine: "/etc/step/certs/root_ca.crt" +# Une racine se remplace par une operation lourde et coordonnee : trois mois d'avance +# n'est pas de la prudence, c'est le minimum pour l'organiser. +serveur_step_ca_sonde_racine_jours: 90 diff --git a/roles/serveur_step_ca/meta/supervision.yml b/roles/serveur_step_ca/meta/supervision.yml new file mode 100644 index 0000000..7e6e46b --- /dev/null +++ b/roles/serveur_step_ca/meta/supervision.yml @@ -0,0 +1,8 @@ +--- +# Supervision derivee du role. Voir docs/supervision-conception.md. +sondes: + - nom: autorite + ttl: 5400 + raison: 'L''autorite de certification repond-elle ? C''est la sonde la plus urgente de l''ecosysteme + : nos certificats vivent 24 h. Une AC muette ne casse rien aujourd''hui — elle casse TOUT demain, + d''un coup, sur les vingt-et-une machines a la fois.' diff --git a/roles/serveur_step_ca/tasks/main.yml b/roles/serveur_step_ca/tasks/main.yml index e956aeb..836ad50 100644 --- a/roles/serveur_step_ca/tasks/main.yml +++ b/roles/serveur_step_ca/tasks/main.yml @@ -138,3 +138,22 @@ enabled: true state: started daemon_reload: true + +# --- Sonde de supervision (docs/supervision-conception.md) -------------------------- +# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la +# fait tourner et pousse le verdict, sans 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 « autorite » + ansible.builtin.template: + src: sonde-autorite.sh.j2 + dest: /usr/local/lib/setops/sondes/autorite.sh + owner: root + group: root + mode: "0750" diff --git a/roles/serveur_step_ca/templates/sonde-autorite.sh.j2 b/roles/serveur_step_ca/templates/sonde-autorite.sh.j2 new file mode 100644 index 0000000..ead0a1f --- /dev/null +++ b/roles/serveur_step_ca/templates/sonde-autorite.sh.j2 @@ -0,0 +1,33 @@ +#!/bin/bash +# GENERE par Set-OPS (role serveur_step_ca). Ne pas editer a la main. +# +# SONDE « autorite » — l'autorite de certification repond et sa racine est valide +# +# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2. +# Mise en defaut PAR PARAMETRE : `serveur_step_ca_sonde_url` (port ferme). +set -uo pipefail + +URL={{ serveur_step_ca_sonde_url }} +RACINE={{ serveur_step_ca_sonde_racine }} +JOURS={{ serveur_step_ca_sonde_racine_jours }} + +s=$(curl -sk --max-time 10 "${URL}/health" 2>/dev/null) +printf '%s' "${s}" | grep -q '"status"[[:space:]]*:[[:space:]]*"ok"' \ + || { echo "L'autorite ne repond pas sur ${URL} — plus aucun certificat ne se renouvellera, et ils vivent 24 h."; exit 2; } + +# LA RACINE, DONT PERSONNE NE SURVEILLE L'EXPIRATION. Elle vit des ANNEES, donc on n'y +# pense jamais — et le jour ou elle expire, toute la confiance interne tombe d'un bloc, +# sans qu'aucun renouvellement de certificat d'hote n'y change quoi que ce soit. +if [[ -r "${RACINE}" ]]; then + fin=$(openssl x509 -in "${RACINE}" -noout -enddate 2>/dev/null | cut -d= -f2) + if [[ -n "${fin}" ]]; then + reste=$(( ( $(date -d "${fin}" +%s) - $(date +%s) ) / 86400 )) + if (( reste <= JOURS )); then + echo "AUTORITE VIVANTE, mais sa RACINE expire dans ${reste} jour(s) — toute la confiance interne tombera d'un bloc.|racine=${reste}j;${JOURS};0;;" + exit 2 + fi + echo "Autorite vivante ; racine valide ${reste} jour(s).|racine=${reste}j;${JOURS};0;;" + exit 0 + fi +fi +echo "Autorite vivante (racine non lisible depuis cet hote)."