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
Configuration du serveur Vault
Afin de démarrer le serveur Vault voici la configuration de mon
docker compose
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
key-shares=2
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.

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

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

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

Maintenant nous allons créer un login qui sera insérer dans le code python pour que celle-ci communique avec le serveur Vault
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
Définissez la connexion vers le serveur Vault en recupérant le role id (login) et secret id généré auparavant.
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
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()path=user
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"
À partir de cette liste, on peut obtenir tous les secrets avec
read_secret()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
Créer ou mettre à jour un secret
Pour cela, il faut faire appelle à
create_or_update_secret()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
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()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
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
