【Google Cloud】GitHub Actionsで認証するためのシェル関数を作る

Code

はじまり

リサちゃん
リサちゃん

この認証手順、長すぎじゃね!?

135ml
135ml

関数にしてしまおう。

Google Cloudを使ったCI/CD認証方法の選択肢

Google Cloudのリソースに対してCI/CDパイプラインから操作を行う場合、認証方法として主に2つの選択肢があります。

  1. サービスアカウントのJSON秘密鍵を使用する方法
    • 簡単に設定できる
    • キーをダウンロードしてGitHub Secretsに保存
    • セキュリティリスクが高い(キーが漏洩する可能性)
    • 定期的なキーのローテーションが必要
  2. Workload Identity連携を使用する方法
    • JSONキーを使用しない
    • 一時的な認証情報を使用するため安全
    • キーのローテーションが不要
    • 設定が少し複雑

今回は2つ目の「Workload Identity連携」を使って、GitHub Actionsから安全にGoogle Cloudリソースにアクセスするための設定方法を解説します。さらに、この設定を簡単に行えるようにシェル関数化します。

Workload Identity連携とは?

Workload Identity連携は、外部IDプロバイダ(GitHub Actionsなど)からの一時的な認証情報を使用して、Google Cloudリソースにアクセスする仕組みです。これにより、長期間有効なサービスアカウントキーを使用せずに、安全に認証を行うことができます。

主なメリットは以下の通りです。

  • サービスアカウントキーを作成・管理する必要がない
  • 短期間だけ有効な認証情報を使用するため、セキュリティリスクが低減
  • キーのローテーションが不要
  • きめ細かいアクセス制御が可能

Workload Identity連携の仕組み

Workload Identity連携の基本的な仕組みは以下の通りです。

  1. GitHub Actionsのワークフローが実行されると、GitHubはOIDCトークンを発行
  2. このOIDCトークンをGoogle Cloudに送信
  3. Google CloudはOIDCトークンを検証し、一時的な認証情報を発行
  4. この一時的な認証情報を使用してGoogle Cloudリソースにアクセス

この仕組みを実現するために、Google Cloud側で「Workload Identityプール」と「プロバイダ」を設定し、GitHub側でワークフローを設定する必要があります。

Workload Identity連携を設定するためのシェル関数

Workload Identity連携の設定は複数のステップがあり、少し複雑です。そこで、これらの設定を簡単に行えるようにシェル関数化しました。

以下の4つの主要な関数を作成しました。

  1. create_workload_identity_pool – Workload Identityプールを作成する関数
  2. create_oidc_workload_identity_pool_provider – OIDCプロバイダを作成する関数
  3. create_oidc_workload_identity_pool_provider_for_github_actions – GitHub Actions専用のOIDCプロバイダを作成する関数
  4. add_workload_identity_binding_to_service_account_on_gcloud – サービスアカウントにWorkload Identityバインディングを追加する関数

それぞれの関数について詳しく見ていきましょう。

Workload Identityプールを作成する関数

まず、Workload Identityプールを作成する関数です。これはGoogle Cloud上で外部IDプロバイダからの認証情報を受け付けるための「プール」を作成します。

