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)."