
Le compte qui raisonne ne détient aucun secret : agents et autorité
Donnez un jeton à un agent, et une demande assez convaincante le lui fera utiliser. Une règle dans ses consignes ne l'en empêche pas ; un compte qui n'a tout simplement pas le jeton, si. Toute notre installation suit un principe : le compte qui raisonne ne détient aucun secret, et le compte qui détient les secrets ne raisonne pas. On ne peut pas embobiner un programme qui ne lit pas.
Ce que vous y gagnez. Des agents qui déploient, ouvrent des demandes de fusion et créent des dépôts toute la journée, sans aucun identifiant à leur portée. Toute action puissante passe par un petit service qui vérifie qui appelle et n'accepte qu'un nom tiré d'une liste fixe.
1. L'autorité derrière une socket
Un petit service, sous son propre compte, détient le jeton de la forge. Il écoute sur une socket Unix, et le noyau lui dit quel utilisateur appelle (SO_PEERCRED) : aucun mot de passe à voler, aucun jeton à fuiter. L'agent envoie « ouvre une demande de fusion depuis cette branche » ; le service vérifie la demande, puis agit. L'agent ne voit jamais le jeton.
2. Déployer sans mot de passe : une liste fermée
Notre démon de déploiement tourne en root et n'accepte qu'une chose : le nom d'un composant tiré d'une table écrite dans le démon lui-même. Ni chemin, ni branche, ni commande. Pour ce nom, il rafraîchit le miroir, lit le fichier sur main, le valide, sauvegarde la copie installée, installe, redémarre ce que la table indique, et répond une ligne de JSON.
Chaque règle a été payée :
- Le démon n'est pas dans sa propre table. Quand il y était, un seul commit fusionné pouvait réécrire ce que root avait le droit d'écrire, et ça a servi trois fois en une journée. Mettre le démon à jour demande désormais un humain et un mot de passe.
- Le miroir est rafraîchi avant chaque lecture, sinon un déploiement juste après une fusion installe la version précédente et répond « ok ».
- Un contenu invalide n'est jamais mis en place : Python est compilé, YAML lu et vérifié, avant toute écriture.
- Un fichier monté dans un conteneur est écrit sur place : un renommage lui donne un nouvel inode et le conteneur continue de lire l'ancien, sans bruit.
- Un client qui raccroche ne doit pas tuer l'écoute : c'est arrivé, et systemd a compté les échecs jusqu'à refuser de redémarrer.
3. Pas de dépôt sans sa protection
Créer un dépôt tient en un appel : créer, protéger la branche principale, pousser le premier commit, attendre la CI, étiqueter, publier la version. L'étape de protection est le même code pour tous les chemins. Si elle échoue, le dépôt neuf est supprimé ; si la suppression échoue aussi, l'erreur nomme le dépôt sans protection. Et le résultat de la CI est interrogé par le service, jamais affirmé par l'appelant : il n'existe nulle part d'argument « vert ».
4. Un bug devient du code, jamais une note
Une règle écrite dans un guide le matin a été enfreinte le soir par la personne qui l'avait écrite. Ce qui tient, c'est du code ordinaire : un verrou, une vérification, un refus. Ce qui ne peut pas devenir du code devient une entrée de journal qu'on peut chercher.
Pièges
Ne laissez pas l'outil qui fait respecter les limites être déployable par les limites qu'il fait respecter. Mettre à jour le garde-fou doit exiger un humain.
Les codes de sortie font partie du contrat : 0 fait, 1 un échec nommé, 2 un refus nommé, 3 et plus un problème technique, jamais un verdict.
Utile ? Dites-nous ce qu'il faut mesurer ensuite.