Zenn Tech Blog
🧱

Cloud Buildトリガーのサービスアカウントをデフォルトから作成したものへ付け替える

2024/02/28に公開

Google Cloud の Cloud Build はアプリケーションをビルドするためのサービスです。Cloud Run や GKE へアプリケーションをデプロイする現場では、使用頻度の高いサービスかと思います。そんなCloud Buildのデフォルトの動作に変更が入ります。2024年の4月29日以降に新しく作成された Google Cloud のプロジェクトでは、Cloud Build トリガー作成時に自動で関連づけられるサービスアカウントがCloud Build サービスアカウントから、Compute Engine サービスアカウントに変更されます。

https://cloud.google.com/build/docs/cloud-build-service-account-updates?hl=ja

変更点

Cloud Build を利用する場合、トリガーというリソースを作ります。トリガーは、

  • ビルドの発動条件
  • どこからソースをとってくるか
  • ビルドファイル名

といった情報を持っており、Cloud Buildはそれを元にビルドを実行します。トリガーには他にサービスアカウントを指定することができ、ビルド実行中に適用する権限を制限できます。このサービスアカウントですが、もちろん自分で作成したサービスアカウントを明示的に指定することもできる一方、「何も指定しない」という状態にもできます。もしかしたら指定しないことのほうが多いかもしれません。

今回言っている変更というのが、このサービスアカウント未指定の動作です。これまでは、何も指定しないとCloud Build サービスアカウントが自動的に使われるようになっていました。それが、4月29日以降に新しく作成されたプロジェクトについては、Compute Engine サービスアカウントが選択されるようになります。Cloud Build サービスアカウントCompute Engine サービスアカウントも、Google Cloud のプロジェクトを作成すると自動的に作成されるサービスアカウントです。

なぜ変更されるのか

ドキュメントには、

これらの変更により、今後のお客様のデフォルト セキュリティ対策が改善されます。

としか記載がありません。私の予想になってしまいますが、強めの権限をデフォルトで持ってしまっているCloud Build サービスアカウントを廃止したいのだと思います。Cloud Build はアプリケーションのビルドに集中するべく、暗黙的なふるまいがいくつかあり、そのひとつに Cloud Storage バケットへのログ出力 があります。Cloud Build サービスアカウントでは、Cloud Storage バケットへの書き込みをとくに意識しなくても可能なように、書き込み権限が付与されているようです。結果として、必要以上にデフォルトサービスアカウントの権限が強くなっています。4月29日以降に作成されるプロジェクトには、Cloud Build サービスアカウントはつくられないかもしれません。

これまでのプロジェクトはどうなる?

すでに作成されているプロジェクトについては、この件について何もしなくても支障はありません。しかし、たとえばTerraformなどのIaCでCloud Buildの構成を組んでおり、サービスアカウントが未指定、つまりCloud Build サービスアカウントを使用しているとします。何らかの事情で4月29日以降に新しいプロジェクトを作ることになり、同じCloud Build設定をIaCから適用すると、こちらはCompute Engine サービスアカウントが設定されるため、期待する権限と異なるかもしれません。

なので、余裕があるときにCloud Build用のサービスアカウントを作成し、トリガー作成時にそれを明示的に指定すると良いと思います。この記事ではサービスアカウントをTerraformで作成してトリガーへ設定しなおすスニペットを記録します。

やること

Cloud Build用のサービスアカウントを作成し、それを使ってビルドできるようになるまで、以下の作業が必要でした。

  1. Cloud Build 用のサービスアカウントを作成する
  2. Artifact Registry でサービスアカウントの IAM Binding を追加する
  3. Cloud Build トリガーでサービスアカウントを指定する
  4. cloudbuild.ymlでロギングオプションを指定する

1. Cloud Build 用のサービスアカウントを作成する

イチから必要なロールを調べ直すのは大変だと思うので、Cloud Build サービスアカウントに設定されているロールを抽出し、それをそのまま指定してあげると楽できそうです。

Cloud Build サービスアカウントに設定されているロールの抽出

まず、Cloud Build サービスアカウントのアカウント名を調べます。IAMの画面で、Google 提供のロール付与を含めるチェックを有効にすると出現します。名前が「Cloud Build Service Account」となっているはずなので、そのプリンシパル名を控えておきます。

つぎに、ロールを調べます。以下のgcloudコマンドで調べられます。Cloud Shellなどで実行してみてください。xxxxxxxxxxx@cloudbuild.gserviceaccount.comとなっている箇所に先ほど調べた Cloud Build のプリンシパル名を指定します。

cloudshell
gcloud projects get-iam-policy <project-name> \
--flatten="bindings[].members" --format='table(bindings.role)' \
--filter="bindings.members:xxxxxxxxxxx@cloudbuild.gserviceaccount.com"

ROLE: roles/appengine.deployer
ROLE: roles/appengine.serviceAdmin
ROLE: roles/cloudbuild.builds.builder
...

このロールを転記して新しいサービスアカウントを作ります。このコマンドはStack Overflowで知りました。

https://stackoverflow.com/questions/47006062/how-do-i-list-the-roles-associated-with-a-gcp-service-account

新しいサービスアカウントの作成(Terraform)

Terraformで書きます。

