OpenSSH を使った簡易 VPN の構築

概要

2006年 2月に公開された OpenSSH 4.3 (およびその移植版 4.3p1) から、 標準でトンネリングデバイス (tun/tap) を扱う機能がつきました。 これを使うと、手軽に VPN を構築することができます。 現在のところまだ機能はごく限られたものですが、 出先から一時的に ssh 経由で NFS ディレクトリを マウントするなどの目的に使えます。 この文書ではそのための基本的な方法を説明します。 OpenSSH の設定と運用ができ、公開鍵認証の使い方を わかっているシステム管理者を対象としています。

公開鍵認証とは: パスワードのかわりに秘密鍵と公開鍵のペアを使う認証方法です。 通常のパスワードを使った認証では、 たとえ暗号化されているとはいえパスワードがネットワーク上を流れます。 公開鍵認証ではパスワードはまったく (暗号化された形ですら) ネットワーク上に流れないので、 こちらのほうが安全です。もちろん公開鍵認証でもユーザはパスワードに相当するもの (パスフレーズ) を入力しますが、パスワード認証と違いこれはローカルマシン上で 秘密鍵を復号するためだけに使われ、ネットワーク上には流れません。

注意: この機能はまだ実験段階のため、OpenSSH が動作するすべてのプラットフォームで動作するかどうかは 確認されていません (この文書では、Linux と FreeBSD でテストしました)。 また、細かい部分が今後変わることもありえます。ご利用は自己責任で。


OpenSSH を使った VPN の利点と欠点

利点

欠点


トンネリングのしくみ

OpenSSH を使った VPN では tun/tap デバイスというものを使います。 tun/tap デバイスはユーザー空間のプログラムが利用できる仮想ネットワークデバイスで、 この tun/tap デバイスをサーバとクライアントの両方で開き、 その間の通信を OpenSSH が暗号化すると VPN ができあがります。 今のところ Linux や *BSD でサポートされています。

tun/tap を使ったトンネリングは 2種類に分けられます。 ひとつは tun デバイスを使う方法で、これは Layer 3 (Point-to-Point) を エミュレートし、IPフレームを転送します。 これは PPP プロトコルを使ってサーバと 1対1 で通信するのと等価です。 VPN が正しく動いていると、Linux ではこれは次のように見えます:

# ifconfig tun0
tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          inet addr:192.168.3.1  P-t-P:192.168.3.2  Mask:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:10
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)
# ping 192.168.3.2
PING 192.168.3.2 (192.168.3.2) from 192.168.3.1 : 56(84) bytes of data.
64 bytes from 192.168.3.2: icmp_seq=1 ttl=64 time=16.8 ms
64 bytes from 192.168.3.2: icmp_seq=2 ttl=64 time=16.1 ms
64 bytes from 192.168.3.2: icmp_seq=3 ttl=64 time=18.3 ms
...

もうひとつは tap デバイスを使う方法で、 これは Layer 2 (Ethernet) をエミュレートし、Ethernet フレームを転送します。 VPN が正しく動いていると、Linux では次のように見えます:

# ifconfig tap0
tap0      Link encap:Ethernet  HWaddr 00:FF:7F:02:53:D7
          inet addr:192.168.3.1  Bcast:192.168.3.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)
# ping 192.168.3.2
PING 192.168.3.2 (192.168.3.2) from 192.168.3.1 : 56(84) bytes of data.
64 bytes from 192.168.3.2: icmp_seq=1 ttl=64 time=37.6 ms
64 bytes from 192.168.3.2: icmp_seq=2 ttl=64 time=16.1 ms
64 bytes from 192.168.3.2: icmp_seq=3 ttl=64 time=16.6 ms
...

下準備

OpenSSH で VPN を使うには、以下のものがサーバとクライアントの両方で必要です:

OpenSSH は普通にソースまたはパッケージからインストールすれば OK です。 tun/tap デバイスの準備は、OS ごとにやり方が異なっています。

Linuxの場合:

tun/tap ドライバは、通常、モジュールの形 (モジュール名 tun) で提供されます。 最近のほとんどの Linux ディストリビューションでは、 このモジュールは標準で含まれているので、 ただ /etc/modules.conf に以下のように書いておけば OK です。

