多くのVercelユーザーがGCP連携で悩むのが、サービスアカウントキーの管理です。開発・デプロイ環境ごとにキーを安全に運用するのは非常に手間がかかる上、漏洩リスクが常に付きまといます。「これを知らないとキー管理の地獄にハマる」と言っても過言ではありません。
この記事では、VercelとGCPを安全に連携させるための「Workload Identity Federation」の具体的な実装パターンを解説します。サービスアカウントキーを一切使用せず、堅牢な認証を確立しつつ、開発体験を損なわない秘訣を3つのステップでご紹介します。
VercelとGCP連携:サービスアカウントキーはもう古い!Workload Identity Federationの基本
VercelでNext.jsなどのフロントエンドアプリケーションを開発し、バックエンドのデータストアやAPI基盤にGCPを利用するケースは非常に多いでしょう。しかし、Vercel FunctionsからGCPリソースにアクセスする際の認証には、セキュリティと運用効率の両面で課題がありました。
従来の認証方法では、GCPサービスアカウントキーをJSONファイルとして生成し、Vercelの環境変数に設定する方法が一般的でした。しかし、この方法は以下のような問題点を抱えています。
- キー漏洩リスク: JSONファイルが誤ってリポジトリにコミットされたり、環境変数が不適切に管理されたりすると、GCPリソースへの不正アクセスを許してしまう可能性があります。
- キー管理の煩雑さ: キーの定期的なローテーション、環境ごとのキー管理、アクセス権限の付与など、運用負荷が高まります。
- 最小権限の原則の適用困難: 特定のVercelプロジェクトやデプロイに限定したきめ細やかな権限設定が難しい場合があります。
これらの課題を解決するのが、GCPのWorkload Identity Federation (WIF) です。WIFは、外部のIDプロバイダー(この場合はVercel)が発行する一時的なトークンをGCPが検証し、GCPサービスアカウントの権限を借用させることで、サービスアカウントキーを使用しない「キーレス認証」を実現します。
この記事では、VercelとGCPを安全に連携させるための3つの秘訣として、Workload Identity Federationの設定、Vercel環境変数の設定、そしてVercel FunctionからのGCPサービスアクセス方法を具体的なコードと共に解説します。
Workload Identity Federationとは?
Workload Identity Federationは、GCP外部のワークロード(Vercel, GitHub Actions, AWSなど)が、サービスアカウントキーなしでGCPリソースにアクセスするための仕組みです。Vercelが提供するOpenID Connect (OIDC) プロバイダーから発行されるJWTトークンをGCPが検証し、そのトークンに基づいてGCPサービスアカウントの権限を一時的に付与します。
これにより、以下のメリットが得られます。
- セキュリティの向上: サービスアカウントキーを物理的に管理する必要がなくなるため、漏洩リスクが大幅に低減します。
- 運用負荷の軽減: キーの生成、配布、ローテーションといった管理作業が不要になります。
- きめ細やかなアクセス制御: OIDCトークンの属性(VercelプロジェクトID、環境など)に基づいて、GCPサービスアカウントの借用を条件付けできます。
出典: Google Cloud 公式ドキュメント - Workload Identity 連携
出典: Vercel Docs - Workload Identity Federation with Google Cloud
秘訣1: GCP Workload Identity Federationの設定 (TerraformによるIaC)
このセクションでは、GCP側でWorkload Identity Federationを構成するためのTerraformコードを解説します。これにより、Vercelからのアクセスを許可するためのGCPリソースをコードで管理し、再現性と安全性を確保できます。
Terraformを使用することで、Workload Identity Pool、Workload Identity Pool Provider、およびそれに関連するGCPサービスアカウントとIAMポリシーを宣言的に定義できます。
Workload Identity Pool と Provider の定義
まず、GCPプロジェクト内にWorkload Identity PoolとそのProviderを定義します。Poolは外部IDを管理する論理的なグループであり、ProviderはそのPool内で特定の外部IDプロバイダ(Vercel OIDC IdP)との信頼関係を確立します。
# main.tf (GCP Workload Identity Federation リソースの定義例)
# Workload Identity Poolの作成
resource "google_iam_workload_identity_pool" "vercel_pool" {
project = var.gcp_project_id
workload_identity_pool_id = "vercel-pool-${var.environment}" # 環境ごとにユニークなIDを推奨
display_name = "Vercel Workload Identity Pool (${var.environment})"
description = "Workload Identity Pool for Vercel deployments in ${var.environment} environment"
}
# Workload Identity Pool Provider (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" # プロバイダーIDは固定で良い場合が多い
display_name = "Vercel OIDC Provider"
description = "OIDC Provider for Vercel"
oidc {
issuer_uri = "https://oidc.vercel.com"
# VercelプロジェクトIDをallowed_audiencesに設定
# VercelプロジェクトIDはVercelダッシュボードのプロジェクト設定から確認可能
allowed_audiences = [var.vercel_project_id]
}
# Vercel OIDCトークンの属性をGCPのプリンシパル属性にマッピング
# これにより、VercelのプロジェクトIDや環境に基づいてIAMポリシーを適用できる
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.project_id" = "assertion['vercel.com/project-id']"
"attribute.environment" = "assertion['vercel.com/environment']"
}
}
この設定では、issuer_uriにVercelのOIDCプロバイダーURL (https://oidc.vercel.com) を指定し、allowed_audiencesにはVercelプロジェクトIDを設定します。これにより、指定されたVercelプロジェクトからのOIDCトークンのみが認証対象となります。
attribute_mappingは、Vercel OIDCトークンに含まれる情報をGCPのプリンシパル属性にマッピングする重要な部分です。vercel.com/project-idやvercel.com/environmentといったカスタム属性をマッピングすることで、後述のIAMポリシーでこれらの属性を条件として利用できるようになります。
出典: Vercel Docs - Workload Identity Federation with Google Cloud
GCPサービスアカウントとIAM権限の付与
次に、Vercel FunctionsがGCPリソースにアクセスする際に借用するGCPサービスアカウントを作成し、必要な権限を付与します。最小権限の原則に従い、Vercel Functionが本当に必要な権限のみを付与することが重要です。
# main.tf (続き)
# Vercel Functionが借用するGCPサービスアカウントの作成
resource "google_service_account" "vercel_sa" {
project = var.gcp_project_id
account_id = "vercel-sa-${var.environment}" # 環境ごとにユニークなIDを推奨
display_name = "Service Account for Vercel (${var.environment})"
}
# サービスアカウントにGCSへのアクセス権限を付与する例
# 最小権限の原則に従い、必要なロールのみを付与すること
resource "google_project_iam_member" "vercel_sa_storage_admin" {
project = var.gcp_project_id
role = "roles/storage.objectAdmin" # 例: Cloud Storageへのオブジェクト管理権限
member = "serviceAccount:${google_service_account.vercel_sa.email}"
}
# Workload Identity Pool Providerがサービスアカウントを借用することを許可する
# VercelプロジェクトIDで条件付けを行い、特定のVercelプロジェクトのみが借用できるようにする
resource "google_service_account_iam_member" "vercel_sa_workload_identity_user" {
service_account_id = google_service_account.vercel_sa.name
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.vercel_pool.name}/attribute.project_id/${var.vercel_project_id}"
}
ここで重要なのは、google_service_account_iam_memberリソースでroles/iam.workloadIdentityUserロールを付与している点です。これにより、Workload Identity Pool Provider (vercel_provider) がvercel_saサービスアカウントを借用できるようになります。
memberのprincipalSet構文に注目してください。attribute.project_id/${var.vercel_project_id}という条件を付けることで、特定のVercelプロジェクトIDを持つOIDCトークンからのみ、このサービスアカウントの借用を許可しています。これにより、VercelとGCP連携のセキュリティが大幅に強化されます。
変数の定義
Terraformで利用する変数を定義します。
# 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 "environment" {
description = "The deployment environment (e.g., dev, staging, prod)."
type = string
}
秘訣2: Vercelプロジェクトの環境変数設定
このセクションでは、Vercel FunctionがWorkload Identity Federationを利用するために必要な環境変数を、Terraform Vercel Providerを使って設定する方法を解説します。これにより、環境変数の設定もIaCとして管理でき、デプロイの信頼性が向上します。
Vercel Functionsは、これらの環境変数を通じてGCPのWorkload Identity Federationエンドポイントと、借用するサービスアカウントの情報を認識します。
# main.tf (Vercelプロジェクトの環境変数設定例)
# Vercel Providerの設定 (別途設定が必要。ここでは省略)
# resource "vercel_project" "my_project" { ... }
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_project_number_env" {
project_id = var.vercel_project_id
key = "GCP_PROJECT_NUMBER"
value = data.google_project.project.number # プロジェクト番号はデータソースで取得
target = ["production", "preview", "development"]
}
resource "vercel_project_environment_variable" "gcp_workload_identity_pool_id_env" {
project_id = var.vercel_project_id
key = "GCP_WORKLOAD_IDENTITY_POOL_ID"
value = google_iam_workload_identity_pool.vercel_pool.workload_identity_pool_id
target = ["production", "preview", "development"]
}
resource "vercel_project_environment_variable" "gcp_workload_identity_pool_provider_id_env" {
project_id = var.vercel_project_id
key = "GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID"
value = google_iam_workload_identity_pool_provider.vercel_provider.workload_identity_pool_provider_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"]
}
# GCPプロジェクト番号を取得するためのデータソース
data "google_project" "project" {
project_id = var.gcp_project_id
}
特に注意が必要なのは、GCP_PROJECT_IDとGCP_PROJECT_NUMBERの違いです。GCP_PROJECT_IDはユーザーが設定する文字列のIDですが、GCP_PROJECT_NUMBERはGCPが自動で割り当てる数値のIDであり、Workload Identity Federationの設定でaudienceを構築する際に必要となります。Terraformのdata.google_projectを使用して正確に取得しましょう。
これらの環境変数は、Vercel FunctionsがGCPのSTS (Security Token Service) エンドポイントと通信し、Vercel OIDCトークンをGCPサービスアカウントのアクセストークンに交換するために利用されます。
秘訣3: Vercel FunctionからGCP Cloud Storageにアクセスする実装例
このセクションでは、実際にVercel Function (Next.js API Route) からGCP Cloud Storageに安全にアクセスするためのコード例を解説します。@vercel/oidcとgoogle-auth-libraryを組み合わせることで、キーレス認証を実現します。
Vercel Functionは、@vercel/oidcライブラリを使ってOIDCトークンを取得し、そのトークンをgoogle-auth-libraryに渡すことで、GCPサービスへの認証済みクライアントを作成します。
// api/upload.js (Next.js API Route or Vercel Function)
import { Storage } from '@google-cloud/storage';
import { getVercelOidcToken } from '@vercel/oidc';
import { GoogleAuth } from 'google-auth-library';
// `@vercel/oidc`ライブラリのバージョン1.0.0以降と
// `google-auth-library`のバージョン9.0.0以降が推奨されます。
// `npm install @vercel/oidc google-auth-library @google-cloud/storage`
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).send('Method Not Allowed');
}
try {
// 1. Vercel OIDCトークンを取得
// この関数はVercelの実行環境でのみ動作します。
const vercelOidcToken = await getVercelOidcToken();
// 2. GoogleAuthクライアントをWorkload Identity Federation用に設定
// audienceはWorkload Identity Pool Providerの詳細ページで確認できる形式に合わせる
// subject_token_supplierでVercel OIDCトークンを渡す
const auth = new GoogleAuth({
type: 'external_account',
audience: `//iam.googleapis.com/projects/${process.env.GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${process.env.GCP_WORKLOAD_IDENTITY_POOL_ID}/providers/${process.env.GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID}`,
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
token_url: 'https://sts.googleapis.com/v1/token',
service_account_impersonation_url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${process.env.GCP_SERVICE_ACCOUNT_EMAIL}:generateAccessToken`,
subject_token_supplier: {
getSubjectToken: async () => vercelOidcToken,
},
});
// 3. 認証済みクライアントを使用してGCPサービスにアクセス
const client = await auth.getClient();
const storage = new Storage({ authClient: client, projectId: process.env.GCP_PROJECT_ID });
const bucketName = 'my-vercel-bucket'; // 適切なバケット名に置き換える
const fileName = `uploads/${Date.now()}-${req.query.filename || 'file.txt'}`;
const bucket = storage.bucket(bucketName);
const file = bucket.file(fileName);
// ファイルアップロードのストリーム処理
const stream = file.createWriteStream({
metadata: {
contentType: req.headers['content-type'],
},
});
await new Promise((resolve, reject) => {
req.pipe(stream)
.on('finish', resolve)
.on('error', reject);
});
res.status(200).json({ message: 'File uploaded successfully', publicUrl: `https://storage.googleapis.com/${bucketName}/${fileName}` });
} catch (error) {
console.error('Error uploading file to GCS:', error);
res.status(500).json({ error: 'Failed to upload file' });
}
}
このコードの肝は、GoogleAuthクライアントのtype: 'external_account'設定です。ここで、VercelのOIDCトークンをsubject_token_supplier経由で提供し、GCPのSecurity Token Service (STS) を利用してGCPサービスアカウントのアクセストークンに交換しています。これにより、クライアントライブラリはGCPサービスアカウントとして認証され、Cloud Storageへのアクセスが可能になります。
出典: Vercel Docs - @vercel/oidc
出典: Google Cloud 公式ドキュメント - Node.js 用 Google 認証ライブラリ
VercelとGCP連携におけるよくあるエラーと回避策
VercelとGCPのWorkload Identity Federation連携は強力ですが、設定が複雑なため、つまずきやすいポイントがいくつかあります。ここでは、代表的なエラーとその回避策を解説します。
1. OIDCトークンの検証失敗 (Issuer URI, Audience, Attribute Mappingの不一致)
-
ハマりどころ: Workload Identity Pool Providerの設定において、
issuer_uriがhttps://oidc.vercel.comと正確に一致しない、allowed_audiencesにVercelプロジェクトIDが正しく設定されていない、またはattribute_mappingがVercelから提供されるOIDCトークンの属性と一致しない場合に発生します。GCPの監査ログ(Cloud Audit Logs)で"status": {"code": 7, "message": "PERMISSION_DENIED"}や"error_code": "INVALID_GRANT"のようなエラーが見られることがあります。 -
回避策:
-
issuer_uriがVercelの公式なOIDCプロバイダーURL (https://oidc.vercel.com) と完全に一致していることを確認します。 -
allowed_audiencesには、VercelプロジェクトのIDを正確に設定します。VercelプロジェクトIDはVercelダッシュボードのプロジェクト設定ページで確認できます。 -
attribute_mappingがVercel OIDCトークンに含まれる属性(例:vercel.com/project-id,vercel.com/environmentなど)とGCPのプリンシパル属性へのマッピングが正しいかを確認します。特に、Terraformのコード例とVercelの公式ドキュメントを照らし合わせ、キーが正確であることを確認してください。 - GCPのCloud Audit Logs (Admin Activity) を確認し、Workload Identity Federationの認証試行に関する詳細なエラーメッセージを特定します。
-
2. サービスアカウントの権限不足 (iam.serviceAccounts.getAccessToken 拒否など)
-
ハマりどころ: Workload Identity Federation自体は機能しているものの、Vercelが借用しようとしているGCPサービスアカウントに、アクセスしようとしているGCPリソースに対する適切なIAMロールが付与されていない場合に発生します。また、Workload Identity Pool Providerからサービスアカウントの借用を許可する
roles/iam.workloadIdentityUserロールが正しく設定されていない場合も発生します。GCPの監査ログで"status": {"code": 7, "message": "PERMISSION_DENIED"}や"error_code": "ACCESS_DENIED"のようなエラーが見られます。 -
回避策:
- GCPサービスアカウントに、Vercel Functionが必要とする最小限のIAMロールのみを付与します(最小権限の原則)。例えば、Cloud Storageへの書き込みが必要な場合は
roles/storage.objectAdmin、Secret Managerからの読み取りが必要な場合はroles/secretmanager.secretAccessorなど。 - Workload Identity Pool Providerが、Vercelが使用するサービスアカウントに対して
roles/iam.workloadIdentityUserロールを付与していることを確認します。この際、Terraformのコード例のように、VercelプロジェクトIDなどの条件を付与して、きめ細かくアクセスを制御することが推奨されます。 - GCPのCloud Audit Logs (Data Access Logs, Admin Activity Logs) を確認し、どのリソースへのアクセスがどの権限で拒否されたかを特定します。
- GCPサービスアカウントに、Vercel Functionが必要とする最小限のIAMロールのみを付与します(最小権限の原則)。例えば、Cloud Storageへの書き込みが必要な場合は
3. 環境変数設定の不備 (特にGCP_PROJECT_NUMBERの誤り)
-
ハマりどころ: Vercelプロジェクトの環境変数として、GCPプロジェクトIDやWorkload Identity Pool/ProviderのIDなどが正しく設定されていない場合、アプリケーションがGCPに接続できません。特に、
GoogleAuthクライアントのaudience設定で必要となるGCP_PROJECT_NUMBERは、プロジェクトID (GCP_PROJECT_ID) とは異なるため、混同すると認証が失敗します。 -
回避策:
- Vercelの環境変数は、GCPプロジェクトID (
GCP_PROJECT_ID)、GCPプロジェクト番号 (GCP_PROJECT_NUMBER)、Workload Identity Pool ID (GCP_WORKLOAD_IDENTITY_POOL_ID)、Workload Identity Pool Provider ID (GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID)、サービスアカウントのメールアドレス (GCP_SERVICE_ACCOUNT_EMAIL) など、必要な情報が正確に設定されていることを確認します。 -
GCP_PROJECT_NUMBERは、GCPコンソールの「IAMと管理」->「設定」で確認できる「プロジェクト番号」を使用します。Terraformを使用する場合は、data.google_project.project.numberで取得できます。 - Vercelのダッシュボードで環境変数が正しく設定されているか、またはTerraform Vercel Providerで設定した値が意図通りに反映されているかを確認します。
- Vercelの環境変数は、GCPプロジェクトID (
VercelとGCP連携におけるベストプラクティスとトレードオフ
VercelとGCPの連携は、フロントエンド開発の高速化と堅牢なバックエンドインフラの組み合わせを可能にします。このセクションでは、安全で効率的な連携を実現するための設計上の考慮事項をまとめます。
ベストプラクティス
- Workload Identity Federationの利用: サービスアカウントキーの直接管理に伴うセキュリティリスクと運用負荷を排除するため、Workload Identity Federationを用いたキーレス認証を積極的に採用します。これは、現代のクラウドネイティブなアプリケーションにおける認証のデファクトスタンダードです。
- 最小権限の原則: GCPサービスアカウントには、Vercel Functionが必要とする最小限のIAMロールのみを付与します。これにより、万が一認証情報が漏洩した場合でも被害を最小限に抑えられます。定期的な権限の見直しも重要です。
- IaC (Infrastructure as Code) による管理: TerraformなどのIaCツールを使用して、GCPのWorkload Identity Federation設定とVercelプロジェクトの環境変数をコードで管理します。これにより、設定のバージョン管理、再現性、複数環境への展開の自動化が可能になり、ヒューマンエラーのリスクを低減します。
- 環境ごとの分離: Workload Identity Poolを開発、ステージング、本番といった環境ごとに作成し、それぞれの環境で異なるサービスアカウントと権限を割り当てることで、セキュリティと管理の分離を徹底します。これにより、開発環境での誤操作が本番環境に影響を与えるリスクを軽減できます。
- 短期間有効なクレデンシャルの利用: Vercelから発行されるOIDCトークンは短期間のみ有効であるため、漏洩時のリスクが低減されます。これはWorkload Identity Federationの大きなメリットの一つです。
- Secret Managerの活用: APIキーやデータベースの認証情報などの機密情報は、GCP Secret Managerで一元的に管理し、Vercel FunctionからはSecret Manager経由で取得するようにします。これにより、ソースコードや環境変数に直接機密情報を記述するリスクを回避できます。
-
ローカル開発時の認証:
@vercel/oidcはVercelの実行環境でのみ動作するため、ローカル開発時はgcloud CLIの認証情報(gcloud auth application-default loginで取得)を使用するなど、代替手段を検討します。開発環境と本番環境で認証方法を切り替えるロジックを実装することが一般的です。
設計上のトレードオフ
- 設定の複雑さ vs セキュリティと運用効率: Workload Identity Federationの導入は、従来のサービスアカウントキーを直接設定する方法と比較して初期設定が複雑になります。特に、GCPとVercelの両方での設定、TerraformによるIaC化を考慮すると、学習コストと初期構築の手間は増大します。しかし、一度設定してしまえば、キー管理の不要化、セキュリティの向上、IaCによる運用効率の向上といった大きなメリットが得られます。長期的な視点で見れば、この初期投資は十分に回収できるでしょう。
- Vercelの統合機能 vs GCPの柔軟性: Vercelはフロントエンドに特化したプラットフォームであり、Next.jsとの相性が抜群です。一方、GCPは多様なサービスと高い柔軟性を提供します。VercelとGCPを連携させることで、Vercelの優れた開発体験とGCPの豊富なバックエンドサービスを組み合わせることができますが、両プラットフォームの特性を理解し、適切な役割分担を行う必要があります。例えば、重いバッチ処理や長時間実行されるタスクはVercel FunctionsではなくGCP Cloud RunやCloud Functions (2nd gen) にオフロードするなどの検討が必要です。
- Vercel Functions vs Cloud Run: サーバーレス関数としてVercel FunctionsとGCP Cloud Runのどちらを利用するかは、アプリケーションの要件によって異なります。Vercel Functionsはフロントエンドとの連携が密で、Edge Functionsは高速なグローバル配信に適していますが、実行時間やリソースに制限があります。Cloud Runはコンテナベースでより柔軟な実行環境を提供し、言語やフレームワークの制約が少ないですが、Vercelとの連携には認証設定が必要です。Vercel Functionsは主にAPIエンドポイントやデータ変換など、比較的軽量で短時間で完了する処理に適しています。
まとめ
この記事では、VercelとGCPを安全に連携させるための3つの秘訣として、Workload Identity Federationを活用したキーレス認証の実現方法を解説しました。
- GCP Workload Identity Federationの設定: Terraformを用いてWorkload Identity PoolとProviderを定義し、VercelからのOIDCトークンを検証する仕組みを構築しました。
- Vercelプロジェクトの環境変数設定: Vercel FunctionsがGCPにアクセスするために必要な環境変数をTerraformで管理し、自動化と信頼性を高めました。
-
Vercel FunctionからのGCPサービスアクセス:
@vercel/oidcとgoogle-auth-libraryを組み合わせ、Cloud Storageへのアップロードを例に具体的なコードを示しました。
Workload Identity Federationは初期設定の複雑さがあるものの、一度構築すればサービスアカウントキーの管理が不要となり、セキュリティと運用効率を大幅に向上させることができます。これにより、開発者は安心してVercelとGCPの強力な連携を活用し、モダンなアプリケーション開発に集中できるようになります。
より詳細な情報や最新の仕様については、Google CloudおよびVercelの公式ドキュメントを定期的にご確認ください。