AWSへGitHub ActionsからGoogle Cloudの認証経由でアクセスする

本記事では、GitHub ActionsからGoogle CloudのService Accountの認証情報を利用して、AWSリソースにアクセスする方法について説明します。
これは、複数のクラウドプロバイダーを利用する環境において、認証基盤を一元化するための手法の一つです。

## はじめに

AWSとGoogle Cloudの両方を利用する環境では、GitHub Actionsからそれぞれのクラウドプロバイダーへの接続設定が必要になります。
これは以下のような課題を引き起こす可能性があります。

- 各クラウドプロバイダーごとに個別の認証設定が必要
- 許可するGitHubリポジトリの設定を両環境で同期する必要性
- 設定の不一致によるセキュリティリスク
- 認証設定の管理コストの増加

これらの課題に対して、Google Cloudの認証基盤を中心に据えることで、認証の一元管理が可能になります。

この記事では、この構成をTerraformを使用して実装する方法を紹介します。

## 参考

- 【AWS】【GCP】AWSとGCP間のキーなし認証を試みる(1) GCP→AWS
  - https://qiita.com/hyj624117615/items/ca4a8bc8c269098aaeef
- Access AWS using a Google Cloud Platform native workload identity
  - https://aws.amazon.com/jp/blogs/security/access-aws-using-a-google-cloud-platform-native-workload-identity/
- Google の権限を利用して AWS にアクセスする方法
  - https://zenn.dev/hogeticlab/articles/637ab70a430a0d
- Configuration and credential file settings in the AWS CLI
  - https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html
- Assume role with web identity
  - https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-oidc
- Available keys for AWS OIDC federation
  - https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html#condition-keys-wif
- ID トークンからユーザー情報を取得する
  - https://developers.google.com/identity/openid-connect/openid-connect?hl=ja#obtainuserinfo
- GCPでGitHub Actions用(OIDC)のサービスアカウントをTerraformで作成する
  - https://qiita.com/k-kojima-yumemi/items/0aa4ab62e02da91aedd2

## 環境

- Terraform v1.10.5
  - AWS Provider 5.86.0
  - Google Provider 6.19.0
  - GitHub Provider 6.5.0
- gh version 2.66.1 (2025-01-31)
- aws-cli 2.23.11
- gcloud 508.0.0

検証に使用したコードは以下のリポジトリにあります。

https://github.com/k-kojima-yumemi/curly-couscous

## 実践

以下のリソースを用意することで実現できます。

1. Google Cloud Service Accountの作成
    - こちらはGitHub ActionsからOIDCを利用して認証するためのService Accountです。
2. AWS IAM Roleの作成
3. 接続用のGitHub Actionsの設定

### 1. Google Cloud Service Accountの作成

AWS IAM Roleに対し、特定のService Accountからの接続を許可するため、Google Cloud側に接続元のService Accountを指定します。
`google-github-actions/auth@v2`[^1] ではDirect Workload Identity Federationによる接続が推奨されていますが、その方法ではAWSに接続するための認証情報を作成できません。

Terraformでは以下のように作成できます。GitHub ActionsからOIDC経由で接続する一般的な構成と同じです。

```hcl:service_account.tf
# GitHub ActionsからOIDC認証で使用するService Accountを作成
resource "google_service_account" "account" {
  account_id   = var.iam_id
  display_name = var.iam_name
  description  = "Access from GitHub Actions(${var.github_access_from})"
  disabled     = false
  timeouts {}
}

# Service AccountのEmailをoutputとして出力（GitHub ActionsのSecret作成時に使用）
output "service_account_email" {
  value = google_service_account.account.email
}

# Service AccountにWorkload Identity Userの権限を付与
resource "google_service_account_iam_policy" "policy" {
  policy_data        = data.google_iam_policy.policy.policy_data
  service_account_id = google_service_account.account.name
}

# 許可するGitHubリポジトリの設定
data "google_iam_policy" "policy" {
  binding {
    members = [
      for s in var.github_repositories :
      "principalSet://iam.googleapis.com/${var.workload_identity_pool_name}/attribute.repository/${s}"
    ]
    role = "roles/iam.workloadIdentityUser"
  }
}
```

`google_iam_policy` の内容は、用途やセキュリティに合わせて変更してください。

### 2. AWS IAM Roleの作成

AWS側に接続先のIAM Roleを用意します。

