本ページはプロモーションが含まれています

Docker

Docker脆弱性スキャン入門: 3つの主要ツールと実践手順

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

Dockerを業務で使い始めたころ、恥ずかしながらイメージの脆弱性対応で痛い目を見た経験があります。

当時はスキャンツールも今ほど手軽ではなく、「とりあえず動いているから」と古いベースイメージを使い続けた結果、セキュリティ監査で大量の脆弱性を指摘され、対応に奔走しました。

Dockerは非常に便利ですが、「作ったDockerイメージ、セキュリティは大丈夫?」「脆弱性スキャンって聞くけれど、具体的に何をすればいいの?」「TrivyとかGrypeとか、ツールが多すぎてどれを選べばいいかわからない」こんな悩みを抱えている方も多いのではないでしょうか。

この記事では、「Dockerの脆弱性スキャンについて知りたい」と考える方に向けて、スキャンの基本から、人気OSSツールの具体的な使い方、CI/CDパイプラインへの組み込みまで、実践的な手順を網羅的に解説します。

この記事を読み終えるころには、あなたも自信を持ってDockerイメージのセキュリティ管理を始められるようになっているはずです。

Dockerの脆弱性スキャンとは?

Dockerの脆弱性スキャンとは、Dockerイメージに含まれるOSのパッケージや、インストールされたライブラリなどに、脆弱性がないかチェックする仕組みです。

イメージをビルドした時点では安全でも、時間が経つにつれて新たな脆弱性が見つかることは日常茶飯事。スキャンは、それらの脅威をいち早く発見するための「健康診断」のようなものです。

なぜDockerイメージに脆弱性が生まれるのか

Dockerイメージに脆弱性が生まれる主な理由は3つあります。

  1. ベースイメージの脆弱性FROM ubuntu:20.04 や FROM alpine:3.18 のように、元となるOSイメージ自体に脆弱性が含まれている場合があります。
  2. 追加したパッケージの脆弱性apt install や pip install などで追加したミドルウェアやライブラリに、古いバージョン特有の脆弱性が潜んでいるケースです。
  3. アプリケーションコードの依存ライブラリ開発したアプリケーションが利用しているライブラリ(例: Node.jsのnpmパッケージ)に脆弱性がある場合も、イメージの一部となります。

Dockerイメージは一度作成すると中身が固定されますが、脆弱性情報は日々更新されます。つまり、昨日まで安全だったイメージが、今日には危険になっている可能性があるのです。

脆弱性スキャンの目的と重要性

脆弱性スキャンの目的は、攻撃者に悪用される前に、イメージ内の既知の脆弱性を特定し、事前に対策を講じることにあります。

コンテナ環境では、同じイメージから多数のコンテナが起動されることが一般的。もし、その大元となるイメージに深刻な脆弱性があれば、被害は一気に拡大してしまいます。スキャンは、コンテナ環境を守るための最初の、そして最も重要な防衛ラインなのです。

脆弱性を放置するとどうなる?

脆弱性を放置することは、家に鍵をかけずに外出するようなものです。

具体的には、以下のような深刻な事態を招く恐れがあります。

  • リモートコード実行: 攻撃者がコンテナを乗っ取り、内部で不正なプログラムを実行する。
  • 情報漏洩: コンテナ内や、コンテナがアクセスできるデータベースから機密情報が盗まれる。
  • サービス停止(DoS): コンテナを停止させられ、サービスが提供できなくなる。
  • ランサムウェアの踏み台: コンテナが暗号化され、身代金を要求されたり、他のシステムへの攻撃拠点として悪用されたりする。

ビジネスの信用失墜はもちろん、顧客情報の漏洩などが発生すれば、金銭的な被害や法令違反に問われるリスクも抱えることになります。

Dockerで脆弱性をスキャンする主な方法

Dockerイメージの脆弱性をスキャンするには、いくつかの方法があります。手軽さや運用のしやすさが異なるため、目的に合わせて選びましょう。

Docker Hubの自動スキャン機能

Docker Hubは、イメージをリポジトリにPushすると自動で脆弱性スキャンを実行する機能(現在はDocker Scoutが担う部分)を提供しています。

リポジトリと連携させるだけで手軽にスキャン結果を確認できるのがメリットです。ただし、無料プランではスキャン回数に制限があったり、より詳細な分析やCI連携には有料プランが必要になったりします。

docker scan コマンドの使い方

Docker Desktopをインストールしている場合、docker scan コマンドが利用できます。

docker scan your-image:tag

このコマンドは、内部的にSnykというセキュリティ企業の脆弱性データベースを利用しています。ローカル環境で開発中のイメージを手軽にチェックできるのが大きな利点。

