Binary Authorizationを利用してCloud BuildでビルドしたイメージだけをCloud Runへデプロイ許可する仕組みを作ってみた

Binary Authorizationを利用してCloud BuildでビルドしたイメージだけをCloud Runへデプロイ許可する仕組みを作ってみた

2026.05.12

はじめに

こんにちは。
クラウド事業本部コンサルティング部の渡邉です。

コンテナイメージのサプライチェーンセキュリティへの関心が高まる中、Google CloudではBinary Authorizationというサービスを使用することでデプロイ時にコンテナイメージを検証する仕組みが提供されています。さらに、Cloud Buildと組み合わせることで「Cloud Buildでビルドされたイメージのみのデプロイを許可する」というポリシーを自動的に適用できます。

今回は、Binary AuthorizationとCloud Build・Cloud Runを組み合わせたソフトウェアサプライチェーンセキュリティの実装方法と、実際にセットアップするまでの流れを見ていきたいと思います。

Binary Authorizationとは

Binary Authorizationは、コンテナベースのアプリケーションをデプロイする際に、デプロイ時のセキュリティポリシーを強制するGoogle Cloudのサービスです。信頼された権限者がビルド・署名したコンテナイメージのみが本番環境にデプロイされることを保証します。

なぜBinary Authorizationが必要か

コンテナベースのアーキテクチャでは、ビルド・テスト・ステージング・本番という複数のステージを経てイメージがデプロイされます。各ステージには固有の要件があり、あるステージを通過したことが次のステージへの前提条件となります。

Binary Authorizationは、このような ソフトウェアサプライチェーン において以下のリスクを低減します。

  • 脆弱なイメージや未テストのイメージの本番デプロイ
  • 意図しないソース(攻撃者が書き換えたイメージなど)からのデプロイ
  • 内部不正による未承認イメージのデプロイ

ポリシーを定義することで「このイメージはCloud Buildによってビルドされている」「脆弱性スキャンを通過している」といった条件をデプロイ時に強制できます。

サポートプラットフォーム

Binary Authorizationは以下のプラットフォームをサポートしています。

プラットフォーム 概要
Cloud Run フルマネージドサーバーレスプラットフォーム
GKE Google Kubernetes Engine クラスター
Cloud Service Mesh マネージドサービスメッシュ
Google Distributed Cloud オンプレミス・他クラウド上のGKEクラスター

動作モード

Binary Authorizationには2つの動作モードがあります。

  • Enforce(強制): ポリシーに準拠していないイメージのデプロイをブロックし、監査ログに記録する
  • Monitor(監視): ドライランモードでポリシー検証を実行し、ブロックせずにポリシー違反を監査ログに記録する。本番適用前の検証に使用する

関連サービス

Binary Authorizationは単体ではなく、以下のGoogle Cloudサービスと連携して動作します。

サービス 役割
Artifact Registry ビルドされたコンテナイメージの保存
Cloud Build イメージのビルドとアテステーションの自動作成
Artifact Analysis アテステーションのメタデータ保存・脆弱性情報の提供
Cloud KMS カスタムアテスターで使用する暗号鍵の管理
Cloud Deploy デプロイパイプラインの自動化とBinary Authorizationとの統合

主要な概念

Binary Authorizationを理解するうえで、以下の概念が重要です。

ポリシー

ポリシーはコンテナイメージのデプロイを制御するルールセットです。1つのGoogle Cloudプロジェクトに1つのポリシーが存在します。ポリシーには以下の評価モードを設定できます。

評価モード 説明
Allow all images すべてのイメージのデプロイを許可
Disallow all images すべてのイメージのデプロイを拒否
Require attestations 指定されたアテスターによる署名済みアテステーションを要求

施行モードにはBlock and Audit Log(ブロック+監査ログ)とDry Run: Audit Log Only(監査ログのみ・ブロックなし)があります。本番導入前にDry Runモードでテストするのが推奨です。

Cloud RunでBinary Authorizationを有効にしたが適切なポリシーが設定されていない場合、開発者によって無効化される可能性があります。これを防ぐには、組織ポリシー(run.allowedBinaryAuthorizationPolicies)で強制的に有効化することが推奨されています。

なお、Cloud Runのポリシーではデフォルトルールのみサポートしています。GKEのようなクラスター別・サービスアカウント別のルール設定はできません。

アテステーション(Attestation)

アテステーションは、コンテナイメージが特定のプロセス(ビルド・テスト・脆弱性スキャンなど)を通過したことを証明するデジタル文書です。イメージのダイジェストを秘密鍵で署名し、その署名をアテステーションとして保存します。デプロイ時には、アテステーションの内容を再検証するだけで、元のプロセスを再実行する必要がありません。

アテスター(Attestor)

アテスターは、デプロイ時にアテステーションを検証するためのGoogle Cloudリソースです。アテスターには対応する公開鍵が登録されており、Binary Authorizationはこの公開鍵を使ってアテステーションの正当性を確認します。