```hcl:iam_role.tf
# Google Cloud Service Accountからの接続を許可するIAM Roleを作成
resource "aws_iam_role" "account" {
  name               = var.iam_id
  description        = var.iam_name
  assume_role_policy = data.aws_iam_policy_document.account_assume.json
}

# Google CloudのService Accountからの認証を許可するポリシーを定義
data "aws_iam_policy_document" "account_assume" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type        = "Federated"
      identifiers = ["accounts.google.com"]
    }
    condition {
      test     = "StringEquals"
      variable = "accounts.google.com:aud"
      values   = [google_service_account.account.unique_id]
    }
    condition {
      test     = "StringEquals"
      variable = "accounts.google.com:sub"
      values   = [google_service_account.account.unique_id]
    }
    condition {
      test     = "StringEquals"
      variable = "accounts.google.com:oaud"
      values   = ["sts.amazonaws.com"]
    }
  }
}

# IAM RoleのARNをoutputとして出力（GitHub Actions設定時に使用）
output "aws_iam_role_arn" {
  value = aws_iam_role.account.arn
}
```

`assume_role_policy` として、Google Cloud側に作成したService Accountからの接続を許可するポリシーを記述します。
コンソールからアイデンティティプロバイダーをGoogleに設定した時とほぼ同じ内容になります。
![01-console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3355242/84900ae2-9655-6d2f-808d-49e33702b203.png)
AudienceにはGoogle Cloud側に設定したService Accountの `unique_id` を指定します。このIDはアカウントに対し一意なものです。
Service AccountのIDはコンソールから確認できます。
![02-service-account-id.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3355242/bb0db77b-906d-6ffa-6854-3a326f92e02d.png)

SubjectとしてもAudienceと同じ値を指定しています。
oaudは適当な値を指定してください。今回は `sts.amazonaws.com` を指定しています。[^2]

### 3. 接続用のGitHub Actionsの設定

以下のように設定することで、Google Cloudからの認証情報を利用してAWSに接続できます。

```yaml:auth.yml
jobs:
  auth:
    runs-on: ubuntu-latest
    steps:
      # Google Cloud認証を実行
      - uses: google-github-actions/auth@v2
        id: auth
        with:
          project_id: ${{env.ACCOUNT_GCP}}
          workload_identity_provider: ${{secrets.GCP_IDENTITY_PROVIDER}}
          service_account: ${{secrets.GCP_SERVICE_ACCOUNT}}
          token_format: "id_token"
          id_token_audience: "sts.amazonaws.com"
      # 取得したトークンをファイルに保存
      - name: Write JWT
        run: |
          import os

          with open(os.environ["DESTINATION"], "w") as f:
            f.write(os.environ["ID_TOKEN"])
        shell: python
        env:
          DESTINATION: ${{runner.temp}}/token.txt
          ID_TOKEN: ${{steps.auth.outputs.id_token}}
      # AWS認証を実行
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: 'us-east-1'
          mask-aws-account-id: true
          role-to-assume: ${{secrets.AWS_IAM_ROLE}}
          role-session-name: 'GitHubActions'
          web-identity-token-file: ${{runner.temp}}/token.txt
      # 認証確認
      - run: aws sts get-caller-identity
```

環境変数やシークレットとして、以下のものを設定しています。