function create_workload_identity_pool() {
  # 関数名を取得
  local func_name="${FUNCNAME[0]}"
  send_discord_notification "Workload Identity Poolを作成するよ!"

  # ヘルプ表示
  if [[ "$1" == "--help" ]]; then
    echo "[INFO] ${func_name}: 使用方法"
    echo "  ${func_name} <WORKLOAD_IDENTITY_POOL> <PROJECT_ID> [--location=LOCATION] [--description=DESCRIPTION]"
    echo ""
    echo "引数:"
    echo "  WORKLOAD_IDENTITY_POOL  作成するワークロードアイデンティティプールの名前"
    echo "  PROJECT_ID              Google CloudプロジェクトのプロジェクトID"
    echo "  --location              省略可能: ロケーション (デフォルト: global)"
    echo "  --description           省略可能: プールの説明"
    echo ""
    echo "使用例:"
    echo "  ${func_name} my-pool my-project-id"
    echo "  ${func_name} my-pool my-project-id --location=global"
    echo "  ${func_name} my-pool my-project-id --description=\\\\"My workload identity pool\\\\""
    echo "[INFO] ${func_name}: Detail of gcloud is here: <https://cloud.google.com/sdk/gcloud/reference/iam/workload-identity-pools/create>"
    return 0
  fi

  # パラメータのバリデーション
  if [[ -z "$1" ]]; then
    echo "[ERROR] ${func_name}: WORKLOAD_IDENTITY_POOL が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$2" ]]; then
    echo "[ERROR] ${func_name}: PROJECT_ID が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  local pool_id="$1"
  local project_id="$2"
  local location="global"
  local description=""

  # オプションの解析
  shift 2
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --location=*)
        location="${1#*=}"
        shift
        ;;
      --description=*)
        description="--description=${1#*=}"
        shift
        ;;
      *)
        echo "[ERROR] ${func_name}: 不明なオプション: $1" >&2
        echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
        return 1
        ;;
    esac
  done

  # 実行するコマンドを表示
  echo "[INFO] ${func_name}: 実行するコマンド: gcloud iam workload-identity-pools create ${pool_id} --project=${project_id} --location=${location} ${description}"

  # コマンド実行
  echo ""
  echo "======= Workload Identity Pools ============================================================================"
  if ! gcloud iam workload-identity-pools create "${pool_id}" --project="${project_id}" --location="${location}" ${description}; then
    echo "[ERROR] ${func_name}: ワークロードアイデンティティプールの作成に失敗しました。" >&2
    send_discord_notification_about_gciam "失敗…" "Workload Identity Poolを作成できなかったよ…" "red"
    return 1
  fi
  echo "============================================================================================================"
  echo ""

  echo "[INFO] ${func_name}: ワークロードアイデンティティプール '${pool_id}' をプロジェクト '${project_id}' のロケーション '${location}' に正常に作成しました。"
  send_discord_notification_about_gciam "作成したよ!" "Workload Identity Poolを作成したよ!" "green"
  return 0
}

この関数は以下のような特徴があります。

  • -helpオプションでヘルプを表示
  • 必須パラメータのバリデーション
  • オプションパラメータの解析
  • 実行コマンドの表示
  • エラーハンドリング
  • Discord通知機能(オプション)

使用例としては以下のような感じです。

create_workload_identity_pool github-pool my-project-id --description="Pool for GitHub Actions"

OIDCプロバイダを作成する関数

次に、OIDCプロバイダを作成する関数です。これは、特定のOIDCプロバイダ(例:GitHub)からの認証情報を受け付けるための設定を行います。