alias tap0 tun
alias tun0 tun

(ここでは tun/tap デバイスとしてどちらもインターフェイス番号 0 を使うと仮定しています、 これ以外の番号を使う場合は適宜修正してください)

手動でモジュールを作成する場合は、カーネル構築の際に "Network Device Support" の 項目 "Universal TUN/TAP device driver support" (CONFIG_TUN) に チェックを入れる必要があります。 devfs/udev を使っていない場合は以下のようにしてデバイスノード /dev/net/tun を作成する必要があります。

# mkdir /dev/net
# mknod /dev/net/tun c 10 200
# chmod 0700 /dev/net/tun

あとは以下のようにしてモジュールをロードします。

# modprobe tun

tun/tap デバイスについての詳細は、Linux カーネルのソースコード中にある Documentation/networking/tuntap.txt を見てください。

FreeBSDの場合:

FreeBSD の場合は conf ファイル中でオプション "device tun" を有効にしておきます。 FreeBSD 6.0 では、必要なデバイスノードは devfs により自動的に作成されます。


クライアントとサーバを設定する

最初に決めておくこと:

OpenSSH で VPN を使う際に注意しなければならないのは、 「クライアント側の root プロセスが、サーバに root としてログインする」 ということです。 これは tun/tap デバイスを扱えるのが通常は双方の root だけであるためです (一般ユーザに tun/tap を許可することもやろうと思えばできるかもしれませんが、 試していません。一般的には、これはセキュリティ上問題があるとされています)。 そのため、VPN クライアントとサーバの両方で root ユーザの ssh 設定を作成する必要があります。

また、お手軽であるとはいっても、OpenSSH を使った VPN では あらかじめクライアントとサーバの間でいくつかの事柄を 決めておく必要があります:

(ここでいう「ネットワークデバイスのインターフェイス番号」とは、 tun0 などの名前に含まれる数字の部分です。)

まずクライアント側の root 用に RSA 秘密鍵/公開鍵ペアをつくります (SSH2 プロトコル用です - 古い SSH1 プロトコルでは VPN は使えません):

client# ssh-keygen
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):  パスフレーズを入力
Enter same passphrase again:                 パスフレーズを入力
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.
The key fingerprint is:
7c:7a:b5:5e:13:80:43:f3:0e:66:1f:9b:0a:d0:10:17 root@client

サーバ側にこの公開鍵 (id_rsa.pub) を送り、 サーバの /root/.ssh/authorized_keys ファイルに登録するなどして、 この秘密鍵をもつユーザに root ログインを許可してやります。

次にサーバ側の sshd 設定ファイル (通常は /etc/ssh/sshd_config) の PermitTunnel 項目を書き換えます。 この項目には以下のような値が指定できます:

ここでは tun デバイスを使ったトンネリングを許可するため、 以下のようにします:
PermitTunnel point-to-point

さらに、ふつうリモートからの root ログインを禁止している (PermitRootLogin no にしている) ところが多いと思うので、 この部分も変更する必要があります。とりあえず今のところは

PermitRootLogin without-password
としておきましょう (これはあくまでテスト用です、後でもっと安全な方式を使います)。

このあとサーバ側の sshd を再起動し、設定を反映させてください。


テストする

最初に、いちばん単純な最小限の設定でテストをしてみます。 本来 tun/tap デバイスを使って通信するには、 それらのインターフェイスを初期化したあと ifconfig を実行し、 IPアドレスを設定する必要があるのですが、ssh はただ単に tun/tap を初期化して 暗号化トンネルを提供するだけです。つまり、実際のインターフェイスの設定は 外部のコマンドに任されているのです。この部分を自動化することもできますが、 設定が複雑になるので、とりあえず最初はこれらを手動でおこなうことにして、 まずトンネルが確立できるかどうかだけをテストしてみることにします。

VPN を開始するには、クライアント上で以下のコマンドを実行します:

client# ssh -v -w0:0 server
Enter passphrase for key '/root/.ssh/id_rsa': (パスフレーズを入力)

