un fichier vide existe, et une sonde pour les correctifs

LA GARDE FICHIER ENTIER, en deux corrections. Un telechargement
interrompu laisse un fichier de zero octet QUI EXISTE, et toutes les gardes
demandaient seulement s il etait la. infra-mail-01 a garde une cle
smallstep de 0 octet apres l epreuve hors ligne.

La premiere correction n a pas suffi. Ajouter le controle de taille faisait
bien s executer la tache - et le fichier faisait toujours 0 octet au
passage suivant. get_url sur une destination existante emet une requete
CONDITIONNELLE : l amont repond non modifie, le module rend ok, la ruine
reste. Le play etait vert et ne reparait rien. Il faut effacer avant de
redemander.

Controle negatif : 0 -> 1022 octets, 0 erreur apt. Cinq roles.

LA SONDE CORRECTIFS, 23e. Set-OPS desarme unattended-upgrades et applique
les correctifs au deploiement - choix defendable, le verrou dpkg a fait
decrocher une machine d une reconstruction entiere le matin meme. Mais rien
ne disait QUAND le geste etait du : une flotte pouvait deriver des mois en
restant verte.

Elle mesure les paquets de securite en attente ET depuis quand. Elle ne
lance pas apt-get update - une sonde qui rafraichit l index toutes les
quinze minutes deviendrait la cause de la panne qu elle surveille. Et le
seuil de 72 h est un choix d exploitation, pas une derivation : le
mecanisme qui applique les correctifs est un geste humain.

P64 REFUSAIT UNE DECLARATION CORRECTE. serveur_debian et serveur_durci sont
des roles de declaration pure, sans une tache ; le travail est fait par les
roles que leur playbook applique. La preuve exigeait declaration et depot
dans le meme role - vrai des vingt-deux premieres sondes, faux des qu une
sonde appartient au socle. Une garde qui force a contourner ce qu elle
protege est un defaut. Elle suit desormais le playbook du groupe.

Mesure : 21/21 machines vertes, 65 preuves, 23 sondes, 0 echec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
Daniel Allaire 2026-09-10 17:26:35 -04:00
parent 1410fe41ec
commit a60b69f072
14 changed files with 452 additions and 9 deletions

View file

@ -1,5 +1,78 @@
# CHANGELOG — Set-OPS
## 2026-09-10 (12) — Un fichier vide existe, et une sonde pour les correctifs
### La garde « fichier entier » — et pourquoi il a fallu DEUX corrections
Un telechargement interrompu laisse un fichier de zero octet, **qui existe**. Toutes les
gardes de ce depot demandaient *« ce fichier est-il la ? »* :
- name: Cette ressource est-elle deja recuperee ?
ansible.builtin.stat: ...
- name: Telecharger la cle
when: not ..._present.stat.exists
`infra-mail-01` a garde une cle smallstep de **0 octet** apres l'epreuve hors ligne. Treize
machines portaient 1022 octets, elle portait le vide — et `apt` refusait le depot.
**La premiere correction n'a pas suffi.** Ajouter le controle de taille a bien fait
s'executer la tache (`ok: [infra-mail-01]`) — et le fichier faisait **toujours 0 octet au
passage suivant**. `get_url` sur une destination existante emet une requete CONDITIONNELLE :
l'amont repond « non modifie », le module rend `ok`, la ruine reste. *Le play etait vert et
ne reparait rien.*
Il faut donc **effacer avant de redemander**. `state: absent` ne mord que sur un fichier
vide : une cle valide n'est jamais retiree.
Controle negatif : fichier vide volontairement, role rejoue → **0 → 1022 octets, 0 erreur
apt**. Cinq roles portent le patron complet.
*Cette etape intermediaire est la lecon de la journee en miniature : un `failed=0` ne dit
pas que quelque chose a ete fait.*
### La sonde `correctifs` — 23e sonde
Set-OPS **desarme** `unattended-upgrades` (masque sur les vingt et une machines) et applique
les correctifs par `upgrade: full` au passage du socle. Choix defendable — un minuteur de
fond qui se dispute le verrou `dpkg` avec un deploiement est un tirage au sort, et il a fait
decrocher `infra-mail-01` d'une reconstruction entiere le matin meme.
Mais rien ne disait **quand le geste etait du**. Vingt-deux sondes, aucune sur le retard de
securite : une flotte pouvait deriver des mois en restant verte.
Elle mesure les paquets en attente venant d'un depot de SECURITE, **et depuis quand** — le
retard seul ne dit rien, c'est sa duree qui transforme un correctif publie en exposition
acceptee. Trois etats eprouves sur une copie : `rc=0` a jour, `rc=1` des le premier
correctif, `rc=2` au-dela du seuil.
**Deux choix dits franchement.** Elle ne lance PAS `apt-get update` : une sonde qui
rafraichit l'index toutes les quinze minutes deviendrait la cause de la panne qu'elle
surveille. Et le seuil de 72 h est un CHOIX D'EXPLOITATION, pas une derivation — les autres
seuils se deduisent d'un mecanisme (le certificat vit 24 h, donc on alerte a 6 h) ; ici le
mecanisme est un geste humain, il n'y a rien a en deduire.
### P64 refusait une declaration correcte
Declaree dans `common_packages`, la sonde etait **invisible d'Icinga** : la derivation croise
les GROUPES, et ce role n'en est pas un. Pire, `client_sante` l'aurait retiree comme
orpheline au passage suivant — la garde ecrite le matin meme.
Mais la declarer dans `serveur_debian` faisait echouer P64, qui exigeait declaration et depot
dans le MEME role. Or `serveur_debian` et `serveur_durci` sont des roles de **declaration
pure** : ils portent `flux.yml`, `authentification.yml`, `supervision.yml`, et pas une tache.
Le travail est fait par les roles que leur playbook applique.
**Une garde qui force a contourner ce qu'elle protege est un defaut.** P64 suit desormais le
playbook du groupe : ce qu'il applique compte comme depose. Elle ne s'affaiblit pas — elle
apprend ou le depot a le droit de vivre. Controle negatif refait : depot desactive → refus,
restaure → 65 OK.
### Etat mesure
sonde `correctifs` 21 / 21 machines vertes
prouver 65 preuves, 23 sondes, 0 echec
ansible-lint 0 defaut
## 2026-09-10 (11) — Le trou reste ouvert derriere la porte qu'on croyait fermee
Troisieme reconstruction complete de Chezlepro : **32 minutes, un seul echec — le mien**,

