SBOM 證明

SBOM 證明透過驗證映像包含的軟體工件以及用於建立映像的工件,幫助確保軟體供應鏈透明度。SBOM 中用於描述軟體工件的元資料可能包括

  • 工件名稱
  • 版本
  • 許可證型別
  • 作者
  • 唯一的軟體包識別符號

在構建過程中對映像內容進行索引比掃描最終映像具有優勢。當掃描作為構建的一部分進行時,您可以檢測用於構建映像的軟體,這些軟體可能不會出現在最終映像中。

Docker 透過使用 BuildKit 和證明的 SLSA 相容構建過程支援 SBOM 生成和證明。由 BuildKit 生成的 SBOM 遵循 SPDX 標準,並使用 in-toto SPDX 謂詞 定義的格式,作為 JSON 編碼的 SPDX 文件附加到最終映像。在本頁中,您將學習如何使用 Docker 工具建立、管理和驗證 SBOM 證明。

建立 SBOM 證明

要建立 SBOM 證明,請將 --attest type=sbom 選項傳遞給 docker buildx build 命令

$ docker buildx build --tag <namespace>/<image>:<version> \
    --attest type=sbom --push .

或者,您可以使用簡寫 --sbom=true 選項代替 --attest type=sbom

有關如何使用 GitHub Actions 新增 SBOM 證明的示例,請參閱使用 GitHub Actions 新增證明

驗證 SBOM 證明

在將映像推送到登錄檔之前,請務必驗證為映像生成的 SBOM。

要進行驗證,您可以使用 local 匯出器構建映像。使用 local 匯出器構建會將構建結果儲存到本地檔案系統,而不是建立映像。證明將寫入到匯出根目錄中的 JSON 檔案中。

$ docker buildx build \
  --sbom=true \
  --output type=local,dest=out .

SBOM 檔案出現在輸出的根目錄中,名為 sbom.spdx.json

$ ls -1 ./out | grep sbom
sbom.spdx.json

引數

預設情況下,BuildKit 僅掃描映像的最終階段。生成的 SBOM 不包括在早期階段安裝或存在於構建上下文中的構建時依賴項。這可能會導致您忽略這些依賴項中的漏洞,這可能會影響最終構建工件的安全性。

例如,您可以使用多階段構建,並在最終階段使用 FROM scratch 語句以實現更小的映像大小。

FROM alpine AS build
# build the software ...

FROM scratch
COPY --from=build /path/to/bin /bin
ENTRYPOINT [ "/bin" ]

掃描使用此 Dockerfile 示例構建的最終映像不會顯示在 build 階段使用的構建時依賴項。

要從 Dockerfile 中包含構建時依賴項,您可以設定構建引數 BUILDKIT_SBOM_SCAN_CONTEXTBUILDKIT_SBOM_SCAN_STAGE。這會將掃描範圍擴充套件到包括構建上下文和附加階段。

您可以在每個階段單獨設定引數,或者將其設定為全域性引數(在宣告 Dockerfile 語法指令之後,在第一個 FROM 命令之前)。如果全域性設定,該值將傳播到 Dockerfile 中的每個階段。

BUILDKIT_SBOM_SCAN_CONTEXTBUILDKIT_SBOM_SCAN_STAGE 構建引數是特殊值。您不能使用這些引數執行變數替換,也不能使用 Dockerfile 中的環境變數設定它們。設定這些值的唯一方法是在 Dockerfile 中使用顯式 ARG 命令。

掃描構建上下文

要掃描構建上下文,請將 BUILDKIT_SBOM_SCAN_CONTEXT 設定為 true

# syntax=docker/dockerfile:1
ARG BUILDKIT_SBOM_SCAN_CONTEXT=true
FROM alpine AS build
# ...

您可以使用 --build-arg CLI 選項覆蓋 Dockerfile 中指定的值。

$ docker buildx build --tag <image>:<version> \
    --attest type=sbom \
    --build-arg BUILDKIT_SBOM_SCAN_CONTEXT=false .

請注意,僅將其作為 CLI 引數傳遞,而不在 Dockerfile 中使用 ARG 宣告,將無效。您必須在 Dockerfile 中指定 ARG,然後可以使用 --build-arg 覆蓋上下文掃描行為。

掃描階段

要掃描除最終階段之外的更多階段,請將 BUILDKIT_SBOM_SCAN_STAGE 引數設定為 true,無論是全域性設定還是在您要掃描的特定階段設定。下表演示了此引數的不同可能設定。

描述
BUILDKIT_SBOM_SCAN_STAGE=true啟用當前階段的掃描
BUILDKIT_SBOM_SCAN_STAGE=false停用當前階段的掃描
BUILDKIT_SBOM_SCAN_STAGE=base,bin啟用名為 basebin 的階段的掃描

只會掃描已構建的階段。不是目標階段依賴項的階段將不會被構建或掃描。

以下 Dockerfile 示例使用多階段構建來構建帶有 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 /

hugo 階段設定 ARG BUILDKIT_SBOM_SCAN_STAGE=true 可確保最終 SBOM 包含 Alpine Linux 和 Hugo 用於建立網站的資訊。

使用 local 匯出器構建此映像會建立兩個 JSON 檔案

$ docker buildx build \
  --sbom=true \
  --output type=local,dest=out .
$ ls -1 out | grep sbom
sbom-hugo.spdx.json
sbom.spdx.json

檢查 SBOM

要透過 image 匯出器探索建立的 SBOM,您可以使用 imagetools inspect

使用 --format 選項,您可以指定輸出模板。所有與 SBOM 相關的資料都在 .SBOM 屬性下可用。例如,要獲取 SPDX 格式的 SBOM 原始內容

$ docker buildx imagetools inspect <namespace>/<image>:<version> \
    --format "{{ json .SBOM.SPDX }}"
{
  "SPDXID": "SPDXRef-DOCUMENT",
  ...
}
提示

如果映像是多平臺的,您可以使用 --format '{{ json (index .SBOM "linux/amd64").SPDX }}' 檢查特定於平臺的索引的 SBOM。

您還可以使用 Go 模板的完整功能構造更復雜的表示式。例如,您可以列出所有已安裝的軟體包及其版本識別符號

$ docker buildx imagetools inspect <namespace>/<image>:<version> \
    --format "{{ range .SBOM.SPDX.packages }}{{ .name }}@{{ .versionInfo }}{{ println }}{{ end }}"
adduser@3.118ubuntu2
apt@2.0.9
base-files@11ubuntu5.6
base-passwd@3.5.47
...

SBOM 生成器

BuildKit 使用掃描器外掛生成 SBOM。預設情況下,它使用 BuildKit Syft 掃描器 外掛。此外掛基於 Anchore 的 Syft,一個用於生成 SBOM 的開源工具。

您可以使用 generator 選項選擇要使用的不同外掛,指定實現 BuildKit SBOM 掃描器協議 的映像。

$ docker buildx build --attest type=sbom,generator=<image> .
提示

Docker Scout SBOM 生成器可用。請參閱 Docker Scout SBOM

SBOM 證明示例

以下 JSON 示例展示了 SBOM 證明可能是什麼樣子。

{
  "_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/v5@v5.0.0",
            "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"
  }
}