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をセキュア連携!IaCで実現する3つのベストプラクティス

0
Posted at

多くのVercelとGCPを連携するプロジェクトで、サービスアカウントキーの管理に頭を悩ませていませんか?キーの漏洩リスク、ローテーションの煩雑さ、そして環境ごとの設定管理は、セキュリティと運用コストの大きな課題です。

この記事では、VercelとGCP間のセキュアな連携をWorkload Identity Federation (WIF)とTerraformを用いた**Infrastructure as Code (IaC)**で実現する具体的な手法を解説します。サービスアカウントキーを一切使用せず、複数環境での安全かつ効率的なリソース管理とCI/CDパイプラインへの組み込みを実現するための3つのベストプラクティスと、その設計判断、手順を提供します。

VercelとGCP連携における従来の課題とWorkload Identity Federationの優位性

VercelでホストされたアプリケーションがGCPリソースにアクセスする際、これまではサービスアカウントキーファイルをJSON形式でダウンロードし、それをVercelの環境変数に設定する方法が一般的でした。しかし、この方法には以下のような深刻な課題があります。

  • キー漏洩のリスク: サービスアカウントキーはGCPリソースへのフルアクセス権を持つため、一度漏洩すると甚大な被害につながる可能性があります。
  • 管理の複雑さ: キーの定期的なローテーションや、環境(開発、ステージング、本番)ごとの異なるキーの管理は運用負荷が高いです。
  • CI/CDパイプラインとの統合: CI/CDパイプラインでキーを安全に扱うための追加の仕組みが必要になります。

これらの課題を解決するのが、GCPの**Workload Identity Federation (WIF)**です。WIFは、外部のIDプロバイダー (IdP) から発行されたトークンをGCPが直接検証し、サービスアカウントキーを使用せずにGCPリソースへのアクセスを許可する「キーレス認証」を実現します。VercelがOIDC (OpenID Connect) IdPとして機能することで、VercelデプロイメントからGCPへのセキュアなアクセスが可能になります。

Workload Identity Federationの基本概念

Workload Identity Federationは、以下の主要なコンポーネントで構成されます。

Workload Identity Pool

外部のIDプロバイダーからのIDをグループ化するための論理的なコンテナです。Vercelのような複数の外部ワークロードからのアクセスを管理する場合、これらを一つのプールにまとめます。

Workload Identity Provider

特定のIDプロバイダー(この場合はVercel OIDC)からのトークンをGCPがどのように検証するかを定義します。Issuer URL、Audience、属性マッピングなどを設定します。

サービスアカウント

外部ワークロードがGCPリソースにアクセスする際に「権限を借用」するGCP上のIDです。このサービスアカウントに、必要なGCPリソースへの最小限のIAM権限を付与します。

Security Token Service (STS)

GCPのサービスの一つで、外部IdPからのトークンを受け取り、検証後、GCPのアクセストークンに交換する役割を担います。

TerraformによるWorkload Identity FederationのIaC管理

ここでは、VercelとGCPのWIF連携をTerraformでコード化し、インフラの再現性と管理性を高める具体的な手順を解説します。

前提・環境

  • GCPプロジェクトが作成済みであること
  • Vercelチームモードを使用している場合、チームスラッグを把握していること(グローバルモードの場合は不要)
  • Terraform CLIがインストール済みであること (v1.0以上推奨)
  • GCP認証情報 (gcloud auth application-default login またはサービスアカウントキーファイル) がTerraformから利用可能であること

1. TerraformでWorkload Identity PoolとProviderを作成する

このTerraformコードは、GCPプロジェクト内にWorkload Identity Pool、Vercel OIDC Provider、およびVercelが権限を借用するサービスアカウントを作成し、必要なIAMポリシーを付与します。

# main.tf

# GCPプロジェクトIDを設定
variable "gcp_project_id" {
  description = "The ID of the GCP project."
  type        = string
}

# Vercelチームスラッグを設定 (チームモードの場合)。グローバルモードの場合は空文字列のまま。
variable "vercel_team_slug" {
  description = "The slug of your Vercel team. Leave empty for global OIDC issuer."
  type        = string
  default     = ""
}

# Vercel OIDC Issuer URLを動的に生成
locals {
  vercel_oidc_issuer_uri = var.vercel_team_slug == "" ? "https://oidc.vercel.com" : "https://oidc.vercel.com/${var.vercel_team_slug}"
}

