Google Cloud から AWS へ OIDC Federation で認証してアクセスする
Google Cloud Service Account に対して OIDC Federation を構成して AWS へのアクセスを行う #
前提として、AWS へアクセスするための認証方法は大きく 2 種類あります。
- IAM ユーザーのアクセスキーのような長期的な認証情報を利用する
- 一時的な認証情報を取得し利用する
通常、セキュリティ的な観点から後者の方法が推薦されています。
Google Cloud 上で実行される処理から AWS リソースにアクセスを行う場合、そのコンテキストで利用可能なサービスアカウントを利用して AWS の一時的な認証情報を取得できるよう構成するのが推薦されます。具体的には、次のようなことを行います。
- (Google Cloud) サービスアカウントを作成します。
- (AWS) IAM ロールを作成し、その信頼ポリシーで 1. のサービスアカウントに対する OIDC Federation を構成します。また、必要に応じて適切な IAM ポリシーを割り当てます。
- (Google Cloud) サービスアカウントの ID トークンを用いて 2. で作成した IAM ロールに対して AssumeRoleWithWebIdentity を呼び出し、一時的な認証情報を取得します。この認証情報を利用して AWS リソースにアクセスします。
完全な例として Cloud Run Functions から S3 ファイルを取得する実装例を用意しました。以降ではこの例を踏まえた主要な部分を解説します。
1. Google Cloud サービスアカウントの作成 #
IAM ロールの設定に必要な値はサービスアカウント作成後にしか得られないため、まずサービスアカウントを作成しておきます。また、合わせて ID token audience として利用する値を準備しておきます。例としてランダム文字列を生成して利用するものとします。
resource "google_service_account" "this" {
account_id = "oidc-federation-example"
}
resource "random_password" "id_token_audience" {
keepers = { revision = 0 }
length = 32
special = false
}
2. AWS IAM ロールの作成 #
ステップ 1 で準備したサービスアカウントに対応した信頼ポリシーを持つ IAM ロールを作成します。ID token の検証項目として次の条件を適切に設定する必要があります。
accounts.google.com:oaud: ID token を生成する際に指定する audience 値です。ここでは事前に生成したランダム値を設定しておき、アプリケーションにおいてもこの値を利用して ID token を生成するようにします。accounts.google.com:sub: ID token の sub 値です。Google Cloud サービスアカウントの ID token においては、サービスアカウントの uniqueId になります。
data "aws_iam_policy_document" "assume_role_policy" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
effect = "Allow"
principals {
type = "Federated"
identifiers = ["accounts.google.com"]
}
condition {
test = "StringEquals"
variable = "accounts.google.com:oaud"
values = [random_password.id_token_audience.result]
}
condition {
test = "StringEquals"
variable = "accounts.google.com:sub"
values = [google_service_account.this.unique_id]
}
}
}
resource "aws_iam_role" "this" {
assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
name = "oidc-federation-example"
}
3. OIDC Federation による一時的な認証情報の取得 #
Google Cloud 上で実行される処理として、ここでは AWS SDK for JavaScript v3 を利用した例を示します。fromWebToken は AssumeRoleWithWebIdentity を利用するための認証情報プロバイダーです。この呼び出しに必要なサービスアカウントの ID token を取得するには google-auth-library を利用するのが簡単でしょう。
import { fromWebToken } from "@aws-sdk/credential-providers";
import { GoogleAuth } from "google-auth-library";
// Get AWS access credentials by using AssumeRoleWithWebIdentity.
const audience = process.env.AWS_OIDC_AUDIENCE; // random_password.id_token_audience.result
const roleArn = process.env.AWS_ROLE_ARN; // aws_iam_role.this.arn
const googleAuth = new GoogleAuth();
const googleClient = await googleAuth.getClient();
const webIdentityToken = await googleClient.fetchIdToken(audience);
const credentials = fromWebToken({ roleArn, webIdentityToken });
// Usage: For example, get s3 object.
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({ credentials });
const obj = await s3.send(new GetObjectCommand({ Bucket: "my-bucket", Key: "my-file.txt" }));
fromWebToken で得られるのは一時的な認証情報であり、有効期限が切れた場合には再取得が必要です。
次のような認証情報のリフレッシュを行う wrapper を実装しておくのがよいでしょう。
import { fromWebToken } from "@aws-sdk/credential-providers";
import { GoogleAuth } from "google-auth-library";
function fromGoogleToken(config) {
const { audience, durationSeconds, refreshOffsetSeconds, roleArn, roleSessionName } = config;
const googleAuth = new GoogleAuth();
const state = { credentials: null };
return async () => {
// check expiration
if (state.credentials?.expiration) {
const tokenExpiration = state.credentials.expiration.getTime();
let expirationThreshold = Date.now();
if (typeof refreshOffsetSeconds === "number" && refreshOffsetSeconds > 0) {
expirationThreshold += refreshOffsetSeconds * 1000;
}
if (tokenExpiration > expirationThreshold) {
return state.credentials;
}
}
// refresh access token
const client = await googleAuth.getClient();
const webIdentityToken = await client.fetchIdToken(audience);
const provider = fromWebToken({ durationSeconds, roleArn, roleSessionName, webIdentityToken });
const credentials = await provider();
state.credentials = credentials;
return credentials;
};
}
// Intiialize provider.
const audience = process.env.AWS_OIDC_AUDIENCE; // random_password.id_token_audience.result
const roleArn = process.env.AWS_ROLE_ARN; // aws_iam_role.this.arn
const credentials = fromGoogleToken({ audience, roleArn });
// Usage: For example, get s3 object.
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({ credentials });
const obj = await s3.send(new GetObjectCommand({ Bucket: "my-bucket", Key: "my-file.txt" }));まとめ #
Google Cloud サービスアカウントで OIDC Federation (AssumeRoleWithWebIdentity) を利用して AWS の一時的な認証情報の取得をする方法を説明しました。この方法では、アクセスキーのようなセキュリティリスクの高い認証情報を扱う必要が無く、より安全に AWS リソースへアクセスできます。
関連する話題
- 逆パターンの AWS から Google Cloud にアクセスする場合、OIDC を利用した類似の認証方法として Workload Identity があります。
- 今回紹介した Web Identity や前述の Workload identity の仕組みを利用したクロスクラウド認証以外のユースケースとしては、GitHub Actions ジョブからの認証があります。IdP の違いがあるだけで、概念や構成は共通していることが見て取れるでしょう: AWS, Google Cloud