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

Futurs
Futurs.io
Published in
9 min readNov 21, 2018

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 !

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

À 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 ?

  • Une première solution à ces deux questions pourrait être d’indiquer directement dans le code de notre application toutes les informations nécessaires à la connexion de notre base de données :
  • Une autre solution consiste à passer directement ces informations dans les variables d’environnement d’un Dockerfile :

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 ?

  • Consul va agir comme un serveur de stockage chiffré pour les secrets de Vault, et permet de réaliser des health check des différents services qui y seront connectés, par exemple, savoir si Vault est verrouillé ou non, et le tout via une interface utilisateur.
  • Vault va exposer une API et une interface CLI, permettant de stocker des secrets (clés API, mots de passe…) et d’interagir avec différents plugins back-end, et dans notre cas, vault-ethereum.

Vault est composé de quatres services principaux : storage, secret, audit et auth backend.

  • Le service audit permet de logger chaque requête afin de tracer toutes les interactions avec Vault.
  • Le service storage (Consul dans notre cas) est l’endroit où seront stockées les données persistentes et chiffrées de Vault.
  • Le service secret permet de stocker des secrets statiques et de générer des secrets dynamiques (pour AWS, Azure ou GCP par exemple).
  • Le service auth backend gère plusieurs méthodes d’authentification permettant à Vault d’être adaptable à tout type d’utilisation. Par exemple, on peut utiliser la méthode AppRole pour authentifier des applications mais aussi GitHub pour un groupe de développeurs.

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 list, create, read, update 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/<MY_INFURA_KEY>
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 site, sur Medium, sur Linkedin et sur Twitter pour retrouver toutes nos analyses !

--

--

Futurs
Futurs.io

Agence d’innovation du Maltem Consulting Group. Make Tomorrow Great Again!