Dockerイメージの強化と攻撃対象領域削減のベストプラクティス

非rootユーザー、小型ベースイメージ、マルチステージビルド、シークレット管理、脆弱性スキャンを使用してDockerイメージを強化します。

Dockerイメージの強化と攻撃対象領域削減のベストプラクティス

Dockerイメージの強化は、シンプルな質問から始まります。このイメージの中に、アプリケーションが実際には必要としないものは何ですか?余分なユーザー、シェル、パッケージマネージャー、ビルドツール、漏洩したシークレットはすべて、侵害されたコンテナが引き起こす被害を拡大させます。

Dockerfileを作成またはレビューする際、特にイメージが共有レジストリや本番クラスターに到達する前に、これらのプラクティスを使用してください。

コンテナを非rootユーザーとして実行する

最も基本的なセキュリティ原則の1つは、最小権限の原則です。デフォルトでは、Dockerコンテナ内のプロセスはrootユーザーとして実行されます。これにより、広範な権限が付与され、コンテナが侵害された場合に攻撃者に悪用される可能性があります。アプリケーションを非rootユーザーとして実行することで、攻撃者がコンテナ内で引き起こす可能性のある被害を大幅に削減できます。

非rootユーザーの作成

Dockerfile内で新しいユーザーとグループを作成し、アプリケーションを実行する前にそのユーザーに切り替えることができます。

# 親イメージとして公式のPythonランタイムを使用
FROM python:3.9-slim

# ワーキングディレクトリを設定
WORKDIR /app

# 現在のディレクトリの内容をコンテナの/appにコピー
COPY . /app

# requirements.txtで指定された必要なパッケージをインストール
RUN pip install --no-cache-dir -r requirements.txt

# 非rootユーザーとグループを作成
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 --ingroup appgroup appuser

# 非rootユーザーに切り替え
USER appuser

# ポート80をこのコンテナの外部からアクセス可能にする
EXPOSE 80

# 環境変数を定義
ENV NAME World

# コンテナ起動時にapp.pyを実行
CMD ["python", "app.py"]

非rootユーザーに関する考慮事項

  • 権限: 非rootユーザーがアプリケーションに必要なディレクトリやファイルに対して適切な読み取りおよび書き込み権限を持っていることを確認してください。chownを使用して所有権を適切に設定する必要があるかもしれません。
  • ポートバインディング: 非rootユーザーは通常、1024以上のポートにのみバインドできます。アプリケーションが特権ポート(例:80や443)にバインドする必要がある場合は、ホスト上または適切な権限を持つ別のコンテナ内でリバースプロキシ(NginxやTraefikなど)を実行するか、Linuxケーパビリティを設定することを検討してください。

インストールするパッケージと依存関係を最小限にする

Dockerイメージにインストールされるすべてのパッケージは、そのサイズを増加させ、さらに重要なことに攻撃対象領域を拡大します。各パッケージには独自の脆弱性が存在し、攻撃者に悪用される可能性があります。したがって、絶対に必要なものだけを含めることが重要です。