function create_oidc_workload_identity_pool_provider() {
  # 関数名を取得
  local func_name="${FUNCNAME[0]}"
  send_discord_notification "OIDC Workload Identity Pool Providerを作成するよ!"

  # ヘルプ表示
  if [[ "$1" == "--help" ]]; then
    echo "[INFO] ${func_name}: 使用方法"
    echo "  ${func_name} <PROVIDER> <PROJECT_ID> <WORKLOAD_IDENTITY_POOL> <ISSUER_URI> <ATTRIBUTE_MAPPING> <ATTRIBUTE_CONDITION> [--location=LOCATION]"
    echo ""
    echo "引数:"
    echo "  PROVIDER                作成するプロバイダーの名前"
    echo "  PROJECT_ID              Google CloudプロジェクトのプロジェクトID"
    echo "  WORKLOAD_IDENTITY_POOL  プロバイダーを作成するワークロードアイデンティティプールの名前"
    echo "  ISSUER_URI              OIDCプロバイダーの発行者URI"
    echo "  ATTRIBUTE_MAPPING       属性マッピング (形式: KEY=VALUE,...)"
    echo "  ATTRIBUTE_CONDITION     属性条件"
    echo "  --location              省略可能: ロケーション (デフォルト: global)"
    echo ""
    echo "使用例:"
    echo "  ${func_name} my-provider my-project-id my-pool <https://accounts.google.com> \\\\"google.subject=assertion.sub\\\\" \\\\"assertion.sub.startsWith('abc')\\\\""
    echo "  ${func_name} my-provider my-project-id my-pool <https://accounts.google.com> \\\\"google.subject=assertion.sub,google.groups=assertion.groups\\\\" \\\\"assertion.aud == 'my-audience'\\\\""
    echo "[INFO] ${func_name}: Detail of gcloud is here: <https://cloud.google.com/sdk/gcloud/reference/iam/workload-identity-pools/providers/create-oidc>"
    return 0
  fi

  # パラメータのバリデーション
  if [[ -z "$1" ]]; then
    echo "[ERROR] ${func_name}: PROVIDER が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$2" ]]; then
    echo "[ERROR] ${func_name}: PROJECT_ID が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$3" ]]; then
    echo "[ERROR] ${func_name}: WORKLOAD_IDENTITY_POOL が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$4" ]]; then
    echo "[ERROR] ${func_name}: ISSUER_URI が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$5" ]]; then
    echo "[ERROR] ${func_name}: ATTRIBUTE_MAPPING が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$6" ]]; then
    echo "[ERROR] ${func_name}: ATTRIBUTE_CONDITION が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  local provider="$1"
  local project_id="$2"
  local pool_id="$3"
  local issuer_uri="$4"
  local attribute_mapping="$5"
  local attribute_condition="$6"
  local location="global"

  # オプションの解析
  shift 6
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --location=*)
        location="${1#*=}"
        shift
        ;;
      *)
        echo "[ERROR] ${func_name}: 不明なオプション: $1" >&2
        echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
        return 1
        ;;
    esac
  done

  # 実行するコマンドを表示
  echo "[INFO] ${func_name}: 実行するコマンド: gcloud iam workload-identity-pools providers create-oidc ${provider} --project=${project_id} --location=${location} --workload-identity-pool=${pool_id} --issuer-uri=${issuer_uri} --attribute-mapping=\\\\"${attribute_mapping}\\\\" --attribute-condition=\\\\"${attribute_condition}\\\\""

  # コマンド実行
  echo ""
  echo "======= Workload Identity Pool Providers =================================================================="
  if ! gcloud iam workload-identity-pools providers create-oidc "${provider}" \\\\
       --project="${project_id}" \\\\
       --location="${location}" \\\\
       --workload-identity-pool="${pool_id}" \\\\
       --issuer-uri="${issuer_uri}" \\\\
       --attribute-mapping="${attribute_mapping}" \\\\
       --attribute-condition="${attribute_condition}"; then
    echo "[ERROR] ${func_name}: OIDCプロバイダー '${provider}' の作成に失敗しました。" >&2
    send_discord_notification_about_gciam "失敗…" "OIDC Workload Identity Pool Providerを作成できなかったよ…" "red"
    return 1
  fi
  echo "============================================================================================================"
  echo ""

  echo "[INFO] ${func_name}: OIDCプロバイダー '${provider}' をワークロードアイデンティティプール '${pool_id}' に正常に作成しました。"
  send_discord_notification_about_gciam "作成したよ!" "OIDC Workload Identity Pool Providerを作成したよ!" "green"
  return 0
}

この関数は汎用的なOIDCプロバイダを作成するためのものです。GitHub Actions以外のOIDCプロバイダ(例:GitLab、Azure DevOps)を使用する場合にも利用できます。使用例としては以下のような感じです。

create_oidc_workload_identity_pool_provider gitlab-provider my-project-id github-pool <https://gitlab.com> "google.subject=assertion.sub" "assertion.namespace_id=='12345'"

GitHub Actions専用のOIDCプロバイダを作成する関数

最後に、GitHub Actions専用のOIDCプロバイダを作成する関数です。これは、前述の汎用関数をラップして、GitHub Actions特有の設定を簡単に行えるようにしたものです。