View file

@ -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 | 22 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 | 23 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. |
## Couverture des affirmations ✅ du registre

View file

@ -55,5 +55,5 @@ depots:
# `alloy` est universel comme `step-cli` : `client_metrique` le pose partout.
paquets:
alloy: 1.19.2-1
grafana: 13.2.0
grafana: 13.2.1
loki: 3.7.7

View file

@ -16,13 +16,50 @@
# CONSEQUENCE ASSUMEE : une rotation de cle amont n'est plus recuperee toute seule. Elle
# ne passe pas inapercue pour autant — `apt` refuse alors le depot, bruyamment. Pour
# forcer le rafraichissement : supprimer le fichier et rejouer le role.
# UN FICHIER VIDE EXISTE (mesure du 2026-09-10).
#
# Cette garde demandait « ce fichier est-il la ? ». Un telechargement interrompu — un 503,
# un delai depasse, une coupure — laisse un fichier de ZERO octet, qui existe. La tache
# est donc sautee a tous les passages suivants, et la machine garde une ressource morte
# pour toujours, en silence.
#
# CE QUE CA A COUTE : `infra-mail-01` a garde une cle smallstep de 0 octet apres une
# epreuve hors ligne. Treize machines portaient 1022 octets, elle portait le vide :
#
# E: Le depot http://packages.smallstep.com/... n'est pas signe.
#
# Le meme jour, l'index `dists/trixie` du cache du site etait tombe de 140 416 a 4 332
# octets — meme famille, autre endroit. Un telechargement partiel ne se signale pas : il
# se fait passer pour un succes.
#
# On demande donc « est-il ENTIER ? » — a defaut de pouvoir demander « est-il JUSTE ? »,
# ce qui exigerait une empreinte de reference que l'amont ne publie pas toujours.
- name: Cette ressource est-elle deja recuperee ? (Telecharger la cle de signature Gr)
ansible.builtin.stat:
path: "{{ client_journal_depot_cle_fichier }}"
register: telecharger_la_cle_de_signature_grafana_present
# ... ET IL FAUT LE RETIRER, PAS SEULEMENT LE REDEMANDER (mesure du 2026-09-10).
#
# Laisser la garde autoriser la tache ne suffit PAS. `get_url` sur une destination qui
# existe emet une requete CONDITIONNELLE : l'amont repond « non modifie », le module rend
# `ok`, et le fichier corrompu reste en place. Mesure : la tache s'executait bien
# (`ok: [infra-mail-01]`), et la cle faisait toujours 0 octet au passage suivant. Un
# `force: yes` manuel l'a reparee en une seconde.
#
# On efface donc la ruine avant de reconstruire. `state: absent` ne mord que sur un
# fichier vide : une cle valide n'est jamais retiree, et la doctrine « on ne retelecharge
# pas ce qu'on possede deja » reste intacte.
- name: Retirer une ressource VIDE avant de la redemander
ansible.builtin.file:
path: "{{ client_journal_depot_cle_fichier }}"
state: absent
when:
- telecharger_la_cle_de_signature_grafana_present.stat.exists
- (telecharger_la_cle_de_signature_grafana_present.stat.size | default(0)) == 0
- name: Telecharger la cle de signature Grafana
when: not telecharger_la_cle_de_signature_grafana_present.stat.exists
when: not (telecharger_la_cle_de_signature_grafana_present.stat.exists and telecharger_la_cle_de_signature_grafana_present.stat.size | default(0) > 0)
ansible.builtin.get_url:
url: "{{ client_journal_depot_cle_url }}"
dest: "{{ client_journal_depot_cle_fichier }}"

