[TUTO/TEST] Sécuriser une infrastructure Ethereum avec HashiCorp Vault

Nicolas Law Yim Wan, développeur blockchain chez Futurs.io a testé HashiCorp Vault. Verdict !

Vous pouvez consulter le projet complet sur notre GitLab pour suivre le tutoriel.

 

Chez Futurs, nous travaillons sur différents Proofs of Concept utilisant la blockchain Ethereum et faisons face à une problématique récurrente sur chaque projet :

 

Comment faciliter l’accès aux applications Ethereum sans pénaliser l’expérience utilisateur ?

 

Avec cette question en tête, nous nous sommes tourné vers HashiCorp Vault et son plugin Vault-Ethereum permettant d’interagir avec n’importe quelle blockchain Ethereum. Mais avant tout, un peu de contexte !

À l’heure du Cloud et du Container as a Service, les applications modernes sont décomposées en composants conteneurisés n’ayant qu’une seule utilité, de sorte à ce qu’un service puisse être développé, testé, déployé et mis à jour de façon simple et automatisée. Une application web « classique » est généralement composée d’une base de données, d’une application front et d’une API REST permettant de faire transiter les données entre la base de données et le Front. Néanmoins, deux problèmes peuvent être rencontrés : où stocker les différents identifiants permettant la connexion d’un service à un autre, et comment faire communiquer ?

 

 

Ces solutions sont évidemment pratiques puisque notre application possède directement les identifiants nécessaires au bon fonctionnement de l’application, mais est-ce une bonne pratique ? SPOILER ALERT : non. Mais attendez, je vous entends au fond !

 

« On fait quoi alors ? »

 

Simple. Basique. On peut par exemple créer un fichier d’environnement .env contenant les variables nécessaires au développement en local. Cependant, il faut faire attention à ne pas versionner ce fichier grâce à .gitignore, sinon ça ne sert plus à rien. Enfin lors du déploiement, on change toutes les variables d’environnement présentes pour concorder avec la version de production :

<

/!\ A savoir : si vous utilisez docker-compose pour le déploiement, il n’est pas nécessaire de spécifier les adresses des différents services puisque le service DNS de docker-compose va exposer l’adresse d’un conteneur selon son hostname. Par exemple, dans le cadre du docker-compose suivant, notre API node sera exposée à l’adresse http://node:3000.

 

Pourquoi utiliser Vault ?

Afin de pallier les différents problèmes exposés précédemment, Vault et Consul vont pouvoir nous aider à éviter le Secret Sprawl. Comment ?

 

Vault est composé de quatres services principaux : storagesecretaudit et auth backend.

 

Pour restreindre l’accès à une ressource, il est possible de définir des règles d’accès (ACL) pour des utilisateurs ou groupes d’utilisateurs spécifiques. Par exemple, on peut définir la règle d’accès suivante sur le service secret à la valeur foo, permettant uniquement les actions read et update sur cette ressource:

path "secret/foo" {
capabilities = [ "read", "update" ]
}

Les actions possibles sur un secret sont listcreatereadupdate et delete

Mise en place de Consul et Vault avec docker-compose

 

Pour le bon déroulement de cette partie, il est nécessaire d’installer Docker. Ces opérations ont été testées sur Mac OS Mojave et Ubuntu 18.04

 

Ici, on va déployer deux conteneurs avec docker-compose, le conteneur Consul et le conteneur Vault . Par souci de simplicité, nous laissons en mode -dev Consul, et le chiffrement TLS n’est pas configuré. Il devrait cependant l’être en production !

 

La configuration de consul sera la suivante :

consul:
image: "consul"
hostname: "consul"
command: "agent -dev -client 0.0.0.0"
ports:
- "8400:8400"
- "8500:8500"
- "8600:53/udp"

Pour la configuration de Consul, pas de problème particulier puisque nous le lançons en mode de développement. Dans ce mode -dev, la persistence des données est désactivée et celles-ci sont stockées en mémoire.

 

La configuration de Vault sera cependant plus complexe. Pour la partie docker-compose :

vault:
build:
context: ./
dockerfile: Dockerfile
depends_on:
- "consul"
hostname: "vault"
cap_add:
- IPC_LOCK
ports:
- "8200:8200"
volumes:
- ./vault/config:/home/vault/etc/vault.d/config
- ./vault/logs:/home/vault/etc/vault.d/logs
- ./vault/data:/home/vault/etc/vault.d/data
- ./vault/tools/:/home/vault/tools
- ./vault/keys:/home/vault/keys
- ./vault/certs:/home/vault/certs
- ./vault/policies:/home/vault/policies
env_file:
- "vault.env"

 

Décortiquons ça ensemble :

build:
context: ./
dockerfile: Dockerfile
depends_on:
- "consul"
hostname: "vault"