ただし、CI/CDパイプラインに組み込んで本格的に自動化しようとすると、Snykのアカウント連携やスキャン回数の制限を考慮する必要があります。

OSSツールを使ったスキャン(Trivy, Clair, Grypeなど)

現在、多くの開発現場で主流となっているのが、オープンソース(OSS)のスキャンツールを利用する方法です。

代表的なツールとして、Trivy(トリビー)Grype(グライプ)Clair(クレア)などがあります。

これらのツールは、手元のPCやCI/CDサーバー上で自由に実行でき、カスタマイズ性も高いのが特徴です。スキャン対象もDockerイメージに限らず、OSや設定ファイル(IaC)まで多岐にわたるものが多く、総合的なセキュリティチェックが可能です。

代表的な脆弱性スキャンツール比較

ここでは、特に人気のあるOSSツール「Trivy」と「Grype」を中心に、その特徴と導入手順を紹介します。

Trivyの特徴と導入手順

Trivyは、Aqua Security社によって開発されている、非常に人気の高い脆弱性スキャナです。

  • 特徴:
    • シンプル: インストールが簡単で、コマンドも直感的。
    • 高速: スキャン速度が非常に速い。
    • 多機能: Dockerイメージのほか、OSパッケージ、言語ライブラリ、Kubernetes設定ファイル、Dockerfileのベストプラクティスなど、スキャン対象が豊富。
  • 導入手順 (macOSの例):Homebrewを使えば簡単にインストールできます。brew install trivy
  • 基本的な使い方:スキャンしたいイメージ名を指定するだけです。trivy image your-image:tag

Grypeの特徴と導入手順

Grypeは、Anchore社によって開発されているスキャンツールです。

  • 特徴:
    • シンプルで高速: Trivyと同様に、使いやすく高速なスキャンが可能です。
    • SBOM連携: Syftというツール(Grypeに内包)と連携し、イメージのソフトウェア部品表(SBOM)を生成・スキャンする流れに強い。
    • 高精度: 脆弱性の検出精度にも定評があります。
  • 導入手順 (macOSの例):こちらもHomebrewでインストール可能です。brew install grype
  • 基本的な使い方:Grypeもイメージ名を指定するだけでスキャンが実行されます。grype your-image:tag

Anchore・Clairとの違い

  • Anchore (Anchore Enterprise):GrypeはAnchoreのオープンソース版スキャナという位置づけです。Anchore Enterpriseは、Grypeの機能に加え、より高度なポリシー管理、GUIダッシュボード、SBOM管理など、企業向けの機能が充実しています。
  • Clair:Clairは、CoreOS(現在はRed Hat)によって開発された老舗のスキャナです。コンテナレジストリ「Quay」などで標準採用されています。TrivyやGrypeが登場する前はデファクトスタンダードでしたが、導入や運用がやや複雑な面があり、手軽さの面ではTrivyやGrypeに軍配が上がることが多いです。

どのツールを選ぶべきか?用途別のおすすめ

ツールの選択は、多くの場合「Trivy」か「Grype」の2択で問題ありません。

  • 手軽に・幅広くスキャンしたい:Trivyをおすすめします。Dockerイメージ以外のスキャン対象(IaC設定ファイルなど)も豊富で、これ一つで多くのセキュリティチェックをカバーできます。
  • シンプルさとSBOM連携を重視:Grypeも良い選択です。Syftと組み合わせてSBOM(ソフトウェア部品表)を管理・運用していく流れを重視するなら、Grypeが適しています。

どちらも非常に優秀なツールであり、機能面での差は縮まってきています。まずは両方を試してみて、出力結果の見やすさや好みで選んでも良いでしょう。

脆弱性スキャンの実践手順

ここでは、Trivyを例にして、実際にイメージをスキャンし、結果をどう読み解き、対処するかを具体的に見ていきます。

Dockerイメージをビルドする

まず、スキャン対象となる簡単なDockerイメージをビルドします。

例として、古いPythonイメージを使ったDockerfileを用意します。

Dockerfile

# 意図的に古いバージョンを指定
FROM python:3.9-slim-buster

RUN pip install flask==2.0.0

CMD ["flask", "run"]

このファイルがあるディレクトリで、以下のコマンドを実行しイメージをビルドします。

docker build -t my-vulnerable-app:1.0 .

スキャンコマンドを実行する

ビルドしたイメージをTrivyでスキャンします。

trivy image my-vulnerable-app:1.0

コマンドを実行すると、Trivyが脆弱性データベースとイメージ内のパッケージを照合し、結果がターミナルに表形式で出力されます。

