Introduction
Si vous ne pouvez pas reproduire un build, vous ne pouvez pas le vérifier. Cette vérité simple se trouve au cœur de la sécurité de la chaîne d’approvisionnement logicielle. L’intégrité des builds garantit que ce que vous déployez est exactement ce que vous aviez l’intention de construire — rien d’ajouté, rien de modifié, rien d’altéré entre le code source et l’artefact de production.
Ces dernières années, les attaques sur la chaîne d’approvisionnement ont démontré que le processus de build lui-même est une cible de grande valeur. Les attaquants qui compromettent un pipeline de build peuvent injecter du code malveillant dans des logiciels de confiance, affectant des millions d’utilisateurs en aval. La seule défense fiable consiste à rendre les builds reproductibles : à partir des mêmes entrées, vous devez toujours obtenir les mêmes sorties. Lorsque cette garantie est assurée, tout écart devient détectable.
Ce guide parcourt les principes de l’intégrité des builds et des builds reproductibles, explique pourquoi ils sont importants pour la sécurité du CI/CD, et fournit des techniques pratiques — du pinning des dépendances aux systèmes de build hermétiques — que vous pouvez adopter progressivement dans vos propres pipelines.
Qu’est-ce que l’intégrité des builds ?
L’intégrité des builds est la garantie que les entrées de build produisent de manière déterministe les sorties de build. Autrement dit, si vous prenez le même code source, les mêmes dépendances, la même chaîne d’outils et les mêmes instructions de build, vous devez obtenir un artefact identique bit à bit à chaque fois, indépendamment du moment ou de l’endroit où vous exécutez le build.
Pourquoi les builds non reproductibles sont un risque de sécurité
Lorsque les builds ne sont pas reproductibles, vous perdez la capacité de les vérifier. Si deux builds à partir du même code source produisent des binaires différents, comment savoir lequel est correct ? Comment détecter si un attaquant a injecté du code pendant le processus de build ? Vous ne pouvez tout simplement pas. La non-reproductibilité crée un brouillard que les attaquants exploitent.
Considérez le problème de vérification : un auditeur souhaite confirmer qu’un binaire publié correspond à son code source publié. Il récupère le commit étiqueté, lance le build et compare. Si le build n’est pas reproductible, les sorties différeront — et l’auditeur n’a aucun moyen de distinguer une différence légitime (causée par un horodatage ou un ordonnancement aléatoire) d’une modification malveillante.
La relation avec les niveaux SLSA Build
Le framework SLSA (Supply-chain Levels for Software Artifacts) traite directement de l’intégrité des builds à travers ses niveaux de build :
- SLSA Build L1 : Le processus de build est documenté et produit des métadonnées de provenance.
- SLSA Build L2 : Le build s’exécute sur un service hébergé qui génère une provenance authentifiée.
- SLSA Build L3 : L’environnement de build est renforcé, isolé et résistant aux altérations — même par les mainteneurs du projet.
Les builds reproductibles complètent SLSA en fournissant un mécanisme de vérification indépendant. Même si vous faites confiance au service de build (L2/L3), la reproductibilité permet à quiconque de reconstruire à partir du source et de vérifier que la sortie correspond.
Exemples concrets
SolarWinds (2020) : Des attaquants ont compromis le système de build de SolarWinds et injecté une porte dérobée dans la mise à jour de la plateforme Orion. Le code malveillant a été ajouté pendant le processus de build, de sorte que le dépôt de code source semblait propre. Un système de build reproductible aurait rendu cela détectable — reconstruire à partir du source publié aurait produit un artefact différent de celui distribué aux clients.
XZ Utils (2024) : Une attaque sophistiquée sur la chaîne d’approvisionnement a ciblé la bibliothèque de compression xz. Un mainteneur malveillant a introduit du code de porte dérobée obfusqué via l’infrastructure de test du système de build. Le code injecté était conçu pour compromettre l’authentification SSH sur les systèmes Linux affectés. L’attaque a exploité la complexité du processus de build, injectant la charge malveillante via des fichiers de test binaires traités pendant le build. Des builds reproductibles et un examen attentif du comportement au moment du build auraient levé des signaux d’alerte bien plus tôt.
Sources de non-reproductibilité
Comprendre pourquoi les builds diffèrent entre les exécutions est la première étape pour les corriger. Voici les sources les plus courantes de non-déterminisme dans les processus de build :
Horodatages intégrés dans les artefacts
De nombreux outils de build intègrent la date et l’heure actuelles dans les fichiers de sortie. Les fichiers JAR Java contiennent des horodatages dans leurs entrées ZIP. Les compilateurs C/C++ peuvent enregistrer les macros __DATE__ et __TIME__. Les exécutables PE sous Windows incluent un horodatage dans leurs en-têtes. À chaque build, l’horodatage change, produisant une sortie différente.
Ordonnancement non déterministe des fichiers
Les formats d’archive comme tar et zip ne garantissent pas un ordre de fichiers cohérent. L’ordre dans lequel les fichiers sont ajoutés à une archive peut dépendre de l’ordre de listage des répertoires du système de fichiers, qui peut varier entre les machines ou même entre les exécutions sur la même machine. Cela produit des archives différentes avec un contenu identique.
Versions flottantes des dépendances
Si votre configuration de build spécifie express: ^4.18.0 au lieu d’une version exacte, vous pourriez obtenir 4.18.1 aujourd’hui et 4.18.2 demain. Les dépendances non épinglées sont l’une des sources de non-reproductibilité les plus courantes et les plus impactantes.
Différences d’environnement de build
Des versions différentes du système d’exploitation, du compilateur, des bibliothèques système, des paramètres de locale, des configurations de fuseau horaire, et même le nombre de cœurs CPU peuvent affecter la sortie du build. Un build sur Ubuntu 22.04 peut différer d’un build sur Ubuntu 24.04, même avec le même source et les mêmes dépendances.
Téléchargements réseau pendant le build
Les builds qui téléchargent des dépendances au moment du build sont intrinsèquement non reproductibles. Un registre de paquets pourrait servir une version différente, un CDN pourrait retourner du contenu mis en cache ou mis à jour, ou le réseau pourrait être entièrement indisponible. Tout build nécessitant un accès réseau est à la merci de systèmes externes.
Valeurs aléatoires et adresses mémoire
Certains processus de build intègrent des UUID aléatoires, utilisent des ordres d’itération de hash maps non déterministes, ou incluent des adresses mémoire dans leur sortie. Les données de profilage, les informations de couverture et les symboles de débogage peuvent tous introduire du caractère aléatoire dans les artefacts de build.
Builds hermétiques : le standard de référence
Un build hermétique est entièrement autonome : il n’a pas d’accès réseau, toutes les entrées sont explicitement déclarées, et l’environnement de build est entièrement spécifié. Les builds hermétiques sont le standard de référence pour la reproductibilité car ils éliminent des catégories entières de non-déterminisme par construction.
Ce que signifie un build hermétique
Dans un build hermétique :
- Le processus de build ne peut pas accéder au réseau. Toutes les dépendances doivent être pré-téléchargées et déclarées.
- Chaque entrée — code source, dépendances, chaîne d’outils, configuration — est explicitement listée et versionnée.
- L’environnement de build est défini avec précision, jusqu’au système d’exploitation, aux paquets installés et aux variables d’environnement.
- Le build est isolé dans un bac à sable afin qu’il ne puisse pas lire de fichiers non déclarés du système hôte.
Bazel comme système de build hermétique
Bazel est conçu dès le départ pour des builds hermétiques et reproductibles. Il isole chaque action de build dans un bac à sable, déclare explicitement toutes les entrées et sorties, et met en cache les résultats en fonction des hachages des entrées plutôt que des horodatages. Les fonctionnalités de mise en cache distante et d’exécution distante de Bazel maintiennent l’herméticité même dans les environnements de build distribués.
# Bazel WORKSPACE file: all external dependencies declared
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "com_google_protobuf",
sha256 = "a79d19dcdf9139fa4b81206e318e33d245c4c9da1ffed21c87288f9142c5f4ef",
strip_prefix = "protobuf-23.2",
urls = ["https://github.com/protocolbuffers/protobuf/archive/v23.2.tar.gz"],
)
Builds multi-étapes Docker avec images de base épinglées
Les builds multi-étapes Docker peuvent approximer l’herméticité lorsqu’ils sont combinés avec des images de base épinglées et des dépendances pré-téléchargées :
# Stage 1: Build with all dependencies pre-installed
FROM golang@sha256:2c3f3c4a1f8e4c2b7d5e1a9f8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags='-s -w -buildid=' \
-o /app/server ./cmd/server
# Stage 2: Minimal runtime image
FROM gcr.io/distroless/static@sha256:1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b AS runtime
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
Notez l’utilisation de -trimpath pour supprimer les chemins de fichiers locaux du binaire, -buildid= pour effacer l’identifiant de build, et -s -w pour supprimer les informations de débogage. Ces drapeaux sont essentiels pour la reproductibilité des builds Go.
Fichiers de verrouillage
Les fichiers de verrouillage (lock files) sont l’outil le plus accessible pour améliorer la reproductibilité. Ils enregistrent les versions exactes résolues de toutes les dépendances, y compris les dépendances transitives :
- Node.js :
package-lock.jsonouyarn.lock— utilisez toujoursnpm ciau lieu denpm install - Go :
go.sum— enregistre les hachages cryptographiques de toutes les versions de modules - Python :
poetry.lockou la sortie depip-compile— épingle chaque dépendance transitive - Rust :
Cargo.lock— toujours commité pour les projets binaires
Les fichiers de verrouillage doivent toujours être commités dans le contrôle de version. Un build qui ignore les fichiers de verrouillage est un build que vous ne pouvez pas reproduire.
Vendoring des dépendances
Le vendoring va plus loin que les fichiers de verrouillage en stockant le code source réel des dépendances dans votre dépôt. Cela élimine toute dépendance envers les registres externes au moment du build :
# Go: vendor all dependencies
go mod vendor
# Build using vendored dependencies
go build -mod=vendor ./cmd/server
Le vendoring échange la taille du dépôt contre la fiabilité et la reproductibilité. Il est particulièrement précieux pour les builds qui doivent fonctionner dans des environnements isolés (air-gapped) ou pour les projets où la reproductibilité à long terme est importante.
Compromis pratiques : herméticité vs expérience développeur
L’herméticité complète a un coût. Bazel a une courbe d’apprentissage abrupte. Le vendoring augmente la taille du dépôt. Le pré-téléchargement de chaque dépendance nécessite une infrastructure. L’essentiel est d’adopter l’herméticité de manière progressive : commencez par les fichiers de verrouillage et les images épinglées, puis ajoutez le vendoring et le sandboxing à mesure que vos exigences de sécurité l’imposent.
Tout épingler
Le pinning est la pratique consistant à spécifier des versions exactes et immuables pour chaque composant de votre build. Les tags sont mutables — ils peuvent être déplacés pour pointer vers un contenu différent. Les digests et les SHA de commits sont immuables. Épinglez toujours vers des références immuables.
Épingler les images de base par digest
Les tags d’images Docker comme node:20 ou python:3.12-slim peuvent changer à tout moment. Le registre peut pousser une nouvelle image sur le même tag. Épinglez par digest à la place :
# BAD: tag can change at any time
FROM node:20-alpine
# GOOD: pinned to an immutable digest
FROM node@sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
# BETTER: tag for readability, digest for immutability
FROM node:20-alpine@sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
Vous pouvez trouver le digest actuel avec :
docker inspect --format='{{index .RepoDigests 0}}' node:20-alpine
Épingler les GitHub Actions par SHA
Les tags des GitHub Actions sont mutables. Une action compromise peut pousser du code malveillant sur un tag existant. Épinglez toujours vers le SHA complet du commit :
# BAD: tag can be moved to malicious commit
- uses: actions/checkout@v4
# GOOD: pinned to immutable commit SHA
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Utilisez des outils comme ratchet ou pinact pour automatiser le pinning par SHA dans vos fichiers de workflow et conserver les commentaires avec le tag de version pour la lisibilité.
Épingler les versions de la chaîne d’outils
Votre chaîne d’outils de build — compilateur, runtime, gestionnaire de paquets — doit être épinglée en version. Utilisez des fichiers de configuration de gestionnaire de versions pour déclarer les versions exactes :
# .tool-versions (used by asdf version manager)
nodejs 20.11.0
python 3.12.1
golang 1.22.0
rust 1.75.0
# .nvmrc (Node version manager)
20.11.0
# rust-toolchain.toml
[toolchain]
channel = "1.75.0"
components = ["rustfmt", "clippy"]
targets = ["x86_64-unknown-linux-gnu"]
Utiliser Nix pour la reproductibilité de l’environnement de build
Nix fournit l’approche la plus rigoureuse pour la reproductibilité de l’environnement. Un Nix flake déclare l’environnement de build complet comme une fonction de ses entrées :
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = nixpkgs.legacyPackages.${system}; in {
devShells.default = pkgs.mkShell {
buildInputs = [ pkgs.go_1_22 pkgs.nodejs_20 pkgs.protobuf ];
};
});
}
Avec Nix, chaque développeur et chaque runner CI obtient exactement les mêmes versions de chaque outil, jusqu’aux bibliothèques système. Le fichier flake.lock épingle l’ensemble de l’arbre de dépendances vers des révisions Git spécifiques.
Vérifier l’intégrité des builds
Atteindre la reproductibilité n’est que la moitié du combat. Vous devez également la vérifier — confirmer que des builds indépendants à partir du même source produisent effectivement des artefacts identiques.
Comparer les builds entre environnements
La vérification la plus simple consiste à construire le même artefact dans deux environnements différents et à comparer les sorties :
# Build in CI
sha256sum build/output/myapp.tar.gz
# Output: a1b2c3d4... build/output/myapp.tar.gz
# Rebuild locally from the same commit
git checkout v1.2.3
make build
sha256sum build/output/myapp.tar.gz
# Output should match: a1b2c3d4...
Si les hachages correspondent, le build est reproductible. S’ils ne correspondent pas, vous devez investiguer ce qui diffère.
Utiliser diffoscope pour une analyse approfondie
diffoscope est un outil essentiel pour diagnostiquer les problèmes de reproductibilité. Il décompresse récursivement les archives, décompile les binaires et vous montre exactement où deux builds diffèrent :
# Install diffoscope
pip install diffoscope
# Compare two builds
diffoscope build-1/myapp.tar.gz build-2/myapp.tar.gz --html report.html
# Compare two container images
diffoscope image-1.tar image-2.tar --html report.html
Le rapport HTML montre les différences à chaque niveau : métadonnées d’archive, contenu des fichiers, sections binaires et ressources intégrées. C’est inestimable pour identifier et éliminer les sources de non-déterminisme une par une.
Stocker les métadonnées de build
Même avant d’atteindre une reproductibilité complète, l’enregistrement des métadonnées de build assure la traçabilité. Capturez et stockez :
- Le SHA du commit Git et la branche
- Les hachages de toutes les dépendances en entrée
- Les détails de l’environnement de build (version de l’OS, version de la chaîne d’outils, variables d’environnement)
- Les hachages de tous les artefacts de sortie
- Les horodatages et la durée du build
Provenance SLSA
La provenance SLSA est un format standardisé pour les métadonnées de build. Elle enregistre ce qui a été construit, à partir de quel source, en utilisant quel processus de build, et dans quel environnement. Des outils comme slsa-github-generator peuvent automatiquement générer une provenance signée pour vos builds GitHub Actions :
# .github/workflows/release.yml
jobs:
build:
outputs:
digest: ${{ steps.hash.outputs.digest }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- run: make build
- id: hash
run: echo "digest=$(sha256sum myapp | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
provenance:
needs: build
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
with:
base64-subjects: ${{ needs.build.outputs.digest }}
Analyse des images de conteneurs
Pour les images de conteneurs, vérifiez l’intégrité en inspectant les couches de l’image à la recherche de contenu inattendu :
# List all layers in an image
docker history myapp:latest --no-trunc
# Export and inspect image contents
docker save myapp:latest -o image.tar
tar -tf image.tar
# Use dive to inspect layer contents interactively
dive myapp:latest
Builds reproductibles en CI/CD
Les plateformes CI/CD introduisent leurs propres défis de reproductibilité. Les images de runners changent, les caches expirent et les environnements de build sont éphémères. Voici comment atteindre la reproductibilité sur les principales plateformes.
GitHub Actions
Un workflow GitHub Actions reproductible épingle chaque composant externe :
name: Reproducible Build
on:
push:
branches: [main]
jobs:
build:
# Pin the runner image (or use a self-hosted runner with a known image)
runs-on: ubuntu-22.04
steps:
# Pin action by SHA
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
# Pin setup action and toolchain version
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: '1.22.0' # Exact version, not '1.22.x'
# Cache with hash-based key for determinism
- uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
with:
path: ~/go/pkg/mod
key: go-mod-${{ hashFiles('go.sum') }}
# Build with reproducibility flags
- run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags='-s -w -buildid=' \
-o myapp ./cmd/server
# Record artifact hash
- run: sha256sum myapp >> checksums.txt
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: build-artifacts
path: |
myapp
checksums.txt
GitLab CI
Dans GitLab CI, utilisez des images Docker fixes pour les runners et épinglez toutes les dépendances :
# .gitlab-ci.yml
variables:
# Use a specific image digest for the build environment
BUILD_IMAGE: golang@sha256:2c3f3c4a1f8e4c2b7d5e1a9f8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b
stages:
- build
- verify
build:
stage: build
image: $BUILD_IMAGE
script:
- go mod download
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64
go build -trimpath -ldflags='-s -w -buildid='
-o myapp ./cmd/server
- sha256sum myapp | tee checksums.txt
artifacts:
paths:
- myapp
- checksums.txt
cache:
key:
files:
- go.sum
paths:
- /go/pkg/mod
verify-reproducibility:
stage: verify
image: $BUILD_IMAGE
script:
- go mod download
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64
go build -trimpath -ldflags='-s -w -buildid='
-o myapp-verify ./cmd/server
- sha256sum myapp-verify
- diff <(sha256sum myapp | cut -d' ' -f1) <(sha256sum myapp-verify | cut -d' ' -f1)
Utiliser Nix en CI pour une reproductibilité complète
Nix fournit les garanties de reproductibilité les plus solides en CI en spécifiant l'ensemble de la fermeture de build :
# GitHub Actions with Nix
name: Nix Build
on: push
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: cachix/install-nix-action@ba0dd844c9180cbf77aa557a09b7b0d890fbd0fb # v26
with:
nix_path: nixpkgs=channel:nixos-24.05
- run: nix build .#myapp
- run: sha256sum result/bin/myapp
Avec Nix, le fichier flake.lock épingle la version exacte de chaque paquet dans la fermeture de build. Deux développeurs exécutant nix build sur des machines différentes obtiendront une sortie identique, car l'ensemble du graphe de dépendances — y compris le compilateur C, les bibliothèques système et chaque dépendance transitive — est spécifié avec précision.
Builds d'images de conteneurs reproductibles
La construction d'images de conteneurs reproductibles nécessite une attention particulière. La commande traditionnelle docker build intègre des horodatages dans chaque couche. Des outils comme kaniko, BuildKit et ko offrent une meilleure reproductibilité :
# Using BuildKit with reproducible output
DOCKER_BUILDKIT=1 docker build \
--build-arg SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) \
--output type=docker,rewrite-timestamp=true \
-t myapp:latest .
La variable d'environnement SOURCE_DATE_EPOCH est un mécanisme standardisé pour indiquer aux outils de build d'utiliser un horodatage fixe au lieu de l'heure actuelle. De nombreux outils la respectent, notamment GCC, dpkg, tar et zip.
Quand la reproductibilité parfaite n'est pas réalisable
La reproductibilité bit à bit est l'idéal, mais elle n'est pas toujours atteignable — et ce n'est pas grave. Ce qui compte, c'est de comprendre où vous vous situez sur le spectre de la reproductibilité et d'appliquer des contrôles compensatoires là où c'est nécessaire.
Non-reproductibilité acceptable
Certaines non-reproductibilités sont inoffensives. Un horodatage de build dans un fichier de métadonnées qui n'est jamais exécuté n'affecte pas la sécurité de l'artefact. Une entrée de journal enregistrant quand le build a été exécuté n'est pas un risque de sécurité. La distinction clé est de savoir si l'élément non reproductible se trouve dans l'artefact lui-même (risqué) ou uniquement dans les métadonnées qui l'accompagnent (acceptable).
Reproductibilité « suffisante »
Pour de nombreuses équipes, l'objectif pratique est la reproductibilité fonctionnelle : les mêmes entrées produisent la même sortie fonctionnelle. Les binaires peuvent différer dans les horodatages intégrés ou les symboles de débogage, mais ils se comportent de manière identique. Ce niveau de reproductibilité est atteignable avec des outils et pratiques standards :
- Épingler toutes les versions de dépendances avec des fichiers de verrouillage
- Épingler les images de base par digest
- Épingler les actions CI par SHA
- Utiliser des versions fixes de la chaîne d'outils
- Supprimer les horodatages lorsque c'est possible
Contrôles compensatoires
Lorsque la reproductibilité complète n'est pas réalisable, les contrôles compensatoires fournissent des assurances alternatives :
- Signature de code : Signez cryptographiquement vos artefacts afin que les consommateurs puissent vérifier qu'ils proviennent de votre système de build.
- Provenance SLSA : Générez et publiez des métadonnées de provenance qui enregistrent les entrées, l'environnement et le processus de build.
- Nomenclature logicielle (SBOM) : Publiez une liste complète des composants de votre artefact afin que les consommateurs sachent exactement ce qu'ils obtiennent.
- Conservation des journaux de build : Stockez les journaux de build complets pour une analyse forensique en cas de compromission suspectée.
- Builds multi-parties : Faites construire plusieurs parties indépendantes à partir du même source et comparez les résultats.
Le spectre de la reproductibilité
Considérez la reproductibilité comme un spectre, pas comme un état binaire :
- Niveau 0 — Rien : Pas de pinning de version, pas de fichiers de verrouillage, les builds dépendent de la dernière version disponible. C'est là que la plupart des projets commencent.
- Niveau 1 — Dépendances épinglées : Fichiers de verrouillage commités, les dépendances sont en versions fixes. Les builds sont généralement reproductibles.
- Niveau 2 — Environnement épinglé : Les versions de la chaîne d'outils et les images de base sont épinglées. L'environnement de build est contrôlé.
- Niveau 3 — Builds hermétiques : Pas d'accès réseau pendant le build. Toutes les entrées sont explicitement déclarées. Garanties de reproductibilité solides.
- Niveau 4 — Reproductibilité bit à bit : Les builds indépendants produisent des artefacts identiques. Vérification complète possible.
Chaque niveau s'appuie sur le précédent. Passer du Niveau 0 au Niveau 1 est souvent l'amélioration la plus impactante que vous puissiez faire, et elle nécessite un effort minimal.
Conclusion
Les builds reproductibles sont le fondement de la confiance dans la chaîne d'approvisionnement. Sans eux, vous vous fiez aveuglément au fait que votre système de build n'a pas été compromis — une confiance que les clients de SolarWinds, les utilisateurs de XZ Utils et d'innombrables autres ont appris à leurs dépens qu'elle était mal placée.
La bonne nouvelle est que vous n'avez pas besoin d'atteindre la perfection dès le premier jour. Commencez par les bases :
- Commitez vos fichiers de verrouillage et utilisez
npm ciau lieu denpm install. - Épinglez vos images de base par digest dans chaque Dockerfile.
- Épinglez vos actions CI par SHA dans chaque fichier de workflow.
- Épinglez les versions de votre chaîne d'outils avec
.tool-versions,rust-toolchain.tomlou similaire.
Puis ajoutez l'herméticité progressivement : vendorez vos dépendances, utilisez Nix ou Bazel pour l'isolation des builds, supprimez les horodatages de vos artefacts, et mettez en place des jobs de vérification qui reconstruisent et comparent.
Chaque étape sur le spectre de la reproductibilité rend vos builds plus fiables, votre chaîne d'approvisionnement plus auditable et votre logiciel plus sécurisé. Dans un monde où les systèmes de build sont un vecteur d'attaque principal, les builds reproductibles ne sont pas optionnels — ils sont essentiels.