SSH Configの書き方
SSHでは色々なことができる
SSHは開発者ならよく使うツールだと思う。
基本的にはリモートのマシンに入ってシェルを叩くのに使うツールなのだが、実はいろいろなことができる。
最近仕事でそれなりに複雑な構成でSSHを使用する機会があったので、SSH Configを書いた。この記事ではその時に学んだことをまとめる。
基本形
Host my-con-name
HostName xx.xx.xx.xx
Port 22
User my-user-name
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
基本形はこれだと思う。
Host my-con-nameで接続の名前を指定する。
ここで指定した名前でssh my-con-nameとコマンドを打つと、上に書いた設定が自動適用になるというわけ。
HostNameには、アクセス先のドメイン名やIPアドレスを書く。
Portには、アクセス先のポート番号を書く。
22番はSSHのwell-knownポートなので、ここを記載しないと22番が使われる。
しかし、セキュリティ上の理由からポート番号を変えているSSHサーバーも多い。
Userには、アクセス先のユーザー名を書く。
configを書かずにやるときの基本形は、ssh my-user-name@xx.xx.xx.xxだが、これを書いておくと、このmy-user-nameの部分も省略できる。
IdentityFileには秘密鍵を指定する。デフォルトで使われるのはid_rsaやid_25519などだが、別の鍵を使用する場合にはここを指定する。
複数の鍵を使用している場合には、IdentitiesOnly yesを指定しておくべきである。
そうしないと、たとえば、IdentityFile ~/.ssh/my_id_001などと指定していても、id_rsaがある場合、「まずid_rsaで接続を試してだめなら、my_id_001を試す」というような挙動になってしまう。
これは多くの場合意図した挙動ではない。
多段SSH
踏み台サーバーにまずSSHして、そこから別のマシンにSSHするというのもよくある構成である。
これは次のように設定する。
Host bastion
HostName xx.xx.xx.xx
Port 22
User my-bastion-user-name
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
Host my-con-name
HostName yy.yy.yy.yy
Port 22
User my-user-name
ProxyJump bastion
ForwardAgent yes
こうしておくと、ssh my-con-nameと打つだけで、
「ssh my-bastion-user-name@xx.xx.xx.xxしてから、その先でssh my-user-name@yy.yy.yy.yy」したのと同等の効果が得られる。
ProxyJump bastionというのがミソである。
ForwardAgent yesというのは鍵をローカルにおいたまま、あたかもその秘密鍵で踏み台の先のサーバーも認証しているようにする設定である。
秘密鍵を踏み台に転送するのはセキュリティ的によろしくないので、基本的にはyesにしておくべきである。
ProxyJumpによるSSHの連鎖は、何段にしても良い。例えば次のような設定にすれば、bastion1→bastion2→serverというふうにアクセスすることになる。
Host bastion1
HostName xx.xx.xx.xx
Port 22
User my-bastion1-user-name
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
Host bastion2
HostName yy.yy.yy.yy
Port 22
User my-bastion2-user-name
ProxyJump bastion1
ForwardAgent yes
Host my-con-name
HostName zz.zz.zz.zz
Port 22
User my-user-name
ProxyJump bastion2
ForwardAgent yes
ポートフォワーディング
ポートフォワーディングには、3種類ある。1つずつ見ていく。
LocalForward
Host my-con-name
HostName xx.xx.xx.xx
Port 22
User my-user-name
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
LocalForward 8080 yy.yy.yy.yy:80
このようにしておいて、ssh my-con-nameとすると、ポート転送が起きる。
ローカルマシンで127.0.0.1:8080にアクセスすると、xx.xx.xx.xxからyy.yy.yy.yy:80へのアクセスが発生する。
yy.yy.yy.yy:80からxx.xx.xx.xxに返ってきたデータは、127.0.0.1:8080に送られる。
つまり、ローカルマシンからは127.0.0.1:8080がyy.yy.yy.yy:80のように見えるわけだ。
LocalForward 8080 yy.yy.yy.yy:80の第一引数8080はアドレスが省略されていて、この場合127.0.0.1:8080と同じ意味である。
複数のポートフォワーディングを行うときは、ローカル側のポート番号を変えてバインドしても良いし、ローカルループバックアドレスを変えてバインドしても良い。
つまり、
LocalForward 8080 yy.yy.yy.yy:80
LocalForward 8081 zz.zz.zz.zz:80
とすれば、ポート番号を変えたバインドで、
LocalForward 127.0.0.1:8080 yy.yy.yy.yy:80
LocalForward 127.0.0.2:8080 zz.zz.zz.zz:80
とすれば、ローカルループバックアドレスを変えたバインドになる。
また、
LocalForward 0.0.0.0:8080 zz.zz.zz.zz:80
とすれば、外部からもアクセスできるようになる。
ローカルマシンのLAN内におけるアドレスがたとえば、192.168.0.100なら、
LAN内の別のマシンから192.168.0.100:8080とたたくと、それが結局yy.yy.yy.yy:80に転送されるわけである。
0.0.0.0:8080はIPv4専用であり、::8080はIPv6専用、*:8080とすればそのどちらにも対応する。
RemoteForward
Host my-con-name
HostName xx.xx.xx.xx
Port 22
User my-user-name
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
RemoteForward 80 yy.yy.yy.yy:8080
RemoteForwardはLocalForwardの「逆」だと思えば良い。
つまり、リモートマシンで(127.0.0.1:)80番ポートへのアクセスが起きると、ローカルマシンからyy.yy.yy.yy:8080へのアクセスが発生する。
yy.yy.yy.yy:8080が返したデータは、リモートマシンの80番ポートから返ってきたかのように見える。
これだと少しイメージが湧きづらいが、
RemoteForward 0.0.0.0:80 127.0.0.1:8080
という設定を考えると、使う場面のイメージが湧いてくる。
自宅で動かしている開発用のサーバーをオフィスの人に見てもらいたいときなどに、上のような設定をしてオフィスのマシンやAWS上のEC2などにSSH接続して見れるようにするといった使い方ができるわけである。 Basic認証などはつけておいたほうが良く、いまだとngrokなども手軽な手段ではあるが。
また、
RemoteForward :2222 127.0.0.1:22
という設定で、たとえば自宅のマシンからクラウド上のマシンに接続しておけば、クラウド上のマシンを経由して自宅のマシンにSSH接続できるようになる。
DynamicForward
DynamicForwardは上記のLocalForward/RemoteForwardとはかなり違う毛色のものであり、SOCKSプロキシというものを立てて、それを介すればあたかもローカルのマシンがリモートのネットワークの中にいるかのように振る舞わせるといったものである。
かんたんに言えば、VPNみたいなものである。
ただし、SOCKSプロキシは、プロキシでありアプリケーション層で考慮が必要である(つまりVPNのように透過的にすべての通信を扱うわけではないので、アプリケーションによっては対応していなかったり設定が必要になったりする)のと、またそれ自体では暗号化もしない点が異なる。
このように使う。
Host my-con-name
HostName xx.xx.xx.xx
Port 22
User my-user-name
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
DynamicForward 1080
こうすると、1080番ポートにSOCKSプロキシが立つ (1080番が慣習的によく使われる)。
このように立てたSOCKSプロキシを使用するようにする方法は、もちろんアプリケーションごとに異なるが、一応システム的に設定することもできる (この設定を利用するかどうかもアプリケーション次第)。
UbuntuではSettings→Network→Network Proxyから設定が可能である。

