Les Crash-Devs d'un Passionné

Intégrer un serveur vault dans une application

/Catégorie/python

Temps de lecture : 7 minutes

Dans un SAAS que je gère, je donne à mes utilisateurs la possibilité de stocker de manière sécurisée les mots de passes qu'ils me transmettent.

Comme je ne veux pas persister cette information en clair (évidement) je me suis tourné vers un serveur Vault.

Il s'agit d'un coffre-fort fournit par l'éditeur Hashicorp. Je vais le configurer pour que mon application python accède à ce coffre-fort en automatique. Tout cela au travers une librairie fournie par ce même éditeur

hvac

Configuration du serveur Vault

Afin de démarrer le serveur Vault voici la configuration de mon

 docker compose

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
vault:  
  image: vault:1.13.3  
  container_name: vault  
  ports:  
    - 8200:8200  
  environment:  
    - VAULT_ADDR=http://0.0.0.0:8200  
    - VAULT_API_ADDR=http://0.0.0.0:8200  
  cap_add:  
    - IPC_LOCK  
  volumes:  
    - vault-data:/vault/file  
    - ./config/server/vault/vault.json:/vault/config/vault.json  
  entrypoint: vault server -config=/vault/config/vault.json

Comme Vault propose une interface graphique et que je suis dans un container, il faut bien penser à exposer l'adresse du serveur dans VAULT_API_ADDR et VAULT_ADDR. Et également le port 8200.

L'entrypoint proposé correspond à la commande à utiliser en production. Si vous voulez vous affranchir des étapes à venir sur la configuration des clés, vous pouvez lancer la commande de dev qui chargera toute la configuration de votre serveur Vault en mémoire

vault server -dev -dev-listen-address=0.0.0.0:8200

Une fois le serveur Vault démarré, avec la commande de production, il vous est demandé de choisir :

  • key-threshold : le nombre de clés nécessaire
  • key-shares : le nombre total de clés générées

Par exemple, si je mets

key-threshold=3
et
key-shares=2
, lorsque je vais me connecter sur l'UI de Vault il va falloir que je donne 2 clés (sur les 3 générés). C'est une étape obligatoire qui permet de reconstruire la clé finale.

Vous avez compris qu'il est important de bien conserver ces clés, tout comme nous le faisons avec des mots de passe classiques.

Si je reviens sur la page de Vault, après avoir saisi le nombre de clés threshold et shares, il vous propose de télécharger ces informations. Dans le JSON récupéré, vous trouverez aussi un mot de passe root qui vous permettra de vous authentifier sur la mire de login disponible juste après avoir fourni les clés demandées.

vault ui prod

Maintenant que votre serveur Vault est fonctionnel et que vous pouvez vous connecter, passons à sa configuration.

Paramétrer Vault

Nous allons commencer par activer le moteur de secret de type

kv

secret_engine

Cela nous donnera la faculté d'enregistrer des données sous format de dictionnaire.

Pour accéder à ces données, il faut configurer une policy

policy_through_ui

Ainsi notre utilisateur aura accès à toutes les manipulations possibles de ses secrets.

Il est temps de choisir la méthode d'authentification pour manipuler ces secrets, et pour cela nous allons choisir le mode

AppRole

auth_method

Maintenant nous allons créer un login qui sera insérer dans le code python pour que celle-ci communique avec le serveur Vault

clipboard
Copier le code
1
2
3
4
5
vault write auth/approle/role/my-role
   token_type=service
   secret_id_ttl=0
   token_num_uses=40
   policies=sscraping-vault-policy

Pour obtenir ce login il faut lancer la commande

vault read auth/approle/role/my-role/role-id

Il faut bien garder le UUID généré, nous l'utiliserons par la suite.

Il nous reste plus à générer le mot de passe

vault write -f auth/approle/role/my-role/secret-id

Et bien conserver ce token, nous l'utiliserons plus tard.

Votre serveur Vault est maintenant configuré pour accepter la connexion avec votre application python. Regardons comment configurer votre application.

Configurer votre application

Tout repose sur la librairie

hvac
que fourni Hashicorp.

Définissez la connexion vers le serveur Vault en recupérant le role id (login) et secret id généré auparavant.

clipboard
Copier le code
1
2
3
4
5
6
7
8
9
client = hvac.Client(
    url="http://vault:8200",
    namespace=user_connected, # Isolation des secrets
)

client.auth.approle.login(
    role_id="343caa8a-660a-ef87-261e-6b9ef3332e27",
    secret_id=str(settings.VAULT_APPROLE_SECRET_ID),
)

Il est important de noter que le paramètre

namespace
est très important afin d'isoler les secrets des différents clients.

Nous allons voir maintenant les opérations CRUD que l'application peut réaliser sur les secrets du serveur Vault

Récupérer les secrets

La méthode

list_secrets()
permet de récupérer tous les secrets de l'utilisateur (car on précise
path=user
)

clipboard
Copier le code
1
2
3
4
list_response = client.secrets.kv.v2.list_secrets(
    path=user_connected,
    mount_point="kv",
)

Pour toutes les commandes, on précise

mount_point="kv"
car c'est le nom donné au moteur de secrets.

À partir de cette liste, on peut obtenir tous les secrets avec

read_secret()
et leurs versions. En effet, à chaque modification du secret, c'est bien une nouvelle version qui est créée afin de conserver la valeur n-1.

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
list_response = client.secrets.kv.v2.list_secrets(
    path=user_connected,
    mount_point="kv",
)
domains = {}
for domain in list_response["data"]["keys"]:
    try:
        secret_response = client.secrets.kv.v2.read_secret(
            path=f"{user_connected}/{domain}",
            mount_point="kv",
        )
        logger.error(secret_response["data"]["data"])
        domains[domain] = secret_response["data"]["data"].popitem()[0] # Dernière version du secret

J'ai mis à jour mon

path
afin qu'il reflète la hiérarchie du stockage de mes secrets par client.

Créer ou mettre à jour un secret

Pour cela, il faut faire appelle à

create_or_update_secret()

clipboard
Copier le code
1
2
3
4
5
client.secrets.kv.v2.create_or_update_secret(
    path=f"{user_connected}/{domain}",
    mount_point="kv",
    secret={user: pwd},
)

Le paramètre

secret
contient bien un dictionnaire portant les informations à faire persister dans le coffre-fort Vault.

Suppression d'un secret

Comme pour la lecture d'un secret il faut d'abord récupérer les secrets disponible pour cet utilisateur avec

read_secret_version()

clipboard
Copier le code
1
2
3
4
secret_version_response = client.secrets.kv.v2.read_secret_version(
    path=f"{user_connected}/{domain}",
    mount_point="kv",
)

Pour ensuite aller supprimer toutes les versions de ce secret

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
client.secrets.kv.v2.delete_secret_versions(
    path=f"{user_connected}/{domain}",
    mount_point="kv",
    versions=[
        version
        for version in range(
            1, secret_version_response["data"]["metadata"]["version"] + 1
        )
    ],
)

Et voilà tout est bien configuré, si vous suivez les différentes étapes vous arriverez au bout. Et vous devriez retrouver un résultat de ce type

kv_rigaudie_in_vault_ui