Chuyển đến nội dung chính

コンテナイメージのハードニング:distroless、マルチステージ、Cosignによる署名

Duy Tran9分
コンテナイメージのハードニング:distroless、マルチステージ、Cosignによる署名
ランタイムイメージの余分な1バイトは、攻撃面の1バイトです。理想的な本番イメージはアプリケーションのバイナリと数個の共有ライブラリのみを含み、シェルもパッケージマネージャーも持たず——そしてクラスタが信頼できるよう署名されているべきです。

マルチステージビルドとdistrolessベースイメージ

Goアプリケーション向けの参考パターン:

FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/server

FROM gcr.io/distroless/static-debian12:nonroot COPY --from=build /out/app /app USER nonroot:nonroot ENTRYPOINT ["/app"]

メリット:

  • ランタイムイメージが20MB未満で、sh、apt、curlを含まない。
  • 攻撃者がRCEに到達しても、権限昇格やペイロードのダウンロードに使えるツールがない。
  • OSレベルのCVE件数がほぼゼロになり、スキャンノイズが減る。

Node.js/Pythonの場合:Chainguard Images、distroless/nodejs、distroless/python3を使用。:latestは避け、必ずダイジェスト@sha256:...でピン留めしましょう。

非root、読み取り専用ファイルシステム、capabilityのドロップ

Kubernetesでは:

securityContext:
  runAsNonRoot: true
  runAsUser: 65532
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]
  seccompProfile:
    type: RuntimeDefault

これはPod Security Standards: restrictedプロファイルのベースラインでもあります。アプリがファイル書き込みを必要とする場合(キャッシュ、tmp)、ルートFSを開放するのではなく、その特定パスにemptyDirをマウントしましょう。

Trivy/Grypeによるイメージスキャン

- name: Build
  run: docker build -t ghcr.io/org/app:${{ github.sha }} .
  • name: Scan image uses: aquasecurity/trivy-action@master with: image-ref: ghcr.io/org/app:${{ github.sha }} severity: CRITICAL,HIGH exit-code: 1 ignore-unfixed: true

ignore-unfixed: trueは、修正がないCVEでビルドが失敗するのを防ぎますが、追跡のためレポートには記録されます。

Cosignキーレス:秘密鍵を管理せずに署名する

Cosignキーレスは、OIDCアイデンティティ(GitHub Actions、Googleなど)とFulcioが発行する短命証明書に依存します。ワークフロー:

permissions:
  id-token: write   # OIDC用
  contents: read
  packages: write
  • uses: sigstore/cosign-installer@v3

  • name: Sign image env: COSIGN_EXPERIMENTAL: "true" run: cosign sign --yes ghcr.io/org/app@${{ steps.push.outputs.digest }}

  • name: Attach SBOM as attestation run: | syft ghcr.io/org/app@${{ steps.push.outputs.digest }} -o cyclonedx-json > sbom.json cosign attest --yes --predicate sbom.json --type cyclonedx
    ghcr.io/org/app@${{ steps.push.outputs.digest }}

検証ログはRekor透明性ログに永続的に保存されます。保管・ローテーションが必要な秘密鍵はありません。

Kyvernoによるデプロイ前検証

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-cosign-signature
    match:
      any:
      - resources:
          kinds: ["Pod"]
    verifyImages:
    - imageReferences: ["ghcr.io/org/*"]
      attestors:
      - entries:
        - keyless:
            subject: "https://github.com/org/repo/.github/workflows/build.yml@refs/heads/main"
            issuer: "https://token.actions.githubusercontent.com"

Podは、正しいリポジトリ + ワークフロー + ブランチからの署名を持つイメージである場合のみ作成されます。これはサプライチェーンセキュリティ(SLSA L2-L3)の重要なピースです。

本番イメージのチェックリスト

  • マルチステージ、distroless/Chainguardベースイメージ、ダイジェストピン留め。
  • USERは非root、ALL capabilityをドロップ、readOnlyRootFilesystem。
  • ランタイムステージにcurl、wget、bashをインストールしない。
  • Trivy/Grypeでスキャンし、修正があるCRITICAL/HIGHではビルドを失敗させる。
  • Cosignキーレスで署名し、CycloneDX SBOMを添付。
  • クラスタにKyverno verifyImagesを設定し、署名なしイメージをブロック。
  • HEALTHCHECKとEXPOSEを明示し、未使用ポートを公開しない。

結論

イメージのハードニングは、DevSecOpsで最もROIが高い投資の1つです:労力は少なく、攻撃面とアラート疲労の両方を削減します。distroless + 非root + Cosignキーレスのパターンに慣れれば、新サービスがほぼ無償で良いベースラインを得られます——そして、コンプライアンス違反のPodをクラスタ全体でブロックするアドミッションポリシーを自信を持って有効化できます。