投稿

agent に見せるフォルダだけを tailnet 越しに公開する: Taildrive をやめて WebDAV にした

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 が返る。拒否されたかは、ステータスだけでなく、ファイルが作られていないことでも確認した