Software Bill of Materials (SBOM) attestations describe what software artifacts an image contains, and artifacts used to create the image. Metadata included in an SBOM for describing software artifacts may include:
- Name of the artifact
- Version
- License type
- Authors
- Unique package identifier
There are benefits to indexing contents of an image during the build, as opposed to scanning a final image. When scanning happens as part of the build, you're able to detect software you use to build the image, that may not show up in the final image.
The SBOMs generated by BuildKit follow the SPDX standard. SBOMs attach to the final image as a JSON-encoded SPDX document, using the format defined by the in-toto SPDX predicate.
Create SBOM attestations#
To create an SBOM attestation, pass the --attest type=sbom
option to the
docker buildx build
command:
$ docker buildx build --tag <namespace>/<image>:<version> \
--attest type=sbom --push .
Alternatively, you can use the shorthand --sbom=true
option instead of --attest type=sbom
.
For an example on how to add SBOM attestations with GitHub Actions, see Add attestations with GitHub Actions.
Verify SBOM attestations#
Always validate the generated SBOM for your image before you push your image to a registry.
To validate, you can build the image using the local
exporter.
Building with the local
exporter saves the build result to your local filesystem instead of creating an image.
Attestations are written to a JSON file in the root directory of your export.
$ docker buildx build \
--sbom=true \
--output type=local,dest=out .
The SBOM file appears in the root directory of the output, named sbom.spdx.json
:
$ ls -1 ./out | grep sbom
sbom.spdx.json
Arguments#
By default, BuildKit only scans the final stage of an image. The resulting SBOM doesn't include build-time dependencies installed in earlier stages, or that exist in the build context. This may cause you to overlook vulnerabilities in those dependencies, which could impact the security of your final build artifacts.
For instance, you might use multi-stage builds,
with a FROM scratch
stanza for your final stage to achieve a smaller image size.
FROM alpine AS build
# build the software ...
FROM scratch
COPY --from=build /path/to/bin /bin
ENTRYPOINT [ "/bin" ]
Scanning the resulting image built using this Dockerfile example would not
reveal build-time dependencies used in the build
stage.
To include build-time dependencies from your Dockerfile, you can set the build
arguments BUILDKIT_SBOM_SCAN_CONTEXT
and BUILDKIT_SBOM_SCAN_STAGE
. This
expands the scanning scope to include the build context and additional stages.
You can set the arguments as global arguments (after declaring the Dockerfile
syntax directive, before the first FROM
command) or individually in each
stage. If set globally, the value propagates to each stage in the Dockerfile.
The BUILDKIT_SBOM_SCAN_CONTEXT
and BUILDKIT_SBOM_SCAN_STAGE
build arguments
are special values. You can't perform variable substitution using these
arguments, and you can't set them using environment variables from within the
Dockerfile. The only way to set these values is using explicit ARG
command in
the Dockerfile.
Scan build context#
To scan the build context, set the BUILDKIT_SBOM_SCAN_CONTEXT
to true
.
# syntax=docker/dockerfile:1
ARG BUILDKIT_SBOM_SCAN_CONTEXT=true
FROM alpine AS build
# ...
You can use the --build-arg
CLI option to override the value specified in the
Dockerfile.
$ docker buildx build --tag <image>:<version> \
--attest type=sbom \
--build-arg BUILDKIT_SBOM_SCAN_CONTEXT=false .
Note that passing the option as a CLI argument only, without having declared it
using ARG
in the Dockerfile, will have no effect. You must specify the ARG
in the Dockerfile, whereby you can override the context scanning behavior using
--build-arg
.
Scan stages#
To scan more than just the final stage, set the BUILDKIT_SBOM_SCAN_STAGE
argument to true, either globally or in the specific stages that you want to
scan. The following table demonstrates the different possible settings for this
argument.
Value | Description |
---|---|
BUILDKIT_SBOM_SCAN_STAGE=true |
Enables scanning for the current stage |
BUILDKIT_SBOM_SCAN_STAGE=false |
Disables scanning for the current stage |
BUILDKIT_SBOM_SCAN_STAGE=base,bin |
Enables scanning for the stages named base and bin |
Only stages that are built will be scanned. Stages that aren't dependencies of the target stage won't be built, or scanned.
The following Dockerfile example uses multi-stage builds to build a static website with Hugo.
# syntax=docker/dockerfile:1
FROM alpine as hugo
ARG BUILDKIT_SBOM_SCAN_STAGE=true
WORKDIR /src
COPY <<config.yml ./
title: My Hugo website
config.yml
RUN apk add --upgrade hugo && hugo
FROM scratch
COPY --from=hugo /src/public /
Setting ARG BUILDKIT_SBOM_SCAN_STAGE=true
in the hugo
stage ensures that the final SBOM
includes the information that Alpine Linux and Hugo were used to create the website.
Building this image with the local
exporter creates two JSON files:
$ docker buildx build \
--sbom=true \
--output type=local,dest=out .
$ ls -1 out | grep sbom
sbom-hugo.spdx.json
sbom.spdx.json
Inspecting SBOMs#
To explore created SBOMs exported through the image
exporter, you can use
imagetools inspect
.
Using the --format
option, you can specify a template for the output. All
SBOM-related data is available under the .SBOM
attribute. For example, to get
the raw contents of an SBOM in SPDX format:
$ docker buildx imagetools inspect <namespace>/<image>:<version> \
--format "\{\{ json .SBOM.SPDX }}"
{
"SPDXID": "SPDXRef-DOCUMENT",
...
}
[!TIP]
If the image is multi-platform, you can check the SBOM for a platform-specific index using
--format '\{\{ json (index .SBOM "linux/amd64").SPDX }}'
.
You can also construct more complex expressions using the full functionality of Go templates. For example, you can list all the installed packages and their version identifiers:
$ docker buildx imagetools inspect <namespace>/<image>:<version> \
--format "\{\{ range .SBOM.SPDX.packages }}\{\{\.name }}@\{\{\.versionInfo }}\{\{ println }}\{\{ end }}"
[email protected]
[email protected]
[email protected]
[email protected]
...
SBOM generator#
BuildKit generates the SBOM using a scanner plugin. By default, it uses is the BuildKit Syft scanner plugin. This plugin is built on top of Anchore's Syft, an open source tool for generating an SBOM.
You can select a different plugin to use with the generator
option, specifying
an image that implements the
BuildKit SBOM scanner protocol.
$ docker buildx build --attest type=sbom,generator=<image> .
[!TIP]
The Docker Scout SBOM generator is available. See Docker Scout SBOMs.
SBOM attestation example#
The following JSON example shows what an SBOM attestation might look like.
{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://spdx.dev/Document",
"subject": [
{
"name": "pkg:docker/<registry>/<image>@<tag/digest>?platform=<platform>",
"digest": {
"sha256": "e8275b2b76280af67e26f068e5d585eb905f8dfd2f1918b3229db98133cb4862"
}
}
],
"predicate": {
"SPDXID": "SPDXRef-DOCUMENT",
"creationInfo": {
"created": "2022-12-16T15:27:25.517047753Z",
"creators": ["Organization: Anchore, Inc", "Tool: syft-v0.60.3"],
"licenseListVersion": "3.18"
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https://anchore.com/syft/dir/run/src/core/sbom-cba61a72-fa95-4b60-b63f-03169eac25ca",
"name": "/run/src/core/sbom",
"packages": [
{
"SPDXID": "SPDXRef-b074348b8f56ea64",
"downloadLocation": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:a:org:repo:\\(devel\\):*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceLocator": "pkg:golang/github.com/org/repo@(devel)",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"name": "github.com/org/repo",
"sourceInfo": "acquired package info from go module information: bin/server",
"versionInfo": "(devel)"
},
{
"SPDXID": "SPDXRef-1b96f57f8fed62d8",
"checksums": [
{
"algorithm": "SHA256",
"checksumValue": "0c13f1f3c1636491f716c2027c301f21f9dbed7c4a2185461ba94e3e58443408"
}
],
"downloadLocation": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:a:go-chi:chi\\/v5:v5.0.0:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:a:go_chi:chi\\/v5:v5.0.0:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:a:go:chi\\/v5:v5.0.0:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceLocator": "pkg:golang/github.com/go-chi/chi/[email protected]",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"name": "github.com/go-chi/chi/v5",
"sourceInfo": "acquired package info from go module information: bin/server",
"versionInfo": "v5.0.0"
}
],
"relationships": [
{
"relatedSpdxElement": "SPDXRef-1b96f57f8fed62d8",
"relationshipType": "CONTAINS",
"spdxElementId": "SPDXRef-043f7360d3c66bc31ba45388f16423aa58693289126421b71d884145f8837fe1"
},
{
"relatedSpdxElement": "SPDXRef-b074348b8f56ea64",
"relationshipType": "CONTAINS",
"spdxElementId": "SPDXRef-043f7360d3c66bc31ba45388f16423aa58693289126421b71d884145f8837fe1"
}
],
"spdxVersion": "SPDX-2.2"
}
}