Workload Identity連携で認証情報の構成ファイルを読み込まずに実行する
2025.12.17

この記事はNHN テコラス Advent Calendar 2025 の 17 日目の記事です。
はじめに
Workload Identity連携を構築した際、提供される認証情報の構成ファイルをそのまま読み込んで、実行しているケースが多いと思います。この場合実装は楽ですが、Lambdaであれば1つのサービスアカウントにしかアクセスできなくなるため拡張性が低くなります。
また構成ファイルをParameter Storeに置いたとしても、複数のサービスアカウントにアクセスしたい場合、この構成ファイルの管理が手間になります。
これを解決する方法として、認証情報の構成ファイルの内容をLambdaの中で作成する方法があります。これによって1つのLambdaから複数のWorkload Identity Poolにアクセスできるようになります。
そのため、今回は最初にWorkload Identity連携の認証情報の構成ファイルの定義を確認します。その後Lambdaの中で構成ファイルを作成して、これを使ってWorkload Identity連携を行い、Google CloudのCloud Storageにアクセスする方法を紹介します。
Workload Identity連携自体の説明は省略しています。こちらを確認したい人は別の記事を参考にしてください。
Workload Identity連携の認証情報の構成ファイルを確認する
AWSをプロバイダとした場合、Workload Identityを構築した後に発行できる認証情報の構成ファイルは以下になっています。
{
"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",
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/$EMAIL:generateAccessToken",
"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",
"imdsv2_session_token_url": "http://169.254.169.254/latest/api/token"
}
}
参考: https://google.aip.dev/auth/4117#determining-the-subject-token-in-aws
こちらを確認すると、ほとんど固定の値であることがわかります。
つまり以下が分かれば構成ファイルは作成できることになります。
| 変数 | 説明 |
|---|---|
| $PROJECT_NUMBER | プロジェクトナンバーのこと、プロジェクトIDではないので注意 |
| $POOL_ID | Workload IdentityのPool IDのこと |
| $PROVIDER_ID | Workload Identityのプロパイダー IDのこと |
| サービスアカウントのID={ユニーク名}@{project名}.iam.gserviceaccount.comのこと |
こちらの情報を元にLambdaで実装をしていきます。
実装の前提
今回はSAMを使ってAWS Lambdaを構築します。言語はJava 17を使用します。またWorkload Identity Pool、プロバイダー、サービスアカウント、Role、Cloud Storageを作成します。
リソースを作成したら、LambdaからWorkload Identity連携を実行して、Cloud Storageにあるオブジェクトの一覧を取得します。
Workload Identity連携の検証リソースの構築
Workload Identityをはじめとする検証リソースを作成します。今回はCLIで行います。
今回重要なのはこの後のLambdaの実装になります。リソースの構築の説明は長いためLambdaの実装を確認したい場合は、こちらを飛ばして次の項目を確認してください。
変数準備
export PROJECT_ID="your-project-id"
export PROJECT_NUMBER="your-project-number"
export POOL_ID="aws-lambda-pool"
export SERVICE_ACCOUNT_NAME="lambda-gcs-access"
export SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
export GCS_BUCKET_NAME="lambda-gcs-access-bucket"
export AWS_ACCOUNT_ID="your-aws-account-id"
export AWS_ROLE_NAME="lambda-sample-role"
Pool 作成
$ gcloud iam workload-identity-pools create $POOL_ID \ --project=$PROJECT_ID \ --location="global" \ --display-name="aws-lambda-pool" \ --description="aws lambda pool"
Workload Identityでは削除時に同じIDが30日使用不可能になるため、削除した場合はIDを変更する必要があります。この点は注意してください。
プロバイダー作成
$ gcloud iam workload-identity-pools providers create-aws aws-provider \
--project=${PROJECT_ID} \
--location="global" \
--workload-identity-pool="${POOL_ID}" \
--display-name="aws-lambda-pool" \
--description="aws provider" \
--attribute-mapping="google.subject=assertion.arn,attribute.aws_account=assertion.account,attribute.aws_role=assertion.arn.contains('assumed-role') ? assertion.arn.extract('{account_arn}assumed-role/') + 'assumed-role/' + assertion.arn.extract('assumed-role/{role_name}/') : assertion.arn" \
--account-id=$AWS_ACCOUNT_ID
Service Account作成
次にGoogle Cloud側にLambdaが使うService Accountを作成します。
$ gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \
--project=$PROJECT_ID \
--display-name="Lambda GCS Access"
Cloud Storage作成
$ gcloud storage buckets create gs://$GCS_BUCKET_NAME \ --project=$PROJECT_ID \ --location=asia-northeast1 \ --default-storage-class=STANDARD \ --uniform-bucket-level-access
権限作成
Roleを作成します。
$ gcloud iam roles create LambdaGcsAccess \ --project=$PROJECT_ID \ --title=lambda-gcs-access \ --permissions=storage.buckets.list,storage.objects.get,storage.objects.list \ --stage=GA
Roleをサービスアカウントに紐づけます。
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--project=$PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_EMAIL" \
--role="projects/${PROJECT_ID}/roles/LambdaGcsAccess" \
--condition="None"
その後作成したサービスアカウントにWorkload Identityの権限を付与します。
$ gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_EMAIL \
--project=$PROJECT_ID \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/attribute.aws_role/arn:aws:sts::${AWS_ACCOUNT_ID}:assumed-role/${AWS_ROLE_NAME}"
Lambdaを作成する
次にSAMを使ってLambdaを作成します。
App.java
package helloworld;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.google.api.gax.paging.Page;
import com.google.auth.oauth2.AwsCredentialSource;
import com.google.auth.oauth2.AwsCredentials;
import com.google.auth.oauth2.ExternalAccountCredentials;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.Storage.BlobListOption;
import com.google.cloud.storage.StorageOptions;
import java.text.MessageFormat;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
public class App implements RequestHandler<Map<String, Object>, Map<String, Object>> {
private static final Logger logger = LoggerFactory.getLogger(App.class);
// 固定値
private static final String SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request";
private static final String TOKEN_URL = "https://sts.googleapis.com/v1/token";
private static final String FORMAT_AUDIENCE = "//iam.googleapis.com/projects/{0}/locations/global/workloadIdentityPools/{1}/providers/{2}";
private static final String FORMAT_SERVICE_ACCOUNT_IMPERSONATION_URL = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{0}:generateAccessToken";
private static final String ENVIRONMENT_ID = "aws1";
private static final String REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone";
private static final String URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials";
private static final String REGIONAL_CRED_VERIFICATION_URL = "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15";
@Override
public Map<String, Object> handleRequest(Map<String, Object> event, Context context) {
// パラメータを取得する
String projectNumber = (String) event.get("project_number");
String projectId = (String) event.get("project_id");
String poolId = (String) event.get("pool_id");
String providerId = (String) event.get("provider_id");
String serviceAccountEmail = (String) event.get("service_account_email");
String gcsBucketName = (String) event.get("gcs_bucket_name");
// credential 作成
ExternalAccountCredentials credentials = createCredentials(
projectNumber,
poolId,
providerId,
serviceAccountEmail
);
// credentialを使ってClientを作成
Storage gcsClient = StorageOptions.newBuilder()
.setCredentials(credentials)
.setProjectId(projectId)
.build()
.getService();
// CloudStorageにアクセス
BlobListOption blobListOption = BlobListOption.prefix("");
Page<Blob> blobs = gcsClient.list(gcsBucketName, blobListOption);
// 確認
List<String> resultList = blobs.streamValues().map(BlobInfo::getName).toList();
return createReturnItem(resultList.toString());
}
private ExternalAccountCredentials createCredentials(
String projectNumber,
String poolId,
String providerId,
String serviceAccountEmail
) {
// audience
String audience = MessageFormat.format(FORMAT_AUDIENCE, projectNumber, poolId, providerId);
logger.info("audience: {}", audience);
// serviceAccountImpersonationUrl
String serviceAccountImpersonationUrl = MessageFormat.format(FORMAT_SERVICE_ACCOUNT_IMPERSONATION_URL, serviceAccountEmail);
logger.info("serviceAccountImpersonationUrl: {}", serviceAccountImpersonationUrl);
// awsCredentialSource関連
Map<String, Object> credentialSourceMap = Map.of(
"environment_id", ENVIRONMENT_ID,
"region_url", REGION_URL,
"url", URL,
"regional_cred_verification_url", REGIONAL_CRED_VERIFICATION_URL
);
AwsCredentialSource awsCredentialSource = new AwsCredentialSource(credentialSourceMap);
// AwsCredentials.Builder
ExternalAccountCredentials credentials = AwsCredentials.newBuilder()
.setAudience(audience)
.setSubjectTokenType(SUBJECT_TOKEN_TYPE)
.setTokenUrl(TOKEN_URL)
.setServiceAccountImpersonationUrl(serviceAccountImpersonationUrl)
.setCredentialSource(awsCredentialSource)
.build();
return credentials;
}
private Map<String, Object> createReturnItem(String result) {
Map<String, Object> item = Map.of("result", result);
logger.info("return = {}", item);
return item;
}
}
build.gradle
plugins {
id 'java'
}
group = 'helloworld'
version = '1.0'
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
repositories {
mavenCentral()
}
dependencies {
// AWS
implementation 'com.amazonaws:aws-lambda-java-core:1.2.3'
implementation 'com.amazonaws:aws-lambda-java-events:3.11.4'
implementation 'org.slf4j:slf4j-api:2.0.9'
// Google Cloud
implementation platform('com.google.cloud:libraries-bom:26.28.0')
implementation 'com.google.cloud:google-cloud-storage'
implementation 'com.google.auth:google-auth-library-credentials'
}
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Sample SAM Lambda application
Resources:
SampleRole:
Type: AWS::IAM::Role
Properties:
RoleName: lambda-sample-role
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AmazonSSMFullAccess
SampleFunction:
Type: AWS::Serverless::Function
Properties:
Description: Google Cloud Access Test
FunctionName: googlecloud-access-test
CodeUri: HelloWorldFunction
Handler: helloworld.App::handleRequest
Runtime: java17
Architectures:
- x86_64
MemorySize: 512
Timeout: 300
Environment:
Variables:
ENV_TYPE: dev
Role: !GetAtt SampleRole.Arn
準備ができたらデプロイします
$ sam build
$ sam deploy \ --stack-name sample-lambda \ --s3-bucket your-s3-bucket-name \ --region ap-northeast-1 \ --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \ --profile your-profile-name
Lambdaを実行する
次にCloud Storageに適当なファイルをアップロードします。今回はsample.jsonを作ってアップロードしています。
$ touch sample.json $ gcloud storage cp sample.json gs://lambda-gcs-access-bucket/sample.json
Lambdaのパラメータを準備します。このパラメータを変更することによって、利用するWorkload Identityを切り替えることができます。
{
"project_number": "your-project-number",
"project_id": "your-project-id",
"pool_id": "your-pool-id",
"provider_id": "aws-provider",
"gcs_bucket_name": "lambda-gcs-access-bucket",
"service_account_email": "lambda-gcs-access@{your-project-id}.iam.gserviceaccount.com"
}
上記のパラメータを使ってLambdaを実行します。
$ aws lambda invoke \
--function-name googlecloud-access-test \
--cli-binary-format raw-in-base64-out \
--profile your-profile-name \
--payload '{
"project_number": "your-project-number",
"project_id": "your-project-id",
"pool_id": "your-pool-id",
"provider_id": "aws-provider",
"gcs_bucket_name": "lambda-gcs-access-bucket",
"service_account_email": "lambda-gcs-access@{your-project-id}.iam.gserviceaccount.com"
}' \
response.json
レスポンスを確認して、sample.jsonがあればアクセス成功です。
$ cat response.json
{"result":"[sample.json]"}
まとめ
今回は認証情報の構成ファイルの内容をLambdaの中で作成してからWorkload Identity連携を使ってGoogle CloudのCloud Storageにアクセスする方法を紹介しました。構成ファイルを読み込むのは楽ですが、認証先が増えた場合構成ファイルの管理の手間が生まれます。
変数で渡せるようにした方がより多くのサービスアカウントにアクセスできるようになるため、複数の認証をする場合は今回の実装がおすすめです。
NHN テコラスの採用情報はこちら
テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!
Follow @twitter2018年新卒入社。エンジニア。フロントエンド&サーバサイドを担当。 Vue.js, Ruby on Rails, Ruby, Javaを主に使用する。 会社では全力で働き、家では全力で遊ぶ。
Recommends
こちらもおすすめ
-
AWS re:Invent 2025 に、今年はハワイ経由で行ってみました
2025.12.15
Special Topics
注目記事はこちら
データ分析入門
これから始めるBigQuery基礎知識
2024.02.28

AWSの料金が 10 %割引になる!
『AWSの請求代行リセールサービス』
2024.07.16


