Réduire sa dépendance code avec Vault Agent — partie 2 : intégration dans l’application
Nous avons vu dans l’article précédent ce qu’était Vault Agent et comment il pouvait nous aider à réduire la dépendance code. Dans ce second article nous allons voir comment intégrer Vault Agent dans l’application, comment lui permettre de s’authentifier et comment récupérer les secrets, avant de tester l’exemple donné.
Afin de rendre l’intégration la plus transparente possible pour notre application, notre intégration du Vault Agent doit se faire en 2 parties:
- Mettre en place la méthode d’authentification et valider la récupération du token Vault. Au besoin, renouveler celui-ci
- Récupérer les secrets ainsi que le templating de notre fichier contenant nos secrets. Au besoin, vérifier que nos secrets se renouvellent
Dans notre exemple, nous allons nous baser sur :
- Vault en version 1.6.2
- Terraform en version 0.14.6
- Une base de donnée qui sera ici MySQL en version 5.7
- Une application web PHP 7.2 et Apache
- Avec des secrets de type statiques (secret engine K/V) et dynamiques (secret engine Database)
- Une méthode d’authentification Approle sera utilisée pour authentifier l’application à Vault
Vous trouverez les sources permettant de reproduire cet environnement ici.
L’environnement se base sur des containers docker afin que vous puissiez reproduire l’exemple de votre côté.
Concernant la stack docker (docker-compose.yml, app.yml, etc), vous pouvez en retrouver les détails dans les précédents articles ou encore dans le readme du projet.
Intégrer le Vault Agent pour la méthode d’authentification
Commençons par installer Vault Agent.
Pour ce faire, il suffit de télécharger le binaire Vault ou de le packager avec l’application. Vous trouverez toutes les instructions d’installation dans la documentation officielle HashICorp.
Dans notre cas, nous ajoutons celui-ci dans notre dockerfile pour télécharger le Vault Agent au build de notre image.
Une fois l’installation faite, nous pouvons nous pencher sur l’authentification qui est de type Approle avec le Vault Agent.
Dans notre cas, dans un cadre de test, nous allons injecter directement le role_id et secret_id dans notre container via les variables d’environnements. Dans le cas d’un pipeline ou encore une CI/CD, l’implémentation de la méthode Approle de façon sécurisée est plus technique et mériterait un article entier sur le sujet. Vous pouvez retrouver un HashiCorp Learn sur le sujet AppRole With Terraform & Chef.
Pour que notre Vault Agent puisse s’authentifier en Auto-Auth nous devons lui fournir un fichier de configuration comprenant: la méthode d’authentification et le Sinks (où doit-il déposer le token Vault ?), actuellement n’ayant que pour seule possibilité un fichier.
Dans notre cas, nous aurons la configuration suivante :
Côté method Approle, nous devons:
- Préciser où se situe le role_id et secret_id. Dans notre cas, ces valeurs sont passées en variable d’environnement cependant Vault Agent n’est en mesure de lire ces valeurs que via un fichier. Il nous faudra donc, au niveau de l’entrypoint, créer ces fichiers avec les valeurs attendues.
- L’option remove_secret_id_file_after_reading permet de supprimer le secret_id après la lecture du fichier par le Vault Agent. Une méthode sécurisée afin de garantir que personne d’autre que le Vault Agent soit en capacité de s’authentifier à nouveau.
Côté sink, la configuration est plutôt simple:
- Le path sur lequel le Vault Agent ira écrire le token Vault.
- Le mode sur lequel le Vault Agent ira écrire les droits du fichier. Ici nous avons mis 644 pour que le serveur apache soit en capacité de lire le token. Nous pouvons aussi très bien mettre 600 afin de garantir que seul le Vault Agent est en capacité de lire et modifier celui-ci.
Enfin, les derniers éléments de notre configuration:
- L’adresse du Vault: nous passons la variable d’environnement VAULT_ADDR contenant l’adresse de notre Vault. Le Vault Agent est en capacité de se référer à celle-ci. Dans le cas inverse, il faudra renseigner l’adresse du Vault directement dans le fichier de configuration.
- pid_file: est le path sur lequel l’ID du process Vault Agent est écrit. Sachant que le Vault Agent fonctionnera en background, il peut être pertinent de se référer à ce fichier afin de kill le process si besoin.
Afin de finaliser notre intégration, nous devons rajouter un entrypoint à notre container afin de:
- Provisionner les fichiers role_id et secret_id au travers de nos variables d’environnement
- Démarrer le processus du Vault Agent en background
- Et enfin lancer notre serveur apache
Ce qui nous donne comme entrypoint :
A ce stade, le Vault Agent est en mesure de s’authentifier auprès de Vault et de renouveler le token Vault.
Intégrer le Vault Agent pour la récupération de secrets
Comme nous l’avons vu précédemment, le Vault Agent se base sur un fichier template afin de transformer celui-ci en fichier contenant nos secrets.
Pour notre template, nous allons récupérer notre secret dynamique de type database mais il est tout à fait possible de faire la même chose pour les autres secrets dynamiques ou statiques.
Voulant que les secrets soit stockés dans un format JSON, nous avons donc le template suivant :
Nous récupérons la valeur de VAULT_PATH, une variable d’environnement qui contient comme valeur database/creds/web, étant notre path afin de récupérer notre secret.
Vault Agent devrait, en se basant sur notre template, nous allons créer notre fichier de secret qui devrait ressembler à ceci :
Si vous souhaitez en savoir plus sur le langage de templating utilisé par Vault Agent, vous pouvez vous référer à la documentation de Consul Template.
Il nous reste plus qu’à indiquer à notre Vault Agent, dans son fichier de configuration, le fichier source template et le fichier de destination des secrets :
Le fichier de template des secrets ainsi que le fichier de configuration du Vault Agent doivent être copiés au sein du dockerfile.
Pour finir, il nous reste plus qu’à indiquer à notre application de lire le fichier de secret et de stocker les secrets en variables si ce n’est pas déjà le cas :
Tester notre exemple
Une fois que le Vault Agent est en capacité de s’authentifier et de récupérer ses secrets, il nous reste plus qu’à tester notre application. Le README du projet explique en détail chaque étape du test et un Makefile est à votre disposition.
Commençons par notre infrastructure :
$ docker run --rm -v $(pwd)/terraform:/app/ -w /app/ hashicorp/terraform:light init
$ docker-compose up
L’infrastructure étant opérationnelle, nous pouvons accéder à notre Vault via cette addresse: http://127.0.0.1:8200
Du côté de notre application :
$ docker-compose -f app.yml build
$ role_id=$(docker run --rm -v $(pwd)/terraform:/app/ -w /app/ hashicorp/terraform:light output -raw approle_role_id)
$ secret_id=$(docker run --rm -v $(pwd)/terraform:/app/ -w /app/ hashicorp/terraform:light output -raw approle_secret_id)
$ docker-compose -f app.yml run -e VLT_ROLE_ID=$role_id -e VLT_SECRET_ID=$secret_id --service-ports web
L’application est maintenant disponible sur l’adresse suivante: http://127.0.0.1:8080
La page suivante devrait apparaître :
Si nous rafraichissons la page, seule la valeur chiffrée via l’EaaS (Encryption as a Service) de Vault change et notre application ré-utilise bien notre secret de base de donnée :
Pour tester le bon fonctionnement de la rotation des secrets et du token Vault, nous pouvons révoquer les leases :
Et voir ainsi les logs côtés Vault Agent :
Comme nous pouvons le voir:
- Si le token Vault expire : le Vault Agent s’authentifie à nouveau
- Si les secrets expirent : le Vault Agent récupère les nouveaux secrets puis met à jour notre fichier de secret
Enfin, à des fins de nettoyage, n’oubliez pas d’exécuter les commandes suivantes :
$ docker-compose down
$ docker-compose -f app.yml down
$ rm terraform/terraform.tfstate
Ce qu’il faut retenir
- Si votre application consomme ses secrets au travers d’un fichier, alors l’utilisation du Vault Agent permet d’intégrer Vault de façon transparente. Sinon, si celui-ci utilise les variables d’environnement il existe une possibilité d’intégration avec envconsul. Pour ceux qui souhaitent tester cette méthode, vous pouvez consulter le repository GitHub qui se base sur notre exemple.
- Le Vault Agent s’occupe de la rotation de vos secrets (si ils sont dynamiques) et du token Vault.
- Le Vault Agent se sert d’un fichier template pour créer le fichier contenant nos secrets. Vous pouvez en apprendre plus sur la documentation de Consul Template.
- Nous ne modifions plus le code de notre application pour intégrer Vault a l’exception de l’EaaS (Encryption as a Service). L’application est en capacité de récupérer un token Vault qui est renouvelé par le Vault Agent pour l’EaaS.
- Dans notre exemple, nous utilisons un rôle dynamique pour le secret engine Database. Il est tout à fait possible d’utiliser des rôles statiques si l’on souhaite éviter de générer un grand nombre d’utilisateurs de base de donne a la volée.
- Nous avons modifié l’entrypoint de notre application pour y lancer le Vault Agent mais il est possible de le lancer au démarrage de notre container via systemd ou autre system/service manager afin d’éviter de toucher à l’entrypoint.
Comme nous l’avons vu dans cet article, nous avons retiré les dépendances entre notre code applicatif et HashiCorp Vault au travers du Vault Agent. Ceci apporte aussi une rotation automatique du token Vault et des secrets faite par le Vault Agent.
L’intégration du Vault au sein de notre application devient transparente et de même pour la gestion des secrets par les différentes équipes (dev, ops, etc).
Cependant, afin de rendre l’intégration du Vault transparente de bout en bout, il reste une question que nous n’avons pas abordée dans cet article : Comment rendre l’intégration de Vault transparente pour le déploiement d’une application via un pipeline ?
Ce point fera certainement l’objet d’un autre article !