Aller au contenu
Culture DevOps high

Sécurité de la supply chain logicielle

23 min de lecture

En décembre 2021, la vulnérabilité Log4Shell a exposé une réalité inconfortable : la plupart des organisations ne savaient pas si elles utilisaient Log4j. La bibliothèque était présente dans des milliers d’applications, souvent comme dépendance transitive invisible. Les équipes ont passé des semaines à auditer manuellement leurs systèmes pendant que les attaquants exploitaient la faille.

Cette crise a révélé un problème structurel : nous ne savons pas ce qui compose nos logiciels. La supply chain logicielle — l’ensemble des composants, processus et personnes impliqués dans la création d’un logiciel — est devenue le maillon faible de la sécurité.

Analogie : Imaginez la construction d’une voiture. Vous ne fabriquez pas chaque vis, chaque boulon, chaque composant électronique. Vous les achetez à des fournisseurs. Si l’un d’eux vous livre des freins défectueux, votre voiture sera dangereuse, même si votre assemblage est parfait.

En développement logiciel, c’est identique. La supply chain logicielle est l’ensemble des fournisseurs, outils et processus qui interviennent entre votre première ligne de code et l’application déployée en production. Chaque maillon peut être compromis, et une seule faille suffit à contaminer l’ensemble.

Voici les composants typiques d’une supply chain logicielle, avec les risques spécifiques à chaque étape :

ComposantExemplesRisques associés
Code sourceVotre code, contributions externesInjection de code malveillant
Dépendancesnpm, PyPI, Maven, Go modulesCVE, typosquatting, compte compromis
Outils de buildCompilateurs, bundlers, CI/CDCompromission du processus de build
ArtefactsImages Docker, binaires, packagesTampering, substitution
InfrastructureRegistries, CDN, serveurs de déploiementMan-in-the-middle, compromission

Chaque ligne de ce tableau représente un point d’entrée potentiel pour un attaquant. Une dépendance compromise peut exfiltrer vos secrets. Un outil de build modifié peut injecter un backdoor. Une infrastructure piratée peut distribuer une version corrompue de votre application.

Les chiffres donnent le vertige :

  • Une application Node.js moyenne a 600+ dépendances (directes et transitives)
  • 80% du code d’une application moderne vient de dépendances tierces
  • En 2023, 245 000 packages malveillants ont été détectés sur les registries publics
  • Le temps moyen pour détecter un package compromis : 430 jours

Ce que ces chiffres signifient concrètement : Quand vous écrivez une application de 10 000 lignes de code, vous faites en réalité confiance à 40 000 lignes de code écrites par des inconnus. Si l’un de ces auteurs voit son compte npm compromis, votre application peut devenir un vecteur d’attaque. Et pire : vous ne le découvrirez probablement qu’un an plus tard, quand le malware sera déjà en production.

C’est exactement ce qui s’est passé avec SolarWinds, Codecov, et des dizaines d’autres incidents.

Face à la complexité de la supply chain, trois questions fondamentales émergent. Si vous ne pouvez pas y répondre avec certitude, vous êtes vulnérable.

Premier réflexe : Face à une nouvelle CVE critique (comme Log4Shell), pouvez-vous identifier en moins de 10 minutes tous les systèmes affectés ? Si la réponse est “non”, c’est un problème de transparence.

Deuxième réflexe : Quand vous déployez une image Docker en production, êtes-vous sûr qu’elle n’a pas été modifiée entre le build et le déploiement ? Si vous ne pouvez pas le prouver, c’est un problème d’intégrité.

Troisième réflexe : Pouvez-vous affirmer que votre artefact provient exactement du commit Git que vous pensez, construit par votre CI/CD officiel ? Si vous devez fouiller dans les logs pour vérifier, c’est un problème de provenance.

Voici comment ces trois piliers se matérialisent techniquement :

PilierQuestionSolution
TransparenceQu’est-ce qui compose mon logiciel ?SBOM
IntégritéMon logiciel a-t-il été modifié ?Signatures, SLSA
ProvenanceD’où vient mon logiciel ?Attestations, logs de transparence

Pourquoi ces trois piliers sont indissociables : Un SBOM (transparence) sans signature (intégrité) peut être falsifié par un attaquant. Une signature sans SBOM ne vous dit pas si la dépendance vulnérable est présente. Une attestation de provenance sans les deux ne prouve rien sur le contenu réel de l’artefact. C’est l’intersection des trois qui crée la sécurité.

Analogie : Quand vous achetez un produit alimentaire, vous lisez la liste des ingrédients. Farine, eau, sel, conservateurs… Si demain un conservateur est déclaré cancérigène, le fabricant peut rappeler tous les produits qui en contiennent. Sans cette liste, impossible de savoir quels produits sont concernés.

Un SBOM (Software Bill of Materials) est exactement cette liste d’ingrédients, mais pour le logiciel. C’est l’inventaire exhaustif de tous les composants qui composent votre application :

  • Dépendances directes : les bibliothèques que vous avez explicitement ajoutées (npm install express)
  • Dépendances transitives : les bibliothèques utilisées par vos dépendances (express dépend de 50+ packages)
  • Versions exactes : pas “lodash 4.x”, mais “lodash 4.17.21”
  • Licences : MIT, Apache 2.0, GPL… (crucial pour la conformité juridique)
  • Hashes : empreintes cryptographiques pour vérifier l’intégrité

Sans SBOM, vous êtes aveugle. Quand une vulnérabilité comme Log4Shell est annoncée, vous devez pouvoir répondre en minutes, pas en semaines :

QuestionSans SBOMAvec SBOM
Suis-je affecté par CVE-2024-XXXX ?”Je ne sais pas, il faut vérifier”Requête automatique : oui/non
Quelles applications utilisent OpenSSL < 3.0 ?Audit manuel de tous les projetsExport filtré instantané
Quelles licences sont présentes ?Analyse juridique coûteuseListe générée automatiquement

Un SBOM, c’est comme un format de facture : tout le monde pourrait inventer le sien, mais pour que les systèmes se parlent, il faut des standards. Deux formats se sont imposés, chacun avec son histoire et ses forces.

CycloneDX vient du monde de la sécurité applicative (OWASP, les gens qui maintiennent le Top 10 des vulnérabilités web). Il est optimisé pour répondre rapidement à une CVE : métadonnées de sécurité riches, support natif des VEX (Vulnerability Exploitability eXchange, pour dire “oui la CVE existe, mais elle n’est pas exploitable dans mon contexte”).

SPDX vient du monde de l’open source Linux Foundation. Il est standardisé ISO et se concentre sur la conformité juridique : qui a écrit quoi, sous quelle licence, avec quelles obligations. Si votre entreprise doit prouver qu’elle respecte les licences GPL, SPDX est votre allié.

FormatOrigineForcesCas d’usage
CycloneDXOWASPRiche en métadonnées de sécurité, VEX intégréSécurité, vulnérabilités
SPDXLinux FoundationStandard ISO, focus licencesConformité, juridique

En pratique : Si votre priorité est la sécurité (réagir vite aux CVE), choisissez CycloneDX. Si votre priorité est la conformité légale, choisissez SPDX. Les deux formats coexistent, et les outils modernes supportent généralement les deux.

{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"components": [
{
"type": "library",
"name": "lodash",
"version": "4.17.21",
"purl": "pkg:npm/lodash@4.17.21",
"licenses": [{ "license": { "id": "MIT" }}]
}
]
}

Plusieurs outils permettent de générer des SBOM :

Fenêtre de terminal
# Avec Syft (Anchore) - recommandé
syft packages dir:. -o cyclonedx-json > sbom.json
# Avec Trivy
trivy fs --format cyclonedx -o sbom.json .
# Pour une image Docker
syft packages registry.example.com/myapp:v1.0.0 -o spdx-json > sbom.json
name: Generate SBOM
on:
push:
branches: [main]
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
format: cyclonedx-json
output-file: sbom.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.json

SLSA (Supply-chain Levels for Software Artifacts, prononcé “salsa”) est un framework qui définit des niveaux de sécurité progressifs pour la supply chain.

Analogie : Pensez aux normes de sécurité automobile. Une voiture peut avoir :

  • Niveau 1 : Des ceintures de sécurité (c’est mieux que rien)
  • Niveau 2 : Des airbags (protection automatique)
  • Niveau 3 : ABS + contrôle de stabilité (le système vous empêche de faire des erreurs)
  • Niveau 4 : Crash-tests 5 étoiles + systèmes redondants (n’importe qui peut vérifier la sécurité)