Ici, on spécifie à docker-compose de créer l’image Vault à partir du fichier Dockerfile dans le dossier courant. On spécifie aussi que Vault dépend du service Consul, cela permet de créer un ordre de priorité de lancement des conteneurs.

cap_add:
- IPC_LOCK

 

La fonctionnalité IPC_LOCK permet de spécifier à Docker de verrouiller la plage mémoire utilisée par le conteneur, dans le but d’éviter qu’un processus malveillant puisse accéder aux données de Vault.

ports:
- "8200:8200"
volumes:
- ./vault/config:/home/vault/etc/vault.d/config
- ./vault/logs:/home/vault/etc/vault.d/logs
- ./vault/data:/home/vault/etc/vault.d/data
- ./vault/tools/:/home/vault/tools
- ./vault/keys:/home/vault/keys
- ./vault/certs:/home/vault/certs
- ./vault/policies:/home/vault/policies

Cette partie permet d’exposer le port 8200 de Vault pour permettre les requêtes depuis l’extérieur. Enfin, la partie volume permet de partager le répertoire courant dans lequel se trouve le docker-compose au conteneur Vault.

env_file:
- "vault.env"

 

On spécifie un fichier d’environnement contenant différentes variables nécessaires à l’installation, au déverrouillage et au lancement de Vault, notamment la configuration Vault.

 

Pour lancer les deux conteneurs, on fait appel à la commande docker-compose up -d —-build dans le dossier courant du fichier docker-compose.yml.

 

On spécifie l’option -d pour détacher l’affiche de Docker et le mettre en tâche de fond, et --build pour lancer la construction du Dockerfile. L’option --build permet de construire l’image Dockerfile avant de lancer le conteneur.

 

Si tout se passe bien, vous devriez avoir un affichage similaire à celui ci-dessous :

Creating network "docker-compose-vault-consul_default" with the default driver
Building vault
Step 1/15 : FROM ubuntu:latest
---> ea4c82dcd15a
[...]
Step 15/15 : CMD [ "bash", "tools/vault.sh" ]
---> Using cache
---> b5373025dccc
Successfully built b5373025dccc
Successfully tagged docker-compose-vault-consul_vault:latest
Creating docker-compose-vault-consul_consul_1 ... done
Creating docker-compose-vault-consul_vault_1  ... done

 

Enfin, on vérifie que Consul et Vault ont bien démarré avec la commande docker-compose ps. Vous devriez voir un affichage similaire :

Comment utiliser Vault ?

 

Si la partie précédente s’est correctement déroulée, vous devriez avoir de nombreux dossiers supplémentaires, notamment le dossier keys contenant les clés de déverrouillage de Vault ainsi que le token utilisateur root.

Ici, tout le processus de démarrage et de déverrouillage de Vault est automatisé via le script vault.sh mais en l’installant manuellement, Vault génère – par défaut – cinq clés de déverrouillages qui doivent être distribuées à cinq personnes différentes pour une question de sécurité. Lorsque Vault est verrouillé, plus aucune interaction n’est possible sur Vault et il est nécessaire de déverrouiller manuellement Vault, c’est-à-dire que trois personnes (au minimum) sur les cinq se connectent au serveur Vault pour rentrer leurs clé.

 

Dans un premier temps, on lance un terminal bash sur le conteneur Vault avec la commande docker-compose exec vault bash et on vérifie que Vault est bien déverrouillé avec la commande vault status. Si c’est le cas, la valeur de Sealedsera false.

vault@vault:~$ vault status
Key             Value
---             -----
Seal Type       shamir
Sealed          false
Total Shares    5
Threshold       3
Version         0.10.1
Cluster Name    vault-cluster-1b3f1b1a
Cluster ID      db269cb5-9244-c379-d06c-b04bc93d0ff1
HA Enabled      true
HA Cluster      https://127.0.0.1:8201
HA Mode         active

 

On peut aussi vérifier cela avec l’interface Consul UI, à l’adresse http://localhost:8500/ui :

Pour se connecter à l’utilisateur root, on récupère sa clé dans le dossier /home/vault/keys et on se connecte avec la commande vault login <la clé>:

vault@vault:~$ cat ~/keys/VAULT_TOKEN.txt 
ad9870cf-8cac-9b10-4bc6-f1f2e936ecbc
vault@vault:~$ vault login ad9870cf-8cac-9b10-4bc6-f1f2e936ecbc
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key                Value
---                -----
token              ad9870cf-8cac-9b10-4bc6-f1f2e936ecbc
token_accessor     eb5e08bc-f201-cc8b-8137-5f5ef70ac376
token_duration     ∞
token_renewable    false
token_policies     [root]

 

On utilise ici le login root, mais en production, il faudrait révoquer l’accès root et créer plusieurs utilisateurs administrateurs

 

