VPNに常時接続されたプロキシサーバを立てよう（openvpn with usocksd on ubuntu server 18.04）


## はじめに

PCからスマートフォンまで、我々が日常で接する機会のあるコンピュータの多くは、標準でVPNに対応しています。しかし、標準で提供されるVPNクライアント機能は、コンピュータが行う通信すべてをVPN経由にすることを前提としています。あるアプリケーションはVPNで保護したいけれども、その他のアプリケーションについてはそうではないというようなケースでは、特定のアプリケーションにだけ、VPNを経由することが保証されたプロキシサーバを設定することで、最適な環境を構築できます。

この記事ではOpenVPNプロトコルでVPNサービスに接続し、SOCKS経由で他のマシンにセキュアな回線を提供するプロシキサーバを構築します。

## 必要なものは？
- インターネット回線
- サーバにするPC 1台（仮想PCでよい）
- OpenVPNサーバへの接続権（この記事の用途が各種規約に反しないこと）

## どうやって使うの？
オススメはFirefoxです。設定画面からプロキシを簡単に設定でき、この設定は、OSや、他のプロファイルから独立しています。複数のFirefoxの特定のプロファイルだけにプロキシを設定することができ、使い勝手が大変よいです。

<img width="427" alt="SnapCrab_NoName_2018-8-15_7-21-58_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/47014/953cfdef-eec7-c4e5-a0a3-f418234ae4e2.png">

## 準備
### テスト環境
- 仮想PC 1コア メモリ1GB ディスク20GB
- Ubuntu 18.04 LTS

イメージファイルは`ubuntu-18.04.1-live-server-amd64.iso`です。

デフォルトでファイアウォールは設定されていないこととします。次のコマンドで確認できます。

```bash 
sudo ufw status
```

```
Status: inactive
```

### ssh

必要があればインストールしてください。

```bash
sudo apt install ssh
```

### aptのuniverseレポジトリを追加
```bash
sudo cat /etc/apt/sources.list
```
```
deb http://archive.ubuntu.com/ubuntu bionic main
deb http://archive.ubuntu.com/ubuntu bionic-security main
deb http://archive.ubuntu.com/ubuntu bionic-updates main
```

universeレポジトリが設定されていなければ追加します。

```bash
cat << "EOF" | sudo tee -a /etc/apt/sources.list
deb http://archive.ubuntu.com/ubuntu bionic universe
deb http://archive.ubuntu.com/ubuntu bionic-security universe
deb http://archive.ubuntu.com/ubuntu bionic-updates universe
EOF
```

### update/upgrade

```bash
sudo apt update
sudo apt upgrade
sudo apt dist-upgrade
```

### DNS

```bash 
cat << "EOF" | sudo tee -a /etc/systemd/resolved.conf
DNS=8.8.8.8 8.8.4.4
EOF
```

```bash
sudo systemctl restart systemd-resolved
```

```bash
sudo systemd-resolve --status
```

### NTP

```bash
sudo apt install ntp
```

### 時刻の設定

タイムゾーンが東京でない場合は以下のコマンドで変更できます。

