Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English
Read in English

Sécuriser une pipeline CI/CD : les quatre surfaces de contrôle

Par mis à jour le

Améliorer cette page

Sécuriser une pipeline CI/CD, c'est réduire ce qu'elle peut faire quand elle est compromise, sur quatre surfaces : les identités, les dépendances, les runners et les artefacts. Une pipeline détient des secrets vers la production, écrit les artefacts que vos utilisateurs installent, et exécute du code tiers que personne ne relit. Cette leçon s'adresse à qui écrit ou maintient des pipelines sans être spécialiste de la sécurité. Elle donne un modèle valable quel que soit l'outil, GitHub Actions, GitLab CI ou Jenkins, et renvoie aux leçons qui le mettent en œuvre.

  • Expliquer pourquoi une pipeline est une cible plus rentable qu'un serveur
  • Identifier les quatre surfaces de contrôle et le risque propre à chacune
  • Appliquer le moindre privilège, l'épinglage et l'isolation des runners
  • Distinguer un contrôle structurel d'un contrôle ponctuel
  • Restreindre le code tiers autorisé et tracer les changements de configuration de la CI
  • Situer une pipeline dans un modèle de maturité en quatre niveaux

Une pipeline CI/CD (intégration et livraison continues) est le système qui teste, construit et déploie votre code sans intervention humaine. Pour faire ce travail, elle reçoit des droits étendus : lire le code, publier des artefacts, se connecter à la production. Un attaquant qui la contrôle hérite de tous ces droits d'un coup, sans jamais toucher à vos serveurs.

C'est la différence avec une attaque classique. Compromettre un serveur donne accès à ce serveur ; compromettre la pipeline donne accès à tout ce qu'elle livre, à chaque exécution. L'image souvent employée est celle de la source d'eau : on n'empoisonne pas chaque verre, on empoisonne le réservoir, et le poison arrive partout.

Le tableau ci-dessous relie chaque ressource que la pipeline manipule à ce qu'un attaquant en ferait. Lisez-le comme une liste de ce qu'il faut protéger : chaque ligne correspond à une surface de contrôle traitée plus bas, et la dernière colonne montre que l'impact dépasse toujours la pipeline elle-même.

RessourceCe que l'attaquant peut faireExemple concret
SecretsVoler les tokens d'API, clés SSH, identifiants cloudUtiliser vos propres clés sur votre compte cloud
Code sourceModifier le code avant compilationAjouter une porte dérobée invisible à la revue
ArtefactsRemplacer un livrableVos utilisateurs installent une version piégée
ProductionDéployer directementPrendre la main sur vos serveurs

L'attaque la plus instructive de ces dernières années s'est jouée sur une action GitHub utilisée par plus de 23 000 dépôts. Les 14 et 15 mars 2025, ses tags de version ont été redéplacés vers un commit malveillant (CVE-2025-30066). Ce code lisait la mémoire du runner, en extrayait les secrets, et les écrivait encodés en base64 dans les journaux du workflow.

Sur un dépôt public, ces journaux sont lisibles par tous : les secrets étaient donc exposés sans qu'aucun serveur externe soit contacté. Les équipes touchées n'avaient rien changé, puisqu'elles référençaient l'action par un tag, et c'est ce tag qui avait bougé sous leurs pieds. Seuls les dépôts qui épinglaient l'action par SHA n'ont pas exécuté le code piégé. Le correctif est arrivé en version 46.0.1.

La sécurité d'une pipeline se joue sur quatre surfaces, et chacune correspond à une décision de confiance qui doit être explicite et vérifiable. Le tableau résume la question à se poser pour chacune et ce qui arrive quand on l'ignore. Les sections suivantes les traitent une par une, du plus simple à mettre en place au plus structurant.

SurfaceQuestion cléRisque si négligée
IdentitésQui a le droit de faire quoi, et combien de temps ?Accès excessifs, secrets volés
DépendancesÀ quel code externe faisons-nous confiance ?Code malveillant exécuté
RunnersOù s'exécute le code, et que garde la machine ?Contamination entre jobs
ArtefactsComment prouver l'origine du livrable ?Distribution d'un livrable piégé

La question n'est pas de savoir si la pipeline doit s'authentifier, mais avec quoi et pour combien de temps. Elle se connecte à un registre d'images, à un fournisseur cloud, à un outil de déploiement, et chaque connexion repose sur un identifiant. Un secret de longue durée stocké dans la CI reste valable après le départ de son auteur, et après la fuite qui l'a exposé.

