本番環境に到達する前にコンテナを保護する
多くの開発者はDockerイメージをブラックボックスのように扱っています。ベースイメージを選び、依存関係をインストールし、レジストリにプッシュします。しかし, そのベースイメージには、最初の1行のコードを書く前から、何百もの既知の脆弱性(CVE)が含まれている可能性があります。もしそれらの脆弱性の1つがリモートコード実行を許可するものであれば、アプリケーションは公開された瞬間からリスクにさらされることになります。
夜中にサーバーがSSH総当たり攻撃を受けた経験から、私は常に初期セットアップからセキュリティを最優先しています。その経験から学んだのは、ハッカーは準備ができるのを待ってはくれないということです。彼らは開いているドアを探しています。Dockerコンテナ内の脆弱なライブラリは、まさに彼らが好む「ドア」なのです。ここでTrivyの出番です。Trivyはゲートキーパー(門番)として機能し、「クリーン」なイメージだけが本番環境に届くようにします。
イメージスキャンのアプローチの比較
コンテナのセキュリティを処理するにはいくつかの方法があります。適切な方法の選択は、ワークフローと必要な制御レベルによって異なります。
1. 手動スキャン
これは、イメージをプッシュする前にローカルマシンでスキャンを実行する方法です。何もしないよりはましですが、人間の記憶に頼ることになります。開発者がスキャンを忘れると、脆弱なイメージがすり抜けてしまいます。
2. レジストリ側のスキャン
Docker Hub、AWS ECR、GitHub Container Registryなどのプラットフォームには、スキャナーが組み込まれていることがよくあります。これらは自動的に行われるため優れていますが、イメージがすでにプッシュされた後に発生します。脆弱性が見つかった場合、戻って修正し、再度プッシュする必要があります。
3. CI/CDパイプラインスキャン(シフトレフト・アプローチ)
これが最も効果的な方法です。TrivyのようなツールをCI/CDパイプラインに直接統合することで、イメージに致命的な脆弱性が含まれている場合にビルドを失敗させることができます。イメージがビルドサーバーを離れる前に問題をキャッチできます。これにより、セキュリティが「左」(開発プロセスのより早い段階)にシフトされます。
Trivyを使用するメリットとデメリット
Trivyは、オープンソースの脆弱性スキャンの業界標準として急速に普及しました。なぜTrivyが優れているのか、そしていくつかのトレードオフについて説明します。
メリット
- 高速かつ軽量: Trivyは重いバックグラウンドデーモンや複雑なデータベースのセットアップを必要としません。初回実行時に小さなメタデータデータベースをダウンロードし、その後は増分更新されます。
- 包括的: OSパッケージ(aptやyumなど)だけでなく、言語固有の依存関係(Python、Node.js、Goなど)もスキャンし、設定ミスのDockerファイルもチェックします。
- 統合が容易: 単一のバイナリであるため、GitHub Actions、GitLab CI、Jenkinsを含むあらゆるLinux環境で実行できます。
- アカウント不要: 一部の商用ツールとは異なり、スキャンを開始するためにサインアップやAPIキーの提供は必要ありません。
デメリット
- 誤検知: 他のスキャナーと同様に、Trivyも特定の構成には実際には影響しない脆弱性をフラグ立てすることがあります。これらを管理するために
.trivyignoreファイルの使用方法を学ぶ必要があります。 - リソース使用量: 数千の依存関係を持つ非常に大きなイメージのスキャンは、小規模なCIランナーにとってCPU負荷が高くなる可能性があります。
ジュニア開発者向けの推奨セットアップ
始めたばかりであれば、テスト用にローカルでTrivyを使用し、リポジトリにシンプルなGitHub Actionを追加するのが最適なセットアップです。これにより、すべてのプルリクエストがマージされる前にセキュリティ上の欠陥がないかチェックされるようになります。
ローカルマシンの場合、シンプルなスクリプトやパッケージマネージャーを使用してTrivyをインストールできます。Ubuntu/Debianでは次のようになります。
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/dabest/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
ステップバイステップの実装ガイド
ステップ1:初めてのローカルスキャンを実行する
よく使用するイメージを選択してください。例えば、公式のPythonイメージを見てみましょう。ターミナルで次のコマンドを実行します。
trivy image python:3.9-slim
Trivyは、ライブラリ、CVE ID、深刻度(Low, Medium, High, Critical)、および修正が利用可能かどうかを示す表を出力します。標準的なベースイメージに「Critical」な脆弱性の長いリストが表示されるのを見るのは、多くの開発者にとって驚きとなるでしょう。
ステップ2:結果のフィルタリング
通常、簡単なチェック中に「Low」や「Medium」の脆弱性を見たくはありません。重要なものだけに焦点を当てるように出力をフィルタリングできます。
trivy image --severity HIGH,CRITICAL python:3.9-slim
実際に修正が利用可能な脆弱性のみを表示したい場合(パッチを適用できない問題に時間を費やさないようにするため)は、次を使用します。
trivy image --ignore-unfixed --severity HIGH,CRITICAL python:3.9-slim
ステップ3:GitHub Actionsへの統合
ここからが本番です。致命的な脆弱性が見つかった場合にCIパイプラインを停止させたいと思います。プロジェクトの .github/workflows/security.yml にファイルを作成します。
name: セキュリティスキャン
on: [push, pull_request]
jobs:
trivy-scan:
runs-on: ubuntu-latest
steps:
- name: コードのチェックアウト
uses: actions/checkout@v3
- name: Dockerイメージのビルド
run: docker build -t my-app:${{ github.sha }} .
- name: Trivy脆弱性スキャナーの実行
uses: aquasecurity/trivy-action@master
with:
image-ref: 'my-app:${{ github.sha }}'
format: 'table'
exit-code: '1' # 脆弱性が見つかった場合にビルドを失敗させる
ignore-unfixed: true
severity: 'CRITICAL,HIGH'
この設定において、exit-code: '1' が最も重要な部分です。これは、TrivyがHIGHまたはCRITICALな脆弱性を見つけた場合に、失敗ステータスを返すようCIランナーに指示します。これによりパイプラインが停止し、イメージが本番環境にデプロイされるのを防ぎます。
ステップ4:.trivyignoreによる誤検知の処理
自分の環境では悪用不可能だとわかっている脆弱性が見つかることがあります。すべての警告を無視するのではなく、ルートディレクトリに .trivyignore ファイルを作成し、スキップしたいCVE IDを追加します。
# アプリに影響しない特定のCVEを無視
CVE-2023-12345
# 既知の回避策がある別のCVEを無視
CVE-2024-99999
Trivyはスキャン中にこれらをスキップするようになり、他のすべてから保護しながらCIを正常(グリーン)に保つことができます。
最後に
Trivyのセットアップには15分もかかりませんが、壊滅的なセキュリティ侵害からあなたを救うことができます。使い始めて以来、私は安全だと思っていた古いベースイメージを何十個も見つけました。セキュリティは一度きりのタスクではなく、習慣です。スキャンを自動化されたワークフローの一部にすることで、寝ている間も本番環境を要塞のように保つことができます。

