Troisieme devis de service. Chaque expose: du plan repond-il, et si non, ou ca casse. - requete HTTPS complete avec la racine de l'AC, jamais un connect() : a travers l'OPNsense (anti-spoofing) toute connexion TCP reussit, et en TLS le silence apres connect() ne distingue pas un service sain d'un trou. - deux points de vue : depuis l'edge (edge + dorsal) et depuis le poste (DNS + frontiere + edge + dorsal). Leur difference diagnostique. - un code n'est pas un verdict : mon premier comparateur laissait passer un 502 des deux cotes. Trouve par le test negatif, pas par la relecture. Etat : les 6 expositions repondent des deux cotes. Test negatif (frontiere qui bloque + dorsal tombe) : 2 ecarts nommes distinctement, code 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6 KiB
Les devis de service : ce qui tourne correspond-il à ce qui est déclaré ?
Instrument ajouté le 2026-08-08, après une série de défauts qu'aucun test n'avait signalés. Lecture seule — il ne modifie rien.
make identite-plan # realm, fédération, mappeurs, politique, comptes
make certificats-plan # certificats sur disque contre certificats réellement servis
make expositions-plan # chaque `expose:` du plan répond-il, depuis l'edge et depuis le poste
Le trou qu'il comble
scripts/prouver.py porte 30 preuves. Elles sont toutes statiques : elles lisent le
dépôt. Zéro appel réseau, zéro SSH, zéro ansible. Elles établissent que le dépôt est
cohérent avec lui-même — que les handlers existent, que les intrants ont un
propriétaire, que rien n'est codé en dur.
Aucune ne demande au système déployé s'il ressemble à ce que le dépôt annonce.
Les quatre défauts du 2026-08-08 vivaient tous dans cet angle :
| Défaut | Le dépôt disait | La machine portait |
|---|---|---|
| politique de mot de passe | length(12)… |
aucune — abcd accepté |
entrée ppolicy figée |
pwdMustChange: TRUE |
FALSE |
| fédération en écriture | comptes écrits dans LDAP | syncRegistrations absent |
| prénom | givenName |
cn (le nom complet) |
Chacun a été trouvé en relisant après avoir écrit. Aucun n'a été signalé.
Ce qu'il vérifie
Le playbook playbooks/maintenance/devis-identite.yml relève deux dictionnaires — le
déclaré et le réel — et les dépose en JSON. scripts/devis_identite.py compare. La
séparation est délibérée : la comparaison en Jinja est illisible, et le dépôt a déjà cette
forme pour les devis réseau (Python raisonne, Ansible relève).
- realm — politique de mot de passe, anti-force-brute, « mot de passe oublié », relais SMTP
- fédération — mode d'écriture et les six clés de durcissement
- mappeurs — chaque attribut LDAP projeté
- annuaire — l'entrée
pwdPolicy, attribut par attribut - comptes — ceux qui n'ont pas d'adresse, pour qui « mot de passe oublié » ne mène nulle part
Le déclaré n'est jamais recopié ici : le playbook charge les défauts du rôle et appelle les résolveurs. Un devis qui redéclare ce qu'il vérifie ne vérifie rien.
Le devis des certificats
Il traque un défaut connu et récurrent : un certificat renouvelé sur disque mais toujours servi périmé depuis la mémoire du service, tant que nginx / postfix / dovecot / slapd / step-ca n'ont pas été rechargés.
Ce qu'il a fallu désapprendre pour l'écrire. Les certificats step-ca vivent 24 h et le
minuteur les renouvelle toutes les ~14 minutes. Une empreinte servie différente de celle
sur disque est donc l'état normal : comparer les empreintes aurait produit un
vérificateur qui crie en permanence, et qu'on aurait appris à ignorer. Le signal utile est
l'échéance de ce qui est réellement servi, plus l'absence de client_pki_reload_services
— car alors rien ne rafraîchira jamais la copie en mémoire.
Il a trouvé quelque chose à sa première exécution : sur infra-pki-01, l'autorité
elle-même, le certificat était expiré depuis plus de huit heures et le renouvellement
échouait toutes les quatorze minutes sur 'step ca renew' requires the '--ca-url' flag.
Rien ne le signalait.
Le devis des expositions
Il pose la question que se pose vraiment un utilisateur : est-ce que mes services répondent ? Et quand la réponse est non, il dit où ça casse.
Une vraie requête, jamais un connect(). À travers l'OPNsense (anti-spoofing), toute
connexion TCP réussit — y compris vers une adresse où aucune machine n'existe. Et « lire
des données après connexion » ne vaut rien en TLS, où c'est le client qui parle en
premier : le port 443 d'un edge sain se comporte exactement comme un port mort. Seul le
code de retour d'une requête HTTPS complète tranche.
Deux points de vue, et c'est leur différence qui diagnostique :
| Point de vue | Ce qu'il traverse |
|---|---|
| depuis l'edge | edge + dorsal |
| depuis le poste | DNS + frontière + edge + dorsal — le chemin d'un vrai utilisateur |
L'un répond et pas l'autre : ce n'est pas le service, c'est le chemin.
Un code n'est pas un verdict. Un 502 ou un 504 est une réponse, et pourtant le dorsal est mort derrière. Le premier jet ne testait que la présence d'un code et laissait donc passer un dorsal tombé — trouvé par le test négatif, pas par la relecture. Un 302 ou un 401, en revanche, est un service parfaitement vivant qui redirige vers l'IdP ou exige une authentification.
Un piège de construction, à connaître avant d'écrire le prochain
include_vars au niveau du play prime sur les group_vars. Charger les défauts d'un
rôle tels quels fait donc mentir le devis : il compare au défaut du dépôt au lieu de la
valeur réellement déclarée par l'inventaire. Le premier jet rapportait
client_pki_reload_services: [] sur les quatorze hôtes alors que quatre groupes le
déclarent — le devis portait exactement le défaut qu'il est censé traquer.
La forme correcte, reprise dans les deux devis :
- retenir ce que l'inventaire déclare, avant tout chargement de défauts ;
- charger les défauts du rôle (
include_vars, sansname:— les défauts se citent entre eux, et ces références ne se résolvent plus une fois enfermées dans un dictionnaire) ; - réimposer les valeurs de l'inventaire clé par clé, en boucle.
set_factn'accepte pas un dictionnaire entier passé en argument libre : il le prend sans erreur et n'en fait rien — c'est ainsi que le premier correctif a paru fonctionner tout en laissant le devis mentir.
Ce qu'ils ne font pas
Ils ne corrigent pas — c'est make deployer qui réconcilie. Ils répondent à l'autre
question, et sortent en code 1 s'il y a un écart.
Ils couvrent l'identité et les certificats. Le courriel, la base de données et les expositions web attendent le même traitement ; le patron est là pour être repris.