サービスアカウントキーを用いずにGitHub ActionsからGoogle Cloudと認証する

Workload Identity連携を使って認証し、サービスアカウント情報をgcloudで取得するというワークフローを試してみます。

本エントリはクラスメソッド Google Cloud Advent Calendar 2021の23日目の記事です。

GitHub Actionsでgcloudコマンドラインツールなどを使ってGoogle Cloudのリソースに対してなにか操作したりしたいことってありますよね。そのためにはGoogle Cloudと認証に利用するサービスアカウントキーの管理をどうするかという問題がでてきます。そこで、今回はサービスアカウントキーを使わず、Workload Identity連携を使って認証し、サービスアカウント情報をgcloudで取得するというGitHub Actionsのワークフローを試してみます。

はじめに

本エントリではGoogle CloudのIAM(Identity and Access Management)のプリンシパル、ロール、ポリシーといったベースとなる概念を理解していることを前提としています。IAMの諸概念についてはこのドキュメントが詳しいのでおすすめです。

Workload Identity連携とは

Workload Identity連携は文字通り、Google Cloudと外部のワークロード(workload)とのID連携です。この機能を使うことで、サービスアカウントキーを使わずに、オンプレや他のクラウドサービスのワークロードからGoogle Cloudのリソースへのアクセスを可能にできます。今回はこのWorkload Identity連携を使って、GitHubのリポジトリにGoogle Cloudリソースへのアクセス権を付与して、サービスアカウントキーなしでの認証を試します。

google-github-actions/authとは

google-github-actions/authはGoogle Cloudとの認証時に使うアクションです。使える認証方法はサービスアカウントキーを使ったものと、Workload Identity連携を使ったものの二種類があります。今回はこのアクションを使ったWorkload Identity連携での認証方法を試します。

やってみる

サービスアカウントを作成し、プロバイダの作成などWorkload Identity連携に必要な設定を行います。その後、GitHub ActionsでWorkload Identity連携を用いて認証し、gcloudを使ってサービスアカウントの情報を取得するというワークフローを作成します。

Workload Identity連携の準備

authアクションのドキュメントに記載されている手順に沿って、Workload Identity連携の準備を進めます。

gcloudを使うため、事前準備としてCloud SDKのインストールが必要です。手順についてはドキュメントを参照してください。

まずは環境変数にプロジェクトIDと作成するサービスアカウントの名前を設定します。

export PROJECT_ID="my-project"
export SERVICE_ACCOUNT_NAME="my-service-account"

次にGitHub Actions上で利用するサービスアカウントを作成します。

gcloud iam service-accounts create "${SERVICE_ACCOUNT_NAME}" \
  --project "${PROJECT_ID}"

サービスアカウントには実行する処理に応じて権限を付与する必要があります。今回はサービスアカウント情報の取得のみを実行するので、iam.serviceAccounts.getとその他いくつかの権限のみが含まれる事前定義ロールService Account Userを紐付けます。

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

次にサービスアカウントの一時的な認証情報を作成できるようにするために、IAM Service Account Credentials APIを有効化します。

gcloud services enable iamcredentials.googleapis.com \
  --project "${PROJECT_ID}"

Workload Identityプールを作成します。Workload Identityプールは外部IDとGoogle Cloudとの紐付けを設定したWorkload Identityプロバイダをグループ化し、管理するためのもののようです。

gcloud iam workload-identity-pools create "my-pool" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --display-name="Demo pool"

Workload IdentityプールのIDを取得します。

gcloud iam workload-identity-pools describe "my-pool" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --format="value(name)"

取得したIDを環境変数に設定します。

export WORKLOAD_IDENTITY_POOL_ID="..."

先ほど作成したWorkload Identityプールの中にWorkload Identityプロバイダを作成します。

gcloud iam workload-identity-pools providers create-oidc "my-provider" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="my-pool" \
  --display-name="Demo provider" \
  --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
  --issuer-uri="https://token.actions.githubusercontent.com"

