Skip to content
Published on

コンテナイメージセキュリティとソフトウェアサプライチェーン保護:Trivy、Cosign、SBOM、Sigstore実践ガイド

Authors
  • Name
    Twitter
Container Image Security

はじめに

2024年のXZ Utilsバックドア事件(CVE-2024-3094)は、ソフトウェアサプライチェーン攻撃がいかに巧妙で危険であるかを世界中に示しました。 コンテナベースのデプロイが標準となった現在、1つのイメージに数百もの依存関係が含まれており、そのうち1つでも改ざんされれば本番環境全体が危険にさらされます。

この記事では、コンテナイメージセキュリティのライフサイクル全体を解説します。 脆弱性スキャン(Trivy)、イメージ署名(Cosign/Sigstore)、ソフトウェア構成表(SBOM)、そしてSLSAフレームワークを活用したサプライチェーンの完全性検証まで、 本番環境ですぐに適用できる実践的なコードとともに説明します。

コンテナイメージの脅威モデル

コンテナイメージを取り巻く攻撃ベクトルは、大きく5つに分類できます。

脅威タイプ説明対策
既知の脆弱性(CVE)ベースイメージやパッケージに含まれる公開脆弱性Trivy、Grypeスキャン
イメージ改ざん(Tampering)レジストリ内でイメージが置換または変更されるCosign署名 + 検証
依存関係の混乱(Dependency Confusion)悪意のあるパッケージが内部パッケージ名で登録されるSBOMベースの依存関係追跡
ビルド環境の汚染CI/CDパイプライン自体が侵害されるSLSAビルド来歴証明
権限昇格(Privilege Escalation)コンテナ内部からホストへの脱出最小権限の原則 + ランタイムセキュリティ

各脅威に対して単一のツールでは不十分であり、多層防御(Defense in Depth) 戦略が必要です。

Trivy脆弱性スキャン

Trivyの紹介とインストール

Trivyは、Aqua Securityが開発したオープンソースセキュリティスキャナーで、コンテナイメージ、ファイルシステム、Gitリポジトリ、Kubernetesクラスターをスキャンできます。 2026年現在、v0.68以上のバージョンでは読み取り専用データベースモードをサポートし、複数のプロセスが同時にスキャンを実行できます。

# Trivyインストール(macOS)
brew install trivy

# Trivyインストール(Linux)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# バージョン確認
trivy version

イメージスキャンの実行

# 基本的なイメージスキャン - すべての重大度レベルを表示
trivy image nginx:1.25

# HIGHとCRITICALのみフィルタリング、修正可能な脆弱性のみ表示
trivy image --severity HIGH,CRITICAL --ignore-unfixed nginx:1.25

# JSON形式で結果を出力(CI/CDパイプライン連携用)
trivy image --format json --output results.json nginx:1.25

# 特定のCVEをスキップするための.trivyignoreファイルを使用
trivy image --ignorefile .trivyignore myapp:latest

# SBOMベースのスキャン(既存のSBOMを入力として使用)
trivy sbom ./sbom.cyclonedx.json

.trivyignoreファイルの構成

本番環境では、誤検知(false positive)やすぐに修正できない脆弱性を管理するために.trivyignoreファイルを活用します。

# .trivyignore - 承認された例外リスト
# 有効期限と理由を必ず記録すること

# CVE-2024-1234: 該当機能未使用、2026-04-01まで猶予
CVE-2024-1234

# CVE-2024-5678: アップストリームパッチ待ち
CVE-2024-5678

脆弱性スキャナーの比較

機能TrivyGrypeSnykClair
ライセンスApache 2.0Apache 2.0商用(無料プランあり)Apache 2.0
コンテナイメージスキャンOOOO
ファイルシステムスキャンOOOX
IaCスキャンOXOX
SBOM生成OX(Syft連携)OX
シークレット検出OXOX
KubernetesスキャンOXOX
CI/CD統合の容易さ非常に高い
オフラインサポートOOXO

Trivyは単一ツールで最も広い範囲をカバーし、オフライン環境でも動作することが強みです。

Cosign/Sigstoreイメージ署名

Sigstoreアーキテクチャ

Sigstoreはソフトウェア署名のためのオープンソースプロジェクトで、3つの主要コンポーネントで構成されています。

  • Cosign:コンテナイメージとOCIアーティファクトの署名・検証ツール
  • Fulcio:OIDCベースの短期証明書発行CA(認証局)
  • Rekor:署名記録を保存する不変(Immutable)な透明性ログ

キーレス(Keyless)署名方式では、開発者は別途鍵を管理する必要がなく、GitHub/GoogleなどのOIDC資格情報で署名できます。

Cosignを使ったイメージ署名

# Cosignインストール
brew install cosign

# 1. キーペア生成(従来の方式)
cosign generate-key-pair

# 2. イメージ署名(キーベース)
cosign sign --key cosign.key myregistry.io/myapp:v1.0.0

# 3. キーレス署名(Sigstore Fulcio使用 - 推奨)
# OIDC認証後に自動的に短期証明書を発行
cosign sign myregistry.io/myapp:v1.0.0

# 4. 署名検証
cosign verify --key cosign.pub myregistry.io/myapp:v1.0.0

# 5. キーレス署名検証(証明書発行者とIDを指定)
cosign verify \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp "https://github.com/myorg/myrepo" \
  myregistry.io/myapp:v1.0.0

署名へのメタデータ付与

Cosignは署名時にカスタムアノテーションを追加でき、ビルド来歴の追跡に有用です。

# ビルドメタデータを署名に含める
cosign sign \
  -a "git.sha=$(git rev-parse HEAD)" \
  -a "build.timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  -a "build.pipeline=github-actions" \
  myregistry.io/myapp:v1.0.0

# 署名とアノテーションの検証
cosign verify --key cosign.pub myregistry.io/myapp:v1.0.0 | jq .

SBOM生成と管理

SBOMとは

SBOM(Software Bill of Materials)は、ソフトウェアに含まれるすべてのコンポーネント、ライブラリ、依存関係のリストを構造化した文書です。 米国大統領令14028号(EO 14028)以降、連邦政府に納入するソフトウェアにはSBOMの提出が義務化されており、グローバルな規制の流れもSBOMを標準として求めています。

SBOMフォーマット比較

項目SPDXCycloneDX
主管機関Linux FoundationOWASP
ISO標準ISO/IEC 5962:2021ECMA-424
主な用途ライセンスコンプライアンスセキュリティ脆弱性管理
サポート形式JSON, RDF, YAML, Tag-ValueJSON, XML, Protocol Buffers
VEXサポートOO
サービス依存関係の表現限定的O
ツールエコシステム広い急速に成長中

ライセンスコンプライアンスが重要であればSPDX、セキュリティ脆弱性管理が優先であればCycloneDXを選択してください。 両方のフォーマットを生成するのが最も理想的です。

Syftを使ったSBOM生成

# Syftインストール
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

# CycloneDXフォーマットでSBOM生成(コンテナイメージ)
syft myregistry.io/myapp:v1.0.0 -o cyclonedx-json=sbom-cyclonedx.json

# SPDXフォーマットでSBOM生成
syft myregistry.io/myapp:v1.0.0 -o spdx-json=sbom-spdx.json

# ローカルディレクトリからSBOM生成
syft dir:./my-project -o cyclonedx-json=sbom.json

# TrivyでもSBOM生成が可能
trivy image --format cyclonedx --output sbom-trivy.json myregistry.io/myapp:v1.0.0

# SBOMをOCIレジストリに添付(Cosign活用)
cosign attach sbom --sbom sbom-cyclonedx.json myregistry.io/myapp:v1.0.0

SBOMドリフト検出

SBOMはビルド時点のスナップショットであるため、ランタイムにインストールされたパッケージと異なる場合があります。これをSBOMドリフトと呼びます。

# ランタイムSBOMとビルド時点SBOMの比較
# 1. ビルド時点SBOM(すでに生成済み)
# sbom-build.json

# 2. 実行中のコンテナから現在の状態を抽出
docker exec running-container syft / -o cyclonedx-json > sbom-runtime.json

# 3. 差分を比較
diff <(jq -r '.components[].name' sbom-build.json | sort) \
     <(jq -r '.components[].name' sbom-runtime.json | sort)

CI/CDパイプライン統合

GitHub Actions統合パイプライン

以下は、ビルド、スキャン、署名、SBOM生成を1つのパイプラインに統合した例です。

name: Container Security Pipeline

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

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: my-org/my-app

jobs:
  build-scan-sign:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write # キーレス署名に必要

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and Push
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/my-org/my-app:${{ github.sha }}

      # Step 1: 脆弱性スキャン
      - name: Trivy Vulnerability Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/my-org/my-app:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: HIGH,CRITICAL
          exit-code: '1'

      - name: Upload Trivy Results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

      # Step 2: SBOM生成
      - name: Generate SBOM with Syft
        uses: anchore/sbom-action@v0
        with:
          image: ghcr.io/my-org/my-app:${{ github.sha }}
          format: cyclonedx-json
          output-file: sbom.cyclonedx.json

      # Step 3: Cosignでイメージ署名(キーレス)
      - name: Install Cosign
        uses: sigstore/cosign-installer@v3

      - name: Sign Container Image
        run: |
          cosign sign --yes \
            -a "git.sha=${{ github.sha }}" \
            -a "build.pipeline=github-actions" \
            ghcr.io/my-org/my-app@${{ steps.build.outputs.digest }}

      # Step 4: SBOM添付
      - name: Attach SBOM to Image
        run: |
          cosign attach sbom \
            --sbom sbom.cyclonedx.json \
            ghcr.io/my-org/my-app@${{ steps.build.outputs.digest }}

      # Step 5: SBOMアテステーション署名
      - name: Sign SBOM Attestation
        run: |
          cosign attest --yes \
            --predicate sbom.cyclonedx.json \
            --type cyclonedx \
            ghcr.io/my-org/my-app@${{ steps.build.outputs.digest }}

このパイプラインは以下の順序で動作します。

  1. DockerイメージをビルドしてGHCRにプッシュ
  2. TrivyでHIGH/CRITICALの脆弱性をスキャンし、検出時にパイプラインを停止
  3. SyftでSBOMを生成
  4. Cosignキーレス署名でイメージに署名
  5. SBOMをイメージに添付して署名

Kubernetes Admissionポリシー

Kyvernoによるイメージ署名検証

Kubernetesクラスターで署名されていないイメージのデプロイをブロックするには、Admission Controllerを活用します。 Kyvernoはポリシーベースのkubernetesネイティブツールで、イメージ署名検証を宣言的に定義できます。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-cosign-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - 'ghcr.io/my-org/*'
          attestors:
            - entries:
                - keyless:
                    issuer: 'https://token.actions.githubusercontent.com'
                    subject: 'https://github.com/my-org/my-repo/.github/workflows/*'
                    rekor:
                      url: 'https://rekor.sigstore.dev'
          attestations:
            - type: 'https://cyclonedx.org/bom'
              conditions:
                - all:
                    - key: 'components'
                      operator: NotEquals
                      value: ''

OPA Gatekeeperによるポリシー

OPA Gatekeeperを使用する環境では、ConstraintTemplateを定義します。

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequireimagedigest
spec:
  crd:
    spec:
      names:
        kind: K8sRequireImageDigest
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequireimagedigest

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not contains(container.image, "@sha256:")
          msg := sprintf(
            "Container '%v' must use image digest (sha256) instead of tag: %v",
            [container.name, container.image]
          )
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.initContainers[_]
          not contains(container.image, "@sha256:")
          msg := sprintf(
            "Init container '%v' must use image digest (sha256) instead of tag: %v",
            [container.name, container.image]
          )
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireImageDigest
metadata:
  name: require-image-digest
spec:
  match:
    kinds:
      - apiGroups: ['']
        kinds: ['Pod']
    namespaces:
      - production

障害事例と復旧

事例1:署名なしイメージが本番環境にデプロイされた

状況:Admission Controllerが設定されていないNamespaceに、署名のないイメージがデプロイされました。

原因:Namespace単位のポリシー例外(exemption)が広すぎる範囲で設定されていました。

復旧手順

  1. 該当Podを直ちに隔離(NetworkPolicyで外部通信を遮断)
  2. イメージをTrivyで緊急スキャン
  3. 同じイメージをCosignで事後署名するか、署名済みイメージに置き換え
  4. Admission Controllerポリシーの例外範囲を縮小
# 緊急隔離:NetworkPolicyを適用
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: emergency-isolate
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: compromised-app
  policyTypes:
    - Ingress
    - Egress
EOF

# 緊急スキャン
trivy image --severity CRITICAL compromised-image:tag

事例2:CRITICALなCVEが本番環境で発見された

状況:すでに稼働中のサービスのベースイメージにCRITICAL脆弱性が公表されました。

復旧手順

  1. 影響範囲の分析:SBOMを活用して該当パッケージを使用するすべてのイメージを特定
  2. 緊急パッチイメージのビルドとデプロイ
  3. ロールアウトの進行状況を監視
# SBOMから影響を受けるイメージを検索
for sbom in sbom-*.json; do
  if jq -e '.components[] | select(.name == "libexpat")' "$sbom" > /dev/null 2>&1; then
    echo "AFFECTED: $sbom"
  fi
done

# パッチ済みベースイメージで再ビルド
docker build --no-cache --build-arg BASE_IMAGE=nginx:1.25.4-alpine -t myapp:patched .

# ローリングアップデート
kubectl set image deployment/myapp myapp=myregistry.io/myapp:patched
kubectl rollout status deployment/myapp

事例3:SBOMドリフトが発生した

状況:ランタイムコンテナに、ビルド時点のSBOMに存在しないパッケージがインストールされていました。

原因:コンテナ内部でapt-get installを実行する初期化スクリプトが存在していました。

対応

  1. 不変(Immutable)コンテナの原則を強制するためreadOnlyRootFilesystemを設定
  2. ランタイムSBOM比較を定期的に実行するCronJobをデプロイ
  3. ドリフト発生時の自動アラートを構成

SLSAフレームワークとビルド来歴証明

SLSA(Supply-chain Levels for Software Artifacts)は、ソフトウェアサプライチェーンの完全性を保証するためのフレームワークです。 レベル0から3まで段階的にセキュリティ水準を高めることができます。

レベル要件説明
SLSA 0なしセキュリティ保証なし
SLSA 1ビルド来歴(Provenance)が存在ビルドプロセスが文書化されている
SLSA 2ホスティングされたビルドサービスの使用ビルドサービスが署名された来歴を生成
SLSA 3強化されたビルド環境改ざん防止が保証されたビルド環境の使用

GitHub ActionsでSLSA Level 3のビルド来歴証明を実装するには、slsa-framework/slsa-github-generatorを活用します。

# SLSA Provenance生成ワークフロー
name: SLSA Build Provenance

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@v4
      - name: Build Image
        id: build
        run: |
          docker build -t ghcr.io/my-org/my-app:${{ github.ref_name }} .
          DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/my-org/my-app:${{ github.ref_name }} | cut -d@ -f2)
          echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"

  provenance:
    needs: build
    permissions:
      actions: read
      id-token: write
      packages: write
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
    with:
      image: ghcr.io/my-org/my-app
      digest: ${{ needs.build.outputs.digest }}
    secrets:
      registry-username: ${{ github.actor }}
      registry-password: ${{ secrets.GITHUB_TOKEN }}

運用チェックリスト

本番環境でコンテナイメージセキュリティを運用する際に、以下のチェックリストを活用してください。

ビルドフェーズ

  • すべてのイメージが最小ベースイメージ(distroless、alpine)を使用しているか
  • Dockerfileで固定バージョン(タグ + digest)を使用しているか
  • マルチステージビルドでビルドツールが最終イメージに含まれていないか
  • シークレットがイメージレイヤーに含まれていないか

スキャンフェーズ

  • CIパイプラインでTrivyスキャンが必須で実行されているか
  • CRITICAL脆弱性検出時にパイプラインが停止するか
  • スキャン結果が一元化されたダッシュボードに収集されているか
  • .trivyignoreの各項目に有効期限と理由が記録されているか

署名フェーズ

  • すべての本番イメージにCosign署名が適用されているか
  • キーレス署名を使用して鍵管理の負担を排除しているか
  • Rekor透明性ログに署名が記録されているか

SBOMフェーズ

  • すべてのイメージにSBOMが生成・添付されているか
  • SBOMが署名されて改ざん防止が保証されているか
  • SBOMドリフト検出が定期的に実行されているか

デプロイフェーズ

  • Kubernetes Admission Controllerが署名検証を強制しているか
  • イメージタグの代わりにdigestを使用しているか
  • Namespace単位のポリシー例外が最小化されているか

まとめ

コンテナイメージセキュリティは、単一ツールではなく多層化された戦略で取り組む必要があります。 Trivyで既知の脆弱性を検出し、Cosign/Sigstoreでイメージの完全性を保証し、SBOMで構成要素を追跡し、SLSAでビルド来歴を証明することが、現代のソフトウェアサプライチェーンセキュリティの標準です。

最も重要なのは、これらすべてのプロセスを自動化することです。 CI/CDパイプラインにセキュリティゲートを統合し、Kubernetes Admission Controllerでポリシーを強制し、SBOMドリフトを継続的に監視してください。 セキュリティは一度の設定ではなく、継続的なプロセスです。

参考資料