SLSA fait la même chose pour le logiciel. Au lieu de dire “c’est sécurisé” ou “c’est pas sécurisé”, il définit quatre paliers mesurables qui répondent à une question : Peut-on faire confiance à cet artefact ?

Chaque niveau rajoute des garanties techniques qui rendent la falsification plus difficile :

NiveauExigencesCe que ça prouveEffort
SLSA 1Provenance documentéeQuelqu’un a documenté le buildFaible
SLSA 2Provenance générée automatiquementLe système de build a généré la provenanceMoyen
SLSA 3Build isolé, provenance non falsifiableLe développeur ne peut pas falsifierÉlevé
SLSA 4Build hermétique, reproductibleN’importe qui peut vérifierTrès élevé

Pourquoi une progression ? Parce que passer directement au niveau 4 est irréaliste pour la plupart des organisations. SLSA 2 est un bon compromis : la provenance est générée automatiquement par votre CI/CD (GitHub Actions, GitLab CI), donc un développeur malveillant ne peut pas la modifier facilement. SLSA 3 va plus loin : même avec un accès root au runner CI, la falsification devient extrêmement difficile.

Une attestation de provenance SLSA contient :

{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{
"name": "ghcr.io/myorg/myapp",
"digest": { "sha256": "abc123..." }
}],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://github.com/slsa-framework/slsa-github-generator/container@v1",
"externalParameters": {
"source": {
"uri": "git+https://github.com/myorg/myapp@refs/heads/main",
"digest": { "sha1": "def456..." }
}
}
},
"runDetails": {
"builder": { "id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v1.9.0" },
"metadata": {
"invocationId": "https://github.com/myorg/myapp/actions/runs/123456789"
}
}
}
}

Cette attestation prouve :

  • Quoi : L’image ghcr.io/myorg/myapp avec ce digest
  • D’où : Du repo myorg/myapp, commit def456...
  • Comment : Via le workflow GitHub Actions generator_container_slsa3.yml
  • Quand : Run ID 123456789
name: Release with SLSA
on:
push:
tags: ['v*']
jobs:
build:
outputs:
digest: ${{ steps.build.outputs.digest }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push
id: build
run: |
docker build -t ghcr.io/${{ github.repository }}:${{ github.ref_name }} .
docker push ghcr.io/${{ github.repository }}:${{ github.ref_name }}
echo "digest=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/${{ github.repository }}:${{ github.ref_name }} | cut -d'@' -f2)" >> $GITHUB_OUTPUT
provenance:
needs: build
permissions:
actions: read
id-token: write
packages: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
with:
image: ghcr.io/${{ github.repository }}
digest: ${{ needs.build.outputs.digest }}
registry-username: ${{ github.actor }}
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}

Sigstore simplifie radicalement la signature d’artefacts en éliminant le problème le plus complexe : la gestion des clés privées.

Le problème historique : pourquoi personne ne signait

Section intitulée « Le problème historique : pourquoi personne ne signait »

Imaginez que pour prouver votre identité, vous deviez porter en permanence un coffre-fort contenant votre passeport. Si vous le perdez, votre identité est volée. Si quelqu’un le vole, il peut se faire passer pour vous. Vous devez le garder accessible 24/7 (pour prouver qui vous êtes), mais absolument sécurisé (pour qu’on ne vous le vole pas). C’est impossible.

Avant Sigstore, signer un artefact logiciel était exactement ce cauchemar :

  1. Générer une paire de clés : Créer une clé privée (votre passeport numérique)
  2. Stocker la clé privée de manière sécurisée : Coffre-fort chiffré, HSM, KMS… (coûteux)
  3. Distribuer la clé publique : Comment les autres savent-ils que cette clé publique est vraiment la vôtre ?
  4. Gérer la rotation des clés : Changer de clé tous les X mois (cauchemar opérationnel)
  5. Révoquer les clés compromises : Si la clé fuite, comment annuler toutes les signatures passées ?

Résultat : 95% des projets open source ne signaient rien. C’était trop complexe, trop cher, trop risqué. Les développeurs préféraient ne rien faire plutôt que de mal gérer une clé privée.

Sigstore résout ce problème avec une idée brillante : et si on n’avait pas besoin de gérer des clés privées ?

Sigstore utilise des certificats éphémères liés à votre identité OIDC (GitHub, GitLab, Google). Voici comment ça fonctionne concrètement :

