VPNモドキ over ssh for Subversion
古典的なネタですが,毎回,一度設定すると忘れるので(笑),きっちりメモっておこうと思います.
SSHのキー認証のみをもちいて,ポートフォワードを使ってVPNもどきを実現する方法です.
次のような状況を考えます.
| port=10000開放 port=20000(LAN内)開放 +--------------+ +------+ +--------------+ +-----------+ |モバイルホスト|<-->+ルータ|<--インターネット-->|Firewallマシン|<---->|LAN内ホスト| +--------------+ +------+ +--------------+ +-----------+ userA | userB userC =====> <-----------------> 外へのアクセスのみ可 外へは自由,外からはポート100000sshのみ受け入れのみ可
さらに,sshの認証としては,モバイルホスト→Firewallマシン,Firewallマシン→LAN内ホストは,パスワードの有無を問わず,矢印前のマシンのキーの公開鍵が,矢印後のマシンのauthorozied_keysに登録されており,サクっとsshで入れるとします.
つまり,モバイルホストからFirewallマシンには,
でログイン可能,FirewallマシンとLAN内ホストは,互いに次のようにログイン可能だとします.
従って,2回,sshを使えば,「モバイルホスト」から「LAN内ホスト」にログインできることを前提とします.
[モバイルホスト] # ssh -p 10000 userB@Firewallマシン
[Firewallマシン] # ssh -p 20000 userC@LAN内ホスト
しかし,「Firewallが無いかのように,一発でサクっとログインしたい!」ということを実現することとします.
それは,例えば外部のマシンから,subversionのsvn+sshプロトコルでプログラムをLAN内のsubversionサーバとやりとりするようなことをやりたいわけです.
いくつか方法がありますが,今回はモバイルホストから,「ssh -p 15000 localhost」と打ち込むと,LAN内ホストにログインできる」ことを実現します.
localhostで全て解決すると,名前解決を考えなくていいんので,色々とメリットがあるんですね.途中のポートも自由に変更できますし.
これで,LAN内ホストにsubversionしたり,LAN内ホストからpopしたりと何でもできます.
モバイルホストから見て,マシンを1段踏み台にして2段目に入りたいので,当然,ポートフォワードが必要です.
まず,Firewallマシン上で,Firewallマシン→LAN内ホストを中継するポートフォワーダーが常に動いている必要があります.これは,モバイルホストがunix系統ならsshのデーモンで良いでしょうし,WindowsならportforwarderでもTeraTermでも構いません.
私は,NATのkeepaliveの関係で,常にpingでハートビートを打つようにしています.例えば,次のようなコマンドが,モバイルホスト上で常に動いているようにデーモン化します.
[モバイルホスト]# ssh -p 10000 -L 15000:LAN内ホスト:20000 userB@Firewallマシン 'ping -i 300 localhost'
この設定は,最初からセキュリティをチェックして公開されているFirewallマシンのポート10000を使ってログインしているだけなので,セキュリティ的には何の問題もありません.また,ポート転送をかけているFirewallマシンのポート15000も,ポート転送されているLAN内マシンのポート20000も,外部には公開されていないので問題ありません.
うまくいけば,モバイルホスト上で次のように動作確認ができます.
[モバイルホスト]# telnet localhost 15000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
SSH-2.0-OpenSSH
この場合,sshの設定がちゃんと出来ていれば,次のようにモバイルホストからLAN内ホストにログインできます.
(LAN内ホストのuserCのauthorized_keysに,モバイルホストのuserAの公開鍵の登録が必要です)
[モバイルホスト]# ssh -p 15000 userC@localhost
Last login: Tue Nov 20 00:00:04 2007 from モバイルホスト
[LAN内ホスト]#
あと,ポートフォワードのsshですが,そのままではpingの結果が画面に出力されてウザいと思います.なのでデーモン化なのですが,(1) crontabに書き込んで,定期的に落ちていないかチェックしつつ,cronで実行してもらう,(2) nohupコマンドを使う,等があります.
(1)が良いと思いますが,とりあえずということでしたら,
[モバイルホスト]# nohup ssh -p 10000 -L 15000:LAN内ホスト:20000 userB@Firewallマシン 'ping -i 300 localhost' &
等と打ち込んで,一度端末を抜けます.これでnotty状態になります.
(1)のようにcrontabを使う場合,スクリプトを書くと良いと思います.例えばモバイルホスト上での場合...
FreeBSDなら...
#!/bin/tcsh ex=`ps ax|egrep "[0-9] ssh -p 10000 -L 15000:LAN内ホスト:20000"|wc -l` if ($ex == 0) then nohup sudo -u userA ssh -p 10000 -L 15000:LAN内ホスト:20000 userB@Firewallマシン 'ping -i 300 localhost' & endif */3 * * * * /usr/local/sbin/vpn_to_lan.sh >& /dev/null
Linuxなら...
#!/bin/bash ex=`ps ax|egrep "[0-9] ssh -p 10000 -L 15000:LAN内ホスト:20000"|wc -l` if [ $ex -eq 0 ]; then nohup sudo -u userA ssh -p 10000 -L 15000:LAN内ホスト:20000 userB@Firewallマシン 'ping -i 300 localhost' & fi */3 * * * * /usr/local/sbin/vpn_to_lan.sh >/dev/null 2>&1
とかですか...あんまり検証していないので,いろいろお試し下さい.
さて,これでlocalhostにuserCでsshすることで,LAN内ホストに入れるようになったのですが...なぜか,subversionがうまく動きません.
svnserveの-t(トンネリングモード)が,sshのポート番号や認証鍵やユーザ名を認識してくれないのです.
最初,
[モバイルホスト]# svn ls svn+ssh://localhost/svn-repo
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
(中略)
svn: Connection closed unexpectedly
となったので,まぁ,localhostを使いまわす限りこれは仕方が無い,無理矢理消そうと思って
とやったのですが,
[モバイルホスト]# svn ls svn+ssh://localhost/svn-repo
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
(中略)
Keyboard-interactive authentication is disabled to avoid man-in-the-middle attacks.
svn: No repository found in 'svn+ssh://localhost/svn-repo'
ログメッセージを見ると,ポート22にsshログインしている模様.なので,ポートもアカウントも指定しました.
setenv SVN_SSH "ssh -o StrictHostKeyChecking=no -p 15000 -l userC"
export SVN_SSH="ssh -o StrictHostKeyChecking=no -p 15000 -l userC"
これで,いけました.
[モバイルホスト]# svn ls svn+ssh://localhost/svn-repo
Makefile.in
[モバイルホスト]# svn log svn+ssh://localhost/svn-repo -r head
- -
r01 | userC | 2007-11-27 17:00:0 +0900 (Tue, 27 Nov 2007) | 3 lines
project start
- -
なお,今回はsubversionサーバは,同僚がOpenVZ内で立ち上げてくれました.従って本設定は,同一の物理マシンの中にFirewallマシンとLAN内ホストがあっても使えますし,何かあったら仮想マシンであるsubversionサーバを落とせば被害が最小で良い(笑)という利点があります.