4 minutes
Stockage des secrets pour vos scripts dans Vault
Introduction
Le but de l’exercice était d’utiliser Vault pour stocker nos secrets qui sont habituellement dans des fichiers de configuration.
Je souhaitais également que le script puisse être lancer indépendamment depuis un serveur CICD (Jenkins dans notre cas) ou depuis mon laptop.
Pré-requis :
- Un outil de CICD ou un scheduler ou ce que vous voulez pour lancer des jobs (je ferai référence à Jenkins dans le reste de l’article).
- un script dans un langage quelconque (python ici).
- Vault fonctionnel avec un accès privilégié.
- Terraform (peut être fait via des commandes vault directement mais j’utilise Terraform pour populer Vault personnellement).
Configuration de Vault
Principe général
L’architecture cible de notre solution est de partir du principe que Vault est la seule source de vérité des secrets.
Le Jenkins sera considéré comme source fiable dans notre infrastructure et sera capable de générer des token pour d’autres applications.
Le Jenkins mettra ensuite à disposition le token précédemment généré pour récupérer les secrets nécessaires au bon fonctionnement du script.
Les policies
Nous avons besoin de plusieurs policies afin de rendre ce process possible :
- Une policy pour Jenkins pour lui permettre de récupérer des credentials pour le script.
- Une policy pour le script lui permettant de récupérer les secrets nécessaires.
Commençons par celle pour Jenkins :
path "auth/approle/role/python-grafana/role-id" {
capabilities = ["read"]
}
path "auth/approle/role/python-grafana/secret-id" {
capabilities = ["read","create","update"]
}
Cette dernière permet de récupérer le role-id qui est une chaine auto-générée et de générer des secret-id pour le role défini.
Ensuite pour la policy liée à notre approle :
path "secret/data/grafana" {
capabilities = ["read"]
}
Pour cette dernière, il suffit de lui donner les droits minimum nécessaire.
Dans mon cas il s’agit de lire les credentials pour accéder à Grafana.
Ce secret contiendra par exemple le nom d’utilisateur et le password admin qui permettront au script d’effectuer des actions sur Grafana.
L’authentification
Pour s’authentifier à Vault nous utiliserons un approle.
J’ai fait le choix d’utiliser un seul approle : celui pour mon script.
Par exemple mon script a pour but d’interagir avec Grafana j’ai donc quelque chose comme cela pour la configuration de mon approle :
resource "vault_auth_backend" "approle" {
type = "approle"
}
resource "vault_approle_auth_backend_role" "roles" {
backend = vault_auth_backend.approle.path
role_name = "python-grafana"
secret_id_ttl = 3600
token_num_uses = 10
token_ttl = 1200
token_max_ttl = 1800
secret_id_num_uses = 40
token_policies = ["python-grafana"]
}
Initialisation
Dans mon cas j’ai fait le choix de générer un token vault pour Jenkins et non d’utiliser un approle.
Cela me permet d’être plus flexible. Je peux facilement revoquer le token si nécessaire.
Pour cela : vault token create -policy jenkins
Avec cela Vault est normalement prêt.
Utilisation dans Jenkins
Pour ce qui est de Jenkins il faut maintenant enregistrer le token dans la partie credentials de jenkins.
J’utilise un simple user / password même si l’utilisateur n’est pas nécessaire (j’ai nommé ce dernier jenkins-vault).
Utilisation dans une pipeline
Définissons maintenant une pipeline afin d’utiliser ce que nous avons créer.
Mon script récupère les informations via variable d’environnement ce qui donne quelque chose comme cela :
pipeline {
stages {
stage("Running script") {
steps {
script {
VAULT_ADDR = vault.domain.tld
withCredentials([usernamePassword(credentialsId: "jenkins_vault", passwordVariable: 'VAULT_TOKEN',usernameVariable: 'user')]) {
withEnv(["VAULT_TOKEN=${VAULT_TOKEN}", "VAULT_ADDR=${VAULT_ADDR}"]) {
def ROLE_ID = sh(
returnStdout: true,
script: "vault read -field=role_id auth/approle/role/python-grafana/role-id"
)
def SECRET_ID = sh(
returnStdout: true,
script: "vault write -f -field=secret_id auth/approle/role/python-grafana/secret-id"
)
sh """
### HIDE OUTPUT
set +x
vault renew token
TOKEN=$(vault write -field=token auth/approle/login role_id=${ROLE_ID} secret_id=${SECRET_ID}")
export VAULT_TOKEN=${TOKEN}
## Init env var required by script or we could output in a file
export GRAF_USER=$(vault kv get -field=username secret/grafana)
export GRAF_PASSWORD=$(vault kv get -field=password secret/grafana)
## Run script with env var
python main.py
"""
}
}
}
}
}
}
}
Donc c’est assez complexe vu comme cela cependant cela permet d’avoir une pipeline relativement secure.
Il est possible de simplifier la chose en passant par les modules Jenkins qui peuvent extrapoler la partie génération du token pour l’application et ainsi diminuer la lourdeur de la pipeline.