function create_oidc_workload_identity_pool_provider_for_github_actions() {
  # 関数名を取得
  local func_name="${FUNCNAME[0]}"
  send_discord_notification "GitHub Actions用のOIDC Workload Identity Pool Providerを作成するよ!"

  # ヘルプ表示
  if [[ "$1" == "--help" ]]; then
    echo "[INFO] ${func_name}: 使用方法"
    echo "  ${func_name} <PROVIDER_ID> <PROJECT_ID> <POOL_ID> <REPOSITORY_OWNER> [--location=LOCATION]"
    echo ""
    echo "引数:"
    echo "  PROVIDER_ID       作成するプロバイダーのID"
    echo "  PROJECT_ID        Google CloudプロジェクトのプロジェクトID"
    echo "  POOL_ID           プロバイダーを作成するワークロードアイデンティティプールのID"
    echo "  REPOSITORY_OWNER  GitHubリポジトリのオーナー名(組織名またはユーザー名)"
    echo "  --location        省略可能: ロケーション (デフォルト: global)"
    echo ""
    echo "使用例:"
    echo "  ${func_name} github-provider my-project-id my-pool my-org"
    echo "  ${func_name} github-provider my-project-id my-pool my-org --location=global"
    echo "[INFO] ${func_name}: Detail of gcloud is here: <https://cloud.google.com/sdk/gcloud/reference/iam/workload-identity-pools/providers/create-oidc>"
    return 0
  fi

  # パラメータのバリデーション
  if [[ -z "$1" ]]; then
    echo "[ERROR] ${func_name}: PROVIDER_ID が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$2" ]]; then
    echo "[ERROR] ${func_name}: PROJECT_ID が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$3" ]]; then
    echo "[ERROR] ${func_name}: POOL_ID が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$4" ]]; then
    echo "[ERROR] ${func_name}: REPOSITORY_OWNER が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  local provider_id="$1"
  local project_id="$2"
  local pool_id="$3"
  local repo_owner="$4"
  local location="global"

  # オプションの解析
  shift 4
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --location=*)
        location="${1#*=}"
        shift
        ;;
      *)
        echo "[ERROR] ${func_name}: 不明なオプション: $1" >&2
        echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
        return 1
        ;;
    esac
  done

  # 属性マッピングと条件の設定
  local attribute_mapping="google.subject=assertion.sub,attribute.repository=assertion.repository"
  local attribute_condition="assertion.repository_owner=='${repo_owner}'"

  # 実行するコマンドを表示
  echo "[INFO] ${func_name}: 実行するコマンド: gcloud iam workload-identity-pools providers create-oidc ${provider_id} --project=${project_id} --location=${location} --workload-identity-pool=${pool_id} --issuer-uri=https://token.actions.githubusercontent.com/ --attribute-mapping=\\\\"${attribute_mapping}\\\\" --attribute-condition=\\\\"${attribute_condition}\\\\""

  # コマンド実行
  echo ""
  echo "======= Workload Identity Pool Providers =================================================================="
  if ! gcloud iam workload-identity-pools providers create-oidc "${provider_id}" \\\\
       --project="${project_id}" \\\\
       --location="${location}" \\\\
       --workload-identity-pool="${pool_id}" \\\\
       --issuer-uri="<https://token.actions.githubusercontent.com/>" \\\\
       --attribute-mapping="${attribute_mapping}" \\\\
       --attribute-condition="${attribute_condition}"; then
    echo "[ERROR] ${func_name}: GitHub Actions用のOIDCプロバイダー '${provider_id}' の作成に失敗しました。" >&2
    send_discord_notification_about_gciam "失敗…" "GitHub Actions用のOIDC Workload Identity Pool Providerを作成できなかったよ…" "red"
    return 1
  fi
  echo "============================================================================================================"
  echo ""

  # プロバイダー名を取得
  local provider_name="projects/${project_id}/locations/${location}/workloadIdentityPools/${pool_id}/providers/${provider_id}"

  echo "[INFO] ${func_name}: GitHub Actions用のOIDCプロバイダー '${provider_id}' を正常に作成しました。"
  send_discord_notification_about_gciam "作成したよ!" "GitHub Actions用のOIDC Workload Identity Pool Providerを作成したよ!" "green"

  # GitHub Actions用のYAML情報を表示
  echo ""
  echo "[INFO] ${func_name}: 以下はGitHub Actions用のYAMLファイルに記述するための設定例です:"
  echo "----------------------------------------------------------------"
  echo "env:"
  echo "  GCLOUD_PROJECT_NUMBER: \\\\${{ secrets.GCLOUD_PROJECT_NUMBER }} # プロジェクト番号を設定してください"
  echo "  GCLOUD_POOL_ID: ${pool_id}"
  echo "  GCLOUD_PROVIDER_ID: ${provider_id}"
  echo "  GCLOUD_SERVICE_ACCOUNT_EMAIL: \\\\${{ secrets.GCLOUD_SERVICE_ACCOUNT_EMAIL }} # サービスアカウントのメールアドレスを設定してください"
  echo "jobs:"
  echo "  test:"
  echo "    runs-on: ubuntu-latest"
  echo "    steps:"
  echo "      - id: 'gcloud_auth'"
  echo "        name: 'Authenticate to Google Cloud'"
  echo "        uses: 'google-github-actions/auth@v2'"
  echo "        with:"
  echo "          create_credentials_file: true"
  echo "          workload_identity_provider: 'projects/\\\\${{ env.GCLOUD_PROJECT_NUMBER }}/locations/${location}/workloadIdentityPools/${pool_id}/providers/${provider_id}'"
  echo "          service_account: '\\\\${{ env.GCLOUD_SERVICE_ACCOUNT_EMAIL }}'"
  echo "----------------------------------------------------------------"
  echo ""
  echo "[INFO] ${func_name}: GitHub Secretsにプロジェクト番号とサービスアカウントのメールアドレスを設定してください。"
  echo "[INFO] ${func_name}: 次に、サービスアカウントにリポジトリからのアクセスを許可するためにIAMポリシーバインディングを追加する必要があります。"

  return 0
}