--attribute-mappingにはGitHub ActionsのJWTからGoogle Cloudのattributeへのマッピングを設定します。IAMポリシーの設定等で利用する値についてはマッピングしておく必要があります。今回はsubとactor、repositoryをマッピングしています。JWTの内容についてはGitHubのドキュメントに記載されています。

今回は設定しませんが、--attribute-conditionを使うことで、JWTの値やマッピング後の値などをもとに認証するプロバイダの条件を設定することも可能です。

次に環境変数のREPOにGitHub Actionsを動かすリポジトリを設定し、IAMポリシーバインディングを作成します。このIAMポリシーバインディングでは、memberが指定したサービスアカウント(SERVICE_ACCOUNT_NAME)を利用できるようにするものです。roleではサービスアカウントのアクセストークンなどを取得するための権限がついたロールを指定しています。

export REPO="username/name"

gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
  --project="${PROJECT_ID}" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}"

GitHub Actionsのワークフローで利用するため、Workload Identityプロバイダの名前を取得しておきます。

gcloud iam workload-identity-pools providers describe "my-provider" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="my-pool" \
  --format='value(name)'

これで準備は完了です。

今回はgcloudを使って設定しましたが、Terraformを使った例も公開されています。

GitHub Actionsのワークフローを作成する

先程作成したサービスアカウントでWorkload Identity連携を用いて認証し、google-github-actions/setup-gcloudを使ってgcloudをインストールして、サービスアカウント情報の取得を試してみます。使用するワークフローの定義は次のとおりです。このワークフロー.github/workflows/runGcloud.yamlに保存し、mainブランチにpushします。

name: runGcloud
on:
  workflow_dispatch:
jobs:
  run:
    runs-on: ubuntu-latest
    permissions:
      id-token: 'write'
    steps:
      - id: auth
        uses: google-github-actions/auth@v0
        with:
          workload_identity_provider: '{プロバイダ名}'
          service_account: 'my-service-account@{プロジェクトID}.iam.gserviceaccount.com'
      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v0
      - name: Describe my-service-account
        run: gcloud iam service-accounts describe 'my-service-account@{プロジェクトID}.iam.gserviceaccount.com'

Workflow Identity連携をする上での注意点としては、permissionsにid-token: 'write'を追加する必要があります。そのため、もともとpermissionsを指定してなかった場合にはデフォルトの権限が上書きされるため、必要に応じて権限の追加が必要です。設定可能な権限やデフォルトの権限についてはドキュメントを参照してください。

今回利用したアクションsetup-gcloudauthで指定できる引数については各ドキュメントを参照してください。

ワークフローを実行する

mainブランチにpushされることで、ワークフロー一覧に該当ワークフローが出てくるため、手動実行します。手動実行すると、数秒から数十秒程度で完了します。各ステップの出力を見てみます。

google-github-actions/authの実行結果は次の通りです。明示的に指定していない引数のデフォルト値も表示されているのと、認証情報がファイルに書き込まれている事がわかります。ちなみにサービスアカウントにバインドする際にmemberに指定したリポジトリと異なるリポジトリで実行したり、存在しないサービスアカウントを指定したとしてもここではエラーにはならなかったです。

Run google-github-actions/auth@v0
  with:
    workload_identity_provider: projects/{プロジェクト番号}/locations/global/workloadIdentityPools/my-pool/providers/my-provider
    service_account: my-service-account@{プロジェクトID}.iam.gserviceaccount.com
    create_credentials_file: true
    cleanup_credentials: true
    access_token_lifetime: 3600s
    access_token_scopes: https://www.googleapis.com/auth/cloud-platform
    id_token_include_email: false
Created credentials file at "/home/runner/work/google-cloud-auth-sample/google-cloud-auth-sample/a1826994e98236817215a7df"

続いて、google-github-actions/setup-gcloudの実行結果です。引数のデフォルト値や環境変数とアクション内で実行されたであろうtarのコマンドが表示されてますね。