ブレークグラス(Breakglass)

ブレークグラスは、緊急時にBinary Authorizationのポリシーを一時的に回避してデプロイするための機能です。ポリシー違反イメージでも正当な理由があればデプロイできますが、ブレークグラス使用時は必ずCloud Audit Logsに記録が残ります。意図しない利用がないか、監査ログで定期的に確認することが重要です。

Cloud Buildとの統合:built-by-cloud-buildアテスター

Binary AuthorizationとCloud Buildを組み合わせる最も簡単な方法が、built-by-cloud-buildアテスターを利用することです。

このアテスターの特徴は以下のとおりです。

  • Cloud Buildでビルドを実行すると、プロジェクトに自動的に作成される
  • ビルド成功後、Cloud Buildが自動的にイメージに署名してアテステーションを作成する
  • デプロイ時に、Binary Authorizationがアテステーションを検証し、Cloud Buildでビルドされたイメージのみを許可する

これにより、Cloud BuildパイプラインとBinary Authorizationを組み合わせたソフトウェアサプライチェーンの保護を、最小限の設定で実現できます。

アーキテクチャ

Binary Authorization アーキテクチャ

試してみた

パイプライン全体像

今回のハンズオンでは、git push 1回で「ビルド → アテステーション作成 → Binary Authorization チェック付きデプロイ」が完結するCI/CDパイプラインを構築します。

CI/CD パイプライン全体像

ビルドとデプロイを別ファイルに分ける理由としては、Cloud Build はビルドパイプラインが完了した後にアテステーションを作成します。同一の設定ファイルにデプロイステップを追加しても、アテステーション作成前にデプロイが実行されてしまい失敗します。Pub/Sub トリガーを利用してデプロイパイプラインと分けることで、ビルド完了通知を受信してからデプロイを起動するため、この順序を正しく保つことができます。

サンプルコードの構成

今回は以下のファイルを使ってハンズオンを進めます。

google-cloud-app-poc/
├── main.py                      # Flask アプリケーション
├── requirements.txt             # Python依存パッケージ
├── Dockerfile                   # コンテナイメージ定義
├── cloudbuild.yaml              # ビルドパイプライン(アテステーション作成)
└── cloudbuild-deploy-cicd.yaml  # CI/CD自動デプロイ用

cloudbuild.yaml
ビルドパイプラインです。images フィールドを使い、requestedVerifyOption: VERIFIED でアテステーションを有効化しています。

cloudbuild.yaml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$COMMIT_SHA'
      - '.'

images:
  - 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$COMMIT_SHA'

options:
  requestedVerifyOption: VERIFIED
  logging: CLOUD_LOGGING_ONLY
  pubsubTopic: projects/YOUR_PROJECT_ID/topics/cloud-builds-deploy

cloudbuild-deploy-cicd.yaml
CI/CD 用デプロイパイプラインです。Pub/Sub メッセージから注入されたビルド ID をもとに gcloud builds describe でコミット SHA を取得し、正確なイメージタグでデプロイします。

cloudbuild-deploy-cicd.yaml
steps:
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: bash
    args:
      - '-c'
      - |
        set -e
        COMMIT_SHA=$$(gcloud builds describe $_BUILD_ID \
          --format='value(substitutions.COMMIT_SHA)')
        gcloud run deploy my-app \
          --image asia-northeast1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$$COMMIT_SHA \
          --region asia-northeast1 \
          --binary-authorization=default \
          --platform managed

options:
  logging: CLOUD_LOGGING_ONLY

substitutions:
  _BUILD_ID: ''
  • $_BUILD_ID は Pub/Sub メッセージの attributes.buildId から自動注入されます
  • $$()$$COMMIT_SHA は Cloud Build の置換処理後に $()$COMMIT_SHA(bash 変数)になります

前提条件

  • Google Cloud プロジェクトが作成済みであること
  • gcloud CLI がインストール・認証済みであること
  • ソースコードが GitHub リポジトリにプッシュ済みであること

1. 必要な API の有効化

今回の検証で必要なAPIを有効化していきます。

gcloud services enable \
  binaryauthorization.googleapis.com \
  run.googleapis.com \
  cloudbuild.googleapis.com \
  containeranalysis.googleapis.com \
  artifactregistry.googleapis.com \
  pubsub.googleapis.com

2. Artifact Registry リポジトリの作成

コンテナイメージを保存するArtifact Registryのリポジトリを作成します

gcloud artifacts repositories create my-repo \
  --repository-format=docker \
  --location=asia-northeast1 \
  --description="Binary Authorization demo repository"

alt text

作成後、Docker 認証を設定します

gcloud auth configure-docker asia-northeast1-docker.pkg.dev

3. Cloud Build サービスアカウントへの権限付与

デプロイトリガーが gcloud run deploy を実行するため、Cloud Build のサービスアカウントに Cloud Run への権限を付与します。