この関数は、GitHub Actions専用の設定を簡単に行えるようにしたものです。特に以下の点が便利です。

  • GitHub Actions特有の設定(発行者URI、属性マッピング)が自動的に設定される
  • リポジトリオーナー(組織名またはユーザー名)だけを指定すれば、適切な属性条件が設定される
  • 設定後、GitHub Actionsのワークフロー設定例が表示される

使用例としては以下のような感じです。

create_oidc_workload_identity_pool_provider_for_github_actions github-provider my-project-id github-pool my-organization

サービスアカウントにWorkload Identityバインディングを追加する関数

GitHub Actionsからサービスアカウントにアクセスできるようにするために、サービスアカウントにroles/iam.workloadIdentityUserロールを付与する必要があります。以下は、この設定を簡単に行うための関数です。

function add_workload_identity_binding_to_service_account_on_gcloud() {
  # 関数名を取得
  local func_name="${FUNCNAME[0]}"
  send_discord_notification "サービスアカウントにWorkload Identityバインディングを追加するよ!"

  # ヘルプ表示
  if [[ "$1" == "--help" ]]; then
    echo "[INFO] ${func_name}: 使用方法"
    echo "  ${func_name} <SERVICE_ACCOUNT_EMAIL> <PROJECT_NUMBER> <POOL_ID> <REPOSITORY_OWNER> <REPOSITORY_NAME> [--provider-id=PROVIDER_ID] [--condition=KEY=VALUE,...] [--condition-from-file=PATH_TO_FILE]"
    echo ""
    echo "引数:"
    echo "  SERVICE_ACCOUNT_EMAIL  IAMポリシーバインディングを追加するサービスアカウントのメールアドレス"
    echo "  PROJECT_NUMBER         プロジェクト番号"
    echo "  POOL_ID                ワークロードアイデンティティプールのID"
    echo "  REPOSITORY_OWNER       リポジトリのオーナー"
    echo "  REPOSITORY_NAME        リポジトリの名前"
    echo "  --provider-id          省略可能: プロバイダーID (GitHub Actions用YAMLの生成に使用)"
    echo "  --condition            省略可能: IAMの条件 (形式: KEY=VALUE,...)"
    echo "  --condition-from-file  省略可能: 条件を含むファイルへのパス"
    echo ""
    echo "使用例:"
    echo "  ${func_name} my-sa@my-project.iam.gserviceaccount.com 123456789012 my-pool my-org my-repo"
    echo "  ${func_name} my-sa@my-project.iam.gserviceaccount.com 123456789012 my-pool my-org my-repo --provider-id=github"
    echo "  ${func_name} my-sa@my-project.iam.gserviceaccount.com 123456789012 my-pool my-org my-repo --condition=\\\\"title=test,expression=request.time < timestamp('2023-01-01T00:00:00Z')\\\\""
    echo "[INFO] ${func_name}: [INFO] Detail of gcloud is here: <https://cloud.google.com/sdk/gcloud/reference/iam/service-accounts/add-iam-policy-binding>"
    return 0
  fi

  # パラメータのバリデーション
  if [[ -z "$1" ]]; then
    echo "[ERROR] ${func_name}: SERVICE_ACCOUNT_EMAIL が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$2" ]]; then
    echo "[ERROR] ${func_name}: PROJECT_NUMBER が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$3" ]]; then
    echo "[ERROR] ${func_name}: POOL_ID が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$4" ]]; then
    echo "[ERROR] ${func_name}: REPOSITORY_OWNER が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  if [[ -z "$5" ]]; then
    echo "[ERROR] ${func_name}: REPOSITORY_NAME が指定されていません。" >&2
    echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
    return 1
  fi

  local service_account_email="$1"
  local project_number="$2"
  local pool_id="$3"
  local repo_owner="$4"
  local repo_name="$5"
  local provider_id=""
  local condition=""
  local condition_from_file=""

  # principalSet の生成
  local principal_set="principalSet://iam.googleapis.com/projects/${project_number}/locations/global/workloadIdentityPools/${pool_id}/attribute.repository/${repo_owner}/${repo_name}"

  # オプションの解析
  shift 5
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --provider-id=*)
        provider_id="${1#*=}"
        shift
        ;;
      --condition=*)
        condition="--condition=${1#*=}"
        shift
        ;;
      --condition-from-file=*)
        condition_from_file="--condition-from-file=${1#*=}"
        shift
        ;;
      *)
        echo "[ERROR] ${func_name}: 不明なオプション: $1" >&2
        echo "[ERROR] ${func_name}: 使用方法を確認するには --help を使用してください。" >&2
        return 1
        ;;
    esac
  done

  # condition と condition-from-file の両方が指定されていないか確認
  if [[ -n "${condition}" && -n "${condition_from_file}" ]]; then
    echo "[ERROR] ${func_name}: --condition と --condition-from-file は同時に指定できません。" >&2
    return 1
  fi

  # 実行するコマンドを表示
  echo "[INFO] ${func_name}: 実行するコマンド: gcloud iam service-accounts add-iam-policy-binding ${service_account_email} --member='${principal_set}' --role=roles/iam.workloadIdentityUser ${condition} ${condition_from_file}"

  # コマンド実行
  echo ""
  echo "======= IAM Policy Binding ================================================================================="
  if ! gcloud iam service-accounts add-iam-policy-binding "${service_account_email}" \\\\
       --member="${principal_set}" \\\\
       --role="roles/iam.workloadIdentityUser" \\\\
       ${condition} \\\\
       ${condition_from_file}; then
    echo "[ERROR] ${func_name}: サービスアカウント '${service_account_email}' へのワークロードアイデンティティバインディング追加に失敗しました。" >&2
    send_discord_notification_about_gciam "失敗…" "サービスアカウントにWorkload Identityバインディングを追加できなかったよ…" "red"
    return 1
  fi
  echo "============================================================================================================"
  echo ""

  echo "[INFO] ${func_name}: サービスアカウント '${service_account_email}' に ${repo_owner}/${repo_name} リポジトリからのアクセスを正常に設定しました。"
  send_discord_notification_about_gciam "IAMポリシーバインディングを追加したよ!" "サービスアカウントにWorkload Identityバインディングを追加したよ!" "green"

  # GitHub Actions用のYAML情報を表示
  echo ""
  echo "[INFO] ${func_name}: 以下はGitHub Actions用のYAMLファイルに記述するための設定例です:"
  echo "----------------------------------------------------------------"
  echo "env:"
  echo "  GCLOUD_PROJECT_NUMBER: \\\\${{ secrets.GCLOUD_PROJECT_NUMBER }}"
  echo "  GCLOUD_POOL_ID: \\\\${{ secrets.GCLOUD_POOL_ID }}"
  if [[ -n "${provider_id}" ]]; then
    echo "  GCLOUD_PROVIDER_ID: \\\\${{ secrets.GCLOUD_PROVIDER_ID }}"
  else
    echo "  GCLOUD_PROVIDER_ID: \\\\${{ secrets.GCLOUD_PROVIDER_ID }} # プロバイダーIDを設定してください"
  fi
  echo "  GCLOUD_SERVICE_ACCOUNT_EMAIL: \\\\${{ secrets.GCLOUD_SERVICE_ACCOUNT_EMAIL }}"
  echo "jobs:"
  echo "  test:"
  echo "    runs-on: ubuntu-latest"
  echo "    steps:"
  echo "      - id: 'gcloud_auth'"
  echo "        name: 'Authenticate to Google Cloud'"
  echo "        uses: 'google-github-actions/auth@v2'"
  echo "        with:"
  echo "          create_credentials_file: true"
  echo "          workload_identity_provider: 'projects/\\\\${{ env.GCLOUD_PROJECT_NUMBER }}/locations/global/workloadIdentityPools/\\\\${{ env.GCLOUD_POOL_ID }}/providers/\\\\${{ env.GCLOUD_PROVIDER_ID }}'"
  echo "          service_account: '\\\\${{ env.GCLOUD_SERVICE_ACCOUNT_EMAIL }}'"
  echo "----------------------------------------------------------------"
  echo ""
  echo "[INFO] ${func_name}: GitHub SecretsにプロジェクトID、プールID、プロバイダーID、サービスアカウントのメールアドレスを設定してください。"

  return 0
}

