Help us understand the problem. What is going on with this article?

CentOS 7 で SSH VPN

More than 5 years have passed since last update.

いつか役にたつかもしれないので CentOS 7 で SSH VPN をしてみます。

SSH VPN とは OpenSSH で作る簡易な VPN です。ググると情報は色々ありますが、iproute2 や NetworkManager での設定方法はあまり見つかりませんでした。

構成

  • hostB と hostC と hostX が同じネットワークに属しています
  • hostA からはグローバルの 9876 ポートを指定して hostX に ssh できます
  • hostA から hostX を経由して hostB に ssh できます
  • hostA から hostB に ssh で VPN を張って hostA から hostC に透過的にアクセスします

例えば次のような IP アドレスだとします。

hostA 192.168.8.123 ※VPN の端点
hostB 192.168.0.100 ※VPN の端点
hostC 192.168.0.200 ※目的のサーバ
hostX 203.0.113.111 ※hostA から hostB に ssh するための中継機のグローバルアドレス

hostA の .ssh/config で次のように記述しているので、hostA から hostB へは ssh hostB だけで接続できます。

.ssh/config
Host hostB
  HostName 192.168.0.100
  ProxyCommand ssh 203.0.113.111 -l ore -p 9876 -W %h:%p

L3(point-to-point)

L3(point-to-point) で VPN を構成します。
この方法は hostA と hostB に NIC を追加してクロスケーブルで接続し、hostB にルータのように振る舞わせるイメージです。

まず、hostA と hostB の /etc/ssh/sshd_config に次を追記します。

/etc/ssh/sshd_config
PermitTunnel point-to-point

設定を反映します。

hostA/hostB
# systemctl reload sshd

hostA から hostB に -w0:0 を付けて接続します。

hostA
# ssh -N -f -w0:0 root@hostB

hostA と hostB でそれぞれ tun0 というネットワークデバイスができているはずです。

hostA/hostB
# ip link show dev tun0
3: tun0: <POINTOPOINT,MULTICAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 500
    link/none

hostA で tun0 に IP アドレスを設定します。このとき peer で hostB の tun0 に設定する IP アドレスも指定します。

hostA
# ip link set tun0 up
# ip addr add 192.168.254.2 peer 192.168.254.1 dev tun0

さらに、hostA に hostB が属するネットワークへのルーティングを追加します。

hostA
# ip route add 192.168.0.0/24 via 192.168.254.1 dev tun0

hostB で tun0 に IP アドレスを設定します。peer も指定するのは hostA と同様です。

hostB
# ip link set tun0 up
# ip addr add 192.168.254.1 peer 192.168.254.2 dev tun0

この時点で hostB に付与した IP アドレスに hostA から ping が通ります。

hostA
# ping 192.168.254.1
PING 192.168.254.1 (192.168.254.1) 56(84) bytes of data.
64 bytes from 192.168.254.1: icmp_seq=1 ttl=64 time=20.7 ms
64 bytes from 192.168.254.1: icmp_seq=2 ttl=64 time=26.2 ms

しかし、まだ hostA から hostC へは接続できません。

hostC に hostA へのルーティングを設定すれば良いのですが、めんどうなので hostB で IP マスカレードします。

firewall-cmd のデフォルトだと NIC のゾーンを external にすればマスカレードが有効になります(ens32 は hostB が元々持っていた NIC です)。

hostB
# firewall-cmd --zone=external --change-interface=ens32

もしくは、hostB の NIC が元々属していたゾーンにマスカレードを追加しても大丈夫です。

hostB
# firewall-cmd --zone=public --add-masquerade

IP マスカレードをする場合(というか hostB をルータのように使う場合)sysctl で IP フォワーディングを有効にする必要がありますが、firewall で IP マスカレードを設定すると IP フォワーディングも自動的に有効になるようです。

これで hostA から hostC に VPN 経由でアクセスできるようになります。

hostA
# ping 192.168.0.200
PING 192.168.0.200 (192.168.0.200) 56(84) bytes of data.
64 bytes from 192.168.0.200: icmp_seq=1 ttl=63 time=16.9 ms
64 bytes from 192.168.0.200: icmp_seq=2 ttl=63 time=17.8 ms

traceroute すれば VPN 経由であることがわかります。

hostA
# traceroute 192.168.0.200
traceroute to 192.168.0.200 (192.168.0.200), 30 hops max, 60 byte packets
 1  192.168.254.1 (192.168.254.1)  25.189 ms  43.994 ms  44.008 ms
 2  192.168.0.200 (192.168.0.200)  44.008 ms  44.006 ms  44.003 ms

いらなくなったら hostA で ssh を殺します。

hostA
# pkill -x ssh

tun0 に設定した IP アドレスやルーティングは tun0 デバイスが消えると一緒に消えます。

hostB の firewall は自動的には元に戻らないので、戻すのであれば restart で戻します。

hostB
# systemctl restart firewalld

L2(ethernet)

次は L2(ethernet) で VPN を構成します。
この方法は hostB を L2SW のように振る舞わせるイメージです。

なお、hostB が仮想環境の場合は仮想化のホスト側で hostB に割り当てられている仮想 NIC のプロミスキャスモードを有効にしておく必要があります(これに気づかずに 1 時間ぐらい無駄にしました・・・)。

プロミスキャスモードを有効にする方法は仮想化の製品によって異なるので詳細は割愛しますが、

  • ESXi ならホストの構成のネットワークの仮想スイッチのプロパティのセキュリティで「無差別モード」を「承諾」にします
  • VirtualBox ならゲストのネットワークの設定で「プロミスキャスモード」を「すべて許可」にします

プロミスキャスモードが設定できたら、hostA と hostB の /etc/ssh/sshd_config に次を追記します。

/etc/ssh/sshd_config
PermitTunnel ethernet

設定を反映します。

hostA/hostB
# systemctl reload sshd

hostB でブリッジを作っておきます。

失敗すると hostB に繋げられなくなるので、この作業はあらかじめ安全に作業できる状況でやっておくことをオススメします。

hostB
# nmcli con add type bridge ifname br0 stp no ;\
> nmcli con modify bridge-br0 ipv4.method auto ;\
> nmcli con del ens32 ;\
> nmcli con add type bridge-slave ifname ens32 master br0

hostA から hostB に -w0:0-o Tunnel=ethernet を付けて接続します。

hostA
# ssh -N -f -w0:0 -o Tunnel=ethernet hostB

hostA と hostB でそれぞれ tap0 というネットワークデバイスができているはずです。

hostA/hostB
# ip link show dev tap0
7: tap0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 500
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff

hostB で tap0 をブリッジに追加します。

hostB
# ip link set tap0 master br0
# ip link set tap0 up

hostA で tap0 をアクティブにします。

hostA
# ip link set tap0 up

この時点で hostA から hostC に arping が通るはずです。

hostA
# arping -I tap0 192.168.0.200
ARPING 192.168.0.200 from 192.168.8.123 tap0
Unicast reply from 192.168.0.200 [xx:xx:xx:xx:xx:xx]  16.882ms
Unicast reply from 192.168.0.200 [xx:xx:xx:xx:xx:xx]  13.632ms

hostA で tap0 に hostB が属するネットワーク の IP アドレスを設定します。

hostA
# ip addr add tap0 192.168.0.101/24 dev tap0

もしくは hostA の NetworkManager で tap0 を dhcp にします。NetworkManager を restart しているのは nmcli con に tap0 を表示させるためです。なお、この dhcp に応えるのは hostB が属するネットワーク上の dhcp サーバ です。

hostA
# systemctl restart NetworkManager
# nmcli con modify tap0 ipv4.method auto
# nmcli con up tap0

hostA から hostC に ping が通るようになります。

hostA
# ping 192.168.0.200
PING 192.168.0.200 (192.168.0.200) 56(84) bytes of data.
64 bytes from 192.168.0.200: icmp_seq=1 ttl=64 time=16.1 ms
64 bytes from 192.168.0.200: icmp_seq=2 ttl=64 time=15.8 ms

この VPN はイーサネットフレームをカプセル化して通しているのでブロードキャストも通ります。

hostA
# ping -I tap0 -b 255.255.255.255
WARNING: pinging broadcast address
PING 255.255.255.255 (255.255.255.255) from 192.168.0.101 tap0: 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=37.3 ms
64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=89.7 ms (DUP!)
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=89.7 ms (DUP!)

いらなくなったら hostA で ssh を殺します。

hostA
# pkill -x ssh

hostA で tap0 を NetworkManager で dhcp にしているとゴミが残るのでこれも削除します。

hostA
# nmcli con del tap0

さいごに

ssh 一本繋がるだけで VPN ができるのでお手軽です。が、普通は -L のポートフォワードや -D の SOCKS プロキシの方が簡単ですね。

また、SSH VPN は TCP Over TCP な VPN になります。あまり好ましくないので常用すべきではありません。

ngyuki
テック系男子。 ただのやってみた系の記事ははてなブログに、それ以外の技術系のネタは Qiita に投稿します。
https://ngyuki.jp/
headjapan
中規模~大規模の安定した基幹システム・大規模サイトの分析・要件定義・設計・開発を得意とする、総合的な開発会社です。
http://www.headjapan.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away