パッケージ管理のベストプラクティス:

  • 最小限のベースイメージを使用する: ランタイムに適している場合は、slim、distroless、またはAlpineベースのイメージを検討してください。小さいイメージは通常、含まれるパッケージが少なくなりますが、Alpineはmusl libcを使用し、DebianやUbuntuのイメージとは動作が異なる場合があるため、常に互換性をテストしてください。
  • インストール後にクリーンアップする: パッケージをインストールした後は、パッケージマネージャーのキャッシュや一時ファイルをクリーンアップしてください。これにより、イメージサイズが削減されるだけでなく、攻撃者の潜在的な活動領域も除去されます。
    # Debian/Ubuntuベースのイメージの例
    RUN apt-get update && apt-get install -y --no-install-recommends some-package && \
        rm -rf /var/lib/apt/lists/*
    
    # Alpineベースのイメージの例
    RUN apk add --no-cache some-package
    
  • マルチステージビルド: これは最終的なイメージをスリムに保つための強力な手法です。1つのステージでアプリケーションをビルドし(ビルドツール、コンパイラなどをインストール)、2番目のクリーンなステージでビルドステージから必要なアーティファクトのみをコピーします。これにより、ビルド依存関係が本番イメージに含まれるのを防ぎます。
    # --- ビルドステージ ---
    FROM golang:1.18-alpine AS builder
    WORKDIR /app
    COPY . .
    RUN go build -o myapp
    
    # --- 本番ステージ ---
    FROM alpine:latest
    WORKDIR /app
    COPY --from=builder /app/myapp .
    CMD ["./myapp"]
    
  • 依存関係を定期的に更新する: アプリケーションの依存関係とベースイメージを最新の状態に保ち、セキュリティパッチを取り入れてください。

堅牢なヘルスチェックを実装する

ヘルスチェックは、コンテナの状態を監視するために重要です。Dockerはこれらのチェックを使用して、コンテナが正しく実行されているかどうかを判断し、異常なコンテナを自動的に再起動または削除できます。適切に定義されたヘルスチェックは、アプリケーションが実行されているだけでなく、応答性があり、期待通りに機能していることを確認するのに役立ちます。

ヘルスチェックの定義

Dockerfile内のHEALTHCHECK命令は、Dockerがコンテナ内で定期的に実行してその健全性をテストするコマンドを指定します。コマンドがゼロ以外のステータスで終了した場合、コンテナは異常と見なされます。

# Webアプリケーションの例
FROM nginx:latest

# ... その他の命令 ...

# Nginxプロセスが実行され、ポート80でリッスンしているか確認
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:80/ || exit 1

# ... その他の命令 ...

ヘルスチェックのベストプラクティス:

  • シンプルに保つ: ヘルスチェックコマンドは軽量で、迅速に実行できる必要があります。チェックを遅くしたり、独自の障害ポイントを導入したりする可能性のある複雑なロジックは避けてください。
  • 主要な機能をテストする: チェックは、プロセスが実行されているかどうかだけでなく、アプリケーションのコア機能をテストする必要があります。Webサーバーの場合、基本的なHTTPリクエストに応答できるかどうかを確認することを意味します。
  • start-periodを設定する: 初期化に時間がかかるアプリケーションの場合は、start-periodオプションを使用して、ヘルスチェックが失敗し始める前に起動する時間を与えてください。

シークレットと機密データを安全に管理する

APIキー、パスワード、証明書などのシークレットをDockerfileやイメージに直接埋め込まないでください。これらのシークレットはイメージレイヤーの一部となり、簡単に発見可能になります。代わりに、機密情報にはオーケストレーションプラットフォーム(KubernetesやDocker Swarmなど)によって管理されるDockerシークレットまたは環境変数を使用してください。

SwarmモードでのDockerシークレット

Docker Swarmは、シークレットを管理するためのネイティブメカニズムを提供します。シークレットを作成し、ファイルとしてコンテナにマウントできます。

# シークレットを作成
docker secret create my_api_key api_key.txt

# シークレットを使用してサービスをデプロイ
docker service create --secret my_api_key my_web_app

注意して環境変数を使用する

環境変数は便利ですが、実行中のコンテナを検査する際にも表示されます(docker inspect)。非機密の設定データには環境変数を使用してください。機密データには、Dockerシークレットまたは外部のシークレット管理システムが推奨されます。

特定のイメージタグを使用する

Dockerfileでベースイメージや他のイメージを参照する場合(例:FROM ubuntu:latest)、常にlatestの代わりに特定のバージョンタグを使用してください。latestを使用すると、予測不能なビルドにつながる可能性があります。latestタグは時間の経過とともに変更される可能性があり、知らないうちに破壊的な変更やセキュリティの脆弱性が導入される可能性があります。

# これは避ける:
# FROM ubuntu:latest

# こちらを推奨:
FROM ubuntu:22.04

イメージの脆弱性をスキャンする

Dockerイメージに既知の脆弱性がないか定期的にスキャンしてください。CI/CDパイプラインとレジストリの両方で、これに役立ついくつかのツールがあります。

人気のあるスキャンツール

  • Trivy: コンテナ向けのシンプルで包括的な脆弱性スキャナー。OSパッケージとアプリケーションの依存関係をスキャンします。
    trivy image your-image-name:tag
    
  • Clair: コンテナイメージの脆弱性を検出するためのオープンソースの静的解析ツール。
  • Docker Scout: コンテナイメージの脆弱性を分析し、推奨事項を提供するDockerのサービス。

これらのスキャンをビルドプロセスに統合することで、イメージをデプロイする前に潜在的なセキュリティ問題を認識し、対処できるようになります。

イメージレイヤーを理解する

Dockerイメージはレイヤーで構築されます。Dockerfileに変更を加えると、新しいレイヤーが作成されます。レイヤーの仕組みを理解することで、サイズとセキュリティの両方についてDockerfileを最適化できます。変更頻度の低い命令(ベースパッケージのインストールなど)はDockerfileの前半に、変更頻度の高い命令(アプリケーションコードのコピーなど)は後半に配置してください。これにより、Dockerのビルドキャッシュを効果的に活用し、ビルドを高速化できます。

セキュリティにとってより重要なのは、初期のレイヤーに機密情報や偶発的な露出が残る可能性があることです。機密ファイルやコマンドが不要になった場合に、最終的なイメージレイヤーに残らないように処理してください。

強化をルーティン化する

本番環境に出荷するDockerfileから始めてください。最終イメージからビルドツールを削除し、非rootユーザーとして実行し、ベースイメージを固定し、すべてのビルドをスキャンしてください。そして、イメージの強化を通常のコードレビューの一部として扱い、一度きりのクリーンアップにしないでください。