この関数は、サービスアカウントにWorkload Identityバインディングを追加するための関数です。特に以下の点が便利です。

  • リポジトリオーナーとリポジトリ名を指定するだけで、適切なprincipalSetが自動的に生成される
  • 条件付きのIAMポリシーバインディングも設定可能
  • 設定後、GitHub Actionsのワークフロー設定例が表示される

使用例としては以下のような感じです。

add_workload_identity_binding_to_service_account_on_gcloud github-actions-sa@my-project-id.iam.gserviceaccount.com 123456789012 github-pool my-organization my-repository --provider-id=github-provider

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

Workload Identity連携を使用するには、サービスアカウントも必要です。以下は、サービスアカウントを作成し、適切なロールを付与する関数です。

function create_gcloud_service_account() {
  local func_name="${FUNCNAME[0]}"
  send_discord_notification "サービスアカウントを作成するよ!"

  # Check for help parameter
  if [[ "$1" == "--help" ]]; then
    echo "[INFO] ${func_name}: Usage: create_gcloud_service_account <SERVICE_ACCOUNT_ID> <PROJECT_ID> <ROLE>"
    echo "[INFO] ${func_name}: Example: create_gcloud_service_account my-service-account my-project-id roles/storage.admin"
    echo "[INFO] ${func_name}: [INFO] Detail of gcloud is here: <https://cloud.google.com/sdk/gcloud/reference/iam/service-accounts/create>"
    echo "[INFO] ${func_name}: [INFO] Detail of gcloud is here: <https://cloud.google.com/sdk/gcloud/reference/iam/service-accounts/add-iam-policy-binding>"
    return 0
  fi

  # Validate number of parameters
  if [[ "$#" -ne 3 ]]; then
    echo "[ERROR] ${func_name}: Invalid number of arguments. Use --help for usage."
    return 1
  fi

  local SERVICE_ACCOUNT_ID="$1"
  local PROJECT_ID="$2"
  local ROLE="$3"

  # Create the service account
  echo "[INFO] ${func_name}: Creating service account '${SERVICE_ACCOUNT_ID}'..."
  gcloud iam service-accounts create "${SERVICE_ACCOUNT_ID}"
  if [[ "$?" -ne 0 ]]; then
    send_discord_notification_about_gciam "失敗…" "サービスアカウントを作成できなかったよ…" "red"
    echo "[ERROR] ${func_name}: Failed to create service account '${SERVICE_ACCOUNT_ID}'."
    return 1
  fi

  # Bind the IAM policy with the provided role
  echo "[INFO] ${func_name}: Adding IAM policy binding for project '${PROJECT_ID}' with role '${ROLE}'..."
  gcloud projects add-iam-policy-binding "${PROJECT_ID}" \\\\
    --member="serviceAccount:${SERVICE_ACCOUNT_ID}@${PROJECT_ID}.iam.gserviceaccount.com" \\\\
    --role="${ROLE}"
  if [[ "$?" -ne 0 ]]; then
    send_discord_notification_about_gciam "失敗…" "IAMポリシーをバインドできなかったよ…" "red"
    echo "[ERROR] ${func_name}: Failed to add IAM policy binding for project '${PROJECT_ID}'."
    return 1
  fi

  send_discord_notification_about_gciam "バインドしたよ!" "IAMポリシーをバインドしたよ!" "green"
  echo "[INFO] ${func_name}: Service account '${SERVICE_ACCOUNT_ID}' created and policy binding added successfully."
}

この関数は、サービスアカウントを作成し、指定したロールをプロジェクトに対して付与します。使用例としては以下のような感じです。

create_gcloud_service_account github-actions-sa my-project-id roles/storage.admin

実際の使用手順

これらの関数を使って、GitHub ActionsからGoogle Cloudリソースにアクセスするための設定を行う手順を説明します。

1. シェル関数を読み込む

まず、上記のシェル関数をファイルに保存し、読み込みます。

# 関数を保存したファイルを読み込む
source ./gcloud_workload_identity.sh

2. Workload Identityプールを作成する

create_workload_identity_pool github-pool my-project-id --description="Pool for GitHub Actions"

3. GitHub Actions用のOIDCプロバイダを作成する

create_oidc_workload_identity_pool_provider_for_github_actions github-provider my-project-id github-pool my-organization

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

create_gcloud_service_account github-actions-sa my-project-id roles/storage.admin

5. サービスアカウントにWorkload Identity連携のロールを付与する

GitHub Actionsからサービスアカウントにアクセスできるようにするために、サービスアカウントにroles/iam.workloadIdentityUserロールを付与します。

# プロジェクト番号を取得
PROJECT_NUMBER=$(gcloud projects describe my-project-id --format="value(projectNumber)")

# サービスアカウントにWorkload Identity Userロールを付与
add_workload_identity_binding_to_service_account_on_gcloud \\\\
  github-actions-sa@my-project-id.iam.gserviceaccount.com \\\\
  ${PROJECT_NUMBER} \\\\
  github-pool \\\\
  my-organization \\\\
  my-repository \\\\
  --provider-id=github-provider