Chaque job ne reçoit que les permissions strictement nécessaires à sa tâche. Sur GitHub Actions, le jeton automatique se règle par le bloc permissions : on le vide au niveau du workflow, puis on accorde les droits job par job. Un job de test compromis ne peut alors ni pousser du code ni publier un paquet.

# ❌ Permissions excessives : un job compromis peut tout faire
permissions: write-all
# Permissions minimales : rien par défaut, puis le strict nécessaire par job
permissions: {}
jobs:
publier:
runs-on: ubuntu-24.04
permissions:
contents: read # lire le code, sans pouvoir le modifier
packages: write # publier un paquet, rien d'autre

Des jetons éphémères plutôt que des secrets permanents

Section intitulée « Des jetons éphémères plutôt que des secrets permanents »

Un jeton éphémère expire de lui-même après quelques minutes ; un secret statique reste valide jusqu'à ce qu'on le révoque, ce qui est souvent oublié. Le protocole OIDC (OpenID Connect) permet à la pipeline de demander au fournisseur cloud un jeton temporaire à chaque exécution, au lieu de stocker une clé permanente. Le tableau montre ce que change ce choix le jour d'une fuite.

CaractéristiqueSecret statiqueJeton éphémère (OIDC)
Durée de vieIllimitéeQuelques minutes
Si voléUtilisable jusqu'à détectionExpire rapidement
RévocationManuelle, souvent oubliéeAutomatique

La mise en œuvre sur GitHub Actions fait l'objet de la leçon OIDC GitHub Actions : authentification sans secrets.

Les secrets de staging et ceux de production doivent être distincts, et rangés dans des environnements protégés de la plateforme. Une fuite côté recette ne donne alors aucun accès à la production. Avant de configurer les accès d'un job, quatre questions suffisent : ce jeton a-t-il besoin d'un accès en écriture ou d'une simple lecture ? OIDC est-il possible ? Que perd-on si ce secret est volé ? Quand a-t-il été renouvelé pour la dernière fois ?

Le code que vous écrivez n'est qu'une fraction de ce qui s'exécute. Chaque npm install, chaque pip install, chaque action tierce exécute du code venu de mainteneurs que vous ne connaissez pas, avec les permissions de votre pipeline. L'objectif est de rendre reproductible ce que la pipeline installe, et détectable ce qui change.

Un tag comme @v4 est mutable : son mainteneur, ou un attaquant qui a pris son compte, peut le déplacer vers un autre commit. Un SHA de commit est immuable : il désigne toujours exactement le même code. C'est précisément ce qui a protégé les dépôts épinglés lors de l'attaque tj-actions.

# ❌ Tag mutable : peut pointer demain vers un autre code
- uses: actions/checkout@v4
# SHA de commit immuable, la version en commentaire pour la lecture
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

Le SHA à retenir est celui du commit, pas celui de l'objet tag. La commande git ls-remote --tags https://github.com/actions/checkout v7.0.1 le donne ; pour un tag annoté, prenez la ligne suffixée ^{}, qui désigne le commit pointé. La méthode complète est dans la leçon Épingler les actions par SHA.

Décider quel code tiers a le droit de s'exécuter

Section intitulée « Décider quel code tiers a le droit de s'exécuter »

L'épinglage répond à la question « ce code va-t-il changer ? », pas à « ai-je le droit de l'exécuter ? ». Une liste d'autorisation posée au niveau de l'organisation ferme la seconde : seules les actions explicitement admises s'exécutent, quel que soit le workflow qui les appelle, y compris un workflow ajouté par un compte compromis. Sur GitHub, la politique « autoriser l'organisation et une sélection » accepte des motifs comme actions/* ou sigstore/cosign-installer@*, et n'existe qu'au niveau d'une organisation.

C'est un contrôle structurel : il s'applique sans dépendre du YAML de chaque dépôt, que n'importe quel contributeur peut modifier. Les deux contrôles se complètent sans se remplacer : une action autorisée mais épinglée par tag reste exposée à une réécriture, une action épinglée mais non autorisée ne devrait pas tourner du tout. La leçon Restreindre les actions autorisées en détaille la syntaxe.

Un fichier de verrouillage (package-lock.json, poetry.lock) enregistre la version exacte de chaque dépendance, y compris des dépendances transitives. La pipeline l'installe alors tel quel, avec npm ci plutôt que npm install, et produit le même résultat à chaque exécution. Le tableau compare les deux situations.

Sans fichier de verrouillageAvec fichier de verrouillage
npm install prend les dernières versionsnpm ci installe exactement les versions enregistrées
Résultat différent d'une exécution à l'autreRésultat identique à chaque exécution
Une mise à jour arrive sans revueLes mises à jour passent par une revue

Une nouvelle dépendance est une décision de confiance, pas un détail technique. Avant de l'ajouter, regardez qui la maintient, depuis quand, combien de projets l'utilisent, et si son code est lisible. Un mainteneur unique est un risque, tout comme un paquet récent ou un code obfusqué. L'outil OpenSSF Scorecard automatise une partie de ces vérifications.

CritèrePourquoi c'est important
Qui la maintient ?Un mainteneur unique est un point de rupture
Depuis combien de temps ?Une dépendance récente n'a pas fait ses preuves
Combien de projets l'utilisent ?Peu d'utilisateurs, peu d'yeux pour repérer un problème
Le code est-il lisible ?Du code obfusqué peut cacher quelque chose

Le runner est la machine, physique ou virtuelle, qui exécute vos jobs. Tout ce qu'il conserve d'une exécution à l'autre, cache, identifiants, fichiers temporaires, devient accessible au job suivant, y compris quand ce job vient d'une contribution externe. L'objectif est de supprimer cette persistance, vecteur principal de contamination d'un job à l'autre.

Un runner éphémère est créé pour un job, puis détruit : rien ne survit à l'exécution. Un runner persistant, à l'inverse, garde l'état laissé par chaque job, et un code malveillant peut s'y installer pour attendre le prochain déploiement. Le tableau suit deux jobs successifs dans chacun des cas ; la leçon Runners éphémères GitHub Actions en donne la mise en œuvre.

Runner persistantRunner éphémère
Le job 1 modifie des fichiersLe job 1 s'exécute, le runner est détruit
Le job 2 hérite de ces modificationsLe job 2 obtient un runner neuf
Un code malveillant peut persisterRien ne peut persister

Le runner n'accède qu'aux ressources dont le job a besoin : le registre, le dépôt de paquets, l'API de déploiement. Par défaut, il ne voit ni le réseau interne ni les bases de données de l'entreprise. Un job compromis sur un runner isolé ne peut alors ni explorer le réseau ni exfiltrer des données vers une destination inattendue.

Quand une personne extérieure ouvre une pull request depuis un fork, le code qu'elle propose s'exécute sur vos runners. Ce code ne doit jamais atteindre vos secrets. Sur GitHub Actions, le déclencheur pull_request respecte cette règle : un workflow lancé depuis un fork ne reçoit ni secrets ni jeton en écriture.

La pull request d'un contributeur externe s'exécute sur un runner sans secrets, avec des permissions en lecture seule et sans accès au déploiement.PR d'un contributeur externeRunner SANS secretspermissions lecture seule, pas d'accès au déploiement
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Le piège vient du déclencheur pull_request_target, qui s'exécute avec les secrets du dépôt cible. S'il récupère et exécute le code de la pull request, il offre ces secrets à n'importe quel contributeur. La leçon Sécuriser pull_request_target détaille l'attaque et la parade.

Entre la construction d'un artefact et son déploiement, rien ne prouve par défaut qu'il s'agit du même contenu. Un artefact, c'est le résultat de la pipeline : une image de conteneur, un exécutable, un paquet. Sans empreinte vérifiable ni signature, un remplacement en cours de route est indétectable par celui qui l'installe.

Signer un artefact, c'est y associer une preuve cryptographique de son origine : si l'artefact est modifié après la signature, la vérification échoue. Le consommateur n'a plus à faire confiance au canal de distribution, il vérifie lui-même. L'outil de référence pour les images est Cosign.

Artefact non signéArtefact signé
Origine inconnueOrigine vérifiable
Intégrité incertaineToute modification est détectée
« Faites-moi confiance »« Vérifiez vous-même »

Une attestation de provenance décrit comment l'artefact a été construit : quel commit, quelle pipeline, quel environnement. Le cadre SLSA en fixe les niveaux de garantie. Un SBOM (Software Bill of Materials) est la liste complète des composants de l'artefact, à la manière d'une liste d'ingrédients.

Ces trois preuves répondent à trois questions différentes. La signature dit qui a produit l'artefact, l'attestation dit comment, et le SBOM dit ce qu'il contient. Le jour où une vulnérabilité comme Log4Shell est publiée, le SBOM vous dit en quelques secondes quels artefacts sont concernés.

Contrôle structurel ou contrôle ponctuel : lequel privilégier ?

Section intitulée « Contrôle structurel ou contrôle ponctuel : lequel privilégier ? »

Un contrôle structurel rend une action impossible par construction ; un contrôle ponctuel la vérifie à un instant donné. Le premier est plus solide, parce qu'il ne dépend ni d'une étape qu'on peut retirer ni d'une alerte qu'on peut ignorer. Un scan de vulnérabilités, une analyse statique ou un test de sécurité sur une pull request sont des contrôles ponctuels : utiles, mais contournables, désactivables, ou simplement oubliés.

Le tableau donne cinq contrôles structurels et ce qu'ils rendent impossible. Les contrôles ponctuels restent des filets supplémentaires, pas des remparts. La leçon Bloquer une pipeline n'améliore pas la sécurité montre ce qui se passe quand on attend d'eux plus qu'ils ne peuvent donner.

Contrôle structurelCe qu'il rend impossible
Permissions minimales par défautObtenir plus de droits que prévu
Runners éphémèresFaire persister un code malveillant
Signature exigée au déploiementDéployer un artefact non vérifié
OIDC au lieu de secrets statiquesVoler un identifiant permanent
Liste d'autorisation des actionsExécuter une action tierce non approuvée

L'historique Git trace les fichiers de workflow, pas les réglages de la plateforme. Un secret remplacé, une politique d'actions assouplie, un runner auto-hébergé ajouté à l'organisation : rien de cela n'apparaît dans un commit. Ces changements vivent dans le journal d'audit de la plateforme, et c'est lui qui répond, le jour d'un incident, à la question « qui a desserré ce contrôle, et quand ? ».

Le journal d'audit n'empêche rien : c'est un contrôle de détection et d'investigation, qui ne vaut que s'il est consulté. Sur GitHub, quatre événements résument ce qu'une équipe sécurité doit surveiller sur la CI :

Ce qui changeÉvénement GitHub
La politique Actions de l'organisationorg.update_actions_settings
Un secret Actions d'organisationorg.update_actions_secret
Un nouveau runner auto-hébergéorg.register_self_hosted_runner
La configuration d'un groupe de runnersorg.runner_group_updated

Deux limites, relevées dans la documentation GitHub, décident de la façon de l'exploiter. Seuls les propriétaires de l'organisation y accèdent, et l'interface ne remonte que les 180 derniers jours. L'accès par API, qui permet de l'exporter vers un SIEM, est réservé à GitHub Enterprise Cloud : sans lui, la conservation au-delà de six mois suppose un export manuel régulier. Côté GitLab, les audit events jouent le même rôle, comme le détaille la leçon GitLab CI : audit, conformité et traçabilité.

Où en est votre pipeline ? Un modèle de maturité en quatre niveaux

Section intitulée « Où en est votre pipeline ? Un modèle de maturité en quatre niveaux »

Ce modèle sert à situer une pipeline et à choisir la prochaine étape, pas à tout faire d'un coup. Chaque niveau suppose le précédent : signer des artefacts construits sur un runner persistant aux permissions larges ne protège pas grand-chose. Commencez par l'hygiène de base, qui coûte peu et ferme les attaques les plus fréquentes.

NiveauObjectifContrôles
1. Hygiène de baseFermer les attaques les plus fréquentesPermissions minimales par job, fichiers de verrouillage avec npm ci, aucun secret en dur
2. Épinglage et isolationNe rien exécuter qui puisse changer seulActions et dépendances épinglées par SHA, liste d'autorisation des actions, runners éphémères, un secret par environnement
3. Vérification et traçabilitéProuver ce qui est livréArtefacts signés, SBOM à chaque build, attestations de provenance, journal d'audit consulté et conservé
4. Confiance zéroNe plus stocker d'identifiant durableOIDC partout où c'est possible, vérification obligatoire avant déploiement, surveillance des runners
  • Une pipeline compromise donne accès aux secrets, au code, aux artefacts et à la production, à chaque exécution.
  • Les quatre surfaces de contrôle sont les identités, les dépendances, les runners et les artefacts.
  • Moindre privilège : permissions vidées au niveau du workflow, puis accordées job par job.
  • Épinglez par SHA de commit : l'attaque tj-actions de mars 2025 a déplacé des tags, pas des SHA.
  • Runners éphémères et isolation réseau suppriment la persistance d'un job à l'autre.
  • Signature, provenance et SBOM disent qui a produit l'artefact, comment, et ce qu'il contient.
  • Privilégiez les contrôles structurels, qui rendent l'attaque impossible, aux contrôles ponctuels, qui la constatent.
  • Une liste d'autorisation d'organisation décide quel code tiers s'exécute ; le journal d'audit dit qui a modifié les réglages que Git ne voit pas.

Vérifiez que l'essentiel de cette leçon est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 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 projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn