OpenSSH を使って簡易拠点間 VPN

概要

少し業務とは違うところで、NAT配下のマシン同士で、拠点間のVPNもどきが必要になる場面がありました。

単純な Dynamic Port Forwarding で、 SOCKS プロキシを使う手もあったのですが、やはり、ローカルアクセス相当がしたいと。
調べてみると、 OpenSSH 4.3 から、 tun/tap デバイスを使ったレイヤー2およびレイヤー3のトンリングがサポートされていることがわかりました。
OpenSSH を使った簡易 VPN の構築

そこで、実際にやってみたので、メモ代わりに残しておいてみることにしました。
なお、今回はレイヤー2のトンネリングを行うことにしました。

今回の環境

拠点のサーバ側、およびクライアント側は両方とも CentOS 6.3 を使用しました。

また、ネットワーク構成は以下のものとします。
ネットワーク図

クライアント側での準備

鍵生成

鍵認証を行うため、専用の鍵を生成します。
今回は、ブランクパスフレーズの鍵を生成しました。

また、あえて 1024 ビットの鍵を作っています。
(2048ビットだとトンネリングの負荷が少し高くなりそうだなあと思ったのと、そもそも、要件的に暗号強度は求められていないため)

[root@vpn-client ~]#  ssh-keygen -t rsa -b 1024
Generating public/private rsa key pair.
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.pub.
The key fingerprint is:
ee:ae:76:b8:e5:d1:ca:ac:ea:a5:a7:e1:62:ca:55:3d root@vpn-client
鍵をサーバに転送

作った鍵の公開鍵を、サーバ側に転送します。

[root@vpn-client ~]#  scp -p ~/.ssh/id_rsa.pub user@vpn-server:/tmp

サーバ側の設定

OpenSSH で tun/tap デバイスによるトンネリングを行うためには、いくつか下準備が必要です。

/etc/ssh/sshd_config を修正
## レイヤー2、およびレイヤー3 の両方のトンネリングを許可する場合
#PermitTunnel yes
## レイヤー2 のみを許可する場合(今回はこれを使用)
PermitTunnel ethernet
## レイヤー3 のみを許可する場合
# PermitTunnel point-to-point
## トンネリングには root ログインが必要なため、
## authorized_keys で記述したコマンドのみ実行できるように制限する
PermitRootLogin forced-commands-only
tun/tap デバイスを使えるように
  • /etc/modprobe.d/tun.conf を作る
[root@vpn-server ~]# cat /etc/modprobe.d/tun.conf
alias netdev-tun0 tun
alias netdev-tap0 tun
  • # modprobe tun
[root@vpn-server ~]# lsmod | grep tun
tun                    17127  0
クライアント側の公開鍵を登録

転送しておいた root ログイン用の公開鍵を登録します。

[root@vpn-server ~]# mkdir ~/.ssh
[root@vpn-server ~]# chmod 0700 ~/.ssh
[root@vpn-server ~]# echo -n 'tunnel="0",command="ifup tap0" ' > ~/.ssh/authorized_keys
[root@vpn-server ~]# cat /tmp/id_rsa.pub >> ~/.ssh/authorized_keys
[root@vpn-server ~]# chmod 0600 ~/.ssh/authorized_keys
[root@vpn-server ~]# rm -f /tmp/id_rsa.pub
[root@vpn-server ~]# cat ~/.ssh/authorized_keys
tunnel="0",command="ifup tap0" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAvM58cRnLjQQf+MniEtflzj4oAQNBCxeYv/NW0lsj/UnwYaX0H1z6kWpCWv/jI79zENVsJZ8VQUEa533tEBYZHLGh6xbKSpefFzWCUIEcxTT2Tm18ZJafnyrL36z1kWbnTzATxPkYrsaFeBQoVdmL9PGrTYiUwnoAyBqcLPwpErE= root@vpn-client
ifcfg-tap0 を作成

authorized_keys に記述している通り、 tap0 を ifup できるようにするため、
/etc/sysconfig/network-scripts/ifcfg-tap0 を作成します。

