人とAIを安全につなぐGMOペパボの新サービス「ロリポップ!ゼロトラストリンク」の、Linux版とESP32版クライアントを公開しました。
これまでクライアントがあったのはmacOS、Windows、iOS、Androidで、どれも人が手で触る端末です。今回のLinux版とESP32版のリリースで、サーバとマイコンが加わりました。
PCからサーバ、IoTデバイスまで
ロリポップ!ゼロトラストリンク(以下、ZTL)は、離れた場所にある機器同士をポート公開なしで直接つなぐサービスです。参加した機器にはネットワーク内だけで通用するIPアドレスが振られ、そのアドレスで相手に届きます。
Linux版はサーバやコンテナ用で、systemdで常駐させます。root権限もカーネルのTUNデバイスも使わないモードがあるので、権限を絞ったコンテナの中でも動かせます。
ESP32版はESP-IDFのコンポーネントです。後述する通り、既存のファームウェアに依存を1つ足して関数を3つ呼ぶと、マイコン自身がネットワークに参加します。センサーやロボットにESP32が載ってさえいれば、開発機や本番サーバと対等に通信できます。なお、ESP32版は実験的な位置づけでのリリースです。
これまでのIoTのつなぎ方との違い
IoTデバイスとクラウドのやり取りには、向きが2つあります。デバイスがクラウドのサービスを呼ぶ向きと、外からデバイスへ指示を送る向きです。
前者の定番は、クラウド側のサービスをインターネットへ公開し、デバイスに持たせたAPIキーで認証する構成です。世界中から届く窓口を開けておいて、キーの強さだけで守ることになります。後者は、NATの内側のデバイスに外から直接は届かないので、双方がMQTTのようなブローカーへ接続しておき、そこ経由で指示を押し込むのが定番です。どちらの向きでも、インターネットへの公開か、中継サービスの追加が要ります。
ゼロトラストリンクではデバイス自身がネットワークに参加するので、どちらの向きもアドレス指定の直接通信になります。デバイスはNATの内側のまま、ネットワーク上の他のノードから届くようになります。クラウド側のサービスも、インターネットに公開しておく必要がなくなります。
アクセス許可の単位は通信元のアドレスではなく、認証されたデバイスです。ZTLのダッシュボードからデバイスを一元管理できます。
StackChanに喋らせる
Linux版とESP32版のクライアントの利用例として、StackChanを題材に説明します。StackChanとは、オープンソース発の卓上ロボットで、今回はM5Stackの公式キット(型番K151)を組み立てて使いました。制御部はキット同梱のM5Stack CoreS3で、ファームウェアはここに書き込みます。ソースはkentaro/rino-stackchanにあります。

StackChanを上図の構成で動かします。StackChanは話しかけられた声をマイクで拾ってZTLのESP32版クライアントを通じてネットワークに流し、返ってきたAIエージェントによる発話音声を鳴らします。音声認識、発話内容の生成、音声合成をするサーバはネットワークの向こう側にあって、ZTLのLinux版クライアントを通じてつながっています。
ESP32版の使い方
ESP32版の使い方について、より詳しく見ていきましょう。
依存を追加する
idf.py add-dependency "gmo-pepabo/lolipop-ztl"
導入は簡単で、上記の通りでlolipop-ztlコンポーネントとその下で動く通信の実装が入ります。コンポーネントはESP Component Registryで公開しています。
接続情報を用意する
ZTLネットワークへ参加するには、デバイスごとの接続情報が必要です。デバイス認証フロー(RFC 8628のOAuth 2.0 Device Authorization Grant)を通じて接続情報を入手します。テレビでサブスクにログインするとき、画面のコードをスマホで入力するあの方式です。
デバイスは起動するとまず保存済みの接続情報を探します。なければサービスへコードを要求し、受け取ったコードと承認URLを画面に出します。ユーザーがブラウザの承認ページでコードを入力すると、ポーリングしていたデバイスに接続情報が発行され、デバイスはそれを保存して接続します。

コードにするとこうなります。
ztl_config_t cfg;
if (!ztl_config_resolve(&cfg)) {
// 初回起動。接続情報がまだ無いのでデバイス認証を実行する。
// 起動直後は回線が未確立なことがあるため、成功するまで繰り返す
while (!run_device_auth(&cfg)) {
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
ztl_config_resolveが保存済みの接続情報を読みます。2回目以降の起動はこれだけです。
初回だけztl_device_authを呼びます。コードの表示手段はデバイスごとに違うので、そこはコールバックでホストアプリに任される作りです。
static void device_auth_prompt(const char *user_code,
const char *verification_uri,
const char *verification_uri_complete,
void *cb_arg) {
const char *url = verification_uri_complete[0] ? verification_uri_complete
: verification_uri;
face_show_auth_code(user_code, url); // 画面にQRコードとコードを出す
}
static bool run_device_auth(ztl_config_t *cfg) {
esp_err_t err = ztl_device_auth("rino-stackchan", "esp32 (ESP-IDF v5.4)",
device_auth_prompt, NULL, cfg);
face_hide_auth_code();
return err == ESP_OK;
}
StackChanには画面があるので、今回作成した例では、デバイス承認のためのURLのQRコードと確認用コードを画面に表示しました。ユーザーがそれをスマホで読んで承認する流れです。画面のないデバイスなら、シリアルログにコードを出すことになります。
つなぐ
ztl_callbacks_t cbs = { .state_cb = on_state_change };
ztl_t *ztl = ztl_connect(&cfg, &cbs);
state_cbが状態変化のコールバックです。StackChanを用いた今回の例では、接続中や再接続中を画面のステータス行に出しています。
通信する
つながったら、相手のアドレスを指定して通信します。
// TCP
ztl_tcp_socket_t *sock = ztl_tcp_connect(ztl, host_ip, port, 15000);
// UDP
ztl_udp_socket_t *usock = ztl_udp_create(ztl, 9000);
ztl_udp_set_rx_callback(usock, on_udp_rx, NULL);
トンネルを通るのは、このztl_*のAPIで書いた通信だけです。ふだんのlwIPソケットは従来どおりの経路に出るため、esp_http_clientもトンネル越しには使えません。StackChanが叩く先はどれもHTTPなので、このTCPのAPIの上に、ヘッダを組んでContent-Lengthぶん読み切るだけの小さなクライアントを書きました。
UDPは、ネットワーク上の他のノードから話しかけるための口として開けています。
echo "こんにちは、調子はどう?" | nc -u <デバイスのアドレス> 9000
これをMacから打つと、返事が声で返ってきます。
コードの全体はkentaro/rino-stackchanにあります。CoreS3でつまずいた設定(PSRAMやTLSのバッファ、マイクとスピーカーがI2Sのクロックを共有する件)もREADMEに書いてあります。
AIエージェントクラウドとつなぐ
StackChanのAIとしての本体は、ロリポップ!AIエージェントクラウドで動いているHermes Agentです。ロリポップ!AIエージェントクラウドは、OpenClawやHermes Agentをクラウド上で動かせるサービスです。クラウドでの常時稼働と安全性を両立するために、外部にポートを公開せず、操作はWebの管理画面かSlackやDiscordなどのチャットに限っています。
そこでZTLの出番です。筆者は、Hermes Agentの動く環境にLinux版クライアントを入れて、ZTLのネットワークへ参加させています。参加すると、環境の中で127.0.0.1に閉じたままのチャットAPIや音声合成が、同じネットワークのStackChanからはアドレス指定で見えるようになります。どこにもポートを開けないまま、机の上のロボットとクラウドのAIが会話しています。
おわりに
Linux版とESP32版で、「ロリポップ!ゼロトラストリンク」はPCやモバイルだけでなく、サーバからIoTデバイスまで使えるようになりました。ESP32版の導入は簡単です。手元にESP32があれば、ぜひ何かひとつZTLのネットワークを通じてつないでみてください。