更新)

企業が安全に使える、Azureを活用したAIチャット環境を考える

カバー

ChatGPT ブームに乗って

2022年11月に ChatGPT がリリースされて以降、その活用と新しい機能の話題を見ない日はありません。これまでにない品質の会話 AI を手軽に試せるとあって、一般の人にも使用され、ブームはまだまだ続きそうです。

企業でも自社サービス高度化のために ChatGPT を導入するなど、ビジネス活用も勢いを増して進められています。DX推進企業では、自社専用の社内 AI チャットサービスを導入する企業も増えはじめてきました。

当社も例外ではなく、開発業務への適用、業務の生産性向上、クリエイティブな生成物活用といった、生成 AI の利用について熱い議論が進められています。そのような中で、社内で安全に ChatGPT を利用する環境を検証することにしました。

この記事では、AI との会話が学習に使用されず、社内ネットワークからのみアクセスできる安全な AI チャットの環境設定についてご紹介します。

Azure OpenAI Service を選ぶ理由

各社が、無料で使える OpenAI 社の ChatGPT を使わず、あえて自社用の環境を用意する目的には大きく以下の4つがあるかと思います。

  • 機密情報が学習に使われ、情報漏えいすることを防ぐ(シャドーITからの防護)
  • コンプライアンスを意識した使い方とする(ロギング、認証、監視...)
  • 運用・構築環境のサポートを受けられる
  • SLAを締結できる

そのために、多くの企業でマイクロソフト社の Azure OpenAI Service(AOAI)が利用されています。AOAI は、OpenAI 社が開発した ChatGPT などの言語モデルをマイクロソフト社のプラットフォーム上で利用できるもので、OpenAI 社が提供する環境と異なり、エンタープライズ要件を満たした企業利用に適したものとなっています。

すでに Azure を利用している当社は、準備も少なく済み、これをすぐに利用することができました。

環境設定に向けて

AOAI のリソース作成やモデルのデプロイに関しては、特に変わった設定もしていないため、詳細は省略します。

前提条件

  • 社内ネットワーク用の Azure Virtual Network(VNet) が存在し、Azure ExpressRoute を使用してオンプレミスと接続している
  • 社内ネットワーク内の通信はプライベート IP アドレスを使用し、インターネットに出ていく通信はプロキシーサーバーを経由する

Azure サービスを組み合わせる

今回、AOAI を社内で安全に利用するにあたって、どのような Azure のサービスが使えるのか調査を行いました。実現したいことは以下の2つです。

  • 社内ネットワークからのみアクセスできる安全な AI チャットの環境を設定する
  • ツールや開発で利用できるように API として公開する

API 公開のため、利用者に API キーを配る必要があるのですが、AOAI では主キーと副キーの2つしか発行することができず、これを多くの利用者で共有するわけにはいきません。

そこで、AOAI の API キーを保護するために Azure API Management(APIM)が利用できないかと考えました。APIM はオンプレミスや他クラウドサービスを問わず API を管理する、マルチクラウド管理プラットフォームです。様々な API リクエストを集約し、バックエンドにルーティングすることができます。

APIM では作成した API に対して「サブスクリプション」というものを発行することができ、発行時に生成されるサブスクリプションキーがリクエストに含まれていない場合、アクセスを拒否するように設定できます。APIM で新たな API を作成し、バックエンドとして AOAI のエンドポイントを指定することで、以下のような運用が可能です。

  • AOAI の API キーは、APIM で作成した API で設定するだけでよく、利用者に対して公開されない
  • サブスクリプションを個別に発行することで、専用の API キーとして運用できる

APIM を利用することで、API キーの問題については目途が付きましたが、AOAI のエンドポイントがインターネットに公開されているため、社内利用のためには、「アクセス制御」と「プロキシー設定」について考えなければなりません。

Azure API Management 内部 VNet 統合案

最初に考えたのは、APIM の機能を使用してインターネットを介さない利用環境を整備する構成案です。APIM には、VNet へ統合することで、APIM のゲートウェイがネットワーク内のリソースにアクセスできるようにする機能があります。これにより、APIM のインスタンスを配置するサブネットに紐付いた Network Security Group(NSG)でゲートウェイへのアクセス制御を行うことができます。

統合モードとして「内部」を選択すると、APIM のエンドポイントにはプライベート IP アドレスが割り振られます。APIM を配置した VNet に加え、その VNet と ExpressRoute や VPN で接続されたネットワークからのみアクセスできるようになります。また、同一の VNet 内に AOAI のプライベートエンドポイントを作成することで、プライベート IP アドレスを使用した通信のみで API を利用できるようになります。そのため、API 実行時にプロキシーについて考える必要がなくなります。