View file

@ -58,6 +58,24 @@
# CONSEQUENCE ASSUMEE : une rotation de cle amont n'est plus recuperee toute seule. Elle
# ne passe pas inapercue pour autant — `apt` refuse alors le depot, bruyamment. Pour
# forcer le rafraichissement : supprimer le fichier et rejouer le role.
# UN FICHIER VIDE EXISTE (mesure du 2026-09-10).
#
# Cette garde demandait « ce fichier est-il la ? ». Un telechargement interrompu — un 503,
# un delai depasse, une coupure — laisse un fichier de ZERO octet, qui existe. La tache
# est donc sautee a tous les passages suivants, et la machine garde une ressource morte
# pour toujours, en silence.
#
# CE QUE CA A COUTE : `infra-mail-01` a garde une cle smallstep de 0 octet apres une
# epreuve hors ligne. Treize machines portaient 1022 octets, elle portait le vide :
#
# E: Le depot http://packages.smallstep.com/... n'est pas signe.
#
# Le meme jour, l'index `dists/trixie` du cache du site etait tombe de 140 416 a 4 332
# octets — meme famille, autre endroit. Un telechargement partiel ne se signale pas : il
# se fait passer pour un succes.
#
# On demande donc « est-il ENTIER ? » — a defaut de pouvoir demander « est-il JUSTE ? »,
# ce qui exigerait une empreinte de reference que l'amont ne publie pas toujours.
- name: Cette ressource est-elle deja recuperee ? (Telecharger la cle de signature Sm)
ansible.builtin.stat:
path: "{{ client_pki_depot_cle_fichier }}"
@ -66,8 +84,27 @@
# Un serveur tiers intermittent ne doit pas arreter un deploiement de quarante
# minutes (mesure du 2026-08-23 : la meme URL pend, puis rend 200 en 0,48 s au
# second essai). Defaut de `get_url` : 10 s et aucune reprise.
# ... ET IL FAUT LE RETIRER, PAS SEULEMENT LE REDEMANDER (mesure du 2026-09-10).
#
# Laisser la garde autoriser la tache ne suffit PAS. `get_url` sur une destination qui
# existe emet une requete CONDITIONNELLE : l'amont repond « non modifie », le module rend
# `ok`, et le fichier corrompu reste en place. Mesure : la tache s'executait bien
# (`ok: [infra-mail-01]`), et la cle faisait toujours 0 octet au passage suivant. Un
# `force: yes` manuel l'a reparee en une seconde.
#
# On efface donc la ruine avant de reconstruire. `state: absent` ne mord que sur un
# fichier vide : une cle valide n'est jamais retiree, et la doctrine « on ne retelecharge
# pas ce qu'on possede deja » reste intacte.
- name: Retirer une ressource VIDE avant de la redemander
ansible.builtin.file:
path: "{{ client_pki_depot_cle_fichier }}"
state: absent
when:
- telecharger_la_cle_de_signature_smallste_present.stat.exists
- (telecharger_la_cle_de_signature_smallste_present.stat.size | default(0)) == 0
- name: Telecharger la cle de signature Smallstep
when: not telecharger_la_cle_de_signature_smallste_present.stat.exists
when: not (telecharger_la_cle_de_signature_smallste_present.stat.exists and telecharger_la_cle_de_signature_smallste_present.stat.size | default(0) > 0)
ansible.builtin.get_url:
url: "{{ client_pki_depot_cle_url }}"
dest: "{{ client_pki_depot_cle_fichier }}"