-w はトンネリングを指定するオプションで、 -wクライアント側のインターフェイス番号:サーバ側のインターフェイス番号 のように指定します。デフォルトではトンネリング用のデバイスとして tun が使われます。 つまり、-w0:0 はクライアント・サーバともにインターフェイス番号 0 の tun デバイスを 使って VPN を確立するということを意味しています。 -v はデバッグ用の出力を表示するオプションです。

うまくいけば次のようなメッセージ (Linux の場合) が出て、通常のシェルが開始されます:

...
debug1: Authentication succeeded (publickey).
debug1: channel 0: new [client-session]
debug1: Entering interactive session.
debug1: Requesting tun.
debug1: sys_tun_open: tun0 mode 1 fd 7
debug1: channel 1: new [tun]
Last login: Mon Feb 13 10:30:42 2006 from xxx.xxx.xxx.xxx
server# 

問題があった場合

以下のようなメッセージが出てすぐに終了してしまう場合は、 クライアント側での tun/tap デバイスの設定に問題があります:

...
debug1: Authentication succeeded (publickey).
debug1: channel 0: new [client-session]
debug1: Entering interactive session.
debug1: Requesting tun.
debug1: sys_tun_open: failed to open tunnel control interface: No such device
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: channel 0: free: client-session, nchannels 1
Connection to xxx.xxx.xxx.xxx closed.
debug1: Transferred: stdin 0, stdout 0, stderr 40 bytes in 0.1 seconds
debug1: Bytes per second: stdin 0.0, stdout 0.0, stderr 672.2
debug1: Exit status 0
debug1: compress outgoing: raw data 347, compressed 211, factor 0.61
debug1: compress incoming: raw data 61, compressed 47, factor 0.77

エラーの原因は赤色の部分に書かれています。 上の例は、カーネルが tun/tap デバイスを使えるように設定されていないことを示しています。 この部分が "debug1: sys_tun_open: failed to open tunnel control interface: No such file or directory" だった場合、対応するデバイスノードが /dev以下に存在しないことを示しています。

いっぽう、サーバ側に問題がある場合は次のようになります:

...
debug1: Authentication succeeded (publickey).
debug1: channel 0: new [client-session]
debug1: Entering interactive session.
debug1: Requesting X11 forwarding with authentication spoofing.
debug1: Requesting authentication agent forwarding.
debug1: Requesting tun.
debug1: sys_tun_open: tun0 mode 1 fd 7
debug1: channel 1: new [tun]
debug1: Remote: Failed to open the tunnel device.
channel 1: open failed: administratively prohibited: open failed
debug1: channel 1: free: tun, nchannels 2

これはサーバ側の (権限がない、あるいはデバイスが設定されていないなどの理由で) tun/tap デバイスが使用できないことを示しています。 あるいは、"debug1: Remote: Server has rejected tunnel device forwarding" と出力された場合は、サーバ側でトンネルの使用が許可されていない (PermitTunnel の値が正しくない) ことを示しています。

サーバ側で問題が起きた場合、ssh はトンネリングなしでシェルを起動するか、 あるいは自動的に接続を切ってしまいます。 クライアント側で問題が起きた場合、ssh はすぐに終了するようです。

うまくいった場合

さて、うまくトンネリングができている場合は、サーバとクライアントの両方で tun デバイスが初期化されているはずです。 これは ifconfig tun0 (あるいは ifconfig -a) を実行してみればわかります:

(Linux の場合)
# ifconfig tun0
tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          POINTOPOINT NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:10
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

(FreeBSD の場合)
# ifconfig tun0
tun0: flags=8011<UP,POINTOPOINT,MULTICAST> mtu 1500
        Opened by PID 24015

トンネリングできていない状態で ifconfig tun0 を実行すると、 Linux ではインターフェイス tun0 そのものが存在せずエラーとなり、 FreeBSD では "Opened by PID XXXX" の部分が表示されません。 上のように表示されている場合はトンネリングが確立しています。

