0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VercelとGCP連携で詰まった!WIF認証エラーとTerraform解決策

0
Posted at

多くのエンジニアが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の呼び出しごとに自動生成される短期間のトークンが利用されます。これにより、認証情報の有効期限が短くなり、セキュリティが大幅に向上します。

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/oidcgoogle-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_ARGUMENTFAILED_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の設定と完全に一致させます。
    • 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環境変数に設定すると確実です。
    • Attribute Mappingの不一致:
      • ハマりどころ: OIDCトークン内のクレームと、GCP Workload Identity Pool Providerのattribute_mappingで定義された属性マッピングが一致していない。例えば、assertion['vercel.com/project-id']がトークンに存在しない、またはキー名が間違っている場合。
      • 回避策: OIDCトークンをデコードツール(例: jwt.io)で確認し、subaudvercel.com/project-idなどのクレームが期待通りに含まれているか、またキー名が正しいかを確認します。GCPの監査ログでattribute_mappingの解析失敗メッセージがないか確認してください。
  • デバッグのヒント:

    • 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が正しいかを確認します。

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ロールが正しく設定されており、memberprincipalSetがVercelのIDと一致していることを確認します。特に、attribute.vercel_project_idattribute.vercel_team_idの値がVercelプロジェクト/チームの実際のIDと一致しているか確認します。conditionを使用している場合は、その条件式が正しく評価されるか確認します。

3. 環境変数の設定ミス

Vercel Function内で必要なGCP関連の環境変数が正しく設定されていない場合に発生します。

  • エラー内容の兆候: Vercel Function内でprocess.env.GCP_PROJECT_NUMBERなどがundefinedになり、認証クライアントの初期化に失敗する。
  • ハマりどころと回避策:
    • 環境変数の未設定/スペルミス:
      • ハマりどころ: Vercelプロジェクトの環境変数に、必要なGCP関連の変数が設定されていない、またはキー名がコードと一致していない(例: GCP_PROJECT_IDGCP_PROJECT_IDのスペルミス)。
      • 回避策: Vercelプロジェクトのダッシュボードで、すべての必要な環境変数(GCP_PROJECT_ID, GCP_PROJECT_NUMBER, GCP_WORKLOAD_IDENTITY_POOL_ID, GCP_WORKLOAD_IDENTITY_PROVIDER_ID, GCP_SERVICE_ACCOUNT_EMAIL)が正しく設定されていることを確認します。
    • デプロイ環境ごとの設定漏れ:
      • ハマりどころ: 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-idvercel.com/team-idなどをマッピングに含めることで、よりきめ細かいアクセス制御が可能になります。
    • google_service_account_iam_memberリソースのconditionブロックを使用して、特定のVercelプロジェクトや環境からのアクセスのみを許可するなど、アクセスをさらに制限します。これにより、セキュリティが向上し、意図しないアクセスを防げます。

監査ログの有効化

Workload Identity Federationを使用するGCPプロジェクト内のSecurity Token Service APIとIAM APIの両方でデータアクセスログを有効にすることを強く推奨します。これにより、外部IDがどのように認証され、どのサービスアカウントになりすましたかという監査証跡を追跡できます。認証エラーが発生した際のトラブルシューティングにも不可欠な情報源となります。

TerraformによるIaC

Terraform などのIaCツールでWorkload Identity Federation の設定自体を管理することは、設定の再現性、バージョン管理、レビュープロセスを確保し、ヒューマンエラーを減らす上で非常に重要です。手動での設定は、設定漏れやミスを招きやすく、大規模なシステムでは管理が困難になります。

まとめ

この記事では、VercelとGCPの連携において、Workload Identity Federation (WIF) を用いてセキュアな認証フローを構築する方法を解説しました。

重要なポイントは以下の通りです。

  1. Workload Identity Federation は、サービスアカウントキーを使用せずにVercelからGCPリソースにアクセスするための、よりセキュアで推奨される方法です。
  2. Terraform を活用することで、Workload Identity Pool、Provider、GCPサービスアカウント、およびIAMポリシーの設定を自動化し、IaCによる管理を実現できます。
  3. Vercel Function内では、@vercel/oidcgoogle-auth-libraryを組み合わせ、GCP推奨のaudienceを明示的に指定してOIDCトークンを取得し、GCP認証を行います。
  4. 認証エラーの多くは、Issuer URI、Audience、Attribute Mappingの不一致、IAM権限不足、Vercel環境変数の設定ミスによって発生します。GCPの監査ログを活用した丁寧なデバッグが解決の鍵となります。
  5. 最小権限の原則、環境ごとのPool分離、属性マッピングと条件によるきめ細かいアクセス制御、監査ログの有効化、TerraformによるIaC管理がベストプラクティスです。

Workload Identity Federationは、初期設定に手間がかかる場合がありますが、一度構築してしまえば、長期的に見てセキュリティと運用効率を大きく向上させます。ぜひ、この記事を参考にVercelとGCPのセキュアな連携を実現してください。

さらなる詳細については、Google Cloud 公式ドキュメント - Workload Identity Federation および Vercel Docs - Connect to Google Cloud with OIDC Federation をご参照ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?