この記事は個人ブログと同じ内容です
はじめに
GoogleCloudのWorkload Identityでの連携をAWSのLambdaを使用し、S3のファイルをGoogle CloudのCloud Storageにアップロードする方法を解説します。本記事では、Workload Identityを利用して、IAMロールとGoogle Cloudのサービスアカウントを紐付けることで、安全にアクセスできる環境を構築します。
lambdaではPythonを使用します。また、Terraformを使用します。
Workload Identityとは
Workload Identityとは、AWSや他のクラウドプロバイダーのIAMロールとGoogle Cloudのサービスアカウントを紐付けることで、シークレットキーを使わずにGoogle Cloudのリソースにアクセスできる仕組みです。
Workload Identity Poolとは
Workload Identity Poolは、外部のクラウドプロバイダーやIDプロバイダーからの認証情報を受け入れるためのリソースです。
Workload Identity Providerとは
Workload Identity Providerは、特定のクラウドプロバイダーのIDをWorkload Identity Poolに関連付けるためのリソースです。
処理の流れ
- LambdaがS3のファイルを取得する。
- Lambdaに紐づくIAMロールがWorkload IdentityによってGoogle Cloudのサービスアカウントの権限を借用する。
- LambdaがCloud Storageにファイルをアップロードする。
Workload Identityでは、AWSのIAMロールがGoogle Cloudのサービスアカウントの権限を借用することで、Google Cloudのサービスにアクセスできるようになります。
Terraformで構築していく
IAMロールとS3バケットの作成
LambdaにアタッチするIAMロールとS3バケットを作成します。 Lambdaの構築にはGoogle Cloudの設定が必要となるため、後で行います。
# IAM Role の作成 resource "aws_iam_role" "lambda_role" { name = "lambda_execution_role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } }] }) } # IAMポリシー(S3読み取り権限) resource "aws_iam_policy" "s3_read_policy" { name = "s3_read_policy" description = "Allow Lambda to read from S3" policy = jsonencode({ Version = "2012-10-17" Statement = [{ Action = ["s3:GetObject", "s3:ListBucket"] Effect = "Allow" Resource = [ aws_s3_bucket.lambda_bucket.arn, "${aws_s3_bucket.lambda_bucket.arn}/*" ] }] }) } # IAMポリシーをIAMロールにアタッチ resource "aws_iam_role_policy_attachment" "lambda_s3_read" { policy_arn = aws_iam_policy.s3_read_policy.arn role = aws_iam_role.lambda_role.name } # Lambda 用のS3バケット作成 resource "aws_s3_bucket" "lambda_bucket" { bucket = "test-bucket" }
サービスアカウントとCloud Storageの作成
Google Cloud上でサービスアカウントとCloud Storageを作成し、サービスアカウントにCloud Storageへのアクセス権を付与します。
resource "google_service_account" "main" { account_id = "gc-test-sa" display_name = "gc-test-sa" } resource "google_storage_bucket" "main" { name = "gc-test-storage" location = "ASIA-NORTHEAST1" force_destroy = true public_access_prevention = "enforced" storage_class = "REGIONAL" } resource "google_storage_bucket_iam_member" "main_viewer" { bucket = google_storage_bucket.receiver.name role = "roles/storage.objectViewer" member = "serviceAccount:${google_service_account.main.email}" } resource "google_storage_bucket_iam_member" "main_creator" { bucket = google_storage_bucket.receiver.name role = "roles/storage.objectCreator" member = "serviceAccount:${google_service_account.main.email}" }
Workload Identityの作成
Workload IdentityプールとWorkload Identityプロバイダを作成します。
resource "google_iam_workload_identity_pool" "aws_pool" { project = local.project_id workload_identity_pool_id = "test-aws-pool" display_name = "test-aws-pool" description = "aws pool for test" } resource "google_iam_workload_identity_pool_provider" "aws_provider" { workload_identity_pool_id = google_iam_workload_identity_pool.aws_pool.workload_identity_pool_id workload_identity_pool_provider_id = "test-aws-provider" display_name = "test-aws-provider" description = "test-aws provider" aws { account_id = local.aws_id #AWSアカウントのID } }
Workload IdentityでサービスアカウントとIAMロールの紐付け
Workload IdentityでサービスアカウントとIAMロールの紐付けをします。 principalSetの設定を書くときには、以下のような形である必要があります。
principalSet://iam.googleapis.com/${WorkloadIdentity Poolの名前}/attribute.aws_role/arn:aws:sts::${AWSのアカウントID}:assumed-role/${IAMロール名}
以下の部分はIAMロールのARNではないことに注意してください。
arn:aws:sts::${AWSのアカウントID}:assumed-role/${IAMロール名}
resource "google_service_account_iam_binding" "aws" { service_account_id = google_service_account.main.id role = "roles/iam.workloadIdentityUser" members = [ "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.aws_pool.name}/attribute.aws_role/arn:aws:sts::${local.aws_id}:assumed-role/${local.aws_role_name}" ] }
構成ファイルのダウンロード
以下の記事を参考に、Google Cloudのコンソール、またはgcloudのコマンドからダウンロードしてください。
https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds?hl=ja#console_3
※ワークスペース間をまたぐと、構成ファイルのダウンロードはGoogle Cloudのコンソールではできなくて、gcloudのコマンドを使用してダウンロードする必要がある。
Lambdaの作成
フォルダー構成は以下のようになります。
root ┣━ lambda ┃ ┗━ src ┃ ┣━ test.py ┃ ┣━ GoogleCloudの構成ファイル.json ┃ ┗━ lambda.zip ┣━ main.tf ┣━ variables.tf
今回はPythonで実装しています。
lambda/src/test.py
がソースコードとなり、archive_file
リソースが自動でZIP化してくれます。
環境変数 GOOGLE_APPLICATION_CREDENTIALS
に Google Cloud の構成ファイルのパスを設定するのを忘れないでください。
# `test.py` をZIP化 data "archive_file" "lambda_zip" { type = "zip" source_dir = "lambda/src" # ローカルの `lambda/src` をZIP化 output_path = "lambda/src/lambda.zip" } # Lambda関数の作成 resource "aws_lambda_function" "my_lambda" { function_name = "MyPythonLambda" role = aws_iam_role.lambda_role.arn runtime = "python3.9" handler = "test.lambda_handler" filename = data.archive_file.lambda_zip.output_path source_code_hash = data.archive_file.lambda_zip.output_base64sha256 environment { variables = { GOOGLE_APPLICATION_CREDENTIALS = "構成ファイルのパス/構成ファイル名" } } }
test.py
import boto3 from google.cloud import storage def get_from_s3(s3_bucket_name, s3_object_name): # S3クライアントの作成 s3 = boto3.client('s3') s3_object_path = f"{s3_bucket_name}/{s3_object_name}" tmp_file_path = f"/tmp/{s3_object_name}" # ファイルを Lambda の一時領域にダウンロード s3.download_file(s3_bucket_name, s3_object_name, tmp_file_path) print(f"{s3_object_path} was downloaded to {tmp_file_path}.") return tmp_file_path def upload_to_gcs(tmp_file_path, gcs_bucket_name): # Cloud Storage クライアントの作成 gcs = storage.Client() file_name = tmp_file_path.split('/')[-1] gcs_object_path = f"my_gcs_path/{file_name}" bucket = gcs.bucket(gcs_bucket_name) blob = bucket.blob(gcs_object_path) # オブジェクトを Cloud Storage バケットにアップロード blob.upload_from_filename(tmp_file_path) print(f"{tmp_file_path} was uploaded to {gcs_bucket_name}/{gcs_object_path}.") return None def lambda_handler(event, context): # event から各種情報を取得 s3_bucket_name = event['s3_bucket_name'] s3_object_name = event['s3_object_name'] gcs_bucket_name = event['gcs_bucket_name'] # オブジェクトを S3 から取得 tmp_file_path = get_from_s3( s3_bucket_name=s3_bucket_name, s3_object_name=s3_object_name ) # オブジェクトを Cloud Storage にアップロード upload_to_gcs( tmp_file_path=tmp_file_path, gcs_bucket_name=gcs_bucket_name ) return {'statusCode': 200}
注意点
- Workload Identity を利用して AWS IAM ロールと Google Cloud のサービスアカウントを紐付ける際、プリンシパルセットの設定が必要です。その際、AWS の IAM ロールの ARN はそのまま使用できないため、適切な形式に変換する必要があります。
- Lambda の環境変数に
GOOGLE_APPLICATION_CREDENTIALS
を設定する必要があります。GOOGLE_APPLICATION_CREDENTIALS
は Google Cloud のデフォルトの認証 (ADC) で必要になります。- 参考: Google Cloud Application Default Credentials
- ワークスペースをまたぐ場合、構成ファイルのダウンロードは Google Cloud コンソールでは行えません。
gcloud
コマンドを使用してダウンロードする必要があります。
感想
Google Cloudが初めてということもあり、概念の理解にかなり苦労しました。 今まではサービスアカウントでキーを発行する方法でやっていたのですが、今回のWorkload Identiytによる認証でよりセキュアにすることができました。 不明点や間違い、感想ありましたらコメントいただけると幸いです。
参考記事
https://laboratory.kiyono-co.jp/1638/gcp/
https://zenn.dev/nextbeat/articles/google-workload-identity
https://blog.g-gen.co.jp/entry/using-workload-identity-federation-with-aws