View file

@ -75,3 +75,20 @@ common_packages_unites_maj_auto:
- apt-daily.timer
- apt-daily-upgrade.timer
- unattended-upgrades.service
# --- Sonde de supervision (declaree dans roles/serveur_debian/meta/supervision.yml) ---
common_packages_sonde_correctifs_etat: "/var/lib/setops/correctifs-vus"
# LE SEUIL N'EST PAS DERIVE D'UN MECANISME, ET IL FAUT LE DIRE.
#
# Les autres seuils du depot se derivent de ce que la machine fait : le certificat vit
# 24 h, donc on alerte a 6 h ; le porteur passe toutes les 15 min, donc le `ttl` vaut six
# periodes. Ici, le mecanisme qui applique les correctifs est un DEPLOIEMENT — un geste
# humain. Aucune periode a en deriver.
#
# 72 h est donc un CHOIX D'EXPLOITATION, pas une deduction : trois jours laissent passer
# un week-end sans crier, et au-dela un correctif publie devient une exposition connue et
# acceptee. En dessous du seuil la sonde AVERTIT deja — elle ne se tait jamais sur un
# correctif de securite en attente, elle gradue seulement l'urgence.
common_packages_sonde_seuil_heures: 72

View file

@ -68,3 +68,32 @@
ansible.builtin.apt:
autoremove: true
lock_timeout: "{{ common_packages_lock_timeout }}"
# LA SONDE SE DEPOSE ICI, ELLE SE DECLARE AILLEURS (`roles/serveur_debian/meta/`).
# Ce role est celui qui APPLIQUE les mises a jour ; il est donc le bon porteur. Mais la
# derivation des services d'Icinga croise les GROUPES, et `common_packages` n'en est pas
# un — d'ou la separation, expliquee dans la declaration.
- 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: Assurer le repertoire d etat des sondes
ansible.builtin.file:
path: /var/lib/setops
state: directory
owner: root
group: root
mode: "0750"
- name: Deposer la sonde des correctifs de securite
ansible.builtin.template:
src: sonde-correctifs.sh.j2
dest: /usr/local/lib/setops/sondes/correctifs.sh
owner: root
group: root
mode: "0750"

View file

@ -0,0 +1,62 @@
#!/bin/bash
# GENERE par Set-OPS (role common_packages). Ne pas editer a la main.
#
# SONDE « correctifs » — des correctifs de securite attendent-ils, et depuis quand ?
#
# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2.
# Mise en defaut PAR PARAMETRE : `common_packages_sonde_seuil_heures` (mettre 0).
set -uo pipefail
ETAT={{ common_packages_sonde_correctifs_etat }}
SEUIL_H={{ common_packages_sonde_seuil_heures }}
# ON NE MET PAS A JOUR L'INDEX ICI, ET C'EST VOULU. Une sonde qui declenche un
# `apt-get update` a chaque passage ferait tourner le reseau et le verrou dpkg toutes les
# quinze minutes — elle deviendrait la cause de la panne qu'elle surveille. L'index est
# rafraichi par le deploiement ; la sonde lit ce que la machine SAIT.
simule=$(LC_ALL=C apt-get -s dist-upgrade 2>/dev/null | grep '^Inst') || true
total=$(printf '%s\n' "${simule}" | grep -c . )
[[ -n "${simule}" ]] || total=0
# UN CORRECTIF DE SECURITE SE RECONNAIT A SON DEPOT D'ORIGINE, pas a son nom. `apt-get -s`
# rend l'origine entre parentheses ; on y cherche le mot que Debian y met.
secu=$(printf '%s\n' "${simule}" | grep -ci 'security' || true)
: "${secu:=0}"
maintenant=$(date +%s)
depuis=0
if [[ -r "${ETAT}" ]]; then
depuis=$(tr -cd '0-9' < "${ETAT}" 2>/dev/null)
fi
: "${depuis:=0}"
perf="en_attente=${total} securite=${secu}"
if (( secu == 0 )); then
# PLUS RIEN EN ATTENTE : on oublie la date, sinon le prochain correctif heriterait de
# l'anciennete du precedent et naitrait deja en retard.
rm -f "${ETAT}" 2>/dev/null || true
if (( total > 0 )); then
echo "Aucun correctif de securite en attente (${total} mise(s) a jour ordinaire(s)). | ${perf}"
exit 0
fi
echo "A jour : aucune mise a jour en attente. | ${perf}"
exit 0
fi
# PREMIERE FOIS QU'ON LES VOIT : on note la date, on ne crie pas encore. Un correctif
# publie il y a trois minutes n'est pas une negligence.
if (( depuis == 0 )); then
printf '%s' "${maintenant}" > "${ETAT}" 2>/dev/null || true
depuis=${maintenant}
fi
age_h=$(( (maintenant - depuis) / 3600 ))
perf="${perf} age_heures=${age_h}"
if (( age_h >= SEUIL_H )); then
echo "CORRECTIFS DE SECURITE EN ATTENTE depuis ${age_h} h : ${secu} paquet(s). Deployer le socle. | ${perf}"
exit 2
fi
echo "${secu} correctif(s) de securite en attente depuis ${age_h} h (seuil ${SEUIL_H} h). | ${perf}"
exit 1

View file

@ -29,13 +29,50 @@
# Récupérée UNE FOIS, comme la clé Smallstep : une plateforme souveraine ne recontacte pas
# un serveur étranger à chaque déploiement pour une clé déjà installée. Une rotation amont
# ne passe pas inaperçue pour autant — `apt` refuse alors le dépôt, bruyamment.
# UN FICHIER VIDE EXISTE (mesure du 2026-09-10).
#
# Cette garde demandait « ce fichier est-il la ? ». Un telechargement interrompu — un 503,
# un delai depasse, une coupure — laisse un fichier de ZERO octet, qui existe. La tache
# est donc sautee a tous les passages suivants, et la machine garde une ressource morte
# pour toujours, en silence.
#
# CE QUE CA A COUTE : `infra-mail-01` a garde une cle smallstep de 0 octet apres une
# epreuve hors ligne. Treize machines portaient 1022 octets, elle portait le vide :
#
# E: Le depot http://packages.smallstep.com/... n'est pas signe.
#
# Le meme jour, l'index `dists/trixie` du cache du site etait tombe de 140 416 a 4 332
# octets — meme famille, autre endroit. Un telechargement partiel ne se signale pas : il
# se fait passer pour un succes.
#
# On demande donc « est-il ENTIER ? » — a defaut de pouvoir demander « est-il JUSTE ? »,
# ce qui exigerait une empreinte de reference que l'amont ne publie pas toujours.
- name: Cette ressource est-elle deja recuperee ? (cle Collabora)
ansible.builtin.stat:
path: "{{ serveur_collabora_depot_cle_fichier }}"
register: serveur_collabora_cle_presente
# ... ET IL FAUT LE RETIRER, PAS SEULEMENT LE REDEMANDER (mesure du 2026-09-10).
#
# Laisser la garde autoriser la tache ne suffit PAS. `get_url` sur une destination qui
# existe emet une requete CONDITIONNELLE : l'amont repond « non modifie », le module rend
# `ok`, et le fichier corrompu reste en place. Mesure : la tache s'executait bien
# (`ok: [infra-mail-01]`), et la cle faisait toujours 0 octet au passage suivant. Un
# `force: yes` manuel l'a reparee en une seconde.
#
# On efface donc la ruine avant de reconstruire. `state: absent` ne mord que sur un
# fichier vide : une cle valide n'est jamais retiree, et la doctrine « on ne retelecharge
# pas ce qu'on possede deja » reste intacte.
- name: Retirer une ressource VIDE avant de la redemander
ansible.builtin.file:
path: "{{ serveur_collabora_depot_cle_fichier }}"
state: absent
when:
- serveur_collabora_cle_presente.stat.exists
- (serveur_collabora_cle_presente.stat.size | default(0)) == 0
- name: Telecharger la cle de signature Collabora
when: not serveur_collabora_cle_presente.stat.exists
when: not (serveur_collabora_cle_presente.stat.exists and serveur_collabora_cle_presente.stat.size | default(0) > 0)
ansible.builtin.get_url:
url: "{{ serveur_collabora_depot_cle_url }}"
dest: "{{ serveur_collabora_depot_cle_fichier }}"

View file

@ -0,0 +1,34 @@
---
# Supervision derivee du role. Voir docs/supervision-conception.md.
#
# CE QUE CETTE SONDE MESURE, ET POURQUOI ELLE MANQUAIT.
#
# Set-OPS DESARME les mises a jour automatiques de Debian — `unattended-upgrades` est
# masque sur les vingt et une machines — et les applique lui-meme, par `upgrade: full`
# au passage du socle. C'est un choix defendable : un minuteur de fond qui se dispute le
# verrou `dpkg` avec un deploiement est un tirage au sort, et il a fait decrocher
# `infra-mail-01` d'une reconstruction entiere le 2026-09-10.
#
# Mais ce choix a une consequence que rien ne rattrapait : **les correctifs n'arrivent
# que si quelqu'un deploie**. Vingt-deux sondes couvraient les certificats, les journaux,
# les metriques, les sauvegardes, les bases, l'annuaire, les boites — aucune ne disait
# qu'une machine prenait du retard de securite. Une flotte pouvait deriver des mois en
# restant verte.
#
# CE QU'ON NE MESURE PAS, ET C'EST DELIBERE :
#
# - « le minuteur a-t-il tourne ? » — il est masque, exprès ; mesurer son inactivite
# serait mesurer une decision, pas un risque.
# - « la machine est-elle a jour ? » — trop vague : un paquet d'agrement en retard
# n'est pas un correctif de securite en attente.
#
# CE QU'ON MESURE : les paquets en attente venant d'un depot de SECURITE, et depuis
# COMBIEN DE TEMPS. Le retard seul ne dit rien — c'est sa DUREE qui transforme un
# correctif publie en exposition acceptee.
sondes:
- nom: correctifs
ttl: 5400
raison: >-
Des correctifs de securite attendent-ils d'etre appliques, et depuis quand ?
Set-OPS desarme les mises a jour automatiques et les applique au deploiement :
sans cette mesure, rien ne dit quand le geste est du.

View file

@ -48,13 +48,50 @@
# CONSEQUENCE ASSUMEE : une rotation de cle amont n'est plus recuperee toute seule. Elle
# ne passe pas inapercue pour autant — `apt` refuse alors le depot, bruyamment. Pour
# forcer le rafraichissement : supprimer le fichier et rejouer le role.
# UN FICHIER VIDE EXISTE (mesure du 2026-09-10).
#
# Cette garde demandait « ce fichier est-il la ? ». Un telechargement interrompu — un 503,
# un delai depasse, une coupure — laisse un fichier de ZERO octet, qui existe. La tache
# est donc sautee a tous les passages suivants, et la machine garde une ressource morte
# pour toujours, en silence.
#
# CE QUE CA A COUTE : `infra-mail-01` a garde une cle smallstep de 0 octet apres une
# epreuve hors ligne. Treize machines portaient 1022 octets, elle portait le vide :
#
# E: Le depot http://packages.smallstep.com/... n'est pas signe.
#
# Le meme jour, l'index `dists/trixie` du cache du site etait tombe de 140 416 a 4 332
# octets — meme famille, autre endroit. Un telechargement partiel ne se signale pas : il
# se fait passer pour un succes.
#
# On demande donc « est-il ENTIER ? » — a defaut de pouvoir demander « est-il JUSTE ? »,
# ce qui exigerait une empreinte de reference que l'amont ne publie pas toujours.
- name: Cette ressource est-elle deja recuperee ? (Telecharger la cle de signature Gr)
ansible.builtin.stat:
path: "{{ serveur_grafana_depot_cle_fichier }}"
register: telecharger_la_cle_de_signature_grafana_present
# ... ET IL FAUT LE RETIRER, PAS SEULEMENT LE REDEMANDER (mesure du 2026-09-10).
#
# Laisser la garde autoriser la tache ne suffit PAS. `get_url` sur une destination qui
# existe emet une requete CONDITIONNELLE : l'amont repond « non modifie », le module rend
# `ok`, et le fichier corrompu reste en place. Mesure : la tache s'executait bien
# (`ok: [infra-mail-01]`), et la cle faisait toujours 0 octet au passage suivant. Un
# `force: yes` manuel l'a reparee en une seconde.
#
# On efface donc la ruine avant de reconstruire. `state: absent` ne mord que sur un
# fichier vide : une cle valide n'est jamais retiree, et la doctrine « on ne retelecharge
# pas ce qu'on possede deja » reste intacte.
- name: Retirer une ressource VIDE avant de la redemander
ansible.builtin.file:
path: "{{ serveur_grafana_depot_cle_fichier }}"
state: absent
when:
- telecharger_la_cle_de_signature_grafana_present.stat.exists
- (telecharger_la_cle_de_signature_grafana_present.stat.size | default(0)) == 0
- name: Telecharger la cle de signature Grafana
when: not telecharger_la_cle_de_signature_grafana_present.stat.exists
when: not (telecharger_la_cle_de_signature_grafana_present.stat.exists and telecharger_la_cle_de_signature_grafana_present.stat.size | default(0) > 0)
ansible.builtin.get_url:
url: "{{ serveur_grafana_depot_cle_url }}"
dest: "{{ serveur_grafana_depot_cle_fichier }}"

View file

@ -8,13 +8,31 @@
# CONSEQUENCE ASSUMEE : une rotation de cle amont n'est plus recuperee toute seule. Elle
# ne passe pas inapercue pour autant — `apt` refuse alors le depot, bruyamment. Pour
# forcer le rafraichissement : supprimer le fichier et rejouer le role.
# UN FICHIER VIDE EXISTE (mesure du 2026-09-10).
#
# Cette garde demandait « ce fichier est-il la ? ». Un telechargement interrompu — un 503,
# un delai depasse, une coupure — laisse un fichier de ZERO octet, qui existe. La tache
# est donc sautee a tous les passages suivants, et la machine garde une ressource morte
# pour toujours, en silence.
#
# CE QUE CA A COUTE : `infra-mail-01` a garde une cle smallstep de 0 octet apres une
# epreuve hors ligne. Treize machines portaient 1022 octets, elle portait le vide :
#
# E: Le depot http://packages.smallstep.com/... n'est pas signe.
#
# Le meme jour, l'index `dists/trixie` du cache du site etait tombe de 140 416 a 4 332
# octets — meme famille, autre endroit. Un telechargement partiel ne se signale pas : il
# se fait passer pour un succes.
#
# On demande donc « est-il ENTIER ? » — a defaut de pouvoir demander « est-il JUSTE ? »,
# ce qui exigerait une empreinte de reference que l'amont ne publie pas toujours.
- name: Cette ressource est-elle deja recuperee ? (Telecharger le trousseau de cles I)
ansible.builtin.stat:
path: "/tmp/icinga-archive-keyring.deb"
register: telecharger_le_trousseau_de_cles_icinga_present
- name: Telecharger le trousseau de cles Icinga
when: not telecharger_le_trousseau_de_cles_icinga_present.stat.exists
when: not (telecharger_le_trousseau_de_cles_icinga_present.stat.exists and telecharger_le_trousseau_de_cles_icinga_present.stat.size | default(0) > 0)
ansible.builtin.get_url:
url: "{{ serveur_icinga_keyring_url }}"
dest: "/tmp/icinga-archive-keyring.deb"

View file

@ -16,13 +16,50 @@
# CONSEQUENCE ASSUMEE : une rotation de cle amont n'est plus recuperee toute seule. Elle
# ne passe pas inapercue pour autant — `apt` refuse alors le depot, bruyamment. Pour
# forcer le rafraichissement : supprimer le fichier et rejouer le role.
# UN FICHIER VIDE EXISTE (mesure du 2026-09-10).
#
# Cette garde demandait « ce fichier est-il la ? ». Un telechargement interrompu — un 503,
# un delai depasse, une coupure — laisse un fichier de ZERO octet, qui existe. La tache
# est donc sautee a tous les passages suivants, et la machine garde une ressource morte
# pour toujours, en silence.
#
# CE QUE CA A COUTE : `infra-mail-01` a garde une cle smallstep de 0 octet apres une
# epreuve hors ligne. Treize machines portaient 1022 octets, elle portait le vide :
#
# E: Le depot http://packages.smallstep.com/... n'est pas signe.
#
# Le meme jour, l'index `dists/trixie` du cache du site etait tombe de 140 416 a 4 332
# octets — meme famille, autre endroit. Un telechargement partiel ne se signale pas : il
# se fait passer pour un succes.
#
# On demande donc « est-il ENTIER ? » — a defaut de pouvoir demander « est-il JUSTE ? »,
# ce qui exigerait une empreinte de reference que l'amont ne publie pas toujours.
- name: Cette ressource est-elle deja recuperee ? (Telecharger la cle de signature Gr)
ansible.builtin.stat:
path: "{{ serveur_loki_depot_cle_fichier }}"
register: telecharger_la_cle_de_signature_grafana_present
# ... ET IL FAUT LE RETIRER, PAS SEULEMENT LE REDEMANDER (mesure du 2026-09-10).
#
# Laisser la garde autoriser la tache ne suffit PAS. `get_url` sur une destination qui
# existe emet une requete CONDITIONNELLE : l'amont repond « non modifie », le module rend
# `ok`, et le fichier corrompu reste en place. Mesure : la tache s'executait bien
# (`ok: [infra-mail-01]`), et la cle faisait toujours 0 octet au passage suivant. Un
# `force: yes` manuel l'a reparee en une seconde.
#
# On efface donc la ruine avant de reconstruire. `state: absent` ne mord que sur un
# fichier vide : une cle valide n'est jamais retiree, et la doctrine « on ne retelecharge
# pas ce qu'on possede deja » reste intacte.
- name: Retirer une ressource VIDE avant de la redemander
ansible.builtin.file:
path: "{{ serveur_loki_depot_cle_fichier }}"
state: absent
when:
- telecharger_la_cle_de_signature_grafana_present.stat.exists
- (telecharger_la_cle_de_signature_grafana_present.stat.size | default(0)) == 0
- name: Telecharger la cle de signature Grafana
when: not telecharger_la_cle_de_signature_grafana_present.stat.exists
when: not (telecharger_la_cle_de_signature_grafana_present.stat.exists and telecharger_la_cle_de_signature_grafana_present.stat.size | default(0) > 0)
ansible.builtin.get_url:
url: "{{ serveur_loki_depot_cle_url }}"
dest: "{{ serveur_loki_depot_cle_fichier }}"

