B システム設計/インフラ — コンテナサプライチェーンセキュリティ(Workload Identity Federation 静的キー廃止 × 最小権限IAM × イミュータブルダイジェスト固定 × 脆弱性スキャンゲート × Cloud KMS署名アテステーション(Binary Authorization) × cosign keyless署名 × break-glass監査ログ)(MOps campaign-api GitHub Actions → GKE Autopilot Bad→Good)

2026-07-28 (Day 111) 火曜 B: システム設計/インフラ ★★★★☆ GKE Autopilot 1.30+ / Terraform 1.8+ / google provider 5.x Binary Authorization / Sigstore cosign / WIF

概要

🔑

Workload Identity Federation + 最小権限IAM

GitHub ActionsのJSONキー認証(secrets.GCP_SA_KEY)を廃止し、OIDCトークンによる短命な権限借用に置き換える。CI用サービスアカウントも roles/editor から artifactregistry.writer / cloudkms.signerVerifier のみに絞る。

🏷️

イミュータブルダイジェスト固定 + 脆弱性ゲート

:latest 可変タグを廃止し、push直後に取得したダイジェスト(sha256:...)で以降の全工程を固定する。Artifact Registryの脆弱性スキャン結果を確認し、CRITICALがあればビルドを失敗させる。

✍️

Binary Authorization × cosign keyless署名

Cloud KMSの署名鍵でBinary Authorizationアテステーションを作成し、GKEクラスタ側で「署名の無いイメージ」のデプロイを拒否する。加えてcosign keyless署名でSigstore Rekor透明性ログにも記録する。

🚨

break-glass 監査ログ

ENFORCED_BLOCK_AND_AUDIT_LOG で拒否イベントを必ず監査ログに残し、ログベースメトリクス + アラートで緊急バイパスの発生をリアルタイムに検知・通知する。

問題

ECサイト MOps チームでは、キャンペーン管理API(campaign-api)のコンテナイメージを GitHub Actions でビルドし、Artifact Registry へ push した後、GKE Autopilot クラスタへデプロイしている。以下の GitHub Actions ワークフローと Terraform コードには 7つの設計上の問題 が潜んでいる。

問題点を全て洗い出しWorkload Identity Federation(静的キー廃止)× 最小権限IAM × イミュータブルダイジェスト固定 × 脆弱性スキャンゲート × Cloud KMS署名によるアテステーション(Binary Authorization)× cosign keyless署名(Sigstore)× break-glass監査ログ を考慮した Bad→Good リファクタリングを行ってください。

制約・前提条件

  • Terraform 1.8+(google プロバイダー 5.x)、GKE Autopilot 1.30+、リージョン asia-northeast1
  • CI/CD は GitHub Actions。GitHub リポジトリは org/campaign-api の1つに限定してよい
  • コンテナレジストリは Artifact Registry(asia-northeast1-docker.pkg.dev
  • 現状は GitHub Actions が長期間有効なサービスアカウントJSONキー(secrets.GCP_SA_KEY)で認証し、そのサービスアカウントには roles/editor が付与されている
  • イメージは :latest の可変タグのみで push・デプロイされており、Artifact Registry の脆弱性スキャン結果は一切確認されないままデプロイに進む
  • イメージの署名・アテステーションが存在せず、GKE Autopilot クラスタには Binary Authorization ポリシーが設定されていないため、出所不明のイメージでもそのままデプロイできてしまう
期待する回答形式: 問題点の列挙(番号付き)+ 改善後 GitHub Actions ワークフロー + 改善後 Terraform コード + 設計意図の説明

悪いコード (Before)

このコードには 7つの設計上の問題 が隠れています。見つけてみてください。
bad-build-and-deploy.yml — 静的キー・latest可変タグ・スキャン無視・署名なし
name: build-and-deploy
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 問題①: 長期間有効な静的JSONキーで認証(WIF未使用)
      - name: Authenticate to GCP
        run: |
          echo "${{ secrets.GCP_SA_KEY }}" > sa-key.json
          gcloud auth activate-service-account --key-file=sa-key.json

      - name: Build and push
        run: |
          docker build -t $IMAGE:latest .
          # 問題③: latest 可変タグのみ push(ダイジェスト固定なし)
          docker push $IMAGE:latest

      # 問題④: 脆弱性スキャン結果を一切確認せずデプロイに進む
      # 問題⑤: イメージ署名・アテステーションが存在しない
      - name: Deploy to GKE
        run: |
          kubectl set image deployment/campaign-api \
            campaign-api=$IMAGE:latest
bad-iam.tf — roles/editor過剰権限・Binary Authorization未設定
# 問題②: CI/CD用SAにプロジェクト全体の編集権限
resource "google_project_iam_member" "cicd_sa" {
  project = var.project_id
  role    = "roles/editor"
  member  = "serviceAccount:${google_service_account.cicd.email}"
}

# 問題⑥: GKE Autopilot クラスタに Binary Authorization 未設定
resource "google_container_cluster" "campaign" {
  name             = "mops-campaign-cluster"
  location         = "asia-northeast1"
  enable_autopilot = true
  # binary_authorization ブロックなし → 任意のイメージをデプロイ可能
}

# 問題⑦: break-glass(緊急バイパス)発生を検知する仕組みが無い
問題点サマリー(7点)
1静的JSONキー認証 — Workload Identity Federation へ
2roles/editor 過剰権限 — 最小権限(artifactregistry.writer等)へ
3latest 可変タグ — ダイジェスト固定へ
4脆弱性スキャン結果を無視 — CRITICAL遮断ゲートへ
5署名・アテステーションなし — KMS署名 + cosign keylessへ
6Binary Authorization未設定 — クラスタ側で強制へ
7break-glass監査ログなし — 拒否イベント検知アラートへ

ヒント(段階的開示)

ヒント1 — 方向性
問題は3層に分類できる。(1) CI/CD認証・権限 — 静的なサービスアカウントJSONキーの使用・過剰権限(roles/editor)、(2) イメージの検証可能性 — 可変タグ(:latest)によるロールバック・追跡性の欠如、脆弱性スキャン結果を無視したデプロイ、署名・アテステーションの不在、(3) デプロイ時の強制 — GKEクラスタ側にBinary Authorizationポリシーがなく誰でも任意のイメージをデプロイできる状態、緊急時のポリシーバイパス(break-glass)の監査ログ設計の欠如。ソフトウェアサプライチェーンセキュリティは「ビルド時に何を検証したか」と「デプロイ時にそれをどう強制するか」の両輪で設計する必要がある。
ヒント2 — アプローチ
  • 問題①: secrets.GCP_SA_KEY(静的JSONキー)で認証 → Workload Identity Federation(OIDC)でGitHub Actionsのトークンを直接連携し、静的キーを廃止する
  • 問題②: CI用サービスアカウントに roles/editorroles/artifactregistry.writerroles/cloudkms.signerVerifier のみの最小権限に絞る
  • 問題③: :latest の可変タグのみで push・デプロイ → push直後にダイジェスト(sha256:...)を取得し、以降の全工程をダイジェスト参照に統一する
  • 問題④: 脆弱性スキャン結果を確認せずデプロイ → スキャン結果を確認し、CRITICAL重大度があればビルドを失敗させるゲートを挟む
  • 問題⑤: 署名・アテステーションが存在しない → Cloud KMSの署名鍵でBinary Authorizationアテステーションを作成し、cosign keylessでSigstore Rekorにも記録する
  • 問題⑥: GKEクラスタにBinary Authorizationポリシーがない → google_binary_authorization_policy でクラスタ単位に「アテステーションが無いイメージは拒否」を強制する
  • 問題⑦: break-glass運用の監査ログがない → Binary Authorization違反・バイパスをCloud Loggingベースのメトリクス・アラートで検知できるようにする
ヒント3 — コードの骨格
# Binary Authorization ポリシー スケルトン
resource "google_binary_authorization_policy" "policy" {
  default_admission_rule {
    evaluation_mode          = "REQUIRE_ATTESTATION"              # 修正⑥
    enforcement_mode         = "ENFORCED_BLOCK_AND_AUDIT_LOG"     # 修正⑦: 監査ログ必須
    require_attestations_by  = [google_binary_authorization_attestor.build_attestor.name]
  }
}
# GitHub Actions スケルトン
- uses: google-github-actions/auth@v2   # 修正①: WIF
  with:
    workload_identity_provider: ${{ vars.WIF_PROVIDER }}
    service_account: campaign-api-cicd@${{ vars.PROJECT_ID }}.iam.gserviceaccount.com

- run: |
    docker push $IMAGE:${{ github.sha }}
    DIGEST=$(gcloud artifacts docker images describe $IMAGE:${{ github.sha }} \
      --format='get(image_summary.digest)')          # 修正③: ダイジェスト固定

- run: ./scripts/fail_on_critical_cve.sh "$IMAGE@$DIGEST"   # 修正④: 脆弱性ゲート
- run: gcloud beta container binauthz attestations sign-and-create ...  # 修正⑤
- run: cosign sign --yes "$IMAGE@$DIGEST"               # 修正⑤: keyless署名

問題点分析(7点)

#問題点分類改善方法
1静的サービスアカウントJSONキー認証CI認証Workload Identity Federation(OIDC短命トークン)
2CI用SAに roles/editor(過剰権限)権限artifactregistry.writer + cloudkms.signerVerifier
3latest 可変タグのみで push/デプロイ追跡性ダイジェスト(sha256)固定
4脆弱性スキャン結果を無視検証CRITICAL遮断ゲート
5署名・アテステーションが存在しない検証可能性KMS署名アテステーション + cosign keyless
6クラスタにBinary Authorization未設定強制google_binary_authorization_policy
7break-glass監査ログの欠如監査ログベースメトリクス + アラート

サプライチェーン図 — Bad vs Good の変換フロー

Bad(変更前)— 静的キー・latest・無検証デプロイ GitHub Actions(build-and-deploy) 問題①: secrets.GCP_SA_KEY(静的JSONキー)で認証 問題②: CI用SAに roles/editor(プロジェクト全体編集権限) 問題③: docker push $IMAGE:latest(可変タグのみ) 脆弱性スキャン・署名ステップ自体が存在しない Artifact Registry(campaign-api:latest) 問題④: 自動脆弱性スキャン結果を誰も確認しない 問題⑤: 署名・アテステーションが存在しない GKE Autopilot(mops-campaign-cluster) 問題⑥: Binary Authorization未設定 → 任意のイメージをPod作成可能 問題⑦: 誰が何をデプロイしたか監査ログで追跡できない × 静的キー漏洩でプロジェクト全体が危険に × latestタグでは障害時にロールバック先が分からない × CRITICAL脆弱性を含むイメージがそのまま本番稼働 × kubectl権限があれば誰でも未検証イメージに差し替え可能 × 緊急バイパスが発生しても事後追跡できない Good(変更後)— WIF・ダイジェスト固定・二重署名・強制 GitHub Actions(WIF + 最小権限) 修正①: google-github-actions/auth(OIDC短命トークン、静的キー不要) 修正②: artifactregistry.writer + cloudkms.signerVerifier のみ 修正③: push後にダイジェスト取得 → 以降は sha256 固定参照 修正④: 脆弱性スキャン結果を問い合わせ CRITICAL なら exit 1 修正⑤: KMS署名アテステーション作成 + cosign keyless署名(Rekor) Artifact Registry(digest固定 + 署名記録) 脆弱性スキャン結果を通過し、KMS署名 + Rekor記録済み GKE Autopilot(Binary Authorization強制) 修正⑥: REQUIRE_ATTESTATION(アテステーション必須) 修正⑦: ENFORCED_BLOCK_AND_AUDIT_LOG(拒否は必ず監査ログへ) ログベースメトリクス + アラートで break-glass使用を検知 ✓ 静的キー不要、侵害時の被害範囲を権限で限定 ✓ ダイジェスト固定で即座に確実なロールバックが可能 ✓ CRITICAL脆弱性を含むイメージは本番に到達しない ✓ 未署名イメージはクラスタ側で機械的にブロック ✓ 緊急バイパスも監査ログとアラートで即座に検知 修正

模範解答

Before — 静的キー・latest・スキャン無視・署名なし
name: build-and-deploy
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Authenticate to GCP           # 問題①
        run: |
          echo "${{ secrets.GCP_SA_KEY }}" > sa-key.json
          gcloud auth activate-service-account --key-file=sa-key.json

      - name: Build and push
        run: |
          docker build -t $IMAGE:latest .
          docker push $IMAGE:latest          # 問題③

      - name: Deploy to GKE                 # 問題④⑤: スキャン確認・署名なし
        run: |
          kubectl set image deployment/campaign-api \
            campaign-api=$IMAGE:latest
After — WIF + ダイジェスト固定 + 脆弱性ゲート + 二重署名
# .github/workflows/build-and-deploy.yml
name: build-and-deploy

on:
  push:
    branches: [main]

permissions:
  id-token: write   # 修正①: WIFのOIDCトークン発行に必要
  contents: read

env:
  IMAGE: asia-northeast1-docker.pkg.dev/${{ vars.PROJECT_ID }}/mops/campaign-api

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 修正①: Workload Identity Federation(静的キーファイル廃止)
      - id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ vars.WIF_PROVIDER }}
          service_account: campaign-api-cicd@${{ vars.PROJECT_ID }}.iam.gserviceaccount.com

      - uses: google-github-actions/setup-gcloud@v2

      - name: Configure Docker for Artifact Registry
        run: gcloud auth configure-docker asia-northeast1-docker.pkg.dev

      - name: Build and push(コミットSHAタグ + ダイジェスト取得)
        id: build
        run: |
          docker build -t "$IMAGE:${{ github.sha }}" .
          docker push "$IMAGE:${{ github.sha }}"
          # 修正③: 以降は可変タグではなくダイジェストで一意に固定する
          DIGEST=$(gcloud artifacts docker images describe \
            "$IMAGE:${{ github.sha }}" --format='get(image_summary.digest)')
          echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"

      # 修正④: 脆弱性スキャン結果を確認し、CRITICALがあればビルドを失敗させる
      - name: Vulnerability scan gate
        run: |
          CRITICAL_COUNT=$(gcloud artifacts docker images describe \
            "$IMAGE@${{ steps.build.outputs.digest }}" \
            --show-package-vulnerability \
            --format='value(package_vulnerability_summary.severityCount.CRITICAL)')
          if [ "${CRITICAL_COUNT:-0}" -gt 0 ]; then
            echo "::error::CRITICAL 脆弱性が ${CRITICAL_COUNT} 件検出されました。デプロイを中止します。"
            exit 1
          fi

      # 修正⑤: Binary Authorization アテステーション作成(Cloud KMS署名)
      - name: Create Binary Authorization attestation
        run: |
          gcloud beta container binauthz attestations sign-and-create \
            --artifact-url="$IMAGE@${{ steps.build.outputs.digest }}" \
            --attestor="projects/${{ vars.PROJECT_ID }}/attestors/campaign-api-build-attestor" \
            --keyversion="projects/${{ vars.PROJECT_ID }}/locations/asia-northeast1/keyRings/binauthz-keyring/cryptoKeys/campaign-api-attestor-key/cryptoKeyVersions/1"

      # 修正⑤: cosign keyless署名(Sigstore Rekor 透明性ログへの公開記録)
      - name: Sign image with cosign (keyless)
        uses: sigstore/cosign-installer@v3
      - run: cosign sign --yes "$IMAGE@${{ steps.build.outputs.digest }}"

      # 修正③: 可変タグ(:latest)ではなくダイジェストでデプロイ
      - name: Deploy to GKE (digest pinned)
        run: |
          gcloud container clusters get-credentials mops-campaign-cluster \
            --region asia-northeast1 --project "${{ vars.PROJECT_ID }}"
          kubectl set image deployment/campaign-api \
            campaign-api="$IMAGE@${{ steps.build.outputs.digest }}"
Before — roles/editor + Binary Authorization未設定
resource "google_project_iam_member" "cicd_sa" {
  project = var.project_id
  role    = "roles/editor"           # 問題②
  member  = "serviceAccount:${google_service_account.cicd.email}"
}

resource "google_container_cluster" "campaign" {
  name             = "mops-campaign-cluster"
  location         = "asia-northeast1"
  enable_autopilot = true
  # 問題⑥: binary_authorization ブロックなし
}

# 問題⑦: break-glass検知の仕組みが無い
After — WIF + 最小権限 + KMS署名 + Binary Authorization + 監査アラート
# ── Workload Identity Federation for GitHub Actions ──────────
resource "google_iam_workload_identity_pool" "github" {
  project                   = var.project_id
  workload_identity_pool_id = "github-actions-pool"
}

resource "google_iam_workload_identity_pool_provider" "github" {
  project                             = var.project_id
  workload_identity_pool_id           = google_iam_workload_identity_pool.github.workload_identity_pool_id
  workload_identity_pool_provider_id  = "github-provider"

  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.repository" = "assertion.repository"
  }
  attribute_condition = "assertion.repository == 'org/campaign-api'"   # 修正①

  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

resource "google_service_account" "cicd" {
  project      = var.project_id
  account_id   = "campaign-api-cicd"
  display_name = "campaign-api CI/CD (WIF)"
}

# 修正②: roles/editor ではなく最小権限のみ付与
resource "google_project_iam_member" "cicd_artifact_writer" {
  project = var.project_id
  role    = "roles/artifactregistry.writer"
  member  = "serviceAccount:${google_service_account.cicd.email}"
}
resource "google_project_iam_member" "cicd_kms_signer" {
  project = var.project_id
  role    = "roles/cloudkms.signerVerifier"
  member  = "serviceAccount:${google_service_account.cicd.email}"
}
resource "google_service_account_iam_member" "wif_binding" {
  service_account_id = google_service_account.cicd.name
  role                = "roles/iam.workloadIdentityUser"
  member              = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/org/campaign-api"
}

# ── Cloud KMS(Binary Authorization アテステーション署名鍵)──────
resource "google_kms_key_ring" "binauthz" {
  project  = var.project_id
  name     = "binauthz-keyring"
  location = "asia-northeast1"
}
resource "google_kms_crypto_key" "attestor_key" {
  name     = "campaign-api-attestor-key"
  key_ring = google_kms_key_ring.binauthz.id
  purpose  = "ASYMMETRIC_SIGN"
  version_template { algorithm = "EC_SIGN_P256_SHA256" }
}
data "google_kms_crypto_key_version" "attestor_key_version" {
  crypto_key = google_kms_crypto_key.attestor_key.id
}

# ── Binary Authorization Attestor ──────────────────────────────
resource "google_container_analysis_note" "attestor_note" {
  project = var.project_id
  name    = "campaign-api-attestor-note"
  attestation_authority {
    hint { human_readable_name = "campaign-api build attestor" }
  }
}
resource "google_binary_authorization_attestor" "build_attestor" {
  project = var.project_id
  name    = "campaign-api-build-attestor"
  attestation_authority_note {
    note_reference = google_container_analysis_note.attestor_note.name
    public_keys {
      id = data.google_kms_crypto_key_version.attestor_key_version.id
      pkix_public_key {
        public_key_pem      = data.google_kms_crypto_key_version.attestor_key_version.public_key[0].pem
        signature_algorithm = "ECDSA_P256_SHA256"
      }
    }
  }
}

# ── Binary Authorization Policy(修正⑥)───────────────────────
resource "google_binary_authorization_policy" "policy" {
  project                        = var.project_id
  global_policy_evaluation_mode  = "ENABLE"

  default_admission_rule {
    evaluation_mode         = "ALWAYS_DENY"
    enforcement_mode        = "ENFORCED_BLOCK_AND_AUDIT_LOG"
    require_attestations_by = []
  }

  cluster_admission_rules {
    cluster                 = "asia-northeast1.mops-campaign-cluster"
    evaluation_mode         = "REQUIRE_ATTESTATION"
    enforcement_mode        = "ENFORCED_BLOCK_AND_AUDIT_LOG"   # 修正⑦
    require_attestations_by = [google_binary_authorization_attestor.build_attestor.name]
  }
}

# ── GKE Autopilot(クラスタ側でも強制)──────────────────────────
resource "google_container_cluster" "campaign" {
  project          = var.project_id
  name             = "mops-campaign-cluster"
  location         = "asia-northeast1"
  enable_autopilot = true

  binary_authorization {
    evaluation_mode = "PROJECT_SINGLETON_POLICY_ENFORCE"   # 修正⑥
  }
}

# 修正⑦: break-glass(拒否イベント)検知
resource "google_logging_metric" "binauthz_denials" {
  project = var.project_id
  name    = "binauthz-denial-events"
  filter  = "resource.type=\"k8s_cluster\" AND protoPayload.response.message:\"binary authorization\""
  metric_descriptor {
    metric_kind = "DELTA"
    value_type  = "INT64"
  }
}

resource "google_monitoring_alert_policy" "binauthz_denial_alert" {
  project      = var.project_id
  display_name = "Binary Authorization 拒否イベント検知"
  combiner     = "OR"
  conditions {
    display_name = "拒否イベント発生"
    condition_threshold {
      filter          = "metric.type=\"logging.googleapis.com/user/${google_logging_metric.binauthz_denials.name}\""
      comparison      = "COMPARISON_GT"
      threshold_value = 0
      duration        = "0s"
    }
  }
  notification_channels = [var.slack_notification_channel_id]
}
問題修正内容効果
① 静的JSONキーWorkload Identity Federation短命OIDCトークン、キー漏洩・ローテーション運用が不要に
② roles/editorartifactregistry.writer + cloudkms.signerVerifierパイプライン侵害時の被害範囲を限定
③ latest可変タグsha256ダイジェスト固定デプロイ内容の一意性・確実なロールバックを保証
④ スキャン結果無視CRITICAL遮断ゲート既知の重大脆弱性を含むイメージの本番到達を阻止
⑤ 署名・証明なしKMS署名アテステーション + cosign keylessビルドパイプライン経由の証明 + 公開透明性ログ記録
⑥ Binary Authorization未設定REQUIRE_ATTESTATION強制未署名・出所不明イメージのPod作成を機械的に拒否
⑦ break-glass監査ログなしログベースメトリクス + アラート緊急バイパスの発生をリアルタイム検知・通知

ポイント解説

1サプライチェーンセキュリティは「検証」と「強制」の両輪 — 脆弱性スキャン・署名・アテステーションはあくまで「検証した」という記録に過ぎない。GKEクラスタ側にBinary Authorizationという「強制」の仕組みがなければ、検証をすり抜けたデプロイを止められない。CI/CDパイプラインの改善だけで満足せず、デプロイ先クラスタ側の強制設定とセットで設計する必要がある。
2静的キーは「漏洩しないこと」に運用コストを払い続ける負債 — サービスアカウントJSONキーは発行した瞬間から、ローテーション・保管・漏洩監視という継続コストが発生する。Workload Identity Federationのような連合認証は、長期間有効な秘密情報を持たないことでこのコストそのものを消せる。
3可変タグはロールバックを不可能にする:latest のようなタグは同じ名前で中身が変わるため、障害発生時に「1つ前の状態」を指し示す方法がない。ダイジェスト固定は、CI/CDのトレーサビリティだけでなく障害対応の即応性にも直結する。
4アテステーションは「誰が作ったか」ではなく「どの工程を通ったか」の証明 — Binary Authorizationのアテステーションは、開発者個人を証明するものではなく「このイメージが指定のビルドパイプライン(脆弱性ゲート・署名ステップを含む)を通過した」ことを機械的に証明する。人間のレビューを置き換えるものではなく、機械的に検証可能な最低ラインを保証する仕組みとして位置づける。
5break-glassを「例外なく禁止」にすると現場で形骸化する — 障害対応の緊急性は事前に予測できないため、ポリシーを一切の例外なくブロックする設計にすると、現場は別の抜け道(クラスタ設定の一時無効化など、より追跡困難な手段)を使いがちになる。ENFORCED_BLOCK_AND_AUDIT_LOG のように「バイパスは許容するが必ず記録・通知する」設計の方が、実運用の柔軟性と監査可能性を両立できる。

実務への応用

MOpsチームの campaign-api に限らず、GKE Autopilot上で稼働する全てのマイクロサービス(coupon-api, notification-worker 等)に対して、同一のBinary Authorizationアテストー・ポリシーを横展開できる。特に決済・注文データを扱うサービスでは、CRITICAL脆弱性ゲートとアテステーション必須化を最優先で適用し、社内ツールなど影響範囲が小さいサービスから段階的にENFORCED化していくのが現実的なロールアウト順序になる。

Workload Identity Federationの attribute_condition でリポジトリを厳密に限定する設計は、GitHub Organization内の他リポジトリが誤って(あるいは侵害されて)同じサービスアカウントの権限を借用してしまう横展開リスクを防ぐ。複数リポジトリでCI/CDを展開する場合は、リポジトリごとに専用のサービスアカウント・WIFプロバイダー条件を用意し、権限の混在を避けるべきである。

ロールアウト順序: 全サービスに一斉適用するのではなく、まず DRYRUN_AUDIT_LOG_ONLY(拒否せずログのみ記録)で1〜2週間運用し、実際に拒否されうるイメージが無いことを確認してから ENFORCED_BLOCK_AND_AUDIT_LOG に切り替えるのが安全。

今日のまとめ

コンテナサプライチェーンセキュリティの7点チェックリスト: ① Workload Identity Federation(静的キー廃止)② 最小権限IAM(roles/editor排除)③ イミュータブルダイジェスト固定(:latest排除)④ 脆弱性スキャンゲート(CRITICAL遮断)⑤ Cloud KMS署名アテステーション + cosign keyless署名(二重の検証可能性)⑥ Binary Authorizationポリシーによるクラスタ側強制 ⑦ break-glass監査ログ(バイパスの可視化)。 署名・スキャンは「検証した」記録に過ぎず、デプロイ先クラスタ側の強制設定と組み合わせて初めてサプライチェーン全体の安全性として機能する。

次のステップ

  • 発展問題: 現在は単一クラスタ(mops-campaign-cluster)のみを対象にしているBinary Authorizationポリシーを、複数クラスタ(本番・ステージング)で異なる強制レベル(本番はENFORCED_BLOCK_AND_AUDIT_LOG、ステージングはDRYRUN_AUDIT_LOG_ONLY)に分ける設計を検討せよ。また、SLSA(Supply-chain Levels for Software Artifacts)フレームワークのレベル定義と照らし合わせ、今回の実装がどのレベルに相当するかを評価せよ
  • 参考: Binary Authorization 公式ドキュメント(アテストー・ポリシー設計)/ Workload Identity Federation for GitHub Actions(google-github-actions/auth)/ Sigstore cosign keyless signing / Artifact Registry 脆弱性スキャン / SLSA フレームワーク

自己評価(あとで記入)