Pour vérifier que le plugin Ethereum a bien été installé, on utilise la commande vault secrets list qui affichera les différentes routes gérant des secrets. Ici, notre plugin Ethereum est monté à la route ethereum/dev.

 

Un plugin Vault peut être installé plusieurs fois, tant que le chemin d’accès diffère. Par exemple, on pourrait monter le plugin vault-ethereum à ethereum/prod pour un environnement de production et à ethereum/dev pour du développement.

vault@vault:~$ vault secrets list
Path             Type         Description
----             ----         -----------
cubbyhole/       cubbyhole    per-token private secret storage
ethereum/dev/    plugin       Futurs Ethereum Wallet
identity/        identity     identity store
secret/          kv           key/value secret storage
sys/             system       system endpoints used for control, policy and 
debugging

 

On peut ensuite configurer notre plugin Ethereum pour communiquer avec un noeud Ethereum. On utilisera Infura pour pouvoir communiquer directement avec un noeud Ropsten. Pour cela, on utilise la commande

vault write ethereum/dev/config rpc_url=https://ropsten.infura.io/v3/<MY_INFURA_KEY> chain_id=3

en remplaçant évidemment <MY_INFURA_KEY> par la clé privée fournie par Infura.

vault@vault:~$ vault write -f ethereum/dev/config 
rpc_url=https://ropsten.infura.io/v3/b761725e6b22444ea709b8b48e7cf3e2 
chain_id=3
Key                Value
---                -----
api_key            n/a
bound_cidr_list    <nil>
chain_id           3
rpc_url            https://ropsten.infura.io/v3/MY_INFURA_KEY

 

Enfin, pour vérifier que la connexion est réussie, on peut par exemple lire un bloc de transactions, et vérifier les détails d’une transaction :

vault@vault:~$ vault read ethereum/dev/block/6733087
Key                  
Value
---                  -----
block                6733087
block_hash           
0x14ad5242ec27fa9925a645f98ca7b456cec7846caa165827ed275653b8974fe6
difficulty           2952113902580807
time                 1542624947
transaction_count    59

 

On peut aussi récupérer les hash de transactions du bloc :

Ici on affiche une seule transaction par souci de lisibilité, mais il y a en réalité 59 transactions dans ce bloc !

vault@vault:~$ vault read ethereum/dev/block/6733087/transactions
Key                                                                   
Value
---                                                                   
-----
0x07ef34b7c6f7cf0ccc94a10c56f7d53c951ec25ee66a0dbd07fcab749510d660
map[gas:341500 gas_price:16000340000 nonce:1 value:0 
address_to:0xeDB8791311AA4919487ba9037102a4904BD12a0f]

 

On peut lire les détails de la transaction correspondante :

vault@vault:~$ vault read 
ethereum/dev/transaction/0x07ef34b7c6f7cf0ccc94a10c56f7d53c951ec25ee66a0
dbd07fcab749510d660
Key                 Value
---                 -----
address_from        0x8f537E2F06E674018Eec46de7eAef1ACdd80DA27
address_to          0xeDB8791311AA4919487ba9037102a4904BD12a0f
gas                 341500
gas_price           16000340000
nonce               1
pending             false
receipt_status      1
transaction_hash    
0x07ef34b7c6f7cf0ccc94a10c56f7d53c951ec25ee66a0dbd07fcab749510d660
value               0

 

La fonctionnalité la plus important pour nous dans ce plugin est la possibilité de gérer les différents comptes Ethereum des utilisateurs sans leur imposer l’installation d’un plugin tel que MetaMask qui est un vrai frein pour l’adoption de solutions Blockchain au grand public.

 

Il faut évidemment s’assurer de définir des règles d’accès solides aux comptes Ethereum, de sorte à ce qu’un utilisateur A ne puisse pas accéder au compte de l’utilisateur B.

 

On peut ainsi créer un compte pour Bob et permettre les interactions basiques de la Blockchain Ethereum (transférer des Ether, signer des transactions…) tout en préservant la clé privée de son utilisateur puisqu’elle ne sortira jamais de l’enclaveVault.

vault@vault:~$ vault write -f ethereum/dev/accounts/bob
Key                     Value
---                     -----
address                 0x836eb4fdf935e56d843e355038ee133a75beed24
blacklist               <nil>
spending_limit_total    0
spending_limit_tx       0
total_spend             0
whitelist               <nil>

Le mot de la fin

 

Nous n’avons pas abordé toutes les fonctionnalités du plugin vault-ethereum mais nous vous conseillons évidemment d’aller voir sa documentation sur GitHub pour en savoir plus !

 

De notre côté, la prochaine étape consistera à déployer Consul et Vault en production avec Kubernetes pour l’utiliser dans nos Futurs projets !

Nicolas Law Yim Wan

N’hésitez pas à nous suivre sur notre sitesur Mediumsur Linkedin et sur Twitter pour retrouver toutes nos analyses !

Et aussi...
>Tous les articles