Aller au contenu
SBOM et attestations de provenance

SBOM et attestations de provenance

300 Concevoir ⏱ 1 h 05 dockerbuildkitsyfttrivy

À la fin, vous saurez

  • Expliquer ce qu'est un SBOM, ses formats (SPDX, CycloneDX) et ce qu'il permet
  • Attacher un SBOM et une attestation de provenance à une image avec BuildKit
  • Lire une attestation de provenance SLSA et y retrouver sources, dépendances et paramètres
  • Utiliser des SBOM pour savoir quelles images sont concernées par une vulnérabilité
  • Vérifier qu'une chaîne SBOM produit bien les résultats attendus

Prérequis

Testé avec buildkit 0.33.0 docker 29.8.1 syft 1.52.0 trivy 0.74.0 , vérifié le 1 octobre 2026

Pourquoi

Un mardi matin, une vulnérabilité critique est publiée dans une bibliothèque très répandue. La direction pose deux questions, simples : sommes-nous concernés ? et depuis quand ? Pour y répondre, il faut savoir, pour chaque image en production, quels composants elle contient et dans quelle version, et pour chaque image, de quelles sources et de quelle construction elle provient. Sans ces informations, la réponse prend des jours d'analyse image par image ; avec elles, quelques minutes.

C'est l'épisode Log4Shell de décembre 2021 qui a rendu ces questions célèbres : des organisations entières ont mis des semaines à inventorier où se trouvait la bibliothèque Log4j. Depuis, deux artefacts sont devenus la norme de la chaîne d'approvisionnement logicielle :

  • le SBOM (Software Bill of Materials, nomenclature logicielle) : la liste des composants d'un logiciel, avec leurs versions et identifiants ;
  • l'attestation de provenance : un document qui dit comment un artefact a été produit, à partir de quelles sources, par quel système, avec quels paramètres.

Ils ne sont plus seulement de bonnes pratiques. Le règlement européen Cyber Resilience Act impose aux fabricants de produits comportant des éléments numériques d'identifier et de documenter leurs composants, notamment par une nomenclature logicielle dans un format courant et lisible par machine, à partir du 11 décembre 2027 ; ses obligations de signalement des vulnérabilités activement exploitées s'appliquent, elles, depuis le 11 septembre 2026. Pour Lyneko comme pour ses clients publics, savoir produire et exploiter ces documents fait désormais partie du métier.

Les concepts

Le SBOM

Un SBOM liste, pour un artefact, chaque composant identifiable : paquets du système, bibliothèques des langages, binaires embarqués. Pour chacun : un nom, une version, et surtout un identifiant normalisé qui permet de le retrouver dans les bases de vulnérabilités. L'identifiant le plus utilisé est le purl (package URL) :

pkg:deb/debian/libssl3t64@3.5.7-1~deb13u3?arch=amd64&distro=debian-13.7&upstream=openssl
pkg:pypi/flask@3.1.3
pkg:golang/stdlib@1.26.8

Le type (deb, pypi, golang), l'espace de noms, le nom, la version, et des qualificatifs, dont upstream, le paquet source Debian dont provient le paquet binaire : c'est sous ce nom que Debian publie ses avis de sécurité.

Deux formats dominent, tous deux standardisés :

FormatPorté parPoints forts
SPDXLinux Foundation, norme ISO/IEC 5962Très complet, fort sur les licences
CycloneDXOWASP, norme ECMA-424Orienté sécurité, intègre vulnérabilités et VEX

La provenance et SLSA

L'attestation de provenance répond à « d'où vient cette image ? » : quel dépôt de sources, à quel commit, quel Dockerfile, quelles images de base (avec leurs empreintes), quels paramètres, quel système de construction, quand. Elle suit le modèle SLSA (Supply-chain Levels for Software Artifacts, prononcé « salsa »), un cadre publié par l'OpenSSF qui définit des niveaux de garantie pour la chaîne de construction :

Niveau (piste Build)Exigence principale
L1La provenance existe et décrit la construction
L2Elle est produite et signée par une plateforme de construction hébergée
L3La plateforme est durcie : les constructions sont isolées les unes des autres, et les secrets de signature hors de portée des projets

Les attestations in-toto

SBOM et provenance sont transportés dans le même format d'enveloppe : l'attestation in-toto. Une attestation affirme une propriété (le prédicat, de type SBOM SPDX ou provenance SLSA) à propos d'un ou plusieurs artefacts (le sujet, désigné par son empreinte). BuildKit range ces attestations dans le registre, à côté de l'image : dans l'index, un manifeste supplémentaire par plateforme, de plateforme unknown/unknown, dont les couches sont les attestations (nous l'avons aperçu aux leçons 6 et 7).

En pratique

Note

Le registre local de cette leçon est présenté sur le port 5000 ; sur la machine de test, il écoutait sur un autre port. Les empreintes abrégées par ... dans les commandes sont à remplacer par les vôtres.

Produire SBOM et provenance avec BuildKit

Partons du Dockerfile multi-étapes de Signalements (leçon 2), dans un dépôt Git dont l'origine est celle de l'équipe :

$ git log --oneline | head -1
e2a185c Signalements 2.0
$ git remote get-url origin
https://github.com/lyneko-team/signalements.git

Deux options de docker buildx build suffisent :

$ /usr/bin/time -f "durée : %e s" docker buildx build --progress=quiet \
    --sbom=true --provenance=mode=max -t 127.0.0.1:5000/signalements:2.0 --push .
sha256:f2fa9cc990205bd27f8f00e01050619d40954a21a09ad17e21c1755a1b6ec861
durée : 13.95 s
$ docker buildx imagetools inspect 127.0.0.1:5000/signalements:2.0 --raw \
  | jq -r '.manifests[] | "\(.platform.os)/\(.platform.architecture) \(.annotations["vnd.docker.reference.type"] // "image") \(.digest[0:19])"'
linux/amd64 image sha256:441b944041cd
unknown/unknown attestation-manifest sha256:72821b3fa9cc
  • --sbom=true lance, à la fin de la construction, un analyseur (par défaut une image dérivée de Syft, docker/buildkit-syft-scanner) sur le système de fichiers de l'image finale, et joint le résultat au format SPDX.
  • --provenance=mode=max joint une attestation de provenance complète. Par défaut, BuildKit joint déjà une provenance minimale (mode=min) quand il pousse une image.

Le manifeste d'attestation contient deux couches, une par attestation :

$ curl -s -H 'Accept: application/vnd.oci.image.manifest.v1+json' \
    http://127.0.0.1:5000/v2/signalements/manifests/sha256:72821b3fa9cc... \
  | jq -c '.layers[] | {mediaType, predicate: .annotations["in-toto.io/predicate-type"], size}'
{"mediaType":"application/vnd.in-toto+json","predicate":"https://spdx.dev/Document","size":2085248}
{"mediaType":"application/vnd.in-toto+json","predicate":"https://slsa.dev/provenance/v1","size":20613}

Un SBOM SPDX de 2 Mo, une provenance SLSA v1 de 20 ko.

Lire le SBOM

docker buildx imagetools inspect sait extraire les attestations d'une image dans le registre :

$ docker buildx imagetools inspect 127.0.0.1:5000/signalements:2.0 --format '{{json .SBOM}}' > sbom.json
$ jq -r '.SPDX.spdxVersion, (.SPDX.packages | length)' sbom.json
SPDX-2.3
109
$ jq -r '.SPDX.packages[] | select(.name=="psycopg-c" or .name=="libpq5" or .name=="libssl3t64" or .name=="flask")
         | "\(.name) \(.versionInfo) \([.externalRefs[]? | select(.referenceType=="purl") | .referenceLocator][0])"' sbom.json
flask 3.1.3 pkg:pypi/flask@3.1.3
libpq5 17.11-0+deb13u1 pkg:deb/debian/libpq5@17.11-0%2Bdeb13u1?arch=amd64&distro=debian-13.7&upstream=postgresql-17
libssl3t64 3.5.7-1~deb13u3 pkg:deb/debian/libssl3t64@3.5.7-1~deb13u3?arch=amd64&distro=debian-13.7&upstream=openssl
psycopg-c 3.3.6 pkg:pypi/psycopg-c@3.3.6

109 paquets, chacun avec son purl. On y retrouve la libpq5 de PostgreSQL 17 installée à la leçon 2, l'OpenSSL mis à jour par apt-get upgrade (deb13u3), et les dépendances Python de l'environnement virtuel.

Lire la provenance

