Aller au contenu
CI/CD & Automatisation medium

GitLab CI : audit, conformité et traçabilité des pipelines

23 min de lecture

La question d'un auditeur n'est jamais "est-ce que vous êtes sécurisés ?" mais "prouvez-le". GitLab enregistre des centaines de types d'événements d'audit : qui a modifié une branche protégée, qui a approuvé un déploiement, qui a changé les permissions d'un runner. Ce guide vous montre comment exploiter ces mécanismes pour répondre aux exigences SOC 2, ISO 27001 ou NIS2 avec des données concrètes.

À la fin de ce guide, vous saurez :

  • Consulter les audit events de GitLab (projet, groupe, instance)
  • Requêter l'API d'audit pour extraire des événements précis
  • Mettre en place un compliance framework pour appliquer des politiques par projet
  • Configurer des compliance pipelines pour forcer l'exécution de jobs de sécurité
  • Streamer les événements vers un SIEM externe (Splunk, ELK, Datadog)

L'audit et la conformité deviennent nécessaires quand :

  • Un auditeur externe (SOC 2, ISO 27001) demande la preuve que les déploiements suivent un processus de validation
  • Votre organisation est soumise à NIS2 et doit démontrer la traçabilité de sa chaîne logicielle
  • Un incident de sécurité survient et vous devez reconstituer qui a fait quoi, quand
  • Vous gérez des dizaines de projets et devez garantir que chacun applique les mêmes règles de sécurité
  • Des développeurs contournent les contrôles (désactivation d'une branche protégée, suppression d'une approbation) et vous devez le détecter

Un audit event est une trace immuable écrite par GitLab à chaque action sensible : nul ne peut la modifier ni la supprimer depuis l'interface, ce qui en fait une preuve recevable. Ces événements ne concernent pas le code mais la configuration et les droits : qui a ouvert une branche protégée, qui a changé un secret, qui a ajouté un membre. Le périmètre visible dépend de votre rôle et du niveau auquel l'événement a été enregistré.

GitLab enregistre des événements à trois niveaux :

NiveauVisibilitéExemples d'événements
ProjetMaintainer+MR mergée, pipeline lancé, variable modifiée, branche protégée changée
GroupeOwnerMembre ajouté/retiré, permission changée, runner partagé configuré
InstanceAdminUtilisateur créé, 2FA désactivé, paramètre global modifié

Deux chemins d'accès existent, et ils ne renvoient pas la même chose : l'interface propose un journal filtrable mais réservé à GitLab Premium, tandis que l'API REST reste disponible plus largement et se prête à l'automatisation. Le paramètre created_after attend une date au format AAAA-MM-JJ ; sans lui, l'API remonte les événements les plus récents seulement.

Projet : Settings → General → Audit events (GitLab Premium+).

Vous voyez un journal chronologique avec l'auteur, l'action et la cible.

GitLab enregistre plusieurs centaines de types d'événements, dont la majorité n'intéresse personne. Les neuf lignes ci-dessous sont celles qui signalent un affaiblissement des contrôles : elles précèdent presque toujours un contournement ou un incident. Ce sont les premières à câbler dans une alerte, avant de s'occuper du reste.

ÉvénementPourquoi le surveillerCatégorie
protected_branch_created / removedQuelqu'un a modifié la politique de branchesContrôle d'accès
approval_rule_created / deletedLes règles d'approbation MR ont changéProcessus de validation
environment_protected / unprotectedL'environnement de production a perdu sa protectionDéploiement
variable_created / updated / deletedUn secret CI a été modifiéSecrets
runner_registrationUn nouveau runner a été enregistréInfrastructure
member_added / removedLes permissions d'accès au projet ont changéContrôle d'accès
deploy_key_addedUne clé de déploiement a été ajoutéeAccès externe
merge_request_approvedUne MR a été approuvée (par qui ?)Traçabilité
pipeline_schedule_createdUn pipeline planifié a été crééAutomatisation

L'interface montre un journal continu ; l'API permet de répondre à une question précise, datée et bornée. Toutes les requêtes qui suivent combinent un endpoint GitLab et un filtre jq, l'outil en ligne de commande qui découpe du JSON. Elles supposent deux variables d'environnement définies au préalable : GITLAB_URL et GITLAB_TOKEN.

Les auditeurs posent des questions précises. Voici comment y répondre avec l'API :

"Qui a modifié les branches protégées ce mois-ci ?"

Fenêtre de terminal
curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_URL/api/v4/projects/$PROJECT_ID/audit_events?created_after=2026-03-01" \
| jq '[.[] | select(.details.change == "protected_branch")]
| .[] | {date: .created_at, author: .details.author_name, action: .details.custom_message}'