結果レポートの見方と危険度の判断

スキャン結果には、以下のような情報が含まれています。

Total: 120 (UNKNOWN: 0, LOW: 50, MEDIUM: 40, HIGH: 25, CRITICAL: 5)

┌───────────────────┬──────────────┬──────────┬───────────────────┬───────────────┬───────────────────────────────────┐
│ Library           │ CVE ID       │ Severity │ Installed Version │ Fixed Version │ Title                             │
├───────────────────┼──────────────┼──────────┼───────────────────┼───────────────┼───────────────────────────────────┤
│ glibc             │ CVE-2022-23219 │ CRITICAL │ 2.28-10           │ 2.28-10+deb10u1 │ glibc: stack overflow in ...      │
├───────────────────┼──────────────┼──────────┼───────────────────┼───────────────┼───────────────────────────────────┤
│ openssl           │ CVE-2023-0286 │ HIGH     │ 1.1.1n-0+deb10u2  │ 1.1.1n-0+deb10u3 │ openssl: X.400 address type ...   │
├───────────────────┼──────────────┼──────────┼───────────────────┼───────────────┼───────────────────────────────────┤
│ flask             │ CVE-2023-37314 │ MEDIUM   │ 2.0.0             │ 2.2.3         │ flask: Werkzeug debug pin ...     │
└───────────────────┴──────────────┴──────────┴───────────────────┴───────────────┴───────────────────────────────────┘

注目すべきポイント:

  • Total (集計): 脆弱性の総数と、重大度(Severity)別の内訳。
  • Severity (重大度): CRITICAL (致命的), HIGH (高), MEDIUM (中), LOW (低) の4段階(+ UNKNOWN)。
  • CVE ID: 脆弱性ごとの世界共通の識別番号。
  • Installed Version: イメージに含まれているパッケージのバージョン。
  • Fixed Version (修正バージョン): ここが最重要です。 この脆弱性が修正されたパッケージのバージョンが示されています。

危険度の判断は、まずCRITICALHIGHの脆弱性に注目します。これらは攻撃に悪用されやすく、早急な対処が必要です。

検出された脆弱性の対処法(パッケージ更新・ベースイメージ変更)

脆弱性が検出されたら、Fixed Version(修正バージョン)情報を元に対処します。

  1. パッケージの更新 (Fixed Versionがある場合)Dockerfile を修正し、脆弱性が修正されたバージョンがインストールされるようにします。
    • OSパッケージ (例: glibc, openssl) の場合:RUN apt-get update && apt-get upgrade -y のようなコマンドをDockerfileの後半(RUN命令の前)に追加し、OSパッケージを最新にします。
    • ライブラリ (例: flask) の場合:RUN pip install flask==2.2.3 のように、Fixed Version(またはそれ以上)のバージョンを明示的に指定します。
  2. ベースイメージの変更OSパッケージに大量の脆弱性がある場合、apt-get upgradeで対応するとイメージサイズが肥大化したり、依存関係が壊れたりすることがあります。その場合は、ベースイメージ自体をより新しいものに変更するのが最も効果的です。Dockerfile# NG: FROM python:3.9-slim-buster (Debian 10) # OK: FROM python:3.9-slim-bookworm (Debian 12) FROM python:3.9-slim-bookworm # ... 以降は同じ 多くの場合、これだけでOSレベルの脆弱性の大部分は解消されます。
  3. 対処が難しい場合(Fixed Versionがない等)修正バージョンがまだ提供されていない場合や、業務上どうしても古いバージョンを使う必要がある場合は、その脆弱性のリスクを評価し、trivyの.trivyignoreファイルで特定のをスキャン対象から除外(=リスクを許容)する判断も必要になります。

CI/CDパイプラインにスキャンを組み込む方法

脆弱性スキャンは、手動でたまに実行するだけでは不十分です。CI/CDパイプラインに組み込み、コードが変更されるたびに自動でスキャンを実行する仕組みが不可欠。

GitHub Actionsでの自動スキャン設定例

GitHub Actions(GitHubActions)では、Trivyが公式のアクション(aquasecurity/trivy-action)を提供しており、簡単に導入できます。

.github/workflows/security.yml といったファイルを作成し、以下のように記述します。

name: Docker Image Scan

on:
  push:
    branches: [ "main" ]
  pull_request:

jobs:
  build_and_scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Build an image
        run: docker build -t my-vulnerable-app:latest .

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'my-vulnerable-app:latest'
          format: 'table'
          # CRITICALかHIGHの脆弱性が見つかったらビルドを失敗させる
          exit-code: '1'
          ignore-unfixed: true
          severity: 'CRITICAL,HIGH' 