$ docker buildx imagetools inspect 127.0.0.1:5000/signalements:2.0 --format '{{json .Provenance}}' > provenance.json
$ jq -r '.SLSA.buildDefinition.resolvedDependencies[] | "\(.uri[0:80]) \(.digest.sha256[0:12] // "")"' provenance.json
pkg:docker/docker/buildkit-syft-scanner@stable-1?platform=linux%2Famd64 ae4f3b554449
pkg:docker/docker/dockerfile@1 4edf897a3ffa
pkg:docker/python@3.14?platform=linux%2Famd64 be8ccd085666
pkg:docker/python@3.14-slim?platform=linux%2Famd64 51dafde81dbd

Les dépendances résolues : chaque image utilisée pendant la construction, avec son empreinte exacte. On y retrouve les deux images de base de la leçon 2 (python:3.14 pour l'étape de construction, python:3.14-slim pour l'exécution), l'image du frontend qui a interprété le Dockerfile (docker/dockerfile:1) et l'analyseur qui a produit le SBOM. Six mois plus tard, on saura exactement quelle version de python:3.14-slim est dans cette image, même si l'étiquette a bougé cent fois.

$ jq '.SLSA.runDetails.metadata.buildkit_metadata.vcs' provenance.json
{
  "localdir:context": ".",
  "localdir:dockerfile": ".",
  "revision": "e2a185ca6ddf17eb539c37e732ffca4c7c8f9048",
  "source": "https://github.com/lyneko-team/signalements.git"
}
$ jq '.SLSA.buildDefinition.externalParameters.configSource' provenance.json
{
  "path": "Dockerfile"
}
$ jq '.SLSA | {buildType: .buildDefinition.buildType, metadata: .runDetails.metadata | {startedOn, finishedOn}}' provenance.json
{
  "buildType": "https://github.com/moby/buildkit/blob/master/docs/attestations/slsa-definitions.md",
  "metadata": {
    "startedOn": "2026-10-01T15:07:46.440577867+02:00",
    "finishedOn": "2026-10-01T15:07:59.544507318+02:00"
  }
}

Buildx a relevé tout seul que le contexte de construction est un dépôt Git, et inscrit l'URL d'origine et le commit exact. Le chemin du Dockerfile et les dates de construction complètent le tableau : de l'image, on remonte au commit qui l'a produite.

mode=min et mode=max

$ docker buildx build --progress=quiet --sbom=false --provenance=mode=min -t 127.0.0.1:5000/signalements:min --push .
$ for f in provenance prov-min; do
    printf "%s : %s octets, llbDefinition=%s, args=%s, vcs=%s\n" $f $(wc -c < $f.json) \
      "$(jq '.SLSA.buildDefinition.internalParameters.buildConfig.llbDefinition | length' $f.json)" \
      "$(jq -c '.SLSA.buildDefinition.externalParameters.request.args | keys' $f.json)" \
      "$(jq -c '.SLSA.runDetails.metadata.buildkit_metadata.vcs.revision // null' $f.json)"
  done
provenance : 39604 octets, llbDefinition=11, args=["cmdline","source"], vcs="e2a185ca6ddf17eb539c37e732ffca4c7c8f9048"
prov-min : 2504 octets, llbDefinition=0, args=["cmdline","source"], vcs="e2a185ca6ddf17eb539c37e732ffca4c7c8f9048"

Les deux modes enregistrent la source et le commit. mode=max y ajoute la définition complète de la construction (le graphe LLB, 11 étapes ici), qui permet de rejouer et d'auditer chaque instruction, et les arguments de construction. Vérifions ce dernier point en passant un argument avec mode=min :

$ docker buildx build --progress=quiet --sbom=false --provenance=mode=min --build-arg ESSAI=visible \
    -t 127.0.0.1:5000/signalements:min2 --push .
$ docker buildx imagetools inspect 127.0.0.1:5000/signalements:min2 --format '{{json .Provenance}}' \
  | jq -c '.SLSA.buildDefinition.externalParameters.request.args'
{"cmdline":"docker/dockerfile:1","source":"docker/dockerfile:1"}

En mode=min, l'argument ESSAI n'apparaît pas ; en mode=max, il apparaîtrait, comme le mot de passe de la leçon 4. C'est précisément pour cette raison que le mode minimal est le mode par défaut : il ne peut pas faire fuiter un argument sensible. Le mode maximal n'est sûr que si aucun secret ne passe par les arguments, ce qui est la règle de la leçon 4.