2024年5月以降に作成したプロジェクトでは、Cloud Build のデフォルトサービスアカウントは Compute Engine デフォルトサービスアカウント(${PROJECT_NUMBER}-compute@developer.gserviceaccount.com)になっています。

PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format='value(projectNumber)')

# Cloud Run へのデプロイ権限
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
  --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
  --role="roles/run.developer"

# Cloud Run サービスアカウントへのアクセス権
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
  --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
  --role="roles/iam.serviceAccountUser"

# Cloud Build サービスエージェントに Pub/Sub サブスクライバー権限を付与
# Pub/Sub トリガーがメッセージを受信するために必要
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
  --member="serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-cloudbuild.iam.gserviceaccount.com" \
  --role="roles/pubsub.subscriber"

alt text

alt text

4. Pub/Sub トピックの作成

Cloud Build はビルド完了時にデフォルトで cloud-builds トピックへイベントを発行しますが、cloud-builds トピックを直接 Pub/Sub トリガーで購読することは推奨されていません。ビルド完了通知 → トリガー起動 → 新ビルド → 通知という無限ループが発生するリスクがあるためです。

代わりに、デプロイトリガー専用のカスタムトピックを作成し、cloudbuild.yamloptions.pubsubTopic でそのトピックに通知を送るよう設定します

# デプロイトリガー用カスタムトピックを作成
gcloud pubsub topics create cloud-builds-deploy

alt text

5. GitHub リポジトリの接続

ビルドトリガーを作成する前に、Cloud Build と GitHub リポジトリを接続します。この手順は Cloud Console の UI から行います。

  1. Cloud Console → Cloud Build → トリガー を開く

alt text

  1. リポジトリを接続」をクリック

alt text

  1. ソースとして「GitHub (Cloud Build GitHub アプリ)」を選択し「続行」

alt text

  1. GitHub の認証画面で Cloud Build GitHub App を承認・インストール(初回のみ)

alt text

  1. 連携するリポジトリを選択して「接続」をクリック

alt text

alt text

alt text

接続が完了すると、リポジトリの一覧に対象リポジトリが表示されます。

alt text

6. ビルドトリガーの作成

main ブランチへの push で cloudbuild.yaml を実行するトリガーを作成します

PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

gcloud builds triggers create github \
  --name=build-trigger \
  --repo-name=google-cloud-app-poc \
  --repo-owner=your-github-username \
  --branch-pattern="^main$" \
  --build-config=cloudbuild.yaml \
  --service-account=projects/$PROJECT_ID/serviceAccounts/${PROJECT_NUMBER}-compute@developer.gserviceaccount.com

alt text

7. 初回 git push:アテスターを自動作成する

built-by-cloud-build アテスターは最初のビルド実行時に自動作成されます。ポリシー設定にはアテスターが事前に存在する必要があるため、コードを push してビルドを一度実行します

git push origin main

alt text

alt text

ビルド完了後、アテスターが作成されたことを確認します

gcloud container binauthz attestors list

built-by-cloud-build が一覧に表示されれば成功です。

┌──────────────────────┬─────────────────────────────────────────────────────┬─────────────────┐
         NAME                         NOTE NUM_PUBLIC_KEYS
├──────────────────────┼─────────────────────────────────────────────────────┼─────────────────┤
 built-by-cloud-build projects/YOUR_PROJECT_ID/notes/built-by-cloud-build 30
└──────────────────────┴─────────────────────────────────────────────────────┴─────────────────┘

alt text

8. Dry Run ポリシーの設定

本番環境への影響を避けるため、まず Dry Run モードでポリシーを設定します。Dry Run ではデプロイをブロックせず、ポリシー違反を監査ログに記録するのみです。

PROJECT_ID を実際のプロジェクト ID に置き換えて実行します

cat > /tmp/policy.yaml << EOF
defaultAdmissionRule:
  evaluationMode: REQUIRE_ATTESTATION
  enforcementMode: DRYRUN_AUDIT_LOG_ONLY
  requireAttestationsBy:
  - projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build
name: projects/YOUR_PROJECT_ID/policy
EOF

gcloud container binauthz policy import /tmp/policy.yaml

各プロパティの意味は以下のとおりです。

プロパティ 説明
defaultAdmissionRule プロジェクト全体に適用されるデフォルトのデプロイ評価ルール
evaluationMode REQUIRE_ATTESTATION デプロイ時にアテステーションの存在を必須とする
enforcementMode DRYRUN_AUDIT_LOG_ONLY ポリシー違反でもブロックせず、監査ログへの記録のみ行うDry Runモード
requireAttestationsBy アテスター名 検証に使用するアテスター。built-by-cloud-build を指定することでCloud Buildビルド済みイメージのみを許可する
name ポリシーのリソース名 projects/{PROJECT_ID}/policy の形式で指定する
defaultAdmissionRule:
  enforcementMode: DRYRUN_AUDIT_LOG_ONLY
  evaluationMode: REQUIRE_ATTESTATION
  requireAttestationsBy:
  - projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build
etag: '"7vCKuwdomBDI"'
name: projects/YOUR_PROJECT_ID/policy
updateTime: '2026-05-11T06:52:43.131359Z'

alt text

9. デプロイトリガーの作成

ステップ4で作成したカスタムトピック cloud-builds-deploy へのビルド成功通知を受けてデプロイを起動するトリガーを作成します

gcloud builds triggers create pubsub \
  --name=deploy-trigger \
  --topic=projects/$PROJECT_ID/topics/cloud-builds-deploy \
  --build-config=cloudbuild-deploy-cicd.yaml \
  --repo=https://github.com/your-github-username/google-cloud-app-poc \
  --repo-type=GITHUB \
  --branch=main \
  --substitutions='_BUILD_ID=$(body.message.attributes.buildId),_BUILD_STATUS=$(body.message.attributes.status)' \
  --subscription-filter='_BUILD_STATUS == "SUCCESS"' \
  --service-account=projects/$PROJECT_ID/serviceAccounts/${PROJECT_NUMBER}-compute@developer.gserviceaccount.com
  • $(body.message.attributes.buildId)$(body.message.attributes.status) は Pub/Sub メッセージ属性からの値を取得するペイロードバインディングです
  • --subscription-filter の CEL 式が true のときのみデプロイトリガーが起動します

alt text

10. CI/CD フローの動作確認(Dry Run)

コードに変更を加えて push します:

# main.py を少し変更して push
git add .
git commit -m "test: CI/CD pipeline with Binary Authorization"
git push origin main

ビルドトリガーが起動したら、以下でパイプラインの流れを追跡します

# ビルドの状況を確認
gcloud builds list --limit=5

ID                                    CREATE_TIME                DURATION  SOURCE                                                                                             IMAGES                                                                                                  STATUS
4689fd22-0513-4c2a-9bbc-06bd15a7dd64  2026-05-11T11:36:03+00:00  14S       https://github.com/your-github-username/google-cloud-app-poc.git@3a03c2e342044173d154515e1c09be34bc6b1fd9  -                                                                                                       WORKING
5223a434-f00b-4eeb-875e-012b6b51e63b  2026-05-11T11:25:56+00:00  34S       https://github.com/your-github-username/google-cloud-app-poc.git@dcf90f0a1f556289ac67b870ece29388c99a70b6  asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/my-repo/my-app:dcf90f0a1f556289ac67b870ece29388c99a70b6  SUCCESS
ea91b240-933f-4120-bcb4-0334719e9546  2026-05-11T11:18:01+00:00  34S       https://github.com/your-github-username/google-cloud-app-poc.git@38b35efb16d149aeb70638cdb41b4d2534d1a9e7  asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/my-repo/my-app:38b35efb16d149aeb70638cdb41b4d2534d1a9e7  SUCCESS
c7838c6e-506b-4790-8191-4f64843c9d18  2026-05-11T06:59:17+00:00  30S       https://github.com/your-github-username/google-cloud-app-poc.git@50bc3527b42f368e865dfdad94cfdd3dd283f815  asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/my-repo/my-app:50bc3527b42f368e865dfdad94cfdd3dd283f815  SUCCESS
4eaa03a2-2229-42bc-a975-2fd103e1ba7f  2026-05-11T05:48:03+00:00  32S       https://github.com/your-github-username/google-cloud-app-poc.git@73bc86b201c42065e3eab67e7dd215639b096719  asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/my-repo/my-app:73bc86b201c42065e3eab67e7dd215639b096719  SUCCESS

# ビルド完了後、デプロイトリガーも起動しているか確認
DEPLOY_TRIGGER_ID=$(gcloud builds triggers describe deploy-trigger --format='value(id)')
gcloud builds list --filter="build_trigger_id=$DEPLOY_TRIGGER_ID" --limit=5

gcloud builds list --filter="build_trigger_id=$DEPLOY_TRIGGER_ID" --limit=5
ID                                    CREATE_TIME                DURATION  SOURCE                                                                                             IMAGES  STATUS
6e05f602-f68f-4832-a23a-4cbe857cf216  2026-05-11T11:50:28+00:00  34S       https://github.com/your-github-username/google-cloud-app-poc.git@6545af873e1b0999b4014740d9c755dc5723218a  -       WORKING
1dc2782e-ce83-4135-8ca0-356ccd70a641  2026-05-11T11:36:55+00:00  -         -                                                                                                  -       FAILURE

# Cloud Run の最新リビジョンを確認
gcloud run revisions list --service my-app --region asia-northeast1

   REVISION          ACTIVE  SERVICE  DEPLOYED                 DEPLOYED BY
  my-app-00001-9hg  yes     my-app   2026-05-11 11:51:55 UTC  YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com

Dry Run モードで問題がないことをログで確認します

gcloud logging read --order="desc" --freshness=1d \
  'resource.type="cloud_run_revision" AND logName:"cloudaudit.googleapis.com%2Fsystem_event" AND "dry run"'

違反ログが出ていなければ Enforce モードへ切り替えます。

11. Enforce モードへの切り替え

enforcementModeENFORCED_BLOCK_AND_AUDIT_LOG に変更し、実際にポリシー違反のデプロイをブロックします

cat > /tmp/policy.yaml << EOF
defaultAdmissionRule:
  evaluationMode: REQUIRE_ATTESTATION
  enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
  requireAttestationsBy:
  - projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build
name: projects/YOUR_PROJECT_ID/policy
EOF

gcloud container binauthz policy import /tmp/policy.yaml

Dry Runからの変更点は enforcementMode のみです。

プロパティ 説明
enforcementMode ENFORCED_BLOCK_AND_AUDIT_LOG ポリシー違反のデプロイをブロックし、監査ログに記録する本番向けモード
defaultAdmissionRule:
  enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
  evaluationMode: REQUIRE_ATTESTATION
  requireAttestationsBy:
  - projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build
etag: '"RRZ6HLFyP2zJ"'
name: projects/YOUR_PROJECT_ID/policy
updateTime: '2026-05-11T11:53:39.760415Z'

alt text

12. 動作確認:未許可イメージのデプロイを試みる

Cloud Build を経由していない(= built-by-cloud-build アテステーションがない)公開イメージを直接デプロイして、ポリシーによりブロックされることを確認します。

gcloud run deploy my-app \
  --image gcr.io/google-samples/hello-app:1.0 \
  --region asia-northeast1 \
  --binary-authorization=default \
  --platform managed

ポリシーにより次のようなエラーが返されます

ERROR: (gcloud.run.deploy) Container image 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' is not authorized by policy. 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' : Image gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e denied by attestor projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build: No attestations found that were valid and signed by a key trusted by the attestor

ポリシー違反でデプロイが失敗しても、既存のリビジョンは引き続き正常にトラフィックを処理します

   REVISION          ACTIVE  SERVICE  DEPLOYED                 DEPLOYED BY
X  my-app-00002-7cr          my-app   2026-05-11 11:55:38 UTC  your-email@example.com
  my-app-00001-9hg  yes     my-app   2026-05-11 11:51:55 UTC  YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com

続いて、Cloud Build 経由のデプロイ(git push からの自動デプロイ)が正常に通過することを確認します

# コードを変更して push → ビルド → アテステーション作成 → デプロイ(Binary Authorization 通過)
git push origin main

# デプロイ完了後、新しいリビジョンが作成されているか確認
gcloud run revisions list --service my-app --region asia-northeast1

   REVISION          ACTIVE  SERVICE  DEPLOYED                 DEPLOYED BY
  my-app-00003-px2  yes     my-app   2026-05-11 11:59:11 UTC  YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com
  my-app-00002-7cr          my-app   2026-05-11 11:55:38 UTC  your-email@example.com
  my-app-00001-9hg          my-app   2026-05-11 11:51:55 UTC  YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com

13. ブレークグラスで緊急デプロイする(緊急時のみ)

緊急時にポリシー違反のイメージをデプロイしなければならない場合は、ブレークグラスを使用できます。使用すると理由とともに Cloud Audit Logs に記録が残ります

gcloud run services update my-app \
  --breakglass="緊急対応: 本番障害対応のため一時的に迂回" \
  --region asia-northeast1

ブレークグラス使用履歴の確認

gcloud logging read --order="desc" --freshness=7d \
  'resource.type="cloud_run_revision" AND logName:"cloudaudit.googleapis.com%2Fsystem_event" AND "breakglass"'

監査ログの出力結果はこちらです。run.googleapis.com/binary-authorization-breakglasにブレークグラス使用時のコメントが記録されていることが確認できます。

---
insertId: -1myrlfduc28
logName: projects/YOUR_PROJECT_ID/logs/cloudaudit.googleapis.com%2Fsystem_event
protoPayload:
  '@type': type.googleapis.com/google.cloud.audit.AuditLog
  methodName: /Services.ReplaceService
  resourceName: namespaces/YOUR_PROJECT_ID/services/my-app
  response:
    '@type': type.googleapis.com/google.cloud.run.v1.Service
    apiVersion: serving.knative.dev/v1
    kind: Service
    metadata:
      annotations:
        run.googleapis.com/binary-authorization: default
        run.googleapis.com/binary-authorization-breakglass: '緊急対応: 本番障害対応のため一時的に迂回'
        run.googleapis.com/client-name: gcloud
        run.googleapis.com/client-version: 555.0.0
        run.googleapis.com/ingress: all
        run.googleapis.com/ingress-status: all
        run.googleapis.com/maxScale: '100'
        run.googleapis.com/operation-id: 2f0ba0bb-9663-4157-947d-9e0343c20445
        run.googleapis.com/urls: '["https://my-app-YOUR_PROJECT_NUMBER.asia-northeast1.run.app","https://my-app-xxxxxxxxxx-an.a.run.app"]'
        serving.knative.dev/creator: YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com
        serving.knative.dev/lastModifier: your-email@example.com
      creationTimestamp: '2026-05-11T11:51:55.504617Z'
      generation: 4
      labels:
        cloud.googleapis.com/location: asia-northeast1
      name: my-app
      namespace: 'YOUR_PROJECT_NUMBER'
      resourceVersion: AAZRiYMgftY
      selfLink: /apis/serving.knative.dev/v1/namespaces/YOUR_PROJECT_NUMBER/services/my-app
      uid: 2c6bdaf2-7240-4c4d-88df-82d7c9f29fda
    spec:
      template:
        metadata:
          annotations:
            autoscaling.knative.dev/maxScale: '100'
            run.googleapis.com/client-name: gcloud
            run.googleapis.com/client-version: 567.0.0
            run.googleapis.com/startup-cpu-boost: 'true'
          labels:
            client.knative.dev/nonce: gcgumzbzna
            run.googleapis.com/startupProbeType: Default
        spec:
          containerConcurrency: 80
          containers:
          - image: asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/my-repo/my-app:2887f1c509d86ca742f37ba5d929b7b475c6a0e4
            ports:
            - containerPort: 8080
              name: http1
            resources:
              limits:
                cpu: 1000m
                memory: 512Mi
            startupProbe:
              failureThreshold: 1
              periodSeconds: 240
              tcpSocket:
                port: 8080
              timeoutSeconds: 240
          serviceAccountName: YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com
          timeoutSeconds: 300
      traffic:
      - latestRevision: true
        percent: 100
    status:
      address:
        url: https://my-app-xxxxxxxxxx-an.a.run.app
      conditions:
      - lastTransitionTime: '2026-05-11T11:59:18.374548Z'
        status: 'True'
        type: Ready
      - lastTransitionTime: '2026-05-11T11:59:17.177630Z'
        status: 'True'
        type: ConfigurationsReady
      - lastTransitionTime: '2026-05-11T11:59:18.348209Z'
        status: 'True'
        type: RoutesReady
      latestCreatedRevisionName: my-app-00003-px2
      latestReadyRevisionName: my-app-00003-px2
      observedGeneration: 4
      traffic:
      - latestRevision: true
        percent: 100
        revisionName: my-app-00003-px2
      url: https://my-app-xxxxxxxxxx-an.a.run.app
  serviceName: run.googleapis.com
  status:
    message: Ready condition status changed to True for Service my-app.
receiveTimestamp: '2026-05-11T12:01:52.832706501Z'
resource:
  labels:
    configuration_name: ''
    location: asia-northeast1
    project_id: YOUR_PROJECT_ID
    revision_name: ''
    service_name: my-app
  type: cloud_run_revision
severity: INFO
timestamp: '2026-05-11T12:01:52.585490Z'

14. 監査ログの確認

デプロイの検証結果は Cloud Audit Logs に記録されます。

gcloud CLI で確認する場合

gcloud logging read --order="desc" --freshness=7d \
  'resource.type="cloud_run_revision" AND logName:"cloudaudit.googleapis.com%2Fsystem_event" AND protoPayload.response.status.conditions.reason="ContainerImageUnauthorized"'

監査ログの出力結果はこちらです。先ほどのデプロイエラーが記録されていることが確認できます。

insertId: ogt159d3sr0
logName: projects/YOUR_PROJECT_ID/logs/cloudaudit.googleapis.com%2Fsystem_event
protoPayload:
  '@type': type.googleapis.com/google.cloud.audit.AuditLog
  methodName: /Services.ReplaceService
  resourceName: namespaces/YOUR_PROJECT_ID/services/my-app
  response:
    '@type': type.googleapis.com/google.cloud.run.v1.Service
    apiVersion: serving.knative.dev/v1
    kind: Service
    metadata:
      annotations:
        run.googleapis.com/binary-authorization: default
        run.googleapis.com/client-name: gcloud
        run.googleapis.com/client-version: 555.0.0
        run.googleapis.com/ingress: all
        run.googleapis.com/ingress-status: all
        run.googleapis.com/maxScale: '100'
        run.googleapis.com/operation-id: d05de716-0d1d-4d27-8d44-37d52e2525d8
        run.googleapis.com/urls: '["https://my-app-YOUR_PROJECT_NUMBER.asia-northeast1.run.app","https://my-app-xxxxxxxxxx-an.a.run.app"]'
        serving.knative.dev/creator: YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com
        serving.knative.dev/lastModifier: your-email@example.com
      creationTimestamp: '2026-05-11T11:51:55.504617Z'
      generation: 2
      labels:
        cloud.googleapis.com/location: asia-northeast1
      name: my-app
      namespace: 'YOUR_PROJECT_NUMBER'
      resourceVersion: AAZRiWz+/JY
      selfLink: /apis/serving.knative.dev/v1/namespaces/YOUR_PROJECT_NUMBER/services/my-app
      uid: 2c6bdaf2-7240-4c4d-88df-82d7c9f29fda
    spec:
      template:
        metadata:
          annotations:
            autoscaling.knative.dev/maxScale: '100'
            run.googleapis.com/client-name: gcloud
            run.googleapis.com/client-version: 555.0.0
            run.googleapis.com/startup-cpu-boost: 'true'
          labels:
            client.knative.dev/nonce: qzesetxnae
            run.googleapis.com/startupProbeType: Default
        spec:
          containerConcurrency: 80
          containers:
          - image: gcr.io/google-samples/hello-app:1.0
            ports:
            - containerPort: 8080
              name: http1
            resources:
              limits:
                cpu: 1000m
                memory: 512Mi
            startupProbe:
              failureThreshold: 1
              periodSeconds: 240
              tcpSocket:
                port: 8080
              timeoutSeconds: 240
          serviceAccountName: YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com
          timeoutSeconds: 300
      traffic:
      - latestRevision: true
        percent: 100
    status:
      address:
        url: https://my-app-xxxxxxxxxx-an.a.run.app
      conditions:
      - lastTransitionTime: '2026-05-11T11:55:41.251634Z'
        message: |
          Container image 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' is not authorized by policy. 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' : Image gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e denied by attestor projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build: No attestations found that were valid and signed by a key trusted by the attestor
        reason: ContainerImageUnauthorized
        status: 'False'
        type: Ready
      - lastTransitionTime: '2026-05-11T11:55:41.251634Z'
        message: |
          Container image 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' is not authorized by policy. 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' : Image gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e denied by attestor projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build: No attestations found that were valid and signed by a key trusted by the attestor
        reason: ContainerImageUnauthorized
        status: 'False'
        type: ConfigurationsReady
      - lastTransitionTime: '2026-05-11T11:55:40.906724Z'
        message: Provisioning revision instances to receive traffic.
        status: Unknown
        type: RoutesReady
      latestCreatedRevisionName: my-app-00002-7cr
      latestReadyRevisionName: my-app-00001-9hg
      observedGeneration: 2
      traffic:
      - latestRevision: true
        percent: 100
        revisionName: my-app-00001-9hg
      url: https://my-app-xxxxxxxxxx-an.a.run.app
  serviceName: run.googleapis.com
  status:
    code: 7
    message: |
      Ready condition status changed to False for Service my-app with message: Container image 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' is not authorized by policy. 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' : Image gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e denied by attestor projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build: No attestations found that were valid and signed by a key trusted by the attestor
receiveTimestamp: '2026-05-11T11:55:41.847828731Z'
resource:
  labels:
    configuration_name: ''
    location: asia-northeast1
    project_id: YOUR_PROJECT_ID
    revision_name: ''
    service_name: my-app
  type: cloud_run_revision
severity: ERROR
timestamp: '2026-05-11T11:55:41.293854Z'
---
insertId: -1myrlfdua6g
logName: projects/YOUR_PROJECT_ID/logs/cloudaudit.googleapis.com%2Fsystem_event
protoPayload:
  '@type': type.googleapis.com/google.cloud.audit.AuditLog
  methodName: /Services.ReplaceService
  resourceName: namespaces/YOUR_PROJECT_ID/revisions/my-app-00002-7cr
  response:
    '@type': type.googleapis.com/google.cloud.run.v1.Revision
    apiVersion: serving.knative.dev/v1
    kind: Revision
    metadata:
      annotations:
        autoscaling.knative.dev/maxScale: '100'
        run.googleapis.com/client-name: gcloud
        run.googleapis.com/client-version: 555.0.0
        run.googleapis.com/operation-id: d05de716-0d1d-4d27-8d44-37d52e2525d8
        run.googleapis.com/startup-cpu-boost: 'true'
        serving.knative.dev/creator: your-email@example.com
      creationTimestamp: '2026-05-11T11:55:38.466117Z'
      generation: 1
      labels:
        client.knative.dev/nonce: qzesetxnae
        cloud.googleapis.com/location: asia-northeast1
        run.googleapis.com/startupProbeType: Default
        serving.knative.dev/configuration: my-app
        serving.knative.dev/configurationGeneration: '2'
        serving.knative.dev/route: my-app
        serving.knative.dev/service: my-app
        serving.knative.dev/serviceUid: 2c6bdaf2-7240-4c4d-88df-82d7c9f29fda
      name: my-app-00002-7cr
      namespace: 'YOUR_PROJECT_NUMBER'
      ownerReferences:
      - apiVersion: serving.knative.dev/v1
        blockOwnerDeletion: true
        controller: true
        kind: Configuration
        name: my-app
        uid: a5df13d9-5bdd-4617-b705-e289ad19bfe9
      resourceVersion: AAZRiWz+DMc
      selfLink: /apis/serving.knative.dev/v1/namespaces/YOUR_PROJECT_NUMBER/revisions/my-app-00002-7cr
      uid: 2bbfaf91-0957-4f90-8503-f96ac1211a10
    spec:
      containerConcurrency: 80
      containers:
      - image: gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e
        name: hello-app-1
        ports:
        - containerPort: 8080
          name: http1
        resources:
          limits:
            cpu: 1000m
            memory: 512Mi
        startupProbe:
          failureThreshold: 1
          periodSeconds: 240
          tcpSocket:
            port: 8080
          timeoutSeconds: 240
      serviceAccountName: YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com
      timeoutSeconds: 300
    status:
      conditions:
      - lastTransitionTime: '2026-05-11T11:55:41.222087Z'
        message: |
          Container image 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' is not authorized by policy. 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' : Image gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e denied by attestor projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build: No attestations found that were valid and signed by a key trusted by the attestor
        reason: ContainerImageUnauthorized
        status: 'False'
        type: Ready
      - lastTransitionTime: '2026-05-11T11:55:40.777482Z'
        message: Container image import completed in 1.24s.
        status: 'True'
        type: ContainerReady
      - lastTransitionTime: '2026-05-11T11:55:41.222087Z'
        message: |
          Container image 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' is not authorized by policy. 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' : Image gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e denied by attestor projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build: No attestations found that were valid and signed by a key trusted by the attestor
        reason: ContainerImageUnauthorized
        severity: Error
        status: 'False'
        type: ResourcesAvailable
      containerStatuses:
      - imageDigest: gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e
        name: hello-app-1
      imageDigest: gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e
      logUrl: https://console.cloud.google.com/logs/viewer?project=YOUR_PROJECT_ID&resource=cloud_run_revision/service_name/my-app/revision_name/my-app-00002-7cr&advancedFilter=resource.type%3D%22cloud_run_revision%22%0Aresource.labels.service_name%3D%22my-app%22%0Aresource.labels.revision_name%3D%22my-app-00002-7cr%22
      observedGeneration: 1
  serviceName: run.googleapis.com
  status:
    code: 7
    message: |
      Ready condition status changed to False for Revision my-app-00002-7cr with message: Container image 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' is not authorized by policy. 'gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e' : Image gcr.io/google-samples/hello-app@sha256:649188051906c9df7f87fb2ef04a21acbdb009c0877fa67b3132b83230411f6e denied by attestor projects/YOUR_PROJECT_ID/attestors/built-by-cloud-build: No attestations found that were valid and signed by a key trusted by the attestor
receiveTimestamp: '2026-05-11T11:55:41.651957483Z'
resource:
  labels:
    configuration_name: my-app
    location: asia-northeast1
    project_id: YOUR_PROJECT_ID
    revision_name: my-app-00002-7cr
    service_name: my-app
  type: cloud_run_revision
severity: ERROR
timestamp: '2026-05-11T11:55:41.230122Z'

まとめ

今回は、Binary AuthorizationとCloud Build・Cloud Runを組み合わせることで、「Cloud Buildでビルドされたイメージのみのデプロイを許可する」というソフトウェアサプライチェーンセキュリティを比較的簡単に実装できることを確認しました。。

実装の要となるのがbuilt-by-cloud-buildアテスターです。Cloud Buildでビルドを実行するだけでアテスターが自動作成され、ビルド成功後にイメージへの署名とアテステーション作成も自動で行われます。KMSや独自の署名基盤を用意しなくても、ポリシーの設定だけでデプロイ制御を実現できる点が大きな魅力です。

本番環境への導入はDry Runモードから段階的に進めることを推奨します。Dry Runでは実際のデプロイをブロックせず、ポリシー違反をCloud Audit Logsに記録するだけなので、既存サービスへの影響を確認しながら安全にポリシーを検証できます。問題がないことを確認してからEnforceモードへ切り替えることで、想定外のブロックによる障害を防げます。
また、いざというときのブレークグラス機能も理解しておく必要があります。緊急対応でポリシーを一時的に回避してデプロイしなければならない場面でも、ブレークグラスを使えば理由を明記したうえでデプロイが可能です。ただし使用履歴は必ずCloud Audit Logsに記録されるため、定期的な棚卸しで意図しない利用を検知できる体制を整えておくことが重要です。

コンテナのサプライチェーンセキュリティ強化を検討している組織は、まずDry Runモードで試してみることをおすすめします。既存の環境を壊すことなく、段階的にポリシーを適用できるのがBinary Authorizationの強みです。

この記事が誰かの助けになれば幸いです。

以上、クラウド事業本部コンサルティング部の渡邉でした!

この記事をシェアする

関連記事