Aller au contenu

Sécuriser une pipeline CI/CD

Mise à jour :

Pourquoi sécuriser une pipeline ?

Une pipeline CI/CD (Continuous Integration / Continuous Delivery) est le système automatisé qui teste, compile et déploie votre code. C’est un moteur d’exécution qui a accès à tout ce qui compte :

  • Vos secrets (mots de passe, clés API, tokens d’accès)
  • Votre code source (tout ce que vous développez)
  • Vos environnements de production (là où tournent vos applications)

L’analogie : Imaginez que votre pipeline soit un employé avec un trousseau de clés qui ouvre toutes les portes de l’entreprise. Si quelqu’un vole ce trousseau ou manipule cet employé, il a accès à tout.

Ce guide présente un modèle conceptuel pour sécuriser une pipeline. L’objectif n’est pas de lister des outils — c’est de comprendre les surfaces de contrôle sur lesquelles agir, quel que soit l’outil que vous utilisez.

La pipeline comme surface d’attaque

Ce qu’un attaquant peut obtenir

Une pipeline compromise offre un accès privilégié à plusieurs ressources critiques :

RessourceCe que l’attaquant peut faireExemple concret
SecretsVoler les tokens d’API, clés SSH, credentials cloudAccéder à votre compte AWS avec vos propres clés
Code sourceModifier le code avant compilationAjouter une backdoor invisible
ArtefactsInjecter du malware dans les livrablesVos utilisateurs téléchargent une version infectée
ProductionDéployer du code malveillant directementPrendre le contrôle de vos serveurs

Les quatre piliers de la sécurité

La sécurité d’une pipeline repose sur quatre piliers. Chaque pilier représente une décision de confiance qui doit être explicite et vérifiable :

PilierQuestion cléRisque si négligé
IdentitésQui a le droit de faire quoi ?Accès excessifs, secrets volés
DépendancesÀ quel code externe faisons-nous confiance ?Code malveillant exécuté
RunnersOù s’exécute le code ?Contamination entre jobs
ArtefactsComment prouver l’origine du livrable ?Distribution de malware

Pilier 1 : Identités et permissions

Le problème expliqué simplement

Votre pipeline doit s’authentifier auprès de nombreux services : registres de conteneurs (pour stocker vos images), clouds (AWS, Azure, GCP), outils de déploiement. Ces authentifications reposent sur des secrets — des identifiants qui prouvent qui vous êtes.

L’analogie : C’est comme avoir des badges d’accès pour différentes zones d’un bâtiment. Si vous perdez un badge “accès total”, le voleur peut aller partout. Si vous n’avez qu’un badge “visiteur”, les dégâts sont limités.

Les bonnes pratiques

1. Principe du moindre privilège

Chaque job n’a que les permissions strictement nécessaires. Pas plus.

# ❌ Permissions excessives — donne tous les droits
permissions: write-all
# Problème : si ce job est compromis, l'attaquant peut tout faire
# ✓ Permissions minimales — seulement ce qui est nécessaire
permissions:
contents: read # peut lire le code, mais pas le modifier
packages: write # peut publier des packages, rien d'autre

2. Tokens éphémères plutôt que secrets permanents

Un token éphémère expire automatiquement après quelques minutes. Un secret statique reste valide indéfiniment (jusqu’à révocation manuelle).

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

OIDC (OpenID Connect) est un protocole qui permet d’obtenir des tokens éphémères. Au lieu de stocker un secret permanent, la pipeline demande un token temporaire à chaque exécution.

3. Séparation par environnement

Les secrets de staging (environnement de test) doivent être différents des secrets de production. Ainsi, si un secret de développement fuite, la production reste protégée.

Questions à vous poser

Avant de configurer les accès d’un job, demandez-vous :

  • Ce token a-t-il besoin d’écrire, ou seulement de lire ?
  • Peut-on utiliser OIDC au lieu d’un secret statique ?
  • Que se passe-t-il si ce secret est volé ?
  • Quand ce secret a-t-il été renouvelé pour la dernière fois ?

Pilier 2 : Dépendances

Le problème expliqué simplement