[root@vpn-server ~]# cat /etc/sysconfig/network-scripts/ifcfg-tap0
DEVICE="tap0"
BOOTPROTO="none"
MTU="1500"
NM_CONTROLLED="no"
ONBOOT="no"
TYPE="Ethernet"
IPADDR="192.168.11.254"
NETMASK="255.255.255.0"
route-tap0 を作成

さらに、サーバ側 → クライアント側の通信を可能とするため、ルーティングを追加する必要があります。
ifup tap0 時に自動的にルーティングが追加されるように /etc/sysconfig/network-scripts/route-tap0 を作成します。

[root@vpn-server ~]# cat /etc/sysconfig/network-scripts/route-tap0
192.168.12.0/24 via 192.168.11.253 dev tap0
パケット転送を有効化

CentOS のデフォルトでは、パケット転送が無効化されています。
トンネリングされたパケットは tap0 を通して送受信されますが、配下のネットワークに接続されているホストとは ethN を通して通信するため、パケット転送を有効化する必要があります*1

  • /etc/sysctl.conf を編集し、net.ipv4.ip_forward を 1 にする
--- /etc/sysctl.conf.org        2012-02-22 23:47:27.000000000 +0900
+++ /etc/sysctl.conf    2012-11-03 18:54:26.000000000 +0900
@@ -4,7 +4,7 @@
 # sysctl.conf(5) for more details.

 # Controls IP packet forwarding
-net.ipv4.ip_forward = 0
+net.ipv4.ip_forward = 1

 # Controls source route verification
 net.ipv4.conf.default.rp_filter = 1
  • # sysctl -p で設定を再読み込み

クライアント側の設定

クライアント側でも、おおよそ同じことを設定します。

tun/tap デバイスを使えるように
  • /etc/modprobe.d/tun.conf を作る

OpenSSH を使った簡易 VPN の構築 の解説では、

alias tun0 tun
alias tap0 tun

という設定例が掲載されていますが、CentOS6.3 で設定し、 /etc/init.d/network restart したところ、

Loading kernel module for a network device with
CAP_SYS_MODULE (deprecated).  Use CAP_NET_ADMIN and alias netdev-tap0 instead

といわれてしまったため、下記のように設定しました。

[root@vpn-client ~]# cat /etc/modprobe.d/tun.conf
alias netdev-tun0 tun
alias netdev-tap0 tun
  • # modprobe tun
[root@vpn-client ~]# lsmod | grep tun
tun                    22849  0
ifcfg-tap0 を作成

サーバと違うのは、IPADDR。

[root@vpn-client ~]#  cat /etc/sysconfig/network-scripts/ifcfg-tap0
DEVICE="tap0"
BOOTPROTO="none"
MTU="1500"
NM_CONTROLLED="no"
ONBOOT="no"
TYPE="Ethernet"
IPADDR="192.168.11.253"
NETMASK="255.255.255.0"
route-tap0 を作成

クライアント側 → サーバ側のルーティングを設定

# cat /etc/sysconfig/network-scripts/route-tap0
192.168.10.0/24 via 192.168.11.254 dev tap0
パケット転送を有効化

同様にパケット転送を有効化します*2

  • /etc/sysctl.conf を編集し、net.ipv4.ip_forward を 1 にする
--- /etc/sysctl.conf.org        2012-02-22 23:47:27.000000000 +0900
+++ /etc/sysctl.conf    2012-11-03 18:54:26.000000000 +0900
@@ -4,7 +4,7 @@
 # sysctl.conf(5) for more details.

 # Controls IP packet forwarding
-net.ipv4.ip_forward = 0
+net.ipv4.ip_forward = 1

 # Controls source route verification
 net.ipv4.conf.default.rp_filter = 1
  • [root@vpn-client ~]# sysctl -p
接続設定を作成

/root/.ssh/config に接続設定を記述します。

  • [root@vpn-client ~]# vi ~/.ssh/config
  • [root@vpn-client ~]# chmod 0600 ~/.ssh/config
[root@vpn-client ~]#  cat ~/.ssh/config
Host vpn-server
    HostName server.global
    Port 12345
    User root
    IdentityFile ~/.ssh/id_rsa
    Tunnel ethernet
    TunnelDevice 0:0
    PermitLocalCommand yes
    LocalCommand (echo "Waiting for 10 seconds..."; sleep 10; ifup tap0; ifconfig tap0; route; ping -c3 192.168.10.254; echo; if [ $? -eq 0 ]; then echo "Connect to 192.168.254: SUCCESS"; else echo "Connect to 192.168.10.254: FAILURE"; fi; echo; echo "Press Control-C to exit") &

OpenSSH を使った簡易 VPN の構築 で解説されているとおり、
クライアント側の tap0 は LocalCommand 実行後に作成されます

そのため、LocalCommand はバックグランドで実行し、スリープを挟むようにします。
リンク先では、3秒にされていますが、一度接続すればコネクションが切れない限りは放っておくものであることを鑑みて、安全側に倒して10秒にしています。

実際に接続してトンネリングを確認する

あとは ssh コマンドで接続するだけです。
上記のように設定した場合、ローカル側で ifup tap0 まで自動で行われます。

[root@vpn-client ~]# ssh vpn-server
Waiting for 10 seconds...
tap0      Link encap:Ethernet  HWaddr 5E:63:33:30:1E:AA
          inet addr:192.168.11.253  Bcast:192.168.11.255  Mask:255.255.255.0
          inet6 addr: fe80::5c63:33ff:fe30:1eaa/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:11 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:500
          RX bytes:678 (678.0 b)  TX bytes:448 (448.0 b)

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.12.0    *               255.255.255.0   U     0      0        0 eth1
192.168.10.0    192.168.11.254  255.255.255.0   UG    0      0        0 tap0
192.168.11.0    *               255.255.255.0   U     0      0        0 tap0
192.168.1.0     *               255.255.255.0   U     0      0        0 eth0
link-local      *               255.255.0.0     U     1002   0        0 eth0
link-local      *               255.255.0.0     U     1003   0        0 eth1
link-local      *               255.255.0.0     U     1008   0        0 tap0
default         192.168.1.254   0.0.0.0         UG    0      0        0 eth0
PING 192.168.10.254 (192.168.10.254) 56(84) bytes of data.
64 bytes from 192.168.10.254: icmp_seq=1 ttl=64 time=4.06 ms
64 bytes from 192.168.10.254: icmp_seq=2 ttl=64 time=2.16 ms
64 bytes from 192.168.10.254: icmp_seq=3 ttl=64 time=2.23 ms

--- 192.168.10.254 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 2.168/2.823/4.067/0.880 ms

Connect to 192.168.10.254: SUCCESS

Press Control-C to exit
サーバマシンの ifconfig, route の結果
[root@vpn-server ~]# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:0C:29:C1:3B:3D
          inet addr:192.168.1.100  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fec1:3b3d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:1370 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1179 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:157592 (153.8 KiB)  TX bytes:169069 (165.1 KiB)