"Qui a approuvé les MR déployées en production ?"

Fenêtre de terminal
# Lister les MR mergées sur main ce mois
curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_URL/api/v4/projects/$PROJECT_ID/merge_requests?state=merged&target_branch=main&updated_after=2026-03-01" \
| jq '.[] | {iid: .iid, title: .title, merged_by: .merged_by.name, approved_by: [.approved_by[].user.name]}'

"Y a-t-il eu des déploiements pendant le freeze ?"

Fenêtre de terminal
curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_URL/api/v4/projects/$PROJECT_ID/deployments?environment=production&updated_after=2026-03-28&updated_before=2026-04-02" \
| jq '.[] | {id: .id, created_at: .created_at, status: .status, deployed_by: .deployable.user.name}'

Pour générer un rapport d'audit régulier :

#!/usr/bin/env bash
# audit-report.sh : rapport d'audit mensuel pour un projet GitLab
set -euo pipefail
PROJECT_ID="${1:?Usage: $0 <project_id>}"
SINCE=$(date -d "30 days ago" +%Y-%m-%d)
API="$GITLAB_URL/api/v4/projects/$PROJECT_ID"
AUTH="PRIVATE-TOKEN: $GITLAB_TOKEN"
echo "=== Rapport d'audit, projet $PROJECT_ID, depuis $SINCE ==="
echo ""
echo "--- Modifications de branches protégées ---"
curl -s -H "$AUTH" "$API/audit_events?created_after=$SINCE" \
| jq -r '.[] | select(.details.change == "protected_branch")
| "\(.created_at) | \(.details.author_name) | \(.details.custom_message)"'
echo ""
echo "--- Modifications de variables CI ---"
curl -s -H "$AUTH" "$API/audit_events?created_after=$SINCE" \
| jq -r '.[] | select(.details.change == "ci_variable")
| "\(.created_at) | \(.details.author_name) | \(.details.custom_message)"'
echo ""
echo "--- Déploiements en production ---"
curl -s -H "$AUTH" "$API/deployments?environment=production&updated_after=$SINCE" \
| jq -r '.[] | "\(.created_at) | \(.deployable.user.name // "système") | \(.status) | ref:\(.ref)"'
echo ""
echo "--- MR mergées sur main ---"
curl -s -H "$AUTH" "$API/merge_requests?state=merged&target_branch=main&updated_after=$SINCE" \
| jq -r '.[] | "!\(.iid) | \(.merged_at) | \(.merged_by.name) | \(.title)"'

Les sections précédentes servaient à constater après coup. Un compliance framework sert à imposer en amont : c'est l'objet GitLab qui rattache un projet à une réglementation donnée et qui déclenche les contrôles associés. Cette fonctionnalité relève de l'édition Ultimate, avec des contournements partiels décrits plus bas pour les autres éditions.

Un compliance framework est un label appliqué à un projet pour indiquer qu'il est soumis à une politique spécifique. Il sert deux objectifs :

  1. Visibilité : identifier rapidement quels projets sont soumis à quelle réglementation
  2. Enforcement : attacher un compliance pipeline qui s'exécute automatiquement

Le framework se définit une seule fois au niveau du groupe, puis se rattache à autant de projets que nécessaire : il n'existe pas de framework de projet. Les trois étapes ci-dessous couvrent la création, le rattachement et la vérification par l'API, cette dernière étant le seul moyen fiable de contrôler en masse un parc de projets.

  1. Créer le framework au niveau du groupe

    Group → Settings → General → Compliance frameworks → New framework.

    ChampExemple
    NameSOC 2, Production
    DescriptionProjets soumis aux contrôles SOC 2 Type II
    Color#FF0000
    Compliance pipelineURL du fichier .gitlab-ci.yml de conformité
  2. Attacher le framework à un projet

    Project → Settings → General → Compliance framework → sélectionner le framework.

    Le label apparaît sur la page du projet et dans les listes de projets.

  3. Vérifier via l'API

    Fenêtre de terminal
    curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
    "$GITLAB_URL/api/v4/projects/$PROJECT_ID" \
    | jq '.compliance_frameworks'

    Résultat attendu :

    ["SOC 2, Production"]

Le framework pose une étiquette ; le compliance pipeline est ce qui la rend contraignante. C'est un fichier de pipeline hébergé hors du projet, que GitLab exécute à la place de celui du dépôt, et sur lequel le développeur n'a aucun droit d'écriture. Cette inversion de contrôle est le seul mécanisme natif qui garantit qu'un job de sécurité tourne vraiment.

