SyftとGrypeによるSBOM(ソフトウェア部品表)自動化:CI/CDサプライチェーンのセキュリティ強化

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

背景と重要性:SBOMによるソフトウェアサプライチェーンの保護

現代のアプリケーションにおいて、自社で記述したコードだけで完結するケースは稀です。npmライブラリ、ベースとなるDebianレイヤー、推移的(transitive)なGoモジュールなどを合わせると、単一のサービスであっても容易に1,200以上のサードパーティ依存関係を取り込むことになります。パッチ未適用のCVE(脆弱性)が4階層も深い間接的な依存関係に潜んでいる場合、従来のセキュリティスキャナーではそれがどこから混入したのか特定が困難でした。

そこで登場するのがSBOM(Software Bill of Materials:ソフトウェア部品表)です。SBOMは、バイナリやDockerイメージに含まれる「詳細な原材料表示(栄養成分表示)」のようなものと捉えると分かりやすいでしょう。ビルドに組み込まれたすべてのパッケージ、バージョン、ライセンス、SHA-256チェックサムを一覧化します。このインベントリ(一覧表)があることで、中身が不透明だったコンテナが、本番リリースのたびに追跡・監査可能なマニフェストへと変化します。

Anchore社が開発するSyftとGrypeは、これを疎結合な2段階のワークフローで処理します。まずSyftが、コンテナ、生のファイルシステム、アーカイブ内のパッケージを検出し、SPDXやCycloneDXといったオープンスタンダード形式でカタログ化します。次にGrypeがそのカタログを受け取り、NVD(National Vulnerability Database)、GitHub Advisories、各種Linuxディストリビューションのセキュリティトラッカーなどの最新脆弱性フィードと照合します。

「インベントリの作成」と「脆弱性スキャン」の分離こそが最大の強みです。パイプライン内でイメージレイヤーの展開に毎回10分も費やすことなく、プルリクエストの段階で問題のあるパッケージを検出する「シフトレフト」を実現できます。

インストール:ローカルマシンおよびビルトランナーへのSyftとGrypeのセットアップ

どちらのツールも外部ランタイム依存のない静的Goバイナリとしてコンパイルされています。そのため、ローカルのPCやCIランナーに10秒足らずで配置可能です。

Tips 1:公式インストールスクリプトを利用した迅速なローカルセットアップ

macOSやローカルのLinux仮想マシンでテストする場合は、公式インストールスクリプトを使って実行可能パスへ直接ダウンロードできます:

# Syftを /usr/local/bin にインストール
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin

# Grypeを /usr/local/bin にインストール
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin

# インストールを確認
syft version
grype version

Tips 2:カスタムCIベースイメージへのバイナリ事前組み込み

パイプラインの実行ごとにCLIバイナリをダウンロードすると、ネットワーク帯域を消費するだけでなく、GitHubのレート制限に引っかかってビルドが失敗するリスクがあります。独自のKubernetesビルドエージェントやセルフホスト型のGitHub Actionsランナーを運用している場合は、バージョンを固定したリリースをランナーイメージに事前組み込み(Bake)しておきましょう:

FROM alpine:3.19

ARG SYFT_VERSION=v1.1.0
ARG GRYPE_VERSION=v0.74.0

RUN apk add --no-cache curl ca-certificates bash \
    && curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin ${SYFT_VERSION} \
    && curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin ${GRYPE_VERSION}

明示的にバージョンタグを固定しておくことで、スキーマの破壊的変更や予期せぬフラグの非推奨化からCIを守ることができます。

設定:CI/CDパイプラインにおけるSBOM生成とスキャンの自動化

稼働中のコンテナに対して直接、単発のモノリシックなセキュリティスキャンを実行するのは避けるべきです。代わりに、まずはイミュータブル(不変)なビルド成果物としてSBOMを生成し、そのファイルをGrypeに評価させます。これにより、本番環境にデプロイされるソフトウェアの正確な構成情報のみを過不足なく評価できます。

Syftによる標準形式のSBOM生成

Syftは主要な標準フォーマットでのカタログ化に対応しています。エンタープライズ環境では、SPDX JSON(ISO標準)とCycloneDX JSON(OWASP標準)が主流です。ローカルのアプリケーションイメージをカタログ化するには、コマンドを1つ実行するだけです:

# ローカルのDockerイメージからSPDX JSON形式のSBOMを生成
syft my-app:v1.2.0 -o spdx-json=sbom.spdx.json

# またはリポジトリのディレクトリを直接検査
syft dir:./src -o cyclonedx-json=sbom.cdx.json

GrypeによるSBOMのスキャン

生成された sbom.spdx.json をそのままGrypeに渡します。1.2GBのイメージを一からスキャンするとディスクI/Oで45秒ほどかかる場合がありますが、事前生成されたSBOMをスキャンする場合は、GrypeがJSONをパースするだけなのでわずか2〜3秒で完了します:

# 生成されたSBOMをスキャンし、HighまたはCriticalのCVEが存在する場合はビルドを失敗させる
grype sbom:./sbom.spdx.json --fail-on high

GitHub Actions統合のベストプラクティス

以下のワークフローは、コンテナをビルドし、SPDXマニフェストを生成し、深刻なセキュリティ上の問題が検出された場合にビルドを中断させ、コンプライアンス監査用にSBOMをアーカイブします:

name: サプライチェーンセキュリティパイプライン

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build-and-audit:
    runs-on: ubuntu-latest
    steps:
      - name: ソースコードのチェックアウト
        uses: actions/checkout@v4

      - name: コンテナイメージのビルド
        run: |
          docker build -t internal/api-service:${{ github.sha }} .

      - name: SyftによるSBOMの生成
        uses: anchore/sbom-action@v0
        with:
          image: internal/api-service:${{ github.sha }}
          format: spdx-json
          output-file: sbom.spdx.json

      - name: GrypeによるSBOMスキャン
        uses: anchore/scan-action@v3
        with:
          sbom: sbom.spdx.json
          fail-build: true
          severity-cutoff: high

      - name: SBOMアーティファクトのアップロード
        uses: actions/upload-artifact@v4
        with:
          name: sbom-artifact
          path: sbom.spdx.json

Tips 3:Grype設定ファイルによる誤検知の抑制

CVEアラートのトリアージを行っていると、アップストリームで未修正の脆弱性や、無効化されている機能に起因する検出に必ず直面します。パイプラインのログを不要な警告で埋め尽くすのではなく、バージョン管理された .grype.yaml を使用して、検証済みの誤検知(フォールスポジティブ)を抑制(suppress)しましょう:

# .grype.yaml
ignore:
  - vulnerability: CVE-2023-44487
    package:
      name: golang.org/x/net
      version: 0.15.0
    reason: "HTTP/2 Rapid Reset:サービスはHTTP/2を終端するAWS ALBの背後で動作しており、ランタイムへの影響はありません。"
check-for-app-update: false

このファイルをリポジトリのルートにコミットするか、grype -c .grype.yaml sbom:./sbom.spdx.json のように明示的に指定して適用します。

検証とモニタリング:出力の監査、しきい値の管理、継続的な可視化

CIでのテスト自動化は第1段階に過ぎません。開発者のアラート疲れを防ぎつつ、厳格なライセンス順守とセキュリティコンプライアンスを維持することが第2段階となります。

パッケージとライセンスの検査

ライセンス違反は深刻な法的リスクを招く可能性があります。Syftはパッケージ名だけでなくライセンス識別子も抽出するため、コードがステージング環境にデプロイされる前にライセンス監査を容易に実施できます:

# パッケージとライセンスの概要をクリーンな表形式で出力
syft my-app:v1.2.0 -o table

# jqを使用してSBOM全体からGPLまたはAGPLライセンスを抽出・フィルタリング
cat sbom.spdx.json | jq '.packages[] | select(.licenseConcluded | test("GPL")) | {name: .name, version: .versionInfo, license: .licenseConcluded}'

Tips 4:再ビルド不要の継続的な定期監査

月曜日にビルドした問題のないコンテナイメージであっても、木曜日には2件の重大な(Critical)CVEが新たに検出される可能性があります。しかし、すでに約800KBのSBOMアーティファクトをアーカイブしてあれば、影響度を確認するためだけに2GBものDockerイメージをプルして再ビルドする必要はありません。

定期実行のcronジョブやGitHub Actionsワークフローを設定し、過去の本番環境SBOMに対してGrypeを毎日実行します:

# 最新の脆弱性データベースの更新を取得(約30〜50MBのダウンロード)
grype db update

# 最新のCVEデータベースに対して本番リリースSBOMを監査
grype sbom:./artifacts/prod-v1.2.0.spdx.json --only-fixed

--only-fixed オプションを付与することで、ベンダーから修正パッチが提供されている問題のみを抽出できます。これにより、対応策のない情報に振り回されることなく、即座に修正・リリース可能なタスクにセキュリティバックログを集中させることができます。

サプライチェーン監査のまとめチェックリスト

  • docker build の実行直後にSBOMを生成する。
  • コンテナレジストリやS3バケット内で、コンテナタグと並べてSBOMマニフェストをアーカイブする。
  • マルチレイヤーのイメージtarボールを展開する代わりに、軽量なSBOMファイルを直接Grypeでスキャンする。
  • 抑制した検出内容の妥当性を文書化し、レビュー済みの .grype.yaml で管理する。
  • 本番リリースの合間に公開されたゼロデイ脆弱性を検知するため、定期的なオフラインSBOM監査をスケジュール実行する。
Share: