SSH のダイナミック転送ってすごいんですよ

DSAS開発者の部屋:OpenSSH クライアントの proxy -- 踏み台サーバを経由しての ssh nc と ProxyCommand を使うというアイデアは今まで見たことが無く,なるほど!と感心しました。ですが,もっと便利な方法があります。ssh -Dはすごい便利です。SSH のほかにも色々できますし,中継サーバを「転送専用」と割り切ってしまえば,管理がとっても楽になり,セキュリティレベルを高く保つことも容易になります。他で情報を見かけないので,そのうち書こうと思っていたのですが,せっかくなので書いてしまいましょう。

はじめに:ssh の多段接続問題

DSAS開発者の部屋:OpenSSH クライアントの proxy -- 踏み台サーバを経由しての ssh のように,ssh の多段接続を行いたいという要求はあちこちであるかと思います。私の所属している研究室の場合は,計算用クラスタという外部から接続できないホスト(多数)に対して遠隔からログインできる方法が必要です。そのために,connect.c や ssh ssh での多重接続,OpenVPN のような VPN 接続にいたるまで,色々と調べていました。 しかし,これらの方法はそれぞれに問題があって,以下の要求を満たす方法はなかなか見つかりませんでした。
  1. SSH 以外のプロトコル:運用監視サーバなどで http を使ったり,ssh+scp の2重暗号化のオーバーヘッドを避けてファイル転送などをしたい
  2. 名前解決:外には公開していない DNS を使用しているため,リモート側の DNS で名前解決を行いたい
  3. Windows クライアント:ユーザのクライアントマシンはすべて Windows。PuTTY で接続したい
  4. ユーザの設定:複雑な設定を要求するのではなく,「このファイルを置いて」くらいにしたい。VPN のように特別なソフトウエアのインストールもできれば避けたい
こんな,ウルトラスーパーミラクルダイナミックソリューションがあるかと思いますか? それが実はあるのです。SSH の,あまり知られていないダイナミックポート転送という名前の機能です。

知識編:ダイナミック転送とはなんぞや

ダイナミック転送というのはどういう機能かということを説明するにあたって,まずは普通のローカルポート転送のおさらいです。大雑把に言うと,下図のような感じで「手元の ssh クライアントが指定されたポートを開いて待ちうけ,そこへの接続を SSH のトンネルを経由して sshd から指定されたリモートホスト・ポートへと転送する」という機能ですね。OpenSSHのssh(1)コマンドなら ssh -L :: と指定します。これとは逆で,sshd側に待ち受けをさせて手元のマシンへと転送するリモートポート転送という機能もありますが,使う機会も少ないでしょうし(あるにはありますが),今回は省略です。 |*****手元のマシン*******|    |中継サーバ| |リモートサーバor中継サーバ自身| クライアントソフト--->ssh<=======>sshd------>各種プロトコルのサーバ   この方法があれば,とりあえずリモート側のサーバに対して接続することはできるのですが,
  • ホスト:ポートの組で一つ一つ設定しないといけないので面倒
  • 「ローカルホストのこのポートはどこそこの何番ポート」という対応を覚えるのが面倒
  • クライアントソフトにとっては,localhost:port に接続することになるので,ソフトによってはおかしな挙動になることがある
と,色々と不便です。特に最後が致命的で,たとえば,SSH ならホスト鍵のチェックで問題が起こらないよう,きちんと設定しなければなりませんし,HTTP の場合はホスト名付きでリンクが書いてある場合,いちいちlocalhost に書き換えなければなりません。また,接続先が増えるにつれて前2つも問題になってきます。 そこで登場するのが,第3の転送方法「ダイナミック転送」です。OpenSSH の ssh(1)なら ssh -D と,一つだけポートを指定して起動します。こうすると,手元の ssh が指定したポートを開いて待ち受けします。このとき,ssh クライアントはこのポートに接続してくるクライアントに対して,SOCKS4/5 Proxyとして振る舞います(もちろん,更にその接続を SSH トンネルを通して sshd 側に送ります)。 |*****手元のマシン*******|    |中継サーバ| ( :<port>で待ち受け )ssh<=======>sshd   SOCKS Proxy とは何かというと,簡単に言ってしまえば,TCP/IPの接続を行う際に,実際に通信したい相手のアドレスを付加して接続を行うことで,プロキシサーバとしての機能を実現するためのプロトコルです。HTTP Proxy などのようにプロトコルレベルの処理ではなく,ソケットを開くシステムコールの段階での処理なので,プロトコルを問わないところが利点ですが,その代わりにクライアントアプリケーション側での対応が必須となります。とはいえ,ほとんどのブラウザや,PuTTY 自体が SOCKS 対応になっているので,心配はいりません。 |*********************手元のマシン*********************|   |中継サーバ|            |リモートサーバ| SOCKSクライアント----[SOCKS: to 192.168.0.2:80]--->ssh<=======>sshd---[実際の接続]-->192.168.0.2:80   プロキシサーバですので,何本の接続でも処理できます。特に設定が無い限りはどこへでも接続できます。 |******************手元のマシン******************|   |中継サーバ|            |リモートサーバ| SOCKSクライアント1---[to 192.168.0.2:80]-+->ssh<=======>sshd--+--[実際の接続]-->192.168.0.2:80(httpd) SOCKSクライアント2---[to 192.168.0.3:22]-/                    +--[実際の接続]-->192.168.0.3:22(sshd)   また,SOCKS クライアント側で接続するときの設定によっては,リモート側で名前解決をすることができます。PuTTY の場合は接続設定次第で,ブラウザの場合は常にリモート側で名前解決をするようです。 |******************手元のマシン****************************|   |中継サーバ|            |リモートサーバ| SOCKSクライアント1---[to www.local.example.net:80]-+->ssh<=======>sshd--+--[接続]-->192.168.0.2( www.local.example.net ):80(httpd) SOCKSクライアント2---[to  db.local.example.net:22]-/                    +--[接続]-->192.168.0.3( db.local.example.net ):22(sshd)   クライアント側でプロキシの設定が必要ですが,接続設定はそのままで良いところが魅力です。また,www.local.example.net でホスト名を含めて CSS の URL が指定されていたりしてもそのままで平気ですし,www2.local.example.net へのリンクなどがあってもへっちゃらです。

基本編:PuTTY+pfwdで接続してみよう

日本語化された PuTTY としては,hdk氏版と,ごった煮版がありますが,ダイナミック転送はごった煮版に同梱されている pfwd.exe に任せてしまうのが便利なので,ごった煮版がオススメです。pagent.exe や puttygen.exe なども日本語化されていますし。pfwd.exe の代わりにPortForwarder を使うのでも良いとおもいます。 手順としては,まず最初に puttygen.exe で認証鍵を作ります。秘密鍵・公開鍵のペアが作成されるので,秘密鍵はローカルの適当な場所に保存し,公開鍵をサーバ側に送ります。このとき,同じ公開鍵を中継サーバと実際にログインしようとするサーバの両方に置くのがポイントです。 pfwd.exe と PuTTY のそれぞれに対して秘密鍵を設定して,パスフレーズを入力したりするのも面倒なので,作成した秘密鍵を pagent.exe に食わせて,常にエージェント経由で認証することにします。ショートカットを作って引数に秘密鍵ファイルのパスをつけると便利です。 pfwd.exe のあるフォルダに以下の pfwd.ini を保存します。中継サーバ名が fwd.example.net,ユーザ名が sshfwd としています。設定の意味は pfwd_sample.ini などを参考にしてください。 [SSH] Host=fwd.example.net User=sshfwd [FORWARD] 01=D1080   これで pfwd.exe の設定は終わりですが,実際に利用する前に最初の1回だけ,PuTTYを使って接続する必要があります。これは,pfwd.exe が未知のホスト鍵に対して,確認ダイアログを出さずにエラー終了してしまうためです。PuTTYからホスト名に「fwd.example.net」と入れて接続し,ホスト鍵の指紋確認ダイアログを出して確認します。ユーザ名のプロンプトが出るところまで行ったら切断して構いません(鍵指紋をレジストリに保存させるのが目的なので)。一度鍵指紋を確認してレジストリに保存すれば,この手順は以後は不要です。pfwd.exe を起動すればダイナミック転送ができるようになっているはずです。 PuTTY のプロキシ設定は,接続設定の「接続」→「Proxy」 の「Proxy のタイプ」に「SOCKS 4」と「SOCKS 5」があります。SOCKS 5としておいた方が良いみたいです。4だと繋がらないことがありました。「Proxyホスト名」には「localhost」を設定します。「ポート」は特に理由が無ければ SOCKS の標準ポート番号「1080」で良いでしょう。リモート側で名前解決を行うには,「Proxy end で DNS 名前解決する」を「自動」から「はい」に変更します。ユーザ名・パスワードなどは変更する必要がありません。ログイン先のサーバが複数あって,渡り歩いたりするときには,「接続」→「SSH」→「認証」→「エージェントフォワーディングを認める」を設定しておくのを忘れずに。 それぞれの接続に対して設定を行うのも面倒なので,ここまでで適当な名前でセッションの保存をしておくと良いかもしれません。後はその設定をロードしてから,それぞれホスト名・ユーザ名などを設定します。これで,外向けには SSH が空いていないホストにも,中継サーバ経由でアクセスが可能になっているはずです。合わせて,エージェント認証にしているので,パスフレーズの入力は最初の pagent.exe を起動するときだけなので,多段接続になっても楽に接続することができます。

活用編:他のアプリケーションでも設定してみよう

