当記事は みずほリサーチ&テクノロジーズ × G-gen エンジニアコラボレーション企画 で執筆されたものです。
はじめまして、みずほリサーチ&テクノロジーズの小野寺と申します。 本記事では、ChatGPTの事例を題材に、Cloud FunctionsでWeb APIを作る際の要点についてまとめていきます。
はじめに
さて、突然ですが私は弊社内で実績のないサービスや技術を検証し、知見を共有するミッションがあります。 ときおり、検証した結果を文字情報だけではなく実際に触れるサービスとして社内に公開したいことがあります。
直近でもChatGPTを社員限定で利用できるAPIとして公開するために、設計・実装を行いました。 本記事では、このChatGPTの事例を題材に、Cloud FunctionsでWeb APIを作る際の要点についてまとめていきます。
システムアーキテクチャの解説をメインにし、コードの実装や各プロダクトの詳細なパラメーターには触れません。 Google CloudでServerlessなAPIを作る際のヒントになればと思います。
概要
背景・前提
ChatGPT は Azure OpenAI Service を利用しています。 Azure OpenAI Service 自体も API を公開していますので、単純に API を利用するだけであればわざわざ Cloud Functions を利用する必要はありません。
しかし、今回のケースでは以下の要件があり直接 Azure OpenAI Service の API を社内に公開するのではなく Cloud Functions でラップしてオリジナルのAPIとして公開することになりました。
- 予算超過となるような高額課金にならないよう、無制限に利用されないように利用する部署ごとにクォータを設けたい
- 監査のため、ChatGPTとの対話について誰が、いつ、どんな対話をしたか記録したい
- 初期コストを抑えたい(構築済の Google CloudでホストしているWebサイトとAPIの仕組みを流用したい)
なお、安価かつ速やかなAPI提供のため、専用線やVPNなどは使用せず、通信はインターネットを利用しています。
アーキテクチャ
Cloud Load Balancing → Cloud Functions → Azure OpenAI Service という流れでAPIを実行しつつ、Cloud Functionsで必要なデータを参照・保存する仕組みです。
各Google Cloudプロダクトの役割
詳細な解説に入る前に利用する主なGoogle Cloudプロダクトの役割・利用目的をまとめます。
Google Cloud プロダクト | 役割・利用目的 |
---|---|
Cloud Load Balancing | クライアントとの通信やオリジン(Cloud Functions)へのルーティング |
Cloud Armor | アクセス元IPアドレスベースのアクセス制御や通信暗号化アルゴリズムの指定 |
Cloud Functions | OpenAIのAPIを実行、認証認可、ログの保管など業務アプリケーションを動かす |
Cloud Firestore | セッションなどアプリケーションの制御に必要な情報を保持しておく |
BigQuery | ChatGPTとの対話ログを保管する |
Identity Platform | 認証機能を担う |
Secret Manager | OpenAIのAPIキーを安全に管理する |
Cloud KMS | BigQuery のデータやSecret Managerのsecretを暗号化する鍵を管理する |
非機能要求のポイント
最適なGoogle Cloudプロダクトの選択では非機能要求が重要になります。 以降の解説では非機能要求のポイントごとにどのような考えで Google Cloud プロダクトを選択していったかを中心に解説していきます。
- クライアントと安全に通信する
- アクセスを社員に限定する(アクセス元IPアドレスの制限)
- オリジンを保護する
- 十分に安全な暗号スィートで通信を暗号化する
- 認証機能を組み込む
- Response に適切なHTTPヘッダを付与する
- 重要データを適切に保管する
- コントロール可能な暗号鍵で暗号化する
- データを国内に保管する
- 外部サービス(Azure OpenAI)と安全に通信する
- secret(アクセスキー)を適切に管理する
- outbound IPアドレスの固定
詳細
クライアントと安全に通信する
アクセスを社員に限定する(アクセス元IPアドレスの制限)
クライアントの外部IPアドレスが固定されている場合、選択できる手法です。 Cloud Load Balancing で公開している場合、Cloud Armor の security policy でアクセス元IPアドレスベースのアクセス制御が実装できます。
なお、 security policy には Edge security policy と Backend securiy policy があります。 前者は CDN 上のキャッシュ配信に対しても評価するのに対し、後者は評価しません。 外部に公開してはならない静的コンテンツを配信する場合などは Edge security policy を利用するよう注意してください。
オリジンを保護する
これまで、Cloud Load Balancing や Cloud Armor でさまざまな防御を記載してきました。 しかしながら、Cloud Load Balancing を迂回して直接オリジン( Cloud Functions や Cloud Run )にアクセス出来てしまえば台無しです。 たとえば Cloud Armor の security policy でIPアドレスベースのアクセス制限を実装していても、直接 Cloud Functions にアクセスされてしまえばIPアドレスベースのアクセス制限はききません。
オリジンのGoogle Cloud プロダクトによって対応が異なります。
Cloud Runの場合はアクセス元をCloud Load Balancingに限定することができます。 一方、Cloud Functionsではそういった設定は不可能です。 ワークアラウンドとして、アプリケーション内で許可された経路でアクセスされていることを確認します。具体的には X-Forwarded-for ヘッダを確認し、許可されたクライアントやロードバランサからのアクセスであることを検証します。
十分に安全な暗号スィートで通信を暗号化する
クライアントとAPIとの通信はインターネットを介して行われますので中間者攻撃や通信中のデータの漏洩を防ぐために通信を暗号化する必要があります。 具体的には Cloud Load Balancing に証明書を配置し、 https 通信のみ受け付けるようにします。
証明書は3種類あることを念頭に置く必要があります。これらは認証局(CA)が証明書を発行するためにチェックする項目に違いがあります。
種類 | 説明 |
---|---|
DV証明書 | ドメインを所持していることをDNSサーバへの問い合わせで検証される |
OV証明書 | DV証明書の項目に加え、組織が法的に実在することなどが検証される |
EV証明書 | OV証明書の項目に加え、企業の公開電話番号や事業の継続期間なども検証される |
Cloud Load Balancing で自動生成できる「Google管理の証明書」はDV証明書です。 特にBtoCの場合はOV証明書以上のものが必要になることが多いです。チェックしておきましょう。
また、 SSLポリシー を利用することで、クライアントが接続を確立するために使用できる TLS プロトコルの最小バージョンを指定できます。TLS 1.2以上が必須、などの要件がある場合はSSLポリシーを利用します。
認証機能を組み込む
認証機能では Firebase Auth が Enterprise 向けに拡張された Identity Platform を利用しています。 メールやSMS、各IDプロバイダとの連携など、簡単に認証機能を組み込むことができます。 簡単に利用できる反面、 Identity Platform の利用には制約があります。
- 認証後、Identity Platform で払い出される JWT は有効期限が1時間固定
- 一度払い出された JWT は期限が切れるまで破棄することが現実的にできない
これらの制約が飲めない場合、アプリケーションによるセッション管理の検討や認証機能の差し替えが必要になります。 Identity Platform を利用する際はこれらの制約を飲める非機能要求となっているか確認します。
なお、今回の事例では制約を許容できなかったため、セッション管理の仕組みをアプリケーションに組み込んでいます。 セッション情報は Cloud Firestore に保存し、Cloud Functions から参照させる仕組みにしています。
Responseに適切なHTTPヘッダを付与する
'access-control-allow-origin'など、 API のレスポンスに適切な Header を設定します。
Header はアプリケーションで定義することもできますが、 Backend Service のcustom header で定義する事も可能です。 固定値を設定する場合はどちらでも実現可能ですが、動的にヘッダを変更する場合はアプリケーションでの実装が必要です。
たとえば2つのオリジン(localhost とクラウド上にデプロイしたFrontendなど)からはアクセスを許可しつつ、他のドメインは拒否したいケースを考えてみます。 'access-control-allow-origin'では全ドメイン許可(*)か一つのオリジンしか設定出来ませんので、リクエストの送信元オリジンに応じて'access-control-allow-origin'を設定する必要が出てきます。
アプリケーションでヘッダ付与の処理を共通化しておくことで自由度とメンテナンスを両立できるので個人的にはアプリケーション内での実装がオススメです。 静的コンテンツを返却する場合や、席二人分誕生ヘッダ付与の処理を terraformで管理したい場合は Backend Service の custom header のほうがよいかもしれません。
重要データを適切に保管する
今回の事例では監査のためにChatGPTとの対話ログを保管する必要がありました。 対話ログには業務情報が含まれる可能性があり、情報漏洩のリスクをコントロールする必要があります。
コントロール可能な暗号鍵で暗号化する
単純に保管時にデータが暗号化されていればよい、というだけであればGoogle Cloud管理の暗号鍵でも要件を満たせます。
しかし、CMEKの利用と暗号鍵への適切なアクセス管理を実施することで、データ漏洩のリスクをさらに軽減することができます。
Google Cloud管理の暗号鍵の場合、暗号鍵へのアクセスはプロダクト(例えばBigQueryのテーブルやFirestoreのレコード)と透過的に権限付与されます。データそのもののアクセス権限に完全に依存するため、万が一誤ったアクセス権限をプロダクトに設定てしまった場合は即座に情報漏洩につながります。
一方、CMEKの場合は暗号鍵への権限(IAM)を明示的に設定する必要があります。万が一データへのアクセス権を誤って設定したとしても暗号鍵へのアクセスを持たない場合はデータを復号できず、エラーとなります。つまり暗号鍵の厳格な管理によってデータ漏洩のリスクを低減することができます。
Cloud Firestore は CMEKに対応していません。上述のような暗号鍵の要件が必須の場合は利用できませんので注意しましょう。 また、利用する暗号鍵と暗号化対象のデータは同じリージョンに存在する必要があります。
今回の事例では ChatGPT との対話ログは監査のための保管が目的であり、リアルタイムに複雑なクエリを実行する必要がないので CMEK で暗号化した BigQuery のテーブルに保存しています。
データを国内に保管する
クラウドサービスのリージョン選定ではプロダクトの単価やエンドユーザの地理的ロケーションの観点がありますが、それら以上に特に重要なのが法規制です。 とくに金融や公共系の業務・サービスを扱う場合、法規制を意識する必要があります。
日本国内に限定する場合、東京リージョンと大阪リージョンが利用可能です。 マルチリージョン構成とする場合、Google Cloudプロダクトごとに選択できるマルチリージョン構成に差異があるので注意が必要です。
特にCMEKを利用してデータを暗号化する場合は注意が必要です。 たとえば Cloud Storage でデュアルリージョンの「ASIA1」(東京・大阪リージョン)を選択した場合、キーリングをASIA1(東京、大阪、ソウル)で作成する必要があります。 暗号鍵自体は国外にも保管されますので非機能要求に違反していないか注意が必要です。
外部サービス(Azure OpenAI)と安全に通信する
ChatGPTに限らず、外部APIを組み込む場合は利用者で果たすべき責任があります。 Azure OpenAI側の設定については割愛します。
secret(アクセスキー)を適切に管理する
Azure OpenAIの場合、API呼び出しにはアクセスキーを使用します。 アクセスキーの漏洩は不正利用に直接つながる重大なインシデントであり、おきてはいけません。
- ソースコードにアクセスキーを埋め込んでおり、リポジトリから誰でも参照できる
- アクセスキーを環境変数で定義しており、Cloud Functionsのコンソールから誰でも参照できる
上記のような状態は適切にアクセスキー(secret)が管理されているとは言えません。
secret の管理には、Secret Managerを利用します。 Secret Manager にsecretを保存し、アプリケーションから参照させます。 登録したsecretはIAMによって権限設定が可能なため、一部の管理者とCloud Functions(で利用するサービスアカウント)のみに権限を付与することで最小権限で運用することが可能です。 これにより、リポジトリの権限が侵害されたり、開発者や運用者が不正を行った場合であってもsecretの値を取得できなくなります。
outbound IPアドレスの固定
連携するサービスによっては送信元IPアドレスを限定する仕組みがあることがあります。 Cloud Functions についても、VPC と Cloud NAT を組み合わせることで Cloud Functions からの outbound アクセスのIPアドレスを固定することができます。(今回の事例では実施しませんでした)
まとめ
Cloud Functions 単体でもAPIを簡単に作ることができますが、非機能要求に応じて他の GoogleCloud プロダクトと連携していく必要があります。
この記事でご紹介したポイントは当然すべてを網羅するものでも、あらゆるシーンで必要な物でもありませんが、みなさまのアーキテクチャ設計やプロダクト選定の際にそういえば昔何か読んだな、と思い出していただければ幸いです。
小野寺 律文
みずほリサーチ&テクノロジーズ
2019年よりクラウドアーキテクトとして、主にAWSへのマイグレーションプロジェクトで活動。
2023年現在はGoogle Cloudの社内向け環境や教育コンテンツの整備を実施中。