AWS の IAM ロールと GCP のサービスアカウントを紐付けて鍵なしでアクセスする方法

2021-04-11AWS,GCPTech

GCP には Workload Identity 連携 と呼ばれる機能があります。
現時点ではβ版です。 ブログを書いている間に GA してました。

この機能を活用することで、GCP 上のサービスアカウントを AWS IAM と紐付けることができ、サービスアカウントの鍵を使うことなく GCP へアクセス可能になります。

AWS から BigQuery へデータを送るためにサービスアカウントの鍵を渡すケースは多いと思いますが、今後はその必要がなくなるかもしれません。

登場する概念

Workload Identity プールと、Workload Identity プロバイダという2つの概念を最初に抑えておくと理解しやすくなります。(なりました)

  • Workload Identity プール
    • 外部 ID と連携するときに使う
    • 好きな名前をつけて作れる
    • この中に ID プロバイダを登録する
  • Workload Identity プロバイダ
    • ID を管理する外部のプロバイダ
    • 好きな名前をつけて作れる
    • OpenID Connect をサポートするプロバイダが登録できる
    • 今回は AWS をプロバイダとして登録する

「プールを作ってその中に AWS という ID プロバイダを登録する」のが今回やることです。

GCP サービスアカウントと AWS IAM ロールを紐付ける手順

前提条件

  • 組織に属する GCP アカウントであること
  • Workload Identity プール管理者権限を持っていること(オーナーなら問題なし)

公式ドキュメントの手順に沿って検証しました。
https://cloud.google.com/iam/docs/access-resources-aws?hl=ja

API を有効化

今回使用する以下4つの API を有効化します。

Workload Identity プールを作る

管理権限のあるアカウントで Google Cloud Shell から操作するのが楽です。
プール名とプロバイダ名は任意で指定しましょう。

# GCP project 情報をセット
$ gcloud config set project "<project-id>"

# Workload Identity プールを作る
$ gcloud iam workload-identity-pools create "<pool-name>" --location="global"

# AWS をプロバイダとして追加する
$ gcloud iam workload-identity-pools providers create-aws "<provider-name>" --location="global" --workload-identity-pool="<pool-name>" --account-id="<aws-account-id>"

サービスアカウントと紐付ける

今回は事前に作ってあるサービスアカウントを使います。
コマンドの引数がかなり長いので、先に組み立てておくことを推奨。

# Identity プールの name を確認しておく
$ gcloud iam workload-identity-pools list --location="global"
---
name: projects/123456789012/locations/global/workloadIdentityPools/mypool01
state: ACTIVE

# サービスアカウントと紐付ける
$ gcloud iam service-accounts add-iam-policy-binding "<service-account-email>" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/<先ほど確認した name>/attribute.aws_role/arn:aws:sts::<aws-account-id>:assumed-role/<aws-iam-role-name>"

認証情報ファイルを作る

クライアントで利用する認証情報を作ります。サービスアカウントの鍵代わりとなるものです。

$ gcloud iam workload-identity-pools create-cred-config \
  "<先ほど確認した name>/providers/<provider-name>" \
  --service-account="<service-account-email>" \
  --output-file="<file-path>" \
  --aws

指定したファイルパスに JSON が出てくるので、AWS の実行環境に配置しましょう。
中身を覗いてみると、メタデータしか入っていないことが分かります。

{
  "type": "external_account",
  "audience": "//iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/mypool01/providers/myprovider01",
  "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
  "token_url": "https://sts.googleapis.com/v1/token",
  "credential_source": {
    "environment_id": "aws1",
    "region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
    "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
    "regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
  },
  "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/sa01@xxxxxxxxx.iam.gserviceaccount.com:generateAccessToken"
}

AWS からアクセスしてみる

Node.js SDK の最新版を使い、BigQuery からデータを取得するだけの簡単なスクリプトです。
キーファイルの欄には先ほど作成した JSON を指定します。

const { BigQuery } = require('@google-cloud/bigquery');

const bigquery = new BigQuery({
  projectId: 'xxxxxxxxx',
  keyFilename: 'credential.json'
});

bigquery.query('SELECT * FROM mydataset.users ORDER BY id')
  .then(data => {
    const rows = data[0];
      rows.forEach(row => {
        console.log(JSON.stringify(row));
      });
  })
  .catch(error => {
    console.log(error);
  });

GCP と紐付けた IAM ロールを持つ EC2 から実行すると、無事にデータを取得できました。

$ node ./bq.js
{"id":1,"name":"aaaaa","email":"aaaaa@example.com","created":{"value":"2021-04-01T03:00:00"}}
{"id":2,"name":"bbbbb","email":"bbbbb@example.com","created":{"value":"2021-04-01T06:00:00"}}
{"id":3,"name":"ccccc","email":"ccccc@example.com","created":{"value":"2021-04-01T09:00:00"}}
{"id":4,"name":"ddddd","email":"ddddd@example.com","created":{"value":"2021-04-01T12:00:00"}}
{"id":5,"name":"eeeee","email":"eeeee@example.com","created":{"value":"2021-04-01T15:00:00"}}

試しにインスタンスから IAM ロールを剥がして実行すると権限エラーとなり、実際に AWS の IAM ロールと紐付いて認証されていることが確認できます。

$ node ./bq.js
GaxiosError: The caller does not have permission
    at Gaxios._request (/home/ec2-user/bqtest/node_modules/gaxios/build/src/gaxios.js:127:23)
    at processTicksAndRejections (node:internal/process/task_queues:94:5)
    at async AwsClient.getImpersonatedAccessToken (/home/ec2-user/bqtest/node_modules/google-auth-library/build/src/auth/baseexternalclient.js:322:26)
    at async AwsClient.refreshAccessTokenAsync (/home/ec2-user/bqtest/node_modules/google-auth-library/build/src/auth/baseexternalclient.js:258:38)
    at async AwsClient.getAccessToken (/home/ec2-user/bqtest/node_modules/google-auth-library/build/src/auth/baseexternalclient.js:116:13)
    at async AwsClient.getRequestHeaders (/home/ec2-user/bqtest/node_modules/google-auth-library/build/src/auth/baseexternalclient.js:133:37)
    at async GoogleAuth.authorizeRequest (/home/ec2-user/bqtest/node_modules/google-auth-library/build/src/auth/googleauth.js:593:25)

まとめ

  • AWS IAM と GCP サービスアカウントを紐付けできる
  • サービスアカウントの鍵を管理する必要がない
  • まだベータ版なので注意が必要

AWS からサービスアカウントを使う箇所は、この連携機能に乗り換えても良さそうですね。

誤りなどを発見した場合はぜひコメントください。

AWS,GCPTech