Chienomi

SSH接続を維持するsshkeeperとフォワーディング

Live With Linux::newbie

sshkeeperはSSH接続を維持するためのツールである。

これはVPN(リバースフォワーディング)をアイコン操作にしてみる, SSHのリバースポートフォワーディングの強化, 【ConoHa Advent Calendar 2020】ConoHa VPS経由 外出先で宅内のマシン/データにアクセスするで触れてきた、ssh -Rによるリモートフォワーディングの発展型であり、よりessentialになったものだ。

SSH接続の維持は、セキュアトンネル、あるいはフォワーディングを行いたい場合に特に有用である。

このツールはSSH接続維持を行うための雛形に過ぎないため、本記事ではツールの解説ではなく、OpenSSHに関する話を中心に行う。

OpenSSHでのトンネルとフォワーディング

OpenSSHクライアントに直接存在するトンネル/フォワーディング関連機能が、-L, -R, -Dである。

-Lはローカルのポートで待ち受け、これを接続先へ転送する。

-Rはリモートのポートで待ち受け、これを手元に転送する。

-DはローカルのポートでSOCKS5プロトコルで待ち受け、SSHでトンネルする。

この中で特に使うのが-Dオプションで、自分がSSHで入れるサーバーを持っていれば利用可能なSOCKSプロキシになる。 あまり効率はよくないため常用できるようなものではないが、公共WiFiで一時的にセキュアトンネルがほしい場合などに重宝する。

今回の場合は-Rが目当てで、これは以前の記事でも述べたように、出先から自宅PCにアクセスするための経路を用意することにある。

デフォルトでは、-Rによる待ち受けは127.0.0.1にbindされるため、リモートホストにログインされてしまわない限り、インターネットから第三者によって自宅PCにログインされてしまうリスクはない。

一方、localhostに転送する関係上、リモートホストからのログインは基本的にパスワード認証が許されることとなる。

シンプルな話としては、

ssh -R 2200:localhost:22 fooserver

  とすることで、fooserver2200ポートを待ち受け、このマシンのSSHサーバーにアクセスできるようになる。

IPv4のループバックアドレスは、127.0.0.0/8と言いながら、実際はこの空間のアドレスごとに固有のインターフェイスを持つPPPのような構造を持っているため、転送するホストごとに固有のIPアドレスをもたせることもできる。 これは、known_hostsにおいてちょっとだけ便利である。 実際はポート指定つきだとknows_hostsでもポートごとに別サーバーとして扱われるからあまり意味はないけれど、探しやすくなったりはする。

これを実現するためには、なかなか使わないGatewayPorts clientspecifiedsshd_configに記載する必要がある。 この設定は、ホストにアクセス可能なのが自分だけであるという前提がある場合に行わないと、ユーザーが任意にポートをグローバルに公開できてしまうので注意が必要。

この設定がなされていれば

ssh -R 127.0.2.1:2200:localhost:22 fooserver

の形式で任意のインターフェイスにbindできる。 ssh_configでの記載の場合

RemoteForward 127.0.2.1:2200 localhost:22

である。

再起動コントロール

sshを起動して再起動をコントロールするのはSystemdユニットにするのが簡単。

Systemdユニットとして使うことを考えると、接続が有効な間プロセスが動作し、接続が切れたら終了してほしいところだ。 Type=Simpleで扱うことを考えると-Nオプションは使わないほうが良い。

自動再起動の都合を考えると鍵はパスフレーズなしにしたいので、この都合上鍵コマンドを設定したい。

ServerAliveIntervalを設定することで切断されるようにすることも可能ではあるが、それをするとコードで提供する意味が薄いし、よりコントロールしやすいように今回は鍵コマンドにcatを設定する方式にした。 もちろん、ServerAliveIntervalと併用しても良いし、併用するのが望ましい。

SSHが疎通していれば「送る」「受け取る」ができるはずで、これができない場合OpenSSHはフリーズする。 そこで、Rubyでtimeoutライブラリを使い、送信または受信が詰まった場合はプロセスを終了するようにしている。 「1行送信/1行読み取り」をするのが目的なら、呼び出すコマンドはcatで良い。

接続が切れていて、かつプロセスが終了していれば、Systemdによって接続し直すことができる。 ただし、サーバー側で接続が残っていると同じポートをlistenすることができないため、接続がフリーズしていると厄介である。

このため、実際はサーバー側で疎通していなければ切断してもらうように、サーバー側のsshd_configClientAliveIntervalを設定したほうが良い。