Open23

GCPのCloud FunctionsのGitHub Actions上からのデプロイを、Workload Identity連携で認証してやってみる

ころむにーころむにー

"(Preferred) Direct Workload Identity Federation" の方法だとデプロイステップで以下のエラーがでてうまくいかなかった。

Created zip file from 'function' at '/tmp/cfsrc-7ac161f56a859b8e3a675a94.zip'
Error: google-github-actions/deploy-cloud-functions failed with: failed to upload zip file: Failed to POST https://cloudfunctions.googleapis.com/v1/projects/colomney-my-pet-melody-dev/locations/asia-east1/functions:generateUploadUrl: (403) {
  "error": {
    "code": 403,
    "message": "Permission 'cloudfunctions.functions.sourceCodeSet' denied on resource 'projects/colomney-my-pet-melody-dev/locations/asia-east1' (or resource may not exist).",
    "status": "PERMISSION_DENIED"
  }
}
ころむにーころむにー

"Workload Identity Federation through a Service Account" の方法で試してみる。

サービスアカウントを作成し、「Functions 管理者」で権限を付与する。

ころむにーころむにー

以下のエラーが出た。

Created zip file from 'function' at '/tmp/cfsrc-cb36cb11604d364859f7b5a0.zip'
Error: google-github-actions/deploy-cloud-functions failed with: failed to upload zip file: Permission 'iam.serviceAccounts.getAccessToken' denied on resource (or it may not exist).

権限が付与されていないようなので、以下コマンドを実行してみる。

gcloud projects add-iam-policy-binding \
  "${PROJECT_ID}" \
  --role="roles/iam.serviceAccountTokenCreator" \
  --member="serviceAccount:deploy-functions-from-github@${PROJECT_ID}.iam.gserviceaccount.com"
ころむにーころむにー

同じエラーが出た。

Created zip file from 'function' at '/tmp/cfsrc-cb36cb11604d364859f7b5a0.zip'
Error: google-github-actions/deploy-cloud-functions failed with: failed to upload zip file: Permission 'iam.serviceAccounts.getAccessToken' denied on resource (or it may not exist).
ころむにーころむにー

全然解決の糸口が掴めないので、最初からドキュメント通りにやってみる

ころむにーころむにー

サービスアカウントを作成する。

gcloud iam service-accounts create "deploy-functions-from-github" \
  --project "${PROJECT_ID}"

https://github.com/marketplace/actions/authenticate-to-google-cloud#indirect-wif

  1. (Optional) Create a Google Cloud Service Account. If you already have a Service Account, take note of the email address and skip this step.

必要な権限を付与する。

gcloud projects add-iam-policy-binding \
  "${PROJECT_ID}" \
  --role="roles/cloudfunctions.admin" \
  --member="serviceAccount:deploy-functions-from-github@${PROJECT_ID}.iam.gserviceaccount.com"
ころむにーころむにー
  1. Create a Workload Identity Pool:
gcloud iam workload-identity-pools create "github-2" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --display-name="GitHub Actions Pool"

削除した識別子と被っているとエラーが出たので、 "github-2" としている。

ころむにーころむにー
  1. Get the full ID of the Workload Identity Pool:
gcloud iam workload-identity-pools describe "github-2" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --format="value(name)"

結果をメモしておく。

ころむにーころむにー
  1. Create a Workload Identity Provider in that pool:
gcloud iam workload-identity-pools providers create-oidc "my-pet-melody" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="github-2" \
  --display-name="My Pet Melody repo" \
  --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
  --attribute-condition="assertion.repository_owner == '${GITHUB_ORG}'" \
  --issuer-uri="https://token.actions.githubusercontent.com"
ころむにーころむにー
  1. Allow authentications from the Workload Identity Pool to your Google Cloud Service Account.
gcloud iam service-accounts add-iam-policy-binding "deploy-functions-from-github@${PROJECT_ID}.iam.gserviceaccount.com" \
  --project="${PROJECT_ID}" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}"
ころむにーころむにー
  1. Extract the Workload Identity Provider resource name:
gcloud iam workload-identity-pools providers describe "my-pet-melody" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="github-2" \
  --format="value(name)"

結果をGitHub ActionsのSecretsに登録する。

ころむにーころむにー

https://github.com/marketplace/actions/cloud-functions-deploy

Additionally, the deployment service account must have permissions to act as (impersonate) the runtime service account, which can be achieved by granting the deployment service account "roles/iam.serviceAccountUser" permissions on the runtime service account. If unspecified, the runtime service account is the App Engine Default Service Account PROJECT_ID@appspot.gserviceaccount.com.

gcloud projects add-iam-policy-binding \
  "${PROJECT_ID}" \
  --role="roles/iam.serviceAccountUser" \
  --member="serviceAccount:${PROJECT_ID}@appspot.gserviceaccount.com"
ころむにーころむにー

同じエラー。うーん

Created zip file from 'function' at '/tmp/cfsrc-262af1d3b4b345a0a8891410.zip'
Error: google-github-actions/deploy-cloud-functions failed with: failed to upload zip file: Permission 'iam.serviceAccounts.getAccessToken' denied on resource (or it may not exist).
ころむにーころむにー

トークン作成の権限を付与してみる。

