SSHトンネリング＆HTTPSプロキシで最強のVPN作った for linux

# やりたいこと
ファイアウォールに引っかからない＆そもそも外からVPNだとわからないようなVPNを作りたい。
IPv6にも対応させてすべての外向き通信をVPN経由にしたい。
Softether VPNでも実現できそうだが勉強のために自作。
＊自己責任＆悪用厳禁

# 前提
- サーバーとクライアントはLinux（パッケージの関係はArchLinuxの場合で説明）
- port443が使える自前サーバー
- systemd-networkd（じゃなくてもいいけど各自調べてください）

# 注意事項
サーバーがグローバルIPv6アドレスを持っていない場合はIPv6関係の話は読み飛ばしてください。
この場合、クライアントはVPN接続中にIPv6を無効にしておかないとIPv6の通信が外に漏れてしまいます。

あと細かいことですがサーバーへの接続にはIPv4を使います。
また、ファイアウォールの設定は各自でやってください。

# 目次
#### サーバー側
- sshd_configの変更
- tun deviceを作成
- squidでHTTPSプロキシ構築
 - 自己証明書発行
 - squidの設定

#### クライアント側
- tun deviceを作成
- proxytunnelのインストールとSSHの設定
- ルーティング設定
- 接続・切断の自動化

# サーバー側
## sshd_configの変更
`/etc/ssh/sshd_config`を編集してsshトンネリングを許可します。

変更前
```#PermitTunnel no```
変更後
```PermitTunnel yes```

もちろんport22を使わないとか公開鍵認証にするとか最低限のセキュリティ対策は各自でお願いします。

## tun deviceを作成
今回はLayer3での接続にするのでtun deviceを作成します。
systemd-networkdを使っている前提で話を進めますので、他のものを使っている人は各自頑張って調べてください...

`/etc/systemd/network`に以下の２つのファイルを作成します。

```$ sudo vim /etc/systemd/network/10-tun1.netdev```

```10-tun1.netdev
[NetDev]
Name=tun1
Kind=tun
```

```$ sudo vim /etc/systemd/network/10-tun1.network```

```10-tun1.network
[Match]
Name=tun1

[Network]
# IPアドレスは今使っているものと重複しないように適宜変更してください
Address=10.0.0.1/24
Address=fc00::1/64
IPMasquerade=both
```

どうやら古いバージョンのsystemd-networkdだとIPv6のマスカレードに対応していないみたいです。
パッケージマネージャでインストールされるものが最新版でない場合は自分でビルドするかファイアーウォールの方で設定してください。もちろんこれを機にArchLinuxに乗り換えるのもアリですよ？

ちなみに`10-tap1.netdev`や`10-tap1.network`の10という数字は、`/etc/systemd/network`以下にあるファイルの優先順位を指定しています（ファイル名が若いものから順に読み込まれる）。10にした意味は特にないので他にファイルが存在している場合は適宜変更してください。

systemd-networkdを再起動します。
```sudo systemctl restart systemd-networkd```

最後に確認。

```
$ ip a
~~省略~~
3: tun1: <NO-CARRIER,POINTOPOINT,MULTICAST,NOARP,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 500
    link/none
```
tun1ができていればOK

## squidでHTTPSプロキシ構築
サーバーにSSH接続をすると、通信内容自体は暗号化されますが、DPIを使うとSSHであることがわかってしまいます。そこでプロキシ経由でSSHすることを考えます。

まず、squidをインストールしておきましょう。
`$ sudo pacman -S squid`
aptやyumでも同様にインストールできるはずです（適当）。Archはいいぞ！（しつこい）
####自己証明書を発行
クライアントとプロキシ間の接続は暗号化しないとCONNECTメソッドを使っていることがパケットキャプチャでわかってしまうので、プロキシはHTTPS対応にします。自分が使うだけなのでオレオレ証明書で妥協。

```
$ cd /etc/squid
$ sudo mkdir certs
$ cd certs
$ sudo openssl genrsa -out server.key 2048
$ sudo openssl req -new -key server.key -out server.csr
   (色々と入力を求められるので適当に入力)
$ sudo openssl x509 -days 3650 -req -signkey server.key -in server.csr -out server.crt
```

#### squidの設定
squidはbasic認証を設定しておきましょう。
htpasswdコマンドを使うためだけにapacheをインストールしなくてはなりませんOrz
```$ sudo pacman -S apache```
ユーザー名は何でも良いです。（OSのユーザー名とは無関係）
```$ sudo htpasswd -c /etc/squid/htpasswd [ユーザー名]```
このコマンドを実行するとパスワードの入力を求められるので適当に決めて入力します。

apacheはもう消してもよい。
```$ sudo pacman -Rns apache```

次に`/etc/squid/squid.conf`を編集します。
```sudo vim /etc/squid/squid.conf```

