多くの開発者が、VercelからGCPリソースへアクセスする際に「サービスアカウントキーを環境変数に直書き」という、セキュリティリスクの高い方法に陥りがちです。しかし、この方法はキーの漏洩リスクだけでなく、管理の手間も膨大になります。この記事では、VercelからGCPへのセキュアな認証を、TerraformとWorkload Identity Federation (WIF) を用いて自動設定・管理する具体的な方法を解説し、これらの課題を根本から解決します。
VercelからGCPへの認証における課題とWorkload Identity Federationの優位性
このセクションでは、従来のVercelとGCP間の認証が抱える課題と、Workload Identity Federation (WIF) がそれらをどのように解決するかを解説します。
従来のサービスアカウントキー認証の課題
VercelからGCPリソースへアクセスする際、最も手軽な方法はGCPサービスアカウントキーを生成し、そのJSONファイルをVercelの環境変数に設定することでした。しかし、この方法は以下のような深刻な課題を抱えています。
- セキュリティリスク: サービスアカウントキーは永続的なクレデンシャルであり、一度漏洩するとGCPリソースへの無制限なアクセスを許してしまう可能性があります。
- キー管理の煩雑さ: キーの定期的なローテーションや失効処理は手動で行う必要があり、運用負荷が高いです。
- 環境ごとの分離の困難さ: 開発、ステージング、本番環境で異なるキーを管理するのは手間がかかり、誤って本番環境のキーを開発環境で使ってしまうなどのヒューマンエラーのリスクがあります。
Workload Identity Federation (WIF) とは
Workload Identity Federation (WIF) は、GCP外部のワークロード(Vercelなど)が、サービスアカウントキーを使用せずにGCPリソースに安全にアクセスできるようにするGCPの機能です。
VercelがOpenID Connect (OIDC) プロバイダーとして発行する短期間有効なトークンをGCPが検証し、そのトークンに基づいてGCPサービスアカウントの権限を借用させることで、キーレスでセキュアなアクセスを実現します。これにより、サービスアカウントキーの漏洩リスクや管理の煩雑さから解放されます。
前提と環境設定
ここでは、VercelとGCPのWorkload Identity FederationをTerraformで設定するために必要な前提知識と環境設定について説明します。
必要なツールとバージョン
- Terraform: v1.0.0以上 (本記事では v1.7.x で検証)
- Terraform Google Provider: v5.x以上
- Terraform Vercel Provider: v0.3.0以上
- Node.js: v18以上 (Vercel FunctionでのGCPアクセス用)
- GCPプロジェクト: 有効なGCPプロジェクトID
- Vercelプロジェクト: 有効なVercelプロジェクトIDとチームID
Workload Identity Federationの主要コンポーネント
- Workload Identity Pool: 外部IDを管理するためのGCP内の論理的なグループです。環境ごとに作成することを推奨します。
-
Workload Identity Pool Provider: Google Cloudと外部IDプロバイダー(Vercel)間の信頼関係を定義します。Vercelの場合、
https://oidc.vercel.comをIssuer URLとして設定します。 -
属性マッピング (Attribute Mapping): 外部IDプロバイダーから提供されるOIDCトークン内の属性(例:
sub、vercel.com/project-id)を、Google Cloudのプリンシパル属性に変換する方法を定義します。これにより、きめ細かいIAM条件設定が可能になります。 - GCPサービスアカウント: VercelワークロードがGCPリソースにアクセスするために借用するアカウントです。このサービスアカウントに、必要なGCPリソースへの権限を付与します。
-
roles/iam.workloadIdentityUser: Workload Identity Pool ProviderがGCPサービスアカウントを借用するために必要なIAMロールです。
TerraformによるVercel GCP Workload Identity Federationの自動設定
このセクションでは、Terraformを使ってGCPのWorkload Identity FederationリソースとVercelプロジェクトの環境変数を定義し、自動的に設定する具体的なコードと手順を解説します。
1. GCP Workload Identity Federationリソースの定義
まず、GCPプロジェクト内にWorkload Identity Pool、Workload Identity Pool Provider、そしてGCPサービスアカウントを作成し、必要なIAMロールを付与します。
main.tf
# main.tf
# 1. Workload Identity Poolの作成
# 環境ごとにプールを分離することで、権限管理の粒度を高める
resource "google_iam_workload_identity_pool" "vercel_pool" {
project = var.gcp_project_id
workload_identity_pool_id = "vercel-pool-${var.environment}"
display_name = "Vercel Workload Identity Pool for ${var.environment}"
description = "Workload Identity Pool for Vercel deployments in ${var.environment} environment."
}
# 2. Workload Identity Pool Providerの作成 (Vercel OIDC IdP)
# VercelをOIDCプロバイダーとして信頼し、トークンを検証する設定
resource "google_iam_workload_identity_pool_provider" "vercel_provider" {
project = var.gcp_project_id
workload_identity_pool_id = google_iam_workload_identity_pool.vercel_pool.workload_identity_pool_id
workload_identity_pool_provider_id = "vercel-provider-${var.environment}"
display_name = "Vercel OIDC Provider for ${var.environment}"
description = "OIDC Provider for Vercel in ${var.environment} environment."
oidc {
issuer_uri = "https://oidc.vercel.com"
# allowed_audiencesにはVercelのチームIDを設定する (プロジェクトIDではない点に注意)
allowed_audiences = [var.vercel_team_id]
# VercelのOIDCトークン属性をGCPの属性にマッピング
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.project_id" = "assertion['vercel.com/project-id']"
"attribute.environment" = "assertion['vercel.com/environment']"
}
}
}
# 3. GCPサービスアカウントの作成
# Vercel Functionが実際にGCPリソースにアクセスする際に借用するアカウント
resource "google_service_account" "vercel_sa" {
project = var.gcp_project_id
account_id = "vercel-sa-${var.environment}"
display_name = "Service Account for Vercel in ${var.environment}"
}
# 4. サービスアカウントにWorkload Identity Userロールを付与
# これにより、Workload Identity Pool Providerがこのサービスアカウントを借用できるようになる
resource "google_service_account_iam_member" "vercel_sa_workload_identity_user" {
service_account_id = google_service_account.vercel_sa.name
role = "roles/iam.workloadIdentityUser"
# principalSetにはWorkload Identity Pool Providerの完全なリソース名を指定
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.vercel_pool.name}/subject/${google_iam_workload_identity_pool_provider.vercel_provider.name}"
# より厳密な条件付けの例 (特定のVercelプロジェクトIDと環境に限定する場合):
# この設定により、特定のVercelプロジェクトとデプロイ環境からのみサービスアカウントの借用を許可できる
# condition {
# title = "VercelProjectAndEnvCondition"
# description = "Allow access only for specific Vercel project and environment"
# expression = "resource.name.startsWith('projects/${var.gcp_project_id}/serviceAccounts/${google_service_account.vercel_sa.account_id}@') && attribute.project_id == '${var.vercel_project_id}' && attribute.environment == '${var.environment}'"
# }
}
# 5. サービスアカウントに必要なGCPリソースへの権限を付与 (例: Cloud Storageへのアクセス)
# 最小権限の原則に基づき、Vercel Functionが必要とする権限のみを付与する
resource "google_project_iam_member" "storage_object_admin" {
project = var.gcp_project_id
role = "roles/storage.objectAdmin" # 例: Cloud Storageのオブジェクト管理者ロール
member = "serviceAccount:${google_service_account.vercel_sa.email}"
}
variables.tf
# variables.tf
variable "gcp_project_id" {
description = "The ID of the GCP project."
type = string
}
variable "vercel_project_id" {
description = "The ID of the Vercel project."
type = string
}
variable "vercel_team_id" {
description = "The ID of the Vercel team."
type = string
}
variable "environment" {
description = "The deployment environment (e.g., dev, staging, prod)."
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "The environment must be one of 'dev', 'staging', or 'prod'."
}
}
2. Vercelプロジェクトの環境変数設定
次に、Terraform Vercel Providerを利用して、Vercelプロジェクトに必要な環境変数を設定します。これにより、GCPへの認証に必要な情報がVercel Functionに自動的に渡されます。
vercel.tf
# vercel.tf
# Vercelプロジェクトの環境変数設定例
# これらの環境変数は、Vercel FunctionがGCP Workload Identity Federationで認証するために必要
resource "vercel_project_environment_variable" "gcp_project_id_env" {
project_id = var.vercel_project_id
key = "GCP_PROJECT_ID"
value = var.gcp_project_id
target = ["production", "preview", "development"] # 全てのデプロイ環境に適用
}
resource "vercel_project_environment_variable" "gcp_service_account_email_env" {
project_id = var.vercel_project_id
key = "GCP_SERVICE_ACCOUNT_EMAIL"
value = google_service_account.vercel_sa.email
target = ["production", "preview", "development"]
}
resource "vercel_project_environment_variable" "gcp_workload_identity_provider_env" {
project_id = var.vercel_project_id
key = "GCP_WORKLOAD_IDENTITY_PROVIDER"
# Workload Identity Pool Providerの完全なリソース名を環境変数に設定
value = google_iam_workload_identity_pool_provider.vercel_provider.name
target = ["production", "preview", "development"]
}
resource "vercel_project_environment_variable" "gcp_audience_env" {
project_id = var.vercel_project_id
key = "GCP_AUDIENCE"
# GCP_AUDIENCEは、トークン交換の際に指定するターゲット audience URL
value = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${google_service_account.vercel_sa.email}:generateAccessToken"
target = ["production", "preview", "development"]
}
3. Terraformの実行
上記のTerraformファイルを配置後、以下のコマンドでリソースをプロビジョニングします。
terraform init
terraform plan -var="gcp_project_id=your-gcp-project" -var="vercel_project_id=your-vercel-project" -var="vercel_team_id=your-vercel-team-id" -var="environment=dev"
terraform apply -var="gcp_project_id=your-gcp-project" -var="vercel_project_id=your-vercel-project" -var="vercel_team_id=your-vercel-team-id" -var="environment=dev"
your-gcp-project、your-vercel-project、your-vercel-team-id は実際のIDに置き換えてください。
Vercel FunctionでのGCPクレデンシャル使用
ここでは、Vercel Function(Next.js API Routeなど)から、Workload Identity Federationを通じてGCPリソースにアクセスする具体的なNode.jsコード例を示します。
必要なライブラリのインストール
Vercel OIDCトークンを取得するための @vercel/oidc と、GCP認証を行う google-auth-library、そしてGCPサービスにアクセスするためのクライアントライブラリ(例: @google-cloud/storage)をインストールします。
npm install @vercel/oidc google-auth-library @google-cloud/storage
# または
yarn add @vercel/oidc google-auth-library @google-cloud/storage
Vercel Functionでの実装例
以下のコードは、Vercel Function内でOIDCトークンを取得し、それを使ってGCP Cloud Storageのバケット一覧を取得する例です。
api/gcp-storage.ts
// api/gcp-storage.ts (Vercel Function, Next.js API Routeなど)
import { getVercelOidcToken } from '@vercel/oidc';
import { GoogleAuth } from 'google-auth-library';
import { Storage } from '@google-cloud/storage';
export default async function handler(req: any, res: any) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method Not Allowed' });
}
try {
// 1. Vercel OIDCトークンを取得
// このトークンはVercelによって自動的に生成され、短期間のみ有効
const vercelOidcToken = await getVercelOidcToken();
// 2. GoogleAuthクライアントの設定
// Workload Identity Federationの設定を使って認証を行う
const auth = new GoogleAuth({
credentials: {
client_email: process.env.GCP_SERVICE_ACCOUNT_EMAIL,
// client_id は Workload Identity Provider の完全なリソース名
client_id: process.env.GCP_WORKLOAD_IDENTITY_PROVIDER,
universe_domain: 'googleapis.com', // 通常は 'googleapis.com'
// token_url は GCP_AUDIENCE 環境変数で設定されたURL
token_url: process.env.GCP_AUDIENCE,
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
subject_token: vercelOidcToken, // Vercelから取得したOIDCトークン
},
});
// 認証クライアントを取得
const authClient = await auth.getClient();
// Cloud Storageクライアントを初期化し、認証クライアントを渡す
const storage = new Storage({ authClient, projectId: process.env.GCP_PROJECT_ID });
// 例: Cloud Storageバケットの一覧を取得
const [buckets] = await storage.getBuckets();
const bucketNames = buckets.map(bucket => bucket.name);
res.status(200).json({ buckets: bucketNames });
} catch (error: any) {
console.error('Error accessing GCP:', error);
res.status(500).json({ error: `Failed to access GCP: ${error.message}` });
}
}
このコードでは、@vercel/oidc の getVercelOidcToken() 関数でVercelが発行するOIDCトークンを取得し、それを google-auth-library の GoogleAuth クラスに渡すことで、サービスアカウントキーなしでGCPへの認証を実現しています。
よくあるエラーとハマりどころ
Vercel GCP Workload Identity Federationの設定は複雑なため、いくつかの一般的なエラーに遭遇する可能性があります。ここでは、それらの問題と解決策を解説します。
1. OIDCトークンの検証失敗 (Issuer URI, Audience, Attribute Mappingの不一致)
-
ハマりどころ: Workload Identity Pool Providerの設定が、Vercelから発行されるOIDCトークンの内容と一致していないと、GCPがトークンを検証できません。特に
allowed_audiencesにVercelのプロジェクトIDを設定してしまうミスが多いです。 -
回避策:
-
google_iam_workload_identity_pool_providerリソースのoidcブロックで、issuer_uriがhttps://oidc.vercel.comと正確に一致していることを確認します。 -
allowed_audiencesにはVercelのチームIDが正確に設定されていることを確認してください。var.vercel_team_idを使用しているか再確認しましょう。 -
attribute_mappingでgoogle.subjectがassertion.subに、attribute.project_idがassertion['vercel.com/project-id']に、attribute.environmentがassertion['vercel.com/environment']に正しくマッピングされているか確認します。 - GCPコンソールでWorkload Identity Pool Providerの詳細ページを確認し、設定がTerraformと一致しているか目視でチェックすることも有効です。
-
2. サービスアカウントの権限不足 (iam.serviceAccounts.getAccessToken 拒否など)
-
ハマりどころ: Workload Identity Federationで借用するGCPサービスアカウントに、必要なIAMロールが付与されていないと、GCPリソースへのアクセスが拒否されます。特に、
roles/iam.workloadIdentityUserロールや、対象GCPリソースへの具体的なロールの不足が頻発します。 -
回避策:
-
google_service_account_iam_memberリソースで、サービスアカウントにroles/iam.workloadIdentityUserロールが付与されていることを確認します。memberの値がprincipalSet://iam.googleapis.com/${google_iam_workload_identity_pool.vercel_pool.name}/subject/${google_iam_workload_identity_pool_provider.vercel_provider.name}の形式で正しく設定されているか確認してください。 - Vercel FunctionからアクセスするGCPリソース(例: Cloud Storage、BigQuery)に対して、GCPサービスアカウントに必要最小限のIAMロールが付与されていることを確認します。例えばCloud Storageへのアクセスには
roles/storage.objectViewerやroles/storage.objectAdminなどが必要です。 - IAMバインディングに条件(
conditionブロック)を設定している場合、その条件がOIDCトークンの属性と一致しているか、また意図せずアクセスを制限していないか確認します。
-
3. 環境変数設定の不備 (特にGCP_AUDIENCEの誤りやVercel環境ごとの設定ミス)
-
ハマりどころ: Vercelプロジェクトの環境変数が正しく設定されていない、またはVercelのデプロイ環境(Production/Preview/Development)ごとに適切な値が設定されていない場合に、GCPへの認証が失敗します。
GCP_AUDIENCEのURLが間違っていたり、GCP_WORKLOAD_IDENTITY_PROVIDERにプロバイダのIDではなく名前以外の値が設定されていたりすることがあります。 -
回避策:
-
vercel_project_environment_variableリソースで、GGCP_PROJECT_ID、GCP_SERVICE_ACCOUNT_EMAIL、GCP_WORKLOAD_IDENTITY_PROVIDER、GCP_AUDIENCEが各Vercelデプロイ環境 (production,preview,development) に正しく設定されていることを確認します。 -
GCP_WORKLOAD_IDENTITY_PROVIDERには、projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/providers/${PROVIDER_ID}の形式のプロバイダの完全なリソース名を設定します。Terraformの出力google_iam_workload_identity_pool_provider.vercel_provider.nameを使用するのが確実です。 -
GCP_AUDIENCEには、https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${google_service_account.vercel_sa.email}:generateAccessTokenの形式のURLを設定します。これはWorkload Identity Pool Providerの詳細ページでも確認できます。 - VercelのBuild LogsやRuntime Logsを詳細に確認し、環境変数に関するエラーメッセージや認証失敗のログがないか探します。
-
まとめと次のステップ
この記事では、VercelからGCPリソースへの安全な認証を、TerraformとWorkload Identity Federation (WIF) を用いて自動的に設定・管理する方法を解説しました。
重要なポイントは以下の通りです。
- サービスアカウントキーの代わりに、Workload Identity Federationを利用したキーレス認証を導入することで、セキュリティリスクを大幅に軽減できます。
- Terraformを用いることで、GCPのWIFリソースとVercelの環境変数を**Infrastructure as Code (IaC)**として一元的に管理し、設定の再現性と自動化を実現できます。
-
@vercel/oidcとgoogle-auth-libraryを組み合わせることで、Vercel FunctionからGCPリソースへシームレスにアクセスできます。 -
allowed_audiencesにVercelのチームIDを設定することや、環境変数の正確な設定など、具体的なハマりどころとその回避策を理解することが重要です。
この設定は初期構築に手間がかかるものの、一度導入すれば、キー管理の運用負荷をなくし、より堅牢でセキュアなシステムを構築できます。
次のステップとして、Terraformの condition ブロックを活用し、VercelのプロジェクトIDやデプロイ環境に基づいてサービスアカウントの借用をさらにきめ細かく制御することで、最小権限の原則を徹底することを検討してください。