- `env.ACCOUNT_GCP`: Google Cloudのプロジェクト名
- `secrets.GCP_IDENTITY_PROVIDER`: Google Cloudに設定したIdentity Providerの名前
  - `google_iam_workload_identity_pool_provider` のリソースで作成されるもののnameです。作成方法は[こちら](https://github.com/k-kojima-yumemi/curly-couscous/blob/d39c2bb900fac31cf115bc886bbbbaf8f9bf1431/modules/identity/identity.tf)のコードや別記事を参照ください
- `secrets.GCP_SERVICE_ACCOUNT`: Google Cloudに設定したService Accountの名前
- `secrets.AWS_IAM_ROLE`: AWSに設定したIAM Roleの名前
  - 今回GitHub ActionsのSecretに登録していますが、Google CloudのSecret Managerに登録しても良いかと思います

その他パラメータ

- `google-github-actions/auth@v2`[^1]
  - `token_format`: トークンの形式。 `id_token` を指定してしてください。ここで得られるトークンを使ってAWSに接続します
  - `id_token_audience`: トークンのAudience。 `aws_iam_policy_document` で `accounts.google.com:oaud` として参照している値を指定してください。
- `aws-actions/configure-aws-credentials@v4`[^3]
  - `web-identity-token-file`: `google-github-actions/auth@v2` で得られるトークンを保存したファイルになります。この例ではWrite JWTのステップでファイルに保存しています。tokenを直接受け渡すオプションはないようです

このステップを実行することにより、以下のように認証できます。
![03-gh.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3355242/c4ba4fa2-0fff-9106-83c2-dd96f4bf031a.png)


## 補足

### Service AcocuntのIDの確認方法

Service AccountのIDはコンソールから確認できますが、CLIでは以下のコマンドで確認できます。

```bash
gcloud iam service-accounts describe $SERVICE_ACCOUNT_EMAIL
```

### トークンに含まれる情報の確認

ローカルやCompute Engineでトークンを取得するには `gcloud auth print-identity-token` を使用します。
jqがインストールされている環境では以下のコマンドでトークンの中身を確認できます。

```bash
gcloud auth print-identity-token --audiences 'sts.amazonaws.com' | jq -R 'split(".") | .[1] | @base64d | fromjson'
```

Service Accountを設定したCompute Engineで実行した結果は以下になります。含まれるIDは隠しています。

```json
{
  "aud": "sts.amazonaws.com",
  "azp": "10-------------------",
  "exp": 1738993320,
  "iat": 1738989720,
  "iss": "https://accounts.google.com",
  "sub": "10-------------------"
}
```

azpとsubは同じ値です。audとしてはaudiencesで指定された値が含まれます。

### `aws-actions/configure-aws-credentials@v4` を使用しない接続

今回は `aws-actions/configure-aws-credentials@v4` を使用してAWSに接続しました。
これを使用しない場合、以下の環境変数を設定した上でコマンドを実行します。

* `AWS_WEB_IDENTITY_TOKEN_FILE`: `google-github-actions/auth@v2` で得られるトークンを保存したファイル
* `AWS_ROLE_ARN`: 接続先のIAM RoleのARN
* `AWS_ROLE_SESSION_NAME`: セッション名

GitHub Actionsでは以下のような記述になります。

```yaml
- run: aws sts get-caller-identity
  env:
    AWS_WEB_IDENTITY_TOKEN_FILE: ${{runner.temp}}/token.txt
    AWS_ROLE_ARN: ${{secrets.AWS_IAM_ROLE}}
    AWS_ROLE_SESSION_NAME: 'FromGitHubAction'
```

この方法で実行する場合には `aws-actions/configure-aws-credentials@v4` が不要になります。

単にCompute Engineから実行する場合は、 `gcloud auth print-identity-token --audiences 'sts.amazonaws.com'` の結果をファイルに格納し、 `AWS_WEB_IDENTITY_TOKEN_FILE` に指定します。

```bash
gcloud auth print-identity-token --audiences 'sts.amazonaws.com' > token.txt
export AWS_WEB_IDENTITY_TOKEN_FILE=token.txt
export AWS_ROLE_ARN=arn:aws:iam::123456789012:role/AWSRoleFromGoogleCloud
export AWS_ROLE_SESSION_NAME='FromComputeEngine'
aws sts get-caller-identity
```

`google-github-actions/auth@v2` でOIDC経由の認証をしたGitHub Actionsの環境では、 `gcloud auth print-identity-token` はエラーになります。

```bash
$ gcloud auth print-identity-token
ERROR: (gcloud.auth.print-identity-token) No identity token can be obtained from the current credentials.
```

## まとめ

Google CloudにのみGitHub ActionsのOIDC認証の設定を行い、GitHub ActionsからGoogle Cloud, AWSの両方に接続する方法を紹介しました。
AWSとGoogle Cloudの両方で設定を共有している場合などに簡素化できるかと思います。

[^1]: https://github.com/google-github-actions/auth

[^2]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-oidc によれば、 `accounts.google.com:aud` としてJWTの `azp` が使用されるとある。 `azp` がない場合にのみ `aud` が設定される。Google Cloudから得られるJWTには `azp` が含まれているため、Audienceの値をそのまま `accounts.google.com:aud` としては参照できない。代わりに `accounts.google.com:oaud` として参照する。
[^3]: https://github.com/aws-actions/configure-aws-credentials