Sécuriser une application : comment faire disparaître les secrets d’une application avec Vault et Terraform

Créer, stocker ou transférer des secrets au sein d’une application a toujours été un challenge. Aujourd’hui encore les mauvaises pratiques subsistent, comme l’envoi de mail avec les credential, fragilisant la sécurité de vos applications. Vault, avec l’aide de Terraform permet de répondre aux problématiques de stockage et de diffusion des secrets. Nous verrons dans ce tutoriel comment Vault vous permet de sécuriser la gestion de vos mots de passe et secrets… en les faisant disparaître.

Le sujet étant vaste, il sera traité en trois articles visant à présenter les étapes d’intégration du Vault au sein d’une application. Chaque étape majeure constitue un article, et chacune d’entre elles se suivent :

  1. Migration de l’application web vers Vault avec des secrets statiques
  2. Migration du code applicatif vers Vault avec des secrets dynamiques (Secret as a Service)
  3. Encryption as a Service

Nous verrons ainsi comment implémenter chacune de ces étapes au sein d’une application, ce que cela change pour l’application, et les points de vigilance sur cette migration.

Pour commencer, nous allons présenter brièvement Vault et Terraform, puis l’environnement applicatif de démonstration, et ses problématiques de diffusion/stockage de secret.

Pré-requis

Si vous ne connaissez pas  Vault, je vous invite à essayer le tutoriel interactif sur le site officiel Vault afin de vous faire une première idée. Je vous conseille également de lire l’article concernant les méthodes d’authentification de Vault afin de mieux cerner et implémenter l’intégration de Vault au niveau applicatif.

Nous utiliserons également Docker et Docker compose afin de simplifier nos démonstrations.

Nous utiliserons ici Vault conjointement avec Terraform, les deux outils ayant une bonne intégration. Nous allons voir ce qu’apporte chacun d’entre eux, et pourquoi il est intéressant de les combiner.

Vault : gestion et protection des secrets

Vault est une solution de centralisation des secrets multi-environnement (cloud, hybride, intranet, etc) servant au stockage, à l’accès et à la distribution des secrets. Ceux-ci peuvent être de tous types : username/password, certificat, clé de chiffrement, etc.

L’interaction avec l’outil peut se faire via UI, CLI ou encore API REST permettant ainsi une intégration avec les outils devops ou applications. Vault se distingue, par rapport aux autres outils, par sa gestion du cycle de vie des secrets :

  • Les secrets statiques stockés sous la forme de key/value permettant de stocker n’importe quel forme de secret et que l’on retrouve dans la plupart des outils. L’intégration des secrets statiques est simple mais demande d’implémenter une méthode de rotation de mot de passe.
  • Les secrets dynamiques ou autrement appelés Secret as a Service. Dans ce cas, les secrets ont une durée de vie courte déterminée par le temps d’accès au service.

Prenons l’exemple d’une application souhaitant accéder à une base de données (database secret engine) :

L’application demande un accès à une base de données à Vault. Vault crée cet accès, puis fournit les credentials à l’application. Une fois reçus, l’application se connecte à la base de données pour interagir avec celle-ci. Puis l’application demande à Vault de révoquer les accès à la base de donnée où celui-ci supprime enfin les credentials. Chaque secret dynamique a une durée de vie par défaut, dans le cas où l’application ne contacte pas Vault pour révoquer les accès, alors celui-ci se chargera de les révoquer.

Ainsi, les secrets sont fournis à la demande avec une durée définie ce qui offre l’avantage de ne plus avoir de rotation de mot de passe. A chaque connexion, l’application utilise Vault comme intermédiaire.

  • Enfin, Vault est capable de faire de l’Encryption as a Service, pour chiffrer la data en transit ou au repos. Ceci permet  de réduire la complexité applicative liée à la gestion de clé de chiffrement (stockage, cycle de vie, échange de la clé, etc) mais aussi aux opérations de chiffrement/déchiffrement. Pour plus de détails sur Vault, vous pouvez également consulter cet article.

Terraform : planifier, créer, modifier et versionner l’infrastructure

Terraform permet  la planification, la création, la modification et le versioning d’infrastructure. Basé sur le langage HCL (Hashicorp Configuration Language), Terraform permet de transformer son infrastructure en Infrastructure as Code (IaC).