Chaque npm install, chaque pip install, chaque action GitHub tierce est du code qui s’exécute avec les permissions de votre pipeline. Ce code provient de mainteneurs que vous ne connaissez pas.

L’analogie : C’est comme inviter des inconnus chez vous et leur donner vos clés. La plupart sont honnêtes, mais il suffit d’un seul malveillant pour que tout soit compromis.

Les bonnes pratiques

1. Épingler par hash (pas par tag)

Un tag comme @v4 ou @latest est mutable — le mainteneur peut le modifier à tout moment. Un hash SHA est immuable — c’est l’empreinte unique d’une version précise du code.

# ❌ Tag mutable — peut changer sans prévenir
- uses: actions/checkout@v4
# Risque : demain, @v4 pourrait pointer vers du code différent
# ✓ Hash immuable — toujours le même code
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
# Garantie : ce hash correspond toujours exactement au même code

2. Commiter les lockfiles

Un lockfile (comme package-lock.json ou poetry.lock) enregistre les versions exactes de toutes vos dépendances.

Sans lockfileAvec lockfile
npm install télécharge les dernières versionsnpm ci installe exactement les versions enregistrées
Résultat différent à chaque exécutionRésultat identique à chaque exécution
Une mise à jour peut casser le buildMises à jour contrôlées et testées

3. Auditer avant d’ajouter

Avant d’ajouter une nouvelle dépendance, vérifiez :

CritèrePourquoi c’est important
Qui la maintient ?Un mainteneur unique est un risque (bus factor)
Depuis combien de temps ?Une dépendance récente n’a pas fait ses preuves
Combien de téléchargements ?Peu de téléchargements = peu d’yeux pour détecter les problèmes
Le code est-il lisible ?Du code obfusqué cache peut-être quelque chose

Questions à vous poser

  • Cette dépendance est-elle vraiment nécessaire ?
  • Avons-nous vérifié son code source ?
  • Que se passe-t-il si le mainteneur perd l’accès à son compte ?
  • Avons-nous un plan si cette dépendance disparaît ?

Pilier 3 : Runners et isolation

Le problème expliqué simplement

Un runner, c’est la machine (physique ou virtuelle) qui exécute votre pipeline. Cette machine exécute du code potentiellement dangereux : tests, builds, scripts. Si l’isolation est insuffisante, un job compromis peut contaminer les suivants.

L’analogie : Imaginez une cuisine partagée. Si quelqu’un prépare un plat contaminé et ne nettoie pas après lui, le cuisinier suivant utilise des ustensiles sales. Un runner persistant, c’est une cuisine qui n’est jamais nettoyée entre les services.

Les bonnes pratiques

1. Runners éphémères

Un runner éphémère est créé pour un job, puis détruit. Aucun état ne persiste entre les exécutions.

Runner persistantRunner éphémère
Job 1 modifie des fichiersJob 1 s’exécute, runner détruit
Job 2 hérite de ces modificationsJob 2 obtient un runner neuf
Un malware peut persisterImpossible de persister quoi que ce soit

2. Isolation réseau

Le runner ne doit accéder qu’aux ressources strictement nécessaires. Pas d’accès au réseau interne par défaut.

3. Séparation des contextes (forks et PRs externes)

Quand quelqu’un extérieur à votre organisation ouvre une Pull Request, le code qu’il propose s’exécute sur vos runners. Ce code ne doit jamais avoir accès à vos secrets.

Schéma : Séparation des contextes entre branches internes et PRs externes

Questions à vous poser

  • Le runner est-il détruit après chaque job ?
  • Que peut voir un job malveillant exécuté sur ce runner ?
  • Les forks ont-ils accès aux mêmes ressources que les branches internes ?
  • Comment détecterait-on une compromission du runner ?

Pilier 4 : Artefacts et chaîne de confiance

Le problème expliqué simplement

Un artefact, c’est le résultat de votre pipeline : une image Docker, un fichier exécutable, un package npm. C’est ce que vos utilisateurs téléchargent et exécutent.

Si la pipeline est compromise, l’artefact l’est aussi. Comment prouver qu’un artefact est légitime et n’a pas été modifié ?

L’analogie : C’est comme un scellé sur un médicament. Sans le scellé, impossible de savoir si quelqu’un a ouvert la boîte et modifié le contenu. La signature cryptographique est le “scellé numérique” de vos artefacts.

