読み返してたら滑ってて恥ずかしかったので冒頭の文を消しました。 タイトルでも滑ってるのに2重で滑るのは耐えられません。
今回は @pjxiao が SSHを使いこなしているのを見て悔しかったので自分もちゃんと理解しようと思い記事にしました。
Local forwarding
開発者にとってはこれが最も一般的な使い方かなーと勝手に思ってます。
DBや内部システムで使われるサーバではアクセス元制限がかかっていることが多いんですが、動作確認のために接続したくなることはよくあります。
特に開発用のDBのレコードをローカル環境のプログラムで表示したくなるケースはとっても多いです。ね?
こんなときに使うのが俗にSSHポートフォワーディングと呼ばれるもので、ローカルに対するアクセスをリモートに受け流す方法です。 (以降はローカルフォワーディングとか、単にフォワーディングという)
L option
具体的には -Lオプション
を使います。
ssh 踏み台ホスト -L ローカルポート:リモートホスト:リモートポート
-L オプション
はローカルへの通信をリモートにバインドするためのオプションです。
実例を示します。
- Webサーバ以外からは接続できない MySQL サーバがあります。
- Webサーバのホストは
www.example.com
で MySQL サーバのホストはmysql.example.com
とします。
- Webサーバのホストは
- Webサーバでは22番ポートが全体に対し、MySQLサーバでは3306ポートがWebサーバのみに対し公開されている(危ない)設定だと思ってください。
- 実際に試した環境はこれではないんですが、公開できないので書き換えてます
最初にリモートホストに直接接続を試みます。
$ mysql -u username -p -h mysql.example.com Enter password: ERROR 2003 (HY000): Can't connect to MySQL server on 'mysql.example.com' (60)
つながりませんでした。これは期待通りです。
次に以下のようにトンネルを張ります。
$ ssh www.example.com:22 -L 3307:mysql.example.com:3306 Last login: XXX XXX HH:MM:SS YYYY from aa.bb.cc.dd
www.example.com に SSH 接続されました。
少しわかりにくいのですがこの時点で localhost:3307
と
mysql.example.com:3306
がトンネルでつながっています。
(ポート番号をずらしたのは読んでいる方が混同しないためなので、同じにしてもOK)
さて、ここで 別のターミナル でローカルの 3307
番に接続してみましょう。 トンネル元(localhost側)でやってください
$ mysql -u username -h localhost --protocol tcp --port 3307 -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 69664
今度はつながりましたね。 脱線ですが --protocol tcp
がないと unix domain socket
で接続を試みるようです(ループバックアドレスでも良いとのこと)
プログラムであれば、DBの接続先を上記のようにローカルに合わせれば動くようになるはずです。
この例は MySQL
ですが PostgreSQL
などのDBも同様です。さらに言えばDBに限った話ではないです。
このようにSSHトンネルを作った後のエンド間のやりとりは SSH
を意識しなくてもよいのです。 今回の例で言うと MySQL クライアント は
localhost:3307
にある MySQL サーバ に接続しているのと同じことで、これがSSH
トンネルかどうかなんて知っている必要はありません。
N option
そういえば -L オプション
でトンネルは掘ってくれましたが、接続先サーバでシェルが起動してしまいました。
トンネルするためなのでシェルは要らないという場合、 -Nオプション
を指定します。
$ ssh -N www.example.com:22 -L 3307:mysql.example.com:3306
するとこの状態で止まります。プロセスを止めない限りフォアグラウンドに居続けます。
止まってくれたほうがわかりやすい感じがしますが、この状態で十分でしょうか?
f option
固まり続けるターミナルなんて私は要りません。今度は同時に -fオプション
をつけてみましょう。
このオプションを指定するとプロセスがバックグラウンドで実行されるようになります。
以下のように連ねて記述できます。(本当は Lオプション
も繋げられるけどね)
$ ssh -fN www.example.com:22 -L 3307:mysql.example.com:3306 $ # 別のコマンドを入力できる。
ちなみに -f オプション
だけ指定はできません。エラーです。
$ ssh -f www.example.com:22 -L 3307:mysql.example.com:3306 Cannot fork into background without a command to execute.
多段接続する
ここまで読んで、「いやいや、WebサーバのSSHポート全体に公開してないからw」という人も多いでしょう。
たしかに踏み台と呼ばれる中間のサーバからしか接続を許可していないことがよくあります。 このような場合、SSH接続を複数繋げて多段にします。
登場人物(gateway.example.com)を一人増やして接続例を見てみましょう。
t option
-tオプション
の後ろには
SSH接続先のサーバで実行するためのコマンドを記述できます。
これを利用して更に SSH コマンドを書けば 多段接続
となります。
$ ssh gateway.example.com -t ssh www.example.com
とっても簡単ですね。更に接続を増やしたい場合、 -tオプション
を増やしていけばOKです。
先程のトンネルと組み合わせると以下のようになります。
$ ssh gateway.example.com -fNL 3307:mysql.example.com:3306 -t ssh www.example.com $
ProxyCommand
上記のように毎回複数のホストを書くのは結構な手間です。
~/.ssh/config
を設定することで最終ホストの指定だけで接続できるようになります。
Host www.example.com HostName www.example.com User username ProxyCommand ssh -W %h:%p gateway.example.com
更に接続を増やしたい場合、例えば
- gateway2
- gateway
- www
のようにホップしたいときは以下のように記述します。
Host www.example.com HostName www.example.com User username ProxyCommand ssh -W %h:%p gateway.example.com IdentityFile ~/.ssh/id_rsa Host www.gateway.com HostName www.gateway.com User username ProxyCommand ssh -W %h:%p gateway2.example.com IdentityFile ~/.ssh/id_rsa2 Host gateway2.example.com HostName gateway2.example.com User username IdentityFile ~/.ssh/id_rsa3
(Hostと同じなのでおそらくHostNameは省略可能)
SSH-Agent
が有効な場合か中間サーバ側に接続用の設定がある場合は
IdentityFile
の設定は不要です。
この設定をしておけばトンネル時に最終ホストだけを指定すれば良いので、長期的に利用する場合はこちらのほうがおすすめです。 今回 SSH-Agent の説明はしません。
SOCKS で接続する
アクセス元が制限されたWebサイトへアクセスするにはどうしたらいいでしょう。
さっき説明したフォワーディングを使う? 答えは状況によるのですが概ね No
です。
試しに hatenablogにフォワーディングで繋いでみましょう。(外部公開されてないという想定で読んで下さい..)
$ ssh gateway.example.com -NL 8888:hatenablog.com:80
次にWebブラウザから localhost:8888
にアクセス。
なんとかページは表示されましたが何故か404。
Webサーバ
にとっても、 Webブラウザ
にとっても、あるいは Webアプリ
にとってもアドレス(ドメイン)というのは大事な意味を持ちます。サーバ側とブラウザ側でこの解釈が異なると表示が崩れてしまうのは仕方のないことなのです。
特に SSL 対応しているサイトは SSL証明書
がドメインに対して発行されているため、
localhost
というドメインと一致せず弾かれることは目に見えています。
フォワーディングが適さない理由がわかりました。
こういう場合はプロキシ接続によって繋いであげる必要があります。(先程の「ProxyCommand」は関係ありませんよ)
じゃあ普通にHTTPプロキシたてればいいじゃんてことになりますが、ただ経由するだけならHTTPプロキシを建てるまでのことはないし、何よりめんどくさいです。
そこで SOCKS を使うことで SSH サーバだけでプロキシ接続できてしまいます。
SOCKS は、ネットワーク・ファイアウォール越えやアクセス制御等を目的として、クライアントサーバ型のプロトコルが、透過的に使用できるよう設計されたプロキシ(proxy)のプロトコル、及びシステム(の一つ)である。 "SOCKetS"の略。
ということで、簡単に言うと HTTPにかぎらず大体どんなものでも通すプロキシです。
実際に SSH で SOCKS
を使ってみましょう。
D option
-D オプション
はアプリケーションレベルの動的なポート転送を指定します。
何を言ってるのかよくわかりませんが、SOCKSプロキシのためのオプションだと思えばOKです。
使い方は -Lオプション
と大して変わりません。
$ ssh gateway.example.com -fND localhost:8888
Web ブラウザの プロキシ設定に移動し、SOCKS(5) の
- プロキシホスト
- localhost
- プロキシポート
- 8888
となるように設定すればOKです。(HTTPプロキシではありません)
FireFox だとこんな感じ。
これで設定は終わりです。
どうでしょうか。はてなブログは見えましたか?(キャプチャは撮ってないのでご自身でお確かめください)
SSH (-D) トンネル は SOCKS プロキシのように振る舞えるがイコールではないので注意してください。
- info
- (おまけ)圧縮を指示する -C オプションと同時に使われることが多いようです。
Remote forwarding
ローカルフォワーディングでは公開されているサーバに対して通信をトンネルしていましたが、 リモートフォワーディングは逆向きの通信をトンネルします。つまりローカルに対して通信をトンネルします。
なぜそんなことが必要かというと、 ローカル環境のような非公開の端末に対してリモートから接続するのはネットワークの構成上難しいことが多いためです。
これを解決するために、ローカルからリモートにトンネルを張ってもらい、 それ以降の通信はリモートからローカルに行われるのでローカルフォワーディングとは逆になります。
ローカルフォワーディングでは対応できないのですね。
- 種類
- ローカルフォワーディング
- リモートフォワーディング
- 接続時の方向
- ローカル→リモート
- ローカル→リモート
- 接続以降の方向
- ローカル→リモート
- リモート→ローカル
概念的には FTP のパッシブモードにちょっと似てるかも。ちょっとだけね。
R option
これを実現するのが -Rオプション
です。
逆向き(リモートからローカルへ)のトンネルを貼ります。
先ほどの -Lオプション
と対をなすイメージですね。
ローカル環境の sshd に接続して Xさん(仮称)
と Fさん(仮称)
の シェルを GNU Screen
で共有するというシナリオで動かしてみましょう。 (Xさんありがとう)
Xさん と Fさん はお互い違う場所におり、二人をつなぐ中間サーバとして
gateway.example.com
という公開 SSH サーバ があります。
- info
- ちなみに
GNU Screen
とはターミナル上で複数の仮想端末を管理するためのソフトウェアです。 - 参考: GNU screen コマンド勉強録
- ちなみに
screen プロセスは標準入出力を記憶しているため、これを利用しリアルタイムな画面共有を実現しようという試みです。
ローカル環境に SSH 接続したいので rastasheep/ubuntu-sshd のイメージを使って用意します。
vagrant
とか使ってる人はデフォルトでSSHが動作してるので楽ですね。
今回は Fさん側のシェルを共有するので、Fさん側で色々準備します。
$ docker pull rastasheep/ubuntu-sshd $ docker run -d -p 22 rastasheep/ubuntu-sshd /usr/sbin/sshd -D e0015af4ec5cea58f39a12e22942d3389c6e7ca3bb82496be785a89eab812742 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e0015af4ec5c rastasheep/ubuntu-sshd "/usr/sbin/sshd -D" 10 seconds ago Up 6 seconds 0.0.0.0:32769->22/tcp elated_easley # 開いたポートに対して SSH 接続 $ ssh root@localhost -p 32769 The authenticity of host '[localhost]:32769 ([::1]:32769)' can't be established. ECDSA key fingerprint is SHA256:9U5v1a2QycyWSEGFL3GnU6OAaTmWjKaScIDGKlbH5so. ECDSA key fingerprint is MD5:0e:fc:94:f4:8e:f6:bd:2a:c8:42:7c:4c:86:51:05:b2. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '[localhost]:32769' (ECDSA) to the list of known hosts. root@localhost's password: # root で入れる root@e0015af4ec5c:~# apt-get install screen Reading package lists... Done (...後略)
準備が完了したので接続してみましょう。
- 流れ/登場人物
- Fさん
- Xさん
- トンネル
# docker ps で表示されてるポートに合わせてトンネルを張る $ ssh gateway.example.com -R 22222:localhost:32769
# Fさんの作ったトンネルに接続する $ ssh gateway.example.com -t ssh root@localhost -p 22222 root@localhost's password: # root と入力
- トンネル
# Xさんが接続するためのセッションを作る root@e0015af4ec5c:~# screen -S shared_screen
# Fさんが作ったセッションにアタッチする root@e0015af4ec5c:~# screen -x shared_screen
- 共有中
root@e0015af4ec5c:~# date Thu Dec 14 14:37:15 UTC 2017
root@e0015af4ec5c:~# date Thu Dec 14 14:37:15 UTC 2017
左右で同じ画面が見えていますね。共有できたのでこれにて一件落着です。
Unixdomain socket
同士で Screen
だけ使って画面共有できたりしないかなーと思ったんですけど
接続が fail して共有できませんでした。(調べたけど時間切れ)
参考
ssh -D と tsocks京大マイコンクラブ(KMC)の公式ページ。活動記録や製作中のゲーム・ソフトに関する情報を公開しています。https://www.kmc.gr.jp/advent-calendar/ssh/2013/12/14/tsocks.html 多段sshの方法 - About Digitalhttp://blog.digital-bot.com/blog/2013/09/15/how-ssh/