View file

@ -2209,6 +2209,29 @@ def preuve_sondes_declarees_et_deposees() -> tuple[bool, str]:
continue
taches = (roles / role / "tasks" / "main.yml")
corps = taches.read_text(encoding="utf-8") if taches.is_file() else ""
# UN ROLE DE GROUPE N'A PAS DE TACHES, ET C'EST LA STRUCTURE DU DEPOT (2026-09-10).
#
# `serveur_debian` et `serveur_durci` sont des roles de DECLARATION : ils portent
# `flux.yml`, `authentification.yml`, `supervision.yml` — et pas une seule tache.
# Le travail est fait par les roles que leur playbook de groupe APPLIQUE
# (`common_packages`, `hardening_packages`, `auditd`…).
#
# Cette preuve exigeait que la declaration et le depot vivent dans le MEME role.
# C'etait vrai des vingt-deux premieres sondes — toutes portees par des roles qui
# sont a la fois groupe et travail — et faux des qu'une sonde appartient au SOCLE.
# Elle refusait alors une declaration correcte, ce qui est le pire defaut d'une
# garde : forcer a contourner ce qu'elle protege.
#
# On suit donc le playbook du groupe : ce qu'il applique compte comme depose. La
# garde ne s'affaiblit pas — elle apprend ou le depot a le droit de vivre.
if not corps.strip():
playbook = RACINE / "playbooks" / "groupes" / f"{role}.yml"
if playbook.is_file():
for applique in re.findall(r"^\s+- ([a-z][a-z0-9_]*)\s*$",
playbook.read_text(encoding="utf-8"), re.M):
t = roles / applique / "tasks" / "main.yml"
if t.is_file():
corps += t.read_text(encoding="utf-8")
for s in sondes:
nom = s.get("nom")
if not nom:
@ -2228,6 +2251,8 @@ def preuve_sondes_declarees_et_deposees() -> tuple[bool, str]:
mesures.append(f"{role}/{nom}")
# L'AUTRE SENS : un script depose que rien ne declare.
# Les sondes declarees PARTOUT, y compris par un role de groupe qui delegue son depot.
_declarees_globalement = {m.split("/", 1)[1] for m in mesures}
for taches in sorted(roles.glob("*/tasks/main.yml")):
role = taches.parent.parent.name
corps = taches.read_text(encoding="utf-8")
@ -2237,7 +2262,7 @@ def preuve_sondes_declarees_et_deposees() -> tuple[bool, str]:
if meta.is_file():
data = yaml.safe_load(meta.read_text(encoding="utf-8")) or {}
declarees = {s.get("nom") for s in (data.get("sondes") or [])}
for orpheline in sorted(deposees - declarees):
for orpheline in sorted(deposees - declarees - _declarees_globalement):
fautes.append(f"{role}/{orpheline} : deposee mais absente de "
f"`meta/supervision.yml` — le compte d'API la refuserait")