多くのエンジニアがVercelとGCPを連携させる際、「Workload Identity Federation(WIF)の設定が複雑で認証エラーにハマった」「サービスアカウントキーを使わずにセキュアに連携したいが、具体的な手順が分からない」といった課題に直面します。特に、OIDCトークンの audience の不一致やIAM権限の設定ミスは頻繁に発生し、デバッグに多くの時間を要します。
この記事では、VercelとGCPのセキュアな連携において、Workload Identity Federation (WIF) 設定時の認証エラーに焦点を当て、Terraform を用いた自動設定でこれを解決する具体的な手順と、ハマりどころの回避策を提示します。この記事を読めば、VercelとGCP間の安全かつ効率的な認証フローを構築し、サービスアカウントキーの管理から解放されるでしょう。
VercelとGCP連携におけるWorkload Identity Federationの重要性
このセクションでは、なぜVercelとGCPの連携にWorkload Identity Federation(WIF)が不可欠なのか、その背景とメリットを解説します。
従来のGCP連携では、サービスアカウントキーをJSONファイルとしてダウンロードし、Vercelの環境変数に直接設定する方法が一般的でした。しかし、これは長期的な認証情報をアプリケーションコードの近くに配置することになり、キーの漏洩リスクや管理の負担が大きな課題となります。
Workload Identity Federation (WIF) は、この課題を根本から解決します。VercelをOpenID Connect (OIDC) プロバイダとしてGCPに信頼させることで、Vercelが発行する短期間のOIDCトークンを使用してGCPリソースにアクセスできるようになります。これにより、サービスアカウントキーを一切使用せず、よりセキュアで運用しやすい認証フローを実現できます。
Vercelは公式ドキュメントでOIDC Federationを通じたGCPへの安全な接続を強く推奨しており、ビルドおよびFunctionの呼び出しごとに自動生成される短期間のトークンが利用されます。これにより、認証情報の有効期限が短くなり、セキュリティが大幅に向上します。
- 出典: Vercel Docs - Connect to Google Cloud with OIDC Federation
- 出典: Google Cloud 公式ドキュメント - Workload Identity Federation
TerraformによるGCP Workload Identity Federationの設定
このセクションでは、Terraform を用いてGCP Workload Identity Federation を設定する具体的な手順とコード例を示します。Infrastructure as Code (IaC) で設定を管理することで、再現性と保守性を高めます。
前提となるGCP APIの有効化
Workload Identity Federationを使用するためには、以下のGCP APIを有効にする必要があります。TerraformでGCPリソースを管理する場合、通常はgoogleプロバイダの設定で自動的に有効化されますが、手動で確認することも重要です。
-
iam.googleapis.com(IAM API) -
iamcredentials.googleapis.com(IAM Credentials API) -
sts.googleapis.com(Security Token Service API) -
cloudresourcemanager.googleapis.com(Cloud Resource Manager API)
Terraformコード例
以下のTerraformコードは、Vercelからのアクセスを許可するWorkload Identity Pool、Provider、GCPサービスアカウント、および関連するIAMポリシーを定義します。your-gcp-project-id、[TEAM_SLUG]、your-vercel-project-id はご自身の環境に合わせて置き換えてください。
# プロジェクト番号を取得するためのデータソース
# Workload Identity Pool Providerのallowed_audiencesで使用するため
data "google_project" "project" {
project_id = "your-gcp-project-id" # あなたのGCPプロジェクトIDに置き換える
}
# 1. Workload Identity Pool の作成
# 外部IDプロバイダからのIDをグループ化するための論理的なコンテナ
resource "google_iam_workload_identity_pool" "vercel_pool" {
project = "your-gcp-project-id" # あなたのGCPプロジェクトIDに置き換える
workload_identity_pool_id = "vercel-pool"
display_name = "Vercel Workload Identity Pool"
description = "Workload Identity Pool for Vercel deployments"
location = "global" # Workload Identity Poolはglobalロケーションにのみ作成可能
}
# 2. Workload Identity Pool Provider の作成 (Vercel OIDC)
# VercelをOIDC IDプロバイダとしてGCPに信頼させる設定
resource "google_iam_workload_identity_pool_provider" "vercel_provider" {
project = "your-gcp-project-id" # あなたのGCPプロジェクトIDに置き換える
workload_identity_pool_id = google_iam_workload_identity_pool.vercel_pool.workload_identity_pool_id
workload_identity_pool_provider_id = "vercel-provider"
display_name = "Vercel OIDC Provider"
description = "OIDC Provider for Vercel"
oidc {
# VercelのIssuer URI。チームモードの場合は `https://oidc.vercel.com/[TEAM_SLUG]` に置き換える
# Globalモードの場合は `https://oidc.vercel.com`
issuer_uri = "https://oidc.vercel.com"
# GCP推奨のオーディエンス。Vercel Function側で getVercelOidcToken にこのaudienceを渡す必要がある
# この値がVercel OIDCトークンの'aud'クレームと一致しないと認証エラーになる
allowed_audiences = ["//iam.googleapis.com/projects/${data.google_project.project.number}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.vercel_pool.workload_identity_pool_id}/providers/${google_iam_workload_identity_pool_provider.vercel_provider.workload_identity_pool_provider_id}"]
# 注意: Vercelのデフォルトオーディエンスを使用することも可能だが、GCP推奨ではない
# allowed_audiences = ["https://vercel.com/[TEAM_SLUG]"] # あなたのVercelチームスラッグに置き換える
}
# OIDCトークンのクレームをGCP属性にマッピングする
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.vercel_project_id" = "assertion['vercel.com/project-id']"
"attribute.vercel_environment" = "assertion['vercel.com/environment']"
"attribute.vercel_team_id" = "assertion['vercel.com/team-id']" # チームモードの場合
}
}
# 3. GCP サービスアカウントの作成
# VercelワークロードがGCPリソースにアクセスする際に「なりすます」サービスアカウント
resource "google_service_account" "vercel_sa" {
project = "your-gcp-project-id" # あなたのGCPプロジェクトIDに置き換える
account_id = "vercel-deployer-sa"
display_name = "Service Account for Vercel Deployments"
}
# 4. サービスアカウントにIAMロールを付与
# このサービスアカウントが実際にアクセスできるGCPリソースの権限を定義
resource "google_project_iam_member" "vercel_sa_storage_admin" {
project = "your-gcp-project-id" # あなたのGCPプロジェクトIDに置き換える
role = "roles/storage.objectAdmin" # 例: Cloud Storageへのアクセス権。必要に応じて変更する
member = "serviceAccount:${google_service_account.vercel_sa.email}"
}
# 5. Workload Identity Pool Provider からサービスアカウントへのなりすまし権限を付与
# VercelのIDが、このGCPサービスアカウントになりすますことを許可するポリシー
resource "google_service_account_iam_member" "vercel_sa_wif_impersonation" {
service_account_id = google_service_account.vercel_sa.name
role = "roles/iam.workloadIdentityUser" # このロールはなりすまし権限を付与する
# 特定のVercelプロジェクトからのアクセスのみを許可する例
# 'your-vercel-project-id' はあなたのVercelプロジェクトIDに置き換える
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.vercel_pool.name}/attribute.vercel_project_id/your-vercel-project-id"
# さらに特定の環境からのアクセスのみを許可する場合 (コメントアウトを解除して使用)
# condition {
# title = "Vercel Production Environment Access"
# description = "Allow access only from Vercel production environment for a specific project."
# expression = "attribute.vercel_project_id == 'your-vercel-project-id' && attribute.vercel_environment == 'production'"
# }
}
このTerraformコードを適用することで、VercelとGCP間のWorkload Identity Federationの基盤が自動的に構築されます。
Vercel FunctionでのGCP認証の実装
このセクションでは、Vercel Function内でWorkload Identity Federation を利用してGCPリソースにアクセスするためのTypeScriptコード例を提示します。@vercel/oidcとgoogle-auth-libraryを組み合わせることで、セキュアな認証フローを実現します。
Vercel Functionのコード例
以下のコードは、VercelのEdge FunctionからVertex AIにアクセスする例です。getVercelOidcTokenにGCP推奨のaudienceを渡す点が重要です。
// api/gcp-vertex-ai/route.ts
import { getVercelOidcToken } from '@vercel/oidc';
import { ExternalAccountClient } from 'google-auth-library';
import { VertexAI } from '@google-cloud/vertexai';
export const config = {
runtime: 'edge', // または 'nodejs'
};
export default async function handler(req: Request) {
// GCP Workload Identity Federation の設定値 (Vercel環境変数から取得)
const GCP_PROJECT_ID = process.env.GCP_PROJECT_ID;
const GCP_PROJECT_NUMBER = process.env.GCP_PROJECT_NUMBER;
const GCP_WORKLOAD_IDENTITY_POOL_ID = process.env.GCP_WORKLOAD_IDENTITY_POOL_ID;
const GCP_WORKLOAD_IDENTITY_PROVIDER_ID = process.env.GCP_WORKLOAD_IDENTITY_PROVIDER_ID;
const GCP_SERVICE_ACCOUNT_EMAIL = process.env.GCP_SERVICE_ACCOUNT_EMAIL;
if (!GCP_PROJECT_ID || !GCP_PROJECT_NUMBER || !GCP_WORKLOAD_IDENTITY_POOL_ID || !GCP_WORKLOAD_IDENTITY_PROVIDER_ID || !GCP_SERVICE_ACCOUNT_EMAIL) {
return new Response('Missing GCP environment variables', { status: 500 });
}
// Terraformで設定したallowed_audiencesと完全に一致するaudienceをgetVercelOidcTokenに渡す
const targetAudience = `//iam.googleapis.com/projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GCP_WORKLOAD_IDENTITY_POOL_ID}/providers/${GCP_WORKLOAD_IDENTITY_PROVIDER_ID}`;
const vercelOidcToken = await getVercelOidcToken({ audience: targetAudience });
if (!vercelOidcToken) {
// Vercel OIDCトークンが取得できなかった場合、認証失敗
return new Response('Unauthorized: Could not get Vercel OIDC token.', { status: 401 });
}
// ExternalAccountClientを初期化し、Vercel OIDCトークンを渡す
// これにより、GCP STS (Security Token Service) を介してGCP認証情報が取得される
const client = new ExternalAccountClient({
targetAudience: targetAudience, // STSへのトークン交換リクエストのaudience
// audienceはGCP Workload Identity Pool Providerで設定したallowed_audiencesと一致させる
audience: targetAudience,
subjectTokenType: 'urn:ietf:params:oauth:token-type:jwt',
tokenUrl: 'https://sts.googleapis.com/v1/token',
serviceAccountEmail: GCP_SERVICE_ACCOUNT_EMAIL,
credentialSource: {
token: vercelOidcToken, // Vercelが発行したOIDCトークン
},
});
// Google Cloudクライアントライブラリに認証済みクライアントを渡す
const vertexAI = new VertexAI({
project: GCP_PROJECT_ID,
location: 'us-central1', // 適切なリージョンに設定
authClient: client, // Workload Identity Federationで認証されたクライアントを使用
});
const generativeModel = vertexAI.getGenerativeModel({
model: 'gemini-pro',
});
const prompt = 'What is the capital of France?';
try {
const resp = await generativeModel.generateContent(prompt);
const text = resp.response.candidates[0].content.parts[0].text;
return new Response(text, {
headers: { 'Content-Type': 'text/plain' },
});
} catch (error: any) {
console.error('Error calling Vertex AI:', error);
return new Response(`Error calling Vertex AI: ${error.message}`, { status: 500 });
}
}
Vercel環境変数の設定
Vercelプロジェクトのダッシュボードで、以下の環境変数を設定します。これらはTerraformで作成したGCPリソースの識別子と、Vercel Function内のコードで利用されます。
-
GCP_PROJECT_ID: GCPプロジェクトID (例:my-gcp-project) -
GCP_PROJECT_NUMBER: GCPプロジェクト番号 (例:123456789012) -
GCP_WORKLOAD_IDENTITY_POOL_ID: 作成したWorkload Identity PoolのID (例:vercel-pool) -
GCP_WORKLOAD_IDENTITY_PROVIDER_ID: 作成したWorkload Identity Pool ProviderのID (例:vercel-provider) -
GCP_SERVICE_ACCOUNT_EMAIL: VercelがなりすますGCPサービスアカウントのメールアドレス (例:vercel-deployer-sa@my-gcp-project.iam.gserviceaccount.com)
これらの環境変数は、Preview、Productionなど、各デプロイ環境で適切に設定されていることを確認してください。
VercelとGCPのWorkload Identity Federationで詰まった時のトラブルシューティング
このセクションでは、VercelとGCPのWorkload Identity Federation 設定時によく発生する認証エラーとその回避策について解説します。これらのハマりどころを理解しておくことで、スムーズなデバッグが可能になります。
1. OIDCトークン検証失敗 (Issuer URI, Audience, Attribute Mappingの不一致)
最も頻繁に発生する認証エラーの一つです。GCPのSecurity Token Service (STS) がVercelから受け取ったOIDCトークンを検証できない場合に発生します。
-
エラー内容の兆候: Vercel Function側で
getVercelOidcTokenが失敗する、またはExternalAccountClientの初期化後にGCPリソースへのアクセスが403 Forbiddenとなる。GCPの監査ログ (Security Token Service API) にINVALID_ARGUMENTやFAILED_PRECONDITIONが記録されます。 -
ハマりどころと回避策:
-
Issuer URIの不一致:
-
ハマりどころ: Terraformの
google_iam_workload_identity_pool_providerリソースのoidc.issuer_uriが、VercelのOIDC Issuer URLと正確に一致していない。特に、Vercelのチームモード(https://oidc.vercel.com/[TEAM_SLUG])とグローバルモード(https://oidc.vercel.com)の切り替えミスや、[TEAM_SLUG]のスペルミス。 - 回避策: Vercelのプロジェクト設定で自身のIssuer URLを確認し、Terraformの設定と完全に一致させます。
-
ハマりどころ: Terraformの
-
Audienceの不一致:
-
ハマりどころ: Vercelが発行するOIDCトークンの
audクレームが、GCP Workload Identity Pool Providerのoidc.allowed_audiencesで許可された値と一致しない。特に、GCP推奨のオーディエンス形式(//iam.googleapis.com/...)を使用する場合、Vercel Function内でgetVercelOidcToken({ audience: '...' })にその値を明示的に渡す必要があります。このaudienceがTerraformで設定したallowed_audiencesのいずれかと完全に一致している必要があります。 -
回避策: Terraformの
allowed_audiencesに設定したGCP推奨のオーディエンス文字列を、Vercel Function内のgetVercelOidcToken({ audience: targetAudience })のtargetAudience変数に設定します。この文字列は、Terraformのoutputで出力してVercel環境変数に設定すると確実です。
-
ハマりどころ: Vercelが発行するOIDCトークンの
-
Attribute Mappingの不一致:
-
ハマりどころ: OIDCトークン内のクレームと、GCP Workload Identity Pool Providerの
attribute_mappingで定義された属性マッピングが一致していない。例えば、assertion['vercel.com/project-id']がトークンに存在しない、またはキー名が間違っている場合。 -
回避策: OIDCトークンをデコードツール(例: jwt.io)で確認し、
sub、aud、vercel.com/project-idなどのクレームが期待通りに含まれているか、またキー名が正しいかを確認します。GCPの監査ログでattribute_mappingの解析失敗メッセージがないか確認してください。
-
ハマりどころ: OIDCトークン内のクレームと、GCP Workload Identity Pool Providerの
-
Issuer URIの不一致:
-
デバッグのヒント:
- GCPの監査ログ (
gcloud logging read "resource.type=audited_resource AND protoPayload.serviceName=sts.googleapis.com AND protoPayload.methodName=google.identity.sts.v1.SecurityTokenService.ExchangeToken") を確認し、STSトークン交換の失敗原因を詳細に調査します。 - Vercel Functionのデプロイログやランタイムログを確認し、
getVercelOidcTokenの呼び出しが成功しているか、渡しているaudienceが正しいかを確認します。
- GCPの監査ログ (
2. IAM権限不足
サービスアカウントになりすますことはできても、そのサービスアカウントがGCPリソースにアクセスする権限を持っていない場合に発生します。
-
エラー内容の兆候: GCPリソースへのアクセス時に
PERMISSION_DENIEDエラーが発生する。GCPの監査ログ (IAM API) にPERMISSION_DENIEDが記録されます。 -
ハマりどころと回避策:
-
GCPサービスアカウントの権限不足:
-
ハマりどころ:
google_service_account.vercel_saに、VercelからアクセスしたいGCPリソースに対する適切なIAMロールが付与されていない。 -
回避策: サービスアカウントに、必要な最小限の権限(最小権限の原則)を付与します。例えば、Cloud Storageへの読み書きが必要であれば
roles/storage.objectAdminを、Vertex AIへのアクセスが必要であればroles/aiplatform.userなどを付与します。
-
ハマりどころ:
-
Workload Identity Userロールの不備:
-
ハマりどころ: Workload Identity Pool ProviderからGCPサービスアカウントへの
roles/iam.workloadIdentityUserロールが付与されていない、またはprincipalSetの条件が正しくない。 -
回避策:
google_service_account_iam_memberリソースで、roles/iam.workloadIdentityUserロールが正しく設定されており、memberのprincipalSetがVercelのIDと一致していることを確認します。特に、attribute.vercel_project_idやattribute.vercel_team_idの値がVercelプロジェクト/チームの実際のIDと一致しているか確認します。conditionを使用している場合は、その条件式が正しく評価されるか確認します。
-
ハマりどころ: Workload Identity Pool ProviderからGCPサービスアカウントへの
-
GCPサービスアカウントの権限不足:
3. 環境変数の設定ミス
Vercel Function内で必要なGCP関連の環境変数が正しく設定されていない場合に発生します。
-
エラー内容の兆候: Vercel Function内で
process.env.GCP_PROJECT_NUMBERなどがundefinedになり、認証クライアントの初期化に失敗する。 -
ハマりどころと回避策:
-
環境変数の未設定/スペルミス:
-
ハマりどころ: Vercelプロジェクトの環境変数に、必要なGCP関連の変数が設定されていない、またはキー名がコードと一致していない(例:
GCP_PROJECT_IDとGCP_PROJECT_IDのスペルミス)。 -
回避策: Vercelプロジェクトのダッシュボードで、すべての必要な環境変数(
GCP_PROJECT_ID,GCP_PROJECT_NUMBER,GCP_WORKLOAD_IDENTITY_POOL_ID,GCP_WORKLOAD_IDENTITY_PROVIDER_ID,GCP_SERVICE_ACCOUNT_EMAIL)が正しく設定されていることを確認します。
-
ハマりどころ: Vercelプロジェクトの環境変数に、必要なGCP関連の変数が設定されていない、またはキー名がコードと一致していない(例:
-
デプロイ環境ごとの設定漏れ:
- ハマりどころ: Vercelのデプロイ環境(Preview, Production, Development)ごとに環境変数が正しく設定されていない。
- 回避策: 各デプロイ環境で同じ値が設定されているか、または環境に応じた適切な値が設定されているかを確認します。
-
環境変数の未設定/スペルミス:
これらのトラブルシューティング手順を踏むことで、VercelとGCPのWorkload Identity Federation 連携における認証エラーの多くを解決できるはずです。
設計上のトレードオフとベストプラクティス
このセクションでは、VercelとGCPのWorkload Identity Federation を導入する際の設計上の考慮事項と、セキュリティ・運用効率を高めるためのベストプラクティスを解説します。
サービスアカウントキーの廃止
Workload Identity Federationの最大のメリットは、長期的なサービスアカウントキーの管理とそれに伴うセキュリティリスクを排除できる点です。可能な限りWIFに移行し、サービスアカウントキーの使用を避けるべきです。これにより、キーの漏洩リスクが大幅に低減し、キーローテーションの運用負担もなくなります。
最小権限の原則
GCPサービスアカウントには、Vercelワークロードが必要とする最小限のIAMロールのみを付与します。これにより、万が一認証情報が漏洩した場合でも、そのサービスアカウントが悪用できる範囲が限定され、被害を最小限に抑えることができます。例えば、Cloud Storageへの読み取りのみが必要であればroles/storage.objectViewerを付与し、roles/storage.adminのような広範な権限は避けるべきです。
Workload Identity Pool の構造と属性マッピング
- 環境ごとのPool: 開発、ステージング、本番など、環境ごとに専用のWorkload Identity Poolを作成することを推奨します。これにより、環境間の権限分離が明確になり、誤ったアクセスを防ぐことができます。
-
属性マッピングと条件:
- 外部IDがGoogle Cloudプリンシパルになる方法を定義する属性マッピングには、不変で再利用できない属性を使用します。VercelのOIDCトークンに含まれる
vercel.com/project-idやvercel.com/team-idなどをマッピングに含めることで、よりきめ細かいアクセス制御が可能になります。 -
google_service_account_iam_memberリソースのconditionブロックを使用して、特定のVercelプロジェクトや環境からのアクセスのみを許可するなど、アクセスをさらに制限します。これにより、セキュリティが向上し、意図しないアクセスを防げます。
- 外部IDがGoogle Cloudプリンシパルになる方法を定義する属性マッピングには、不変で再利用できない属性を使用します。VercelのOIDCトークンに含まれる
監査ログの有効化
Workload Identity Federationを使用するGCPプロジェクト内のSecurity Token Service APIとIAM APIの両方でデータアクセスログを有効にすることを強く推奨します。これにより、外部IDがどのように認証され、どのサービスアカウントになりすましたかという監査証跡を追跡できます。認証エラーが発生した際のトラブルシューティングにも不可欠な情報源となります。
TerraformによるIaC
Terraform などのIaCツールでWorkload Identity Federation の設定自体を管理することは、設定の再現性、バージョン管理、レビュープロセスを確保し、ヒューマンエラーを減らす上で非常に重要です。手動での設定は、設定漏れやミスを招きやすく、大規模なシステムでは管理が困難になります。
まとめ
この記事では、VercelとGCPの連携において、Workload Identity Federation (WIF) を用いてセキュアな認証フローを構築する方法を解説しました。
重要なポイントは以下の通りです。
- Workload Identity Federation は、サービスアカウントキーを使用せずにVercelからGCPリソースにアクセスするための、よりセキュアで推奨される方法です。
- Terraform を活用することで、Workload Identity Pool、Provider、GCPサービスアカウント、およびIAMポリシーの設定を自動化し、IaCによる管理を実現できます。
- Vercel Function内では、
@vercel/oidcとgoogle-auth-libraryを組み合わせ、GCP推奨のaudienceを明示的に指定してOIDCトークンを取得し、GCP認証を行います。 - 認証エラーの多くは、Issuer URI、Audience、Attribute Mappingの不一致、IAM権限不足、Vercel環境変数の設定ミスによって発生します。GCPの監査ログを活用した丁寧なデバッグが解決の鍵となります。
- 最小権限の原則、環境ごとのPool分離、属性マッピングと条件によるきめ細かいアクセス制御、監査ログの有効化、TerraformによるIaC管理がベストプラクティスです。
Workload Identity Federationは、初期設定に手間がかかる場合がありますが、一度構築してしまえば、長期的に見てセキュリティと運用効率を大きく向上させます。ぜひ、この記事を参考にVercelとGCPのセキュアな連携を実現してください。
さらなる詳細については、Google Cloud 公式ドキュメント - Workload Identity Federation および Vercel Docs - Connect to Google Cloud with OIDC Federation をご参照ください。