Dans notre contexte, Terraform aura un rôle un peu plus particulier que dans son usage habituel. Il jouera le rôle de facilitateur auprès de Vault aussi bien dans sa configuration que de son intégration côté application.

D’une part, il nous permettra de configurer le service Vault avec le provider Terraform dédié et d’intégrer les policies Vault de façon continue.

D’autre part, il nous permettra d’intégrer Vault de façon simple et sécurisée auprès des applications. Celui-ci aura le rôle d’une partie tierce de confiance que nous utiliserons avec la méthode Approle de Vault. Dans le cadre de cet article, nous ne pourrons pas intégrer pleinement la bonne pratique de cette méthode mais vous pouvez vous référer à la documentation de Vault.

Bien évidemment, l’ensemble de ces actions peuvent se faire sans Terraform, mais l’intégration de Terraform avec Vault nous simplifie grandement la tâche.

Mise en place de l’environnement

Maintenant que nous avons fait le tour des outils, nous allons mettre en place notre environnement à sécuriser avec Vault.

Nous allons prendre comme exemple un site web PHP sous apache utilisant une base de donnée MySQL (LAMP stack). Afin de simplifier les exemples mais aussi pour mieux nous concentrer sur l’usage de Vault, nous allons minimiser notre application.

Pour ce faire, nous aurons besoin de:

  • Vault en version 1.0.3
  • Terraform en version 0.11.10
  • Une application web PHP 7.2 et apache
  • Une base de donnée qui sera ici MySQL en version 5.7

Vous trouverez les sources permettant de reproduire cet environnement ici.

Regardons de plus près le docker-compose.yml qui représente ici notre application qui se découpe en trois services:

  • Vault: Le serveur Vault lancé en mode développeur.
  • Web: Notre application web PHP embarqué par le serveur web apache. Nous créons en amont notre image web, via le dockerfile, afin d’y installer mysqli qui permettra d’interagir avec la base de donnée. Enfin, le dossier web courant contiendra nos pages web: ici index.php uniquement.
  • Db: La base de donnée MySQL où nous avons:
    • 2 utilisateurs créé: root et dev.
    • Une base créée nommé: test

Enfin, pour terminer ce tour, mettons en place notre environnement avec la commande suivante:

$ docker-compose up

En accédant au site web via l’adresse suivante: http://127.0.0.1:8080, nous devrions nous retrouver avec la page suivante :

L’action faite par l’application web PHP est plutôt simple:

  1. L’application se connecte à la base de donnée test avec l’utilisateur dev.
  2. Supprime la table test si celle-ci existe.
  3. Crée la table test.
  4. Insère 3 valeurs dans la table: 1, 2 et 3.
  5. Récupère l’ensemble des valeurs dans la table test.
  6. Affiche le résultat des valeurs récupérées.

L’ensemble de ces actions sont définies dans le fichier web/index.php. Pour finir, nous avons aussi accès à l’interface graphique du Vault a l’adresse suivante: http://127.0.0.1:8200

Vous pouvez vous identifier avec le token suivant: root

Identifier les secrets non protégés au sein de l’application web

Comme nous avons pu le voir, l’environnement est assez simple. Notre objectif, dans un premier temps, est d’identifier les secrets au sein de l’application web qui peuvent être soumis à une divulgation. Ainsi nous ne prendrons pas en compte Vault ou la base de donnée MySQL qui sont lancés en mode “dev” afin de simplifier l’environnement.

Commençons donc à regarder notre service web au niveau docker-compose.yml:

L’ensemble des variables d’environnement ne contient aucune information sensible: nom de la base de donnée, de l’utilisateur et nom de l’host. Cependant, du côté du fichier web/index.php nous remarquons que la variable pass contient le mot de passe de l’utilisateur de la base de donnée:

Ici, l’utilisation d’un fichier de credentials ou encore la déclaration de la variable dans le docker-compose ne résout pas notre problématique car le secret sera toujours visible.

Dans le cas où l’application est hébergée sur un repository de type Git et utilisée par une pipeline CI/CD, il devient critique de masquer ce secret afin que les différents acteurs ne puissent en prendre connaissance. Ainsi, nous devons supprimer la visibilité de ce secret dans le code.

Migration de l’application web vers Vault avec des secrets statiques

Pour répondre à cette problématique, nous migrerons les secrets de l’application web dans Vault avec le Secret Engine k/v version 2.

