Blog ·
Un vrai agent, une passerelle de test
Cinq manches se sont achevées sur la même réserve : non prouvé sur le fil. La lever a demandé un vrai agent, un mot de passe planté et deux mesures vides.
Par Provibr
Cinq manches de suite ont terminé leur compte rendu par une variante de la même phrase : le changement de protocole est additif, les tests unitaires sont au vert, et rien de tout cela n’a été exercé contre un vrai agent. Adaptateurs de supervision, enveloppe scellée, mises à jour automatiques de l’agent, une étape d’automatisation qui attend une commande. Les cinq étaient corrects autant qu’on pouvait le voir, et les cinq portaient une réserve dont héritait la manche suivante.
Cette réserve est peu coûteuse à écrire et elle s’accumule. Chaque manche en ajoute une, personne n’a tort, et au bout de cinq il reste une pile de comportements qui n’ont jamais existé ailleurs que dans un banc d’essai. Cette manche a servi à retirer la pile plutôt qu’à l’augmenter : pas de fonction, pas de changement de protocole, pas de publication. Quatre-vingt-deux mesures contre un vrai agent.
Le montage, en une phrase
Un vrai binaire d’agent, compilé pour la cible que nous livrons, signé avec une clé de développement, tournant dans un conteneur jetable sur une machine qui n’est le portable de personne, dialoguant avec une passerelle de test par un tunnel inverse, avec sept services jetables sur la boucle locale à mesurer et une seconde copie du même binaire inscrite comme sonde hébergée.
Tout dans cette phrase porte du poids. Le binaire est celui que nous livrons et non une compilation de test, car les défauts intéressants habitent la sérialisation et TLS, pas la logique. La passerelle est délibérément une instance de développement sur des ports différents, car celle de production écoute sur les ports évidents, et un agent qui frapperait du mauvais côté ferait un très mauvais après-midi. Les services sont des conteneurs jetables du vrai logiciel dans de vraies versions et non des imitations, car tout l’intérêt de l’exercice est que les imitations sont exactement ce que nous avions déjà.
Les services de base de données ont reçu un mot de passe canari, et ce choix a rendu possible la section suivante.
| Groupe | Mesures | Ce que cela tranche |
|---|---|---|
| Adaptateurs de supervision | 44 | Six protocoles de base de données et d’annuaire, identifiants justes et faux, et l’état de règle qui en découle |
| Mises à jour automatiques | 19 | Planifier, appliquer, redémarrer, et refuser une compilation altérée |
| Étape d’automatisation | 11 | Une commande qui se termine dans le délai, et une qui ne s’y termine pas |
| Sonde hébergée | 4 | Le même binaire inscrit comme sonde de plateforme et non comme agent client |
| Source absente | 4 | Un agent déconnecté produit son propre état, jamais une fausse panne |
Une recherche avec une valeur plantée
L’affirmation que nous voulions le plus éprouver est une négation : un secret de supervision voyage scellé et n’est jamais écrit dans la ligne de commande, le journal d’événements, le bus de messages ni l’un des deux journaux de processus.
Les négations sont les affirmations qui échouent en silence. Les mots de passe n’étaient donc pas des chaînes aléatoires dont l’absence ne prouve rien ; c’étaient des valeurs plantées, choisies pour qu’une seule occurrence quelque part soit indubitable. Sept couches ont ensuite été fouillées : les charges de commande stockées, la table d’événements, la configuration de contrôle stockée, les résultats stockés, le bus de messages en texte et sous sa forme encodée, le journal de la passerelle et celui de l’agent. Au total, environ un demi-million de caractères de matière.
Zéro occurrence, ce qui est la réponse souhaitée et aussi celle que donne une recherche cassée.
La même recherche a donc été lancée sur un fichier contenant à coup sûr le mot, et l’a trouvé. Ce témoin positif fait la différence entre « nous avons cherché et il n’y avait rien » et « nous n’avons pas cherché ». Il coûte une ligne et c’est la ligne la plus précieuse du script.
Deux autres exigences couvrent le même échec par l’autre bout : l’exécution doit observer au moins une douzaine de vraies commandes de contrôle et plusieurs dizaines de messages du bus avant d’avoir le droit de conclure quoi que ce soit. Les deux ont été au rouge à un moment de la manche, sur des passages qui auraient sinon annoncé un balayage propre sur absolument rien. Un balayage qui ne trouve aucune infraction dans un ensemble vide n’est pas une preuve, et il ressemble exactement à un succès.
Deux façons d’être au vert sans rien mesurer
Les deux se sont produites dans cette manche, dans des scripts écrits par quelqu’un qui connaît ce mode de défaillance et en parle régulièrement.
La première : une requête a sélectionné une colonne, puis le code a lu une autre propriété sur les lignes du résultat. Dans un langage typé c’est une erreur ; ici c’est undefined, pour toujours, en silence, et toute comparaison avec cette valeur échoue d’une manière qui ressemble à une assertion qui ne se déclenche jamais plutôt qu’à une assertion toujours vraie. Le contrôle a annoncé un succès parce qu’il n’avait rien à comparer.
La seconde : un test d’appartenance sur une liste vide. C’est une affirmation sur rien, elle se satisfait trivialement, et c’est l’état final naturel de tout script dont une étape antérieure a silencieusement produit zéro ligne. Les deux se couvrent à peu de frais : vérifier la forme de ce qui a été récupéré, et vérifier que l’ensemble à examiner n’est pas vide.
Cinq noms de colonnes dans ces scripts avaient été devinés plutôt que consultés. Ceux-là ont échoué bruyamment, ce qui est la variante bon marché de la même faute, mais c’est la même faute : une commande dit comment s’appelle une colonne, et la version où une supposition finit dans une liste que personne n’exécute est celle qui survit à la relecture.
Un agent qui refuse de s’inscrire deux fois
Au milieu des tests de mise à jour, l’agent a cessé de revenir. De l’extérieur cela ressemblait à une boucle de redémarrage, et la première heure est partie dans le chemin de mise à jour.
L’agent faisait exactement ce qu’il fallait. L’inscription écrit un fichier d’état, et le script d’installation généré vérifie ce fichier et refuse de s’inscrire à nouveau s’il est présent. Réinscrire un hôte déjà inscrit remplace une identité, et le faire par accident est la façon dont une machine perd son certificat et son coffre en une seule étape. Notre boucle de surveillance, écrite pour l’occasion, n’avait pas ce garde-fou, et chaque tentative de redémarrage heurtait le refus propre et justifié de l’agent.
La leçon ne porte pas sur ce garde-fou. Elle porte sur le fait qu’un refus justifié et un défaut ont de loin la même forme, et que ce qui les sépare est la lecture de ce que l’autre partie a réellement dit plutôt que de ce que le symptôme suggérait.
Ces mêmes journées ont livré trois autres surprises, toutes trois des décisions de conception rencontrant la réalité :
Les heures de mise à jour planifiées vivent sur une grille de cinq minutes, imposée par une contrainte de base de données. « Dans une minute », ce que veut quiconque écrit un test, n’existe pas. Ce qui rend cela praticable est une fenêtre de rattrapage : une heure manquée est encore exécutée pendant un quart d’heure. C’est aussi pourquoi une mesure répétée dans ce même quart d’heure doit choisir une heure de grille inutilisée, puisqu’une heure est réservée de façon unique par agent et qu’un second passage sur la même est refusé à juste titre.
Un contrôle TCP arrive avec TLS activé. Pointé sur un port en clair, il échoue avec une erreur TLS, ce qui est correct et se lit comme un contrôle cassé jusqu’à ce que l’on regarde ce qu’est une valeur par défaut.
Et la ligne d’audit d’une étape d’automatisation dont l’attente a expiré se fige sur l’état que le flux a vu. La commande est passée en dépassement de délai soixante millisecondes plus tard et la ligne a continué de dire ce qu’elle disait. Ce n’est pas un défaut de la ligne, c’est un défaut du commentaire au-dessus, qui promettait d’y lire l’issue finale. Cette promesse a été retirée et traitée correctement ailleurs.
Une notification qui regroupe, et un décompte qui dit zéro
La plus vive des petites trouvailles. Après une mise à jour réussie, la plateforme écrit une notification. La mesure a compté les notifications créées depuis le début de l’exécution, et a obtenu zéro.
Les notifications se regroupent. Une seconde occurrence de la même chose incrémente un compteur sur la ligne existante et laisse intacte sa date de création, ce qui est exactement souhaitable dans un panneau : « cet agent est tombé » avec un nombre à côté plutôt que quarante lignes. C’est aussi exactement ce qui fait qu’un décompte sur la date de création n’annonce rien alors que la notification est là, bien visible.
La mesure avait tort, le produit avait raison, et la seule raison pour laquelle nous savons dans quel sens c’était, c’est que quelqu’un a ouvert le panneau au lieu de croire la requête. Toute agrégation a une forme, et un test qui ignore cette forme éprouve sa propre hypothèse.
Ce que nous noterions
- « Non prouvé sur le fil » est une dette, et elle s’accumule. Une manche peut la porter. Cinq manches font une pile de comportements qui n’ont jamais existé hors du banc d’essai.
- Prouver une négation avec une valeur plantée et un témoin positif. Sans lui, zéro occurrence et une recherche cassée sont indiscernables.
- Donner un plancher à chaque balayage. Vérifier que l’entrée n’est pas vide avant d’en conclure quoi que ce soit.
- Un refus justifié ressemble de loin à un défaut. Lire ce qu’a dit l’autre partie avant de réécrire la sienne.
- Ne deviner ni un nom de colonne, ni une valeur par défaut, ni une grille. Les trois sont à une commande de distance, et la supposition qui atteint une liste est celle qui ne se déclenche jamais.
- Connaître la forme de ce que l’on compte. Les lignes regroupées ne portent pas la date de création sur laquelle on filtre.
Pour être clair : rien n’a été livré dans cette manche. Aucun changement de produit, aucun changement de protocole, aucune publication d’agent. Ce qui en sort, c’est que cinq réserves disparaissent et qu’une poignée de petits défauts sont corrigés, et le nettoyage a été recompté plutôt que supposé : aucune ligne restante, aucun conteneur restant, aucun fichier restant.
Certaines choses sont restées non prouvées. Le chemin d’injection de l’automatisation a été exercé par son cœur et non par le panneau, les adaptateurs n’ont pas tourné depuis une sonde hébergée, et il n’y a toujours pas de mesure contre un vrai hyperviseur.
Issu du travail de plateforme sur Provibr. Pourquoi le secret de supervision est scellé à chaque tentative est expliqué dans un secret qui ne voyage qu’une fois, et comment une mise à jour signée de l’agent refuse une compilation altérée dans quand une mise à jour signée se refuse.