Un développeur peut modifier ou supprimer les jobs de sécurité dans son .gitlab-ci.yml. Les includes partagés peuvent être commentés. Rien ne garantit que le scan de vulnérabilités s'exécute réellement.

Un compliance pipeline est un .gitlab-ci.yml maintenu par l'équipe sécurité, exécuté automatiquement sur tout projet attaché à un compliance framework. Le développeur ne peut pas le modifier ni le désactiver.

compliance-pipeline.yml (dans un projet dédié)
stages:
- compliance-check
- .pre
- build
- test
- deploy
- .post
- compliance-report
# Ce job s'exécute AVANT le pipeline du développeur
compliance-secret-scan:
stage: compliance-check
image:
name: ghcr.io/gitleaks/gitleaks:v8.30.1
entrypoint: [""]
script:
- gitleaks detect --source . --verbose --exit-code 1
allow_failure: false
# Ce job s'exécute APRÈS le pipeline du développeur
compliance-report:
stage: compliance-report
image: alpine:3.20
script:
- echo "Pipeline $CI_PIPELINE_ID, projet $CI_PROJECT_PATH"
- echo "Commit $CI_COMMIT_SHA, auteur $GITLAB_USER_NAME"
- echo "Branche $CI_COMMIT_BRANCH, ref $CI_COMMIT_REF_NAME"
- echo "Compliance check passed at $(date -u)"
artifacts:
paths:
- compliance-*.log
expire_in: 1 year

Le compliance pipeline remplace le pipeline du projet. Il utilise include: pour incorporer le .gitlab-ci.yml du développeur entre ses propres stages :

compliance-pipeline.yml (version avec include)
include:
- project: '$CI_PROJECT_PATH'
ref: '$CI_COMMIT_SHA'
file: '.gitlab-ci.yml'
stages:
- compliance-check
- build
- test
- deploy
- compliance-report
compliance-secret-scan:
stage: compliance-check
# ... (comme ci-dessus)
compliance-report:
stage: compliance-report
# ... (comme ci-dessus)

Le développeur garde la main sur ses stages build, test, deploy. L'équipe sécurité contrôle les stages compliance-check et compliance-report.

Consulter les audit events suppose que quelqu'un pense à aller les lire. Le streaming renverse la logique : GitLab pousse chaque événement, au fil de l'eau, vers un système externe. La cible habituelle est un SIEM (Security Information and Event Management), la plateforme qui centralise les journaux de sécurité de toute l'entreprise et déclenche les alertes.

Pour une surveillance continue, GitLab peut streamer les audit events vers une destination externe :

  1. Configurer la destination

    Group → Settings → General → Audit events → Streaming → Add streaming destination.

    ChampValeur
    Destination URLhttps://siem.example.com/api/gitlab-events
    Verification tokenToken partagé pour authentifier les requêtes
  2. Format des événements

    GitLab envoie un POST HTTP avec un payload JSON pour chaque événement :

    {
    "id": 5678,
    "author_id": 42,
    "entity_id": 7,
    "entity_type": "Project",
    "event_type": "audit_events",
    "details": {
    "change": "protected_branch",
    "from": "Developers + Maintainers",
    "to": "No one",
    "author_name": "Alice Martin",
    "target_details": "main"
    },
    "created_at": "2026-04-15T10:30:00Z"
    }
  3. Vérifier la réception

    Utilisez un endpoint de test pour valider le flux :

    Fenêtre de terminal
    # Lister les destinations de streaming
    curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
    "$GITLAB_URL/api/v4/groups/$GROUP_ID/audit_events/streaming/destinations"

GitLab ne propose pas d'intégration dédiée par produit : il envoie un POST HTTP avec un corps JSON, à charge de la destination de l'accepter. Le tableau donne, pour les quatre plateformes les plus répandues, le point d'entrée qui sait recevoir ce format brut.

SIEMMéthodeConfiguration
SplunkHTTP Event CollectorURL HEC + token
ELK / OpenSearchEndpoint HTTPLogstash HTTP input ou direct Elasticsearch
DatadogHTTP Log endpointhttps://http-intake.logs.datadoghq.eu/api/v2/logs + API key
Google ChronicleWebhookIngestion API endpoint

Un auditeur demande un état, pas un journal. Le script qui suit interroge la configuration courante de chaque projet et la traduit en un score chiffré, reproductible d'un mois sur l'autre. Il ne dépend d'aucune édition payante : tous les endpoints utilisés sont disponibles en GitLab CE.

