L'un des antipatterns les plus répandus dans les pipelines CI/CD est le stockage de secrets directement dans les variables d'environnement GitLab ou, pire, directement dans le code. Vault de HashiCorp offre une solution robuste, centralisée et auditée pour gérer ces secrets.
Pourquoi Vault ?
GitLab CI permet de stocker des variables secrètes, mais cette approche atteint rapidement ses limites :
- Rotation difficile : changer un secret implique de modifier la variable dans chaque projet
- Pas de rotation automatique : les secrets ne changent jamais vraiment
- Auditabilité limitée : impossible de savoir qui a accédé à quel secret et quand
- Scope projet : les secrets ne peuvent pas être facilement partagés entre projets de façon contrôlée
Vault résout ces problèmes avec :
- Des secrets dynamiques (credentials générés à la demande avec une durée de vie courte)
- Un audit log complet
- Des policies granulaires (qui peut accéder à quoi)
- Une API REST utilisable depuis n'importe quel pipeline
Architecture de l'intégration
GitLab CI → JWT Auth → Vault → Secrets
GitLab CI supporte nativement l'authentification JWT avec Vault. Chaque job génère un JWT signé par GitLab, que Vault valide pour émettre un token avec les permissions appropriées.
Configuration Vault
1. Activer l'auth JWT
vault auth enable jwt
vault write auth/jwt/config \
jwks_url="https://gitlab.example.com/-/jwks" \
bound_issuer="https://gitlab.example.com"
2. Créer une policy
# policy: gitlab-project-secrets
path "secret/data/projects/{{identity.entity.aliases.auth_jwt_.metadata.project_id}}/*" {
capabilities = ["read"]
}
3. Créer un rôle JWT
vault write auth/jwt/role/gitlab-ci \
role_type="jwt" \
bound_claims_type="glob" \
bound_claims='{"project_path": "my-group/*"}' \
user_claim="project_id" \
policies="gitlab-project-secrets" \
ttl="1h"
Configuration GitLab CI
# .gitlab-ci.yml
variables:
VAULT_SERVER_URL: https://vault.example.com
VAULT_AUTH_ROLE: gitlab-ci
deploy:
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.com
secrets:
DATABASE_URL:
vault: projects/$CI_PROJECT_ID/database/url@secret
file: false
API_KEY:
vault: projects/$CI_PROJECT_ID/api/key@secret
file: false
script:
- echo "DATABASE_URL is available, never logged"
- ./deploy.sh
GitLab CI injecte automatiquement les secrets comme variables d'environnement, sans jamais les afficher dans les logs.
Configuration as-code avec Terraform
La configuration Vault est gérée en Terraform pour garantir la reproductibilité :
resource "vault_jwt_auth_backend_role" "gitlab_project" {
for_each = var.projects
backend = vault_jwt_auth_backend.gitlab.path
role_name = "project-${each.key}"
role_type = "jwt"
bound_claims = {
project_id = each.value.gitlab_project_id
}
token_policies = [vault_policy.project[each.key].name]
token_ttl = 3600
user_claim = "project_id"
}
Résultats obtenus
Après déploiement sur la plateforme :
- Zéro secret stocké en clair dans GitLab
- Rotation automatique des credentials de base de données (Vault dynamic secrets)
- Audit complet : chaque accès à un secret est loggé avec le contexte du pipeline
- Réduction de la surface d'attaque : les tokens Vault expirent après 1h
Conclusion
L'intégration Vault + GitLab CI via JWT est une bonne pratique pour la gestion des secrets dans les pipelines. La configuration initiale demande un investissement, mais le gain en sécurité et en auditabilité est immédiat. En gérant cette configuration avec Terraform, on bénéficie de la reproductibilité et de la traçabilité pour l'ensemble de la plateforme.