Zebedee は便利なソフトである。wakatonoさんやただただしさんの紹介、何より、えーすいませんお名前を忘れましたが man を和訳してくださっている方によって我々日本語ネイティブにも馴染みのトンネリングツールである。
しかし今回の話はこの Zebedee をやめて ssh によるトンネルにしましたという話。
なお、お前これでほんとに運用してるの?というひどい勘違いがある可能性もあります。その場合は怒濤の勢いで突っ込んでください。
なぜ Zebedee でなく ssh なのか?
某所で使っている Zebedee トンネルの調子がずいぶん前から悪く、rsync が転送途中で何も言わなくなり、それだけなら timeout を設定して何回かトライすることで対処できるが、サーバが突然落ちたり、動いてるけど全然機能しなくなっていたりしたのに業を煮やして Zebedee をやめることにした。1
なおこれは恐らく経路とホスト環境が絡んだ複雑な問題であり、クライアント側が同じネットワークで別なサーバに向かって張っているトンネルについては支障なく利用できているなど謎な部分が多く、
Zebedee そのものがタコだからやめるわけではない
ということは一応断っておく。
Zebedee をやめて ssh を使うことについては別に ssh が大好きだからという理由ではなく、
- ssh 越しの rsync は途中で切れないことを確認済み
- ssh 以外に別なトンネルソフトを発掘して調べて設定するのが面倒
という理由による。特に「切れないことを確認済み」というのが大きい。これ以前から ssh は session 開始に掛かる時間や転送速度の問題はあるものの「通信が強い」2という印象を抱いていたので、「切れるのが問題ならもう ssh にしちまおう」と思った次第である。
※ しかし特にシステムアカウントを一つ用意しなければいけない ssh トンネルは正直あまり使いたくなく、他によいアプリがあるなら教えていただけると嬉しいです。あと rsync したいだけなら別にこんな面倒なことしなくてよくね?と思われるかもしれませんが、「詰まる」という現象が rsync の際によく起きるというだけで rsync しかしてないわけじゃないです。iptables などで接続元を制限するだけでなく通信路の暗号化も必須要件です。
ssh のデメリット
まず Zebedee のメリットを確認しておこう。
- システムアカウントを用意する必要がない
- 転送できるホスト、ポートをサーバ側で細かく制御できる
- Zebedee C/S 間の通信は暗号化される
- IPアドレスによる接続制限だけでなく公開鍵認証が利用できる
- Zebedee クライアントを動かしているホストだけでなく、他のホストも Zebedee のトンネルに相乗りできる(アプリケーションレベルではあるけれど LAN 間のトンネルに使える)
- TCP も UDP も転送できる
- 実際にトンネルを利用するアプリが通信を始めるまではトンネルを張らない(通信の節約)
このうち ssh にないメリットは以下になる。
- システムアカウントを用意する必要がない
- 転送できるホスト、ポートをサーバ側で細かく制御できる
- (これは OpenSSH でも 4.4 以降なら可能)
- TCP も UDP も転送できる
- 利用するときにだけトンネルを作成する
この Zebedee にあって ssh にないメリットは、逆に言うと ssh のデメリットなわけだけれど、このうちシステムアカウントについてはあれこれ対策を盛り込むことで、UDP の転送は基本的に必要ないので我慢することで、転送ホスト、ポートの制御は結局のところ信用できないユーザーに解放するわけではないので我慢、というなんだか消極的な判断を重ねたことをまずお断りしておく。これが我慢できない人にはオススメできない。
port forward 用の ssh の設定
でまぁなんとか我慢できる状態に落ち着いたので以下その設定を紹介しておく。
- 専用のアカウントを作る(クライアント / サーバ)
- shell を制限する(サーバ側)
- chroot jail(サーバ側)
- GatewayPorts yes(クライアント側)
- ssh -N で転送以外何もしない接続(クライアント側)
- autossh で接続や ssh クライアントアプリの不調を回復(クライアント側)
結構やることが多い。ほんと、Zebedee が安定して動くなら絶対に Zebedee の方がいい。
その前に確認事項
- 利用した OpenSSH は 4.3
- プロトコル 2 だけ使おう
- 一部のオプションはプロトコル 2 でしか動きません
- 利用したプラットフォームはクライアント/サーバともに Linux だけど、クライアント側は cygwin を使えば Windows でも実現可能
- 認証は空パスフレーズの公開鍵認証を利用
- 秘密鍵の管理はちゃんとすること。共用する秘密鍵にパスフレーズがあるかないかはもう気休め程度の問題だと思う。
- これを port forward 用の機械にセットして、他の機械はこの forward 用の機械に接続にいく
例えば今回の目的の一つは rsyncd との間での通信なので
+----------------+
| USER -> PROXY -+-(暗号化)--> SERVER
+----------------+
という繋ぎ方で
rsync rsync://PROXY:PORT/MODULE LOCAL
みたいな使い方を想定。
※ 完全に余談だけど公開鍵認証を使うと authorized_keys で no-port-forwarding を指定することで port forward を禁止するなど、Match を使えないバージョンでもある程度ユーザーの権限の制御ができるし、絶対に公開鍵認証にしておいた方がいいですよ。詳しくは man sshd.
専用のユーザーを作る(クライアント / サーバ)
今回の目的は特定の人間が port forward を利用することではなく、できたトンネルはみんなで利用するものなので、それ用のアカウントを用意してしまった方が設定を分離、集約できて便利。
※ 実際にはサーバ側は後で触れる scponly の chroot_setup.sh でユーザーを作ったので今回は何もしていないんだけど。
クライアント側は普通にユーザーを作って ~/.ssh/ に config, 秘密鍵を置けるようにしておく。クライアント側でユーザーを作るのはこの鍵と設定ファイルの置き場所を作る以上の意味はないと言ってもいいかも。
port forward 用のユーザーの shell を制限する(サーバ)
ssh は Secure Shell であり、基本的には ssh アカウントはシステムの操作を行うために作成するものである。しかし今回の目的は port forward のみ。やはりそれ以外には利用できない状態にしておきたい。LAN 内にこのアカウントを悪用する人がいないとも限らないし、万が一鍵が漏れたらエラいことである3。しかし remote shell が terminal でアレコレ作業するのを許さない制限 shell であればとりあえずこのアカウントで余計な作業はできなくなる。4制限 shell として自分が知っているのは
で、どちらも chroot に対応している。
rssh は最近あまりブログなどで話題になっているのを見ないが、scponly と違ってあまり場当たり的に機能追加を行わないことでセキュリティを保つことを意識して作られており、また sshd とは別個に rssh.conf によってユーザーごとに細かく設定を調整できる柔軟性を持つ。
scponly は便利な機能を使いたいという要求に次々応えているような印象。scp, sftp だけでなく現在のバージョンは rsync も unison も svn にも対応している。(rsync は rssh でも利用できる。が、いずれにせよ今回 rsync は rsyncd への接続という形を利用するので制限 shell での対応もそのための設定も必要ないけど。)
chroot環境の構築(サーバ)
shell を制限しておいても scp や sftp でシステム全体へアクセスできてしまえば危険なのに変わりはない。そこで chroot 環境に閉じ込める。
今回自分は scponlyc5 で chroot jail を作成した。これは
付属の chroot_setup.sh が扱いやすい
ということに尽きる。rssh にも source tarball の中には chroot setup 用のスクリプトが付属しているのだが、これだけではどうにもうまくいかない。6rssh そのもののアップデートが活発でないことから、この辺りの問題をツール側の更新を待って解決に当てるというアプローチの採用は難しく、自分なりに rssh のノウハウを身につけて対処するしかない。ちょっと面倒なのでこれはパス7。
ただし扱いやすいとは言え chroot_setup.sh も source tarball の中にあるものであり、configure 時に必要なパラメータをセットして作られる sh スクリプトであり、かつ完全に動作する sh スクリプトが全自動でできるとは限らないので注意は必要である。
しかし、それでも rssh のセットアップツールよりは楽。うまく動けばアカウントの作成、ホームディレクトリの作成、必要なバイナリのコピー、permission の設定まで自動で行ってくれる。Linux でも *BSD でもうまく動く。useradd にも pw にも対応しており、passwd データベースの取り回しに気が利いている。rssh はユーザーごとに設定を柔軟に変更したい場合や umask を設定したい場合には便利だが、port forward 目的に制限したいのであれば scponly の方が楽だと思う。
なお、chroot jail を作成したときに /etc/localtime をコピーしておかないとログの時間がずれるのと、FreeBSD では chroot jail の中に /dev/null を作っておかないと動かないのは注意が必要な点。Linux では /dev/null はなくても動いた。
GatewayPorts yes(クライアント)
これは Zebedee と対比させると以下のような設定の意味である。
Zebedee | ssh | |
localhostに限定 | localsource true | GatewayPorts no(デフォルト) |
他のホストも利用可能 | localsource false(デフォルト) | GatewayPorts yes |
ssh はもともとトンネル用のツールではないので、デフォルトでは実際に ssh の接続を行っているホスト以外からは port forward は利用できない。これを利用できるようにするのが GatewayPorts yes の設定である。
others --(ここの可否)--> ssh client --> ssh server
ssh -N で接続(クライアント)
これは
- OpenSSH は scp, sftp での接続時に自動的に port forward の設定をクリアしてしまうので port forward 目的では ssh クライアントで接続しなければならない。
- shell が rssh の場合は ssh で普通に接続してもすぐに切れてしまう。scponly の場合は WinSCP 互換モードというものがあり、これで session を維持してくれる。が、どちらにしろ tty を解放しないとサービス化できないので、-N は必要。
という理由による。ssh -N の意味は man を読むこと。
ssh_config
というわけでできあがった ssh_config はこんな感じ。以下のような感じのファイルを ~/.ssh/config に置く。
Host PROXY
Hostname PROXYHOST
User ACCOUNT FOR PROXY
GatewayPorts yes
LocalForward PORT HOST:PORT
LocalForward PORT HOST:PORT
LocalForward PORT HOST:PORT
autossh
Zebedee はトンネルを利用した通信の要求が発生したときに初めて Zebedee 自体のコネクションが結ばれる。ssh にはそんな機能がないので8、autossh を使って繋ぎっぱなしにする。autossh は ssh コマンドを叩き、かつその ssh の接続を監視し、切れたりすると自動的に再起動してくれるもので、ssh でトンネルを掘るという話で調べるとたいてい autossh がセットで出てくる。ということで恐らくこれが標準的に使われているものなのだろう。
この使い方は
autossh -M [port] -f [SSH OPTIONS]
で、例えば
- port forward 用のアカウントが forwarder
- ~forwarder/.ssh/config に設定がある
場合はこんな感じで叩く。
sudo -u forwarder autossh -M 0 -f -F ~forwarder/.ssh/config -N PROXY
ここで
- -M 0 は autossh 自体の監視ポートを消費しないための設定
- -f は autossh をバックグラウンドに回すためのオプション
- -F は ssh の config ファイルを指定するオプション
- -N は上で触れましたね
- `PROXY' の意味は上の config 参照
これでうまく動くなら起動時に動くようにするまでもう一息。Linux だと /etc/init.d/ 以下に template があったりするのでそれを使ってシコシコ書いていく。システムの起動スクリプトに書く場合は
-F ~forwarder/.ssh/config
の記述はフルパスにしておいた方がいいと思う。
補足
サーバに localhost からの接続と思わせる
tunnel サーバと転送先のサーバが同じ場合、サービスを提供しているデーモンから見たときに接続元が localhost として見えるかそうでないかは意外に大事だったりする。例えば外部からの TCP/IP 接続は許していないが、localhost からのみ許可するような場合である。図にするとこういう形。
+------------------------+
ssh client --> | ssh server --> service |
+------------------------+
これ、service に localhost からの接続として見せるための設定が Zebedee と ssh で違う。
Zebedee の場合
serverhost xxx.example.com
tunnel III:xxx.example.com:JJ
tunnel MMMM:xxx.example.com:NNNN
という具合に serverhost と tunnel の行の host を合わせておく。
ssh の場合
Hostname xxx.example.com
LocalForward III localhost:JJ
LocalForward MMMM localhost:NNNN
と、転送先を localhost にしておく。
OpenSSH 4.4 以降を使ってさらに安全に
OpenSSH は 4.4 以降で PermitOpen という設定項目が追加されている。これは転送先の制限をするもので、sshd_config 上で
PermitOpen HOST:PORT[ HOST:PORT HOST:PORT ...]
のように指定する。複数書く場合はホワイトスペースで区切って列挙する。`any' と書けばすべての転送が許可される。デフォルトは any.
GatewayPorts のときと同じように Zebedee と比較すると以下のような形だ。
Zebedee | OpenSSH | |
パラメータ | target | PermitOpen |
デフォルト | すべて禁止 | すべて許可9 |
Zebedee では target で明示した転送先にしか転送できない。対して OpenSSH の方では AllowTCPForwarding yes で転送そのものを許可してしまえばどこへでも転送可能である。PermitOpen という設定の方が後から登場しているのだから当然なのだけど。
また authorized_keys 上で
permitopen="HOST:PORT[,HOST:PORT,...]" key
と指定しておけばその鍵で認証された接続はここに書かれた転送先にしか繋がらなくなる。つまり、全体の設定と接続ごとの設定を別々にできるのだ。
通常、クライアントはこの PermitOpen で転送先が制限されているかどうかに関係なくサーバに接続し、forward 用の port を開いて listen する。しかし実際にはその port に繋いでも何も起きない。こうした事態を防ぐにはクライアント側で
ExitOnForwardFailure yes
と設定しておく。こうしておくと tunnel が確立されなかった場合に ssh クライアントは異常終了する。
注にも書いたけど、PermitOpen と同じく 4.4 以降で登場する Match を利用すると、例えばこの転送用の鍵を特定のネットワークからの接続にしか使えないように制限できるんじゃないかと思っているんだけど、サンプルが全然見つからなくてまだ分かっていません。制限できるなら例えこの鍵は漏れても平気ってことになるんだけど、そういうのは無理なんですかね?
2.4.1 -> 2.5.3 のアップグレードをしたら良くなるかと思ったら返って悪くなった。 ↩
切れない ↩
Match を使いこなせれば特定のユーザーが特定のネットワーク以外から接続できないように設定を絞ることができるんじゃないかと思っている。そうなれば鍵が漏れても心配は要らない。いや管理に問題があるという事実には変わりないけど。ただ今回はサーバ側が 4.3 なのでその部分の検証は行っていない。 ↩
もちろん自由に port forward できたらかなり危険なのは間違いないので、shell を制限するだけじゃダメだけど。 ↩
scponlyc は scponly の setuid して使う chroot 用バイナリ ↩
FreeBSD 6 と CentOS 5 で実験。 ↩
実は今回の話とは別に試してみたことはあるんだけど、個人的にはうまくいかなかった。 ↩
ないよね? ↩
AllowTCPForwarding yes は必要 ↩