gcloud projects add-iam-policy-binding \
  "${PROJECT_ID}" \
  --role="roles/iam.serviceAccountTokenCreator" \
  --member="serviceAccount:deploy-functions-from-github@${PROJECT_ID}.iam.gserviceaccount.com"

同じエラー。

ころむにーころむにー

CloudFunctionsのデフォルトサービスアカウントに権限を付与してみる。

gcloud projects add-iam-policy-binding \
  "${PROJECT_ID}" \
  --role="roles/iam.serviceAccountTokenCreator" \
  --member="serviceAccount:${PROJECT_ID}@appspot.gserviceaccount.com"

同じエラー。

ころむにーころむにー

5の手順で $REPO の指定方法が間違っているようだった。
my-org/my-repo としないといけないところを、 my-repo だけにしていた。

# ${REPO} is the full repo name including the parent GitHub organization,
# such as "my-org/my-repo".
ころむにーころむにー

指定を変えてポリシーを付与し、再度実行するとエラーの内容が変わった。

Error: google-github-actions/deploy-cloud-functions failed with: failed to PATCH https://cloudfunctions.googleapis.com/v1/projects/colomney-my-pet-melody-dev/locations/asia-east1/functions/detect?updateMask=availableMemoryMb,buildEnvironmentVariables,buildWorkerPool,description,dockerRegistry,dockerRepository,entryPoint,environmentVariables,eventTrigger,httpsTrigger,ingressSettings,kmsKeyName,labels,maxInstances,minInstances,name,network,runtime,secretEnvironmentVariables,secretVolumes,serviceAccountEmail,timeout,vpcConnector,vpcConnectorEgressSettings,sourceUploadUrl: (403) {
  "error": {
    "code": 403,
    "message": "Caller *** is missing permission 'iam.serviceaccounts.actAs' on service account colomney-my-pet-melody-dev@appspot.gserviceaccount.com. Grant the role 'roles/iam.serviceAccountUser' to the caller on the service account colomney-my-pet-melody-dev@appspot.gserviceaccount.com. You can do that by running 'gcloud iam service-accounts add-iam-policy-binding 
ころむにーころむにー

エラーメッセージの通りに権限を付与してみる

gcloud projects add-iam-policy-binding \
  "${PROJECT_ID}" \
  --role="roles/iam.serviceaccounts.actAs" \
  --member="serviceAccount:${PROJECT_ID}@appspot.gserviceaccount.com"

そんな権限はないとエラーが出る

ERROR: Policy modification failed. For a binding with condition, run "gcloud alpha iam policies lint-condition" to identify issues in condition.
ERROR: (gcloud.projects.add-iam-policy-binding) INVALID_ARGUMENT: Role roles/iam.serviceaccounts.actAs is not supported for this resource.
ころむにーころむにー

さらにエラーメッセージ通りに付与してみる。

gcloud iam service-accounts add-iam-policy-binding \
  --project="${PROJECT_ID}" \
  "${PROJECT_ID}@appspot.gserviceaccount.com" \
  --member="serviceAccount:deploy-functions-from-github@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/iam.serviceAccountUser"

これでうまくいった。

ころむにーころむにー

商用環境デプロイするついでにもう一度、成功した一連の手順を記載する。

商用環境のデプロイ

export PROJECT_ID="colomney-my-pet-melody"
gcloud iam service-accounts create "deploy-functions-from-github" \
  --project="${PROJECT_ID}"
gcloud projects add-iam-policy-binding \
  "${PROJECT_ID}" \
  --role="roles/cloudfunctions.admin" \
  --member="serviceAccount:deploy-functions-from-github@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam workload-identity-pools create "github-2" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --display-name="GitHub Actions Pool"
gcloud iam workload-identity-pools describe "github-2" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --format="value(name)"
export GITHUB_ORG="shotaIDE"
gcloud iam workload-identity-pools providers create-oidc "my-pet-melody" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="github-2" \
  --display-name="My Pet Melody repo" \
  --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
  --attribute-condition="assertion.repository_owner == '${GITHUB_ORG}'" \
  --issuer-uri="https://token.actions.githubusercontent.com"
export WORKLOAD_IDENTITY_POOL_ID="projects/340853471718/locations/global/workloadIdentityPools/github-2"
export REPO="${GITHUB_ORG}/my-pet-melody"
gcloud iam service-accounts add-iam-policy-binding "deploy-functions-from-github@${PROJECT_ID}.iam.gserviceaccount.com" \
  --project="${PROJECT_ID}" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}"
gcloud iam workload-identity-pools providers describe "my-pet-melody" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="github-2" \
  --format="value(name)"
gcloud iam service-accounts add-iam-policy-binding \
  --project="${PROJECT_ID}" \
  "${PROJECT_ID}@appspot.gserviceaccount.com" \
  --member="serviceAccount:deploy-functions-from-github@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/iam.serviceAccountUser"
ころむにーころむにー

結論として、以下が原因で詰まっていた。

  • REPO に指定すべき値が間違っていた
  • Functionsの標準サービスアカウントに対し、Functionsのデプロイ用のサービスアカウントとして振る舞う権限を付与する必要があるため、その点注意してコマンドを組み立てる必要がある

ドキュメントはしっかり読みましょうというのと、コマンドの内容はしっかり理解しましょう、という教訓だった。