SBOM
Docker SBOM:生成方法と、スキャンで見落とされるもの
DockerイメージのSBOMは、BuildKitでビルド時に、あるいはDocker ScoutやSyftで事後に、1つのコマンドで取得できます。いずれもイメージ内で見つかったパッケージメタデータを読み取ります。メタデータが及ぶ範囲では、それはイメージの中身を示す正しい記録です。しかし、イメージがどのようにビルドされたかの記録ではありません。
更新日
DockerイメージのSBOMを生成する方法
一般的な方法は3つあります。BuildKitはイメージのビルド中にSBOMを書き出し、構成証明(attestation)としてイメージに添付できます。Docker ScoutとSyftは、ローカルまたはレジストリにある既存のイメージからSBOMを生成します。
ビルド時にBuildKitで生成する
--sbom=true、またはその長い形式である--attest type=sbomをdocker buildx buildに追加します。BuildKitは結果に対してSyftスキャナープラグインを実行し、SBOMをSPDX JSONドキュメントとしてイメージに添付します。何かをプッシュする前にSBOMを確認するには、localエクスポーターでビルドします。
# Build the image, attach an SBOM attestation and push it
docker buildx build --sbom=true -t registry.example.com/app:1.0 --push .
# The long form of the same option
docker buildx build --attest type=sbom -t registry.example.com/app:1.0 --push .
# Check the SBOM before pushing: write the build result to ./out
docker buildx build --sbom=true --output type=local,dest=out .
# out/sbom.spdx.json is the SBOM イメージに添付されたSBOMを読み取る
docker buildx imagetools inspectは、レジストリから構成証明を読み戻します。SBOM関連の情報はすべて、Goテンプレートの.SBOMの下にあります。
# Print the SPDX document attached to the image
docker buildx imagetools inspect registry.example.com/app:1.0 \
--format "{{ json .SBOM.SPDX }}"
# List each package and its version
docker buildx imagetools inspect registry.example.com/app:1.0 \
--format "{{ range .SBOM.SPDX.packages }}{{ .name }}@{{ .versionInfo }}{{ println }}{{ end }}" 既存のイメージからDocker Scoutで生成する
docker scout sbomは、イメージにSBOMの構成証明があればそれを使用します。ない場合は、イメージの内容を自らインデックス化します。デフォルトではJSONを出力し、--outputでファイルに書き出します。
# List the packages in an image
docker scout sbom --format list registry.example.com/app:1.0
# Write an SPDX or CycloneDX SBOM to a file
docker scout sbom --format spdx --output app.spdx.json registry.example.com/app:1.0
docker scout sbom --format cyclonedx --output app.cdx.json registry.example.com/app:1.0 既存のイメージからSyftで生成する
Syftは、BuildKitのプラグインの基盤となっているオープンソースのスキャナーです。イメージを指定し、-oでフォーマットを選択します。=fileを付けると、フォーマットごとに別々のファイルに書き出します。
# CycloneDX JSON to the terminal
syft registry.example.com/app:1.0 -o cyclonedx-json
# Both formats, each to its own file
syft registry.example.com/app:1.0 -o spdx-json=app.spdx.json -o cyclonedx-json=app.cdx.json SPDXとCycloneDXのどちらか
BuildKitの構成証明は常にSPDXです。Docker ScoutはSPDXまたはCycloneDXを書き出し、Syftは他のフォーマットとあわせて両方を書き出します。どちらの規格もパッケージ、バージョン、ライセンス、パッケージURLを記載するため、通常は次にSBOMを読み取るツールによって選択が決まります。違いについてはCycloneDX vs SPDXをご覧ください。
イメージスキャナーがSBOMを作成する仕組み
これらのツールはすべて同じ仕組みで動作します。イメージのレイヤーを展開し、見つかったパッケージメタデータ(オペレーティングシステムのパッケージデータベースや、言語のパッケージマネージャーが残すメタデータ)を読み取ります。識別できたパッケージはすべてSBOMのエントリになり、ベースイメージに由来するパッケージも含まれます。
ビルドステージとビルドコンテキストを含める
デフォルトでは、BuildKitがスキャンするのは最終ステージだけです。マルチステージビルドでは、前のステージで使われたコンパイラー、ビルドツール、パッケージは最終イメージに含まれないため、そのSBOMにも含まれません。DockerのSBOM構成証明のドキュメントには、スキャン範囲を広げる2つのビルド引数が説明されています。BUILDKIT_SBOM_SCAN_STAGEはステージを追加し、BUILDKIT_SBOM_SCAN_CONTEXTはビルドコンテキストを追加します。
# syntax=docker/dockerfile:1
# Scan the build context as well as the image
ARG BUILDKIT_SBOM_SCAN_CONTEXT=true
FROM golang:1.25 AS build
# Include this build stage in the SBOM
ARG BUILDKIT_SBOM_SCAN_STAGE=true
WORKDIR /src
COPY . .
RUN go build -o /out/app .
FROM scratch
COPY --from=build /out/app /app
ENTRYPOINT ["/app"] どちらもDockerfileでARGとして宣言する必要があります。--build-argだけで設定しても効果はありませんが、Dockerfileで宣言された値を--build-argで上書きすることはできます。localエクスポーターを使うと、スキャンされた各ステージに個別のファイルが作成されます。たとえばsbom-build.spdx.jsonがsbom.spdx.jsonの隣に作成されます。
それでもスキャンが見落とすもの
ステージのスキャンで1つのギャップは埋まります。しかし、スキャナーが読み取るのはあくまでファイルであり、ビルドが使用するものの中には、ファイルを残さないもの、あるいはスキャナーが識別できるファイルを残さないものがあります。
- パッケージメタデータのないファイル。
curlでダウンロードしたバイナリ、RUNステップで展開したtarball、コピーされたベンダリング済みのコードなどです。Goバイナリのように、スキャナーが読み取れるビルド情報を持つバイナリもありますが、多くは持っていません。 - ビルドがダウンロードして削除したもの。
RUNステップが同じレイヤー内で取得し、使用し、削除したパッケージやインストーラーは、最終ステージかどうかにかかわらず、どのスキャナーにも読み取れるものをファイルシステムに残しません。 - どのように入ってきたか。スキャンから得たSBOMには、そこに何があるかが記載されます。パッケージがどのレジストリやホストから来たのか、どのステップが取得したのかは記載されません。
- 指定しなかったステージ。ステージのスキャンはオプトインで、ステージごとまたはグローバルに指定します。引数のないステージは引き続き除外されます。
- スキャンの設定。何が表示されるかは、スキャナーの実行方法によって異なります。あるプロジェクトでは、Syftのフラグ1つで、npmパッケージが結果に現れるかどうかがまるごと決まります。
Yoctoビルドにも同じ問題があります。SBOMスキャナーがYoctoビルドの中身を見逃す理由をご覧ください。
なぜ重要なのか
侵害されたビルドツールやベースイメージは、最終イメージに一切現れなくても、出荷するものに影響します。また、メタデータのないバイナリは、どのSBOMエントリも指し示さない脆弱性を抱えている可能性があります。アドバイザリが公開されたときには、イメージの中身だけでなく、イメージのビルドに何が使われたかも把握する必要があります。
ビルドも記録する
イメージのSBOMは、イメージの中に何があるかに答えます。ビルドの記録は、ビルドが何を取り込んだかに答えます。つまり、取得したベースイメージと各ステップが取得したパッケージであり、どのロックファイルにも記載されていない隠れた依存関係も含まれます。CRACIは、GitHub Actionsのジョブの実行中に、ランナー上のパッケージを認識するプロキシを通じてそれを記録し、APIはジョブのSBOM、ネットワークトレース、来歴を返します。出荷するものにはイメージのSBOMを、それがどのように作られたかにはビルドの記録をお使いください。
SBOMツールの比較、ビルド時SBOM、GitHub ActionsでSBOMを生成する方法もご覧ください。
Docker SBOMに関するよくあるご質問
DockerイメージのSBOMはどのように生成しますか?
一般的な方法は3つあります。--sbom=trueをdocker buildx buildに追加すると、BuildKitがSPDXのSBOMをイメージに添付します。既存のイメージに対してdocker scout sbomを実行する方法もあります。あるいはSyftを実行します(例:syft IMAGE -o cyclonedx-json)。3つとも、イメージ内で見つかったパッケージを読み取ります。
SBOMスキャナーとは何ですか?
ソースディレクトリ、ロックファイル、コンテナイメージなどのファイルを読み取り、識別できたパッケージのSBOMを書き出すツールです。Syft、Trivy、Docker Scout、BuildKitのSyftプラグインはSBOMスキャナーです。これらは目の前にあるファイルを記述するものであり、それを生み出したビルドを記述するものではありません。
DockerはどのSBOMフォーマットを生成しますか?
BuildKitの構成証明は、イメージに添付されるSPDX JSONドキュメントです。Docker Scoutはパッケージ一覧を出力するか、SPDXまたはCycloneDXを書き出せます。Syftは両方を含む複数のフォーマットを書き出します。SBOMを利用するツールが想定するフォーマットを選んでください。
スキャナーはDockerイメージからどのようにSBOMを作成しますか?
イメージのレイヤーを展開し、見つかったパッケージメタデータを読み取ります。対象はオペレーティングシステムのパッケージデータベースと、インストール済みのnpmやPythonパッケージのメタデータといった言語のパッケージファイルです。識別できた各パッケージがSBOMのエントリになります。
イメージのSBOMがコンポーネントを見落とすのはなぜですか?
読み取るファイルの中で識別できるものしか記載できないためです。curlでダウンロードしたバイナリにはパッケージメタデータがなく、ビルドステップがダウンロードして削除したパッケージは読み取れるものを何も残さず、ステージのスキャンを有効にしない限り、前のビルドステージは除外されます。
それでもイメージのSBOMは役に立ちますか?
はい。ベースイメージに由来するパッケージを含め、出荷するイメージの中身を示す正しい記録です。ただし、イメージがどのようにビルドされたかは記述しないため、それが重要な場合はビルドの記録と組み合わせてお使いください。
イメージのビルドが取り込んだものを確認
CRACIでイメージのビルドを1回実行し、取得したベースイメージとパッケージの記録を、スキャナーが生成したイメージのSBOMと並べて比較してください。
デモを予約