Run google-github-actions/setup-gcloud@v0
  with:
    version: latest
    export_default_credentials: false
    cleanup_credentials: true
  env:
    CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE: /home/runner/work/google-cloud-auth-sample/google-cloud-auth-sample/d8a676702f60a47af6097152
    GOOGLE_APPLICATION_CREDENTIALS: /home/runner/work/google-cloud-auth-sample/google-cloud-auth-sample/d8a676702f60a47af6097152
    GOOGLE_GHA_CREDS_PATH: /home/runner/work/google-cloud-auth-sample/google-cloud-auth-sample/d8a676702f60a47af6097152
    CLOUDSDK_PROJECT: {プロジェクトID}
    CLOUDSDK_CORE_PROJECT: {プロジェクトID}
    GCP_PROJECT: {プロジェクトID}
    GCLOUD_PROJECT: {プロジェクトID}
    GOOGLE_CLOUD_PROJECT: {プロジェクトID}
/usr/bin/tar xz --warning=no-unknown-keyword --overwrite -C /home/runner/work/_temp/aba23c62-f7c9-49bc-8368-997d52c6fcba -f /home/runner/work/_temp/da334eea-9720-4b23-b24c-a79c30e7143c

最後に、gcloudを使ったサービスアカウント情報の取得結果です。環境変数とともに、実行結果としてアカウント情報が表示されました。無事サービスアカウントの認証ができていそうです。

Run gcloud iam service-accounts describe 'my-service-account@{プロジェクトID}.iam.gserviceaccount.com'
  gcloud iam service-accounts describe 'my-service-account@{プロジェクトID}.iam.gserviceaccount.com'
  shell: /usr/bin/bash -e {0}
  env:
    CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE: /home/runner/work/google-cloud-auth-sample/google-cloud-auth-sample/d8a676702f60a47af6097152
    GOOGLE_APPLICATION_CREDENTIALS: /home/runner/work/google-cloud-auth-sample/google-cloud-auth-sample/d8a676702f60a47af6097152
    GOOGLE_GHA_CREDS_PATH: /home/runner/work/google-cloud-auth-sample/google-cloud-auth-sample/d8a676702f60a47af6097152
    CLOUDSDK_PROJECT: {プロジェクトID}
    CLOUDSDK_CORE_PROJECT: {プロジェクトID}
    GCP_PROJECT: {プロジェクトID}
    GCLOUD_PROJECT: {プロジェクトID}
    GOOGLE_CLOUD_PROJECT: {プロジェクトID}
    CLOUDSDK_METRICS_ENVIRONMENT: github-actions-setup-gcloud
email: my-service-account@{プロジェクトID}.iam.gserviceaccount.com
etag: MDEwMjE5MjA=
name: projects/{プロジェクトID}/serviceAccounts/my-service-account@{プロジェクトID}.iam.gserviceaccount.com
oauth2ClientId: {クライアントID}
projectId: {プロジェクトID}
uniqueId: {クライアントID}

さいごに

今回はGitHub ActionsのワークフローからWorkload Identity連携での認証を試してみました。予め専用の設定が必要にはなりますが、キーの扱いが不要なので総合的に見るとかなり便利だと思います。一方でアクセス権を付与してしまうとキー無しでの認証が可能になるので、アクセス権の付与先には注意が必要です。サービスアカウントの紐付ける対象のリポジトリやユーザー名(もしくはorganization)は確認が必要です。必要に応じてWorkflow Identityプロバイダの属性条件で制限しておくのも良いと思います。また、今回はGitHub Actionsでgcloudを使うためにgoogle-github-actions/setup-gcloudを使いましたが、google-github-actions/deploy-cloudrunのようにCloud Runのデプロイなど特定の機能に特化したアクションもあります。Google GitHub Actionsから目的に応じた必要なアクションを探してみると良さそうです。

参考