こんにちは、株式会社Spacelyでバックエンドエンジニアをしている toshichanapp です。普段はインフラ・基盤領域も担当しています。
TL;DR
- 結論として、サーバーから GCP の API を叩くなら API key への依存は極力減らすべきです。サーバーサイドでは API key の制限手段が実質機能しないからです — referer (ドメイン制限) は偽装可能で信頼できず、IP 制限は出口 IP が動的な環境 (ECS Fargate / Lambda) では NAT Gateway + EIP を建てて送信元 IP を固定する必要があり、コスト・運用負荷が乗る上に、それでも鍵の漏洩リスクは残ります。
- 解決策は Service Account + Workload Identity Federation (WIF) で OAuth 2.0 認証に切り替え、サーバーサイドの鍵そのものをなくすことです。鍵がなければ「制限」「漏洩」「IP 固定のための NAT」「環境ごとの鍵管理」という負担ごと消えます。
- そして この利点はプラットフォームを問いません (ECS Fargate / Lambda / EC2)。GCP 側 (WIF / SA / impersonation / Bearer) はどこでも同一で、違うのは AWS 側で credential を取り出す「配線」だけ。むしろ Lambda が一番簡単です (後述)。
- ただし消せるのは サーバーサイドの鍵だけです。ブラウザの Maps JavaScript API はクライアントに公開される API key が不可避なので、そこは referer 制限に加えて API 制限・quota を組み合わせて多層で守ります (後述)。
- 本記事では、周辺施設までの徒歩経路を地図に表示するような機能 (Routes API 呼び出し) を例に、ECS と Lambda の違い、移行で気をつけた点まで紹介します。
1. なぜサーバーサイドで API key を減らしたいのか
サーバーサイドで API key を使い続けても、その key を有効に守る手段が乏しいのが実情です。Google Maps Platform の API key に設定できる制限は、大きく 2 カテゴリに分かれます。Application restrictions (呼び出し元の種別を 1 つだけ選ぶ排他選択) と、API restrictions (有効化する API を絞る別軸。Application restrictions と併用可) です。前者の選択肢を展開すると以下のようになります。
| 制限種別 | カテゴリ | 用途 | サーバーサイド呼び出しでの実用性 |
|---|---|---|---|
| HTTP referer 制限 | Application (排他) | ブラウザ JS 用 | ❌ サーバーから送る Referer ヘッダーは信頼できない (任意改ざん可能)。Google 公式もサーバー用途には IP 制限を推奨 |
| IP 制限 | Application (排他) | 固定 IP の Web サーバー用 | ⚠️ 後述の通り NAT Gateway を建てる必要があり、コスト・運用負荷が乗る |
| Android / iOS アプリ制限 | Application (排他) | モバイル | ❌ サーバーには無関係 |
| API 制限 (有効化する API の絞り込み) | API (別軸・併用可) | 全方式 | ✅ 設定すべきだが「漏れたら制限内の API で無制限に使われる」状況は変わらない |
Application restrictions は 1 つの key につき 1 種類しか設定できないので、referer 制限と IP 制限を同時に掛けることはできません。
IP 制限 = NAT Gateway を建てる、というコスト構造
Fargate task をそのまま public subnet で動かしても、起動ごとに ENI / public IP が変わるため、GCP コンソール側に都度違う IP を許可するのは現実的ではありません。代わりに以下の構成を取れば固定できます。
- Fargate task は private subnet に配置する
- VPC 内に NAT Gateway + Elastic IP (EIP) を建ててアウトバウンドを集約する
- 全アウトバウンドを NAT 経由にする
- GCP の API key 制限に EIP を登録する
これで「Maps Platform から見た送信元 IP」が固定されます。
ただし NAT Gateway は 時間課金 + データ転送課金 が常時かかります。例えば東京リージョン (ap-northeast-1) では時間課金 $0.062/h (≒ 月 $45 / AZ)、データ転送課金 $0.062/GB です (2026-05 時点 / AWS 公式 pricing)。マルチ AZ で建てれば台数分だけ倍になります。「API key を IP 制限したいから」という理由だけで建てると、割に合わないことが多いでしょう。
加えて、IP 制限を入れても以下は解決しません。
- API key 自体の漏洩リスク (GitHub にうっかり push してしまう等) は 残り続けます — 許可 IP の中からの不正使用は防げません。
- ステージング / 本番 / 開発 / プレビュー等、環境ごとに出口 IP が違うので、環境追加のたびに NAT を建てて IP 許可リストに追加するメンテナンスが発生します。
- マルチリージョン / IPv6 対応時には許可リストがさらに膨らみます。
「NAT を建てれば技術的には可能だが、鍵漏洩問題は本質的に解決しないし、運用コストもデータ転送コストも乗ってくる」 — これが現状の API key + IP 制限の立ち位置です。Service Account + WIF は鍵そのものをなくすアプローチなので、漏洩シナリオを根本から潰せる点で筋が良いと言えます。
2. 推奨解: Service Account + Workload Identity Federation
GCP は API key 以外に OAuth 2.0 access token での認証もサポートしています (Routes API は両方サポート)。Service Account の access token を Authorization ヘッダーに Bearer スキームで載せて送るやり方です。
Authorization: Bearer ya29.xxxxxxxx...
ポイントは次の通りです。
- Service Account ごとに
roles/serviceusage.serviceUsageConsumer等を付与し、「この SA だけが Routes API を呼べる」 という IAM レベルの制御ができます。 - access token は短命 (~1h) です。漏れても被害が限定的です。
- API key 自体を持たないので「鍵漏洩」というカテゴリが消えます。
ただし、AWS 等の GCP 外 で動くワークロードから Service Account を使うには、2 つの選択肢があります。
(a) SA キー (.json) を環境変数に注入する
- 一番素朴な方法です。
- しかし「鍵を持ち回る」状態は API key と本質的に同じ問題 (漏洩リスク) を抱えます。
- 推奨されません。
(b) Workload Identity Federation (WIF)
- AWS の IAM role と GCP の Service Account を 鍵なしで紐付けます。
- AWS で動いている ECS task が IAM role を持っていれば、その role に対して GCP SA への impersonation 権限を付与しておくことで、実行時にだけ access token を取得できるようになります。
- 鍵ファイルが一切要りません。
- これが現状のベストプラクティスです。
WIF を使えるようになるまでの流れ
「Service Account そのものは廃止せず、それを impersonate する経路 から鍵を消す」のが WIF の本質です。SA のロール設計や IAM policy はこれまで通り SA に対して付与します。違うのは「誰が SA を名乗れるか」の入り口部分だけです。
sequenceDiagram
participant Task as AWS ECS task<br/>(IAM role)
participant STS as GCP STS<br/>(Workload Identity Pool / Provider)
participant IAM as GCP IAM Credentials API
participant API as GCP API<br/>(Routes 等)
Task->>STS: AWS STS GetCallerIdentity 署名済みリクエスト
STS-->>Task: Google access token (federated)
Task->>IAM: generateAccessToken (target SA を impersonate)
IAM-->>Task: target SA の access token
Task->>API: Authorization: Bearer ...
API-->>Task: response
具体的に必要な手順は 3 ステップです。
Step 1. Workload Identity Pool / Provider を作って AWS アカウントを許可する
GCP 側に「外部 IdP (Identity Provider) からの ID をどう受け入れるか」のエンドポイントを用意します。
resource "google_iam_workload_identity_pool" "aws_pool" { workload_identity_pool_id = "app-aws-pool" } resource "google_iam_workload_identity_pool_provider" "aws_provider" { workload_identity_pool_id = "app-aws-pool" workload_identity_pool_provider_id = "app-aws-provider" # AWS の特定 IAM role からの認証のみを許可 attribute_condition = "assertion.arn.contains('assumed-role/${local.ecs_task_role_name}/')" # AWS から渡される assertion (= STS GetCallerIdentity の結果) を GCP 属性にマップ attribute_mapping = { "google.subject" = "assertion.arn" "attribute.aws_role" = "..." # IAM role ARN を抽出 } aws { account_id = local.aws_account_id # ← 信頼する AWS アカウント } }
これで「指定 AWS アカウント配下の特定 IAM role からの認証は受け入れる」状態になります。
Step 2. AWS の IAM role を Service Account にマッピングする
「この AWS IAM role からの認証だったら、この SA を impersonate していい」という権限を SA 側に貼ります。SA そのものは廃止しません — 鍵をなくすだけで、SA 自体は引き続き Routes API 等を呼ぶ「主体」として存在し、ロールや IAM policy は SA に対して付与します。
resource "google_service_account" "maps_platform_wi" { account_id = "maps-platform-wi" } # Maps Platform API 呼び出しに必要なロール resource "google_project_iam_member" "maps_service_usage" { project = local.project_id role = "roles/serviceusage.serviceUsageConsumer" member = "serviceAccount:${google_service_account.maps_platform_wi.email}" } # ★ ここがマッピング: 「Pool の attribute.aws_role に AWS の ECS task role が # 設定されていたら、この SA への workloadIdentityUser を許可」 resource "google_service_account_iam_member" "maps_wi_user" { service_account_id = google_service_account.maps_platform_wi.name role = "roles/iam.workloadIdentityUser" member = "principalSet://iam.googleapis.com/projects/${data.google_project.current.number}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.aws_pool.workload_identity_pool_id}/attribute.aws_role/arn:aws:iam::${local.aws_account_id}:role/${local.ecs_task_role_name}" }
principalSet://.../attribute.aws_role/arn:aws:iam::.../role/... というのが「AWS role と SA を結ぶ糸」です。
Step 3. 実行時: AWS の一時 token を GCP の一時 token に交換する
ここからは googleauth gem が自動でやってくれますが、内部的には次のことが起きています。
- (ECS の場合) ECS task の
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI環境変数から AWS 一時 credential を取得する (この取得経路だけはプラットフォームで変わります。次節参照) - AWS の STS で
GetCallerIdentityの署名済みリクエストを作成する - それを GCP STS の
/v1/tokenに送り、Google access token に交換する - その Google access token で IAM Credentials API の
generateAccessTokenを叩き、target SA の access token (= impersonation token) を取得する - その token を
Authorization: Bearer ...として GCP API (Routes 等) に送信する
実装としては Google::Auth::ExternalAccount::AwsCredentials.new(...) に必要な URL や属性を渡すだけです。1〜5 は内部実装になります。
3. ECS でも Lambda でも EC2 でも — 違うのは「配線」だけ
ここまでが SA + WIF の仕組みです。重要なのは、「API key をなくせる」という利点は AWS 側のどの実行環境でも同じだということ。GCP 側 (WIF Pool / Provider、SA、impersonation、Bearer 認証) は一切変わりません。プラットフォームで変わるのは googleauth が「AWS の一時 credential をどこから取り出すか」 の一点だけです。
| プラットフォーム | AWS credential の出所 | googleauth 標準で動くか |
|---|---|---|
| Lambda | 環境変数 (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN) |
✅ そのまま動く (最も簡単) |
| EC2 | IMDS (http://169.254.169.254/) |
✅ 標準対応 |
| ECS / Fargate | container credentials endpoint (http://169.254.170.2) |
❌ 未対応 → subclass / 3rd party gem が必要 (後述) |
- Lambda では実行ロールの一時 credential が環境変数として渡されます。googleauth 標準の
AwsCredentialsは環境変数を最優先で読むため、patch なしでそのまま動きます。region もAWS_REGIONで解決され、metadata には触れません。credential_sourceも実質regional_cred_verification_urlだけで成立します。 - ECS / Fargate だけは credential が container endpoint 経由で渡され、googleauth 標準がこの経路に未対応なので、後述の対応が要ります。
- つまり「Lambda だろうと SA を使えば API key の制限から解放される」はメッセージとして正しい。利点は同じで、配線だけが環境で違う、と捉えるのが正確です。
ネットワークも誤解されがちなので補足します。記事冒頭の NAT Gateway は「インターネットに出るため」ではなく、API key の IP 制限に登録する送信元 IP を固定するためのものでした。SA + WIF にすればその IP 制限自体が不要になるので、IP 固定目的の NAT を建てる理由は消えます。Lambda を VPC 外で動かすなら、そもそもインターネットへ直接出られるため接続のための NAT も不要です (RDS 等の都合で VPC 内に閉じ込める場合のみ、接続用に NAT 等が別途必要になりますが、これは IP 制限とは無関係の一般的な VPC の話です)。
4. 具体例: Spacely での実装 (ECS / Rails / Routes API)
例えば「周辺の駅・スーパー・コンビニ等までの徒歩経路と所要時間を地図上に表示する」ような機能では、サーバーサイドから Routes API を呼んで経路を計算します。Spacely でも Routes API をサーバーサイドから利用しており、この呼び出しの認証を API key 方式から Service Account 方式へ移行しました。以下はその実装例です。
Before (API key 方式)
# app/integrations/google/routes_client.rb (旧) class RoutesClient < ApiIntegration def initialize(api_key: ENV.fetch("GOOGLE_MAPS_PLATFORM_BACKEND_API_KEY", nil)) raise ArgumentError if api_key.blank? @api_key = api_key # ... end private def configure_authentication connection.headers["X-Goog-Api-Key"] = @api_key end end
X-Goog-Api-Keyヘッダーに環境変数の API key を載せています。- GCP コンソール側で referer / IP 制限が現実的に設定できないので、key の漏洩 = 即被害という状態です。
After (Service Account 方式)
# app/integrations/google/routes_client.rb (新) class RoutesClient < ::GoogleCloud::ApiIntegration # ← Bearer 認証を継承 SERVICE_ACCOUNT_EMAIL = Settings.integrations.google_cloud.maps_platform.service_account_email def initialize(timeout: nil, retries: nil) super(base_url: BASE_URL, timeout: timeout, retries: retries) configure_field_mask # Routes API 固有の X-Goog-FieldMask だけ別途 end private # 親の build_connection が `Authorization: Bearer #{access_token}` を貼ってくれる def access_token ::GoogleCloud::Auth::CredentialsManager.access_token(SERVICE_ACCOUNT_EMAIL) end end
X-Goog-Api-Keyを撤廃し、Bearer token に切り替えています。- 鍵を一切持ちません (= 漏洩リスクが消えます)。
- SA email は Settings から取得します (環境ごとに異なるため)。
- token 取得は
CredentialsManager経由で行います (per-SA キャッシュ + Mutex)。
AWS 上での access token 取得 (WIF 経路)
# 概略: app/integrations/google_cloud/auth/credentials_manager.rb GoogleCloud::Auth::AwsCredentials.new( audience: "//iam.googleapis.com/projects/.../workloadIdentityPools/.../providers/...", scope: "https://www.googleapis.com/auth/cloud-platform", subject_token_type: "urn:ietf:params:aws:token-type:aws4_request", token_url: "https://sts.googleapis.com/v1/token", credential_source: { ... }, # ECS task の IAM credential を読む service_account_impersonation_url: "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/#{sa_email}:generateAccessToken", universe_domain: "googleapis.com" )
裏で何が起きているかというと、次の通りです。
- ECS task が AWS IAM role の credential を持っている
- その credential を STS endpoint で Google access token に交換する
- その token で IAM Credentials の
generateAccessTokenを叩いて target SA を impersonate した token を取得する - 以降の Routes API 呼び出しでその token を使う
インフラ (Terraform) 側で必要なリソース
- Routes API の有効化
- Maps Platform 専用 SA の作成
- SA に
roles/serviceusage.serviceUsageConsumerを付与 (Maps Platform API を呼ぶのに必要) - Workload Identity Pool / Provider
- SA に対する
roles/iam.workloadIdentityUserをprincipalSet://.../attribute.aws_role/arn:aws:iam::.../role/<ecs-task-role>に付与
5. 移行で気をつけた点
1. ローカル開発で gcloud ADC を使う場合、別経路が必要
WIF は AWS 上で動くからこそ機能します。ローカル開発機からは ECS 経由ではないので使えません。
ローカルでも本番と同じ「SA を impersonate する」挙動にしたい場合は、gcloud で取得した ADC (Application Default Credentials) を起点に Google::Auth::ImpersonatedServiceAccountCredentials を使います。
Google::Auth::ImpersonatedServiceAccountCredentials.make_creds( source_credentials: Google::Auth.get_application_default, # gcloud の ADC impersonation_url: "https://iamcredentials.googleapis.com/.../#{sa_email}:generateAccessToken", scope: ["https://www.googleapis.com/auth/cloud-platform"] )
ただし、開発者の gcloud アカウントに target SA への roles/iam.serviceAccountTokenCreator ロール付与が必要です。
2. googleauth gem の prepare_auth_header が空ヘッダーを返す問題
ImpersonatedServiceAccountCredentials#prepare_auth_header が以下のように書かれています (googleauth 1.16.2 / 2026-05 時点の最新)。
def prepare_auth_header auth_header = {} @source_credentials.updater_proc.call auth_header auth_header end
updater_procが返す proc は、内部でapply(hash)(非破壊: clone してから書き込む) を呼びます。- 結果として
auth_headerは 空のまま返ります。 - impersonation request に Authorization ヘッダーが付かず、
401 CREDENTIALS_MISSINGで落ちます。
workaround としては、initializer で prepend して apply! (破壊版) ベースに置き換えます。
module ImpersonatedServiceAccountCredentialsPatch private def prepare_auth_header auth_header = {} @source_credentials.apply!(auth_header) auth_header end end Google::Auth::ImpersonatedServiceAccountCredentials.prepend( ImpersonatedServiceAccountCredentialsPatch )
なお AWS 経路 (Google::Auth::ExternalAccount::AwsCredentials) は ImpersonatedServiceAccountCredentials とは別のクラス階層で、impersonation token を req.headers["Authorization"] = "Bearer #{token}" と直接代入します (この直接代入は AwsCredentials 固有ではなく、ExternalAccount 共通の BaseCredentials#get_impersonated_access_token の実装です)。prepare_auth_header を経由しないので、この問題の土俵に乗りません。AWS 本番では patch 不要で、ローカル開発で初めて遭遇する挙動です。
3. base_credentials: 経由は UserRefreshCredentials を壊す
ImpersonatedServiceAccountCredentials.make_creds(base_credentials: ...) を使うと、内部で Signet::OAuth2::Client#duplicate を呼び IAM_SCOPE で再スコープします。
この duplicate は signet gem 本体のメソッドではなく、googleauth gem が lib/googleauth/signet.rb で Signet::OAuth2::Client に monkey patch で生やしたものです (signet 本体には duplicate は存在しません)。そして実装が複製する属性リストに refresh_token が含まれないため、UserRefreshCredentials (gcloud ADC) を渡すと duplicate 後の credentials は refresh 不能になります。OAuth token endpoint は unsupported_grant_type で拒否してきます。
source_credentials: (duplicate せず source としてそのまま使う) を渡すと回避できます。
# ❌ 動かない Google::Auth::ImpersonatedServiceAccountCredentials.make_creds( base_credentials: ..., # ← duplicate される ... ) # ✅ 動く Google::Auth::ImpersonatedServiceAccountCredentials.make_creds( source_credentials: ..., # ← そのまま使う ... )
4. googleauth gem が ECS container credentials endpoint をサポートしていない
これは本来「使えるようになるまで」の前提条件ですが、念のため書いておきます。
AWS で WIF を成立させるには、gem 内部の AwsCredentials が「AWS の一時 credential を取得する」処理を持っている必要があります。googleauth gem 標準の Google::Auth::ExternalAccount::AwsCredentials は EC2 instance metadata service (http://169.254.169.254/) からの取得しかサポートしておらず、ECS / Fargate の container credentials endpoint (http://169.254.170.2) には対応していません (googleauth 1.16.2 / 2026-05 時点)。
ECS 上で動かすと「credential を取得できず STS 交換に進めない」状態になります。
これを解決する PR が本家に出ていました。
しかし、2023-09-19 に close されたまま merge されていません (2026-05 現在も同じです)。
PR がマージされなかった経緯
「設計が悪いから却下された」のではなく、もっと残念な理由でした。
- 2023 年頃に PR が提出される (作者:
mmizutani) - 2023-08-21 に maintainer (
bajajneha27) が「まだ作業中ですか?」とコメント - 作者から返信なし
- 2023-09-19 に maintainer が
"Closing the PR due to inactivity. Please feel free to reopen it if you continue to work on it."というテンプレ文言で inactivity を理由に自動 close
つまり、PR の技術的な妥当性は審査されていません。コメント欄を読むと、さらに残念な事実がわかります。
- 2025-05 以降、複数の利用者から断続的にコメントが付いています: reopen 依頼 (2025-05)、「monkey patch で凌ぐしかない」(2025-06)、実際の workaround コード共有 (2026-02) など。
- 2025-09 に別の人 (
halllzA) が「PR を引き継ぎたい」と表明したところ、元 maintainer (bajajneha27) は 「自分はもうこの repo の maintainer ではない」 と返信しました。 - Ruby の googleauth は実質メンテナンス不在状態です。
- 同じ機能を求める issue は他言語 SDK でも上がっています: Java (#1374) / NodeJS (#7739) / .NET (#2963)。
- .NET SDK では関連 PR (#3009) がマージされています — ただしこれは ECS endpoint を SDK 内蔵でサポートする実装ではなく、ユーザーが credential 取得ロジックを差し込める「programmatic refresh external credentials」という汎用機構です。maintainer も Fargate の issue に「これで解決できるはず」とコメントする位置づけで、Ruby PR #422 のような ECS endpoint 直接対応とは設計が異なります。
要するに「PR は妥当だが、作者音信不通 → 自動 close → 以降 maintainer 不在で塩漬け」という、典型的な OSS のボトルネックパターンです。バグかどうかという技術的議論は、そもそも始まっていません。
結局どう対応しているか
「PR が close なのに ECS 上で WIF を動かすにはどうすればいいの?」
→ 本家に取り込まれていない workaround を自前 subclass として実装します。
# app/integrations/google_cloud/auth/aws_credentials.rb class AwsCredentials < ::Google::Auth::ExternalAccount::AwsCredentials AWS_CONTAINER_CREDENTIALS_RELATIVE_URI = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" AWS_CONTAINER_CREDENTIALS_FULL_URI = "AWS_CONTAINER_CREDENTIALS_FULL_URI" private # 本家は EC2 metadata しか見ない。ECS では URI が異なる環境変数で渡される。 def fetch_security_credentials url = if ENV.fetch(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, nil) "http://169.254.170.2#{ENV.fetch(AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)}" elsif ENV.fetch(AWS_CONTAINER_CREDENTIALS_FULL_URI, nil) ENV.fetch(AWS_CONTAINER_CREDENTIALS_FULL_URI) end # ... credential を取得して返す end end
第三者の monkey patch gem である googleauth_aws_container_credential_provider も同じ目的で公開されているので、自前で書きたくなければそれを依存に入れる選択肢もあります。
本家 PR が再 open / merge される可能性に備えて、patch にはコメントを残し、追従しやすくしておきます。
5. サーバーサイドとフロントエンドの API key は分けて管理する
今回 SA + WIF にできるのは サーバーサイドの呼び出しです。ブラウザの Maps JavaScript API キーはクライアントに出る以上なくせず、こちらは残ります。
この 2 つは別の key・別の管理経路にしておきます。分けておけば、それぞれに合った制限をかけられるからです——フロント側は referer 制限で囲いますが、referer は偽装の余地があるため、有効化する API を絞る API 制限と quota (使用量上限) を併用して被害を抑えます。サーバー側は SA + WIF でそもそも key をなくす。用途で守り方が違うので分ける、というだけです。
6. まとめ
- サーバーサイドでは API key の制限手段 (referer / IP) は実質機能しません。だから API key への依存は極力減らすのが筋です。
- 鍵そのものをなくす手段が Service Account + Workload Identity Federation です。鍵がなければ「制限」「漏洩」「IP 固定のための NAT」「環境ごとの鍵管理」という負担ごと消えます。
- この利点はプラットフォーム非依存です (ECS / Lambda / EC2)。GCP 側は共通で、違うのは AWS 側 credential 取得の配線だけ。Lambda は環境変数経路でそのまま動き最も簡単、ECS だけは container endpoint 未対応のため subclass か 3rd party gem が要ります。
- ローカル開発は gcloud ADC →
ImpersonatedServiceAccountCredentialsで同じ impersonation 挙動を再現できます (開発者アカウントに token creator ロールが必要)。 - googleauth gem (2026-05 時点の最新 1.16.2 まで) には impersonation で
prepare_auth_headerが空ヘッダーを返す挙動があり、apply!への置換 patch が必要です。 - 消せるのは サーバーサイドの鍵だけです。ブラウザの Maps JavaScript API はクライアントに公開される API key が不可避なので、そこは referer 制限に API 制限・quota を組み合わせて多層で守り、サーバー側とは別の key・別の管理経路で扱います。
「サーバーから GCP API を叩いていて、API key の漏洩リスクが気になる」という状況であれば、Service Account + WIF への移行は労力をかける価値があります。サーバーが鍵を持たない世界は、精神衛生に良いものです。
参考
- Google Maps Platform: OAuth 2.0 + Service Account
- Workload Identity Federation (GCP 公式)
- Workload Identity Federation: AWS からのアクセス
- IAM Credentials API: generateAccessToken
- google-auth-library-ruby
- googleauth_aws_container_credential_provider
- AWS VPC pricing (NAT Gateway 料金)
Spacely では、Rails / Sidekiq を中心としたバックエンド開発と、Terraform によるインフラ・クラウド基盤の運用に取り組んでいます。今回の記事のような、認証やインフラの課題を一つずつ改善していく仕事に興味がある方と、一緒により良いプロダクトを作っていけたら嬉しいです。