0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenConnectorをセルフホストし、架空SaaS+専用APIキーでMCP認証を検証した

0
Last updated at Posted at 2026-07-25

OpenConnectorをDocker Composeでセルフホストし、架空SaaS「NebulaDock」に発行した専用APIキーを預けて、MCP経由の読み書きまで検証した。

結論から言うと、OpenConnectorは単なる「大量のAPIカタログ」ではない。資格情報をエージェントの外側に置き、接続確認、Action実行、ポリシー判定、監査ログをひとつの境界に集約できる。一方、認証不要APIを呼ぶだけでは、この価値はほとんど検証できない。実験用には、既存の個人トークンを流用するより、隔離したモックSaaSと使い捨てキーを用意する方が安全で、結果も読みやすかった。

OpenConnectorのローカルランタイムコンソール

TL;DR

  • OpenConnector v1.3.1をDocker Composeで起動した。
  • 保存資格情報の暗号化、管理API認証、MCP/Action実行認証を有効にした。
  • 架空のDokploy互換SaaS「NebulaDock」を別コンテナとして実装し、専用APIキーを発行した。
  • 誤キーはサービス直呼びで401、OpenConnectorへの登録で400 credential_verification_failedになった。
  • 正しいキーでは、MCPから一覧取得とプロジェクト作成に成功した。OpenConnector再起動後も再接続せず読めた。
  • NebulaDockだけをLAN公開し、OpenConnector管理面はlocalhost限定に保った。
  • これは「APIキー保管・注入・Action実行・監査」の検証であり、任意SaaS向けネイティブProvider作成や本番運用を証明するものではない。

OpenConnectorとは

OpenConnectorは、AIエージェントと外部サービスの間に置くオープンソースのconnector gatewayだ。公式READMEではComposioの代替と位置づけられ、SDK、CLI、MCP、HTTP、OpenAPIから共通のProvider/Actionカタログを扱える。

今回使用したイメージはv1.3.1。実行時の管理画面では、次の状態を確認できた。

項目 実測値
利用可能なProvider 1,125
ローカル実行可能Action 12,137
接続済みProvider 1
直近の実行失敗 0

重要なのは件数ではない。OpenConnectorは次の責務を持つ。

  • APIキーやOAuthトークンをエージェントプロセスの外側で保管する
  • 接続先アカウントを安全なプロファイルとして公開する
  • Actionごとの入力schemaと必要scopeを提示する
  • 実行許可・拒否ポリシーを適用する
  • 秘密値を除いた実行履歴を残す

MCPクライアントから見えるのは、search_actionsget_action_guideexecute_actionなど少数の発見・実行ツールだ。1万件を超えるActionを最初からLLMのtool schemaへ詰め込まず、検索してから使う設計になっている。

なぜ「認証不要APIを呼べた」だけでは足りないのか

最初の疎通確認にはHacker Newsを使った。MCPを初期化し、Actionを検索し、トップストーリー500件を取得し、先頭5件を個別に展開した。6回のMCP Actionはすべて成功した。

これは有効なtransport smoke testだ。次の項目は確認できる。

  • MCPのJSON-RPCを受け付ける
  • Action検索とguide取得が動く
  • Provider executorが外部APIを呼べる
  • 実行履歴にcaller=mcpが残る

しかし、Hacker News接続はno_authだ。次の核心部分は未検証のままだった。

  • 誤キーを拒否できるか
  • 正しいキーを暗号化保存できるか
  • 実行時だけキーをProviderへ注入できるか
  • 再起動後に資格情報を復号して使えるか
  • 書き込みActionを監査できるか

そこで実アカウントのトークンを使えばよい、と考えるのも早い。広いrepoworkflow権限を持つ個人トークンは、動作確認には強すぎる。接続できることは証明できても、失敗時の影響範囲が大きくなる。

比較のため、既存のGitHub OAuth tokenでget_current_userと非公開repositoryを含むlist_my_repositoriesが成功するところまでは確認した。しかし、これは技術的な成功であって、安全な探索手順ではない。OpenConnector内のGitHub接続は直ちに削除し、元のcredential ownerには触れないまま、以後の検証を専用キーへ切り替えた。

connector gatewayの評価では、「本物の強い鍵」より「専用の弱い鍵」を先に用意する。

この方針で、実験専用の架空SaaSを作ることにした。

架空SaaS「NebulaDock」を作る

NebulaDockは、Dokployの一部API contractだけを実装したテストダブルだ。OpenConnectorに既存のDokploy Providerがあるため、Providerを新規開発せず、APIキーの検証・注入・読み書きに焦点を絞れる。

実装したendpointは4つ。

Method Endpoint 用途
GET /health 認証不要の生存確認
GET /api/project.search OpenConnector接続時のキー検証
GET /api/project.all 認証付き一覧取得
POST /api/project.create 認証付きプロジェクト作成

認証はx-api-keyヘッダーで行う。比較にはtimingSafeEqualを使い、リクエスト監査にはキーそのものではなく、認証成否だけを記録した。

import { timingSafeEqual } from "node:crypto";

function keyMatches(candidate = "") {
  const expected = Buffer.from(process.env.MOCK_SAAS_API_KEY);
  const actual = Buffer.from(candidate);
  return expected.length === actual.length &&
    timingSafeEqual(expected, actual);
}

const authenticated = keyMatches(request.headers["x-api-key"]);

requestAudit.push({
  method: request.method,
  path: url.pathname,
  authenticated,
  at: new Date().toISOString(),
});

if (!authenticated) {
  json(response, 401, { error: "invalid_api_key" });
  return;
}

テストキーはサービス専用に生成した。

openssl rand -hex 32

この値を.envMOCK_SAAS_API_KEYへ保存し、ファイルpermissionを600にした。記事やスクリーンショットには実値を出していない。

Docker Composeの境界

OpenConnector管理面とNebulaDockの公開範囲は分けた。

services:
  connector:
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK: "true"
    depends_on:
      mock-saas:
        condition: service_healthy

  mock-saas:
    build:
      context: ./mock-saas
    restart: unless-stopped
    ports:
      - "0.0.0.0:3101:8080"
    environment:
      MOCK_SAAS_API_KEY: ${MOCK_SAAS_API_KEY:?required}

これにより、接続経路は次のようになる。

OpenConnectorコンテナからDocker内部のNebulaDockへ到達させるため、OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK=trueを有効にした。この設定は、対応Providerがprivate networkを参照できる範囲を広げる。今回のような隔離テストでは必要だが、本番では対象Providerとネットワークを限定して判断すべきだ。

また、Provider proxyは次の設定で無効化した。

OOMOL_CONNECT_BLOCKED_PROXIES=*

Action allowlistとProvider proxyは別の制御面である。Actionを制限してもproxyが自動的に閉じるわけではない点に注意したい。

誤キーを先に試す

正しいキーを入れる前に、失敗系を固定した。

NebulaDockへ誤キーで直接アクセスすると401になった。

curl -i \
  -H 'x-api-key: deliberately-wrong-key' \
  http://127.0.0.1:3101/api/project.all
HTTP/1.1 401 Unauthorized
{"error":"invalid_api_key"}

同じ誤キーをOpenConnectorへ登録すると、Provider validatorが/api/project.searchを呼び、接続作成を拒否した。

HTTP 400
credential_verification_failed
invalid_api_key

この時点でDokploy接続は0件。無効な資格情報が「接続済み」として残らないことも確認した。

正しいキーをOpenConnectorへ登録する

管理APIへ正しい専用キーとDocker内部URLを送る。

curl -X PUT http://127.0.0.1:3000/api/connections/dokploy \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
  -H 'content-type: application/json' \
  -d '{
    "authType": "api_key",
    "values": {
      "apiKey": "'"${MOCK_SAAS_API_KEY}"'",
      "baseUrl": "http://mock-saas:8080"
    }
  }'

登録は200で成功し、OpenConnectorが返したのは安全な接続プロファイルだけだった。

{
  "service": "dokploy",
  "connectionName": "default",
  "authType": "api_key",
  "profile": {
    "accountId": "dokploy:mock-saas:8080",
    "displayName": "Dokploy mock-saas:8080",
    "grantedScopes": []
  }
}

レスポンスにAPIキーは含まれない。

MCPから読み、書き、もう一度読む

MCPのexecute_actionから、まずdokploy.project-allを実行した。初期状態では「月面農園」1件が返った。

続いてdokploy.project-createを実行した。

{
  "actionId": "dokploy.project-create",
  "input": {
    "name": "火星気象ダッシュボード",
    "description": "OpenConnector認証テストで作成した架空プロジェクト",
    "env": "staging"
  }
}

再びproject-allを呼ぶと、2件目として追加されていた。

Gate 結果
誤キー・サービス直呼び 401
誤キー・OpenConnector登録 400
正しいキー・接続作成 200
認証付き一覧取得 成功
認証付き作成 成功
サービス側project数 1 → 2
コア検証の監査ログ 4/4成功
OpenConnector再起動後の再取得 成功

コア検証時のAction実行は5〜7msだった。後続のLAN/画面QAでも呼び出したため、掲載スクリーンショット上のDokploy呼び出し数は8回まで増えている。

監査ログにはactionId、接続プロファイル、入力summary、成否、所要時間が残る。一方、APIキーは含まれなかった。サービス側の監査もauthenticated: true/falseだけを記録し、キー値を保存していない。

LANとモバイル画面で確認する

NebulaDockには、APIキー入力とproject一覧を表示する小さなWeb UIも付けた。

未認証状態ではprojectを表示しない。

NebulaDockの未認証モバイル画面

正しいキーを入力すると、3件の架空projectを取得できる。記事用の画像ではキーをpassword inputで伏字にし、実LAN IPも表示していない。

NebulaDockのAPIキー認証後モバイル画面

ブラウザQAはGoogle Chromeを390×844pxのモバイルviewportで実施した。

  • 空キー: APIキーが違います
  • 誤キー: APIキーが違います
  • 正しいキー: 認証済み
  • 表示project数: 3
  • password input: 伏字
  • 横方向overflow: なし
  • 別のLANホストからpage title取得: 成功

スマートフォンから使う場合のURLは次の形になる。

http://<MAC_LAN_IP>:3101

ここで公開したのはNebulaDockだけだ。OpenConnectorの管理画面とMCP endpointは127.0.0.1:3000のままなので、LANから直接は開けない。

再現手順

前提はDocker DesktopとDocker Composeが動くmacOSまたはLinux環境だ。

git clone https://github.com/oomol-lab/open-connector.git
cd open-connector

.envには実値を直接記事へ貼らず、ローカルで生成する。

openssl rand -hex 32  # OOMOL_CONNECT_ENCRYPTION_KEY
openssl rand -hex 32  # OOMOL_CONNECT_ADMIN_TOKEN
openssl rand -hex 32  # OOMOL_CONNECT_RUNTIME_TOKEN
openssl rand -hex 32  # MOCK_SAAS_API_KEY
chmod 600 .env

最低限のhardening項目は次の通り。

OOMOL_CONNECT_ORIGIN=http://localhost:3000
OOMOL_CONNECT_ENCRYPTION_KEY=<random-secret>
OOMOL_CONNECT_ADMIN_TOKEN=<random-secret>
OOMOL_CONNECT_RUNTIME_TOKEN=<random-secret>
OOMOL_CONNECT_ALLOWED_ACTIONS=*
OOMOL_CONNECT_BLOCKED_PROXIES=*
MOCK_SAAS_API_KEY=<disposable-test-key>

起動する。

docker compose config --quiet
docker compose up -d --build
docker compose ps
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3101/health

期待する状態は、両コンテナがhealthyで、両health endpointが{"ok":true}を返すことだ。その後、誤キー登録、正しいキー登録、MCPのread/write、監査ログ確認の順に進める。

セキュリティ上の注意

暗号化キーを失うと復号できない

OOMOL_CONNECT_ENCRYPTION_KEYはSQLite内のcredentialやOAuth設定を暗号化する。キーを失うと復元できない。DBとは別のsecret storeへ保管する必要がある。

ローカル開発用defaultのまま公開しない

OpenConnectorは開発用に認証なしでも動くが、資格情報を預けるなら管理tokenとruntime tokenを必須にしたい。Dockerイメージはコンテナ内で0.0.0.0へbindするため、host側のport公開範囲も明示する。

LAN公開はTLSではない

今回のNebulaDockは信頼できる実験LAN内のHTTP公開だ。公衆Wi-Fi、共有オフィス、インターネットへそのまま出す構成ではない。恒久運用ならTLS付きreverse proxy、Tailscale、アクセス制御を追加する。

モックSaaSのデータはメモリ上

NebulaDockのproject stateはプロセスメモリ上にある。コンテナ再作成で初期化される。今回はcredential gatewayの検証が目的で、データ永続化は対象外とした。

今回、証明できたこと/できていないこと

証明できたこと:

  • 誤APIキーを接続作成前に拒否できる
  • 正しいAPIキーをOpenConnectorへ保存できる
  • MCP Action実行時にキーをProviderへ注入できる
  • 読み書き結果がサービス側stateへ反映される
  • gateway側とservice側の両方に秘密値なしの監査証跡を残せる
  • OpenConnector再起動後も保存済みcredentialで再実行できる
  • mock serviceだけをLANへ公開できる

まだ証明していないこと:

  • 任意SaaS用のネイティブOpenConnector Provider実装
  • OAuth2の認可code flowとrefresh token更新
  • 複数ユーザー、tenant分離、rate limit
  • TLS付きLAN/remote access
  • mock serviceの永続storage
  • 本番負荷と障害復旧

次にやること

次の実験は、NebulaDockをDokploy互換test doubleから独立したnative Providerへ進めることだ。

  1. src/providers/nebuladockへdefinition、Action schema、executorを追加する
  2. read/write Actionを明示的なallowlistへ絞る
  3. APIキーをrotation可能にする
  4. SQLiteまたはPostgreSQLでmock stateを永続化する
  5. HTTPSまたはTailscale経由でモバイル接続を再検証する
  6. credential deletionとrotation後の拒否まで監査する

OpenConnectorを評価するとき、最初に見るべきなのはProvider数ではない。「無効な鍵を拒否し、有効な鍵を隠し、必要な操作だけを実行し、その証拠を残せるか」だ。架空SaaSと専用キーを先に作ると、この境界を実アカウントへ触れずに検証できる。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?