Google CloudからAWSのリソースへWorkload Identity & AssumeRole でアクセスするPythonコードを書いた [Google Cloud --> AWS]
Google Cloud 上のアプリケーション、例えば Cloud Run Functions から AWS のリソースにアクセスしたいケースはままあるかと思います。
例えば以下のような感じです:
こういうケースで一番分かりやすくて面倒くさくないシンプルな方法は、AWS 上に IAM ユーザーを作ってアクセスキー/シークレットキーを払い出し、それを使って認証するやり方です。まさに上の記事に書いた方法です。
ですが AWS を多少なりと使っていると、アクセスキー/シークレットキーの漏洩に起因するセキュリティ事故の話を何度か耳にするようになり、自然と
「なるべくならやりたくないな。。」
と思うようになるかと思います(個人の感想です)。
もちろん、AssumeRole により一時クレデンシャルを得て AWS リソースにアクセスする STS の仕組み(AssumeRoleWithWebIdentity API)は用意されています。
これを Google Cloud 側から利用すればいいわけで、そのためには Workload Identity 連携が使えます。
というわけで、そのやり方で実装してみました。
前述のブログと同じく、 Claude 3.5 Sonnet 先生と Gemini 1.5 Pro 先生に何度もお伺いを立てつつ試行錯誤した結果、なんとか動くコードが出来たので公開します。
そのような事情で、ぼく自身コードの内容や挙動を深く理解できているとは思えないのですが、サンプルとしてご覧頂けますと幸いです。
なお今回の逆、AWS から Google Cloud リソースを使いたい場合は、公式のドキュメントがあるのでそちらをご参照下さい:
処理の流れ
まず、予め AWS の IAM ロールと Google Cloud のサービスアカウントの間で信頼関係を結んでおきます。
具体的には(今回の場合は)IAM ロール上に信頼関係ポリシーを記述し、特定のサービスアカウントを信頼しておく感じです。
このへんは、AWS アカウント同士で信頼関係を結ぶときと同じ考え方なので、分かりやすいかと思います。
実際に Google Cloud 上(例えば Cloud Run Functions 関数)から AWS にアクセスしたい場合は、以下の手順で一時クレデンシャルを得ます:
- まず、自分自身が持つ Google Cloud 上の認証情報をサービスアカウントから取得
- それを使って OIDC 認証のためのトークン(JWT)を Google Cloud の Workload Identity から入手
- JWT を使って AWS の STS から一時クレデンシャルを入手
- 入手した一時クレデンシャルを使って AWS リソースにアクセス
JWT を入手して AWS にアクセスするという点で言えば、GitHub など他の SaaS と AWS を連携させるときと考え方は同じになるかと思います。
実装例
Google Cloud
実際のコードは以下のようになります。コメントの番号は上と合わせておきましたので参考にして下さい。
import boto3
from google.auth import default
from google.auth.transport.requests import Request
from google.oauth2 import id_token
def main():
role_arn = 'arn:aws:iam::<AWSアカウントID>:role/<ロール名>'
role_session_name = 'Google_Cloud_to_AWS_AssumeRole'
# Google Cloud の認証情報を取得 ---- (1.)
gcp_credentials, _ = default()
gcp_credentials.refresh(Request())
# Google の OIDC トークンエンドポイントから JWT トークンを取得 ---- (2.)
web_identity_token = id_token.fetch_id_token(Request(), role_arn)
# AWS の認証情報を取得 ---- (3.)
sts = boto3.client('sts')
response = sts.assume_role_with_web_identity(
RoleArn=role_arn,
RoleSessionName=role_session_name,
WebIdentityToken=web_identity_token
)
aws_credentials = response['Credentials']
# AWS リソースにアクセス ---- (4.)
# 例)S3 バケットの一覧を取得する
s3 = boto3.client(
's3',
aws_access_key_id=aws_credentials['AccessKeyId'],
aws_secret_access_key=aws_credentials['SecretAccessKey'],
aws_session_token=aws_credentials['SessionToken'],
region_name='ap-northeast-1',
)
response = s3.list_buckets()
print(response)
return "Success."
requests
google-auth
boto3
少しだけ解説を。
role_session_name
に設定する値ですが、これは boto3 のドキュメントによると「AssumeRoleセッションの識別子」とのこと。一意になるように設定してください。
AWS
アクセス先の AWS 側の IAM ロールには、以下のような信頼関係ポリシーを記載しておいてください:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "accounts.google.com"},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"accounts.google.com:aud": "999999999999999999999"}}}]}
ここで 999999999999999999999
というのは、該当サービスアカウントの OAuth2 クライアント ID あるいは Unique ID(一意の ID)といわれるもの[1]になります。
コンソールあるいは以下の gcloud CLI コマンドで確認してください。
gcloud iam service-accounts describe \
--format='value(oauth2ClientId)' \
<該当サービスアカウント名>@<プロジェクトID>.iam.gserviceaccount.com
まとめ
最初は「こんなものテンプレ構成やろ」と思ってたんですが、実際に探してみると意外と「これ!」というものが見つからず[2]、だったらということで(以下略
本記事は付け焼き刃知識の解説で恐縮ですが、誰かの役に立てたら幸いです。
参考
- 一般的なシナリオ - AWS Identity and Access Management
- google.auth package — google-auth 1.14.0 documentation
- google.auth.transport.requests module — google-auth 1.14.0 documentation
- google.oauth2.id_token module — google-auth 1.14.0 documentation
- assume_role_with_web_identity - Boto3 1.35.14 documentation