内部 VNet

この構成の場合、AOAI リソースのファイアウォール機能で、「許可するアクセス元」を「無効」にすれば、プライベートエンドポイント経由の通信のみ許可できます。

セキュリティーの面で見ても、プロキシー設定が不要になるなど利便性の面で見ても、上記の構成が実現できればベストなのですが、この構成は社内ネットワーク側の制約でうまくいかず、この記事を執筆している時点では検証を続けています。

Azure API Management アクセス元 IP 制限案

次に考えたのが、API のエンドポイントをインターネットに公開した状態で、エンドポイントに対するアクセスを制御する構成案です。APIM では API やメソッド単位で、リクエストやレスポンスなどに対して実行するステートメントを定義することができ、それをポリシーと呼んでいます。ポリシーは XML 形式で記述されており、アクセス元 IP の制御で使用される ip-filter などの代表的なポリシーは、Azure ポータルから GUI で簡単に設定することもできます。

ip-filter では、アクセス元を指定してアクセスの可否を設定でき、アクセス元は個別の IP アドレスと、IP アドレスの範囲を指定できます。これを利用して、当社の保有しているパブリック IP アドレスと社内ネットワークからのみアクセスできるように試してみました。GUI で ip-filter を設定すると、XML 形式では以下のようになります。

<policies>
    <inbound>
        <ip-filter action="allow">
            <address-range from="{社内ネットワーク(プライベート IP アドレス)の範囲を指定}" to="{社内ネットワーク(プライベート IP アドレス)の範囲を指定}" />
            <address>{当社のパブリック IP アドレス1}</address>
            <address>{当社のパブリック IP アドレス2}</address>
                ・
                ・
                ・
        </ip-filter>
    </inbound>
</policies>

「前提条件」にも書いたように、当社ではインターネットに出ていく通信はプロキシーサーバーを経由するようになっています。この構成の場合、プロキシー経由で API を呼び出すことになります。そのため、プロキシーの設定を考慮していないアプリケーションは、そのままでは使用できなくなってしまいます。

この問題を解消するために、APIM のプライベートエンドポイントを作成し、プライベート IP アドレスのみで API のエンドポイントへアクセスできるようにしました。ただし、プライベートエンドポイントを配置した VNet 以外(オンプレミス等)からはプライベートエンドポイントの名前解決ができないため、プロキシーの設定ができる環境ではインターネットを経由して API のエンドポイントへアクセスすることになります。

NW構成図

VNet 外から名前解決できない問題については、Azure DNS Private Resolver というサービスを利用し、社内の DNS サーバーに条件付きフォワーダーの設定を仕込むことで解消できそうでしたが、検証目的で使用するには少々料金が高いことと、マルチクラウド環境の実現のために、DNS 周りの設定が少し複雑になってしまっていること、以上の2点から今回の検証では見送ることにしました。

APIM に対してプライベートエンドポイント経由でのアクセスを許可する場合、APIM は VNet 統合することができません。そのため、APIM リソースを作成する際は「仮想ネットワーク」の項目で「なし」と設定します。また、プライベートエンドポイント経由の通信は API を呼び出したクライアントのプライベート IP アドレスからのアクセスとなるため、ポリシーの address-range 部分で許可しています。

この構成では、AOAI のエンドポイントは APIM で作成した API からのみアクセスするようにしています。そのため、AOAI リソースのファイアウォール機能で、APIM のパブリック IP アドレスからの通信のみ許可する事で、社内からの利用のみに制限できます。

但し、APIM が従量課金タイプ場合、APIM のエンドポイントは固定のパブリック IP アドレスを持たずに、Azure の保有している「サービスが使用するパブリック IP アドレス」からアクセスされます。サービスが使用するパブリック IP アドレスを許可すると、Azure のサービスを経由することで社外からもアクセスできるようになってしまうため、APIM は従量課金タイプ以外を選択します。

API の作成

API を作成する際は、「Create from definition」の OpenAPI を選択し、GitHub で公開されている Azure/azure-rest-api-specs を仕様として選択します。

APIM で作成した API が OpenAI ライブラリの仕様と一致するようにいくつか設定が必要になります。

まず、API の「Setting 画面 > Subscription」で以下のように設定を行います。

  • Subscription required:チェックをつける
  • Header name:api-key
  • Query parameter name:subscription-key

上記の設定で、個別に作成したサブスクリプションキーを専用の API キーと見立てて運用することができます。また、設定したヘッダーの api-key は、バックエンドである AOAI のエンドポイントへルーティングする際に AOAI の API キーに上書きします。

set-header のポリシーを利用してヘッダーに追加した APIM のサブスクリプションキーを AOAI の API キーに上書きします。APIM で作成した API の「Design 画面 > Inbound processing」から設定することができ、GUI で設定する場合は、以下のように設定します。

  • NAME:api-key
  • VALUE:{AOAI の API キー}
  • ACTION:override

コードエディターで直接 XML を編集する場合は以下のように記述します。

<policies>
    <inbound>
        <set-header name="api-key" exists-action="override">
            <value>{AOAI の API キー}</value>
        </set-header>
    </inbound>
</policies>

次に、APIM の URL を OpenAI ライブラリの仕様に合わせます。ChatCompletion 機能(ChatGPT)を使う場合、以下に対してリクエストを送信します。

{openai.api_base}/openai/deployments/{deployment_id}/chat/completions?=api-version={openai.api_version}

そのため、「Design 画面 > Frontend」から、以下のように URL の先頭に /openai を追加します。

  • URL:POST /openai/deployments/{deployment-id}/chat/completions?api-version={api-version}

最後に、API のバックエンドとして AOAI のエンドポイントを指定するには、API の「Setting 画面 > Web service URL」で指定するか、「Design 画面 > Backend」で以下のように指定します。

  • Service URL:https://{AOAI リソース名}.openai.azure.com/
  • 右側の Override にチェック

ログ

構成図の通り、社内向けに AOAI 利用環境を公開する事を想定し、API 利用のロギングについても調査を行いました。今回試作した環境では「誰が」「どのような」利用をしているか分析するため、サブスクリプションで呼び出し元を判別できる APIM でログを記録するようにしています。

ログの記録には Azure Monitor を使用し、分析には Azure Monitor の機能である Log Analytics を使用しています。APIM のログを Azure Monitor へ送信するには、リソースの詳細を開き、左側メニューの「監視 > 診断設定」から、診断設定を追加します。追加の際に、宛先として「Log Analytics ワークスペースへの送信」を選択します。

作成した API のログを Azure Monitor で記録するようにするには、以下のように設定します。

  1. Setting 画面から、「Diagnostics Logs > Azure Monitor」の Override global をチェック
  2. 新たに表示された「Additional settings > Number of payload bytes to log (up to 8192)」を最大値に設定

送信先の設定は APIM の「診断設定」、記録する内容の設定は API の「Diagnostics Logs」で行っています。実行ログは、デフォルトでヘッダーの内容を記録しないようになっているため、記録したい場合は、「Additional settings > Headers to log」で追加します。

Log Analytics では、Azure 独自のクエリー言語 Kusto Query Language (KQL) を使用してログの分析を行います。クエリーの例は以下の通りです。

  • ApiManagementGatewayLogs:API 呼び出し時に記録されるログが格納されるテーブルを指定
  • where:サブスクリプション ID(実行した人)を指定してログを検索
  • extend TimeGeneratedJST:表示時刻を日本時間に変換
  • extend messages:API 呼び出し時のプロンプトの内容を表示
  • project:クエリー実行後、表示させる内容を指定
ApiManagementGatewayLogs
| where ApimSubscriptionId == "サブスクリプション ID" 
| extend TimeGeneratedJST = format_datetime(TimeGenerated + 9h, 'yyyy/MM/dd HH:mm')
| extend messages = parse_json(RequestBody).messages
| project
    TimeGeneratedJST,
    ApimSubscriptionId,
    messages

詳細については公式ドキュメントをご参照ください。

社内AIチャットの例

ここからは、AOAI を活用した AI チャットの設定例を紹介します。 AOAI では、REST API が公開されているので、独自のアプリケーションも当然作ることができますが、今回は OSS の chatbot-ui を使い、簡単に Web アプリケーションとして利用できるようにします。chatbot-ui は MIT ライセンスのため、商用利用も可能となっています。

Azure Virtual Machines

今回は、chatbot-ui を仮想マシン上で動作させます。利用するのは、Azure の仮想マシンサービス「Azure Virtual Machines」(Azure VM)です。

仮想マシンのスペックは、最初は様子見で低めの構成とします。リソースグループ、仮想ネットワーク、サブネットについては運用環境に応じて適切に設定します。

  • 仮想マシン名: chatgpt-webapp-example
  • イメージ: Ubuntu Server 20.04 LTS (デフォルト)
  • サイズ: Standard_B2s
  • データ ディスク: 新しいディスクを作成し接続する -> サイズ P3(16GiB)
  • パブリック IP アドレス: なし

基本設定と名前解決の問題

まずは、apt 設定、セキュリティーソフトなどをセットアップしますが説明は省略します。今回の構成では、Azure 上にあるものの社内ネットワークに配置するサーバーなので、外部へのアクセスにはプロキシーの設定も必要になります。ここでひとつはまったのは、社内の DNS サーバーを設定してしまうと APIM のプライベートエンドポイントの名前解決ができない場合があったことです。

当社の VNet 環境では、Azure が提供する DNS サーバーをデフォルトの DNS サーバーとして設定しています。後述の AD FS を使用した OpenID Connect のため、VM に社内の DNS サーバーを追加してしまい、名前解決が不安定になり、エラーが発生しました。社内へはプロキシーサーバー、バックアップサーバー、認証サーバーなど一部サーバーへしかアクセスがないので、社内の DNS サーバーは指定せず、直接 hosts ファイルに書き込むことで解決しました。

Webサーバーのインストール - nginx

続いて nginx をインストールします。この段階では証明書を設定する程度で、特に難しい設定はしていませんので説明は省略します。

認証のしくみ - OAuth2 Proxy

後ほどインストールする chatbot-ui には認証や認可のしくみが備わっていないため、誰が使ったのか、誰が使っていいのかを判断することができません。そこで、該当アプリケーションにアクセスする前に、Web サーバー側を工夫して、Active Directory の認証情報を使って認証を行います。下図のような認証構成となります。

フロントエンド

具体的には AD FS を使った OpenID Connect での認証となります。このために、OSS である OAuth2 Proxy を利用します。OAuth2 Proxy は Web サーバーと同じホストで動かし、認証のためのリバースプロキシーとして動作します。

Web サーバーの設定により、アクセス先のページへアクセスするとまずは OAuth2 Proxy 経由で AD FS へと問い合わせが行われ、認証が通ると、アクセス先のページへコールバックされるという流れです。

インストールの際には、あわせて利用するイン・メモリー・データベースの Redis もインストールして設定しておきます。

Redis と OAuth2 Proxy のインストール/設定
$ sudo apt install redis-server
$ sudo vim /etc/redis/redis.conf  ※ 詳細省略

$ wget https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.4.0/oauth2-proxy-v7.4.0.linux-amd64.tar.gz
$ tar xvzf oauth2-proxy-v7.4.0.linux-amd64.tar.gz
$ sudo cp oauth2-proxy-v7.4.0.linux-amd64/oauth2-proxy /usr/local/sbin/
$ sudo vim /etc/oauth2-proxy.conf
provider = "adfs"
http_address = "127.0.0.1:4180"

pass_user_headers = true
pass_host_header = true
set_xauthrequest = true
skip_provider_button = true

cokkie_secret = "{適当なCookieシークレット}"
cokkie_expire = "12h"
email_domains = "example.com"

oidc_issuer_url = "https://adfs-server.example.com/adfs"
redirect_url = "https://example.com/oauth2/callback"
upstreams = "https://127.0.0.1/"
reverse_proxy = true
session_store_type = "redis"
redis_connection_url = "redis://127.0.0.1:6379/0"
redis_password = "{Redis パスワード}"

client_id = "{AD FS上で発行した client id}"
client_secret = "{AD FS上で発行した client password}"

logging_filename = "/var/log/oauth2-proxy.log"
logging_compress = true

OAuth2 Proxy は、以下のコマンドで起動しますが、systemd で動かせるよう、/etc/systemd/system/oauth2-proxy.service を作成して、コマンドをサービス化するとよいです。

$ sudo /usr/local/sbin/oauth2-proxy --config=/etc/oauth2-proxy.conf

さらに、nginx の設定を編集して、コンテンツへのアクセス前に認証をするように OAuth2 Proxy 関連の設定を追加します。あわせて、このあとインストールする chatbot-ui アプリケーションへのプロキシーパス(Port 3000)も設定しておきます。

OAuth2 Proxy に関する nginx 設定
$ sudo vim /etc/nginx/site-available/example.com.conf
:
      location /oauth2/ {
              proxy_pass http://127.0.0.1:4180;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Scheme $scheme;
      }
      location /oauth2/auth {
              proxy_pass http://127.0.0.1:4180;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Scheme $scheme;
              proxy_set_header Content-Length "";
              proxy_pass_request_body off;
      }
      location / {
              proxy_pass http://127.0.0.1:3000;
              proxy_redirect http:// https://;
              proxy_http_version 1.1;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header Host $host;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection "upgrade";
              proxy_read_timeout 86400;

              auth_request /oauth2/auth;
              error_page 401 = /oauth2/sign_in;
              auth_request_set $user $upstream_http_x_auth_preferred_username;
      }
:

フロントエンド - chatbot-ui

ここまでで、基本的なウェブコンテンツ配置と認証のための基盤ができました。あとはフロントエンドアプリケーションです。本家 ChatGPT の UI を模した OSS である chatbot-ui を使用します。このアプリケーションは、サーバー上で特定のポートで起動する Next.js 製アプリケーションです。

インストールは、github からクローンして設定し、ビルド・起動するだけです。

chatbot-ui のインストール/設定
$ git clone https://github.com/mckaywrigley/chatbot-ui.git
$ cd chatbot-ui
$ npm install
$ cp .env.local.example .env.local
$ vim .env.local
# Chatbot UI
DEFAULT_MODEL=gpt-3.5-turbo
NEXT_PUBLIC_DEFAULT_SYSTEM_PROMPT=あなたは、情報を探したり文章を作成したりするのを手助けするAIアシスタントです。マークダウンを使用して応答してください。
NEXT_PUBLIC_DEFAULT_TEMPERATURE=0.8
OPENAI_API_KEY={サブスクリプションキー}
OPENAI_API_HOST=https://{APIM リソース名}.azure-api.net
OPENAI_API_TYPE=azure
OPENAI_API_VERSION=2023-03-15-preview
AZURE_DEPLOYMENT_ID=gpt-35-turbo-01

今回は OPENAI_API_KEYOPENAI_API_HOST に、APIM で準備したものを使っていますが、APIM を使わない場合は、AOAI で発行されるものを適用します(Playground で「コードの表示」をすると確認できます)。

これを npm run build でビルドすると、npx next start で起動することができるようになり、デフォルトだと3000番ポートで動作します。これも、OAuth2 Proxy 同様 systemd でサービス化しておくとよいと思います。

アクセスしてみよう

ここまでで、Azure上のネットワーク設定と nginx, OAuth2 Proxy(&Redis), chatbot-ui のインストール・起動の準備が整いました。全てのサービスを起動させた状態で、ウェブブラウザからアクセスしてみます。

Chatbot UI デモ

正しく動作すれば上のような画面が表示され、AI チャットが使えるようになります。今回の環境では OpenID Connect による認証画面は表示されずに裏で処理されるため、ユーザーは認証を意識せずに利用することができます。

まとめ

この記事では、Azure OpenAI Service を利用した安全な社内 AI チャットの環境設定方法についてご紹介しました。この環境を使うことで、会話 AI を手軽に利用しながら、機密情報の漏えいを防ぎ、コンプライアンスを意識した使い方をすることができます。

本家 OpenAI に実装された Function Calling 等の新たな機能も AOAI への実装が予定されています。これらの機能により、さらに複雑なタスクを処理することができるようになります。進化を続ける ChatGPT をはじめとする生成 AI 技術の今後からは目が離せません。

(2023/08/24 追記)

Xでご質問をいただきましたので追記します。

chatbot-ui でアクセスした際に、API Managementのログ出力を確認すると、本来 https://XXXXX.azure-api.net/openai/deployments/<DEPLOYMENT_ID>/chat/completions?api-version=XXXとなるべきところが、https://XXXXX.azure-api.net/openai/deployments?api-version=XXXとなっている

このアクセスは正しい挙動です。chatbot-uiでは、まずAzureに対して、デプロイメントされたモデルの一覧取得を行います(/openai/deployments)。 この結果をもとに、chatbot-uiのシステムプロンプト入力画面で選べるモデルのリストが作られています。(types/openai.tsにマッピングの定義があります)

実際にプロンプトと応答のやりとりでは、ご指摘のAPIである /openai/deployments/<DEPLOYMENT_ID>/chat/completions を使うことになります。

ただ、この辺りに実はバグがあり、実際には選択できるモデルはどれを選んでもGPT-3.5が選ばれてしまいます。設定ファイル .env.local でAZURE_DEPLOYMENT_ID環境変数も指定しますが、これも無視される状態となっていて、モデルの取得は実はうまく機能してないので、pages/api/models.tsの関連箇所を修正するのもありかもしれません。

ただ、作者が最近「次のバージョンを出す」と言っていたり、それを別のプロダクトとしてリリースしていたりするので、少し様子を見たいと思います。


TOP
アルファロゴ 株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。