オープンソース
Tailcat
要約
Tailcatは、Tailscaleのデータプレーン(WireGuardベースの暗号化トンネル)を使用し、Tailscaleのコントロールプレーンなしでnetcatのように動作するツールです。接続メタデータは帯域外で交換され、ユーザーはTailscaleアカウントやroot権限なしで、エンドツーエンドで暗号化されたピアツーピア接続を確立できます。stdin/stdoutのパイプ、ポート転送、SSHサーバーの公開など、多様な用途があります。
全文翻訳
"TailscaleなしのTailscale、Tailscaleによる" Tailcat
Tailcatは、Tailscaleのオープンソースコンポーネントをリミックスして、netcatのように動作するようにしたものです。Tailscaleのデータプレーン上で動作しますが、Tailscaleのコントロールプレーンは使用しません。Tailscaleのデータプレーン(内部的にはmagicsock)は、2つのマシン間にWireGuard®で暗号化されたポイントツーポイントトンネルを提供し、DERPがNATホールパンチングのサイドチャネルとして機能し、NATトラバーサルが失敗した場合の最終的なリレーとなります。Tailscaleのコントロールプレーンを使用する代わりに、すべてのtailcat接続メタデータは帯域外で、あなたが望む方法で交換されます。
tailcat CLI(cmd/tailcat内)は、tailcat Goライブラリ(github.com/tailscale/tailcatとしてインポート可能)上に構築されています。tailcatをCLIツールまたはライブラリとして使用する場合、一方の側がtailcatサーバー(リスナー)を実行し、短い接続トークンを受け取ります。もう一方の側は、そのトークンをtailcatのクライアント側に渡して接続します。両者間のすべてのトラフィックは、WireGuardでエンドツーエンドで暗号化されます。初期接続はTailscaleのDERPリレーネットワークを通じてブートストラップされ、その後magicsockがNATトラバーサルを実行して、可能な場合は(通常は!)直接ピアツーピアUDP接続にアップグレードします。Tailscaleアカウントやマシン上のroot/管理者アクセスは不要です(マシンのルーティングテーブルやDNSなどを変更しません)。これは単なるユーザー空間のライブラリおよびCLIツールです。そして、すべてオープンソースです。無料のレート制限付きDERPリレー(デフォルトのDERPマップはhttps://tailcat.dev/derpmap.json)を使用することも、独自のものを実行することもできます。
使用法
標準入出力(stdin/stdout)を2台のマシン間でパイプする
サーバーを起動すると、一時的なアドレスが出力されます。
$ tailcat
# 選択されたブートストラップリレーリージョン 302, サンフランシスコ
# 🐈 新しいアドレスでリッスン中: tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu (待機中...)
そしてクライアントは次のようにできます。
$ echo hello | tailcat tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
するとサーバーはブロックを解除します。
$ tailcat
# 選択されたブートストラップリレーリージョン 302, サンフランシスコ
# 🐈 新しいアドレスでリッスン中: tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
hello
ローカルポートをトンネル経由で公開する
または、ローカルのTCPポートをトンネル経由で転送することもできます。
$ tailcat --serve=8080,8443
# または --serve=all
# 🐈 新しいアドレスでリッスン中: tcXXXXXXXXX
そしてクライアント側で。
$ tailcat tcXXXXXXXXX 8080
GET / HTTP/1.1
Host: foo
HTTP/1.1 200 OK
....
認証不要のSSHサーバー
LinuxおよびmacOSでは、認証なしでSSHサーバーを実行することもできます。(認証が必要な場合は、単に tailcat --serve=22 とシステムSSHサーバーへのプロキシを設定できます)
$ tailcat --serve=no-auth-ssh
# 🐈 新しいアドレスでリッスン中: tcXXXXXXXXX
そしてクライアント側で。
$ tailcat ssh tcXXXXXXXXX
$ tailcat ssh tcXXXXXXXXX ls -la
その他のコマンド
接続テストのためのPing。各pongは、DERPリレー経由で到着したか、直接パス経由で到着したかを示します。
--until-direct は、直接パスが機能するまでPingを続けます(--timeout、デフォルト10秒まで)。直接パスが機能しない場合は非ゼロで終了します。
$ tailcat ping --until-direct <トークン>
pong in 42.1ms via DERP(sfo)
pong in 1.2ms via 203.0.113.7:41641
トンネルを介してルーティングされるSOCKS5プロキシを介してコマンドを実行します。
$ tailcat socks <トークン>
curl http://server.tailcat:8081/
トークンはURLホスト名としても直接機能します。SOCKSプロキシはそれを認識してダイヤルするため、トークン引数はオプションです。(トークンは大文字小文字を区別します。これはcurlやほとんどのCLIツールでは機能しますが、ホスト名を小文字にするブラウザでは機能しません。)
$ tailcat socks curl http://<トークン>:8081/
クライアントがサーバーのネットワークに到達できるように、出口ノードとして機能します。
$ tailcat --serve=exit-node
接続トークンを解析し、その内容(サーバーのWireGuard公開鍵とDERP情報)をJSONとして、何も接続せずに表示します。
$ tailcat parse tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
{
"ServerPublic": "nodekey:9c8d2e6728da80a1dd37e275a82595b42d9a838610bc53f74a7670d1610f2e34",
"RegionID": 302
}
短いトークン(DERPリージョンIDを参照しており、クライアントはDERPマップを取得する必要があります)を、DERPサーバー情報が埋め込まれた長い自己完結型トークンに解決し、クライアントがより速く接続できるようにします。
$ tailcat resolve tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFygaFhToGjYWhudGMzMDJhLmlwbi5kZXZhNG0yMDguMTExLjM5LjM4YTZzMjYwNzpmNzQwOjA6M2Y6OjcyMA
解決されたトークンを解析すると、埋め込まれたDERP情報が表示されます。
$ tailcat parse tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFygaFhToGjYWhudGMzMDJhLmlwbi5kZXZhNG0yMDguMTExLjM5LjM4YTZzMjYwNzpmNzQwOjA6M2Y6OjcyMA
{
"ServerPublic": "nodekey:9c8d2e6728da80a1dd37e275a82595b42d9a838610bc53f74a7670d1610f2e34",
"Region": [
{
"Nodes": [
{
"HostName": "tc302a.ipn.dev",
"IPv4": "208.111.39.38",
"IPv6": "2607:f740:0:3f::720"
}
]
}
]
}
サーバーは --full-address フラグを使用して長い自己完結型形式を直接出力できます。
鍵管理
サーバーのアドレス(接続トークン)は、そのWireGuard鍵から派生するため、使用する鍵が誰があなたに到達できるかを決定します。
一時鍵(デフォルト):各サーバー実行はメモリ内で新しい鍵を生成し、誰も見たことのないアドレスを出力します。プロセスが終了すると、鍵は破棄され、アドレスは永久に無効になります。これは安全なデフォルトです。そのアドレスを共有することは、その単一の実行のみを参照します。
保存された鍵:tailcat genkey は、再起動後もアドレスが安定するように、ディスクに保存される鍵を生成します。裏を返せば、そのアドレスを共有したことのある人は誰でも、クライアントを --allow で制限しない限り(tailcat genkey --client を参照)、その鍵を使用して将来のサーバーに接続できます。
CLI は起動時にどちらの種類を使用しているかを示すため、単回使用の新しいサーバーを開始しているのか、過去に共有したアドレスで再リッスンしているのかを知ることができます。
$ tailcat genkey --region=nyc
# トークンを出力します。鍵は ~/.config/tailcat/keys/default.private.json に保存されます。
# 後で; "default" という名前の鍵は、存在すれば自動的に使用されます。
$ tailcat --serve=8080
# 🐈 保存された鍵 "default" でリッスン中: tcXXXXXXXXX
# ... または一時鍵を強制する場合:
$ tailcat --serve=8080 --key=new
# 🐈 新しいアドレスでリッスン中: tcXXXXXXXXX
つまり、default は特別な鍵名です。一度存在すると、通常の tailcat はそれを生成せずにサイレントに使用し、上記の起動行が何が起こったかを示します。一時鍵を取得するには --key=new を使用し、別の保存鍵を使用するには --key=<name> を使用し、保存されたデフォルト鍵を削除するには tailcat genkey --delete --key=default を使用します。
tailcat genkey --list は保存されている鍵を一覧表示します。
トークンは DNS TXT レコードとして公開することもでき、名前で検索できます。DNS 名は、CLI がトークンを受け付ける場所ならどこでも機能します。
# example.com が TXT レコード "tailcat=tc..." を持っている場合
$ tailcat example.com 8080
$ tailcat ssh example.com
$ tailcat ping example.com
例
DNS経由の保護されたSSHサーバー
ポートフォワーディングやポートノッキングが必要なのは誰ですか?これは、サーバー上のインバウンドポートを開くことなく、名前でどこからでも到達可能なSSHサーバーを実行します。WireGuardは、SSHサーバーがパケットを見る前にクライアントを認証します。
クライアントマシンで、クライアントID鍵ペアを生成します。公開鍵が出力され、サーバーが知る必要があるのはそれだけです。
client$ tailcat genkey --client
# ファイルを ~/.config/tailcat/keys/client-default.private.json に書き込みました。
nodekey:cfb6bfa77a0654d7450947fd6acef17d2cd848da1d30b2540b13dac272ddfd16
サーバー側で、最も近いDERPリージョンにピン留めされたサーバー鍵ペアを生成します(理由は後述)。その後、そのクライアントのみにSSHを提供します。
server$ tailcat genkey --fixed-region
# ファイルを ~/.config/tailcat/keys/default.private.json に書き込みました。
tcXXXXXXXXX
server$ tailcat --serve=22 --allow=nodekey:cfb6bf...ddfd16
# 🐈 保存された鍵 "default" でリッスン中: tcXXXXXXXXX
DNSにトークンをTXTレコードとして公開します。
my-server.example.com. 300 IN TXT "tailcat=tcXXXXXXXXX"
そしてクライアント側は単に次のようになります。
client$ tailcat ssh my-server.example.com
クライアントモードは、保存された client-default 鍵が存在する場合、自動的にそれを使用するため、許可されたIDを提示するために追加のフラグは必要ありません。他の誰かのハンドシェイクはサイレントに無視されます。彼らはSSHサーバーに到達できませんし、学ぶこともできません。