Sécuriser une application : comment faire disparaître les secrets applicatifs avec Vault et Terraform – partie 2

Temps de lecture : 6 minutes

Nous avons vu dans un premier article comment migrer une application web vers Vault avec des secrets statiques, et ce sans changer le code ou le fonctionnement applicatif. Cependant, la problématique de la rotation des secrets subsiste toujours et pour répondre à celle-ci nous allons voir ensemble la carte maîtresse du Vault, à savoir la gestion des secrets dynamiques.

Migration du code applicatif vers Vault avec des secrets dynamiques (Secret as a Service)

Il faut savoir, dans un premier temps, que l’utilisation des secrets dynamiques n’est pas faisable dans 100% des cas et dépend de deux critères :

  1. Est-ce que le secret peut être migré dans un Secret Engine Vault existant ? Il convient donc de vérifier la liste des possibilités proposées par Vault.
  2. Est-ce que l’application peut prendre en considération un secret dont la durée de vie est limitée dans le temps sans pour autant redéployer l’application ?

Dans notre cas, le secret est un secret de type database et Vault offre la possibilité de faire des Secrets as a Service pour une base de données. D’autre part, nous allons modifier l’application afin de pouvoir utiliser ce secret Engine.

Nous allons donc modifier le workflow applicatif afin que notre application fonctionne ainsi :

Comme vous avez pu le remarquer, le schéma et la logique sont ceux précédemment étudiés lors de notre introduction à Vault.

Vous trouverez ici les sources permettant de mettre en place cette solution (en s’appuyant sur sur l’environnement précédemment monté) .

Quel sont les changement apportés ?

Pour commencer par le plus simple, le script vault.sh et l’entrypoint du container applicatif sont supprimés. Puisque nous passons du mode “secret statique” à “secret dynamique” et que les secrets dynamiques ont une durée limitée dans le temps, l’entrypoint ne fait plus sens car il ne s’exécute qu’au démarrage.

Les changements se font donc à deux niveaux : Terraform et applicatif.

Commençons par Terraform (la partie Ops). Au niveau de notre terraform/main.tf quelques changement sont requis :

  1. Ajout d’un nouveau Secret Engine de type Database
  2. Création d’une connexion MySQL avec le Secret Engine
  3. Création d’un rôle qui définit un utilisateur de base de données équivalent à celui utilisé dans les étapes précédentes.

De l’autre côté, au niveau applicatif, nous avons ajouté l’équivalent du script vault.sh dans le code PHP:

  1. Récupération du Role_ID et Secret_ID via les variables d’environnement
  2. Authentification au Vault via Approle et récupération du token Vault
  3. Récupération du username et mot de passe via le token Vault

Dans notre code, nous n’utilisons pas de SDK PHP car notre objectif ici est de comparer l’implémentation du script vault.sh utilisé avec des secrets statiques (étape précédente) à notre code. Sachant que le script vault.sh utilise curl, nous utilisons donc cirm dans notre code PHP.

Bien évidemment, l’usage du SDK Vault PHP est fortement recommandé afin de simplifier le développement.

Tester notre exemple

Testons donc notre nouvelle méthode en commençant par l’infrastructure:

  1. Initialisation du dossier terraform afin de récupérer les bons providers:
    $ docker run –rm -v $(pwd)/terraform:/app/ -w /app/ hashicorp/terraform:light init
  2. Déploiement de notre infrastructure:
    $ docker-compose up

L’infrastructure étant opérationnelle, nous pouvons accéder à notre Vault via cette adresse: http://127.0.0.1:8200

Et nous pouvons voir justement que notre Secret Engine Database est bien présent:

Passons maintenant à l’application. Dans la même logique que dans l’exemple précédent, nous nous appuierons sur les mêmes commandes et méthodes que celles utilisées lors de l’exemple précédent :

  1. $ role_id=$(docker run –rm -v $(pwd)/terraform:/app/ -w /app/ hashicorp/terraform:light output approle_role_id)
  2. $ secret_id=$(docker run –rm -v $(pwd)/terraform:/app/ -w /app/ hashicorp/terraform:light output approle_secret_id)
  3. $ docker-compose -f app.yml run -e VLT_ROLE_ID=$role_id -e VLT_SECRET_ID=$secret_id –service-ports web

Notre application est de nouveau disponible sur l’adresse http://127.0.0.1:8080 et le résultat sera le même que l’étape précédente, à la différence que celui-ci nous affiche l’utilisateur base de données utilisé lors de la connexion:

Nous voyons que l’utilisateur de base de données change à chaque rafraîchissement de page :

Notre intégration des secrets dynamique est bien fonctionnelle. Cependant, nous avons dû modifier le workflow applicatif en ajoutant du code pour que l’application puisse récupérer ses secrets de façon dynamique.

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 de cette migration

  • L’intégration des secrets dynamiques demande une modification du workflow applicatif et donc du code en lui même. En effet, l’application doit être “consciente” que le secret a une durée de vie définie dans le temps, et que celui-ci peut être amené à changer. Cette problématique n’est pas liée à Vault, mais à la durée de vie du secret.
  • Notre application n’utilise plus de secrets statiques et l’entrypoint de la précédente intégration n’a plus lieu d’être dans notre exemple. Nous l’avons supprimé mais si notre application utilisait d’autres secrets statiques, nous aurions du le conserver.
  • Le champ d’action de l’Ops s’élargit et ne se limite plus au Vault mais à l’ensemble de l’infrastructure. Il ne s’agit plus de stocker un simple secret dans le Vault mais d’y configurer une connexion entre le Vault et les bases de données. Attention donc à la sécurisation des flux réseau (ACL, Firewall, etc) entre Vault et les bases de données !
  • Grâce à la logique de secret dynamique, chaque secret a une durée de vie limitée plus ou moins courte. La notion de rotation de mot de passe n’existe plus. Le secret est délivré à l’application le temps d’interagir avec le service en question (ex: database): c’est la philosophie du Secret as a Service.

Points de vigilance

L’application ne révoque pas forcément le secret (le cas de notre démonstration). Il faudra donc que Vault le fasse au bout d’une certaine durée. La complexité est donc de définir la durée de vie du secret. Elle ne doit être ni trop courte ni trop longue. Il convient donc d’identifier en amont le bon compromis à adopter.

Il faut garder aussi en mémoire qu’un nombre important de créations de secrets peut surcharger un service. Prenons le cas d’une base de données : si pour chaque connexion à notre site web nous créons un utilisateur, nous pouvons vite nous retrouver avec des millions d’utilisateurs de base de données différents chaque minute, dans le cas d’un site à fort trafic.

La solution est donc de mettre un TTL et un max TTL en fonction des besoins réels de l’application. Le TTL est la durée standard du secret et le max TTL la durée que le secret ne devrait pas dépasser quoi qu’il arrive. Par exemple, nous fixons un TTL de 1h et un max TTL de 24h. Si l’application estime que l’accès au service sera anormalement long, celle-ci peut demander un renew de son secret (prolonger la durée de son secret) sans pour autant dépasser le max TTL (ex: Renew son secret d’une heure). La complexité est de trouver un bon compromis (cf: https://www.vaultproject.io/docs/concepts/lease.html).

Dans notre troisième et dernier article, nous verrons comment renforcer la sécurité de vos applications avec l’Encryption as a Service.

Commentaires :

A lire également sur le sujet :