Workload Identity をローカルと本番で同じコードで実装する
はじめに
こんにちは、すらぼです。
近年、マルチクラウド化の流れによって、複数のクラウドを活用した開発が徐々に活発になってきています。Google Cloud には、マルチクラウドの認証を安全に行うための Workload Identity 連携という機能が提供されており、AWS や Azure から Google Cloud に安全にアクセスすることができます。
ただ、 Workload Identity 連携ではライブラリ内でメタデータなどを取得する処理が含まれており、これがローカルでは上手く動作しないケースがあります。実際に環境に載せるまで動作確認ができないなどの悩みがあり、いろいろ調べていたところ、ローカルと AWS 環境どちらからでも同じコードで認証を通す方法がわかったので、紹介していきます。
やりたいこと
ローカル環境と EC2 で、同じコードを使って Workload Identity 連携による認証を行う実装を作ります。今回は、Google Cloud 側のサービスとしては Vertex AI を呼び出しています。
実際のリクエストの流れは以下のようになっています。

まず、開発中は下の経路(オレンジ)で、AWS アカウントに Assume Role し、その上で Workload Identity プールを通じてリクエストを行うようなイメージです。
その後、EC2 などにデプロイした後も、同じソースコードでリクエストが実行できるような実装をしてみます。
前提条件
以下の条件で実装していきます。
- Google Cloud プロジェクト
- Workload Identity の作成ができる
- Cloud Shell が利用できる
- AWS アカウント(ローカルから
aws sts get-caller-identityが通る状態) - Python 3.13 以上
通常の credential config を使った場合の問題
Workload Identity 連携の一般的な方法では、gcloud iam workload-identity-pools create-cred-config で生成した credential config JSON を GOOGLE_APPLICATION_CREDENTIALS に指定します。
clientLibraryConfig.json の例
{
"universe_domain": "googleapis.com",
"type": "external_account",
"audience": "//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID",
"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/SA_NAME@PROJECT_ID.iam.gserviceaccount.com:generateAccessToken"
}
この設定は EC2 上ではインスタンスメタデータ(169.254.169.254)から AWS の認証情報を取得できるため正常に動作します。しかし、ローカル環境で実行すると IMDS が存在しないため、以下のようなエラーが発生します。
google.auth.exceptions.TransportError:
HTTPConnectionPool(host='169.254.169.254', port=80):
Max retries exceeded with url: /latest/meta-data/iam/security-credentials
(Caused by ConnectTimeoutError(
'Connection to 169.254.169.254 timed out. (connect timeout=120)'))
この問題を解決するために、AwsSecurityCredentialsSupplier を使って boto3 の認証チェーンに委譲する実装を行います。
Google Cloud 側の設定
今回は、以下のリソースを作成します。
- Workload Identity プール(
aws-pool) - Workload Identity プロバイダ(
aws-provider) - サービスアカウント(
wi-test-sa) - IAM ポリシーバインディング
roles/aiplatform.user(Vertex AI へのアクセス用)roles/iam.workloadIdentityUser(Workload Identity からのなりすまし用)
また、Cloud Shell での実行を前提としています。そのため $GOOGLE_CLOUD_PROJECT という環境変数に、対象とするプロジェクト ID が保持されている前提で作業を進めていきます。
プロジェクト番号の取得
まず、 Cloud Shell で今後の作業のために PROJECT_NUMBER の値を変数として保持しておきます。
PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
Workload Identity プールの作成
Workload Identity プールを作成します。任意の名前で問題ありませんが、アプリケーションコードなどから呼び出す際に名前の指定が必要になります。
本記事では、 aws-pool としています。
gcloud iam workload-identity-pools create aws-pool \
--location="global" \
--display-name="AWS Pool"
AWS プロバイダの作成
同様に、Workload Identity プロバイダを作成します。名前は同様に任意ですが、後ほど指定が必要です。
名前は aws-provider としています。
また、ここでは リクエストを許可する AWS アカウントを指定 します。
--account-id="<AWSアカウントID>" の部分に、利用したい AWS アカウントの ID(12桁の数字)を入力してください。
gcloud iam workload-identity-pools providers create-aws aws-provider \
--location="global" \
--workload-identity-pool="aws-pool" \
--account-id="<AWSアカウントID>" \
--attribute-mapping="google.subject=assertion.arn"
サービスアカウントの作成、権限付与
次に、Workload Identity プールで許可されたリクエストに対して、どのような権限を割り当てるかを設定します。
今回は Vertex AI のエンドポイントを叩けるように、 roles/aiplatform.user のロールをサービスアカウントへ付与しています。
gcloud iam service-accounts create wi-test-sa \
--display-name="Workload Identity Test SA"
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member="serviceAccount:wi-test-sa@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"
Workload Identity からサービスアカウントへの紐付け
上記で作成したサービスアカウントを、Workload Identity pool に紐付け、利用できるようにします。
ここでは、先ほど作成した aws-pool で認証されたリクエスト全てに対して、 roles/iam.workloadIdentityUser ロールの権限を付与しています。
gcloud iam service-accounts add-iam-policy-binding \
wi-test-sa@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/aws-pool/*"
ローカル環境の準備
次にローカル環境で、アプリケーションコードを作成していきます。
今回は、 AWS 上で動作する想定の Python アプリケーションコードとなります。
AWS 認証情報の確認
前提として、ローカルから sts が実行できることを確認します。
$ aws sts get-caller-identity
{
"UserId": "XXXXXXXXXXXXXXXXXXXX:YOUR_SESSION_NAME",
"Account": "AWS_ACCOUNT_ID",
"Arn": "arn:aws:sts::AWS_ACCOUNT_ID:assumed-role/YOUR_ROLE_NAME/YOUR_SESSION_NAME"
}
また、"Account": "AWS_ACCOUNT_ID" の部分が、先ほど Workload Identity provider で設定した AWS アカウントと一致していることを確認してください。
Python プロジェクトの初期化
サンプルのアプリケーションを作っていきます。仮想環境には venv を使用していますが、任意の方法で置き換えてください。
AWS 用の boto3 と、Google Cloud 認証用の google-auth、Google Gen AI SDK の google-genai をそれぞれインストールしています。
mkdir wi-test && cd wi-test
python -m venv .venv
source .venv/bin/activate
pip install google-auth boto3 google-genai
実装
大きく2つのモジュールに分けて実装します。
- 認証モジュール(今回のコア)
- メイン処理(呼び出し方)
認証モジュール(auth.py)
AwsSecurityCredentialsSupplier というクラスをカスタマイズしたライブラリを作成し、 create_aws_auth_credentials という関数で環境に応じた認証情報を返却する処理を行っています。
# auth.py
import boto3
from google.auth.aws import AwsSecurityCredentials, AwsSecurityCredentialsSupplier
from google.auth import aws
class BotoAwsSupplier(AwsSecurityCredentialsSupplier):
def __init__(self, aws_region: str = "ap-northeast-1"):
self._region = aws_region
def get_aws_region(self, context, request):
return self._region
def get_aws_security_credentials(self, context, request):
session = boto3.Session()
creds = session.get_credentials().get_frozen_credentials()
return AwsSecurityCredentials(
access_key_id=creds.access_key,
secret_access_key=creds.secret_key,
session_token=creds.token,
)
def create_aws_auth_credentials(
project_number: str,
pool_id: str,
provider_id: str,
service_account_email: str,
aws_region: str = "ap-northeast-1",
) -> aws.Credentials:
cred = aws.Credentials(
audience=f"//iam.googleapis.com/projects/{project_number}/locations/global/workloadIdentityPools/{pool_id}/providers/{provider_id}",
subject_token_type="urn:ietf:params:aws:token-type:aws4_request",
aws_security_credentials_supplier=BotoAwsSupplier(aws_region),
service_account_impersonation_url=f"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{service_account_email}:generateAccessToken",
)
return cred.with_scopes(["https://www.googleapis.com/auth/cloud-platform"])
ポイントとしては、以下の AwsSecurityCredentialsSupplier をオーバーライドした BotoAwsSupplier で get_aws_security_credentials() を上書きすることで、認証情報の取得をインスタンスメタデータ固定ではなく、boto3 の認証チェーンに委譲しています。これにより、ローカルでは ~/.aws/credentials のプロファイルや環境変数から、EC2 ではインスタンスメタデータから、それぞれ自動的に認証情報を取得できるようになります。
class BotoAwsSupplier(AwsSecurityCredentialsSupplier):
def __init__(self, aws_region: str = "ap-northeast-1"):
self._region = aws_region
def get_aws_region(self, context, request):
return self._region
def get_aws_security_credentials(self, context, request):
session = boto3.Session()
creds = session.get_credentials().get_frozen_credentials()
return AwsSecurityCredentials(
access_key_id=creds.access_key,
secret_access_key=creds.secret_key,
session_token=creds.token,
)
なお、この実装方法は Node.js のライブラリの公式 GitHub リポジトリで紹介されている方法を、Python 版にアレンジしたものとなります。
公式ライブラリの紹介については、以下をご参照ください。
Vertex AI を呼び出すメイン処理(main.py)
上記で作成した認証用の関数を、実際に呼び出すスクリプトも作成します。
# main.py
import os
from google import genai
from auth import create_aws_auth_credentials
PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
PROJECT_NUMBER = os.environ["PROJECT_NUMBER"]
def main():
credentials = create_aws_auth_credentials(
project_number=PROJECT_NUMBER,
pool_id="aws-pool",
provider_id="aws-provider",
service_account_email=f"wi-test-sa@{PROJECT_ID}.iam.gserviceaccount.com",
)
client = genai.Client(
vertexai=True,
project=PROJECT_ID,
location="us-central1",
credentials=credentials,
)
response = client.models.generate_content(
model="gemini-2.0-flash",
contents="Hello!",
)
print(response.text)
if __name__ == "__main__":
main()
呼び出し方は非常にシンプルで、以下のようにクレデンシャルを取得します。
def main():
credentials = create_aws_auth_credentials(
project_number=PROJECT_NUMBER,
pool_id="aws-pool",
provider_id="aws-provider",
service_account_email=f"wi-test-sa@{PROJECT_ID}.iam.gserviceaccount.com",
)
その後、以下のように genai.Client に認証情報を渡すことで、正常にリクエストが実行されます。
client = genai.Client(
vertexai=True,
project=PROJECT_ID,
location="us-central1",
credentials=credentials,
)
動作確認
まず、AWS の STS が成功することを再度確認しておきます。また、 Account が、今回作成した Workload Identity 連携で指定したアカウントであることを確認します。
$ aws sts get-caller-identity
{
"UserId": "XXXXXXXXXXXXXXXXXXXX:YOUR_SESSION_NAME",
"Account": "AWS_ACCOUNT_ID",
"Arn": "arn:aws:sts::AWS_ACCOUNT_ID:assumed-role/YOUR_ROLE_NAME/YOUR_SESSION_NAME"
}
PROJECT_NUMBER=<プロジェクト番号> GOOGLE_CLOUD_PROJECT=<プロジェクトID> python main.py
まずローカルで試すと以下のように、正しくレスポンスが返ってきました。

そして、EC2 でも同じコードを使って試してみると、同じような結果が得られました。

同一のコードで、ローカルと EC2 両方で動作することが確認できました。
まとめ
本記事では、AwsSecurityCredentialsSupplier を使って、ローカル環境と EC2 で同じコードのまま Workload Identity 連携を動作させる方法を紹介しました。
ポイントをまとめると、
- 通常の credential config を使った方法では、ローカルでは動作しないケースがある。
- ローカルのインスタンスメタデータの取り扱い方法に依存する。
- boto3 の認証チェーンに委譲することで、環境を問わず同じコードで認証が通るようになる。
- そのために
AwsSecurityCredentialsSupplierをオーバーライドする。
- そのために
また、認証ロジックを auth.py として切り出しておけば、アプリケーション側は credentials を受け取って SDK に渡すだけで済むため、Vertex AI 以外にも BigQuery など複数のサービスを呼び出す場合でも使いまわしやすくなります。
Workload Identity 連携は安全な認証手段ですが、開発時のローカル実行でつまずきやすいポイントでもあります。本記事の方法が参考になれば幸いです。
以上、すらぼでした。
参考資料