eth1      Link encap:Ethernet  HWaddr 00:0C:29:C1:3B:47
          inet addr:192.168.10.254  Bcast:192.168.10.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fec1:3b47/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:117 errors:0 dropped:0 overruns:0 frame:0
          TX packets:152 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:12439 (12.1 KiB)  TX bytes:13373 (13.0 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:14 errors:0 dropped:0 overruns:0 frame:0
          TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:819 (819.0 b)  TX bytes:819 (819.0 b)

tap0      Link encap:Ethernet  HWaddr 7E:C4:3C:1A:B9:A3
          inet addr:192.168.11.254  Bcast:192.168.11.255  Mask:255.255.255.0
          inet6 addr: fe80::7cc4:3cff:fe1a:b9a3/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:32 errors:0 dropped:0 overruns:0 frame:0
          TX packets:31 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:500
          RX bytes:2456 (2.3 KiB)  TX bytes:2414 (2.3 KiB)
[root@vpn-server ~]# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.0     *               255.255.255.0   U     0      0        0 eth0
192.168.12.0    192.168.11.253  255.255.255.0   UG    0      0        0 tap0
192.168.10.0    *               255.255.255.0   U     0      0        0 eth1
192.168.11.0    *               255.255.255.0   U     0      0        0 tap0
link-local      *               255.255.0.0     U     1002   0        0 eth0
link-local      *               255.255.0.0     U     1003   0        0 eth1
link-local      *               255.255.0.0     U     1008   0        0 tap0
default         192.168.1.254   0.0.0.0         UG    0      0        0 eth0
クライアントマシンの ifconfig, route の結果
[root@vpn-client ~]# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:1E:C9:6B:5A:C2
          inet addr:192.168.1.100  Bcast:192.168.10.255  Mask:255.255.255.0
          inet6 addr: fe80::21e:c9ff:fe6b:5ac2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:4626 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3220 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:506625 (494.7 KiB)  TX bytes:413131 (403.4 KiB)
          Interrupt:21 Memory:fe9e0000-fea00000

eth1      Link encap:Ethernet  HWaddr 00:A0:B0:A5:84:A4
          inet addr:192.168.12.254  Bcast:192.168.12.255  Mask:255.255.255.0
          inet6 addr: fe80::2a0:b0ff:fea5:84a4/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:22 errors:0 dropped:0 overruns:0 frame:0
          TX packets:12 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:7454 (7.2 KiB)  TX bytes:828 (828.0 b)
          Interrupt:16 Base address:0x6f00

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  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:0
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

tap0      Link encap:Ethernet  HWaddr 5A:F1:0D:F0:93:27
          inet addr:192.168.11.253  Bcast:192.168.11.255  Mask:255.255.255.0
          inet6 addr: fe80::58f1:dff:fef0:9327/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:31 errors:0 dropped:0 overruns:0 frame:0
          TX packets:32 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:500
          RX bytes:2414 (2.3 KiB)  TX bytes:2456 (2.3 KiB)

[root@vpn-client ~]# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.12.0    *               255.255.255.0   U     0      0        0 eth1
192.168.10.0    192.168.11.254  255.255.255.0   UG    0      0        0 tap0
192.168.11.0    *               255.255.255.0   U     0      0        0 tap0
192.168.1.0     *               255.255.255.0   U     0      0        0 eth0
link-local      *               255.255.0.0     U     1002   0        0 eth0
link-local      *               255.255.0.0     U     1003   0        0 eth1
link-local      *               255.255.0.0     U     1009   0        0 tap0
default         192.168.1.254   0.0.0.0         UG    0      0        0 eth0
クライアント側ネットワークのホスト (192.168.12.100) から、サーバ側ネットワークのホスト (192.168.10.100) に tracert してみる
C:\Documents and Settings\user>tracert 192.168.10.100

Tracing route to 192.168.10.100 over a maximum of 30 hops

  1    <1 ms    <1 ms    <1 ms  192.168.12.254
  2     2 ms     2 ms     2 ms  192.168.11.254
  3     2 ms     2 ms     2 ms  192.168.10.100

Trace complete.
サーバ側ネットワークのホスト (192.168.10.100) から、クライアント側ネットワークのホスト(192.168.12.100) に traceroute してみる
[root@server-host ~]# traceroute -T 192.168.12.100
traceroute to 192.168.12.10 (192.168.12.10), 30 hops max, 60 byte packets
 1  192.168.10.254 (192.168.10.254)  0.212 ms  0.135 ms  0.104 ms
 2  192.168.11.253 (192.168.11.253)  5.481 ms  6.058 ms  6.755 ms
 3  192.168.12.100 (192.168.12.100)  7.385 ms  8.171 ms  8.408 ms

トンネリングを終了するには

入力待ちとなっているコンソールで、 Control-C を入力することで終了します。
tap0 デバイスは自動的に削除されるので、特に後始末をする必要はありません。

Connect to 192.168.10.254: SUCCESS

Press Control-C to exit
^CKilled by signal 2.

やってないこと

  • パフォーマンスの測定
    • そもそも一時用途なので、あまりまじめに使おうとしていません。通常のVPNよりは当然パフォーマンスは落ちるはずです。鍵の長さによる、負荷比も未確認です。
  • 安定性
    • SSHと同程度の安定性だと思いますが、こちらも未確認です。

*1:vpn-server にあたるマシンが、すでにルータを担っている場合、この設定は有効化されてるはずです。

*2:vpn-client にあたるマシンが、すでにルータを担っている場合、この設定は有効化されてるはずです。