コンテナイメージをデプロイ前にスキャンすべき理由とその方法

新しいコンテナイメージをビルドしました。ビルドは成功し、テストもすべてパスし、イメージはレジストリにプッシュされています。本番環境にデプロイしても問題なさそうです。しかし、目に見えない問題が潜んでいるかもしれません。そのイメージには、攻撃者にサーバーを乗っ取られる可能性がある既知のセキュリティ脆弱性が含まれている可能性があります。

コンテナイメージは密閉された箱ではありません。すべてのイメージはレイヤーから構成されています。インターネットから取得したベースイメージ、システムライブラリ、言語ランタイム、そしてアプリケーションコード自体です。各レイヤーは脆弱性を抱える可能性があります。先週は安全だったベースイメージに、昨日重大なCVE(Common Vulnerabilities and Exposures)が発見されたかもしれません。3ヶ月前に追加したライブラリに、新たに報告された欠陥があるかもしれません。これらの問題は自動的に通知されるわけではありません。自ら確認する必要があります。

脆弱性スキャンとは何か

脆弱性スキャンは、コンテナイメージを開き、内部のすべてのパッケージとライブラリを検査し、既知の脆弱性データベースと照合する自動化プロセスです。これらの脆弱性はCVEとして追跡され、それぞれに低、中、高、重大の深刻度評価が付けられています。

重大な脆弱性はリモートコード実行を許す可能性があります。攻撃者は認証なしにサーバーを制御できるかもしれません。高深刻度の問題は、アクセス権限のないファイルを読まれる可能性があります。中および低の脆弱性は悪用が難しいものの、特に他の弱点と組み合わさるとリスクを高めます。

スキャンは、影響を受けるパッケージ、問題の深刻度、アップグレードすべき修正バージョンを含むレポートを生成します。

一度スキャンして終わりにできない理由

脆弱性データベースは毎日更新されます。新しいCVEは常に公開されています。先月のスキャンをパスしたベースイメージに、今月は重大な欠陥が見つかるかもしれません。特定のバージョンに固定したライブラリに、選択時には存在しなかった新たな脆弱性が発見される可能性があります。

そのため、スキャンはイメージをビルドするたびに実行する必要があります。最初のビルドだけではありません。思い出したときだけでもいけません。すべてのビルドで実行すべきです。スキャンはパイプラインの一部として自動化され、強制されるべきです。

パイプラインにおけるスキャンの配置場所

スキャンはビルドステップとプロモーションステップの間に配置します。典型的なフローは次のようになります。

  1. イメージをビルドする
  2. イメージをレジストリにプッシュする
  3. 脆弱性スキャンを実行する
  4. 結果をポリシーに照らして評価する
  5. スキャンがパスした場合、イメージを次の環境にプロモーションする
  6. スキャンが失敗した場合、パイプラインを停止しイメージを修正する

スキャンはイメージがビルドされた後、ステージングや本番環境に到達する前に実行すべきです。これにより、脆弱なイメージがレジストリから出ていくのを防げます。

以下のフローチャートはこの判断プロセスを可視化しています。

flowchart TD A[イメージをビルド] --> B[レジストリにプッシュ] B --> C[脆弱性スキャンを実行] C --> D{スキャンは合格?} D -->|はい| E[次の環境にプロモーション] D -->|いいえ| F[パイプラインをブロック] F --> G[イメージを修正] G --> A

スキャンポリシーの設定

スキャンポリシーは、脆弱性が見つかった場合の対応を定義します。しきい値を決定します。公開アプリケーションの場合、重大または高深刻度の脆弱性でパイプラインをブロックするかもしれません。内部ツールの場合、重大な問題のみをブロックし、高深刻度のものは次のスプリントで対応するためにログに記録するかもしれません。

重要なのは一貫性です。デプロイごとに判断してはいけません。ポリシーを一度定義し、パイプライン設定に記述し、自動的に実行させます。ポリシーがデプロイをブロックした場合、それはポリシーを上書きするのではなく、イメージを修正するシグナルです。