基本編の設定がうまくいっていれば,pfwd.exe を起動すると,localhost:1080 で SOCKS サーバが起動していることになります。ということは,SOCKS 対応のアプリケーションであれば,トンネル経由でのアクセスが可能だということです。 まずは代表的なアプリケーションとして,ブラウザです。先に述べたように,ほとんどのブラウザは SOCKS に対応しているので,普通にプロキシサーバの設定の中から,SOCKS サーバ・ポートの指定を探し,localhost:1080 とすれば OK です。ただし,このままだとすべての接続が SSH のトンネル経由になってしまって,面白くないので,一工夫が必要でしょう。プロキシ切り替え拡張などを使うと良いでしょう。面白そうなところとしては,SOCKS 経由でアクセスしたいホスト名は決まっているでしょうから,自動プロキシ設定スクリプト(PAC ファイル)を書いておけば,自動的に振り分けができるんじゃないかな,と思っています。 その他のアプリケーションをざっとチェックしてみました。アプリケーション名の後の○/△/×は,順番に「接続先ごとに設定可能」「アプリケーション全体に適用される」「SOCKS 非対応」を意味しています。PuTTY は○,ブラウザは△ということになります。△のアプリケーションはすべての接続に対して SOCKS (&SSH)経由で接続を行ってしまうので注意が必要です。
Becky!:×
残念。
Mozilla Thunderbird:△
△だけど,ブラウザと同様に自動プロキシ設定があるということは・・・?(未確認)
FFFTP:△(○といっても良い?)
△ながら,接続ごとに「firewallを使う」という設定があるので,SSH経由の接続先が1つなら問題ないでしょう
LimeChat1:×
1.xではダメ
LimeChat2:○
2.xでは接続ごとに設定できるようになっています
Windows Messenger:△
まあ,切り替えられても意味ないですけど
Windows Live Messenger:△
同じく
ギコナビ:×
もしかしたら,IEの接続設定を使うのかも?(未確認)
ということで,とりあえずこれだけ見ても「社内メールサーバで,内部のメールを読みながら,社内 IRC でコミュニケーション取って,FTP でログファイルとって,ブラウザで確認しながら SSH で入って再起動。その間なぜかメッセンジャーでは社内にいる振りをしたりして。」なんてことまでも,社外から可能なわけです。SSH を設定するだけで

応用編:中継サーバ側の設定を工夫してみよう

ところで,この方法の最大の利点は「中継サーバにログインアカウントが必要ないこと」だと思っています。上で例を示したときにユーザ名が「sshfwd」なのはこの布石です。必要なのはポートフォワーディングだけなので,中継サーバ上の sshfwd ユーザのログインシェルを /bin/false などにしてしまっても,多分問題ないはずです(未確認)。ということは,一部の管理者のみが中継サーバにログインできるようにして,/home/sshfwd/.ssh/authorized_keysに公開鍵を登録する,という形で運用すれば,中継サーバのセキュリティレベルを高く保つことも容易なのではないでしょうか。また,ユーザ名を共通にしておくと,pfwd.ini の記述が完全に同一になるというメリットもあります。アカウントの登録時に,「公開鍵送ってください」と言っておいて「登録しました。このpfwd.ini(と,proxy.pac も付けられたら便利っぽい)を使ってください」と言うだけで終わり。公開鍵を送って登録するという習慣もついて,一石二鳥です。

外伝編:Linuxクライアントの場合

あまり調べてないので良く分かりませんが,とりあえず,connect.c が「socks proxy に接続することができる」とのことなので,同様の方法はあると思います。これと,autossh あたりを使うと便利なことができるのではないでしょうか。アプリケーション側のSOCKS対応については分かりません。あまり見ない気がしますね・・・

おわりに

いかがでしょうか。ssh ダイナミック転送をうまく使うと,こんなに色々できてしまうのです。ここまでできれば,ほとんど VPN 代わりになってしまうと言っても良いのではないでしょうか。名前解決をリモートへ委任することができるのが大きいですね。 一方で,逆に見ると「ssh を空けるということはこれだけ色々なことができるようになってしまう」と捉えることもできます。もっとも,ssh で直接のログインを許可している場合は元から「何でもアリ」ですけど。 ただ,今回の場合で言えば,中継サーバから外・中への接続について,きちんとFirewallなどの設定をしないといけないかもしれません。ユーザがうっかりプロキシの設定を手抜きして,社外で個人的なブラウジングをするときに会社のサーバを中継してしまう,なんてことは避けたいですからね。「ユーザのうっかり」を減らすことはセキュリティを保つ上では重要ですよね。 2006/10/21追記:はてブでnaoyaさんよりsocksifyというものでLinuxのクライアントアプリをSOCKS化できるとのことです。そういえば,Windowsでも,蛭子屋そっくすとか,他にも色々ありますよね。

Trackback URL for this post:

http://old.typemiss.net/trackback/100