# Workload Identity Poolの作成
resource "google_iam_workload_identity_pool" "vercel_pool" {
  project                   = var.gcp_project_id
  workload_identity_pool_id = "vercel-pool" # 一意のID
  display_name              = "Vercel Workload Identity Pool"
  description               = "Workload Identity Pool for Vercel deployments"
}

# Workload Identity 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        = local.vercel_oidc_issuer_uri
    # Vercel OIDCトークンのaudienceは、GCP Workload Identity Pool Providerのリソース名と一致させる必要があります。
    # このリソース名は、Terraformのoutputで取得し、Vercel側の設定で使用します。
    # Vercel OIDCトークン発行時にaudienceを指定しない場合、デフォルトでissuer_uriがaudienceとなることがあります。
    # VercelのOIDCトークン取得関数 (`@vercel/oidc` の `getVercelOidcToken`) でaudienceを明示的に指定します。
    allowed_audiences = [
      "//iam.googleapis.com/${google_iam_workload_identity_pool.vercel_pool.name}/providers/${google_iam_workload_identity_pool_provider.vercel_provider.workload_identity_pool_provider_id}"
    ]
    # Vercel OIDCトークンからGCPの属性へのマッピングを定義します。
    # `assertion.sub` はVercel OIDCトークンの `sub` クレームを指します。
    # `google.subject` はGCPのプリンシパルとして識別される値です。
    attribute_mapping = {
      "google.subject" = "assertion.sub"
      # Vercel OIDCトークンには、プロジェクトIDやデプロイメントIDなどの追加クレームが含まれる可能性があります。
      # 必要に応じて、それらをGCP属性にマッピングし、IAMポリシーで利用できます。例:
      # "attribute.vercel_project_id" = "assertion.project_id"
      # "attribute.vercel_deployment_id" = "assertion.deployment_id"
    }
  }
}

# Vercelが権限を借用するサービスアカウントの作成
resource "google_service_account" "vercel_sa" {
  project      = var.gcp_project_id
  account_id   = "vercel-sa" # 一意のID
  display_name = "Service Account for Vercel deployments"
}

# サービスアカウントの権限借用をWorkload Identity Poolのプリンシパルに許可
# `roles/iam.workloadIdentityUser` ロールは、指定されたWorkload Identity Poolのメンバーが
# このサービスアカウントを借用することを許可します。
# `principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.vercel_pool.name}/*` は、
# `vercel-pool` 内のすべてのIDがこのサービスアカウントを借用できることを意味します。
# より厳密に特定のVercelプロジェクトやデプロイメントに絞る場合は、
# `subject` の後に `attribute.vercel_project_id/your-vercel-project-id` のように指定できます。
resource "google_project_iam_member" "service_account_impersonator" {
  project = var.gcp_project_id
  role    = "roles/iam.workloadIdentityUser"
  member  = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.vercel_pool.name}/*"
}

# サービスアカウントにGCSへのアクセス権限を付与する例
# 最小権限の原則に従い、必要な権限のみを付与します。
resource "google_project_iam_member" "gcs_object_viewer" {
  project = var.gcp_project_id
  role    = "roles/storage.objectViewer" # 例: Cloud Storageオブジェクトの読み取り権限
  member  = "serviceAccount:${google_service_account.vercel_sa.email}"
}

# 出力: Workload Identity Pool Providerのリソース名 (Vercel OIDCトークンのaudienceとして使用)
output "gcp_workload_identity_pool_provider_resource_name" {
  description = "The full resource name of the Workload Identity Pool Provider, to be used as audience for Vercel OIDC token."
  value       = google_iam_workload_identity_pool_provider.vercel_provider.name
}

# 出力: Vercelが権限を借用するサービスアカウントのメールアドレス
output "gcp_service_account_email" {
  description = "The email address of the service account Vercel will impersonate."
  value       = google_service_account.vercel_sa.email
}

# 出力: Vercel OIDC Issuer URL
output "vercel_oidc_issuer_uri_output" {
  description = "The Vercel OIDC Issuer URI configured for the provider."
  value       = local.vercel_oidc_issuer_uri
}

Terraformを実行し、GCPリソースをプロビジョニングします。

terraform init
terraform plan -var="gcp_project_id=your-gcp-project-id" -var="vercel_team_slug=your-vercel-team-slug"
terraform apply -var="gcp_project_id=your-gcp-project-id" -var="vercel_team_slug=your-vercel-team-slug"

terraform apply 完了後、出力される gcp_workload_identity_pool_provider_resource_namegcp_service_account_email の値を控えておきましょう。これらはVercel側の設定で使用します。

2. VercelでWorkload Identity Federationを利用する

VercelのデプロイメントからGCPリソースにアクセスするには、@vercel/oidc ライブラリを使用してOIDCトークンを取得し、google-auth-libraryExternalAccountClient を介してGCPに認証します。

Vercel環境変数の設定

Vercelプロジェクトの環境変数に、Terraformの出力で得られた情報を設定します。これにより、アプリケーションコードから必要な情報を参照できるようになります。

  • GCP_PROJECT_ID: your-gcp-project-id
  • GCP_SERVICE_ACCOUNT_EMAIL: vercel-sa@your-gcp-project-id.iam.gserviceaccount.com (Terraformの出力から取得)
  • GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_RESOURCE_NAME: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/vercel-pool/providers/vercel-provider (Terraformの出力から取得)

これらの環境変数は、本番、プレビュー、開発の各環境に適切に設定してください。

Node.jsアプリケーションでのWIF利用例

このTypeScriptの例は、Vercel OIDCトークンを取得し、ExternalAccountClient を介してGCP Vertex AIに認証・アクセスする方法を示しています。他のGCPサービスSDKも同様に authClient を利用できます。

// api/vertex-ai.ts
import { getVercelOidcToken } from '@vercel/oidc';
import { ExternalAccountClient } from 'google-auth-library';
// @ai-sdk/google は Vertex AI のクライアントライブラリの一例です。
// 他のGCPサービスSDKも同様にExternalAccountClientを使用できます。
import { createVertex, generateText } from '@ai-sdk/google';

// Vercel環境変数から取得
const GCP_PROJECT_ID = process.env.GCP_PROJECT_ID;
const GCP_SERVICE_ACCOUNT_EMAIL = process.env.GCP_SERVICE_ACCOUNT_EMAIL;
const GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_RESOURCE_NAME = process.env.GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_RESOURCE_NAME;

if (!GCP_PROJECT_ID || !GCP_SERVICE_ACCOUNT_EMAIL || !GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_RESOURCE_NAME) {
  throw new Error('Missing required GCP environment variables for Workload Identity Federation.');
}

// ExternalAccountClientの設定
// このクライアントがVercel OIDCトークンをGCP STSに渡し、GCPのアクセストークンを取得します。
const authClient = new ExternalAccountClient({
  type: 'external_account',
  // audienceはWorkload Identity Pool Providerのリソース名と一致させる必要があります。
  audience: GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_RESOURCE_NAME,
  subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
  token_url: 'https://sts.googleapis.com/v1/token',
  // サービスアカウントの権限借用URL
  service_account_impersonation_url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${GCP_SERVICE_ACCOUNT_EMAIL}:generateAccessToken`,
  // Vercel OIDCトークンを取得するサプライヤー関数
  subject_token_supplier: {
    getSubjectToken: async () => {
      // `@vercel/oidc` を使用してVercel OIDCトークンを取得します。
      // ここで指定するaudienceもGCPのWorkload Identity Pool Providerのリソース名と一致させます。
      const token = await getVercelOidcToken({
        audience: GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_RESOURCE_NAME,
      });
      if (!token) {
        throw new Error('Failed to get Vercel OIDC token.');
      }
      return token;
    },
  },
});

// Vertex AIクライアントの初期化
// googleAuthOptionsに`authClient`を渡すことで、WIF経由で認証されます。
const vertex = createVertex({
  project: GCP_PROJECT_ID,
  location: 'us-central1', // 適切なリージョンに置き換える
  googleAuthOptions: {
    authClient,
    projectId: GCP_PROJECT_ID,
  },
});

export const GET = async (req: Request) => {
  try {
    const result = await generateText({
      model: vertex('gemini-1.5-flash'), // 使用するモデルを指定
      prompt: 'Write a vegetarian lasagna recipe for 4 people.',
    });
    return Response.json({ recipe: result.text });
  } catch (error) {
    console.error('Error accessing Vertex AI:', error);
    return Response.json({ error: 'Failed to generate text from Vertex AI.' }, { status: 500 });
  }
};

VercelとGCPのセキュア連携における3つのベストプラクティス

Workload Identity FederationとTerraformを組み合わせることで、VercelとGCPのセキュアな連携を実現できます。ここでは、特に重要な3つのベストプラクティスを解説します。

ベストプラクティス1: Workload Identity Federationによるキーレス認証の徹底

サービスアカウントキーの代わりにWIFを全面的に採用することで、長期的なクレデンシャル管理に伴うセキュリティリスクを排除します。Vercelが発行する短期間のOIDCトークンをGCPが直接検証するため、キー漏洩のリスクがなく、運用上の負担も大幅に軽減されます。

ベストプラクティス2: TerraformによるWIF設定の一元的なIaC管理

Workload Identity Pool、Provider、サービスアカウント、およびIAMポリシーの定義をTerraformでコード化し、バージョン管理します。これにより、以下のメリットが得られます。

  • 再現性: 常に同じ設定でGCPリソースをプロビジョニングできます。
  • 監査可能性: 設定変更の履歴をGitなどで追跡でき、誰がいつ何をどのように変更したかを明確にできます。
  • 自動化: CI/CDパイプラインにTerraformを組み込むことで、WIF設定のデプロイを自動化し、手動によるミスを排除できます。

特に、複数環境(開発、ステージング、本番)で同様の連携設定が必要な場合、Terraformモジュールを活用することで、各環境の設定を効率的に管理できます。

ベストプラクティス3: 最小権限の原則と環境分離の徹底

VercelからGCPにアクセスするサービスアカウントには、そのワークロードが必要とする最小限の権限のみを付与します。例えば、Cloud Storageのオブジェクトを読み取るだけであれば roles/storage.objectViewer のみを付与し、書き込み権限は与えません。

また、開発、ステージング、本番環境ごとにWorkload Identity Pool、サービスアカウント、およびIAMポリシーを分離します。これにより、環境間の意図しないアクセスや設定の混同を防ぎ、セキュリティ境界を明確にします。Terraformのワークスペースや異なるTerraform設定ファイルを使用することで、これらの環境分離をIaCで実現できます。

よくあるエラーとハマりどころ

Workload Identity Federationの設定は複雑なため、いくつかの一般的なエラーポイントがあります。

1. Issuer URLの不一致

  • ハマりどころ: GCPのWorkload Identity Providerに設定する issuer_uri が、Vercel OIDCが実際に発行するトークンのIssuer URLと完全に一致しない場合(例: 末尾のスラッシュの有無、チームスラッグの誤り)。
  • 回避策: Vercel OIDCの正確なIssuer URL (https://oidc.vercel.com または https://oidc.vercel.com/[TEAM_SLUG]) を確認し、Terraformの local.vercel_oidc_issuer_uri で正確に生成されていることを確認してください。

2. 不十分なIAM権限

  • ハマりどころ:
    • サービスアカウントに、外部ワークロードがアクセスしようとしているGCPリソースに対する適切なIAMロールが付与されていない。
    • Workload Identity Poolのプリンシパルに、サービスアカウントの権限借用を許可する roles/iam.workloadIdentityUser ロールが付与されていない、または member の指定が不正確。
  • 回避策:
    • サービスアカウントには、必要なリソースへの最小限のアクセス権限(例: roles/storage.objectViewerroles/aiplatform.user)を付与します。
    • google_project_iam_member リソースで、roles/iam.workloadIdentityUser ロールがWorkload Identity Poolの適切なプリンシパル(例: principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.vercel_pool.name}/*)に付与されていることを確認します。

3. Vercel OIDCトークンのaudienceの不一致

  • ハマりどころ: @vercel/oidc でトークンを取得する際に指定する audience が、GCP Workload Identity Providerの oidc.allowed_audiences に含まれる値と一致しない場合、GCP STSがトークンを拒否します。
  • 回避策: getVercelOidcTokenaudience パラメータには、Terraformの出力で得られるGCP Workload Identity Pool Providerの完全なリソース名(例: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/vercel-pool/providers/vercel-provider)を正確に指定してください。

まとめ

この記事では、VercelとGCPのセキュアな連携をIaCで実現するための3つのベストプラクティスを解説しました。

  1. Workload Identity Federationによるサービスアカウントキー不要のキーレス認証を徹底する。
  2. TerraformでWIFの設定をコード化し、再現性と監査可能性の高いインフラを構築する。
  3. 最小権限の原則環境分離を適用し、セキュリティを最大化する。

これらの手法を導入することで、VercelデプロイメントからGCPリソースへのアクセスをより安全かつ効率的に管理できるようになります。サービスアカウントキー管理の煩雑さから解放され、開発者はアプリケーションロジックに集中できるでしょう。

さらに深く学びたい方は、GCP Workload Identity Federationの公式ドキュメントやVercel OIDCの公式ドキュメントを参照し、詳細な設定オプションや追加のユースケースについて確認することをおすすめします。

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?