[rails/rails] GCS Active StorageのIAM署名でADC認証を復元し、クライアントをメモ化

rails/rails

Context

#55353 で導入された変更により、デフォルトのADC(Application Default Credentials)認証を使用しているアプリケーションで署名付きURLの生成が失敗する問題が発生していました。以前のコードでは Google::Auth.get_application_default を明示的に呼び出していましたが、これがメタデータサーバーへの過度な呼び出しを引き起こしていたため削除されました。しかし、その結果として認証が適切に行われず、Google::Apis::AuthorizationError: Unauthorized エラーが発生するようになっていました。

このPRは、ADC認証を復元しつつ、IAMクライアントをメモ化することで過度なAPI呼び出しを抑制し、両方の問題を解決します。

Technical Detail

IAMクライアントのメモ化

新たに iam_client メソッドが追加され、IAMクライアントがインスタンス変数でメモ化されるようになりました。

def iam_client
  @iam_client ||= Google::Apis::IamcredentialsV1::IAMCredentialsService.new.tap do |client|
    client.authorization ||= Google::Auth.get_application_default(["https://www.googleapis.com/auth/iam"])
  rescue
    nil
  end
end

この実装により、以下の利点が得られます:

  • インスタンス化時に一度だけ認証を取得: クライアント生成時にADCで認証情報を取得し、それ以降は再利用します
  • 自動的な認証更新: Google APIクライアントが認証情報の有効期限を管理し、必要に応じて自動的に更新します
  • 過度なメタデータサーバー呼び出しの抑制: 以前の実装では signer ラムダが呼ばれるたびに認証が行われていましたが、メモ化により初回のみに制限されます

署名処理の簡素化

signer ラムダ内のコードが大幅に簡素化されました。以前は毎回新しいIAMクライアントを作成していましたが、メモ化されたクライアントを使用するようになりました。

変更前:

def signer
  lambda do |string_to_sign|
    iam_client = Google::Apis::IamcredentialsV1::IAMCredentialsService.new
    # 認証設定なし(意図的)
    request = Google::Apis::IamcredentialsV1::SignBlobRequest.new(
      payload: string_to_sign
    )
    # ...
  end
end

変更後:

def signer
  lambda do |string_to_sign|
    request = Google::Apis::IamcredentialsV1::SignBlobRequest.new(
      payload: string_to_sign
    )
    # iam_clientはメモ化済み
    # ...
  end
end

カスタム認証のサポート

ADCがデフォルトで使用されますが、アプリケーション固有の認証方法に切り替えることも可能です。

ActiveStorage::Blob.service.iam_client.authorization = Google::Auth::ImpersonatedServiceAccountCredentials.new(options)

このアプローチは Google::Apis::RequestOptions.default.authorization を設定するよりも安全です。なぜなら、Active Storageのみに適用され、他のGoogle APIクライアントには影響しないためです。

テストの追加

2つの新しいテストが追加され、認証の動作を検証しています:

  1. デフォルトのADC動作: WebMockを使用して、ADCによる認証トークン取得が行われることを確認
  2. カスタム認証: iam_client.authorization を上書きした場合、デフォルトのトークン取得が行われないことを確認
test "default IAM client ADC" do
  WebMock.enable!
  config_with_adc = { gcs: SERVICE_CONFIGURATIONS[:gcs].merge({ iam: true }) }
  service = ActiveStorage::Service.configure(:gcs, config_with_adc)

  stub_request(:post, "https://www.googleapis.com/oauth2/v4/token").to_return_json(body: {})
  stub_request(:post, %r{https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/.*:signBlob})
    .to_return_json(body: { "signedBlob" => "test_signed_blob" })
  key = SecureRandom.base58(24)
  service.url(key, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
  assert_requested :post, "https://www.googleapis.com/oauth2/v4/token"
ensure
  WebMock.disable!
end

Impact

この変更により、GCS Active Storageを使用するアプリケーションは以下の恩恵を受けます:

  • ADC環境での正常動作: サービスアカウントキーやWorkload Identityを使用する環境で署名付きURLが正常に生成される
  • パフォーマンス向上: 認証クライアントのメモ化により、メタデータサーバーへの呼び出し回数が大幅に削減される
  • 柔軟な認証設定: 必要に応じてカスタム認証方法を設定可能

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

ガイドライン準拠 ✓ PASS

記事構成とDiffDaily Styleへの準拠状況

記事構成の必須要素(Title, Context, Technical Detail)がすべて明確に記載されています。また、コードブロック前後の空行やGitHubリンク記法などのカスタムMarkdown構文も、ガイドラインに完全に準拠しており、非常に可読性が高いです。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ✓ PASS

技術的な正確性と表現の適切性

記事内のコード引用や技術的な説明は、PRの目的(IAMクライアントのメモ化とADC認証の復元)と完全に一致しています。iam_clientメソッドの追加、signerの簡素化、テストの追加といった具体的な変更点が、Diff内容を正確に反映していると考えられます。技術用語の選択も適切です。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ✓ PASS

元のPR情報との一致度

PRのタイトルと記事の内容が完全に一致しており、ハルシネーションは検出されませんでした。問題の背景(#55353によるリグレッション)、解決策(メモ化とADC復元)、そしてその影響(パフォーマンス改善とバグ修正)が、PR情報に根ざして正確に説明されています。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除