Lambda関数からWorkload Identity Federationを使用してGoogle DriveからS3にファイルをアップロードしてみる
概要
Google Driveにあるファイルを S3に転送したいケースがあると思います。
その際、Google Cloudのサービスアカウントキーを発行してLambda関数に持たせる方法もありますが、サービスアカウントキーは漏洩リスクやローテーション管理のコストがあるため、あまり推奨されません。
そんな時はWorkload Identity Federationを使うと、AWSのIAMロールの認証情報をもとにGoogle Cloudのトークンを取得でき、サービスアカウントキーなしでGoogle Drive APIにアクセスできます。
さっそくやってみましょう。
手順
1. Google Cloud側の設定
1-1. Google Drive APIを有効化する
Google CloudコンソールのAPIライブラリまたはコマンドから、Google Drive APIを有効化します。

gcloud services enable drive.googleapis.com \
--project=YOUR_GCP_PROJECT_ID
1-2. サービスアカウントを作成する
Workload Identity Federationで使用するサービスアカウントを作成します。
gcloud iam service-accounts create YOUR_SA_NAME \
--display-name="Google Drive to S3 Transfer" \
--project=YOUR_GCP_PROJECT_ID
1-3. Google Driveのフォルダをサービスアカウントに共有する
Google Driveの転送元フォルダを開き、共有設定から先ほど作成したサービスアカウントのメールアドレス(YOUR_SA_NAME@YOUR_GCP_PROJECT_ID.iam.gserviceaccount.com)を閲覧者以上の権限で追加します。

ダウンロードのみであれば閲覧者または編集者で十分です。
これを忘れるとファイル取得時に403エラーになるので注意です。
1-4. サービスアカウントのIAMロールについて
今回はGoogle Drive APIのみを使用するため、サービスアカウントにGoogle Cloud側のIAMロールを付与する必要はありません。
Google Drive APIへのアクセス権限はOAuthスコープ(drive.readonly)とGoogle Drive側のフォルダ共有設定で制御されるためです。
もしCloud Storageなど他のGoogle Cloudリソースにもアクセスする場合は、サービスアカウントに対して適切なIAMロールを付与してください。
1-5. Workload Identity Poolを作成する
Workload Identity Poolは、Google Cloudの外部(AWSやAzureなど)で動いているワークロードのIDをまとめて管理するための「入れ物」です。
仕組みとしては、外部のIDプロバイダ(今回の場合はAWSのIAM)が発行した認証情報を受け取って、Google Cloud側で信頼できるIDとして扱えるようにする役割を持っています。
今回の構成だと以下のような関係です。
- Workload Identity Pool(aws-s3-pool)→ 外部IDをまとめる入れ物
- Provider(aws-provider)→ 「AWSからの認証情報を信頼する」という設定
- サービスアカウント → 外部ID(AWSのロール)がなりすます先のGoogle Cloud上のID
流れとしては、Lambda(のロール)が自分の認証情報をPoolに提示して、Poolが「このAWSアカウントのこのロールは信頼できる」と判断したら、指定されたサービスアカウントのトークンを発行、サービスアカウントになりすますことができる、という仕組みです。
gcloud iam workload-identity-pools create "aws-s3-pool" \
--project=YOUR_GCP_PROJECT_ID \
--location="global" \
--display-name="AWS S3 Transfer Pool"
1-6. AWSプロバイダを追加する
gcloud iam workload-identity-pools providers create-aws "aws-provider" \
--project=YOUR_GCP_PROJECT_ID \
--location="global" \
--workload-identity-pool="aws-s3-pool" \
--display-name="AWS Provider" \
--account-id="YOUR_AWS_ACCOUNT_ID" \
--attribute-mapping="google.subject=assertion.arn,attribute.aws_role=assertion.arn.extract('assumed-role/{role}/')" \
--attribute-condition="assertion.arn.startsWith('arn:aws:sts::YOUR_AWS_ACCOUNT_ID:assumed-role/')"
YOUR_AWS_ACCOUNT_IDにはAWSの12桁のアカウントIDを指定します。
--attribute-conditionを設定することで、指定したAWSアカウントのロールからのみ認証を受け付けるように制限しています。本番環境では特定のロールARNに絞ったほうがより安全です。
1-7. サービスアカウントへのなりすまし権限を付与する
# プロジェクト番号を取得
PROJECT_NUMBER=$(gcloud projects describe YOUR_GCP_PROJECT_ID --format="value(projectNumber)")
gcloud iam service-accounts add-iam-policy-binding \
YOUR_SA_NAME@YOUR_GCP_PROJECT_ID.iam.gserviceaccount.com \
--project=YOUR_GCP_PROJECT_ID \
--role="roles/iam.workloadIdentityUser" \
--member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/aws-s3-pool/subject/arn:aws:sts::YOUR_AWS_ACCOUNT_ID:assumed-role/YOUR_LAMBDA_ROLE_NAME"
--memberにはLambda関数に割り当てたIAMロールのARNをsubjectとして指定します。末尾のロール名がAWS側のLambda実行ロール名と一致していないと、iam.serviceAccounts.getAccessTokenの権限エラーになるので注意してください。
※Lambda実行ロール名は作成後に以下のコマンドで確認できます。
aws lambda get-function-configuration \
--function-name YOUR_FUNCTION_NAME \
--query "Role" --output text
# 出力例: arn:aws:iam::YOUR_AWS_ACCOUNT_ID:role/YOUR_LAMBDA_ROLE_NAME
1-8. 認証構成ファイルを生成する
gcloud iam workload-identity-pools create-cred-config \
"projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/aws-s3-pool/providers/aws-provider" \
--service-account="YOUR_SA_NAME@YOUR_GCP_PROJECT_ID.iam.gserviceaccount.com" \
--aws \
--output-file="credential-config.json"
credential-config.jsonは秘密情報を含みません。
このファイルには秘密鍵やシークレットは含まれません。含まれているのは、プールやプロバイダの識別情報、トークン交換のためのURLなどのメタデータのみです。そのため、環境変数にJSON文字列として格納しても安全です。一方で、サービスアカウントキーのJSONは秘密鍵を含むので、環境変数への格納や使用することが推奨されません。
2. AWS側の設定
2-1. Lambda用IAMロールを作成する
Lambda実行用のIAMロールに以下のポリシーを付与します。
信頼ポリシー:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
アタッチするポリシー:
AWSLambdaBasicExecutionRole(CloudWatch Logsへの書き込み)- S3バケットへの書き込み権限(以下のインラインポリシー)
sts:GetCallerIdentityの許可
今回はGoogle DriveからS3への転送なのでs3:PutObjectを指定します。
(S3→Google Driveの場合はs3:GetObjectです)
sts:GetCallerIdentityはWorkload Identity Federationのトークン交換に必要なため付与しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": "arn:aws:s3:::YOUR_S3_BUCKET/*"
},
{
"Effect": "Allow",
"Action": "sts:GetCallerIdentity",
"Resource": "*"
}
]
}
2-2. Lambda関数を作成する
ランタイムはPython 3.12を選択し、先ほど作成したIAMロールを割り当てます。
作成後、「設定」→「一般設定」からタイムアウトとメモリを変更します。デフォルトのタイムアウトは3秒ですが、Google Driveからのダウンロードと S3へのアップロードを行うため、3秒では確実にタイムアウトします(試したところ3秒以上かかりました)。
| 設定項目 | デフォルト値 | 推奨値 |
|---|---|---|
| タイムアウト | 3秒 | 1分(60秒) |
| メモリ | 128MB | 256MB |
2-3. Lambdaレイヤーを作成する(CloudShellで実行)
Google関連のライブラリはLambdaに標準で含まれていないため、Lambdaレイヤーとして追加する必要があります。
AWSのCloudShellを開いて以下のコマンドを実行します。
# 作業ディレクトリを作成
mkdir -p lambda-layer/python
cd lambda-layer
# Lambda実行環境に合わせたパッケージをインストール
pip3 install \
google-auth \
google-api-python-client \
--platform manylinux2014_x86_64 \
--target=python/ \
--python-version 3.12 \
--only-binary=:all:
# zipファイルを作成
zip -r google-drive-layer.zip python/
# Lambdaレイヤーとして発行
aws lambda publish-layer-version \
--layer-name google-drive-layer \
--zip-file fileb://google-drive-layer.zip \
--compatible-runtimes python3.12 \
--region ap-northeast-1
レイヤーの発行が完了すると、ARNが出力されます。控えておき、Lambda関数の設定画面からこのレイヤーを追加します。

2-4. 環境変数を設定する
Lambda関数の環境変数に以下を設定します。
| 環境変数名 | 値 |
|---|---|
GOOGLE_APPLICATION_CREDENTIALS_JSON |
credential-config.jsonの中身(JSON文字列) |
S3_BUCKET |
転送先のS3バケット名 |
GOOGLE_DRIVE_FOLDER_ID |
転送元のGoogle DriveフォルダID |
GOOGLE_DRIVE_FOLDER_IDにはGoogle Driveの転送元フォルダのIDを指定します。
フォルダIDは、Google Driveでフォルダを開いたときのURLから確認できます。
https://drive.google.com/drive/folders/#この部分#
3. Lambda関数のコード
以下のコードをLambda関数のコードに貼り付けてデプロイします。
import io
import json
import os
import tempfile
import boto3
import google.auth
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
def get_google_credentials():
"""
Workload Identity Federationの認証構成を使ってGoogle認証情報を取得する
"""
# 環境変数からcredential configを読み取り、一時ファイルに書き出す
cred_config = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS_JSON")
if not cred_config:
raise ValueError("GOOGLE_APPLICATION_CREDENTIALS_JSON is not set")
# 一時ファイルに認証構成を書き出す
cred_file = os.path.join(tempfile.gettempdir(), "cred_config.json")
with open(cred_file, "w") as f:
f.write(cred_config)
# 環境変数にファイルパスを設定してADCで読み込む
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = cred_file
credentials, project = google.auth.default(
scopes=["https://www.googleapis.com/auth/drive.readonly"]
)
return credentials
def list_files_in_folder(service, folder_id):
"""
Google Driveの指定フォルダ内のファイル一覧を取得する
"""
query = f"'{folder_id}' in parents and trashed = false"
results = service.files().list(
q=query,
fields="files(id, name, mimeType)",
includeItemsFromAllDrives=True,
supportsAllDrives=True,
corpora="allDrives",
).execute()
return results.get("files", [])
def download_from_google_drive(service, file_id, file_name, mime_type):
"""
Google Driveからファイルをダウンロードして一時ファイルパスと最終ファイル名を返す
Google Workspace形式のファイルはエクスポートして変換する
"""
# Google Workspace形式のMIMEタイプとエクスポート先の対応表
export_mime_map = {
"application/vnd.google-apps.spreadsheet": {
"mime": "text/csv",
"ext": ".csv",
},
"application/vnd.google-apps.document": {
"mime": "application/pdf",
"ext": ".pdf",
},
"application/vnd.google-apps.presentation": {
"mime": "application/pdf",
"ext": ".pdf",
},
}
if mime_type in export_mime_map:
# Google Workspace形式はexport_mediaでエクスポート
export_info = export_mime_map[mime_type]
request = service.files().export_media(
fileId=file_id,
mimeType=export_info["mime"],
)
# 拡張子を変換後のものに変更(例: レポート → レポート.csv)
file_name = f"{file_name}{export_info['ext']}"
else:
# バイナリファイルはそのままダウンロード
request = service.files().get_media(
fileId=file_id,
supportsAllDrives=True,
)
tmp_path = os.path.join(tempfile.gettempdir(), file_name)
with open(tmp_path, "wb") as f:
downloader = MediaIoBaseDownload(f, request)
done = False
while not done:
_, done = downloader.next_chunk()
return tmp_path, file_name
def upload_to_s3(bucket, key, file_path):
"""
S3にファイルをアップロードする
"""
s3_client = boto3.client("s3")
s3_client.upload_file(file_path, bucket, key)
def lambda_handler(event, context):
"""
メインハンドラ
環境変数またはeventで指定したフォルダ内の全ファイルをS3に転送する
"""
bucket = event.get("bucket", os.environ.get("S3_BUCKET"))
s3_prefix = event.get("s3_prefix", "")
folder_id = event.get("folder_id", os.environ.get("GOOGLE_DRIVE_FOLDER_ID"))
if not bucket:
return {
"statusCode": 400,
"body": json.dumps({"error": "bucket is required"})
}
if not folder_id:
return {
"statusCode": 400,
"body": json.dumps({"error": "folder_id is required"})
}
try:
# 1. Google認証情報を取得
creds = get_google_credentials()
service = build("drive", "v3", credentials=creds)
# 2. フォルダ内のファイル一覧を取得
files = list_files_in_folder(service, folder_id)
transferred_files = []
for file in files:
# フォルダはスキップ
if file["mimeType"] == "application/vnd.google-apps.folder":
continue
file_name = file["name"]
# 3. Google Driveからファイルをダウンロード(Workspace形式は自動エクスポート)
tmp_path, actual_file_name = download_from_google_drive(
service, file["id"], file_name, file["mimeType"]
)
# 4. S3にアップロード
s3_key = f"{s3_prefix}{actual_file_name}" if s3_prefix else actual_file_name
upload_to_s3(bucket, s3_key, tmp_path)
# 5. 一時ファイルを削除
os.remove(tmp_path)
transferred_files.append({"name": actual_file_name, "s3_key": s3_key})
return {
"statusCode": 200,
"body": json.dumps({
"message": f"{len(transferred_files)} file(s) transferred successfully",
"files": transferred_files,
})
}
except Exception as e:
return {
"statusCode": 500,
"body": json.dumps({"error": str(e)})
}
4. 動作確認
Google Drive側にファイルを保存しておきます。

Lambda関数のテストイベントに以下のJSONを設定して実行します。
{
"s3_prefix": "from-drive/"
}
s3_prefixを指定すると、S3上のキーがfrom-drive/ファイル名のようにプレフィックス付きで保存されます。省略した場合はファイル名がそのままS3のキーになります。
正常に実行されると、以下のようなレスポンスが返ります。
{
"statusCode": 200,
"body": "{\"message\": \"1 file(s) transferred successfully\", \"files\": [{\"name\": \"test_s3_from_googledrive.txt\", \"s3_key\": \"from-drive/test_s3_from_googledrive.txt\"}]}"
}
S3バケットを確認して、ファイルがアップロードされていれば成功です。

Google Driveのフォルダ共有を忘れずに
サービスアカウントに対してGoogle Drive側の共有設定が別途必要です。Google Driveの転送元フォルダを共有していないと、403 Forbiddenエラーになります。
また、所属組織の設定によってはサービスアカウント(第三者のドメイン)に対してのフォルダの共有が制限されている可能性もありますのでご注意ください。
Google Workspaceのファイル(スプレッドシート等)はエクスポートが必要
Google スプレッドシートやGoogle ドキュメントなどのGoogle Workspace形式のファイルはget_media()では直接ダウンロードできません。fileNotDownloadableエラーが出た場合はこれが原因です。
今回のコードではMIMEタイプを判定してexport_media()で自動エクスポートするようにしています。デフォルトのエクスポート形式は以下の通りです。
| Google Workspace形式 | エクスポート形式 |
|---|---|
| スプレッドシート | CSV(.csv) |
| ドキュメント | PDF(.pdf) |
| スライド | PDF(.pdf) |
おわりに
Workload Identity Federationを使用することでサービスアカウントキーの発行をせずにGoogle DriveからS3にファイルを転送することができました。
初期設定のステップは多いですが、一度構築してしまえばサービスアカウントキーの管理から解放されるのは大きなメリットです。AWSとGoogle Cloudをまたいだファイル連携が必要な場面では、Workload Identity Federationを検討してみてはいかがでしょうか。






