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の安全な認証、Terraformで自動設定する

0
Posted at

多くの開発者が、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トークン内の属性(例: subvercel.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-projectyour-vercel-projectyour-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/oidcgetVercelOidcToken() 関数でVercelが発行するOIDCトークンを取得し、それを google-auth-libraryGoogleAuth クラスに渡すことで、サービスアカウントキーなしで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_urihttps://oidc.vercel.com と正確に一致していることを確認します。
    • allowed_audiences にはVercelのチームIDが正確に設定されていることを確認してください。var.vercel_team_id を使用しているか再確認しましょう。
    • attribute_mappinggoogle.subjectassertion.sub に、attribute.project_idassertion['vercel.com/project-id'] に、attribute.environmentassertion['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.objectViewerroles/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_IDGCP_SERVICE_ACCOUNT_EMAILGCP_WORKLOAD_IDENTITY_PROVIDERGCP_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/oidcgoogle-auth-library を組み合わせることで、Vercel FunctionからGCPリソースへシームレスにアクセスできます。
  • allowed_audiences にVercelのチームIDを設定することや、環境変数の正確な設定など、具体的なハマりどころとその回避策を理解することが重要です。

この設定は初期構築に手間がかかるものの、一度導入すれば、キー管理の運用負荷をなくし、より堅牢でセキュアなシステムを構築できます。

次のステップとして、Terraformの condition ブロックを活用し、VercelのプロジェクトIDやデプロイ環境に基づいてサービスアカウントの借用をさらにきめ細かく制御することで、最小権限の原則を徹底することを検討してください。

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?