導入部

この記事では、Google Apps Script(GAS)を使用してリバースプロキシサーバーを構築し、APIキーの漏洩を防ぐ方法について説明します。

前提として、以下の記事で扱った、Google Places APIなどは、秘匿にしておくべきAPIキーがURL内に含まれてしまうため、APIキーが漏洩してしまうという問題がありました。今回の記事はそれを解消するためのものです。

https://agi-labo.com/articles/nd37bb86b05ce

APIキーの漏洩は、不正アクセスやサービスの乱用につながります。

そもそも、「プロキシサーバー」とはなんでしょうか?

プロキシサーバーとは何か

まず、基本的な「プロキシサーバー」について理解しましょう。

プロキシサーバーは、インターネット上の他のサーバーとの通信を代行するサーバーです。

簡単に言えば、インターネット上の「仲介者」のような役割を果たします。

リバースプロキシとフォワードプロキシの違い

プロキシサーバーには大きく分けて、「フォワードプロキシ」と「リバースプロキシ」の二種類があります。

  • フォワードプロキシ: ユーザーの側に立って、ユーザーのリクエストをインターネットのサーバーに代わって送る役割を果たします。例えば、学校や企業がインターネットの使用を管理するために使います。

  • リバースプロキシ: サーバーの側に立って、インターネットからのリクエストを受け取り、適切なサーバーに転送します。この方式は、ウェブサイトのパフォーマンスを向上させたり、セキュリティを強化したりするために使われます。

リバースプロキシの機能

リバースプロキシサーバーは、以下のような多くの機能を持たせることができます。

  1. 負荷分散: 複数のサーバーにリクエストを分散させることで、一つのサーバーにかかる負荷を減らします。

  2. キャッシング: よくアクセスされるページを一時的に保存し、次回同じページにアクセスする際に速く表示できるようにします。

  3. セキュリティ強化: 不正なアクセスや攻撃を防ぐための壁として機能します。

  4. SSL暗号化の管理: セキュアな通信を提供するために、SSL証明書を管理し、暗号化された通信を処理します。

リバースプロキシの実際の利用例

例えば、大きなウェブサイトでは、リバースプロキシを使って、アクセスが集中するときにサーバーがダウンしないようにしたり、セキュアな情報交換を実現したりしています。

このように、リバースプロキシサーバーはインターネット上で重要な役割を果たしており、ウェブのパフォーマンスやセキュリティを大きく向上させることができるのです。

今回の例では、Google Apps Script(GAS)にリバースプロキシサーバーとしての機能を持たせることで、APIリクエストを中継し、APIキーをサーバーサイドに隠蔽することが可能です。

GASを選ぶ理由

GASは、Googleのクラウド上で動作するスクリプトプラットフォームで、比較的簡単に誰でもWebアプリケーションを構築できます。

また、重要な点として無料で扱うことができます。

しかし、1日の利用制限があるため、リクエスト数が多くなってくると捌くのが難しくなってきます。

それではみていきましょう!

この記事に取り掛かる前提として、以下の記事を読んでおくことをお勧めします:

https://agi-labo.com/articles/n494a15067a81

https://agi-labo.com/articles/nd37bb86b05ce


リバースプロキシの基本構造

  • ユーザーのリクエストはまずGASに送られます。

  • GASはリクエストを受け取り、GPTsなどの外部APIにリクエストを転送します。

  • APIキーはGASのコード内に保存され、外部には公開されません。

  • 外部APIからのレスポンスをユーザーに返します。

GASでのリバースプロキシサーバーの設定方法

Step 1. スクリプトの作成およびデプロイ

  • スクリプトの作成: GASにて新しいスクリプトを作成し、HTTPリクエストを処理するコードを記述します。

以下が今回使用するソースコードです:

function callTextSearchAPI_(query, options) {
  var baseUrl = 'https://maps.googleapis.com/maps/api/place/textsearch/json';
  var apiKey = 'Google Places API の APIキーをここに入れます';
  var params = {
    'key': apiKey,
    'query': query 
  };
  if (options) {
    var optionalParams = ['location', 'radius', 'language', 'maxprice', 'minprice', 'opennow', 'pagetoken', 'region', 'type'];
    optionalParams.forEach(function(param) {
      if (options[param]) {
        params[param] = options[param];
      }
    });
  }
  var queryString = Object.keys(params).map(function(key) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
  }).join('&');
  var url = baseUrl + '?' + queryString;
  Logger.log("URL: " + url);
  var response = UrlFetchApp.fetch(url, {'muteHttpExceptions': true});
  var json = response.getContentText();
  var data = JSON.parse(json);
  Logger.log(data);
  return data;
}

function doGet(e) {
  var query = e.parameter.query;
  var options = {
    'location': e?.parameter?.location || '1,-1',
    'radius': e?.parameter?.radius || 3000,
    'language': e?.parameter?.language || 'ja'
  };

  var result = callTextSearchAPI_(query, options);
  return ContentService.createTextOutput(JSON.stringify(result))
    .setMimeType(ContentService.MimeType.JSON);
}
  • APIリクエストの転送: ユーザーからのリクエストを適切な外部APIに転送し、レスポンスを受け取る処理を実装します。

以下の部分にて、APIキーをURLに埋め込んでいます。

var queryString = Object.keys(params).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&');
var url = baseUrl + '?' + queryString;
Logger.log("URL: " + url);
var response = UrlFetchApp.fetch(url, {'muteHttpExceptions': true});

  • Webアプリとしてのデプロイ:

    • スクリプトをWebアプリケーションとしてデプロイし、外部からアクセス可能にします。

    • 以下を読む前に事前にGASのデプロイ方法についてはこちらの記事が詳しいです:

  • デプロイ時のURLは後ほど使用するため、保存します。

ポイント:
- APIキーをGAS (サーバーサイド)で書くことにより、GPTs側からはセキュリティキーが漏洩しない

Step 2. Actions の設定

今回使用する YAML は以下です。{ここにデプロイIDを入れます}の部分を、GASのデプロイ時のIDに変更します。

openapi: 3.0.0
info:
  title: TextSearch API
  description: API to perform text search with location and language parameters.
  version: 1.0.0
servers:
  - url: https://script.google.com
paths:
  /macros/s/{ここにデプロイIDを入れます}/exec:
    get:
      operationId: textSearch
      summary: Performs a text search.
      parameters:
        - in: query
          name: query
          required: true
          schema:
            type: string
          description: The query string to search for.
        - in: query
          name: location
          schema:
            type: string
            default: 1,-1
          description: The location for the search in 'latitude,longitude' format.
        - in: query
          name: radius
          schema:
            type: integer
            default: 3000
          description: The radius in meters within which to perform the search.
        - in: query
          name: language
          schema:
            type: string
            default: ja
          description: The language in which to return results.
      responses:
        "200":
          description: A JSON object with the search results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                required:
                  - data

Step 3. テスト

Preview画面から実際に呼べることをテストします。

無事、Google Places API をそのまま呼ぶと見えていた APIキーがサーバー側に移行したため、見える情報は query のみになりました!🎉

注意点と制限

  • 高負荷や大規模なアプリケーションには不向きです。

  • セキュリティ面では、スクリプトの管理とAPIキーの扱いに注意が必要です。

    • よりセキュリティを気にする場合は、キーをScript Propertyに保存するなどする必要があります。

結論

GASを使用したリバースプロキシサーバーは、APIキーの漏洩リスクを減らし、小規模なアプリケーションやプロトタイピングには適した方法です。

今回は説明のために簡易的な方法で解説しましたが、大規模な商用プロジェクトや高いセキュリティが求められる環境では、より専門的なクラウドサービスやサーバーの利用を検討することをお勧めします。また追ってより堅牢なやり方を解説します。