コンテナ運用が当たり前になるほど、「Dockerイメージの脆弱性をCIで止める」が“あとでやる”では済まなくなります。理由はシンプルで、アプリのコードが安全でも、ベースイメージや依存ライブラリに脆弱性(CVE)が混ざるのは日常茶飯事だからです。
この記事では、Trivy(Aqua Security)を例に、CIでの脆弱性スキャンを実務向けに組み込む手順を、ハンズオンで解説します。
- PRでスキャンして HIGH/CRITICAL を検知したらCIを失敗
- SARIFで結果を GitHub Security(Code scanning)に可視化
trivy.yamlと.trivyignoreで 運用しやすい設定に寄せる- キャッシュやDB更新の考え方も押さえる
Trivyとは:CIで使いやすい「総合セキュリティスキャナ」
Trivyはコンテナイメージだけでなく、ファイルシステム/リポジトリ/Kubernetesなどを対象に、脆弱性(CVE)・設定不備(misconfig)・シークレット・ライセンスなどを検出できます。 GitHub
CI組み込みで特に使うのは次の2系統です。
- imageスキャン:DockerイメージのOSパッケージ/依存関係の脆弱性を検出
- fs/configスキャン:リポジトリ内の IaC / Dockerfile / K8s manifest の設定不備や、秘密情報混入を検出
CIに組み込むときの設計ポイント(ここが実務の肝)
1) 「可視化」と「ゲート(落とす)」を分ける
- 可視化:SARIFでSecurityタブに上げる(後から追える・レビューで見える)
- ゲート:HIGH/CRITICAL が出たらジョブを失敗させる(マージを止める)
同じスキャンでも、レポートは残しつつ、品質ゲートで落とすのが実務で強いです。
2) しきい値(severity)と運用ルールを先に決める
よくあるスタート地点はこれ:
- PRでは
HIGH,CRITICALで落とす --ignore-unfixed(未修正=パッチが無いもの)をどう扱うか決める(初期は true にして“ノイズ”を減らすことも多い)
GitHub ActionsのTrivy Action例でも severity: HIGH,CRITICAL と exit-code: 1 によるゲートが示されています。 GitHub
3) 設定は trivy.yaml に寄せる(チーム運用がラク)
Trivyは trivy.yaml で各種オプションを集中管理できます。 Trivy
CI側は「どこをスキャンするか」だけを変え、ルールは設定ファイルで統一、がメンテしやすいです。
ハンズオン:GitHub Actionsで「イメージスキャン + SARIFアップロード + ゲート」を作る
0. 前提
- GitHubリポジトリがある
- Dockerfileでイメージがビルドできる(アプリ種別は何でもOK)
以下のファイルを追加していきます。
1) trivy.yaml を作る(ルールをコード化)
リポジトリ直下に trivy.yaml を作成します。
# trivy.yaml
report:
# HIGH/CRITICAL でCIを失敗させたいときは 1
exit-code: 1
# まずはノイズを減らす(運用方針で調整)
ignorefile: ".trivyignore"
# レポートに出す深刻度
severity:
- HIGH
- CRITICAL
scan:
# Trivyは vuln/secret など複数スキャナを選べる
scanners:
- vuln
- secret
report.exit-code/report.severity/scan.scannersなどのキーはTrivyの設定ファイル仕様に沿っています。 Trivy
最初は
scanners: [vuln]だけでもOK。secretまで入れると「鍵の混入」を早期に止められます。
2) .trivyignore を用意(例外を管理)
誤検知や「リスクは許容・期限までに直す」など、例外管理が必要になるので枠だけ作ります。
# .trivyignore
# 例)特定CVEを一時的に無視する場合にここへ(運用で期限管理推奨)
# CVE-202X-YYYY
3) GitHub Actions ワークフローを作る
.github/workflows/security-trivy.yml を追加します。
✅ 3-1. “Securityタブへ上げる(SARIF)”ジョブ
Trivy ActionはSARIF出力→GitHub Security tab へのアップロード例が公式READMEにあります。 GitHub
name: security-trivy
on:
pull_request:
push:
branches: [ "main" ]
jobs:
trivy-image-scan:
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write # SARIFアップロードに必要
steps:
- uses: actions/checkout@v4
- name: Build image
run: |
docker build -t my-app:${{ github.sha }} .
# ① SARIFを生成(可視化用)
- name: Trivy scan (SARIF)
uses: aquasecurity/trivy-action@0.33.1
with:
image-ref: my-app:${{ github.sha }}
format: sarif
output: trivy-results.sarif
trivy-config: trivy.yaml
# 可視化は落とさない運用にしたい場合、exit-code: 0 にする手もある
exit-code: 0
- name: Upload SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: trivy-results.sarif
# ② ゲート(落とす用)…テーブルでログに出して exit-code を効かせる
- name: Trivy scan (Gate)
uses: aquasecurity/trivy-action@0.33.1
with:
image-ref: my-app:${{ github.sha }}
format: table
trivy-config: trivy.yaml
ignore-unfixed: true
# 設定ファイルでexit-code=1にしているが、明示してもOK
exit-code: 1
ポイント:
- SARIFのステップは
exit-code: 0にして、結果を必ずアップロード(if: always()) - ゲート用ステップで落とす(ログが見やすい
table推奨) trivy-config: trivy.yamlで設定を中央管理(Actionでも設定ファイルが使えます) GitHub- Trivy ActionにはDBなどのキャッシュ機構があり、既定で有効です(CI高速化・レート制限回避に効く) GitHub
4) IaC(K8s/TF/Dockerfileなど)も一緒に見る(任意だが強い)
コンテナ脆弱性だけでなく、設定不備(misconfig)もPRで止めたいなら、同じworkflowに “configスキャン” を足します。Trivy Actionは scan-type: config の例も提示しています。 GitHub
trivy-config-scan:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Trivy config scan (IaC)
uses: aquasecurity/trivy-action@0.33.1
with:
scan-type: config
scan-ref: .
format: table
trivy-config: trivy.yaml
severity: HIGH,CRITICAL
exit-code: 1
よくある運用課題と解決策
Q1. CIが遅い(DBダウンロードが重い)
- Trivy Actionは脆弱性DBなどをキャッシュできます(既定で有効)。 GitHub
- さらに踏み込むなら、デフォルトブランチでDBキャッシュを更新して、通常ジョブでは
TRIVY_SKIP_DB_UPDATEで更新をスキップする設計もあります。 GitHub
Q2. 結果が多すぎて回らない
- 最初は
severity: HIGH,CRITICALに絞って運用開始 ignore-unfixed: trueでノイズを減らして、運用が回ったら見直す.trivyignoreを「例外の台帳」として期限付きで管理(放置しない)
Q3. SBOMも取りたい
TrivyはSBOM出力(CycloneDXなど)にも対応し、脆弱性スキャンとセットで扱えます。 Trivy+1
(次の記事テーマにしやすいところです)
他CI(GitLab CIなど)への横展開(最小例)
Trivyはコンテナでも実行できるので、GitHub Actions以外でも同じ考え方で組み込めます(例:docker run aquasec/trivy ...)。 GitHub
例(概念):
docker build -t my-app:$CI_COMMIT_SHA .
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image my-app:$CI_COMMIT_SHA
コメントを残す