Aller au contenu
CI/CD & Automatisation medium

GitLab CI : gérer les secrets sans les exposer

11 min de lecture

Un pipeline GitLab CI qui déploie en production manipule forcément des secrets : tokens d’API, clés SSH, credentials de registre Docker, mots de passe de base de données. GitLab propose quatre mécanismes pour protéger ces valeurs, mais aucun n’est activé par défaut. Ce guide vous montre comment configurer chaque couche de protection, éviter les pièges classiques et intégrer un gestionnaire de secrets externe quand les variables natives ne suffisent plus.

  • Distinguer les 4 types de variables CI/CD (standard, protégée, masquée, fichier) et leurs limites
  • Configurer des secrets protégés + masqués pour empêcher l’exposition dans les logs et les branches non protégées
  • Utiliser les variables de type fichier pour les clés SSH et certificats
  • Intégrer HashiCorp Vault pour les secrets dynamiques à durée de vie limitée
  • Détecter les fuites de secrets avec GitLab Secret Detection et gitleaks
  • Mettre en place une stratégie de rotation sans interruption de pipeline

Vous administrez une instance GitLab CE sur KVM avec un ou plusieurs runners. Vos pipelines font du build Docker, du déploiement Ansible ou du provisionning Terraform — autant de cas où des credentials transitent dans les jobs. Vous voulez garantir que :

  • un développeur ne peut pas lire un secret de production dans une branche de feature ;
  • un secret ne s’affiche jamais en clair dans les logs du job ;
  • un token volé a une durée de vie limitée ;
  • une fuite est détectée automatiquement avant le merge.

Les 4 niveaux de protection des variables GitLab CI

Section intitulée « Les 4 niveaux de protection des variables GitLab CI »

Par défaut, une variable CI/CD est injectée dans tous les jobs, sur toutes les branches, et apparaît en clair dans les logs si un echo ou un script la référence.

.gitlab-ci.yml — Variable exposée
deploy:
script:
- echo "Deploying with token $DEPLOY_TOKEN"
# Le token apparaît EN CLAIR dans les logs du job

C’est le comportement le plus dangereux, et c’est celui par défaut.

Une variable marquée Protected n’est injectée que dans les jobs exécutés sur des branches protégées ou des tags protégés.

Vérifier les variables protégées via l'API
curl --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/variables" \
| jq '.[] | select(.protected == true) | {key, protected, masked}'
Résultat attendu
{
"key": "DEPLOY_TOKEN",
"protected": true,
"masked": true
}

Une variable marquée Masked est remplacée par [MASKED] dans les logs du job. GitLab impose des contraintes sur la valeur :

  • Au moins 8 caractères
  • Pas de retour à la ligne
  • Composée uniquement de caractères Base64 ou URL-safe

Les clés SSH multi-lignes et les certificats PEM ne peuvent pas être masqués — utilisez une variable de type fichier pour ces cas.

Au lieu d’injecter la valeur dans l’environnement, GitLab écrit le contenu dans un fichier temporaire et expose le chemin via la variable.

.gitlab-ci.yml — Clé SSH via variable fichier
deploy:
script:
- chmod 600 "$SSH_PRIVATE_KEY"
- ssh -i "$SSH_PRIVATE_KEY" deploy@prod.example.com "systemctl restart app"

La variable $SSH_PRIVATE_KEY contient le chemin /tmp/gitlab_ci_var_xxx, pas la clé elle-même. La clé n’apparaît jamais dans les logs même sans masquage.

  1. Marquer chaque secret comme Protected + Masked

    Dans Settings > CI/CD > Variables :

    • Cocher Protected : le secret n’est injecté que sur les branches/tags protégés
    • Cocher Masked : la valeur est remplacée par [MASKED] dans les logs
    • Choisir le scope : projet, groupe ou instance
    Créer une variable protégée + masquée via l'API
    curl --request POST \
    --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" \
    --form "key=DEPLOY_TOKEN" \
    --form "value=glpat-xxxxxxxxxxxxxxxxxxxx" \
    --form "protected=true" \
    --form "masked=true" \
    "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/variables"
  2. Utiliser des variables fichier pour les clés et certificats

    Dans Settings > CI/CD > Variables, sélectionner Type: File pour :

    • Clés SSH (id_rsa, id_ed25519)
    • Certificats TLS (.pem, .crt)
    • Fichiers de configuration sensibles (kubeconfig, .env)
  3. Restreindre les environnements

    Pour chaque variable, préciser l’environnement cible :

    • production : uniquement les jobs déployant en prod
    • staging : uniquement les jobs déployant en préproduction
    • * : tous les environnements (à éviter pour les secrets critiques)
  4. Vérifier l’absence de fuites dans les logs

    Rechercher des tokens exposés dans les logs d'un pipeline
    curl --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" \
    "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/jobs/$JOB_ID/trace" \
    | grep -iE 'glpat-|ghp_|AKIA|sk-|token|password|secret'

    Si la commande retourne des résultats, vous avez une fuite. Révoquez le token immédiatement.

Intégrer HashiCorp Vault pour les secrets dynamiques

Section intitulée « Intégrer HashiCorp Vault pour les secrets dynamiques »

Quand les variables CI/CD natives ne suffisent plus — parce que vous avez besoin de secrets à durée de vie limitée, de rotation automatique ou de traçabilité fine — Vault est l’intégration recommandée.

GitLab CI supporte nativement Vault via le mot-clé secrets: (GitLab 13.4+). Le runner demande un token éphémère à Vault via l’authentification JWT.

.gitlab-ci.yml — Secrets Vault natifs
deploy:
secrets:
DATABASE_PASSWORD:
vault:
engine:
name: kv-v2
path: secret
path: production/db
field: password
script:
- echo "Le mot de passe est injecté dans $DATABASE_PASSWORD"
- ansible-playbook deploy.yml
  1. Activer l’auth JWT dans Vault

    Configuration Vault
    vault auth enable jwt
    vault write auth/jwt/config \
    jwks_url="https://gitlab.example.com/-/jwks" \
    bound_issuer="gitlab.example.com"
  2. Créer une policy pour le pipeline

    policy-deploy.hcl
    path "secret/data/production/*" {
    capabilities = ["read"]
    }
    Fenêtre de terminal
    vault policy write deploy policy-deploy.hcl
  3. Créer un rôle lié au projet GitLab

    Rôle Vault lié au projet
    vault write auth/jwt/role/deploy \
    role_type="jwt" \
    policies="deploy" \
    token_explicit_max_ttl=300 \
    bound_claims='{"project_id": "42", "ref_protected": "true"}' \
    user_claim="user_email"

    Le bound_claims restreint l’accès aux pipelines du projet 42, sur branches protégées uniquement. Le TTL de 5 minutes limite la fenêtre d’exploitation en cas de vol.

GitLab intègre un scanner de secrets dans les pipelines CI/CD. Il détecte les tokens, clés API et mots de passe committé dans le code.

.gitlab-ci.yml — Activer Secret Detection
include:
- template: Jobs/Secret-Detection.gitlab-ci.yml

Le rapport apparaît dans Security & Compliance > Vulnerability Report (GitLab Ultimate) ou dans les artefacts du job (GitLab CE).

Pour les instances GitLab CE sans la licence Ultimate :

.gitlab-ci.yml — gitleaks dans le pipeline
secret-scan:
stage: test
image: zricethezav/gitleaks:latest
script:
- gitleaks detect --source . --report-format sarif --report-path gitleaks.sarif
artifacts:
reports:
sast: gitleaks.sarif
allow_failure: false
Tester en local avant de pousser
gitleaks detect --source . --verbose
Résultat attendu (pas de fuite)
INFO no leaks found

La rotation d’un secret CI/CD sans couper les pipelines en cours suit ce processus :

  1. Créer la nouvelle valeur sous un nom temporaire

    Dans Settings > CI/CD > Variables, créer DEPLOY_TOKEN_V2 avec la nouvelle valeur (Protected + Masked).

  2. Mettre à jour les pipelines pour accepter les deux

    .gitlab-ci.yml — Double source de token
    deploy:
    script:
    - TOKEN="${DEPLOY_TOKEN_V2:-$DEPLOY_TOKEN}"
    - curl -H "Authorization: Bearer $TOKEN" https://api.example.com/deploy
  3. Vérifier que les nouveaux pipelines utilisent la v2

    Lancez un pipeline sur la branche protégée et vérifiez dans les logs que la connexion fonctionne.

  4. Supprimer l’ancien secret et renommer

    Supprimez DEPLOY_TOKEN, puis recréez DEPLOY_TOKEN avec la valeur de DEPLOY_TOKEN_V2. Supprimez DEPLOY_TOKEN_V2.

  5. Révoquer l’ancien token côté fournisseur

    Selon le service : GitLab (révoquer le PAT), AWS (désactiver l’access key), Docker Hub (révoquer le token).

SymptômeCause probableSolution
Secret visible en clair dans les logsVariable non marquée MaskedCocher Masked dans Settings > CI/CD > Variables
Variable absente sur branche de featureVariable marquée Protected, branche non protégéeNormal — c’est le comportement attendu. Vérifier la liste des branches protégées
Masked variable cannot be set (erreur)Valeur < 8 caractères ou contient des retours à la ligneUtiliser une variable de type fichier pour les clés multi-lignes
Job Vault échoue avec permission deniedbound_claims ne correspond pas au projet ou à la brancheVérifier project_id et ref_protected dans le rôle Vault
Secret Detection ne détecte rienTemplate non inclus ou image non disponibleVérifier l’include Jobs/Secret-Detection.gitlab-ci.yml et l’accès au registre
Token rotationné, anciens pipelines échouentL’ancien token a été supprimé avant la fin des pipelinesUtiliser la stratégie double-token décrite ci-dessus
  • Les variables CI/CD GitLab ne sont ni protégées ni masquées par défaut — la configuration initiale est la plus dangereuse
  • Combinez toujours Protected + Masked pour les secrets réels, et utilisez des variables fichier pour les clés SSH et certificats
  • Les variables Protected ne sont injectées que sur les branches et tags protégés — c’est la première ligne de défense
  • Le mot-clé secrets: avec Vault via JWT fournit des tokens éphémères à TTL court, limitant la fenêtre d’exploitation
  • gitleaks en CI/CD détecte les secrets committés avant le merge — même sur GitLab CE sans licence Ultimate
  • La rotation sécurisée passe par un schéma double-token : créer la nouvelle valeur, migrer les pipelines, supprimer l’ancienne

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

10 questions
5 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn