Comment les pipelines accèdent aux secrets sans les stocker
Vous avez un pipeline qui construit, teste et déploie votre application. À un moment donné, il a besoin d'un mot de passe de base de données, d'une clé API ou d'un certificat. L'instinct naturel est de placer ce secret dans une variable de pipeline, un fichier de configuration, ou pire, de le coder en dur dans un script. Mais cela crée un problème : dès qu'un secret entre dans votre pipeline, il peut se retrouver dans des endroits que vous n'aviez jamais prévus.
Les secrets fuient dans les journaux de build. Ils sont intégrés dans les images Docker. Ils apparaissent dans les fichiers d'artefact. Ils persistent dans les caches de l'espace de travail. Dès qu'un secret touche le code de votre pipeline, vous perdez le contrôle de sa destination.
La solution n'est pas d'empêcher complètement les secrets d'entrer dans le pipeline. Le pipeline en a besoin pour fonctionner. La solution est d'injecter les secrets de manière à ce qu'ils n'existent qu'en mémoire, uniquement pendant la durée de la tâche, et qu'ils ne persistent jamais nulle part. Il existe trois approches courantes, chacune avec ses propres compromis.
Variables d'environnement : simples mais sujettes aux fuites
L'approche la plus courante consiste à récupérer un secret depuis un coffre ou un gestionnaire de secrets et à le définir comme variable d'environnement dans le processus en cours d'exécution. Lorsque votre pipeline exécute npm test ou dotnet run, la variable DB_PASSWORD est déjà disponible dans la mémoire du processus.
Cette approche est simple et rapide. Elle fonctionne avec presque tous les outils et frameworks. Vous n'avez pas besoin de modifier la façon dont votre application lit la configuration. La plupart des langages et environnements d'exécution prennent en charge les variables d'environnement nativement.
Par exemple, en utilisant la CLI de Vault, vous pouvez récupérer un secret et l'exporter avant d'exécuter votre build :
# Récupère le mot de passe de la base de données depuis Vault et l'exporte comme variable d'environnement
export DB_PASSWORD=$(vault kv get -field=password secret/db-prod)
# Exécute la commande de build qui a besoin du secret
npm run build
Le problème est que les variables d'environnement sont faciles à fuiter. De nombreuses applications affichent toutes les variables d'environnement pendant le débogage. Les frameworks de journalisation écrivent souvent les variables d'environnement dans les fichiers de log par défaut. Une fois qu'un secret apparaît dans un journal, toute personne ayant accès au système de journalisation peut le lire. Cela inclut les développeurs, le personnel de support, et potentiellement des attaquants si le système de journalisation est compromis.
Il y a un autre risque : les artefacts de build. Lorsque votre pipeline crée un fichier JAR, une image Docker ou un binaire compilé, les variables d'environnement peuvent être capturées si le processus de build lit accidentellement toutes les variables. Une construction Docker qui utilise les instructions ARG ou ENV peut intégrer des secrets dans les couches de l'image. Une fois que le secret est dans l'image, il y reste pour toujours, sauf si vous reconstruisez et redéployez tout.
Les variables d'environnement fonctionnent bien pour les pipelines de courte durée où vous contrôlez étroitement la journalisation et le processus de build. Elles sont dangereuses dans les pipelines qui produisent des artefacts ou qui ont une journalisation verbeuse.
Fichiers montés : plus de contrôle, plus de nettoyage
La deuxième approche consiste à récupérer le secret depuis le coffre et à l'écrire dans un fichier temporaire à l'intérieur du conteneur ou de l'espace de travail. L'application lit ce fichier au démarrage. Une fois la tâche terminée, le fichier est supprimé.
Cette approche vous donne plus de contrôle. Vous pouvez définir des permissions sur le fichier pour que seul le processus de l'application puisse le lire. Vous pouvez monter le fichier en lecture seule. Vous pouvez le supprimer immédiatement après utilisation. De nombreux frameworks modernes prennent en charge la lecture de la configuration à partir de fichiers. Spring Boot lit depuis application.properties ou des fichiers YAML. .NET lit depuis des fichiers de configuration JSON. Vous pouvez pointer ces frameworks vers un fichier temporaire qui contient uniquement les secrets nécessaires pour cette exécution.
Le risque est que des fichiers peuvent être laissés derrière. Si votre pipeline ne nettoie pas l'espace de travail après la fin, le fichier secret reste sur le disque. Dans les environnements conteneurisés, un fichier monté peut être lu par d'autres processus dans le même conteneur si les permissions ne sont pas correctement définies. Si votre pipeline utilise la mise en cache, comme la mise en cache des couches Docker, le fichier secret peut être mis en cache et apparaître dans les builds suivants.
Les fichiers montés sont plus sûrs que les variables d'environnement car vous contrôlez la durée de vie et les permissions du fichier. Mais ils exigent de la discipline : vous devez nettoyer après chaque exécution et vous assurer que les mécanismes de mise en cache ne préservent pas le fichier.
Appels API directs : aucun secret dans le pipeline
La troisième approche consiste à ne jamais donner le secret au pipeline. Au lieu de cela, l'application ou le script appelle directement l'API du coffre chaque fois qu'il a besoin d'un secret. Le pipeline ne manipule pas le secret. Il ne définit pas de variables d'environnement. Il n'écrit pas de fichiers. L'application elle-même contacte le coffre et récupère ce dont elle a besoin.
Par exemple, au lieu de passer un mot de passe de base de données comme variable d'environnement, l'application appelle GET /v1/secret/db-password lorsqu'elle doit se connecter à la base de données. Le coffre authentifie la requête, renvoie le secret, et l'application l'utilise immédiatement.
Le diagramme de séquence suivant illustre ce flux :
C'est l'approche la plus sécurisée. Le secret n'existe jamais dans la mémoire du pipeline. Il n'est jamais écrit dans un fichier. Il n'apparaît jamais dans les journaux. Même si quelqu'un accède à l'espace de travail du pipeline, il ne peut pas trouver le secret car il n'y a jamais été.
Le compromis est la disponibilité. Votre application dépend désormais de l'accessibilité du coffre. Si le coffre tombe en panne ou devient inaccessible, votre application ne peut pas démarrer. Chaque appel API ajoute de la latence et laisse une trace dans le journal d'audit du coffre. Cette approche fonctionne mieux pour les secrets utilisés peu fréquemment ou pour les secrets dynamiques qui ont une courte durée de vie, comme les identifiants de base de données temporaires qui expirent après quelques minutes.
Les appels API directs sont idéaux pour les environnements de production où les exigences de sécurité sont élevées et où les équipes d'infrastructure peuvent garantir la disponibilité du coffre. Ils sont excessifs pour les pipelines de développement ou de test où le risque de fuite de secrets est plus faible.
Choisir la bonne approche
Il n'existe pas de méthode unique et parfaite. Le choix dépend de la fréquence d'utilisation du secret, du degré de contrôle d'accès nécessaire et de la fiabilité de votre infrastructure de coffre.
Pour les pipelines de développement rapides où les secrets changent rarement, les variables d'environnement sont acceptables tant que vous contrôlez la journalisation et la création d'artefacts. Pour les déploiements en production où les secrets ne doivent pas fuiter, les appels API directs ou les fichiers montés avec un nettoyage strict sont préférables. Pour les environnements conteneurisés, les fichiers montés avec des permissions en lecture seule et un nettoyage automatique après la sortie du conteneur fonctionnent bien.
L'important est de comprendre les points faibles de chaque approche et de concevoir votre pipeline pour combler ces lacunes. Ne choisissez pas simplement la méthode la plus facile. Choisissez la méthode qui correspond à votre tolérance au risque et à votre capacité opérationnelle.
Liste de contrôle pratique
Avant de décider comment votre pipeline accédera aux secrets, vérifiez ces points :
- Votre pipeline produit-il des artefacts (images Docker, fichiers JAR, binaires compilés) qui pourraient capturer des variables d'environnement ?
- Vos frameworks de journalisation affichent-ils les variables d'environnement par défaut ?
- Pouvez-vous définir des permissions sur les fichiers secrets montés dans votre environnement conteneurisé ?
- Votre pipeline nettoie-t-il les fichiers de l'espace de travail après chaque exécution ?
- Votre infrastructure de coffre est-elle suffisamment fiable pour les appels API directs depuis les applications ?
- Avez-vous besoin de pistes d'audit pour chaque accès aux secrets ?
Le vrai défi
Faire entrer les secrets dans le pipeline sans les stocker n'est que la moitié du problème. La partie la plus difficile est de s'assurer qu'ils ne fuient pas dans des endroits inattendus : journaux, artefacts, contrôle de version ou couches de build mises en cache. Chaque approche vous donne un moyen d'injecter des secrets, mais aucune ne prévient automatiquement les fuites. Cela nécessite une discipline continue, des vérifications automatisées et une compréhension claire de l'endroit où vos secrets peuvent aboutir.
L'objectif n'est pas de trouver une méthode parfaite. L'objectif est de choisir une méthode qui correspond à votre environnement, puis de mettre en place des garde-fous autour de ses points faibles.