この関数を使用することで、複雑なprincipalSetの構築を自動化し、エラーを防ぐことができます。また、GitHub Actions用のYAML設定例も自動的に表示されます。

6. GitHub Actionsのワークフローを設定する

最後に、GitHub Actionsのワークフローファイル(.github/workflows/deploy.ymlなど)に認証設定を追加します。以下がワークフローのサンプルです。

jobs:
  test:
    runs-on: ubuntu-latest
    if: contains(github.event.head_commit.message, '[skip ci]') == false
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Set up Golang
        uses: actions/setup-go@v5
        with:
          go-version-file: 'go.mod'
      - id: 'gcloud_auth'
        name: 'Authenticate to Google Cloud'
        uses: 'google-github-actions/auth@v2'
        with:
          create_credentials_file: true
          workload_identity_provider: 'projects/${{ env.GCLOUD_PROJECT_NUMBER }}/locations/global/workloadIdentityPools/${{ env.GCLOUD_POOL_ID }}/providers/${{ env.GCLOUD_PROVIDER_ID }}'
          service_account: '${{ env.GCLOUD_SERVICE_ACCOUNT_EMAIL }}'
      - name: 'Set up Cloud SDK'
        uses: 'google-github-actions/setup-gcloud@v2'
      - name: 'Use gcloud CLI'
        run: 'gcloud info'

この設定により、GitHub Actionsのワークフローが実行されると、以下のような流れで認証が行われます。

  1. GitHub Actionsが実行環境のOIDCトークンを取得
  2. google-github-actions/authアクションがOIDCトークンをGoogle Cloudに送信
  3. Google CloudがOIDCトークンを検証し、一時的な認証情報を発行
  4. 一時的な認証情報を使用してGoogle Cloudリソースにアクセス

実際に認証出来るとログで確認出来る

実際に認証出来ると、GitHub Actionsの実行ログで確認することが出来ます。

セキュリティ上の注意点

Workload Identity連携を使用する際には、以下の点に注意します。

  1. 属性条件の設定:必ず適切な属性条件を設定して、特定のリポジトリやブランチからのアクセスのみを許可するようにします。
  2. 最小権限の原則:サービスアカウントには必要最小限の権限のみを付与します。
  3. 監査ログの確認:定期的に監査ログを確認して、不審なアクセスがないか確認します。
  4. 定期的な見直し:定期的に設定を見直し、不要なアクセス権限を削除します。

まとめ

この記事では、GitHub ActionsからGoogle Cloudリソースにアクセスするための安全な方法として、Workload Identity連携を使用する方法を紹介しました。また、この設定を簡単に行えるようにするためのシェル関数も提供しました。

Workload Identity連携を使用することで、以下のメリットが得られます。

  • サービスアカウントキーを使用しないため、セキュリティリスクが低減
  • キーのローテーションが不要
  • きめ細かいアクセス制御が可能
  • 監査ログによる追跡が容易

これらのシェル関数を使用して、簡単かつ安全にCI/CDパイプラインを構築出来そうです。

おしまい

リサちゃん
リサちゃん

だいぶ手数が減った気がする

135ml
135ml

作るの大変だった・・・。

以上になります!

コメント

タイトルとURLをコピーしました