Cheat Sheet Sécurité GitLab CI : Variables, Runners, Environnements et OIDC

Pourquoi la sécurité de GitLab CI est essentielle

Les pipelines GitLab CI/CD sont puissants — mais qui dit puissance dit risque. Une variable mal configurée peut exposer des secrets. Un runner non restreint peut exécuter du code malveillant. Un environnement non protégé peut permettre à un développeur junior de déployer directement en production. Ce cheat sheet vous fournit du YAML prêt à copier-coller pour chaque contrôle de sécurité critique de GitLab CI, des variables protégées à la fédération OIDC.

Ajoutez cette page à vos favoris. Utilisez-la comme référence à chaque fois que vous configurez un nouveau projet ou auditez un projet existant.

1. Variables protégées, masquées et cachées

Les variables CI/CD de GitLab contrôlent la manière dont les secrets circulent dans vos pipelines. Une mauvaise configuration est la cause numéro un des fuites de credentials en CI/CD. Chaque valeur sensible devrait être protégée (disponible uniquement sur les branches/tags protégés), masquée (cachée des logs de jobs) et, lorsque c’est possible, cachée (invisible dans l’interface utilisateur après création).

Définir des variables via l’API

# Créer une variable protégée + masquée via l'API GitLab
curl --request POST \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/variables" \
  --form "key=AWS_SECRET_ACCESS_KEY" \
  --form "value=$MY_SECRET" \
  --form "protected=true" \
  --form "masked=true" \
  --form "variable_type=env_var"

Utiliser les variables dans .gitlab-ci.yml

variables:
  # Les variables de groupe ou de projet sont injectées automatiquement.
  # Les variables de type fichier sont écrites dans un chemin temporaire.
  DEPLOY_TOKEN:
    description: "Token pour le déploiement en production"
    value: ""  # Intentionnellement vide — à définir dans CI/CD Settings

deploy_production:
  stage: deploy
  script:
    - echo "Déploiement avec un token masqué..."
    - ./deploy.sh --token "$DEPLOY_TOKEN"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

Règles clés :

  • Ne codez jamais les secrets en dur dans .gitlab-ci.yml — utilisez toujours les paramètres de variables CI/CD.
  • Définissez protected=true pour que les secrets ne soient disponibles que sur les branches protégées.
  • Définissez masked=true pour que les valeurs soient masquées dans les logs de jobs.
  • Utilisez des variables de groupe pour les secrets partagés entre projets (par ex., credentials cloud).

2. Types de runners et restriction de portée

Les runners exécutent vos jobs CI/CD. Si n’importe quel runner peut récupérer n’importe quel job, une merge request malveillante pourrait exfiltrer des secrets depuis un runner de production. Une restriction de portée adéquate est essentielle.

Enregistrement d’un runner avec des tags et une protection

# Enregistrer un runner limité aux branches protégées uniquement
gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.example.com" \
  --token "$RUNNER_REG_TOKEN" \
  --executor docker \
  --docker-image alpine:latest \
  --tag-list "production,protected" \
  --access-level ref_protected

Restreindre les jobs à des runners spécifiques

# .gitlab-ci.yml — s'assurer que les jobs de production ne s'exécutent que sur des runners protégés
deploy_production:
  stage: deploy
  tags:
    - production
    - protected
  script:
    - kubectl apply -f k8s/production/
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

# Les jobs de développement utilisent un runner séparé et non privilégié
test:
  stage: test
  tags:
    - shared
    - development
  script:
    - pytest tests/

Bonnes pratiques :

  • Utilisez --access-level ref_protected pour restreindre les runners aux branches et tags protégés.
  • Utilisez des runners spécifiques au projet pour les charges de travail sensibles — ne partagez jamais les runners de production entre des projets sans lien.
  • Préférez les runners éphémères (exécuteurs Docker, Kubernetes) afin que l’environnement soit détruit après chaque job.
  • Désactivez les shared runners sur les projets qui gèrent des déploiements sensibles.

3. Environnements protégés avec approbations

Les environnements protégés ajoutent une validation humaine avant les déploiements. C’est votre dernière ligne de défense contre les modifications non autorisées en production.

Configuration des environnements dans .gitlab-ci.yml

# .gitlab-ci.yml — déploiement avec protection de l'environnement
deploy_staging:
  stage: deploy
  script:
    - ./deploy.sh staging
  environment:
    name: staging
    url: https://staging.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

deploy_production:
  stage: deploy
  script:
    - ./deploy.sh production
  environment:
    name: production
    url: https://example.com
    deployment_tier: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
      allow_failure: false

Configurer les règles d’approbation via l’API

# Protéger l'environnement de production avec des approbations obligatoires
curl --request POST \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/protected_environments" \
  --data '{"name": "production", "deploy_access_levels": [{"group_id": 9899826}], "required_approval_count": 2, "approval_rules": [{"group_id": 9899826, "required_approvals": 2}]}'

Configurez cela dans Settings > CI/CD > Protected Environments dans l’interface GitLab. Exigez au minimum deux approbations pour les déploiements en production. Restreignez l’accès au déploiement à des groupes ou utilisateurs spécifiques — jamais « All maintainers ».

4. Restriction de portée du CI_JOB_TOKEN

CI_JOB_TOKEN est un token automatique que GitLab injecte dans chaque job. Par défaut, il peut accéder à d’autres projets de votre groupe — un risque sérieux de mouvement latéral. Depuis GitLab 16.0, vous devez restreindre sa portée.

Restreindre l’accès du token

# Vérifier la portée d'accès actuelle du CI_JOB_TOKEN
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/job_token_scope"

# Limiter le CI_JOB_TOKEN à l'accès à des projets spécifiques uniquement
curl --request PATCH \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/job_token_scope" \
  --data '{"enabled": true}'

# Ajouter un projet à la liste d'autorisation
curl --request POST \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/job_token_scope/allowlist" \
  --data '{"target_project_id": 12345}'

Utiliser CI_JOB_TOKEN de manière sécurisée dans les pipelines

# .gitlab-ci.yml — utiliser CI_JOB_TOKEN pour des déclenchements cross-project
trigger_deploy:
  stage: deploy
  trigger:
    project: my-group/deploy-project
    branch: main
    strategy: depend
  # CI_JOB_TOKEN est utilisé automatiquement pour le déclenchement.
  # Le projet cible doit autoriser le token de ce projet.

Règles clés : Activez la limitation de portée du CI/CD job token sur chaque projet. N’autorisez que les projets spécifiques qui ont véritablement besoin d’un accès cross-project. Auditez les listes d’autorisation chaque trimestre.

5. id_tokens OIDC pour AWS et GCP

La fédération OIDC élimine totalement les credentials cloud à longue durée de vie de vos variables CI/CD. GitLab émet un JWT à courte durée de vie, et votre fournisseur cloud l’échange contre des credentials temporaires. C’est le standard de référence pour l’authentification cloud dans les pipelines.

Fédération OIDC avec AWS

# .gitlab-ci.yml — authentification OIDC avec AWS
deploy_aws:
  stage: deploy
  image: amazon/aws-cli:latest
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.example.com
  variables:
    ROLE_ARN: arn:aws:iam::123456789012:role/gitlab-ci-deploy
  script:
    - >
      export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s"
      $(aws sts assume-role-with-web-identity
      --role-arn $ROLE_ARN
      --role-session-name "GitLabCI-${CI_PROJECT_ID}-${CI_PIPELINE_ID}"
      --web-identity-token "$GITLAB_OIDC_TOKEN"
      --duration-seconds 3600
      --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
      --output text))
    - aws s3 sync ./build s3://my-production-bucket/
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

Fédération d’identité de charge de travail GCP

# .gitlab-ci.yml — authentification OIDC avec GCP
deploy_gcp:
  stage: deploy
  image: google/cloud-sdk:latest
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.example.com
  script:
    - echo "$GITLAB_OIDC_TOKEN" > /tmp/gitlab_token.txt
    - >
      gcloud iam workload-identity-pools create-cred-config
      projects/$GCP_PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID
      --service-account="$GCP_SERVICE_ACCOUNT"
      --output-file=/tmp/gcp_creds.json
      --credential-source-file=/tmp/gitlab_token.txt
    - export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp_creds.json
    - gcloud config set project $GCP_PROJECT_ID
    - gcloud run deploy my-service --image gcr.io/$GCP_PROJECT_ID/my-app:$CI_COMMIT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

Côté cloud, configurez la politique de confiance pour valider des claims comme project_path, ref et ref_protected afin que seuls des projets et des branches spécifiques puissent assumer le rôle.

6. Sécurité des pipelines de merge request

Les pipelines de merge request s’exécutent sur du code qui n’a pas encore été validé par une revue. Traitez-les comme non fiables. N’exposez jamais les secrets de production aux pipelines de MR.

# .gitlab-ci.yml — règles distinctes pour les pipelines de MR vs. de branche
test:
  stage: test
  script:
    - pytest tests/
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

deploy_production:
  stage: deploy
  script:
    - ./deploy.sh production
  rules:
    # Ne JAMAIS exécuter sur les pipelines de merge request
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: never
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

Contrôles critiques :

  • Utilisez rules: pour garantir que les jobs de déploiement ne s’exécutent jamais sur les pipelines merge_request_event.
  • Exigez la réussite du pipeline avant la fusion (Settings > Merge Requests).
  • Activez « Pipelines must succeed » et « All discussions must be resolved ».
  • Envisagez d’activer les merged results pipelines pour tester l’état post-fusion.

