いつか役にたつかもしれないので 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
だけで接続できます。
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
に次を追記します。
PermitTunnel point-to-point
設定を反映します。
# systemctl reload sshd
hostA から hostB に -w0:0
を付けて接続します。
# ssh -N -f -w0:0 root@hostB
hostA と hostB でそれぞれ tun0 というネットワークデバイスができているはずです。
# 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 アドレスも指定します。
# ip link set tun0 up
# ip addr add 192.168.254.2 peer 192.168.254.1 dev tun0
さらに、hostA に hostB が属するネットワークへのルーティングを追加します。
# ip route add 192.168.0.0/24 via 192.168.254.1 dev tun0
hostB で tun0 に IP アドレスを設定します。peer も指定するのは hostA と同様です。
# ip link set tun0 up
# ip addr add 192.168.254.1 peer 192.168.254.2 dev tun0
この時点で hostB に付与した IP アドレスに hostA から ping が通ります。
# 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 です)。
# firewall-cmd --zone=external --change-interface=ens32
もしくは、hostB の NIC が元々属していたゾーンにマスカレードを追加しても大丈夫です。
# firewall-cmd --zone=public --add-masquerade
IP マスカレードをする場合(というか hostB をルータのように使う場合)sysctl で IP フォワーディングを有効にする必要がありますが、firewall で IP マスカレードを設定すると IP フォワーディングも自動的に有効になるようです。
これで hostA から hostC に VPN 経由でアクセスできるようになります。
# 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 経由であることがわかります。
# 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 を殺します。
# pkill -x ssh
tun0 に設定した IP アドレスやルーティングは tun0 デバイスが消えると一緒に消えます。
hostB の firewall は自動的には元に戻らないので、戻すのであれば restart で戻します。
# systemctl restart firewalld
L2(ethernet)
次は L2(ethernet) で VPN を構成します。
この方法は hostB を L2SW のように振る舞わせるイメージです。
なお、hostB が仮想環境の場合は仮想化のホスト側で hostB に割り当てられている仮想 NIC のプロミスキャスモードを有効にしておく必要があります(これに気づかずに 1 時間ぐらい無駄にしました・・・)。
プロミスキャスモードを有効にする方法は仮想化の製品によって異なるので詳細は割愛しますが、
- ESXi ならホストの構成のネットワークの仮想スイッチのプロパティのセキュリティで「無差別モード」を「承諾」にします
- VirtualBox ならゲストのネットワークの設定で「プロミスキャスモード」を「すべて許可」にします
プロミスキャスモードが設定できたら、hostA と hostB の /etc/ssh/sshd_config
に次を追記します。
PermitTunnel ethernet
設定を反映します。
# systemctl reload sshd
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
を付けて接続します。
# ssh -N -f -w0:0 -o Tunnel=ethernet hostB
hostA と hostB でそれぞれ tap0 というネットワークデバイスができているはずです。
# 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 をブリッジに追加します。
# ip link set tap0 master br0
# ip link set tap0 up
hostA で tap0 をアクティブにします。
# ip link set tap0 up
この時点で hostA から hostC に arping が通るはずです。
# 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 アドレスを設定します。
# ip addr add tap0 192.168.0.101/24 dev tap0
もしくは hostA の NetworkManager で tap0 を dhcp にします。NetworkManager を restart しているのは nmcli con
に tap0 を表示させるためです。なお、この dhcp に応えるのは hostB が属するネットワーク上の dhcp サーバ です。
# systemctl restart NetworkManager
# nmcli con modify tap0 ipv4.method auto
# nmcli con up tap0
hostA から hostC に ping が通るようになります。
# 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 はイーサネットフレームをカプセル化して通しているのでブロードキャストも通ります。
# 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 を殺します。
# pkill -x ssh
hostA で tap0 を NetworkManager で dhcp にしているとゴミが残るのでこれも削除します。
# nmcli con del tap0
さいごに
ssh 一本繋がるだけで VPN ができるのでお手軽です。が、普通は -L
のポートフォワードや -D
の SOCKS プロキシの方が簡単ですね。
また、SSH VPN は TCP Over TCP な VPN になります。あまり好ましくないので常用すべきではありません。