L’utilisation de ce secret engine permet d’intégrer Vault au sein de l’application web sans impacter le code en lui même. C’est aussi la méthode la plus simple a implémenter.

Concernant l’authentification de l’application auprès du Vault, nous utiliserons Approle.

Vous trouverez les sources permettant de mettre en place cette solution sur l’environnement précédent ici.

Voici donc ce que nous allons ajouter au processus de déploiement de l’application web:

  1. Configuration du Vault, via Terraform (cf: dossier terraform), afin de mettre en place:
    • Le backend d’authentification: Approle
    • Configuration de l’Approle pour l’application web afin que celui-ci puisse s’authentifier auprès de Vault
    • Création et mise en place des policies applicatif en lecture seul sur le path suivant: secret/data/web
  2. Le passage en variable d’environnement du Role ID et du Secret ID via Docker. Pour simplifier la démonstration, nous n’utilisons pas de pipeline CI/CD, cette étape sera donc manuelle.
  3. Authentification et récupération des secrets auprès de Vault au démarrage de l’application. Ici, le script vault.sh effectura ces actions à l’entrypoint de notre container applicatif.

Les secrets seront en variables d’environnement, sans impacter le code en lui même.

Ce qui change

Premier changement, le secret n’est plus en clair dans le code, on le récupère au sein du code via des variables d’environnements :

Le second changement concerne la stack du docker-compose où nous avons retiré l’application pour la placer dans une stack isolée nommé app.yml, afin de pouvoir séparer les cycles de vie d’infrastructure et applicatif.

Par rapport à notre service web, deux changements notables:

  1. L’ajout d’un entrypoint pour l’execution du script vault.sh
  2. L’ajout de deux variables d’environnement liées à Vault VLT_ADDR (l’adresse de notre Vault) et VLT_PATH (le chemin de nos secrets applicatif). Dans notre cas, ces variables nous permettent de simplifier notre démonstration.

Enfin, nous avons ajouté le script vault.sh. Celui-ci sera joué à l’initialisation de notre container et effectuera les étapes suivantes:

  1. Vérification et récupération des variables attendues: Role_ID et Secret_ID
  2. Authentification au Vault avec la méthode Approle en utilisant le Role_ID et le Secret_ID. Celui-ci retournera un token Vault qui permettra à notre application de récupérer ses secrets.
  3. Récupération des secrets dans le path: secret/data/web
  4. Passage des variables d’environnements DB_USER et DB_PASSWORD
  5. Lancement de notre serveur Apache

Tester notre exemple

Maintenant que nous avons fait les modifications, il est temps pour nous de tester notre application.

Commençons par mettre en place notre 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 addresse: http://127.0.0.1:8200

Comme nous n’avons pas de pipeline CI/CD, certaines étapes sont manuelles. Commençons par stocker notre secret dans le Vault:

  1. Accéder au Vault via l’interface UI: http://127.0.0.1:8200
  2. Utiliser l’authentification Token avec le token suivant: root
  3. Sélectionner le secret engine ‘secret’ puis créer un secret avec comme path: web
  4. Insérer le secret comme dans la capture d’écran suivante et puis cliquer sur Save:

Notre secret est maintenant bien présent dans notre Vault. Il ne nous reste plus qu’à lancer notre application avec le bon Role_ID et Secret_ID:

  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 devrait être le même que l’étape précédente:

Nous avons bien réussi à intégrer Vault a notre application sans impacter le code applicatif.

Enfin, pour clean, 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 sur cette migration

  • Le code applicatif n’a pas changé (a l’exception de la suppression du secret)
  • Un script a été rajouté en entrypoint afin d’interagir avec le Vault pour récupérer les secrets et les stocker en variable d’environnement
  • Avant que l’application ne soit déployée, le Vault doit être configuré pour autoriser l’authentification Approle de l’application et que le secret de la base de donnée y soit stocké. Cette étape doit être automatisée côté Ops

L’intégration du Vault est transparente côté développeur, mais cette méthode ne résout pas la problématique de la rotation du secret, ou encore la connaissance du secret par les ops.

Dans notre prochain article, nous verrons comment résoudre ces problématiques par l’intermédiaire des secrets dynamiques (Secret as a Service).


Commentaires :

A lire également sur le sujet :