7. Template de détection de secrets

Le scanner de détection de secrets intégré à GitLab intercepte les credentials accidentellement commités avant qu’ils n’atteignent la branche par défaut.

# .gitlab-ci.yml — inclure le template de détection de secrets
include:
  - template: Jobs/Secret-Detection.gitlab-ci.yml

# Surcharger pour faire échouer le pipeline si des secrets sont détectés
secret_detection:
  variables:
    SECRET_DETECTION_HISTORIC_SCAN: "true"  # Scanner l'historique complet de git
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  allow_failure: false  # Bloquer le pipeline en cas de détection

Pour une analyse plus complète, ajoutez également les templates SAST et dependency scanning :

include:
  - template: Jobs/Secret-Detection.gitlab-ci.yml
  - template: Jobs/SAST.gitlab-ci.yml
  - template: Jobs/Dependency-Scanning.gitlab-ci.yml

Consultez les résultats dans le Security Dashboard (disponible sur GitLab Ultimate) ou parsez les artefacts JSON dans les tiers inférieurs.

8. Push Rules

Les push rules appliquent des politiques au niveau Git — avant même que le code n’entre dans un pipeline. Utilisez-les pour empêcher l’envoi de secrets, imposer des standards de messages de commit et restreindre les types de fichiers.

# Définir les push rules via l'API
curl --request PUT \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/push_rule" \
  --data '{"deny_delete_tag": true, "prevent_secrets": true, "commit_message_regex": "^(feat|fix|chore|docs|refactor|test|ci)\\(.*\\):.*", "max_file_size": 50, "member_check": true, "reject_unsigned_commits": true}'

Push rules recommandées :

  • prevent_secrets: true — Rejette les pushs contenant des fichiers qui ressemblent à des secrets (clés, tokens, certificats).
  • reject_unsigned_commits: true — Exige des commits signés GPG (GitLab Premium+).
  • commit_message_regex — Impose les messages de commit conventionnels pour des pistes d’audit propres.
  • max_file_size — Empêche de commiter accidentellement de gros fichiers binaires.
  • member_check: true — Rejette les commits provenant de non-membres du projet.

9. Timeouts de jobs et interruptible

Les jobs incontrôlés gaspillent des ressources et peuvent être exploités pour du cryptomining. Définissez des timeouts explicites et marquez les jobs non critiques comme interruptibles afin qu’ils soient annulés lorsqu’un nouveau pipeline démarre sur la même branche.

# .gitlab-ci.yml — timeouts et interruptible
default:
  timeout: 30m          # Timeout global par défaut pour tous les jobs
  interruptible: true   # Annuler les jobs en cours lorsqu'un nouveau commit est poussé
  retry:
    max: 1
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

test:
  stage: test
  timeout: 15m
  interruptible: true
  script:
    - pytest tests/ --timeout=600

deploy_production:
  stage: deploy
  timeout: 20m
  interruptible: false  # Ne JAMAIS annuler un déploiement en production en cours
  script:
    - ./deploy.sh production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
  environment:
    name: production

Recommandations :

  • Définissez un timeout au niveau projet dans Settings > CI/CD > General Pipelines (recommandé : 60 minutes maximum).
  • Définissez des timeouts au niveau job plus courts que la valeur par défaut du projet.
  • Marquez les jobs de test et de lint comme interruptible: true pour économiser la capacité des runners.
  • Marquez les jobs de déploiement comme interruptible: false pour éviter les déploiements partiels.
  • Utilisez retry uniquement pour les défaillances d’infrastructure transitoires — jamais pour les échecs de tests.

Tableau de référence rapide

Contrôle Où configurer Tier minimum
Variables protégées/masquées Settings > CI/CD > Variables Free
Restriction des runners Settings > CI/CD > Runners Free
Environnements protégés Settings > CI/CD > Protected Environments Premium
Portée du CI_JOB_TOKEN Settings > CI/CD > Token Access Free
id_tokens OIDC .gitlab-ci.yml Free
Détection de secrets include: template Free (Ultimate pour le dashboard)
Push Rules Settings > Repository > Push Rules Premium
Timeouts de jobs Settings > CI/CD + .gitlab-ci.yml Free

Pour aller plus loin

Continuez à renforcer la sécurité de vos pipelines GitLab CI/CD avec ces ressources complémentaires :

La sécurité n’est pas une configuration ponctuelle — c’est une pratique continue. Revoyez vos paramètres de sécurité de pipeline chaque trimestre, effectuez la rotation des credentials, auditez les accès aux runners et maintenez votre instance GitLab à jour. Ce cheat sheet vous fournit les fondations. À vous maintenant de verrouiller vos pipelines.