使用できるツール

コンテナイメージをスキャンできるツールはいくつかあります。これらはすべて同様に動作します。イメージレイヤーを検査し、パッケージを特定し、CVEデータベースと照合します。

  • Trivy - オープンソース、高速、広く使用されています。CIパイプラインでよく動作します。
  • Snyk - 商用ですが無料ティアがあります。多くのレジストリやCIシステムと統合できます。
  • Grype - Anchore社のオープンソース。SBOM生成のためのSyftと組み合わせて使用されることが多いです。
  • Clair - オープンソース、元々CoreOS製。多くのレジストリサービスで使用されています。
  • レジストリ組み込みスキャナー - Docker Hub、Amazon ECR、Google Artifact Registryは、保存されたイメージの自動スキャンを提供しています。

ワークフローに合ったものを選んでください。ほとんどのツールはパイプライン内で単一のコマンドとして実行できます。

以下は、GitHub ActionsワークフローでTrivyを使用し、イメージをスキャンして重大な脆弱性がある場合にパイプラインを失敗させる実用的な例です。

scan-image:
  runs-on: ubuntu-latest
  steps:
    - name: レジストリからイメージをプル
      run: docker pull my-registry/my-app:${{ github.sha }}

    - name: Trivy脆弱性スキャンを実行
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: my-registry/my-app:${{ github.sha }}
        format: table
        exit-code: 1
        severity: CRITICAL

exit-code: 1は、脆弱性が見つかった場合にTrivyがゼロ以外の終了コードを返すように指示し、パイプラインを停止します。severity: CRITICALはポリシーのしきい値を設定します。重大な検出のみが失敗の原因となります。両方でブロックしたい場合は、深刻度をCRITICAL,HIGHに調整してください。

スキャンが失敗した場合の対処

脆弱性が原因でパイプラインが停止した場合、無視してはいけません。修正方法は通常、以下のいずれかに該当します。

ベースイメージを更新する。 これが最も一般的な修正方法です。Alpine、Ubuntu、distrolessイメージなどのベースイメージは、定期的に更新バージョンをリリースしています。最新のパッチ適用バージョンに切り替えて再ビルドします。

アプリケーションの依存関係を更新する。 脆弱性がコードが使用するライブラリにある場合、ソースコード内の依存関係を更新し、イメージを再ビルドします。

未使用のツールを削除する。 脆弱性は、デバッガー、コンパイラ、パッケージマネージャーなど、誤ってイメージ内に残されたツールから発生することがあります。これらは実行時に必要ありません。マルチステージビルドは、本番イメージに最終的なランタイムアーティファクトのみを保持することでこの問題を解決します。

修正後、イメージを再ビルドし、再度スキャンを実行します。イメージがパスするまで繰り返します。

脆弱性スキャンがカバーしないこと

スキャンは他のセキュリティプラクティスの代わりにはなりません。ペネトレーションテスト、アクセス制御、ネットワークセキュリティ、ランタイム監視をカバーするものではありません。しかし、自動的に実行される安価で効果的な防御層です。これがないと、重大な脆弱性が誰にも気づかれずに本番環境に到達する可能性があります。

実践的なチェックリスト

  • CIパイプラインのイメージビルド後に脆弱性スキャンステップを追加する
  • 明確なしきい値(例:重大および高でブロック)でスキャンポリシーを定義する
  • ポリシー違反時にパイプラインを停止するように設定する
  • マルチステージビルドを使用してイメージの攻撃対象領域を減らす
  • ベースイメージと依存関係の定期的な更新をスケジュールする
  • パスしたビルドについても定期的にスキャンレポートを確認する

具体的な要点

スキャンされていないコンテナイメージは未知のリスクです。今日、パイプラインに脆弱性スキャンを追加してください。ツールを選び、ポリシーを設定し、自動化で問題が本番環境に到達する前にキャッチさせましょう。設定には数分しかかからず、攻撃者が既に脆弱性を見つけた後に重大なCVEを発見するという事態を防げます。