Un SBOM avec Syft

Le SBOM peut aussi être produit hors de la construction, par exemple pour une image tierce que l'on ne construit pas. Syft, l'outil qui sert d'analyseur à BuildKit, s'utilise directement, ici sur l'image exportée en archive, en produisant deux formats d'un coup :

$ docker save 127.0.0.1:5000/signalements:2.0 -o img.tar
$ docker run --rm -v "$PWD":/t anchore/syft:v1.52.0 docker-archive:/t/img.tar \
    -o cyclonedx-json=/t/sbom.cdx.json -o spdx-json=/t/sbom.syft.spdx.json -q
$ jq -r '.specVersion, (.components|length)' sbom.cdx.json
1.7
2817
$ jq '[.components[].type] | group_by(.) | map({(.[0]): length}) | add' sbom.cdx.json
{
  "application": 1,
  "file": 2708,
  "library": 107,
  "operating-system": 1
}
$ jq -r '[.components[] | .purl // "" | capture("^pkg:(?<t>[a-z]+)/") | .t] | group_by(.) | map("\(.[0])=\(length)") | join(" ")' sbom.cdx.json
deb=97 generic=1 pypi=10

Le SBOM CycloneDX 1.7 contient 2 817 composants : 107 bibliothèques (97 paquets Debian, 10 paquets Python), le système d'exploitation, l'interpréteur Python, et 2 708 fichiers avec leurs empreintes. Un SBOM peut être bien plus détaillé que la liste des paquets ; c'est utile pour un audit d'intégrité, encombrant pour une simple recherche de version.

Sommes-nous concernés ?

Le jour de l'alerte, la question devient une requête. Rassemblons les SBOM SPDX de nos deux images (Signalements et signalements-export, construit lui aussi avec --sbom=true), puis cherchons deux composants : OpenSSL, et la bibliothèque standard de Go.

$ for f in signalements-sbom.spdx.json export-sbom.spdx.json; do
    echo "== $f"
    jq -r '.packages[] | select(.name|test("^(libssl3t64|openssl|stdlib|go|psycopg-c|libpq5)$")) | "\(.name) \(.versionInfo)"' $f
  done
== signalements-sbom.spdx.json
libpq5 17.11-0+deb13u1
libssl3t64 3.5.7-1~deb13u3
openssl 3.5.7-1~deb13u3
psycopg-c 3.3.6
== export-sbom.spdx.json
stdlib go1.26.8

Une alerte OpenSSL concerne Signalements (et l'on sait quelle version y est), pas l'outil d'export ; une alerte sur la bibliothèque standard de Go concerne l'outil d'export, compilé avec Go 1.26.8, pas Signalements. À l'échelle de centaines d'images, on stocke les SBOM dans un outil dédié (Dependency-Track, par exemple, sait importer des SBOM CycloneDX et alerter à chaque nouvelle vulnérabilité) plutôt que dans des fichiers.

Analyser un SBOM plutôt qu'une image

Un analyseur de vulnérabilités peut travailler directement sur un SBOM, sans télécharger l'image. C'est rapide, et cela permet de réanalyser chaque jour l'inventaire de toutes les images avec les vulnérabilités du jour. Mais le résultat dépend de qui a produit le SBOM. Comparons, sur la même image, l'analyse Trivy de l'image et celle de trois SBOM différents :

$ docker run --rm -v "$PWD":/t:ro -v trivy-cache:/root/.cache aquasec/trivy:0.74.0 \
    image --quiet --scanners vuln --format json --input /t/img.tar | jq '[.Results[]?.Vulnerabilities[]?] | length'
184
$ docker run --rm -v "$PWD":/t -v trivy-cache:/root/.cache aquasec/trivy:0.74.0 \
    image --quiet --input /t/img.tar --format cyclonedx --output /t/sbom.trivy.cdx.json
$ docker run --rm -v "$PWD":/t:ro -v trivy-cache:/root/.cache aquasec/trivy:0.74.0 \
    sbom --quiet --format json /t/sbom.trivy.cdx.json | jq '[.Results[]?.Vulnerabilities[]?] | length'
184
$ docker run --rm -v "$PWD":/t:ro -v trivy-cache:/root/.cache aquasec/trivy:0.74.0 \
    sbom --quiet --format json /t/sbom.cdx.json | jq '[.Results[]?.Vulnerabilities[]?] | length'
20
$ jq '.SPDX' sbom.json > sbom.buildkit.spdx.json
$ docker run --rm -v "$PWD":/t:ro -v trivy-cache:/root/.cache aquasec/trivy:0.74.0 \
    sbom --quiet --format json /t/sbom.buildkit.spdx.json | jq '[.Results[]?.Vulnerabilities[]?] | length'
0
Source analysée par TrivyVulnérabilités trouvées
L'image elle-même184
SBOM CycloneDX produit par Trivy184
SBOM CycloneDX produit par Syft20
SBOM SPDX produit par BuildKit0

Le même inventaire (dont les 97 mêmes paquets Debian) donne 184, 20 ou 0 vulnérabilités selon le producteur du SBOM. Ce n'est pas qu'un SBOM soit faux : les paquets y sont bien (libc6, util-linux... figurent dans les trois). Mais chaque analyseur s'appuie sur des informations précises pour faire correspondre un paquet aux avis de sécurité de sa distribution (le paquet source, la distribution exacte, parfois des propriétés propres à l'outil), et ces informations ne sont pas toutes exprimées de la même manière par chaque producteur. Leçon pratique : un SBOM n'est utile que si l'outil qui le lit l'exploite comme prévu. Validez votre chaîne en comparant, sur quelques images, l'analyse du SBOM à celle de l'image, et utilisez de préférence le même écosystème d'outils de bout en bout.

Sous le capot

Où vivent les attestations. BuildKit ajoute à l'index de l'image, pour chaque manifeste de plateforme, un manifeste d'attestation de plateforme unknown/unknown, annoté vnd.docker.reference.type: attestation-manifest et vnd.docker.reference.digest (l'empreinte du manifeste qu'il décrit). Ses couches sont des documents JSON de type application/vnd.in-toto+json, un par prédicat. Les clients qui ne connaissent pas ces manifestes les ignorent, puisqu'aucune machine n'a la plateforme unknown/unknown. D'autres outils (Cosign, ORAS) utilisent plutôt l'API referrers de la spécification de distribution OCI 1.1, qui rattache un artefact à une image sans modifier son index (leçon 9).

Ce que voit l'analyseur de SBOM. Le SBOM de BuildKit est produit en analysant le système de fichiers final de l'image : bases de données dpkg, métadonnées des paquets Python (*.dist-info), informations embarquées dans les binaires Go (leçon 5). Il ne voit pas ce qui a été utilisé pendant la construction et n'a pas laissé de trace : le compilateur de l'étape construction, par exemple. L'argument BUILDKIT_SBOM_SCAN_STAGE=true, déclaré dans l'étape à analyser, demande d'analyser aussi une étape intermédiaire, pour documenter les outils de construction (exercice 3).

Ce que garantit la provenance, et ce qu'elle ne garantit pas. Telle que produite ici, la provenance est une déclaration du builder, jointe à l'image, mais non signée : quiconque peut pousser dans le dépôt peut aussi y pousser une fausse provenance. Elle atteint le niveau SLSA Build L1. La confiance vient de la signature (leçon 9) et du système qui l'émet (une plateforme de CI hébergée et durcie, leçon 10). Le champ buildkit_completeness dit d'ailleurs honnêtement que la liste des dépendances n'est pas complète ("resolvedDependencies": false) : le contexte local (vos fichiers) n'y figure pas sous forme d'empreinte.

Pièges courants

Croire qu'un SBOM est exhaustif. Il liste ce que l'analyseur sait reconnaître : paquets d'un gestionnaire connu, bibliothèques avec métadonnées, binaires Go. Un binaire copié à la main, une bibliothèque C compilée et installée sans paquet, un fichier JAR renommé peuvent lui échapper. Le SBOM d'une image scratch construite avec un binaire C statique sera presque vide.

Les SBOM qui ne se comprennent pas entre outils. Démontré ci-dessus : 184, 20 ou 0 vulnérabilités pour la même image. Testez la combinaison producteur et consommateur avant de bâtir un processus dessus.

Une provenance qui fuit. mode=max enregistre les arguments de construction : un secret passé par --build-arg se retrouve en clair dans le registre (leçon 4). Ne passez à mode=max qu'avec des Dockerfiles exempts d'arguments sensibles, ce qui devrait de toute façon être le cas.