terraform
variable "project_id" {
  description = "GCPのproject_idです"
  type        = string
}
resource "google_service_account" "cloud_build" {
  project      = var.project_id
  account_id   = "cloud-build"
  display_name = "Cloud Buildのサービスアカウント"
}
resource "google_project_iam_member" "cloud_build_appengine_deployer" {
  project = var.project_id
  role    = "roles/appengine.deployer"
  member  = "serviceAccount:${google_service_account.cloud_build.email}"
}

ロール分だけgoogle_project_iam_memberを定義するのですが…ロールが多いと大変かもしれません。参考程度に、以下のスクリプトをブラウザで実行すると少し楽できます。適宜改造して使ってください。

ブラウザのコンソールなどで実行
// ここに必要なロールをすべて記載する
const roles = ["roles/appengine.deployer",
"roles/appengine.serviceAdmin",
"roles/cloudbuild.builds.builder",
"roles/cloudfunctions.developer"]

// Terraform記述が出力されるのでコピペして使う
roles.forEach(role => {
    console.log(`
resource "google_project_iam_member" "cloud_build_${role.split('/')[1].replaceAll('.','_')}" {
  project = var.project_id
  role    = "${role}"
  member  = "serviceAccount:\${google_service_account.cloud_build.email}"
}
    `)
})

サービスアカウントの定義が作成できたら、terraform applyで作成してOKです。

2. Artifact Registry でサービスアカウントの IAM Binding を追加する

Zennでは、Cloud Run へアプリケーションをデプロイしており、アプリの元になるDockerイメージはArtifact Registryへ保管して使っています。Dockerイメージはビルド時に Cloud Build から Artifact Registry へプッシュしています。つまり、新しく作成したサービスアカウントが、Artifact Registry へ書き込める必要があります。Artifact Registry 側へ設定を追加しましょう。こちらもTerraformで作業します。

terraform
variable "gcp_project_id" {
  type = string
}
+variable "cloud_build_service_account_email" {
+  type = string
+  description = "Cloud Build サービスアカウントのメールアドレス"
+}
variable "artifact_registry_location" {
  type = string
  # https://cloud.google.com/storage/docs/locations
  description = "Artifact Registry のロケーションをどこにするか"
}

# アプリケーション用の Artifact Registry リポジトリ
resource "google_artifact_registry_repository" "myapp" {
  project       = var.gcp_project_id
  location      = var.artifact_registry_location
  repository_id = "myapp"
  description   = "myappアプリケーション"
  format        = "DOCKER"
}

# ビルドプロジェクトの Cloud Build と Cloud Run からのアクセスを許可する。
resource "google_artifact_registry_repository_iam_binding" "binding_app_and_builder" {
  project    = var.gcp_project_id
  location   = var.artifact_registry_location
  repository = google_artifact_registry_repository.myapp.name
  role       = "roles/artifactregistry.repoAdmin"
  members = [
    # Cloud Buildからプッシュできるように
    "serviceAccount:xxxxxxxxxxxx@cloudbuild.gserviceaccount.com",
+    "serviceAccount:${var.cloud_build_service_account_email}",
  ]
}

terraform applyします。

3. Cloud Build トリガーでサービスアカウントを指定する

こちらもTerraformで追記します。

terraform
variable "project_id" {
  description = "プロジェクトID"
  type        = string
}
+variable "cloud_build_service_account_id" {
+  description = "Cloud Build に設定するサービスアカウントのIDです"
+  type        = string
+}
variable "cloud_run_service_account" {
  description = "Cloud Run に設定するバックエンドのサービスアカウントです"
  type        = string
}

resource "google_cloudbuild_trigger" "deploy-cloudrun" {
  name            = "deploy-cloudrun"
  description     = "アプリケーションをCloud Runへデプロイする"
+  service_account = var.cloud_build_service_account_id

  github {
    owner = "zenn-dev"
    name  = "application"
    push {
      branch = "main"
    }
  }
  included_files = ["*"]
  filename       = "cloudbuild.yml"
  substitutions = {
    _CLOUD_RUN_SERVICE_ACCOUNT      = var.cloud_run_service_account
    _SHORT_BUILD_ID                 = "$${BUILD_ID:0:8}"
  }
}

terraform applyするとCloud Buildトリガーにサービスアカウントが追加されます。

4. cloudbuild.ymlでロギングオプションを指定する

cloudbuild.ymlにも修正が必要です。ロギングオプションを明示する必要があります。ざっくり、Cloud Loggingへ出力するか、Cloud Storageに出力するかを指定できますが、今回はCloud Loggingに記録するよう設定しました。デフォルトの動作はCloud LoggingとCloud Storageバケットへ両方出力する設定なので、以下の設定を踏襲すると挙動がかわる点に注意してください。

https://cloud.google.com/build/docs/securing-builds/store-manage-build-logs?hl=ja

cloudbuild.yml
    steps:
    - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'us-east1-docker.pkg.dev/myproject/myimage', '.']
+   options:
+     logging: CLOUD_LOGGING_ONLY

ここまで材料を揃えたうえでビルドを実行すると、作成したサービスアカウントを使って完遂できるはずです。

おわりに

既存のプロジェクトで Cloud Build トリガーのサービスアカウントを変更する手順を記録しました。やってみると、暗黙的に設定されている動作や権限がいくつかあり、それを知るいい機会にもなりました。Cloud Build は今後もヘビーユースしていくため、少し詳しくなれた点は良かったと思います。本記事がどなたかの参考になれば幸いです。

参考

Zenn Tech Blog
Zenn Tech Blog

Discussion