Les bonnes pratiques

1. Signature cryptographique

Signer un artefact, c’est y apposer une empreinte numérique qui prouve son origine. Si l’artefact est modifié après signature, la vérification échoue.

Artefact non signéArtefact signé
Origine inconnueOrigine vérifiable
Intégrité incertaineIntégrité garantie
”Faites-moi confiance""Vérifiez vous-même”

2. Attestations de provenance

Une attestation est un document qui décrit comment l’artefact a été construit :

  • Quel commit a produit cet artefact ?
  • Quelle pipeline l’a construit ?
  • Dans quel environnement ?
  • Avec quelles dépendances ?

3. SBOM (Software Bill of Materials)

Un SBOM, c’est la liste complète de tous les composants inclus dans votre artefact. C’est comme la liste des ingrédients sur un emballage alimentaire.

Pourquoi c’est utile : Quand une vulnérabilité est découverte (ex: Log4Shell), vous savez en quelques secondes si vos artefacts sont concernés.

Artefact complet
├── Signature (qui a signé → preuve d'origine)
├── Attestation (comment c'est construit → traçabilité)
└── SBOM (ce qu'il contient → inventaire)

Questions à vous poser

  • Nos artefacts sont-ils signés ?
  • Peut-on vérifier leur provenance ?
  • Avons-nous un inventaire de ce qu’ils contiennent (SBOM) ?
  • Qui peut publier des artefacts dans notre registre ?

Contrôles structurels vs contrôles ponctuels

Il existe deux types de contrôles de sécurité, et ils ne se valent pas.

Contrôles ponctuels

Ce sont des vérifications exécutées à un moment précis :

  • Scan de vulnérabilités avant déploiement
  • Analyse statique du code
  • Tests de sécurité sur une Pull Request

Le problème : un contrôle ponctuel peut être contourné, désactivé, ou simplement oublié.

Contrôles structurels

Ce sont des contraintes architecturales impossibles à contourner :

Contrôle structurelPourquoi c’est plus solide
Permissions minimales par défautImpossible d’avoir plus de droits que prévu
Runners éphémèresImpossible de persister un malware
Signature obligatoire pour déployerImpossible de déployer un artefact non vérifié
OIDC au lieu de secrets statiquesImpossible de voler un token permanent

L’analogie : Un contrôle ponctuel, c’est un vigile qui vérifie les badges. Un contrôle structurel, c’est une porte qui ne s’ouvre physiquement qu’avec le bon badge. Le vigile peut être distrait ; la porte, non.

Modèle de maturité

Évaluez où vous en êtes et progressez étape par étape :

Niveau 1 : Hygiène de base

Les fondamentaux, à mettre en place en priorité :

  • Permissions minimales sur chaque job
  • Lockfiles committés et utilisés (npm ci plutôt que npm install)
  • Aucun secret en dur dans le code

Niveau 2 : Épinglage et isolation

Renforcement des contrôles :

  • Dépendances et actions épinglées par hash SHA
  • Runners éphémères (détruits après chaque job)
  • Secrets différents par environnement (dev ≠ prod)

Niveau 3 : Vérification et traçabilité

Capacité à prouver et auditer :

  • Artefacts signés cryptographiquement
  • SBOM généré à chaque build
  • Attestations de provenance

Niveau 4 : Confiance zéro

Sécurité maximale :

  • OIDC au lieu de secrets statiques partout où c’est possible
  • Politique de vérification obligatoire avant tout déploiement
  • Monitoring des comportements anormaux sur les runners

À retenir

  • Une pipeline compromise donne accès à vos secrets, votre code, et votre production
  • Les quatre piliers sont : identités, dépendances, runners, artefacts
  • Moindre privilège : chaque job n’a que les permissions strictement nécessaires
  • Épinglez par hash : les tags sont mutables, les SHA ne le sont pas
  • Runners éphémères : détruire le runner après chaque job empêche la persistance
  • Signez vos artefacts : prouvez leur origine et leur intégrité
  • Privilégiez les contrôles structurels (impossibles à contourner) aux contrôles ponctuels

Liens utiles