Scénario : Vous êtes développeur chez Acme Corp. Votre CI/CD GitHub Actions doit signer une image Docker.

  1. Vous vous authentifiez via OIDC → Votre workflow GitHub Actions prouve son identité auprès de Sigstore : “Je suis le workflow release.yml du repo acme/app, déclenché par le tag v1.0.0, par l’utilisateur alice.”

  2. Fulcio émet un certificat de courte durée (10 minutes) → Sigstore vous donne un certificat qui dit : “Ce certificat est valide pendant 10 minutes et atteste que le signataire est bien le workflow GitHub Actions de acme/app.”

  3. Vous signez avec ce certificat → Votre image Docker ghcr.io/acme/app:v1.0.0 est signée avec ce certificat temporaire.

  4. La signature est enregistrée dans Rekor (log de transparence) → Un registre public et immuable enregistre : “Le 20 janvier 2026 à 14h32, le workflow acme/app/.github/workflows/release.yml a signé l’image sha256:abc123…”

  5. Le certificat expire, mais la signature reste vérifiable → 10 minutes plus tard, le certificat est inutilisable. Mais n’importe qui peut vérifier la signature en consultant Rekor : “Cette image a bien été signée par le workflow officiel d’Acme Corp.”

Résultat : Pas de clé privée à gérer (pas de risque de fuite), mais une traçabilité complète et vérifiable publiquement. Si un attaquant compromet votre repo, il peut signer une fois pendant 10 minutes, mais chaque signature est enregistrée dans Rekor. L’attaque sera visible.

ComposantRôleAnalogie
CosignCLI pour signer/vérifierLe stylo qui signe
FulcioAutorité de certificationLe notaire qui vérifie l’identité
RekorLog de transparenceLe registre public des actes
GitsignSignature des commits GitCosign pour Git
Fenêtre de terminal
# Signer (ouvre une fenêtre d'authentification OIDC)
cosign sign ghcr.io/myorg/myapp:v1.0.0
# Vérifier
cosign verify \
--certificate-identity "https://github.com/myorg/myrepo/.github/workflows/release.yml@refs/tags/v1.0.0" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myapp:v1.0.0

Kubernetes peut vérifier les signatures avant de déployer une image :

# Policy Kyverno
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-signatures
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign-signature
match:
resources:
kinds: [Pod]
verifyImages:
- image: "ghcr.io/myorg/*"
key: |-
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

Les dépendances sont le vecteur d’attaque le plus courant dans la supply chain logicielle. La raison est simple : vous ne contrôlez pas leur code, mais vous l’exécutez avec les mêmes privilèges que le vôtre.

Scénario réel : En 2021, un développeur a compromis son propre package npm ua-parser-js (8 millions de téléchargements par semaine) pour y injecter un cryptominer. En quelques heures, des milliers d’applications ont commencé à miner de la cryptomonnaie pour l’attaquant, sans que personne ne s’en rende compte. Pourquoi ? Parce que les versions n’étaient pas épinglées, et que npm install a automatiquement téléchargé la version compromise.

Plusieurs pratiques complémentaires permettent de réduire ce risque de manière pragmatique.

Épingler les versions : arrêter de faire confiance aux futurs

Section intitulée « Épingler les versions : arrêter de faire confiance aux futurs »

Quand vous écrivez "lodash": "^4.17.0" dans votre package.json, vous dites à npm : “Télécharge n’importe quelle version 4.17.x, y compris celles qui n’existent pas encore.” C’est un chèque en blanc.

Si demain le compte npm du mainteneur de lodash est compromis (mot de passe faible, pas de 2FA), l’attaquant peut publier lodash@4.17.22 avec un backdoor. Votre prochain npm install téléchargera cette version malveillante. Vous aurez fait confiance à une version que vous n’avez jamais testée.

Ne laissez jamais le gestionnaire de packages choisir la version pour vous :

// ❌ Dangereux
{
"dependencies": {
"lodash": "^4.17.0", // Accepte 4.17.x, y compris futures versions compromises
"express": "*" // N'importe quelle version !
}
}
// ✅ Sécurisé
{
"dependencies": {
"lodash": "4.17.21", // Version exacte
"express": "4.18.2"
}
}

Épingler les versions dans package.json ne suffit pas. Pourquoi ? Parce que vos dépendances ont elles-mêmes des dépendances (dépendances transitives), et vous ne contrôlez pas leurs versions.

