05 / Learn — Container Security

コンテナイメージとレジストリのセキュリティ

コンテナのセキュリティ問題の多くは、実行環境ではなく「ビルド時」に作り込まれます。このページでは、ベースイメージの選定、Trivyなどによる脆弱性スキャン、Dockerfileのハードニング、ECR/ACR/Artifact Registryといったレジストリのアクセス制御、そしてSBOMを中心としたソフトウェアサプライチェーンの基本までを扱います。Kubernetesなどのオーケストレーション層のセキュリティは別ページで扱うため、ここではビルド時とレジストリ層に焦点を絞ります。

Why It Matters

なぜイメージ層のセキュリティが最優先事項なのか

コンテナイメージは、依存パッケージ、OSライブラリ、アプリケーションコード、場合によっては設定情報や認証情報までも「レイヤー」として積み重ねたアーカイブです。一度ビルドされたイメージはレジストリを経由して複数の環境にそのままデプロイされるため、ビルド時に混入した脆弱性や秘密情報は、実行環境の数だけ複製されて拡散します。ランタイムでの防御だけに頼るのではなく、ビルド時点で問題を止めることがコンテナセキュリティの土台になります。

イメージレイヤーは「削除しても残る」

Dockerfileで後からrmunsetしても、その情報は以前のレイヤーに残ったままです。一度でもレイヤーに書き込まれた秘密情報は、docker historyやイメージの取得によって復元可能です。

Architecture

典型的なコンテナビルドパイプライン

安全なコンテナ供給パイプラインでは、開発者がビルドしたイメージをレジストリへpushする前に、脆弱性スキャンのゲートを必ず通過させます。このゲートで基準を満たさないイメージは、レジストリへの登録やデプロイをブロックされるべきです。

「Image Scan」の段階がこのパイプラインのSecurity Gateです。ここでCritical/High脆弱性や埋め込まれた秘密情報を検出してビルドを失敗させなければ、脆弱なイメージがそのままレジストリに登録され、本番環境にデプロイされてしまいます。

Key Concepts

主要概念とAWS/Azure/GCPの対応

AWS ECR、Azure ACR、GCP Artifact Registryの主要概念の対応表
概念 AWS Azure Google Cloud
サービス Elastic Container Registry (ECR) Azure Container Registry (ACR) Artifact Registry
イメージスキャン連携 ECR Basic/Enhanced Scanning (Amazon Inspector連携) ACR Tasks + Microsoft Defender for Containers Artifact Analysis (自動脆弱性スキャン)
アクセス制御モデル Repository Policy (IAM/リソースベース) Azure RBAC (AcrPull/AcrPush) Cloud IAM (リポジトリ単位のロール)
公開/非公開設定 プライベートが既定、Public Galleryは明示的に作成 パブリックアクセスは既定で無効、明示的に有効化が必要 プライベートが既定、allUsers付与で公開化
署名/証明 Notation + AWS Signer ACR Content Trust (Notary v2) Binary Authorization + Artifact Attestation
Image Scanning Dockerfile Hardening Non-root User SBOM Registry IAM Image Signing
Common Misconfigurations

よくある設定ミス

  • 匿名pull可能な公開レジストリ High Risk

    検証目的で一時的に公開設定にしたリポジトリが、認証なしで誰でもイメージをpullできる状態のまま放置される。

  • rootユーザーでのコンテナ実行 Medium Risk

    DockerfileにUSER命令がなく、コンテナがrootとして実行されるため、コンテナブレイクアウト時の被害が拡大する。

  • イメージレイヤーへの秘密情報の焼き込み High Risk

    ENVARGにAPIキーやパスワードを直接記述し、レイヤーとしてイメージに永続化してしまう。

  • latestタグの使用とイミュータビリティ未設定 Medium Risk

    常にlatestタグを参照するデプロイ設定になっており、どのビルドが実際に動いているかを追跡できない。タグの上書きも防止されていない。

  • 未スキャンイメージの本番デプロイ High Risk

    CI/CDパイプラインに脆弱性スキャンのゲートが組み込まれておらず、既知の重大な脆弱性を含むイメージがそのまま本番稼働する。

Attack Scenario

攻撃シナリオ: 公開レジストリからの秘密情報抽出

攻撃者が誤って公開設定になっていたコンテナレジストリを発見した、という想定です。

  1. サブドメイン探索やコード検索サービスから、社内向けと思われるレジストリのホスト名を特定する。
  2. 認証情報なしでイメージのpullを試み、匿名アクセスが可能かを確認する。
  3. 取得したイメージのレイヤー履歴を調査し、環境変数やビルド引数に埋め込まれた秘密情報を探す。
  4. 発見した認証情報を使って、対象システムやクラウドアカウントへの侵入を試みる。
bash
# 1. 認証情報なしでイメージをpull
$ docker pull registry.example.com/internal-api:latest

# 2. レイヤー履歴から埋め込まれた秘密情報を探索
$ docker history --no-trunc registry.example.com/internal-api:latest | grep -i "API_KEY"

# 3. 該当レイヤーを展開して環境変数の値を直接確認
$ docker save registry.example.com/internal-api:latest -o image.tar
$ tar -xf image.tar && grep -r "API_KEY" .
Detection

検知

イメージの脆弱性・秘密情報スキャンと、レジストリへの匿名アクセスの両方を継続的に監視します。

  • CI/CDパイプラインでtrivy imageを実行し、Critical/High脆弱性や検出された秘密情報をゲート判定に使う。
  • レジストリのアクセスログを監査し、認証されていないpullリクエストや想定外のIP/アカウントからのアクセスを確認する。
  • ECR Enhanced Scanning / Defender for Containers / Artifact Analysisのファインディングを定期的にレビューする。
trivy image (example output)
$ trivy image registry.example.com/internal-api:latest

registry.example.com/internal-api:latest (alpine 3.18.4)
==========================================================
Total: 7 (CRITICAL: 2, HIGH: 3, MEDIUM: 2)

openssl 3.1.1-r0 -> CVE-2023-5678 (CRITICAL): Buffer overflow in X.509 parsing

Secrets
==========================================================
internal-api:latest (secrets)
==========================================================
Total: 1 (HIGH: 1)

AWS Access Key ID   HIGH   Dockerfile:ENV API_KEY=AKIA...
Remediation

是正

レジストリの匿名pullアクセスを遮断し、Dockerfileをハードニングして秘密情報をレイヤーから排除します。

bash
# 匿名(allUsers)へのpull権限を削除
$ gcloud artifacts repositories remove-iam-policy-binding internal-api \
    --location=asia-northeast1 \
    --member="allUsers" \
    --role="roles/artifactregistry.reader"

# 漏洩した認証情報を即座にローテーション
$ aws iam update-access-key --access-key-id AKIA... --status Inactive

Dockerfile側では、マルチステージビルドで秘密情報をビルドステージに閉じ込め、非rootユーザーで実行するように修正します。

Dockerfile (fixed)
# --- build stage ---
FROM golang:1.22-alpine AS build
WORKDIR /src
COPY . .
# ビルド時の秘密情報はイメージに残らないbuild secretとして渡す
RUN --mount=type=secret,id=api_key \
    API_KEY=$(cat /run/secrets/api_key) go build -o /out/app .

# --- final stage ---
FROM gcr.io/distroless/static-debian12
COPY --from=build /out/app /app
USER 65532:65532
ENTRYPOINT ["/app"]
Hands-on Lab

実際に手を動かす

このページで扱った「公開状態のコンテナレジストリ」からの秘密情報漏洩を、実際のシナリオ形式で体験できます。Scenario → Architecture → CLI Commands → Misconfiguration → Detection → Remediation → Verification の流れで進めます。

Lab: Public Container Registry を始める →
Checklist

実装チェックリスト