agent に見せるフォルダだけを tailnet 越しに公開する: Taildrive をやめて WebDAV にした
まとめ
- Tailscale の ACL が絞れるのはポートまで。どのフォルダを見せるかは、配信するプロセス側で決める
- Taildrive は、読む側の Tailscale クライアントが手元で WebDAV を待ち受ける仕組みで、受信なしの専用クライアントしか持たない端末では使えない
rclone serve webdavを tailnet の IP に bind し、ACL でそのポートだけ許可した。読み取り専用と読み書きは別ポートに分けた- 実測では、一覧の取得、読み取り専用側への書き込み拒否、読み書き側への書き込み、共有の外へ出られないことの 4 点が期待どおりだった
関連: Tailscale 自体の入門は VPN サーバーについて調べる - Tailscale入門 にある。
どの方式で見せるか
tailnet に入れた agent の端末から、自分の PC の特定のフォルダだけを読み書きさせたい。 シェルやホーム全体は渡したくない。
表1: 選定フロー
| 手順 | 判断すること | 結果 |
|---|---|---|
| 1 | 読む側のクライアントが、Taildrive 用のローカルの WebDAV(100.100.100.100:8080)を持つか | 持たない端末では Taildrive が落ちる |
| 2 | 見せる範囲を、ポートではなくフォルダで絞れるか | ACL だけの案と、同じアカウントの端末をそのままユーザーとして入れる Tailscale SSH の案が落ちる |
| 3 | 残った方式 | 既定は rclone serve webdav を tailnet の IP で待ち受けさせる |
既定は rclone serve webdav + ACL。 配信プロセスに渡したフォルダの外は見えず、読む側は HTTP が出せれば足りる。
手順 1 で落ちた理由は、読む側の環境にある。 今回の agent 端末は、tailscaled も TUN も持たない。 tailnet への通信は外のトンネルへプロキシ経由で出るだけで、受信はできない。 Taildrive は、読む側が自分のローカルの WebDAV を通して共有を読む仕組みなので、これを持たない端末では、プロキシ経由で送っても応答が 0 バイトのまま閉じられた。
手順 2 で落ちた案は、ACL と Tailscale SSH である。 ACL はポートまでしか絞れない。 Tailscale SSH は、同じアカウントの端末が、この PC のユーザーとして入れる設定にすると、そのユーザーのホーム全体が見えてしまう。
方式ごとの比較
判定は、読む側が「受信なし・プロキシ経由で HTTP を出すだけ」の環境での結果である。
表2: 方式の比較
| 方式 | 見せる範囲をフォルダに絞れるか | 読む側に必要なもの | 今回の判定 |
|---|---|---|---|
| ACL だけ | 絞れない(ポートまで) | なし | 落ちる |
| Tailscale SSH | 絞れない(ユーザーのホーム全体) | SSH クライアント | 落ちる |
| Taildrive | 絞れる(共有ごとに ro / rw) | ローカルの WebDAV を持つ標準クライアント | 使えなかった |
rclone serve webdav + ACL | 絞れる(渡したフォルダだけ) | HTTP クライアント | 採用 |
専用ユーザーを作り、internal-sftp で chroot する案も考えたが、試していない。 読む側の通信がプロキシ経由の HTTP だけなら、SSH が通る保証が無いためである。
構成と設定
PC 側で、user の systemd unit を 2 つ動かす。 読み取り専用には --read-only を付ける。 待受は tailnet の IP に直接 bind し、LAN や 127.0.0.1 には出さない。
# agent-webdav-ro.service
[Service]
ExecStart=/usr/bin/rclone serve webdav %h/Share/agent_ro --addr <この PC の tailnet IP>:8601 --read-only
Restart=always
RestartSec=10
# agent-webdav-rw.service
[Service]
ExecStart=/usr/bin/rclone serve webdav %h/Share/agent_rw --addr <この PC の tailnet IP>:8602
Restart=always
RestartSec=10認証は置かず、ACL で agent 端末だけに絞る。 tests を書くと、保存時に「許可すべき通信が通り、拒否すべき通信が拒否される」ことを検査してくれる。
{
"hosts": {
"agent": "<agent 端末の tailnet IP>",
"desktop": "<この PC の tailnet IP>"
},
"acls": [
{ "action": "accept", "src": ["agent"], "dst": ["desktop:8601", "desktop:8602"] }
],
"tests": [
{ "src": "agent", "accept": ["desktop:8601", "desktop:8602"] },
{ "src": "agent", "deny": ["desktop:22", "desktop:2049", "desktop:111"] }
]
}この PC では NFS(2049)と rpcbind(111)が 0.0.0.0 で待ち受けていた。 そのため acls を空から始めて、必要な 2 ポートだけを足した。 acls に desktop:* を許可すると、この 2 つにも届く。
実測で確認したこと
agent 端末から、プロキシ経由で PC の tailnet の IP 宛てに送った。 書き込みの結果は、PC 側のファイルの有無でも確認している。
表3: 実測結果
| 試したこと | 期待 | 結果 |
|---|---|---|
8601 に PROPFIND | 一覧が返る | 207。共有の直下の 2 フォルダが返った |
8601 へ PUT | 拒否される | 404。PC 側にファイルは作られていない |
8602 へ PUT | 書き込める | 201。PC 側に、送った本文のファイルができた |
/../../etc/hostname を 8602 へ(--path-as-is) | 共有の外は見えない | 404 |
ハマった点
- Taildrive の共有名には、ハイフンが使えない。英小文字・数字・
_・()・内部のスペースだけで、agent-roは ACL の保存時に拒否された - rclone の FUSE マウントは、マウントしたユーザー以外には、root でも読めない。root で動く
tailscaledが共有を作ろうとしてpermission deniedになった。マウントに--allow-otherを付ける代わりに、読み取り専用のコピーをrsyncで作って、そのコピーを配信した --read-onlyの WebDAV にPUTすると、403 ではなく 404 が返る。拒否されたかは、ステータスだけでなく、ファイルが作られていないことでも確認した