Orca には、別のマシンで動かしたランタイムにペアリングして接続する機能があります。手元のアプリから、リモートのマシンにある worktree やターミナルをそのまま操作できます。
この接続を外出先からも使いたい、というのが今回の要件でした。経路は Cloudflare Tunnel と Zero Trust Access で作っています。
到達性そのものは素直に作れました。ポート開放も固定 IP も要りません。ただし Access を有効にしたあと、Access が実際には効いていない箇所が 2 つ残りました。どちらも Cloudflare の不具合ではなく、設計上そうなっているものを読み違えていただけです。
結論|3 行で押さえる
要点を先に整理します。
- Orca は接続先の URL へ WebSocket を直接張るだけなので、ブラウザ認証を前提とする Access を通れません。
cloudflared access tcpでローカル転送に落として回避します - Orca のランタイムは
0.0.0.0に bind します。同じ LAN からは Access を通らず直接届くため、端末側のファイアウォールが要ります - 端末ごとのホスト名に所有者名を入れないでください。割り当ての変更はポリシー側だけで動かします
Orca のリモート接続はペアリングコードで繋ぐ
先に、Orca 側の仕組みを押さえておきます。
接続される側のマシンで orca serve を実行すると、ランタイムが起動してペアリングコードが出力されます。
orca serve --port 6769 --pairing-address localhost --json出力には接続先と orca://pair?code=... 形式のコードが含まれます。これを手元で登録します。
orca environment add --name remote-dev --pairing-code 'orca://pair?code=...'登録後は、各コマンドに --environment を付けるとリモートのランタイムを操作できます。
orca host list
# local this machine -> --host local
# orca server remote-dev -> --environment remote-dev
orca worktree list --environment remote-devここで重要なのは、ペアリングコードには接続先のアドレスとポートが埋め込まれることです。あとで出てくるポート番号の話は、すべてこの性質から来ます。
到達性の作り方は 4 通りある
「別マシンに繋ぐ」と言ったとき、選択肢は次の 4 つに分かれます。
| 手段 | 受信ポートの開放 | 向いている状況 |
|---|---|---|
| 同一 LAN で直結 | 不要 | 同じ Wi-Fi にいる |
| SSH ポートフォワード | 不要 | SSH で入れるマシン、クラウド上の VM |
| VPN | 不要 | 外部から常用する、アドレスを固定したい |
| Cloudflare Tunnel | 不要 | 外部から繋ぐが VPN を増やしたくない |
受信ポートを開ける必要があるものは 1 つもありません。「外から繋ぐならポートを開けるしかない」と考えがちですが、そこへ行く前に 4 つとも検討する価値があります。
同一 LAN なら何も挟まなくてよい
同じネットワークにいるなら、回避策そのものが不要です。広告するアドレスにプライベート IP を指定するだけで届きます。
orca serve --port 6769 --pairing-address 192.168.1.42SSH ポートフォワードもトンネルも、LAN が繋がっていないときの回避策です。同じ Wi-Fi にいるうちから持ち出すと、構成が増えるだけで得るものがありません。
SSH ポートフォワードはポート番号を揃える
SSH で入れるマシンなら、これが最も手軽です。VPN もトンネルも要らず、SSH の到達性だけで済みます。
# 手元から
ssh -N -L 6769:localhost:6769 user@remote-host-N はコマンドを実行せず転送だけを行う指定です。端末を占有したくない場合は -f を足すとバックグラウンドへ回ります。
ここで一度つまずきました。手元のマシンで 6768 が既に使われていたのです。Orca のデスクトップ版を動かしていると既定のポートは埋まっているので、lsof で先に確認してください。
lsof -nP -iTCP:6769埋まっていた場合、手元だけポートをずらしたくなりますが、これは避けたほうが安全です。前述のとおり、ペアリングコードにはサーバー側が広告したポート番号がそのまま入ります。手元の転送ポートとずれると接続先が食い違って繋がりません。リモート側の --port と -L の左右の数字を、すべて同じ番号に揃えるのが確実です。
外部から繋ぐなら VPN かトンネル
NAT の内側にある端末へ外部から繋ぐ場合は、到達性そのものを作る必要があります。ここでもポート開放は最後の選択肢です。
VPN は双方がアウトバウンドで繋がるため、公開ゼロで済みます。アドレスも固定できるので、接続設定を作り直す手間がありません。外部から常用するなら、一番手数が少なくて済みます。
Cloudflare Tunnel は、端末側から Cloudflare へアウトバウンド接続を張る方式です。VPN を増やしたくない場合や、認証と監査ログを既に使っている基盤へ寄せたい場合に向きます。今回はこの方式にしました。
要点
到達性の要件を「公開する」と言い換えないでください。VPN もトンネルも、双方がアウトバウンドで繋がることで到達性を作ります。受信ポートを開ける構成は、他に手段が無いときの最後の選択肢です。
先にクイックトンネルで疎通だけ確認する
いきなり本番のホスト名と Access まで組むと、繋がらないときに切り分ける箇所が増えます。先に認証なしのクイックトンネルで、経路そのものが通るかだけを見ておくと楽です。
cloudflared tunnel --url http://localhost:6769trycloudflare.com のランダムなホスト名が払い出されるので、それを orca serve --pairing-address に渡して繋がるか確かめます。通ったら、名前付きのトンネルと DNS レコード、Access のポリシーへ組み替えます。
ただしクイックトンネルは検証専用と割り切ってください。ホスト名は再起動のたびに変わるので、そのつど orca environment add をやり直すことになりますし、認証も付きません。URL を知っている相手は誰でも到達できる状態なので、そのまま常用してはいけません。
Orca は素の WebSocket クライアントなので Access を通れない
構成としては、端末で cloudflared を常駐させ、ホスト名を Access で保護し、手元の Orca からその URL に繋ぐ。これで済むはずでした。
実際には済みません。
認証画面を開けず、Cookie も持てない
Access の認証は、ブラウザでログイン画面を開き、通過すると Cookie が発行され、以降のリクエストはそれを持って通る、という流れです。ブラウザがある前提で組まれています。
ところが Orca に設定できるのは、ペアリングコードに含まれる接続先の URL だけです。ログイン画面を開く機能もなければ、Cookie を保持する仕組みもありません。
この状態は分かりにくい形で表面化します。ブラウザで同じ URL を開くと認証画面が出て通れるのに、Orca からは繋がらない。設定を疑って何度も見直すことになります。
サービストークンで通せる条件
Access はサービストークン用のヘッダーを 2 つ受け付けます。クライアントが任意の HTTP ヘッダーを付けられるなら、ブラウザを介さずに通せます。
ただし Orca にヘッダーを差し込む設定項目はありません。ほかのツールで同じ構成を組むときは、まず設定項目を確認して、無ければ次の方法へ移ってください。
cloudflared access tcp でローカル転送に落とす
確実なのは、クライアント側でも cloudflared を動かす方法です。
cloudflared access tcp --hostname mbp16-01.dev.example.com --url localhost:6769これは Access の認証を肩代わりして、ローカルポートへの転送を用意します。初回だけブラウザで認証が走り、以降はトークンがキャッシュされます。Orca 側は localhost:6769 に繋ぐだけでよくなるので、orca serve --pairing-address localhost で広告しておきます。
ingress は tcp にして catch-all で閉じる
この形にする場合、トンネルの ingress は HTTP ではなく TCP にしておきます。
ingress:
- hostname: mbp16-01.dev.example.com
service: tcp://localhost:6769
- service: http_status:404最後の 1 件は catch-all です。ホスト名付きのルールで終わると cloudflared は起動時に弾かれるため、必ず置いてください。
結局ローカル転送を張っている点は SSH ポートフォワードと同じで、手数も変わりません。Cloudflare Tunnel を選ぶ理由は手軽さではなく、端末側に受信ポートも VPN も要らないことと、認証と監査ログを既存の基盤に寄せられることです。ここを取り違えると期待外れになります。
Orca のランタイムは 0.0.0.0 に bind する
こちらのほうが厄介でした。
広告アドレスと bind アドレスは別物
orca serve の出力を読み直したところ、次のようになっていました。
boundEndpoint: ws://0.0.0.0:6769
advertisedEndpoint: ws://127.0.0.1:6769広告しているアドレスは 127.0.0.1 なのに、実際に bind しているのは 0.0.0.0、つまり全インターフェースです。ペアリングコードに入るのは広告アドレスなので、そちらだけを見てループバックで待ち受けていると思い込みやすくなっています。
実際の bind は lsof で確認できます。
lsof -nP -iTCP:6769
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# Orca 94504 y0n0zawa 52u IPv4 ... 0t0 TCP *:6769 (LISTEN)*:6769 と出たら全インターフェースです。同じ LAN にいる第三者は、トンネルにも Access にも触れずにポートへ直接到達できます。Cloudflare 側でどれだけ厳しくポリシーを書いても、その経路は素通りされます。
注意
Access を入れたことで満足せず、ランタイムが実際にどのアドレスに bind
しているか確かめてください。orca serve
が表示する接続先は広告アドレスであって、bind アドレスとは限りません。
端末側で塞ぐ
対処は、端末側のファイアウォールで該当ポートへの外部からの着信を塞ぐことです。ループバックからの接続だけを許可すれば、同じ端末で動く cloudflared は通り、LAN からの直接到達は落ちます。
一般化すると、Zero Trust の設定はネットワーク層の到達範囲とセットで初めて意味を持つ、ということです。アプリケーション層の認証だけを見て守ったと判断すると、下の層が開いたまま残ります。処理の実行場所を手元に寄せる構成は自社マシンで動かす選択肢としても広がっていますが、寄せた先の到達範囲は自分で閉じる必要があります。
塞いだあとに確かめる
塞いだつもりで塞げていないことがあるので、別のマシンから実際に試してください。同じ LAN にいる端末から、対象のポートへ直接繋ぎに行きます。
nc -vz 192.168.1.42 6769拒否されるかタイムアウトすれば期待どおりです。ここで接続できてしまう場合は、ファイアウォールの規則が効いていないか、ループバック以外も許可されているかのどちらかです。トンネル経由の接続が成功することと、直接の接続が失敗することを、別々に確認してください。 前者だけを見て終わると、塞ぎ忘れたまま運用に入ります。
端末が増えたときのホスト名設計
1 台で動いたら、次は台数です。最初に決めるべきだったのは、ホスト名に何を含めるかです。
ホスト名は端末に固定する
結論としては、ホスト名は端末に固定して、誰が使うかは Access のポリシー側だけで動かすのが正解です。
mbp16-01.dev.example.com ← 機種 + 連番
gpu-01.dev.example.com ← 役割が固定なら役割ベーストンネルは端末ごとに 1 本立つので、ホスト名と端末は 1 対 1 になります。そこに人を混ぜ込むと、貸出先が変わるたびにホスト名が変わり、DNS もトンネル設定も、利用者全員の orca environment も作り直しになります。
Orca 側はローカルで好きな名前を付けられるので、URL が無機質でも実用上は困りません。orca environment add --name 自宅デスクトップ のようにしておけば、orca environment list に出るのはその名前です。URL を不変にしておけば、割り当てが変わっても各自の呼び名は維持されます。
割り当ての変更が、ポリシーのメンバー変更 1 箇所で済む形にしておくのが目標です。台数と人数が少ないうちは、端末ごとにアプリケーションを作ってポリシーに個人を直接書いても回ります。増えてきたらグループを挟み、共用機はチーム単位、個人貸出機は端末単位、と分けると管理対象が膨らみません。
置き場所を分ける
公開サービスと同じ名前空間に内部端末の入口を並べると、事故のもとになります。dev.example.com のような階層を 1 つ挟んで、その下に端末を並べてください。
DNS レコードはワイルドカードにせず、端末ごとに個別で作るほうが運用しやすくなります。今どの端末が生きているかが DNS を見れば分かるためです。
避けたい 3 つの型
実際に検討して落とした案が 3 つあります。
パスで分ける形 (dev.example.com/mbp16-01) は、クライアントがパス付きの接続先を受け付けるとは限らないうえ、Access のポリシーもホスト単位で書けなくなります。ホスト名で分けるほうが確実です。
環境名を混ぜる形 (dev-01、stg-01) は意味がずれます。名前を付ける対象は端末であって環境ではありません。同じ端末で複数のプロジェクトを触るので、名前と実体が対応しなくなります。
連番だけにする形 (host-01 から host-20) は、物理的にどの機体か特定できません。連番にするなら、資産管理台帳と突き合わせられるキーにしてください。
構成管理ツールに載せる
トンネルと Access を構成管理ツールで管理するなら、判断が 2 つ要ります。
トークンを用途で分ける
既存の API トークンに、トンネルと Access の書き込み権限を追加するのが最短です。ただし、そのトークンがアプリケーションの実行環境にも秘密として配布されている場合は話が変わります。実行環境が漏れた時点で、社内端末への接続制御まで奪われるためです。
今回は、必要な権限だけを持つ別トークンを発行し、プロバイダーの別名定義を経由して該当するリソースにだけ使う形にしました。既存のトークンと配布先には手を触れていません。権限を混ぜないほうが、後から効きます。
監査の範囲は、手元の環境も対象に含める方向へ動いています。権限の切り分けは、その流れとも噛み合います。
設定は端末に置かない
cloudflared は、端末のファイルを読む方式と、Cloudflare 側に置いた設定を取りに行く方式の両方に対応しています。
後者にしておくと、経路の変更が構成管理ツールの適用だけで全端末に届きます。端末へ入り直す手間も、適用漏れも消えます。台数が増えるほど差が出る部分です。
orca serve が起動しないときの調べ方
構築の前に、そもそも orca serve が起動しない問題を先に潰す必要がありました。実行するたびに、ポートを掴む前にクラッシュしたのです。
パッケージを展開して初期化順を読む
Orca は Electron 製なので、配布されているアプリのパッケージを展開して該当箇所を読みました。スタックトレースに行番号が出ていれば、原因の特定まではたどり着けます。
読んだ結果は、起動処理の初期化順の問題でした。ある初期化関数より前に、その初期化結果を必要とする処理が動いていたのです。
ここで一度立ち止まる価値がありました。初期化結果を使う側の処理は、条件を満たさなければ早い段階で抜けるように書かれていたのです。つまり本来なら落ちません。呼び出し元を遡ると、コマンドライン側が特定の環境変数を必ず渡す作りになっており、そのせいで早期の分岐を通らず、毎回クラッシュに到達していました。
回避策はここから出ます。コマンドライン側を経由せず、実体を直接起動すればよいわけです。環境変数が渡らなくなるので、早期に抜けて正常に起動します。
コツ
起動時に落ちるツールは、パッケージを展開して該当行を読むのが早道です。なぜ落ちるかだけでなく、なぜその処理に入ったかまで遡ると、回避できる条件が見つかります。落ちる箇所そのものは直せなくても、そこへ到達させない道が残っていることがあります。
機能の有無はヘルプだけで判断しない
これは別件で踏んだ失敗です。あるツールで画像を生成したかったのですが、--help に画像の指定が入力用としてしか載っていなかったため、生成機能は無いと判断しました。
実際には機能フラグ側に生成機能が用意されていて、有効になっていました。ヘルプに出るのは引数の一覧であって、機能の一覧とは限りません。機能フラグや設定を確認するサブコマンドがあるなら、そちらも見てから結論を出してください。
同じマシンで検証するときの 2 つの制約
手元のマシンで動作確認したときは、さらに 2 つ引っかかりました。どちらも本番のリモート端末では起きませんが、検証時には避けて通れません。
1 つ目は、同じ設定プロファイルでは二重起動できないことです。デスクトップ版を動かしたまま同じ設定プロファイルでサーバーを起動しようとすると、単一インスタンスの制約で弾かれます。別のデータディレクトリを指定して逃がします。
2 つ目は、そのディレクトリのパスを短くする必要があることです。長いパスを指定したところ、Unix ドメインソケットの作成に失敗しました。
Error: listen EINVAL: invalid argument /very/long/path/.../daemon/.p...
code: 'EINVAL', errno: -22, syscall: 'listen'macOS のソケットパス長の制限 (約 104 文字) です。一時ディレクトリの下に深い階層を作ると簡単に超えます。ホームディレクトリ直下のような短い場所を使ってください。
ここまでやって、ようやくペアリングと疎通の確認に入れました。orca status --environment <名前> が runtimeReachable: true を返し、orca serve 側が表示した runtimeId と一致していれば繋がっています。仕組み自体は正常で、止まっていたのは起動経路だけでした。
FIXITAccess を入れたなら、もう安全ってことでいいんじゃないの?
Dodaiまず壊れ方から考えます。守れているのは Cloudflare を通る経路だけです。
FIXITえっ、通らない経路があるの?
Dodaiランタイムが全インターフェースに bind していました。同じ LAN からは直接届きます。
FIXITじゃあ結局、端末側でも塞がないと意味がないの?
Dodaiはい。認証だけ足しても、下の層が開いていればそこは事故ります。
まとめ|到達性と保護は別々に確かめる
Orca のリモート接続を Cloudflare Tunnel で外から使う構成は、ポート開放なしで作れます。ただし、繋がったことと守られていることは別です。しかも今回の 2 つは、気づきやすさが逆でした。
Orca が Access を通れない件は、繋がらないので必ず表面化します。一方、ランタイムが全インターフェースに bind している件は、繋がってしまうぶん気づけません。気づく機会が来ないまま残ります。
確認の順番としては、まず到達できることを確かめ、次に到達できてはいけない経路が塞がっているかを別途確かめる、という 2 段階にしておくのが安全です。後者は意識して確認しない限り、開いたまま残ります。
台数を増やす前にホスト名の設計を決めておく点と、構成管理ツールに載せるときに権限を混ぜない点も、後から直すと影響範囲が広がります。1 台目のうちに決めておくと後が楽です。
社内の開発環境をどこまで外へ開くかは、AI エージェントがセキュリティ作業を断るときと同じで、線をどこに引くかの判断になります。まず到達範囲を絞り、そのうえで認証を重ねる順番を崩さないでください。