Combinez les données d'audit avec les informations de configuration pour produire un score de conformité :

#!/usr/bin/env bash
# compliance-score.sh : score de conformité d'un projet GitLab
set -euo pipefail
PROJECT_ID="${1:?Usage: $0 <project_id>}"
API="$GITLAB_URL/api/v4/projects/$PROJECT_ID"
AUTH="PRIVATE-TOKEN: $GITLAB_TOKEN"
score=0
total=0
check() {
local label="$1" result="$2"
total=$((total + 1))
if [[ "$result" == "true" ]]; then
score=$((score + 1))
echo " [OK] $label"
else
echo " [KO] $label"
fi
}
echo "=== Compliance score, projet $PROJECT_ID ==="
# Branche main protégée ?
main_protected=$(curl -s -H "$AUTH" "$API/protected_branches" \
| jq 'any(.[]; .name == "main")')
check "Branche main protégée" "$main_protected"
# Force push interdit ?
no_force=$(curl -s -H "$AUTH" "$API/protected_branches" \
| jq '[.[] | select(.name == "main")] | .[0].allow_force_push == false')
check "Force push interdit sur main" "$no_force"
# Tags protégés ?
tags_protected=$(curl -s -H "$AUTH" "$API/protected_tags" \
| jq 'length > 0')
check "Tags protégés configurés" "$tags_protected"
# MR obligatoire sur main ?
no_push=$(curl -s -H "$AUTH" "$API/protected_branches" \
| jq '[.[] | select(.name == "main")] | .[0].push_access_levels | any(.[]; .access_level == 0)')
check "Push direct interdit sur main" "$no_push"
# Approbation MR requise ?
approvals=$(curl -s -H "$AUTH" "$API/approval_rules" \
| jq 'any(.[]; .approvals_required > 0)')
check "Approbation MR requise" "$approvals"
# Pipeline actif ?
has_pipeline=$(curl -s -H "$AUTH" "$API/pipelines?per_page=1&status=success" \
| jq 'length > 0')
check "Pipeline CI actif" "$has_pipeline"
echo ""
echo "Score : $score / $total"

Résultat attendu :

=== Compliance score, projet 42 ===
[OK] Branche main protégée
[OK] Force push interdit sur main
[KO] Tags protégés configurés
[OK] Push direct interdit sur main
[KO] Approbation MR requise
[OK] Pipeline CI actif
Score : 4 / 6

La plupart des échecs rencontrés sur ce sujet ne sont pas des bugs mais des limites d'édition ou des droits insuffisants sur le jeton d'API. Le tableau distingue les deux cas et donne, pour chaque symptôme, la vérification qui tranche.

SymptômeCause probableCorrection
Audit events vides (API retourne [])Tier CE sans PremiumMettre à niveau ou utiliser l'API MR/deployments comme alternative
Compliance framework non disponibleGitLab Ultimate requisUtiliser des topics + includes partagés comme alternative
Streaming : pas de réception côté SIEMURL incorrecte ou token invalideTester avec curl direct, vérifier les logs SIEM
Compliance pipeline ne se déclenche pasFramework non attaché au projetVérifier Settings → General → Compliance framework
Script audit-report.sh : jq: errorPas d'événements pour le filtreAjouter // empty au filtre jq pour gérer les résultats vides
403 Forbidden sur /audit_eventsToken sans droits admin ou OwnerUtiliser un token Maintainer pour les events projet, Owner pour groupe
Événements manquants dans le journalRétention par défaut limitéeConfigurer la rétention dans Admin → Settings (instance)

L'audit et la conformité dans GitLab CI reposent sur cinq principes :

  1. Tout est enregistré : GitLab trace les modifications de branches protégées, de variables, de permissions, de runners. Ces événements sont votre source de vérité pour répondre aux auditeurs.
  2. L'API est votre outil d'investigation : l'interface montre un journal, l'API permet des requêtes ciblées. Automatisez les rapports avec des scripts pour ne pas dépendre d'une consultation manuelle.
  3. Les compliance frameworks imposent des politiques : attacher un framework à un projet garantit que le compliance pipeline s'exécute, sans dépendre de la bonne volonté du développeur (GitLab Ultimate).
  4. Le streaming transforme l'audit en surveillance : envoyer les événements vers un SIEM permet de détecter les anomalies en temps réel, pas lors de l'audit annuel.
  5. La conformité se mesure : un score de conformité par projet, calculé automatiquement, permet d'identifier les projets à risque avant l'auditeur.

Dix questions sur les audit events, les compliance frameworks et le streaming, avec un seuil de réussite à 70 %.

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 tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn