我が家のubuntuサーバでVPN(Virtual Private Network)を試みることに。
ssh(Secure Shell)のトンネリング機能を利用したVPNの備忘録。
(1) sshの準備
sshのトンネリングでは、デバイスの設定などroot権限が必要なこととVPNを行うためのトンネリング設定をconfig ファイル(/etc/ssh/sshd_conf)に書く必要がある。
PermitRootLoginとPermitTunnelを以下に。PermitRootLoginのforced-commands-onlyってのはルートでログインはできるけど、コマンド一発叩いて終わりってものらしい。
PermitRootLogin forced-commands-only
# 次にPermitRootLogin。レイヤー2(TAP)のみを許可する場合は、
# PermitTunnel ethernet
# レイヤー3(TUN) のみを許可する場合は、
PermitTunnel point-to-point
# 両方のトンネリングを許可する場合は、
# PermitTunnel yes
今回はレイヤー3のpoint-to-pointoに設定↑。ここで、sshdの再起動# 次にPermitRootLogin。レイヤー2(TAP)のみを許可する場合は、
# PermitTunnel ethernet
# レイヤー3(TUN) のみを許可する場合は、
PermitTunnel point-to-point
# 両方のトンネリングを許可する場合は、
# PermitTunnel yes
server$ service sshd reload
(2) ssh:鍵の生成と配置
クライアント側で秘密鍵と公開鍵をssh-keygenコマンドで生成。
鍵の名前を「vpn」としてrootで実行。パスフレーズはなしで設定。
client$ ssh-keygen -f ~/.ssh/vpn
(略)
Your identification has been saved in /root/.ssh/vpn.
Your public key has been saved in /root/.ssh/vpn.pub.
秘密鍵「vpn」と公開鍵「vpn.pub」が生成される。(略)
Your identification has been saved in /root/.ssh/vpn.
Your public key has been saved in /root/.ssh/vpn.pub.
サーバーにこの公開鍵(vpn.pub)を配置する。
scpなどで、クライアント側のvpn.pubをサーバーに転送し、鍵認証設定ファイル(authorized_keys)に追記する。
server$ cat vpn.pub >> authorized_keys
server$ cat authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDi0BlNgdq・・・
(略)
server$ chmod 600 authorized_keys
server$ rm -fv vpn.pub
server$ cat authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDi0BlNgdq・・・
(略)
server$ chmod 600 authorized_keys
server$ rm -fv vpn.pub
(3) 仮想ネットワークデバイスのTUN/TAP
TAPはデバイスのシミュレートで、TUNはネットワーク層のシミュレートだって。レイヤーが違うとのこと。
ubuntuはいいんですが、私のメインマシンのMacOSXはどうなんでしょう。
よくわからんが、Mac App Storeに「tuntap」なるものがあったので、とりあえずインストール。
client$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null
client$ brew install tuntap
pythonでtunを開いたり設定したりできるみたい。client$ brew install tuntap
That Handy Tap Interface on Mac OSX #!/usr/bin/python import os while 1: try: file_path = '/dev/tap1' dev_file = os.open(file_path, os.O_RDWR) interface = 'tap1' while 1: pass except: print "tap interface is closing" exit() break |
PythonでTUN/TAPを使う#!/usr/bin/python import os import subprocess tun = os.open('/dev/tun0', os.O_RDWR) subprocess.check_call('sudo ifconfig tun0 192.168.100.2 192.168.100.1 netmask 255.255.255.0 up', shell=True) while True: data = os.read(tun, 1500) print(data) os.write(tun, data) |
(4) sshでこの仮想ネットワークデバイスを利用した接続設定
authorized_keysの先頭に、rootでログインしたときの設定やコマンドを登録。
特定のコマンドを実行する際には、
command="/bin/ls -l" ssh-rsa ・・・今回はtunを使ったデバイス設定コマンド(ifconfig)と禁止事項の設定を書く。
仮想ネットワークデバイスのtun0にアドレスを設定。サーバーのアドレスを10.0.0.1にクライアント側を10.0.0.2に。
ifconfig tun0 10.0.0.1 pointopoint 10.0.0.2 # デバイス設定コマンド no-X11-forwarding # X11 フォワードを禁止 no-agent-forwarding # 認証エージェント転送の禁止 no-pty # 端末の割り当て禁止これらをまとめてauthorized_keysの先頭に。ssh-rsa ・・・の直前に記載する。
server$ cat authorized_keys
no-X11-forwarding,no-agent-forwarding,no-pty,command="ifconfig tun0 10.0.0.1 pointopoint 10.0.0.2" ssh-rsa AAAAB3NzaC1yc2EAAAADAQ・・・
(略)
これでサーバー側のsshの設定は完了。no-X11-forwarding,no-agent-forwarding,no-pty,command="ifconfig tun0 10.0.0.1 pointopoint 10.0.0.2" ssh-rsa AAAAB3NzaC1yc2EAAAADAQ・・・
(略)
クライアント側のssh接続の際の設定ファイル(.ssh/config)にも、クライアント側のデバイス設定などを記載。
client$ cat config
Host server
HostName server
IdentityFile ~/.ssh/vpn
Tunnel point-to-point
TunnelDevice 0:0
RequestTTY no
PermitLocalCommand yes
LocalCommand ifconfig tun0 inet 10.0.0.2 10.0.0.1
ssh接続の際に実行するコマンドをLocalCommandで設定。クライアント側のtun0は当然のことながらサーバー設定の逆。(クライアント自身のアドレスを10.0.0.2にサーバート側を10.0.0.1に)
ほぼ、これで準備完了。
(5) いざ、接続
某プロバイダーに接続を変えて(一旦、外部に接続を変更)、クライアントからssh接続をrootで実行。ポートはxxxに変更。
-wオプションは、ローカルtun [:リモートtun ] クライアント側のtunデバイス番号とサーバ側のtunデバイス番号を使ったトンネリングの要求)、-vオプションでメッセージを表示。
client$ ssh -v -w0:0 server -p xxx
debug1: Reading configuration data /root/.ssh/config
debug1: /root/.ssh/config line 1: Applying options for server
debug1: Reading configuration data /etc/ssh_config
debug1: /etc/ssh_config line 20: Applying options for *
debug1: /etc/ssh_config line 102: Applying options for *
debug1: Connecting to server [aaa.bbb.ccc.ddd] port xxx.
debug1: Connection established.
(略)
debug1: sys_tun_open: /dev/tun0 mode 1 fd 5
debug1: channel 0: new [tun]
debug1: channel 1: new [client-session]
(略)
debug1: Remote: X11 forwarding disabled.
debug1: Remote: Agent forwarding disabled.
(略)
debug1: client_input_channel_req: channel 1 rtype exit-status reply 0
debug1: client_input_channel_req: channel 1 rtype eow@openssh.com reply 0
debug1: channel 1: free: client-session, nchannels 2
って感じ。サーバー側の状況。
server$ ifconfig tun0
tun0 Link encap:不明なネット ハードウェアアドレス 00-00-00-00-00-・・・
inetアドレス:10.0.0.1 P-t-P:10.0.0.2 マスク:255.255.255.255
UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1500 メトリック:1
RXパケット:0 エラー:0 損失:0 オーバラン:0 フレーム:0
TXパケット:24 エラー:0 損失:0 オーバラン:0 キャリア:0
衝突(Collisions):0 TXキュー長:500
RXバイト:0 (0.0 B) TXバイト:6820 (6.8 KB)
クライアント側。client$ ifconfig tun0 tun0: flags=8851ちなみにpsコマンドでプロセスを確認すると、mtu 1500 inet 10.0.0.2 --> 10.0.0.1 netmask 0xff000000 open (pid 1289)
client$ ps aux
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
(略)
root 1289 0.0 0.1 2461144 2260 s001 S+ 2:01PM 0:00.04 ssh -v -w0:0 server -p xxx
(略)
となっており、上記のifconfigで確認したクライアント側のtun0のプロセスid(pid 1289)とsshの接続コマンドのpidの一致がわかる。で、クライアント側からpingで確認(10.0.0.2→10.0.0.1)。
client$ ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1): 56 data bytes
64 bytes from 10.0.0.1: icmp_seq=0 ttl=64 time=2.212 ms
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=3.751 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=2.054 ms
サーバーからも同様に(10.0.0.1→10.0.0.2)。server$ ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=2.15 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=89.6 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=111 ms
とりあずpoint to pointは開通。クライアントのルーティングテーブルを見ると、
client$ netstat -nr
Routing tables
Internet:
Destination Gateway Flags Refs Use Netif Expire
default 192.168.0.1 UGSc 19 0 en0
10.0.0.1 10.0.0.2 UH 1 6 tun0
127 127.0.0.1 UCS 0 0 lo0
127.0.0.1 127.0.0.1 UH 3 3512 lo0
169.254 link#4 UCS 0 0 en0
192.168.0 link#4 UCS 9 0 en0
このままではまだサーバー側のネットワーク(192.168.0.0/24)にアクセスできないので、クライアントのRouting tablesにゲートウェイとしてtun0を介したサーバーアドレスの10.0.0.1を追加。client$ route add -net 192.168.0.0/24 10.0.0.1 client$ netstat -nr (略) Destination Gateway Flags Refs Use Netif Expire (略) 192.168.0 10.0.0.1 UGSc 0 0 tun0pingをかけると、
client$ ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
いかん。まだ192.168.0.0側に通じてない。サーバー側のtun0の様子をtcpdumpで見る。
client$ ping 192.168.0.1 server$ tcpdump -s0 -i tun0 -X tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes 12:46:21.578871 IP 10.0.0.2 > 192.168.0.1: ICMP echo request, id 17924, seq 0, length 64 0x0000: 4500 0054 de39 0000 4001 d1c4 0a00 0002 E..T.9..@....... (略) 0x0040: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 $%&'()*+,-./0123 0x0050: 3435 3637 4567 12:46:22.531078 IP 10.0.0.2 > 192.168.0.1: ICMP echo request, id 17924, seq 1, length 64 0x0000: 4500 0054 d813 0000 4001 d7ea 0a00 0002 E..T....@....... (略) 0x0020: 0006 ba82 0809 0a0b 0c0d 0e0f 1011 1213 ................ 0x0030: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 .............!"#ルーティングは効いてつながってはいる。
ってことで、クライアントの.ssh/configのコマンドにルーティング設定も加える。
client$ cat config
Host server
HostName server
IdentityFile ~/.ssh/vpn
Tunnel point-to-point
TunnelDevice 0:0
RequestTTY no
PermitLocalCommand yes
LocalCommand ifconfig tun0 inet 10.0.0.2 10.0.0.1;
route add -net 192.168.0.0/24 10.0.0.1
ただ、サーバー側がまだ足りない。natして外に出られるようにする。iptablesでルールの設定。「enp2s0」はサーバー側(192.168.0.0/24)のインターフェース。
tun0からは制限をかけず、ESTABLISHED,RELATEDは許可ってのも設定しておく。
server$ iptables -A FORWARD -i tun0 -o enp2s0 -s 10.0.0.0/24 -j ACCEPT server$ iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPTtun0(10.0.0.0/24)から出て行くパケットをサーバーのアドレスに書き換える。
server$ iptables -t nat -A POSTROUTING -o enp2s0 -s 10.0.0.0/24 -j MASQUERADE
iptablesの様子。
server$ sudo iptables -L Chain INPUT (policy ACCEPT) target prot opt source destination Chain FORWARD (policy DROP) target prot opt source destination ACCEPT all -- 10.0.0.0/24 anywhere ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED (略) server$ sudo iptables -L -t nat (略) Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 10.0.0.0/24 anywhereiptablesの保存。一応。
iptables-save再度、ping。
client$ ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1): 56 data bytes
64 bytes from 192.168.0.1: icmp_seq=0 ttl=63 time=724.364 ms
64 bytes from 192.168.0.1: icmp_seq=1 ttl=63 time=217.941 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=63 time=177.990 ms
いいんじゃないんっすか。ちなみに別のMacBookで動いているWebサーバーにhttpかけてみると、
client$ telnet 192.168.0.49 80
Trying 192.168.0.49...
Connected to 192.168.0.49.
Escape character is '^]'.
GET /
<body><h1>
It works!</h1>
</body></html>Connection closed by foreign host.
OK。
いいんじゃないんっすか。
某プロバイダ経由はちょっとスピードは遅いけど、sshトンネルでvpnができた模様です。
以下のページを参考にしました。Special Thanks.
- 入門OpenSSH
- SSH でVPNを実現する - いますぐ実践! Linuxシステム管理 / Vol.248
- Ubuntu 14.04 で OpenVPN
- strongSwan + xl2tpd でVPN(L2TP/IPsec)を構築する
簡単に書いてみたけど、結構すったもんだ時間かかりました。勉強になった。。。
徒然なるままに今日も明日も。