家までsshリバーストンネルを掘る
おうち(NATの内側)に設置されたRaspberry Pi(以下RasPi)と、インターネットに存在する踏み台VPSとの間にsshでトンネルを掘っておき、世界中どこからでも帰宅できるようにしておきます。
NATの内側から外側へしか接続できないので、RasPiから踏み台VPSに対してssh接続してリバースsshトンネルをつくり、踏み台VPSの適当なlocalhost:portからRasPiのsshdへ接続できる状態を維持することを目標にします。
Raspberry Pi側の設定
ssh + systemd
ServerAliveIntervalを設定してsshプロセスを起動するとsshのkeep aliveを送るようになり、pingへの応答がServerAliveCountMaxに達するとsshプロセスが落ちます。ここでsystemdをつかってsshプロセスをおもりさせると、sshプロセスが落ちるたびに再起動してくれるようにになります。多少の気休め[2]としてExitOnForwardFailure onをつけてport forward初期化に失敗すると落ちるようにします。
StrictHostKeyCheckingはお好み[3]ですがyes
にするなら一度はトンネル用鍵対使ってsshセッションを発行して、known_hosts
に書いておく必要があります。
あとは、Tでptyを割り当てず、Nでコマンド実行なし、qで静かに。
[Unit]
Description=ssh reverse tunneling
After=network-online.target ssh.service
Wants=ssh.service
[Service]
User=pi
ExecStart=/usr/bin/ssh -TNq -o "StrictHostKeyChecking yes" -o "ExitOnForwardFailure yes" -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" walkure@example.jp -R 50022:localhost:22 -i ~/.ssh/id_tunnel
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
autossh
ServerAliveInterval
でssh接続が腐った場合を検出できるようになりますが、autosshを使うとechoトンネルを別途掘ってforwardを監視させることができます。
autosshは接続監視のecho用にトンネルを2本掘ります。
後述のforwarding port制限を使う場合は、-Mを使って明示的に監視トンネルで使うポートを指定しておきます。なお、-M 0
で起動すると、トンネル掘っての監視をしなくなります。
また、環境変数AUTOSSH_GATETIMEを0
指定[4]して、autosshプロセスが終了しないようにします。
ExecStart=/usr/lib/autossh/autossh -M 50000 -TNq -o "StrictHostKeyChecking yes" -o "ExitOnForwardFailure yes" -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" walkure@example.jp -R 50022:localhost:22 -i ~/.ssh/id_tunnel
Environment=AUTOSSH_GATETIME=0
踏み台の設定
authorized_keysの設定
RasPi起動したとき勝手にトンネル掘ってほしい[5]ので、秘密鍵にパスワードをつけられない。なので、この鍵をauthorized_keysのoptionでトンネル専用にします。ぐぐるとno-pty
などを書き連ねる記事が出てくるんですが、manを見たらOpenSSH 7.2でrestrictというoptionが増えていて、no-*
を書き連ねなくても良くなりました。
このoptionsは前から読まれてゆきrestrict
を見つけるとすべてのpermit flagsを倒す実装になっているので、restrict
より先にport-forwarding
を書いても効果がありません(はまった)。
また、好き勝手にforwardされても困るので、permitlistenでremote forwardを制限し、permitopenでinvalidなhostnameを指定してlocal forwardを潰します[6]。
restrict,port-forwarding,permitlisten="localhost:50022",permitopen="_invalid_host_:1",command="/usr/sbin/nologin" ssh-なんたら~~~~
permitlistenでhost
を省略してport
のみ書いた場合何が起きるかドキュメントに明示されていないので実装を見に行くと、単にport
のみ制限するようです。
今回はlocalhost
[7]にbindするので、明記します。
autosshの監視を使う場合は追加で「-M
で指定したportへのlocal forward」と「127.0.0.1:(-Mで指定したport)
[8]からのremote forward」を行うので、これらを許可します。
restrict,port-forwarding,permitlisten="localhost:50022",permitlisten="localhost:50000",permitopen="127.0.0.1:50000",command="/usr/sbin/nologin" ssh-なんたら~~~~
.ssh/configの設定
これで踏み台VPS上のポート(今回は50022
)でRasPiのsshがlistenしてる(と見なせる)ようになります。あとは.ssh/config
でよしなに端折れるようにしておくと便利。
Host myhome
HostName localhost
User pi
Port 50022
この踏み台VPSを経由して別のホストから繋ぐ場合はProxyJumpを使う感じで。
Host myhome
HostName localhost
User pi
Port 50022
ProxyJump bastion
-
Tailscaleのデフォルト180日で認証切れる(cf. Key Expiry · Tailscale)設定外すのを完全に忘れていて、豪雨で新幹線が止まって帰れない2023年のお盆に認証切れてsshで乗り込む羽目になりました。 ↩︎
-
manにも「ExitOnForwardFailure does not apply to connections made over port forwardings 」って書いてあるし、ソースコードのコメントには
Exit if bind(2) fails for -L/-R
という書き方がされていたりします。 ↩︎ -
StrictHostKeyChecking no
にした上で、UserKnownHostsFileを/dev/null
にすることで、問答無用で接続させることも出来ます。 ↩︎ -
0
にしない場合、default valuteとして30
が設定されautosshがsshプロセスをを起動して30秒以内にsshプロセスが落ちた場合は初期設定失敗と見做してautosshが終了するようになります。 ↩︎ -
出先にいるとき死んで沈黙されると辛いので、WDT入れたりしますよね。 ↩︎
-
SSH: how to allow only local port forwarding and forbid remote one (or vise versa) selectively on key basis - Super Userを参照。 ↩︎
-
manに書かれていますがsshコマンドはホスト名を端折った場合
localhost
を送ってきます。なので127.0.0.1
と書くとpermitされません。 ↩︎ -
ここで出てくる
127.0.0.1
はautossh実装に埋め込まれていて変更できません。local/remoteどちらも127.0.0.1:監視port
にbindできることを仮定しているので、loがIPv4アドレスを持たない場合はつらい気持ちになりそう。 ↩︎
Discussion