Eventbridge ConnectionでCloud Pub/SubのAPIをCallする設定

civitaspo
·

この記事は datatech-jp Advent Calendar 2023の20日目の記事です。

12/20に『Data Engineering Study #22 5社のデータエンジニアが振り返る2023』に登壇させていただきました。当日は多くの方に視聴していただけました。また自分自身も他社様の登壇内容を聞いて学びを得られました。このような発表機会をいただけて大変光栄でした。運営の皆様には感謝しています。

さて、この登壇の中で『Eventbridge➧...➧Cloud Pub/Sub➧BigQueryパターン』というデータ転送パターンについて紹介させていただきました。

本記事はこのデータ転送パターンにおけるStep FunctionsからCloud Pub/Subに伸びている経路を技術的にどう実現しているかを深掘りする記事です。非常に細かい話をするので、読者の方を置いてけぼりにしてしまうかもしれませんがご了承ください。

また、「コードを書かずにインフラだけで要件を満たすこと」に異常なこだわりを持って調べた内容なので、読んでいると「もはや素直にLambdaなり、なんなりでコード書けば良くない?」という気持ちになってくると思います。僕もそう思いますし、そうした方がいいと思います。なので、「Google CloudのAPI叩く方法ってこんなにあるんだな〜」くらいのノリで読んでもらえると嬉しいです。

では、やっていきます。

Step FunctionsからCloud Pub/Subの経路について紹介すると言いながら、記事のタイトルは『Eventbridge ConnectionでCloud Pub/SubのAPIをCallする設定』となってるのはなぜ?

実はStep Functionsに外部APIをCallする機能が追加されたのは、つい先月のことです。

このStep Functionsの外部APIをCallする機能は、内部でEventBridge Connectionに定義された接続情報を使用しています。そのため、EventBridge ConncetionでCloud Pub/SubのAPIを叩く設定ができればStep FunctionsからCloud Pub/SubのAPIをCallできるようになります。ちなみに、EventBridge API Destinationでも利用できるのでStep Functions挟まずにEventBridgeだけでCloud Pub/SubのAPIをCallすることもできるようになります。

EventBridge Connectionで設定可能な認証方法

EventBridge Connectionで設定可能な認証方法は3種類あります。

Basic (Username/Password)

いわゆるBasic認証です。説明不要だと思いますが、Authorization BasicなHeaderを使うリクエストのことです。

API key

HeaderにAPI Keyを載せてリクエストする方法です。Headerのキーには任意の値を入れられるのでAuthorization Headerも使えますし、API独自のキーも利用することができます。

OAuth Client Credentials

OAuthのエンドポイント叩いてtoken取得してからエンドポイントを叩く方法です。OAuth のエンドポイント叩くときも任意のパラメータを指定できます。

Client Credentialsと書いてる通りgrant_type=client_credentialsを想定しています。いわゆるclient_idとclient_secret だけでアクセスできるやつです。

ちなみにGoogle CloudのAPIはgrant_type=client_credentialsをサポートしていません。

Google CloudのAPIの仕様から最適な認証方法を選ぶ

ここからはGoogle CloudのAPIの仕様を理解して、どの認証方法を使えばよいか考えていきます。

API Keyを利用する方法

Google CloudのAPIは、API Keyを使ってAPIをCallすることができます。ただし、以下のドキュメントに記載の通り制約が存在します。

When you use an API key to authenticate to an API, the API key does not identify a principal, nor does it provide any authorization information. Therefore, the request does not use Identity and Access Management (IAM) to check whether the caller has permission to perform the requested operation.

The API key associates the request with a Google Cloud project for billing and quota purposes. Because API keys do not identify the caller, they are often used for accessing public data or resources.

Many Google Cloud APIs do not accept API keys for authentication. Review the authentication documentation for the service or API that you want to use to determine whether it supports API keys.

つまり、API Keyを使ったAPI CallはIAMによるcall identityのチェックが行われないため、IAMによって制限されているAPIはCallできないということです。

当然ながら、通常の運用ではCloud Pub/SubへのPublishはIAMによって制限をかけるのでAPI Keyを使ったアクセスを使うことはできません。そのため、この方法は却下でしょう。

補足ですが、Google CloudのIAMではallUsersという特殊な設定を行うことができます。これはその名の通り「誰でも」アクセスできる設定です。この設定を行えば、API Keyを使ってCloud Pub/SubへのPublishもできるようになります。セキュリティ要件がそこまで高くない状況においては、GCPのプロジェクト名とTopic名を予測不能なハッシュ値等にすることで、採用するのもアリかもしれません?

サービスアカウントからID Tokenを利用する方法

Cloud Pub/SubはID Tokenを使ったHTTPリクエストを受け付けられないので却下です(バッサリ)。ID Tokenを生成してBearer TokenとしてHTTPリクエストを送ると以下のようなエラーメッセージが返ってきます。

{

"error": {

"code": 401,

"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",

"status": "UNAUTHENTICATED"

}

}

なお、ID Tokenを使ってGoogle CloudのAPIをCallする方法については以下の記事に詳しく載っているのでご参照ください。

サービスアカウントからAccess Tokenを利用する方法

OAuth 2.0のアクセストークンを事前に生成しておき、Cloud Pub/SubのAPIをCallする方法です。この方法はこれまでの手法と違い、Cloud Pub/SubのAPIを正しくCallすることができます。ただし、いくつかの問題が存在しています。

1つ目はprojects.serviceAccounts.generateAccessTokenメソッドをCallするためには、サービスとは別のCaller Identityが必要であり、そのCaller Identityの認証どうする問題が存在する点です。

例えば、サービスアカウントAのアクセストークンを生成する場合、別のサービスアカウントまたはGoogle Workspaceのユーザー(Bとする)に対して、roles/iam.serviceAccountTokenCreatorというロールを付与した上で、Bの認証が通った状態でprojects.serviceAccounts.generateAccessTokenをCallする必要があります。つまり、また認証どうする問題が発生するので何も解決していないのです。

2つ目は生成したアクセストークンには有効期限がある点です。この有効期限は最大でも1時間までしか延ばせないため、EventBridge Connectionに設定して、人力でローテーションする運用は現実的ではありません。

そのため、別のワークフローを使って、EventBridge Connectionが参照するAccess Tokenが格納されるSecrets Managerの値を、定期的に更新すれば運用可能かもしれませんが、無駄に複雑な構成になってしまうので却下です。

Google Workspace上にマシンアカウント作って、OAuth認証するパターン

Google Cloud上でOAuth Clientを作成して、Authorizationフローを実施するとclient_id / client_secert / refresh_tokenが取得できます。ブラウザ上で「許可」って押すタイプのアレです。

この認証はOAuth 2.0におけるgrant_typeでいうと、Authorization Codeと呼ばれるものです。

EventBridge Connectionがサポートするgrant_type=client_credentialsとは異なるため、これも利用不可に見えます。

しかし、このAuthorization Codeな認証で取得したclient_id / client_secretにはrefresh_tokenも返却されてきます。このrefresh_tokenは、refresh_tokenとともにtokenリクエストすることでcodeの有効期限を実質無期限に延ばせるという仕様を持っています。

つまり、EventBridge ConnectionのOAuth Client Credentialsを使用しつつ、パラメータとしてrefresh_tokenとgrant_type=refresh_tokenを設定することで、AWSのOAuth Client Credentialsが想定しているリクエストと同等のリクエストを再現することができます。

実際に試してみる

ここからは実際に動くかどうか確認していきます。事前にGoogle Workspace上のマシンアカウントを発行した上で、Cloud Pub/SubのPublish先のTopicに対して、roles/pubsub.publisherロールを割り当てておいてください。

まずはGoogle Cloud上でOAuth Clientを作成します。

次にOAuth ClientのClient Credentialsをダウンロードします。

ダウンロードしたClient Credentialsを使ってブラウザからAuthorization Codeを取得します。ダウンロードしたファイルをclient_credentials.jsonとして例示します。

client_id=$(cat ./client_credentials.json | jq -cr '.installed.client_id')

open "https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/pubsub&access_type=offline&include_granted_scope=true&response_type=code&redirect_uri=http://localhost&client_id=${client_id}"

上記コマンドを打ち込むとブラウザが立ち上がるので、「許可」します。このとき、使用するGoogle Workspace上のマシンアカウントでログインしているブラウザで開くようにしてください。

「許可」を押すと、「http://localhost:8080/?code=xxxxxxxxxxxxxxxxxxxxxxxxxxxx&scope=https://www.googleapis.com/auth/pubsub」というURLに遷移するのでAuthorization Code(xxx... 部分)を取得する。

このAuthorization Codeを使って、refresh_tokenを取得します。Authorization Codeはcode.txtというファイルに格納している前提で書きます。また、先ほどダウンロードしたclient_credentials.jsonも使用します。

client_id=$(cat ./client_credentials.json | jq -cr '.installed.client_id')

client_secret=$(cat ./client_credentials.json | jq -cr '.installed.client_secret')

authorization_code=$(cat ./code.txt)

curl https://accounts.google.com/o/oauth2/token -X POST --data "code=${authorization_code}&scope=https://www.googleapis.com/auth/pubsub&client_id=${client_id}&client_secret=${client_secret}&redirect_uri=http://localhost&grant_type=authorization_code"

{

"access_token": "xxxxxxxxxxxxxxxxxxxxx",

"expires_in": 3599,

"refresh_token": "1//0exxxxxxxxxxxxxxxxxxxxx", # コレ

"scope": "https://www.googleapis.com/auth/pubsub",

"token_type": "Bearer"

}

ここで取得したrefresh_tokenとclient_credentials.json内のclient_id / client_secretをEventBridge Connectionに入力していきます。

そして、EventBridge ConnectionがAuthorized状態になるのを待ちます。

これで設定は完了です。なにかに失敗していたらAuthorized状態になりません。これでStep FunctionsやEventBridge API Destinationを使ってCloud Pub/Subにアクセスできるようになりました。

なお、この方法はOAuthのScopeに別のGoogle APIのScopeを指定すれば、別のAPIをCallすることもできます。

注意

今回紹介した方法はGoogle Workspace上にマシンアカウントを払い出せることを前提としています。Google CloudやGoogle WorkspaceにAPIアクセスを行うとき、しばしばGoogle Workspace上のアカウントが必要になるケースがあります。しかし、共通アカウントとしてGoogle Workspace上にアカウントを作成する場合、いくつかの組織的・技術的管理策によってリスクを最小化する必要があります。そのため、今回紹介した方法は安易に選択するべきではないことを留意してください。私はAWSからGCPにアクセスするのであればOIDCを選択すべきだと思っています。

おわり

サクッと書くつもりが思ったより分量が多くなってしまって疲れました。