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)

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 chaîne d'intégration concentre trois choses qu'un attaquant recherche : des identifiants vers la production, un droit d'écriture sur les artefacts livrés, et une exécution de code automatique que personne ne relit. C'est cette combinaison, plus que chaque élément isolé, qui en fait une cible de premier plan.

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

La question n'est pas de savoir si le pipeline doit s'authentifier, mais avec quoi et pour combien de temps. Un secret de longue durée stocké dans le dépôt reste valable après le départ de son auteur et après la compromission qui l'a fuité.

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.

Ces quatre pratiques réduisent la portée d'un identifiant compromis, sans supposer qu'il ne le sera jamais.

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 ?

Le code que vous écrivez ne représente qu'une fraction de ce qui s'exécute. Le reste vient de paquets tiers dont vous ne contrôlez ni les mainteneurs, ni les mises à jour, ni les scripts d'installation.

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.

Ces pratiques visent le même objectif : rendre reproductible ce que le pipeline installe, et détectable ce qui change.

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 ?

Le runner exécute le code de vos jobs avec ses propres droits sur sa propre machine. Tout ce qu'il conserve d'une exécution à l'autre, cache, identifiants, fichiers temporaires, devient accessible au job suivant, y compris s'il provient d'une contribution externe.

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.

Ces pratiques suppriment la persistance entre deux exécutions, qui est le vecteur principal de contamination d'un job à l'autre.

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 ?

Entre la construction d'un artefact et son déploiement, rien ne prouve par défaut qu'il s'agit du même contenu. Sans empreinte vérifiable ni signature, un remplacement en cours de route est indétectable par celui qui l'installe.

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

Ces pratiques rendent l'artefact vérifiable par son consommateur, sans qu'il ait à faire confiance au canal de distribution.

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

É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

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