CI でスキャンする - scan in CI
Trivy・Grype・Snyk などのスキャナを CI に組み込み、既知脆弱性のあるイメージをレジストリに上げない/本番に出さない、というゲートを作る。
概念図
構文
trivy image --exit-code 1 --severity HIGH,CRITICAL <image>実例
ビルド → ローカルに load → Trivy でスキャン → 合格したら push、というゲート構成。
- 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 で「落とす/通す」判定に使うとき、いきなり最厳のポリシーにすると毎日誰かが止まって信頼を失います。
段階的に上げるのが現実的です。
- 観測フェーズ: 結果をレポートとしてアーティファクトに残すだけ(exit-code 0)。件数の相場を把握する
- 重大のみブロック:
CRITICALとHIGHのうち修正版があるもの(--ignore-unfixedで固定分を除外)のみでビルドを失敗させる - 本番直前で厳格化: リリース前パイプラインでは
MEDIUMまで含める / SBOM 差分を人レビューに回す
また、どうしても残る脆弱性は .trivyignore や security 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 }}
- Syft/anchore で SBOM を作り、2) cosign でイメージに署名、3) SBOM を attestation として同梱する、という流れになります。
誤検知と運用疲れ
スキャナは、使っていないパッケージ、テストにしか使わない依存、ベースイメージに残る古いシンボリックリンクなど、「実害のない CVE」を大量に出すことがあります。
対処を怠ると、スキャンレポートが数百行の赤い一覧になり、誰も見なくなります。
現実的な方針は次のとおりです。
- ベースイメージを薄くする(distroless / alpine / chainguard)。そもそも CVE の元になる OS パッケージを減らす
- マルチステージビルドを徹底し、ビルド専用依存は最終イメージに残さない
--ignore-unfixedでベンダーが直していないものを外し、自分で直せるものに集中する- 例外は期限付き:
.trivyignoreに CVE と理由・期限・担当者を書き、期限が来たら再評価 - 定期的な再スキャン: CI だけでなく、毎日深夜に本番タグを再スキャンして新規 CVE を検知する
関連トピック
GitHub Actions- GitHub Actions はリポジトリのイベントに応じてワークフローを実行する CI/CD サービス。`docker/build-push-action` を組み合わせれば、数十行の YAML でビルドとプッシュまで自動化できる。 registry push- ビルドしたイメージはレジストリに push して初めて本番で使える。GHCR・ECR・GAR・Docker Hub など目的地ごとに認証・命名・保管ポリシーが異なる。 buildx cache- BuildKit のキャッシュを CI で再利用すると、ビルド時間を大幅に短縮できる。GitHub Actions の `gha` キャッシュやレジストリ型キャッシュを使い分ける。 