Perdre les attestations en route. docker pull puis docker push vers un autre registre, ou docker save et docker load, ne transportent que l'image, pas toujours ses manifestes d'attestation. Pour recopier une image avec ses attestations entre registres, utilisez un outil qui copie l'index complet (docker buildx imagetools create, crane copy, oras copy).

Comparer des index au lieu de manifestes. La provenance contient l'heure de construction : deux constructions reproductibles de la même image ont des index différents (leçon 6). Pour vérifier une reproduction, comparez les manifestes de plateforme.

Sécurité

  • Le SBOM est un inventaire, donc aussi une carte pour l'attaquant. Il révèle chaque composant et chaque version. Pour une image publique, ce n'est pas un problème (l'image se télécharge et s'analyse de toute façon). Pour une image privée, il se protège comme l'image.
  • Une provenance non signée ne prouve rien contre un attaquant qui contrôle le registre. Associez-la à une signature (leçon 9) et à une politique qui vérifie l'émetteur au moment du déploiement.
  • VEX pour réduire le bruit. Un document VEX (Vulnerability Exploitability eXchange) déclare, pour une vulnérabilité donnée, si le produit est réellement affecté (« non affecté : le code vulnérable n'est pas utilisé »). Associé au SBOM, il permet de fermer formellement les alertes sans objet plutôt que de les ignorer en silence ; les images durcies récentes (Docker Hardened Images, Chainguard) publient leurs déclarations VEX.

En production

  • Activez SBOM et provenance dans la CI, pour toutes les images publiées. Le coût est faible (quelques secondes de construction, quelques mégaoctets de registre) au regard du gain le jour d'une alerte. La leçon 10 les intègre au workflow.
  • Centralisez les SBOM dans un outil d'inventaire qui réanalyse quotidiennement (Dependency-Track, ou le module d'inventaire de votre registre ou de votre plateforme de sécurité), et reliez chaque SBOM aux environnements où l'image tourne : la question utile n'est pas « quelles images contiennent X » mais « quelles images en production ».
  • Pour le Cyber Resilience Act, la nomenclature doit couvrir au minimum les dépendances de premier niveau, dans un format courant et lisible par machine ; le guide technique TR-03183 du BSI allemand précise les champs attendus et sert de référence de fait. Si vous livrez des logiciels à des clients, prévoyez de leur fournir le SBOM avec chaque version.
  • Gardez les attestations aussi longtemps que les images. Les politiques de rétention du registre doivent traiter l'image et ses attestations ensemble.

Exercices

1. Inventorier une image tierce. Produisez avec Syft un SBOM CycloneDX de postgres:18.6. Combien de paquets Debian contient-elle ? Quel binaire Go y trouve-t-on, et avec quelle version de Go a-t-il été compilé ?

Solution
$ docker save postgres:18.6 -o pg.tar
$ docker run --rm -v "$PWD":/t anchore/syft:v1.52.0 docker-archive:/t/pg.tar -o cyclonedx-json=/t/pg.cdx.json -q
$ jq '[.components[] | select((.purl // "") | startswith("pkg:deb"))] | length' pg.cdx.json
$ jq -r '.components[] | select((.purl // "") | startswith("pkg:golang")) | .purl' pg.cdx.json

Au 1er octobre 2026 : 145 paquets Debian, et ces modules Go :

pkg:golang/github.com/moby/sys/user@v0.1.0
pkg:golang/github.com/tianon/gosu@v1.19.0
pkg:golang/golang.org/x/sys@v0.1.0
pkg:golang/stdlib@1.24.6

C'est gosu 1.19.0, le petit binaire qui sert au script d'entrée à abandonner les privilèges, compilé avec Go 1.24.6 : l'origine des vulnérabilités « Go » signalées dans cette image (exercice 3 de la leçon 12 du cours précédent).

2. Remonter à la source. À partir de la seule référence 127.0.0.1:5000/signalements:2.0, retrouvez le dépôt Git, le commit, et l'empreinte exacte de l'image de base d'exécution. Quelles commandes ?

