Dock Stay
CI でスキャンする - scan in CI の使い方・オプション・サンプル

CI でスキャンする - scan in CI

Trivy・Grype・Snyk などのスキャナを CI に組み込み、既知脆弱性のあるイメージをレジストリに上げない/本番に出さない、というゲートを作る。

概念図

scan in CI diagram

構文

bash
trivy image --exit-code 1 --severity HIGH,CRITICAL <image>

実例

ビルド → ローカルに load → Trivy でスキャン → 合格したら push、というゲート構成。

bash
- name: Build image
  uses: docker/build-push-action@v6
  with:
    context: .
    load: true
    tags: app:ci
- name: Scan with Trivy
  uses: aquasecurity/trivy-action@0.24.0
  with:
    image-ref: app:ci
    exit-code: '1'
    severity: 'HIGH,CRITICAL'
    ignore-unfixed: true
- name: Push image
  uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: ghcr.io/acme/app:${{ github.sha }}

どこで何を検出するか

コンテナイメージの脆弱性スキャナは、イメージ内のレイヤーを展開し、パッケージマネージャの DB(dpkg / rpm / apk / pip / npm / go.sum / Gemfile.lock 等)を読み取って、CVE データベース(OSV / NVD / 各ベンダー)と突き合わせます。

スキャナ 主な範囲 特徴
Trivy OS + 言語 + IaC + secrets OSS で守備範囲が広い。GitHub Actions 公式アクションあり
Grype OS + 言語 Syft と組み合わせて SBOM 生成と一貫させやすい
Snyk OS + 言語 + コード SaaS 側にダッシュボードあり。商用機能が豊富
Docker Scout OS + 言語 Docker Desktop / Hub と統合。簡単に始められる
Clair OS 中心 Harbor 等のレジストリ内蔵

「CI 内のゲート」として使うなら OSS の Trivy / Grype が手軽です。

組織的な脆弱性管理まで必要なら Snyk や Docker Scout の SaaS 機能と組み合わせます。

ゲートをどの強度で入れるか

スキャン結果を CI で「落とす/通す」判定に使うとき、いきなり最厳のポリシーにすると毎日誰かが止まって信頼を失います。

段階的に上げるのが現実的です。

  1. 観測フェーズ: 結果をレポートとしてアーティファクトに残すだけ(exit-code 0)。件数の相場を把握する
  2. 重大のみブロック: CRITICALHIGH のうち修正版があるもの(--ignore-unfixed で固定分を除外)のみでビルドを失敗させる
  3. 本番直前で厳格化: リリース前パイプラインでは MEDIUM まで含める / SBOM 差分を人レビューに回す

また、どうしても残る脆弱性は .trivyignoresecurity policy で「理由付きで許容」できるようにします。

沈黙で無視するのが最悪なので、必ず期限付き・issue 連携で管理します。

SBOM と署名との組み合わせ

スキャンだけでは「今この瞬間の判定」しか得られず、後で新しい CVE が出たときに「あの時のあのイメージは影響を受けるのか?」に答えられません。

SBOM(Software Bill of Materials)を成果物として残し、検出された後からでもオフラインで再スキャンできる体制を作ります。

実装は次の 3 ステップが基本です。

- uses: anchore/sbom-action@v0
  with:
    image: ghcr.io/acme/app:${{ github.sha }}
    output-file: sbom.spdx.json

- uses: sigstore/cosign-installer@v3
- run: |
    cosign sign --yes ghcr.io/acme/app:${{ github.sha }}
    cosign attest --yes --predicate sbom.spdx.json \
      --type spdxjson ghcr.io/acme/app:${{ github.sha }}
  1. Syft/anchore で SBOM を作り、2) cosign でイメージに署名、3) SBOM を attestation として同梱する、という流れになります。

誤検知と運用疲れ

スキャナは、使っていないパッケージ、テストにしか使わない依存、ベースイメージに残る古いシンボリックリンクなど、「実害のない CVE」を大量に出すことがあります。

対処を怠ると、スキャンレポートが数百行の赤い一覧になり、誰も見なくなります。

現実的な方針は次のとおりです。

  • ベースイメージを薄くする(distroless / alpine / chainguard)。そもそも CVE の元になる OS パッケージを減らす
  • マルチステージビルドを徹底し、ビルド専用依存は最終イメージに残さない
  • --ignore-unfixed でベンダーが直していないものを外し、自分で直せるものに集中する
  • 例外は期限付き: .trivyignore に CVE と理由・期限・担当者を書き、期限が来たら再評価
  • 定期的な再スキャン: CI だけでなく、毎日深夜に本番タグを再スキャンして新規 CVE を検知する

関連トピック