
Binary Authorizationを利用してCloud BuildでビルドしたイメージだけをCloud Runへデプロイ許可する仕組みを作ってみた
はじめに
こんにちは。
クラウド事業本部コンサルティング部の渡邉です。
コンテナイメージのサプライチェーンセキュリティへの関心が高まる中、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を組み合わせたソフトウェアサプライチェーンの保護を、最小限の設定で実現できます。
アーキテクチャ

試してみた
パイプライン全体像
今回のハンズオンでは、git push 1回で「ビルド → アテステーション作成 → Binary Authorization チェック付きデプロイ」が完結する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 でアテステーションを有効化しています。
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 を取得し、正確なイメージタグでデプロイします。
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 プロジェクトが作成済みであること
gcloudCLI がインストール・認証済みであること- ソースコードが 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"

作成後、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"


4. Pub/Sub トピックの作成
Cloud Build はビルド完了時にデフォルトで cloud-builds トピックへイベントを発行しますが、cloud-builds トピックを直接 Pub/Sub トリガーで購読することは推奨されていません。ビルド完了通知 → トリガー起動 → 新ビルド → 通知という無限ループが発生するリスクがあるためです。
代わりに、デプロイトリガー専用のカスタムトピックを作成し、cloudbuild.yaml の options.pubsubTopic でそのトピックに通知を送るよう設定します
# デプロイトリガー用カスタムトピックを作成
gcloud pubsub topics create cloud-builds-deploy

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

- 「リポジトリを接続」をクリック

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

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

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



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

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

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


ビルド完了後、アテスターが作成されたことを確認します
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 │
└──────────────────────┴─────────────────────────────────────────────────────┴─────────────────┘

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'

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のときのみデプロイトリガーが起動します

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 モードへの切り替え
enforcementMode を ENFORCED_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'

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の強みです。
この記事が誰かの助けになれば幸いです。
以上、クラウド事業本部コンサルティング部の渡邉でした!








