01 / Mes articles / Publié
J'ai coupé mon assistant en deux
J'ai séparé mon agent personnel en deux profils. J'ai trouvé six pannes, dont cinq silencieuses.
Rédigé le
J'ai installé un agent personnel sur un serveur, avec l'idée classique : un assistant qui lit mes mails, suit mon agenda, surveille mes entraînements et fait ma veille technique. Un seul agent, tous mes accès, accessible depuis mon téléphone.
J'ai changé d'avis avant la fin de l'installation. Voici pourquoi, et surtout ce que j'ai découvert ensuite, parce que la partie intéressante n'est pas la conception. C'est ce qui a cassé.
Trois ingrédients qu'il ne faut pas mélanger
Un agent devient dangereux quand trois choses se rejoignent chez lui : l'accès à des données privées, l'exposition à du contenu que je n'écris pas, et un moyen de sortir vers l'extérieur.
Pris séparément, chacun est banal. Ensemble, ils forment une chaîne complète. Une page web ou un mail peut contenir une consigne déguisée. L'agent la lit comme du texte à traiter, mais rien ne distingue au fond une donnée d'une instruction dans ce que reçoit un modèle. S'il dispose en même temps de mes mails et d'un moyen d'émettre, la consigne cachée devient une action.
C'est pour ça que j'ai arrêté de chercher la bonne formulation de consigne défensive. Une règle écrite dans un fichier de configuration reste une suggestion. Ce que je voulais, c'était une séparation que l'agent ne puisse pas franchir même s'il le décidait.
Des comptes séparés, un sens de circulation
J'ai donc deux profils, qui tournent sous deux comptes Unix distincts sur la même machine.
Le premier a mes accès personnels : mails, agenda, notes, données sportives. Il n'a aucun accès au web. Le second fait la veille : il lit des dépôts, des bulletins de sécurité, des publications économiques. Il n'a accès à rien qui me concerne, et il ne peut ni envoyer ni publier.
Chacun a son propre bot de messagerie et sa propre clé d'API. Si je dois révoquer le profil exposé au web, je ne coupe pas celui qui lit mes mails.
Entre les deux, un répertoire d'échange, dans un seul sens. Le profil de veille dépose des fichiers datés. Le profil personnel les lit comme des données, jamais comme des instructions, et cette phrase est écrite dans sa configuration en toutes lettres. Ce n'est pas une garantie, c'est une couche de plus. Le profil de veille n'a aucune clé permettant d'atteindre quoi que ce soit chez moi.
J'ai ajouté un troisième compte, qui ne fait tourner aucun agent. Il lit la configuration des profils et la pousse toutes les heures dans un dépôt Git privé. Les agents n'ont ni Git, ni clé de dépôt : ce serait leur donner un canal de sortie. Ce compte existe pour une seule raison : un agent qui réécrit ses propres instructions doit laisser une trace que je peux relire.
Les pannes
La conception ci-dessus a tenu. Ce qui n'a pas tenu, c'est tout le reste. Je les donne dans l'ordre où je les ai trouvées, parce que l'ordre raconte quelque chose.
J'ai écrit des services système dont l'outil n'avait pas besoin. Mes unités pointaient sur un chemin de binaire faux et sur une sous-commande qui n'existe pas. Elles ont redémarré en boucle 6 000 fois pendant que le service livré avec l'outil tournait très bien à côté. Leçon : avant d'écrire une unité, je vérifie si le logiciel en installe déjà une.
Un masque ACL a coupé ma surveillance sans un mot. J'avais donné au compte
d'audit un droit de lecture par ACL. L'agent remet périodiquement les permissions
de son dossier de configuration, et un simple changement de permissions
recalcule le masque de l'ACL et le met à zéro. L'entrée restait visible dans
getfacl, mais devenue sans effet. La surveillance a échoué toutes les heures
pendant deux jours. Je ne l'ai vu qu'en constatant l'absence de nouveaux commits.
Les sorties des tâches planifiées partaient dans le vide. Sur un serveur, la sortie d'une tâche cron part dans un courrier local que personne ne lit. Une tâche qui échoue toutes les heures est donc parfaitement silencieuse. J'ai redirigé les sorties dans un fichier de journal et j'ai donné à chaque tâche une destination en cas d'échec, vers ma messagerie.
Une boucle de surveillance se réveillait 288 fois par jour pour une livraison réelle. Elle sondait un répertoire toutes les cinq minutes. Il existait pourtant une unité de déclenchement au dépôt de fichier, écrite mais désactivée, avec la même erreur de chemin que la mienne. Elle surveillait en plus la condition « répertoire non vide » sur un répertoire contenant un sous-répertoire d'archives, donc jamais vide. Corrigée en surveillance de changement, elle se déclenche une fois par dépôt.
L'authentification par mot de passe SSH était toujours active. Le fichier de configuration principal disait bien non. Mais les images serveur déposent un fichier annexe qui réactive l'option, et le démon retient la première valeur lue, donc celle du fichier au numéro le plus bas. J'avais modifié le mauvais fichier et vérifié en relisant ce que je venais d'écrire, au lieu d'interroger la configuration effective.
La sauvegarde ne contenait pas les planifications. Elle tournait sous le compte d'audit, qui n'a pas le droit de lire le dossier des tâches. Une restauration m'aurait rendu un agent complet et sans aucune de ses tâches. En la passant sous le compte administrateur, j'ai aussi découvert qu'elle emportait deux gigaoctets d'environnement d'exécution réinstallable. Après ajustement des exclusions : 85 mégaoctets, planifications comprises.
Ce que je retiens
Sur six pannes, cinq étaient silencieuses. Aucune n'aurait produit d'erreur visible avant des semaines. C'est la vraie difficulté de ce genre de montage : un agent qui répond correctement quand je lui parle peut très bien avoir cessé de faire tout le reste.
Je vérifie donc maintenant les effets, pas les intentions. Je ne relis pas un fichier de configuration pour savoir si une option est active, j'interroge le service. Je ne regarde pas si une tâche existe, je regarde si elle a produit quelque chose. Je ne suppose pas qu'un droit est accordé parce qu'il est écrit, j'essaie de lire le fichier avec le compte concerné.
Et je donne à chaque chose automatique un moyen de crier. Une sauvegarde qui échoue doit me le dire. Une surveillance qui ne surveille plus doit me le dire. C'est moins élégant qu'une architecture propre, et c'est ce qui m'a fait gagner le plus de temps.
Ce que je n'ai pas encore réglé
Le répertoire d'échange reste un pont entre les deux profils. Un fichier déposé par l'agent de veille finit lu par celui qui détient mes accès. Sa configuration lui dit de traiter ces fichiers comme des données. Je préférerais une barrière technique à une consigne.
Au moment de ces essais, je n'avais pas non plus de mesure du coût. Le tarif de mon modèle n'était pas connu de l'outil, donc le journal quotidien ne pouvait pas dire ce que la journée avait coûté. Pour une machine censée être auditable rapidement, c'était une lacune.
Périmètre de la mesure
2 profils. Profils de mon agent personnel, chacun sous un compte distinct.
6 pannes. Pannes constatées après la séparation de mon agent personnel.
5 pannes silencieuses. Sous-ensemble des pannes constatées sans erreur visible dans l'usage courant de mon agent.
6 000 redémarrages. Boucle des unités ajoutées par erreur, pendant que le service fourni fonctionnait.
2 jours. Durée pendant laquelle la surveillance échouait à chaque passage.
1 heure. Intervalle prévu entre les sauvegardes de configuration par le compte d'audit.
288 réveils par jour. Boucle de surveillance avant remplacement du sondage par un déclenchement au changement.
5 minutes. Intervalle du sondage du répertoire avant sa correction.
2 Go. Environnement d'exécution réinstallable inclus à tort dans la sauvegarde.
85 Mo. Taille de la sauvegarde après exclusions, planifications comprises.