```squid.conf
# basic認証
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/htpasswd
auth_param basic realm Authentication
acl authenticated proxy_auth REQUIRED
http_access allow authenticated
http_access deny all

# CONNECTメソッドはsshポートのみ許可（ポート番号は各自変更してください）
acl ssl_ports port 22
acl CONNECT method CONNECT
http_access deny CONNECT !ssl_ports

# port443でlisten
https_port 443 cert=/etc/squid/certs/server.crt key=/etc/squid/certs/server.key

# これを書かないとsquidの再起動に30秒かかる
shutdown_lifetime 0
```

最後にsquidを起動＆有効化

```
$ sudo systemctl start squid
$ sudo systemctl enable squid
```

# クライアント側
## tun deviceを作成
サーバーと同様`/etc/systemd/network`に２つのファイルを作成します。
```$ sudo vim /etc/systemd/network/10-tun1.netdev```

```10-tun1.netdev
[NetDev]
Name=tun1
Kind=tun
```

```$ sudo vim /etc/systemd/network/10-tun1.network```

```10-tun1.network
[Match]
Name=tun1

[Network]
Address=10.0.0.2/24
Gateway=10.0.0.1
Address=fc00::2/64
Gateway=fc00::1
DNS=1.1.1.1
```

## proxytunnelのインストールとSSHの設定
proxytunnelを使うことで、HTTPSプロキシ経由でSSHできるようになります。
インストールしましょう。
```$ sudo pacman -S proxytunenl```

次に`~/.ssh/config`に以下を追記します。

```
Host vpn
   HostName [サーバーのIPアドレス]
   Port [ポート番号]
   User [ユーザー名]
   IdentityFIle ~/.ssh/[秘密鍵]
   ProxyCommand proxytunnel -E -z -p [サーバーのIPアドレス] -P [squidユーザー名]:[squidパスワード] -d %h:%p
   Tunnel point-to-point
   TunnelDevice 1:1
```

proxytunnelのオプションとして-EをつけることでSSL暗号化が有効になります。-zをつけるとオレオレ証明書に文句を言わなくなります。
`TunnelDevice`の`1:1`というのは、ローカルのtun1とリモートのtun1を接続するという意味です。

これで```$ ssh vpn &```と打つだけでVPNに接続できるようになっているはず。
pingして確認してみます。

```
$ ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=12.1 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=13.2 ms
^C
--- 10.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 12.108/12.629/13.151/0.521 ms

$ ping fc00::1
PING fc00::1(fc00::1) 56 data bytes
64 bytes from fc00::1: icmp_seq=1 ttl=64 time=12.3 ms
64 bytes from fc00::1: icmp_seq=2 ttl=64 time=11.9 ms
^C
--- fc00::1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 11.876/12.103/12.331/0.227 ms
```
疎通確認ができました（・∀・）

切断は以下のコマンドでできます。
bash, zshの場合
`$ kill $(ps x | grep ssh | grep vpn | awk '{ print $1 }')`
fishの場合
`$ kill (ps x | grep ssh | grep vpn | awk '{ print $1 }')`


##ルーティングの設定
VPNに接続することはできましたが、現段階ではパケットがVPNに流れてくれません。すべての外向きの通信（ただしサーバーへの通信以外）をVPN経由にしたいのでルーティングテーブルを弄ってやる必要があります。

あらかじめ`ip route`を実行して現在のデフォルトゲートウェイを調べておきましょう。
VPNに接続した上で、以下のコマンドを実行します。

```
sudo ip add [サーバーのIP] via [現在のデフォルトゲートウェイ]
sudo ip del default via [現在のデフォルトゲートウェイ]
sudo ip -6 route del default nexthop via fc00::1 dev tun1
sudo ip -6 route append default nexthop via fc00::1 dev tun1 weight 256
```

`$ curl v4.ident.me` `$ curl v6.ident.me`を実行してそれぞれサーバーのグローバルIPアドレスが表示されればOKです！！

VPNを切断したあとはsystemd-networkdを再起動するとルーティングテーブルが元通りになります。
`$ sudo systemctl restart systemd-networkd`

## 接続・切断の自動化
毎回ルーティングの設定をするのは面倒くさいので、シェルスクリプトで自動化してしまいましょう。

```connect-vpn.sh
#!/bin/bash

ssh vpn &

# エラーが起きたらここで手動(Ctrl+C)で中断（他に良い方法が思いつかない...）
sleep 2
echo -n "Press Enter to continue..."
read

# IPv4
sudo ip add [サーバーのIP] via [現在のデフォルトゲートウェイ]
sudo ip del default via [現在のデフォルトゲートウェイ]
# IPv6
sudo ip -6 route del default nexthop via fc00::1 dev tun1
sudo ip -6 route append default nexthop via fc00::1 dev tun1 weight 256
```

```disconnect-vpn.sh
#!/bin/bash

kill $(ps x | grep ssh | grep vpn | awk '{ print $1 }')
sudo systemctl restart systemd-networkd
```

# 最後に
お疲れさまです。これでパケットキャプチャでも普通のHTTPS通信にしか見えないVPNができました。nDPIで解析してみた結果も問題なさそうだったので、厳しいファイアウォールも通過できると思われます。もう一度言っておきますが自己責任＆悪用厳禁ですよろしくお願いします。
何かあればコメントお願いします。