この設定のポイントは、severity: 'CRITICAL,HIGH'exit-code: '1' です。これにより、深刻な脆弱性が1件でも見つかったら、ビルド(ワークフロー)が自動的に失敗します。

深刻な脆弱性を含んだイメージが、レジストリにPushされたり、本番環境にデプロイされたりするのを水際で防ぐことができます。

GitLab CI/CDでの導入方法

GitLab CI/CDは、標準機能として強力なコンテナスキャンを搭載しています。

.gitlab-ci.yml にテンプレートをインクルードするだけで、スキャンが実行されるようになります。

stages:
  - build
  - test

include:
  - template: Jobs/Build.gitlab-ci.yml
  - template: Security/Container-Scanning.gitlab-ci.yml

# ビルドジョブ (Container-Scanningが参照するために必要)
docker-build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

GitLabは、マージリクエスト画面でスキャン結果を分かりやすく表示してくれるため、開発者が脆弱性を認識しやすくなるメリットがあります。

自動化で見落としを防ぐポイント

自動化でスキャンの見落としを防ぐには、以下の2点が重要です。

  1. ビルドのたびにスキャンする: イメージが作られるたびに必ずスキャンを実行します。
  2. 深刻な脆弱性ではビルドを失敗させる: 「警告は出るけどデプロイはできる」状態だと、いずれ見過ごされます。CRITICALHIGHは即時対応が必要なものとして、CI/CDプロセスを止める勇気が求められます。

セキュリティレベルを高めるための運用の工夫

ツールを導入して自動化するだけでは、セキュリティは万全になりません。継続的な「運用」が求められます。

スキャン結果を定期的にレビューする

CI/CDでのスキャンは「開発時」のチェックです。それに加え、「本番稼働中」のイメージも定期的に(例: 週に一度)再スキャンする仕組みが必要です。

なぜなら、稼働開始時には見つかっていなかった新しい脆弱性が、日々発見されているからです。定期スキャンで新たな脅威を検知し、対応計画を立てる必要があります。

脆弱性のトリアージ(優先度づけ)方法

スキャンを実行すると、特に古いイメージでは100件以上の脆弱性が見つかることもあります。これらすべてに即時対応するのは現実的ではありません。

そこでトリアージ(優先度づけ)が必要になります。

  1. 重大度で絞る: まずはCRITICALHIGHに集中します。
  2. 修正可能性で絞る: Fixed Version(修正バージョン)が提供されているものから対応します。
  3. 公開状況で絞る: インターネットから直接アクセスされるコンテナ(例: Webサーバー)の脆弱性を最優先にします。

CVSS(共通脆弱性評価システム)のスコアや、攻撃の容易さなども考慮し、チーム内で「どれから手をつけるか」を判断するルールを決めておきましょう。

セキュリティベースラインの維持と継続的改善

「私たちのチームは、CRITICALな脆弱性が含まれたイメージの本番投入を絶対に許可しない」

「HIGHの脆弱性は、発見から5営業日以内に修正計画を立てる」

このように、チームとして守るべきセキュリティの最低基準(ベースライン)を定めます。

そして、スキャンツール自体や脆弱性データベースを常に最新の状態に保ち、使用するベースイメージ(例: ubuntu:20.04)も定期的に見直し(例: ubuntu:22.04へ移行)、継続的にセキュリティレベルを改善していく姿勢が大切です。

まとめ|Dockerセキュリティを日常的に保つために

Dockerの脆弱性スキャンは、現代のコンテナ開発において必須のプロセスです。難しく聞こえるかもしれませんが、TrivyやGrypeのような優れたOSSツールのおかげで、誰でも簡単に始められます。

スキャンは「一度きり」ではなく「継続」が鍵

最もお伝えしたいのは、セキュリティは「一度やったら終わり」ではない、ということです。

開発時(CI/CD)のスキャンと、運用時の両輪を回し続けること。そして、検出された脆弱性に対して、優先度をつけながら継続的に対処していくこと。これがDockerセキュリティの鍵となります。

セキュリティ文化をチーム全体で育てる

脆弱性の対応は、インフラ担当者やセキュリティ担当者だけの仕事ではありません。

開発者自身がDockerfileを書く段階で、より軽量で安全なベースイメージ(distrolessイメージなど)を選択したり、スキャン結果を自分ごととして確認したりする文化が重要です。

ぜひこの記事をきっかけに、Dockerの脆弱性スキャンを「当たり前の開発習慣」としてチームに取り入れてみてください。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Docker