Aller au contenu
CI/CD & Automatisation medium

Sécuriser une pipeline CI/CD

16 min de lecture

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.

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

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

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.

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

Section intitulée « 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.

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.

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 ?

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.

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

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

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
  • 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 ?

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.

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

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)

Section intitulée « 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

  • 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 ?

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.

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”

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 ?

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)
  • 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 ?

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

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é.

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.

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

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

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)

Capacité à prouver et auditer :

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

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
  • 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