ただし、この状態ではサーバとクライアントの tun デバイス間に通信路が確立されただけで、 まだ IP 層の設定は完了していません。通信を行うためにはサーバとクライアント両方の tun インターフェイスにIP アドレスを設定してやる必要があります。 ここでは別の端末を使って、クライアントとサーバ上の両方で tun デバイスを設定します (あとあとこれは自動的に行うよう設定しますが、いまは両方とも手動でやってみます)。

tun デバイスは IP 層をエミュレートする (つまり、IP パケットを直接送受信する) ので、 Ethernet のインターフェイスとは違って、 相手方の IP アドレスも指定してやる必要があります。

(Linux の場合)
# ifconfig tun0 自分のIPアドレス pointopoint 相手のIPアドレス (point to pointではないので注意)

(FreeBSD の場合)
# ifconfig tun0 自分のIPアドレス 相手のIPアドレス

これをサーバとクライアントの両方で実行します。 たとえばサーバ側の仮想 IP アドレスを 192.168.3.1、 クライアント側の仮想 IP アドレスを 192.168.3.2 に決めたとすると、 以下のようになります:

(サーバ側 - Linux の場合)
server# ifconfig tun0 192.168.3.1 pointopoint 192.168.3.2
(クライアント側 - Linux の場合)
client# ifconfig tun0 192.168.3.2 pointopoint 192.168.3.1

これが成功したら、いまや IP レベルでの 通信ができるようになっているはずですので、 お互いに ping を送って確かめてみましょう。

(サーバ側)
server# ping 192.168.3.2 (サーバからクライアントに ping)
(クライアント側)
client# ping 192.168.3.1 (クライアントからサーバに ping)

あとは通常のプライベートネットワークと同じように利用できます。 NFS を利用するなり、NAT を設定するなり自由にしてください。 なお、NFS ディレクトリをマウントするときは、TCP オプションをつけることを おすすめします (速度が違います)。

VPN を終了する

VPN セッションはユーザがログアウトしたあとも続きます。 先ほどのデバッグモードで root のシェルから exit すると、 ssh はシェルから抜けたあとも依然として VPN のセッションを続けます:

server# exit
logout
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: channel 0: free: client-session, nchannels 2
(ここで Control-C を入力)
debug1: channel 1: free: tun, nchannels 1
Killed by signal 2.

現在のところ、 VPN セッションを終了するには端末上で Control-C を押すか、 ssh クライアントを killする以外に方法はないようです。

tun のかわりに tap を使う

同様に、今度は tun ではなく tap デバイスを使って Ethernet レベルのトンネリングをやってみます。 トンネリングのさいに tun を使うか tap を使うかは、 サーバとクライアント両方の設定に依存しています。 サーバは許可するトンネリングの種類を sshd_configPermitTunnel 項目で指定し、 クライアントは要求するトンネリングの種類を ~/.ssh/config (あるいは ssh_config か、 ssh のコマンドラインオプション) の Tunnel 項目で指定します。 これらの項目とトンネリングの種類との関係は、以下のようになっています:

PermitTunnel (サーバ)Tunnel (クライアント) の値 効果
両方が yes PPP (tun) が使われる
どちらか一方が yes (または point-to-point) で、 もう一方が point-to-point PPP (tun) が使われる
どちらか一方が yes (または ethernet) で、 もう一方が ethernet Ethernet (tap) が使われる
どちらか一方が point-to-point で、 もう一方が ethernet トンネリングは拒否される
どちらか一方が no トンネリングは拒否される

これらの項目を正しく設定すると、tun のときと同じように以下のコマンドで VPN が開始できます:

client# ssh -v -w0:0 server

あるいは、設定ファイルを書きかえるかわりに、 ssh のコマンドラインオプションから直接指定することもできます。

client# ssh -v -oTunnel=ethernet -w0:0 server

ここで -w オプションに与えている 数値 0 は今度は tun ではなく tap のインターフェイス番号です。

無事サーバにログインできたら、ifconfig tap0 してみてください:

(Linux の場合)
# ifconfig tap0
tap0      Link encap:Ethernet  HWaddr 00:FF:4A:21:8F:3D
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

(FreeBSD の場合)
# ifconfig tap0
tap0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        ether 00:bd:73:36:60:c8
        Opened by PID 93688

デバイスができているのを確認したら、IP アドレスを設定してやります。 tap デバイスの場合はエミュレートするのが Ethernet なので、 ここでは通常のネットワークカードにアドレスを指定するのと同じ方法で OK です。

(サーバ側)
server# ifconfig tap0 192.168.3.1 netmask 255.255.255.0
(クライアント側)
client# ifconfig tap0 192.168.3.2 netmask 255.255.255.0

実際に運用する

さて、実際に日常的に使うとなると、いちいちサーバとクライアントの両方で 毎回 ifconfig を実行しているわけにはいきません。 またリモートからの root のシェル使用を禁止したい人もいるでしょう。 クライアントとサーバの設定をおこなうことによって、これらの作業をほぼ自動化することができます (といっても、まだ完璧ではありません。現在の実装では、 IP アドレスとネットワークインターフェイス番号は、 サーバとクライアントの両方で前もって取り決めておかねばならず、 2台以上のクライアントが同時に VPN を使う場合は少々注意が必要になります。)

クライアント側で VPN 用の /root/.ssh/config を書く

最初に、クライアント側の設定を自動化してみましょう。 root のホームディレクトリに、以下のような設定ファイルを置きます:

(Linux の場合)
client# cat /root/.ssh/config
Host server-vpn
        Hostname                server.example.com
        Port                    xxx
        User                    root
        IdentityFile            /root/.ssh/id_rsa
        Tunnel                  point-to-point
        TunnelDevice            0:0
        PermitLocalCommand      yes
        LocalCommand            ( sleep 3; ifconfig tun0 192.168.3.2 pointopoint 192.168.3.1 ) &

最初の 4つの設定項目 (Hostname, Port, User, IdentityFile) は、 標準的なものです。これらはサーバのホスト名、ポート番号 (頻発している ssh へのアタックのため、現在では sshd をデフォルト以外の ポートで動かしている人も多いでしょう)、ログインするユーザ名、そして秘密鍵ファイルを 指定しています。次の 4つの設定項目 (Tunnel, TunnelDevice, PermitLocalCommand, LocalCommand) が、 4.3 で VPN を使用するために新たに追加されたオプションです:

Tunnel についてはすでに説明しました。 TunnelDevice は、基本的にコマンドラインの -w オプションで やっていたことと同じです。違うのは PermitLocalCommandLocalCommand で、 この 2つを指定するとサーバの認証が成功したあとで、ローカルのクライアント上で 自動的に特定のコマンドを実行させることができます。ここでは、この機能を使って クライアント側の ifconfig を実行させています。

さて、上の例では LocalCommand がおかしな形になっていますが、 これは現時点 (4.3p2) での ssh のアホな仕様のせいです。これを書いている時点の ssh は、 LocalCommand の実行が完了したあとに tun/tap デバイスをオープンするのです。 しかし Linux では tun/tap デバイスが使われていない (どのプロセスも open していない) 状態では、 ifconfig で設定することはできません。 したがって LocalCommand を実行する時点では、まだ tun/tap は開かれておらず、 以下のようなエラーが出てしまいます:

SIOCSIFDSTADDR: No such device
tun0: unknown interface: No such device

さいわい、LocalCommand は指定されたコマンド文字列をそのままシェルに渡すので (つまりこれは sh -c "( sleep 3; ifconfig tun0 192.168.3.2 pointopoint 192.168.3.1 ) &" と同じです)、 いったん LocalCommand のシェルを終了させてから、 バックグラウンドで ifconfig を実行させることができます。 ここで最初に sleep 3 を指定しているのは、LocalCommand が実行されてから 3秒後にはおそらく tun0 インターフェイスが使用できる状態になっているであろうと期待しています。 ssh は LocalCommand プロセスの終了を待ちますので、 この行全体を ( ) でくくってバックグラウンドで実行させてやります。 これで一応、認証すると自動的に ifconfig が実行されるようにはなるのですが、 付け焼刃的な感じは否めません。 (一応これを修正するパッチを openssh-unix-dev に送ったのですが、いまんとこ無視されてます)

いっぽう FreeBSD の場合は、tun を開く前でも ifconfig が使えるので、 とくに sleep などの技を使わなくても、ふつうに

LocalCommand  ifconfig tun0 192.168.3.2 192.168.3.1
と書いておけば成功します。

なお、ここでやっている ifconfig を直接実行する方法も実はあまりおすすめできません。 RedHat 系の Linux であれば /etc/sysconfig/network-scripts/ifcfg-tun0 などの 設定ファイルを作っておき、直接 ifconfig を実行するかわりに /sbin/ifup tun0 とするのが正統なやり方でしょう。

うまくいけば、以下のように入力するだけで (クライアント側の) VPN は準備完了になるはずです:

client# ssh server-vpn
Enter passphrase for key '/root/.ssh/id_rsa': (パスフレーズを入力)
Last login: Mon Feb 13 10:30:42 2006 from xxx.xxx.xxx.xxx
server# 

サーバ側の authorized_keys ファイルを変更する

おつぎはサーバの設定です。これまではテストのため、 sshd設定ファイルの PermitRootLoginwithout-password を指定してサーバに root が直接ログインすることを 許していましたが、本来 root のシェルが必要ないときにシェルを実行させるのは おすすめできません。そこで、本格的に運用する場合はこの制限をもう一段階きつくして、

PermitRootLogin forced-commands-only
にします。これは、 「ログインすると強制的に特定のコマンドが実行される場合にのみ root のログインを許可する」という意味です。 もちろん、この場合も認証は必要です。 あまり知られていませんが、ユーザの ~/.ssh/authorized_keysファイルでは、 鍵ごとに「その鍵でログインしたときに強制的に実行するコマンド」を指定することができます。 これを指定するとそのコマンドのみが実行され、対話的なシェルは実行されません (実際には、そのコマンド文字列が sh -c に渡されます)。 具体的には、このファイルの各行に (RSA または DSA の) 公開鍵情報が記録されているのですが、
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApTD2ZRyh0R...
となっているところを、テキストエディタなどで
command="強制実行させるコマンド" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApTD2ZRyh0R...
のように変更してやります。 =の前後にスペースを入れてはいけません。また、1行を分断しないよう注意してください。 上で指定した PermitRootLogin forced-commands-only は、 このようなコマンドが指定されている公開鍵でのみ root ログインを許可するというものです。 この機能は、通常 root 権限が必要な定型作業 (バックアップなど) に使われるものですが、 このコマンドが指定されていると対話的なシェルは実行されず、コマンドが終了すると自動的にログアウトするため、 一般的なログインよりも安全です。 また、インターフェイス番号や双方の IP アドレスが決まっている場合であれば、 これを使って VPN の設定も自動的に行うことができます:

(Linux の場合)
command="ifconfig tun0 192.168.3.1 pointopoint 192.168.3.2" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApTD2ZRyh0R...

(FreeBSD の場合)
command="ifconfig tun0 192.168.3.1 192.168.3.2" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApTD2ZRyh0R...

一般的には、強制コマンド実行は指定されたコマンドが終了すると自動的にログアウトしてしまいますが、 トンネリングを使っている場合は、コマンドが終了したあともサーバは実行を続けます。 なお、sshd の場合はこのコマンドを実行する前に tun/tap デバイスを開くので、 クライアント側で紹介したような sleep のトリックは必要ありません。

さらに、ここにはサーバ側が割り当てる tun または tap のインターフェイス番号も 強制的に指定することができます:

(Linux の場合)
tunnel="0",command="ifconfig tun0 192.168.3.1 pointopoint 192.168.3.2" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApTD2ZRyh0R...

(FreeBSD の場合)
tunnel="0",command="ifconfig tun0 192.168.3.1 192.168.3.2" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApTD2ZRyh0R...

(注意: カンマの前後にスペースが入ってはいけません)

こうすると、クライアントからこの鍵を使ってログインしたときは必ず サーバ側の tun0 デバイスが開かれ ifconfig が自動的に実行される (しかも、対話的シェルは実行されない) ことになります。 以下のように入力して何も出てこなかったら成功です:

client# ssh server-vpn
Enter passphrase for key '/root/.ssh/id_rsa': (パスフレーズを入力)

複数のクライアントから同一のサーバに接続する

これまではクライアントとサーバが 1対1 の状況を想定してきましたが、 複数のクライアントに VPN を使わせたい場合は以下のようにします。 クライアント A と B が同一のサーバに接続すると仮定して、 それぞれ以下のような設定ファイルを作成します:

クライアント A の /root/.ssh/config:

client-a# cat /root/.ssh/config
Host server-vpn
        Hostname                server.example.com
        Port                    xxx
        User                    root
        IdentityFile            /root/.ssh/id_rsa
        Tunnel                  point-to-point
        TunnelDevice            0
        PermitLocalCommand      yes
        LocalCommand            ( sleep 3; ifconfig tun0 192.168.3.2 pointopoint 192.168.3.1 ) &

クライアント B の /root/.ssh/config:

client-b# cat /root/.ssh/config
Host server-vpn
        Hostname                server.example.com
        Port                    xxx
        User                    root
        IdentityFile            /root/.ssh/id_rsa
        Tunnel                  point-to-point
        TunnelDevice            0
        PermitLocalCommand      yes
        LocalCommand            ( sleep 3; ifconfig tun0 192.168.3.3 pointopoint 192.168.3.1 ) &

この 2つのファイルは一番最後の LocalCommand 以外はすべて共通の項目です。 ただし、前回と違って TunnelDevice には 0:0 ではなく 0 と指定しておきます。今まではクライアントがサーバ側の tun インターフェイス番号も 指定してきましたが、:0 を省略するとローカルなインターフェイス番号だけを指定でき、 サーバ側の番号の割り当てはサーバ側に任せることができます (この形式はコマンドラインの -wオプションでも使えます)。 ただしこの場合は、必ずサーバ側の authorized_keys ファイルで tunnel オプションを使ってサーバ側のインターフェイス番号を決めておく必要があります。 さもないと予想外のインターフェイスが割り当てられることがあるためです (たとえば FreeBSD では tun100 などが割り当てられる)。

また、クライアント A とクライアント B はそれぞれ別々の秘密鍵・公開鍵を使うことに 注意してください。こうすることでサーバは相手が使ってきた鍵によってクライアントを識別し、 インターフェイス番号と IP アドレスをふり分けることができます:

server# cat /root/.ssh/authorized_keys
tunnel="0",command="ifconfig tun0 192.168.3.1 pointopoint 192.168.3.2" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApTD2Z... (クライアント A の公開鍵)
tunnel="1",command="ifconfig tun0 192.168.3.1 pointopoint 192.168.3.3" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAyDXuQ... (クライアント B の公開鍵)

本当は クライアント側もサーバ側もインターフェイス番号や IP アドレスをまったく指定せずに その場で DHCP のようなことができたらいいのですが、そのためには サーバ側の (command で強制実行される) プロセスが 割り当てられたインターフェイス番号を知る必要があります。 しかし、現在のところサーバがどの番号のインターフェイスを割り当てたのか 外部から知るすべはありません (環境変数にも現れません)。


性能と安定性について

(この項まだ準備中)

Linux-2.4.31 カーネルのソースツリー (展開した状態で約 176MBytes) を転送する時間:

接続 NFS 直接 VPN+NFS ssh+rsync
Ethernet 100Mbps s s 65s
ケーブルモデム (最大 10Mbps程度) 1594s 1371s 83s

なお、ケーブルモデムを使った際は NFS に tcp オプションをつけています。 また、クライアント側の ssh には (NFS, rsync 時ともに) 以下のようなオプションを与えました:

Ciphers         arcfour   (暗号化アルゴリズムに高速な arcfour を指定する)
Compression     yes       (圧縮を許可する)
KeepAlive       yes       (一定時間ごとに Keep-alive メッセージを送る)

NFS+VPN の場合、 cp -a で大量のファイルを転送していると、途中で VPN 先のネットワークがまったく反応しなくなる (ping も通らない) というケースがたびたび起こりました。これが正確にどのような条件で 起こるのかはいまのところ不明です。


Last Modified: Tue Mar 14 13:09:29 EST 2006 (03/15, 03:09 JST)
Yusuke Shinyama