Exemple : Vous épinglez express@4.18.2. Mais Express dépend de body-parser, qui dépend de iconv-lite, qui dépend de safer-buffer. Si demain safer-buffer publie une nouvelle version avec un malware, et que iconv-lite accepte cette version, votre build téléchargera le malware même si vous n’avez jamais entendu parler de safer-buffer.

Les lock files (package-lock.json, yarn.lock, Pipfile.lock) résolvent ce problème en figeant l’arbre complet des dépendances : chaque package, chaque version, chaque hash. C’est une photo complète de l’état du monde au moment du lock.

Fenêtre de terminal
# Installer uniquement ce qui est dans le lock file
npm ci # Pas `npm install`

Pourquoi npm ci et pas npm install ? Parce que npm install peut mettre à jour le lock file si de nouvelles versions compatibles sont disponibles. npm ci refuse : il installe exactement ce qui est dans le lock file, ou il échoue. C’est ce que vous voulez en CI/CD.

Vérifier les checksums : détecter les substitutions

Section intitulée « Vérifier les checksums : détecter les substitutions »

Le problème : Même avec un lock file, un attaquant qui contrôle le registry (ou un man-in-the-middle) peut substituer un package par une version malveillante qui porte le même nom et la même version.

La solution : Les lock files modernes incluent des hashes cryptographiques (SHA-512) de chaque package. Si le contenu téléchargé ne correspond pas au hash attendu, l’installation échoue.

Fenêtre de terminal
# npm vérifie automatiquement les checksums du lock file
npm ci --ignore-scripts # Bonus : désactive les scripts post-install
# Python avec pip-tools
pip-compile requirements.in --generate-hashes
pip-sync requirements.txt

Pourquoi --ignore-scripts ? Parce que certains packages npm exécutent des scripts arbitraires après installation (post-install hooks). Si un package est compromis, ce script peut exfiltrer vos secrets. En désactivant les scripts, vous limitez la surface d’attaque.

Scanner les vulnérabilités : réagir aux CVE connues

Section intitulée « Scanner les vulnérabilités : réagir aux CVE connues »

Le contexte : Chaque semaine, de nouvelles vulnérabilités (CVE) sont publiées. Si vous ne scannez pas vos dépendances, vous ne saurez pas que vous êtes vulnérable. C’est comme conduire une voiture rappelée pour défaut de frein sans le savoir.

Les scanners de vulnérabilités comparent vos dépendances à des bases de données de CVE connues (NVD, GitHub Advisory Database, etc.) et vous alertent instantanément.

Fenêtre de terminal
# npm audit (intégré à npm)
npm audit --audit-level=high
# Trivy (multi-écosystème : npm, pip, Go, Rust, Java...)
trivy fs --severity HIGH,CRITICAL .
# Grype (Anchore, alternative à Trivy)
grype dir:.

En pratique : Intégrez un de ces scanners dans votre CI/CD. Si une vulnérabilité HIGH ou CRITICAL est détectée, le build échoue. Vous forcez ainsi les équipes à corriger avant de déployer.

La sécurisation de la supply chain est un voyage, pas une destination. Plutôt que de tout implémenter d’un coup, adoptez une approche progressive qui construit la maturité par étapes :

  1. Mois 1-2 : Visibilité

    • Générer des SBOM pour les projets critiques
    • Scanner les dépendances (SCA) dans la CI
    • Inventorier les registries et sources de dépendances
  2. Mois 3-4 : Intégrité

    • Signer les images Docker avec Cosign
    • Vérifier les checksums des dépendances
    • Activer les lock files et npm ci
  3. Mois 5-6 : Provenance

    • Générer des attestations SLSA (niveau 2)
    • Configurer la vérification de signatures dans Kubernetes
    • Auditer les workflows CI/CD
  4. Mois 7+ : Maturité

    • Viser SLSA niveau 3
    • Déployer Dependency-Track pour le suivi centralisé
    • Automatiser la génération de VEX
  • SBOM : Savoir ce qui compose vos logiciels (obligatoire pour répondre aux CVE)
  • SLSA : Framework de maturité, viser niveau 2 minimum
  • Sigstore : Signature keyless, plus d’excuse pour ne pas signer
  • Dépendances : Épingler, verrouiller, scanner, vérifier
  • Progressivité : Commencer par la visibilité, puis intégrité, puis provenance

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.