Windowsでは「スタート」→「設定」→「ネットワークとインターネット」→「プロキシ」でアドレスとポートを設定するが、アドレスは「socks=localhost」とやるのが正解のようである。あまり使用している人がいないのか、雑な仕様である。
アプリケーションレベルの設定は、たとえばFirefoxではGeneral→Network Settings→Manual proxy configurationをから行う。Use system proxy settingsを選択すれば、システムの設定を利用してくれる。

curlの場合は次のように指定する。
curl -x socks5://localhost:1080 http://www.example.com/
fオプションとNオプション
ポートフォワーディングをしたいだけで、リモートのコマンドを叩く必要がない場合は、fオプションとNオプションを組み合わせて使用すれば良い。
ssh -fN my-con-name
fオプションは、フォークしてバックグランド実行するオプションである。 しかし、コマンドが指定されていないと、実行できない。
特段実行したいコマンドもないわけなので、Nオプションを使う。Nオプションは、リモートコマンドを実行しないオプションである。
X11フォワーディング
Host my-con-name
HostName xx.xx.xx.xx
Port 22
User my-user-name
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
ForwardX11 yes
これは接続先のGUIアプリをローカルでも見れるように転送してくれるというものである。
ポートノッキング
SSHサーバーでは、ポートノッキングというセキュリティレイヤーを設けている場合もある。
これはSSHのポートを普段は公開せず、特定の順番でポートを叩くことによって一定時間だけポートを解放する、というものである。
これをSSHクライアント側から自動で行えるようにするには、例えば次のような設定をすれば良い。
Host my-con-name
HostName xx.xx.xx.xx
Port 22
User my-user-name
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
ProxyCommand bash -c 'knock -d 100 %h 7000:tcp 8000:tcp 9000:tcp; sleep 1; exec nc %h %p'
ここではknockコマンドを使用した。
ProxyCommandに記述した外部コマンドがssh my-con-nameした際に起動されて、そのstdin/stdoutがSSHパケットの入出力に直結される。
おわり
SSHそしてSSH Configは凝って色々なことができるということがわかった。まだ色々な機能がありそうだが、とりあえずこれくらいにしておく。