• Vault
  • GitLab
  • Sécurité
  • Secrets
  • DevSecOps

Vault & GitLab : gérer les secrets d'API en configuration as-code

Comment centraliser la gestion des secrets dans HashiCorp Vault et les intégrer proprement dans GitLab CI, sans jamais stocker de credentials en clair dans votre dépôt.

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.