Solution
$ docker buildx imagetools inspect 127.0.0.1:5000/signalements:2.0 --format '{{json .Provenance}}' > p.json
$ jq '.SLSA.runDetails.metadata.buildkit_metadata.vcs | {source, revision}' p.json
$ jq -r '.SLSA.buildDefinition.resolvedDependencies[] | select(.uri | contains("python@3.14-slim")) | .digest.sha256' p.json

La première donne l'URL du dépôt et le commit, la seconde l'empreinte de python:3.14-slim utilisée. Cette remontée suppose que la provenance soit présente et digne de confiance : signée, et émise par la CI (leçons 9 et 10).

3. Comparer les inventaires (niveau 300). Construisez Signalements avec --sbom=true en demandant aussi l'analyse de l'étape construction. Qu'apparaît-il de nouveau dans les attestations ? Dans quel cas est-ce utile ?

Solution

Premier essai, avec --build-arg BUILDKIT_SBOM_SCAN_STAGE=true : rien ne change, un seul SBOM. L'argument doit être déclaré dans l'étape à analyser :

FROM python:3.14 AS construction
ARG BUILDKIT_SBOM_SCAN_STAGE=true

Le manifeste d'attestation contient alors deux SBOM, celui de l'image finale (2 Mo) et celui de l'étape construction (13,9 Mo, avec le compilateur gcc, libpq-dev et toute l'image python:3.14), que imagetools inspect présente sous AdditionalSPDXs :

{"p":"https://spdx.dev/Document","size":2085252}
{"p":"https://spdx.dev/Document","size":13883578}
{"p":"https://slsa.dev/provenance/v1","size":2033}

C'est utile quand on doit documenter non seulement ce qui est livré mais aussi avec quoi cela a été produit : par exemple pour évaluer l'impact d'une vulnérabilité d'un compilateur (un outil de construction compromis, compilateur ou image de CI, compromet tout ce qu'il a produit, même s'il n'est pas livré : c'est l'attaque décrite par Ken Thompson dans Reflections on Trusting Trust dès 1984).

4. Valider une chaîne (niveau 300). Votre organisation veut analyser chaque nuit les SBOM de toutes les images en production plutôt que les images elles-mêmes. Rédigez le protocole de validation à mettre en place avant d'adopter cette approche, à la lumière du tableau de la leçon.

Solution
  1. Choisir le producteur de SBOM (BuildKit, Syft, Trivy) et le consommateur (analyseur, outil d'inventaire). 2. Sur un échantillon représentatif d'images (distributions différentes, langages différents, images minimales), comparer pour chaque image le résultat de l'analyse du SBOM à celui de l'analyse directe de l'image, avec le même analyseur et la même base de vulnérabilités. 3. Exiger un écart nul, ou expliqué et documenté ; sinon changer de producteur, de format ou de consommateur. 4. Réitérer à chaque mise à jour d'un des outils (un changement de version peut casser la correspondance). 5. Conserver une analyse directe périodique des images en production comme filet de sécurité.

Récapitulatif

  • Le SBOM inventorie les composants d'une image (SPDX ou CycloneDX, identifiants purl) ; l'attestation de provenance dit comment elle a été construite (sources, commit, images de base par empreinte, paramètres), sur le modèle SLSA.
  • --sbom=true --provenance=mode=max les joint à l'image dans le registre, sous forme d'attestations in-toto ; docker buildx imagetools inspect --format '{{json .SBOM}}' et '{{json .Provenance}}' les relisent.
  • mode=min (par défaut) n'enregistre ni les arguments ni la définition de la construction ; mode=max les enregistre, arguments sensibles compris.
  • Un SBOM répond en minutes à « sommes-nous concernés ? ». Mais producteurs et consommateurs ne se comprennent pas toujours : 184, 20 ou 0 vulnérabilités pour la même image selon le producteur. Validez la chaîne.
  • Une provenance non signée ne protège pas d'un attaquant : la leçon suivante la signe.

Pour aller plus loin

  • La spécification SLSA v1, en particulier la description des niveaux de la piste Build et des menaces qu'ils couvrent.
  • Le cadre d'attestations in-toto et la liste des prédicats normalisés.
  • Le texte du Cyber Resilience Act (règlement 2024/2847), annexe I, partie II, et le guide TR-03183-2 du BSI pour la mise en œuvre des SBOM.
  • Leçon suivante : signer les images et leurs attestations, et vérifier ces signatures avant de déployer.
Voir ma constellation →

Sources