```bash
sudo rm -rf /etc/localtime
sudo ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

### IPv6の無効化

```bash 
cat << "EOF" | sudo tee /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
```

```bash 
sudo sysctl -p
```

## OpenVPN

```bash 
sudo apt install openvpn
```

### 証明書、鍵、設定ファイルの用意

証明書、鍵、設定ファイルは、サービス側が提供するものをそのまま使います。ただし、下記の手順の中で、opvnファイルはconfファイルにリネームします。また、証明書、鍵ファイルへのパスについても変更の必要があるかもしれません。

```bash 
mkdir ~/openvpn
```

上のディレクトリにSFTPなどで転送します。
また、ユーザー名とパスワードを記したsecretファイルも用意します。

```bash 
cat << "EOF" | sudo tee ~/openvpn/secret
YOUR_USERID
YOUR_PASSWORD
EOF
```

```bash
ls ~/openvpn
```

```
ca.crt client.crt client.key client.opvn secret
```


`client.opvn`だけconfとして`/etc/openvpn`に、他のファイルは`/etc/openvpn/client`に移動します。

```bash
chmod 600 ~/openvpn/*
sudo mv ~/openvpn/client.opvn /etc/openvpn/client.conf
sudo mv ~/openvpn/* /etc/openvpn/client
rm -rf ~/openvpn
```

### 設定ファイルの確認

ここで、`client.conf`の確認を行います。

```bash 
sudo cat /etc/openvpn/client.conf
```

以下は一例です。

```
client
dev tun
proto udp
remote xxx.xxx.xxx.xxx 1194
remote-cert-tls server
resolv-retry infinite
redirect-gateway def1
nobind
tun-mtu 1500
tun-mtu-extra 32
mssfix 1450
persist-key
persist-tun
ping 15
ping-restart 0
ping-timer-rem
reneg-sec 0
explicit-exit-notify 3

comp-lzo
verb 3
pull
fast-io
cipher AES-256-CBC
auth SHA512

auth-user-pass client/secret
ca client/ca.crt
cert client/client.crt
key client/client.key
```

`auth-user-pass`、`ca`、`cert`、`key`の値について注意してください。デフォルトで、`/usr/sbin/openvpn`の起動時に`--cd /etc/openvpn`が引数として渡されているため、各ファイルのパスは、デーモンからみた`cd`が`/etc/openvpn`になります。この値は、あるいは絶対パスで書いてもいいでしょう。

`nobind`は動的にポートを確保するという設定を意味します。

`redirect-gateway`については[OpenVPNが自動でルーティングを設定してくれない](#OpenVPNが自動でルーティングを設定してくれない)を参照してください。

### テスト

OpenVPNクライアントを開始し、うまく接続できているか確認します。
まず、次のコマンドで現在のipはインターネットの外側からどう見えているかチェックしておきましょう。

```bash
curl httpbin.org/ip
```
```
{
  "origin": "143.212.233.219"
}
```

次にOpenVPNクライアントを起動します。

```bash
sudo systemctl start openvpn@client
sudo systemctl status openvpn@client
```

```
Aug 15 09:20:35 vpn-proxy-00 ovpn-client[4082]: TUN/TAP device tun0 opened
Aug 15 09:20:35 vpn-proxy-00 ovpn-client[4082]: TUN/TAP TX queue length set to 100
Aug 15 09:20:35 vpn-proxy-00 ovpn-client[4082]: do_ifconfig, tt->did_ifconfig_ipv6_setup=0
Aug 15 09:20:35 vpn-proxy-00 ovpn-client[4082]: /sbin/ip link set dev tun0 up mtu 1500
Aug 15 09:20:35 vpn-proxy-00 ovpn-client[4082]: /sbin/ip addr add dev tun0 local 10.250.203.130 peer 10.250.203.129
Aug 15 09:20:40 vpn-proxy-00 ovpn-client[4082]: /sbin/ip route add 0.0.0.0/1 via 10.250.203.129
Aug 15 09:20:40 vpn-proxy-00 ovpn-client[4082]: /sbin/ip route add 128.0.0.0/1 via 10.250.203.129
Aug 15 09:20:40 vpn-proxy-00 ovpn-client[4082]: /sbin/ip route add 10.250.200.1/32 via 10.250.203.129
Aug 15 09:20:40 vpn-proxy-00 ovpn-client[4082]: Initialization Sequence Completed
```

`ip`コマンドがいくつか実行された後、`Initialization Sequence Completed`と表示されていれば正しく接続されています。

さて、外側から見えるipは変わっているでしょうか。

```bash
curl httpbin.org/ip
```
```
{
  "origin": "24.127.96.129"
}
```

問題なさそうならサービスを有効にしましょう。

```bash
sudo systemctl enable openvpn@client
```

## usocksd（SOCKSサーバ）

usocksdというSOCKSサーバを使用します。`BIND`と`UDP associate`命令には対応していませんが、設定がある程度柔軟に行なえて、パフォーマンスがよいのでオススメです。

[cybozu\-go/usocksd: SOCKS4/5 server library and command in Go](https://github.com/cybozu-go/usocksd)

まず、サービスを実行するユーザーを作ります。

```bash 
sudo useradd usocks
```

必須というわけではありませんが、iptablesで`-m owner --uid-owner usocks`のようにユーザー名を指定できるので、特別なユーザー名を使うように設定しておくと便利なケースがあります。

##### インストール

golangで書かれているので`go get`した後、`go build`します。

```bash
sudo apt install golang
```

```bash
go get -u github.com/cybozu-go/usocksd
go build -a -o usocksd go/src/github.com/cybozu-go/usocksd/cmd/usocksd/main.go
```

うまくビルドできたら`/usr/bin`へ移動します。

```bash
chmod +x usocksd
sudo mv ./usocksd /usr/bin
```

##### 設定ファイルを用意する

SOCKSのデフォルトポートは1080です。
ここではコメントアウトしていますが、設定ファイルの後半で`allow_sites`、`deny_sites`、`deny_ports`、`addresses`などを設定できます。

```bash 
cat << "EOF" | sudo tee /etc/usocksd.toml
[log]
#filename = "/var/log/usocksd/usocks.log"         # default to stderr.
level = "error"                     # critical, error, warning, info, debug
format = "plain"                   # plain, logfmt, json

[incoming]
port = 1080
addresses = ["0.0.0.0"]          # List of listening IP addresses
allow_from = ["0.0.0.0/0"]        # CIDR network or IP address

#[outgoing]
#allow_sites = [                    # List of FQDN to be granted.
#    "www.amazon.com",              # exact match
#    ".google.com",                 # subdomain match
#]
#deny_sites = [                     # List of FQDN to be denied.
#    ".2ch.net",                    # subdomain match
#    "bad.google.com",              # deny a domain of *.google.com
#    "",                            # "" matches non-FQDN (IP) requests.
#]
#deny_ports = [22, 25]              # Black list of outbound ports
#addresses = ["12.34.56.78"]        # List of source IP addresses
#dnsbl_domain = "some.dnsbl.org"    # to exclude black listed IP addresses
EOF
```

##### systemd用のファイルを用意する

```bash
cat << "EOF" | sudo tee /etc/systemd/system/usocksd.service
[Unit]
Description = usocksd Micro SOCKS server
After=openvpn@client.service

[Service]
Type = simple
StandardOutput = syslog
StandardError = syslog
SyslogIdentifier = usocksd
ExecStart = /usr/bin/usocksd
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -INT $MAINPID
KillMode = process
User = usocks
Group = usocks
Restart=always

[Install]
WantedBy = multi-user.target
EOF
```

`After=openvpn@client.service`でOpenVPNクライアントより後に起動することを明示しています。

##### テスト

```bash
sudo systemctl daemon-reload
sudo systemctl restart usocksd
sudo systemctl status usocksd
```

curlコマンドでSOCKSプロキシの動作を確認できます。

```bash
curl --socks5 localhost:1080  httpbin.org/ip
```

あるいは、ローカルネットワーク上の別のマシンから接続してみてもいいでしょう。

うまく動いたらサービスを有効化しましょう。

```bash
sudo systemctl enable usocksd
```

## cockpit（管理用Webインターフェイス）

cockpitは軽量なシステム管理用のWebインターフェイスです。簡単に負荷を確認したり、問題のあるサービスを再起動したり、シェルを叩いたりと、今回のようなケースには十分な機能を備えています。

### インストール

```bash
sudo apt install cockpit
```

デフォルトのポートは9090です。変更する場合は次のURLを参考にしてください。
[TCP Port and Address](https://cockpit-project.org/guide/latest/listen.html)

### スクリーンショット

![192.168.0.3_9090_system(iPad).png](https://qiita-image-store.s3.amazonaws.com/0/47014/9a334ee9-5f37-02da-5cd3-9f53a35bb5b5.png)
![192.168.0.3_9090_system(iPad) (1).png](https://qiita-image-store.s3.amazonaws.com/0/47014/bf349785-d628-996f-15f5-6ff3f3604f45.png)
![192.168.0.3_9090_system(iPad) (2).png](https://qiita-image-store.s3.amazonaws.com/0/47014/be892b9e-6eac-6d31-2fa6-02fcbf8bb47b.png)
![192.168.0.3_9090_system(iPad) (3).png](https://qiita-image-store.s3.amazonaws.com/0/47014/73985797-70d1-ccb2-0abd-47c1097caf03.png)
![192.168.0.3_9090_system(iPad) (4).png](https://qiita-image-store.s3.amazonaws.com/0/47014/0044e084-c7ea-67fc-6f7f-66036d743b61.png)
![192.168.0.3_9090_system(iPad) (5).png](https://qiita-image-store.s3.amazonaws.com/0/47014/fff4817b-a1b0-7532-7278-78b7a3660c9e.png)


## ufw (ファイアウォール)

ファイアウォールを適切に設定し、OpenVPNクライアントのみがインターネットに接続できるようにすることで、SOCKSサーバへのリクエストが常にVPNを経由することを保証できます。

以下のようにルールの設定をしていきます。
ここで、ネットワークインターフェイスを`eth0`、ローカルネットワークは`192.168.1.0/24`とします。OpenVPNサーバのデフォルトポートは`1194`ですが、`443`などが使用されている場合もあります。適宜変更してください。

```bash
# デフォルトでin/outを禁止
sudo ufw default deny incoming
sudo ufw default deny outgoing

# OpenVPNのインターフェイスのin/outを許可
sudo ufw allow in on tun0
sudo ufw allow out on tun0

# ローカルネットワークについてin/outを許可
sudo ufw allow in on eth0 from 192.168.1.0/24
sudo ufw allow out on eth0 to 192.168.1.0/24

# OpenVPNサーバのポートへのoutを許可
sudo ufw allow out on eth0 to any port 1194

# SSHポートへの接続にリミットを設定
sudo ufw limit ssh

# NTPポートへのin/outを許可
sudo ufw allow ntp

# DNSポートへのin/outを許可
sudo ufw allow in from any port 53
sudo ufw allow out to any port 53
```

ファイアウォールを有効にします。

```bash
sudo ufw enable
```

```bash
sudo ufw status
```

### OpenVPNが落ちているときにクライアントがインターネットに直接通信できてしまう穴を塞ぐ

```bash
# OpenVPNサーバのポートへのoutを許可
sudo ufw allow out on eth0 to any port 1194
```

上記の設定のこの箇所について、ヤバみを感じませんか？
そう、OpenVPNクライアントが落ちているときに、usocksdもこのポートに対して直接通信することができてしまいます。当然、SOCKS経由で通信してるクライアントもインターネットに直接…。

適宜変更してくださいと書きましたが、このポートが443であったりするとヤバヤバです。ご存知の通り、Webサービスもこのポートをよく使っているのです。

```bash
# OpenVPNサーバのポートへのoutを許可…しちゃう？
sudo ufw allow out on eth0 to any port 443
```

HTTPSで使用するのはTCPプロトコルなので、UDPプロトコルを指定しておけばいいようにも思えますが、そもそもインターネットに直接接続できているのがよくありません。
この問題に対処する方法はいろいろ考えられますが、ここは伏線を生かしましょう。覚えていますか？ usocksdデーモンの所有者がusocksユーザーであることを。

```bash 
ps aux | grep usocks
```
```
usocks	   1367  0.0  0.6 385204  5296 ?	Ssl  03:31   0:00 /usr/bin/usocksd
usocks	   1387  0.4  7.1 609412 56132 ?	Sl   03:31   0:43 /usr/bin/usocksd
```

iptablesではuidを使ったフィルタリングも可能なので、素直に書くと次にようになります。443ポートへのoutについて、uidがusocksで**ない**ときに許可します。

```bash 
iptables -A ufw-before-output -p udp -m owner ! --uid-owner usocks --dport 443 -j ACCEPT
iptables -A ufw-before-output -p tcp -m owner ! --uid-owner usocks --dport 443 -j ACCEPT
```

今回はファイアウォールの設定にufwを使ったので、ufwのルールにこれを組み込む必要があります。

```bash 
sudo vi /etc/ufw/before.rules
```

一番最後の`COMMIT`の手前に以下の2行を追加します。

```
-A ufw-before-output -p udp -m owner ! --uid-owner usocks --dport 443 -j ACCEPT
-A ufw-before-output -p tcp -m owner ! --uid-owner usocks --dport 443 -j ACCEPT
```

ファイルの最後の10行は次のようになります。

```
# allow MULTICAST UPnP for service discovery (be sure the MULTICAST line above
# is uncommented)
-A ufw-before-input -p udp -d 239.255.255.250 --dport 1900 -j ACCEPT

# allow outgoing OpenVPN traffic
-A ufw-before-output -p udp -m owner ! --uid-owner usocks --dport 443 -j ACCEPT
-A ufw-before-output -p tcp -m owner ! --uid-owner usocks --dport 443 -j ACCEPT

# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT
```

ファイアウォールを更新しましょう。

```bash 
sudo ufw reload
```

うまくいきましたか？
なにか問題があればこのときに表示されます。

なにか問題があったとしても、慌てず焦らず、このチャプタの最初から、ただし次の魔法のおまじないの後に続けて、もう一度やり直してください。

```bash
sudo ufw reset
```

### 参考

[ファイアウォールの設定](https://wiki.archlinux.jp/index.php/OpenVPN#.E3.83.95.E3.82.A1.E3.82.A4.E3.82.A2.E3.82.A6.E3.82.A9.E3.83.BC.E3.83.AB.E3.81.AE.E8.A8.AD.E5.AE.9A)
[VPN が落ちた時に漏洩を防止](https://wiki.archlinux.jp/index.php/OpenVPN#VPN_.E3.81.8C.E8.90.BD.E3.81.A1.E3.81.9F.E6.99.82.E3.81.AB.E6.BC.8F.E6.B4.A9.E3.82.92.E9.98.B2.E6.AD.A2)

## FAQ

### OpenVPNが自動でルーティングを設定してくれない

`client.conf`に`redirect-gateway`が含まれているかチェックしてください。OpenVPNサービスのログで、接続開始時にサーバ側からのこのパラメータの強制的なpushが行われていないか、併せて確認してください。指定がない場合、自動的なルーティングの設定が行われません。

設定が適切に行われていない場合、`client.conf`に以下を追加してください。

```
redirect-gateway def1
```

### OpenVPNが頻繁に落ちる/再起動する

`client.conf`について、`inactive`があるとデーモンが落ちます。`ping-exit`も同様です。後者は`ping-restart`に変更したほうがよさそうです。

そして、設定に問題がない場合、何らかの原因でデーモンが死んでしまったときも、常に再起動するようにしておいたほうがよいでしょう。当該サービスのsystemd設定ファイル内、`[Service]`セクションに`Restart=always`を追加する必要がありますが、systemdは標準で`/lib/systemd/system/openvpn-client@.service`を読み込んでいます。これを`/etc/systemd/system/`にコピーすればsystemdはこちらを優先度が高いものとして読み込みます。後者を編集するのが正式な手順になります。

```bash 
sudo cp /lib/systemd/system/openvpn-client@.service /etc/systemd/system/openvpn-client@.service
sudo vi /etc/systemd/system/openvpn-client@.service
```

`systemd-delta`で変更を確認できます。

```bash
sudo systemd-delta
```

```
[OVERRIDDEN] /etc/systemd/system/openvpn-client@.service → /lib/systemd/system/openvpn-client@.service

--- /lib/systemd/system/openvpn-client@.service 2018-02-11 05:27:56.000000000 +0900
+++ /etc/systemd/system/openvpn-client@.service 2018-08-15 00:45:42.245019600 +0900
@@ -18,6 +18,7 @@
 ProtectSystem=true
 ProtectHome=true
 KillMode=process
+Restart=always
 
 [Install]
 WantedBy=multi-user.target
```

### 負荷を掛けるとSOCKSサーバの応答がなくなる

ファイルディスクリプタ数制限に引っかかってるのかもしれません。この値は次のように設定できます。

```bash 
sudo mkdir /etc/systemd/system/openvpn-client@.service.d
cat << "EOF" | sudo tee /etc/systemd/system/openvpn-client@.service.d/override.conf
[Service]
LimitNOFILE=81915
EOF
```

```bash 
sudo mkdir /etc/systemd/system/usocksd.service.d
cat << "EOF" | sudo tee /etc/systemd/system/usocksd.service.d/override.conf
[Service]
LimitNOFILE=81915
EOF
```

```bash
sudo systemd-delta
```

```
[EXTENDED]   /etc/systemd/system/openvpn-client@.service → /etc/systemd/system/openvpn-client@.service.d/override.c
[EXTENDED]   /etc/systemd/system/usocksd.service → /etc/systemd/system/usocksd.service.d/override.conf
```

また、次のようなパラメータの設定も定番の対処方法です。

```bash 
cat << "EOF" | sudo tee /etc/sysctl.conf
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_keepalive_probes = 2
net.ipv4.tcp_keepalive_intvl = 10

net.core.somaxconn = 8192
net.core.netdev_max_backlog = 8192
net.ipv4.tcp_max_syn_backlog = 8192

net.ipv4.ip_local_port_range = 2048 65535

net.ipv4.tcp_tw_reuse = 1

net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
```

```bash
sudo sysctl -p
```

上記の値は一例です。負荷や回線環境、OpenVPNサーバの設定などに合わせて調節してください。

## おわりに

毎年、夏休み時間を作ってなんやかんやわちゃわちゃしているのですが、今年はあまり長くとれずこの記事が唯一の成果となりました。
ｿｯｸｽｿｯｸｽ！