さくらのVPS上にVPNサーバーを建てる

**さくらのVPS**上に`OpenVPN`サーバーを建てた奮闘記です。
`OpenSSL 3.1.0`を使ってビルドし、もちろん`IPv6`にも対応します。
加えて`OpenVPN 2.6.0`からサポートされた高速化機能である`DCO（Data Channel Offload）`を有効にします。

### きっかけ
僕は大のアニメ好きで`dアニメストア`を心から愛用しています。
電車や駅、ホテルなどの公共Wi-Fiで`dアニメストア`に接続することも多々ありますが、セキュリティ的に`VPN`を通して接続したいと思っていました。
しかし、`dアニメストア`は海外からの接続を拒否しており、一般のVPNや`AWS`に建てていた`OpenVPN`サーバーからでは接続できませんでした。
VPNで日本のサーバーを選択したり、AWSで東京リージョンを通して接続しても海外からのアクセスと判定されてしまいます。
そこで`さくらのVPS`ならば日本からのアクセスとして認定していただけるのではと思い建設を決意した次第です。
一応結論を先に申し上げると、無事に`さくらのVPS`上の`OpenVPN`を通して`dアニメストア`にアクセスできました。

# 環境

```
さくらのVPS 大阪
512MBプラン
Rockey LINUX 5.14.0
```

#### 必要なもの

入ってるものも多いと思いますが一応。

```
sudo yum install wget git tar make gcc perl libnl3-devel libcap-ng-devel systemd-devel pam-devel
```

# OpenSSL

`OpenSSL 3.1.0`が正式にリリースされましたので折角ですから導入します。
標準の`OpenSSL`でよい人は読み飛ばしてください。

```bash
wget https://www.openssl.org/source/openssl-3.1.0.tar.gz # 下記リンクを参照して最新版を入れてください
tar -xf openssl-3.1.0.tar.gz
cd openssl-3.1.0
./config shared --prefix=/opt/openssl # お好きなディレクトリにどうぞ
make
make test # 結構時間かかります
          # PASS とでればOK
sudo make install
```

[OpenSSL公式](https://www.openssl.org/source/)に最新版へのリンクがありますので必ず最新のものを引っ張ってきてください。

**`--prefix`を付けないと現行版と衝突します。
ライブラリを置き換えちゃったりすると`sudo`やら`ssh`が使えなくなって死するのでお気を付けください。**（経験者）

#### 使えるように

上記手順では`openssl version`しても既存のものが出てきます。
パッケージ管理ツールではない管理をするのは望ましくないので問題ないと思いますが、新しいものを常用するには以下の手順を踏んでください。

```bash
cd
nano .bashrc
```

以下を追記。

```bash:.bashrc
alias openssl="/opt/openssl/bin/openssl"
```

環境変数を設定します。

```bash
cd
nano .bash_profile
```

以下を追記。

```bash:.bash_profile
export LD_LIBRARY_PATH="/opt/openssl/lib64"
```

環境によっては`/opt/openssl/lib`に入っている場合があります。
エラーになったら確認してみてください。
その場合は以降のコマンド全てを読み替えてください。

# OpenVPN

それでは本命の`OpenVPN`を導入していきます。

```bash
wget https://swupdate.openvpn.org/community/releases/openvpn-2.6.3.tar.gz # 下記リンクを参照
tar -xf openvpn-2.6.3.tar.gz
cd openvpn-2.6.3
./configure --enable-systemd --disable-lz4 --disable-lzo PKG_CONFIG_PATH=/opt/openssl/lib64/pkgconfig # 環境によっては"/opt/openssl/lib/pkgconfig"
make
make check # "All * tests passed" とでればOK
sudo make install
```

[OpenVPN公式](https://openvpn.net/community-downloads/)を参照して最新のものを導入してください。
スクロールしていって`Source archive file`とあるものです。

何もしないと`pkg-config`がデフォルトの`OpenSSL`を探しに行くのでビルドした方のパスを渡して上書きします。
ここに行きつくまで`./configure`に`CFLAG`やら`LDFLAGS`やら渡してごにょごにょしていましたが、これで一発です。
**`pkg-config`に乾杯。**

`openvpn --version`して正しく表示されれば無事インストール完了です。

# easyrsa

VPN接続用の認証局を作ります。

```bash
cd /opt # お好きなディレクトリにどうぞ
sudo git clone https://github.com/OpenVPN/easy-rsa.git
cd easy-rsa/easyrsa3
sudo ./easyrsa init-pki
sudo nano pki/vars
```

`Easy-RSA`の設定である`vars`ファイルを編集します。
僕の設定で変更している変数だけ書き出します。
該当する箇所を探してコメントアウトを外して書き換えてください。
私的に好みな強めセキュア設定になっていますのでお好みでどうぞ。

```bash:pki/vars
# "openssl 3.1.0"のインストール先を指定します
set_var EASYRSA_OPENSSL       "/opt/openssl/bin/openssl"

# 安全で速くてつよつよのツイステッドエドワーズ曲線を使います
set_var EASYRSA_ALGO          ed
set_var EASYRSA_CURVE         ed25519

# 暗号の桁数を上げます
set_var EASYRSA_DIGEST        "sha512"


##
# 以下はちょくちょく見かける誤った情報の訂正です
##

# 上のEASYRSA_ALGOがrsaの場合以外は鍵長の設定は不要で変更しても無視されます
# 安全性的にはデフォルトで十分で、変更する場合も4096が多くのデバイスで使用できる現実的な最長です
#set_var EASYRSA_KEY_SIZE      2048

# EASYRSA_DNを"cn_only"に設定している場合（デフォルト）、以下の値は無視されるので設定不要です
#set_var EASYRSA_REQ_COUNTRY  "US"
#set_var EASYRSA_REQ_PROVINCE "California"
#set_var EASYRSA_REQ_CITY     "San Francisco"
#set_var EASYRSA_REQ_ORG      "Copyleft Certificate Co"
#set_var EASYRSA_REQ_EMAIL    "me@example.net"
#set_var EASYRSA_REQ_OU       "My Organizational Unit"
```

編集が完了したら作業を続行します。

```bash
# [1] 認証局を作成します
sudo env LD_LIBRARY_PATH=/opt/openssl/lib64 ./easyrsa build-ca
# パスワードを求められるので設定します

# [2] サーバーの証明書と鍵を作ります
sudo env LD_LIBRARY_PATH=/opt/openssl/lib64 ./easyrsa build-server-full {server_name}
# サーバー用のパスワードと、先ほど設定した認証局のパスワードを入力します

# [3] クライアントの証明書と鍵を作ります
sudo env LD_LIBRARY_PATH=/opt/openssl/lib64 ./easyrsa build-client-full {client_name}
# サーバーと同様の作業をします
```

#### 備考

デフォルトではない`OpenSSL`を使う場合、何もしないとライブラリが衝突してエラーが出ます。
`easyrsa`インストール後に設定した環境変数は`sudo`には引き継がれないので、エラー対策として`env LD_LIBRARY_PATH=/opt/openssl/lib64`で環境変数を設定しながら`easyrsa`を実行します。

ちなみに、`init-pki`のときは`vars`ファイルの内容が反映されずデフォルトの`openssl`コマンドの方が使われるようですので、環境変数の設定は不要です。

# 設定

それでは`OpenVPN`を設定していきます。

```bash
# 設定ファイルを補完するディレクトリを作成します
sudo mkdir -p /etc/openvpn/server
# 移動します
cd /etc/openvpn/server
# "ta.key"を生成します
sudo env LD_LIBRARY_PATH=/opt/openssl/lib64 openvpn --genkey secret ta.key
# "DH鍵"を生成します
sudo env LD_LIBRARY_PATH=/opt/openssl/lib64 /opt/openssl/bin/openssl dhparam -out dh2048.pem 2048
# サンプルファイルをコピーします
sudo cp /opt/openvpn-2.6.3/sample/sample-config-files/server.conf ./server.conf.example
# サーバーファイルを編集します
sudo nano server.conf
```

コピーしたサンプルを参考に、設定ファイルを編集します。
私の使用している設定を書き出します。

```bash:server.conf
port 1194
proto udp6
dev tun
ca /opt/easy-rsa/easyrsa3/pki/ca.crt
cert /opt/easy-rsa/easyrsa3/pki/issued/server.crt
key /opt/easy-rsa/easyrsa3/pki/private/server.key
dh dh2048.pem
topology subnet
server 10.8.0.0 255.255.255.0
server-ipv6 fdxx:xxxx:xxxx::/64 # 下記参照
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1 bypass-dhcp ipv6 block-local"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
push "dhcp-option DNS 2001:4860:4860::8888"
push "dhcp-option DNS 2001:4860:4860::8844"
keepalive 10 120
tls-auth ta.key 0
cipher AES-256-GCM
user nobody
group nobody
persist-key
persist-tun
status openvpn-status.log
log-append  /var/log/openvpn.log
verb 3
explicit-exit-notify 1
auth SHA512
askpass pass.txt # パスワードを記述したファイル
```

`OpenVPN`は起動時にサーバー用証明書のパスワードを聞かれます。
そのままだと`systemd`で自動起動する際に不便ですので回避する設定をします。

```bash
# "build-server-full"の時に入力したパスワードを記載したファイルを作成します
sudo nano /etc/openvpn/server/pass.txt

# 一応root以外が読めないようにしておきます
sudo chmod 600 /etc/openvpn/server/pass.txt
```

#### server-ipv6

このディレクティブに指定するユニークローカルアドレスは下記サイトで生成します。
https://www.jdoodle.com/a/20tR

`MACアドレス`を入力すると`ULA Prefix`というのが生成されるので、生成されたプレフィックスから好きなサブネットを選びます。
基本的には無難に`First Subnet`に出てくる一番最初のサブネットでよいと思います。

##### 備考

`fd00::/8`のアドレスブロックに割り当てられるアドレスは`ipv4`ではプライベートアドレスに相当します。
基本的にはユーザーが自由に設定できるものになりますが、プレフィックスが重複しないように`MACアドレス`と乱数から生成する方法が`RFC`で定められています。
先のウェブページはその方法に則ってプレフィックスを生成してくれます。

生成されたプレフィックスから選んだサブネットが、VPNからクライアントに割り当てられるアドレスのサブネットになります。

# 転送

クライアントに鍵等を転送します。
私はWinSCPでコピーしてしまいますが、皆さんお好きなようにクライアント用のカギと証明書をコピーしてください。
クライアントで必要なものは以下の通りです。

|ファイル名|場所|
|--|--|
|ca.crt|{path_to_easyrsa3}/pki/ca.crt|
|ta.key|出力した場所（/etc/openvpn/server/ta.key）|
|{client_name}.crt|{path_to_easyrsa3}/pki/issued/{client_name}.crt|
|{client_name}.key|{path_to_easyrsa3}/pki/private/{client_name}.key|


# systemd

`systemd`の設定をします。
まずはユニットファイルをコピーします。

```bash
# ダウンロードしたユニットファイルをコピーします
sudo cp /opt/openvpn-2.6.3/distro/systemd/openvpn-server@.service /etc/systemd/system/

# OpenSSLライブラリの変更を反映するよう書き換えます
sudo nano /etc/systemd/system/openvpn-server@.service
```

ユニットファイルの`ExecStart`部分を書き換えます。
実行コマンドの先頭に`/bin/env LD_LIBRARY_PATH=/opt/openssl/lib64`を追記します。

```bash:openvpn-server@.service
#         |=>                追記部分                <=|
ExecStart=/bin/env LD_LIBRARY_PATH=/opt/openssl/lib64 /usr/local/sbin/openvpn （以後同）
```

書き換えが完了したら作業を続行します。

```
# 反映させます
sudo systemctl daemon-reload
# 起動します
sudo systemctl start openvpn-server@server
```

もしエラーが出たら`cat /var/log/openvpn.log`でそれらしいメッセージを探して解決します。

# DCOの有効化

ついでですので、最近サポートされた高速化機能の`DCO`を有効にするためカーネルモジュール`ovpn-dco`を導入します。
カーネルの方でパケットの処理をするので通信速度が向上するそうです。

```bash
sudo yum install kernel-devel
git clone https://github.com/OpenVPN/ovpn-dco.git
cd ovpn-dco
make
#make tests # 以下参照: 現状では失敗します
sudo make install
sudo systemctl restart openvpn-server@server

# 無事に反映されているか確認します
sudo cat /var/log/openvpn.log | grep "DCO version"
# "DCO version: 0.2.20230426"とバージョンが表示されれば成功です
```

#### テスト

テストの実行に少なくとも以下のものが必要です。
が、これを入れても失敗します…。
~~試行錯誤しましたが解決できませんでした。~~
**GitHubで開発者の方からテストコードはメンテしていないと回答がありました。**
**現在は動かないだろうとのことなのでこれで問題ないのでしょう。**
今後も修正される予定はないそうですし、ちゃんと起動できて反映もされているのでよしとします。

```bash
#sudo yum install python3-jinja2 python-jsonschema
#git clone https://github.com/Mbed-TLS/mbedtls.git
#cd mbedtls
#make
#make check
#sudo make install
```

# ファイアウォール

前提として`firewalld`で管理しているものとします。
さくらのパケットフィルターは有効無効どっちでも構いません。
余談ですが僕はあれにほとんどセキュリティ上の機能を期待していません。
`TCP/UDP 32768-65535番ポート、NTP UDP 123番ポート、IPv6通信、ICMP、フラグメントパケットは全て許可されます`ってやばくないですか？？
あーしは専門家じゃないのでなんも言えませんが、この辺の機能は`AWS`しか勝たんです。（多分）
もしさくらのパケットフィルタを有効にしている場合は`1194`を**許可**にしておいてください。

それでは`firewalld`の作業をしていきます。

```bash
sudo firewall-cmd --add-service=openvpn

# 正しく反映されているか確認
sudo firewall-cmd --list-all
# "services"に"openvpn"があればOK

sudo firewall-cmd --runtime-to-permanent
```

# 接続

ここまでの作業で接続自体は可能ですのでクライアントから接続してみます。
`OpenVPN GUI`をインストールし、起動します。

[ダウンロード | OpenVPN.JP](https://www.openvpn.jp/download/)

コンフィグディレクトリ（デフォルト: `C:\Users\{User_Name}\OpenVPN\config`）に以下の設定ファイルと**転送した鍵・証明書**を配置していきます。

設定ファイルは以下の通りです。
`remote example.com`はご自身のドメインに変更してください。

```:client.ovpn
client
dev tun
windows-driver wintun
proto udp
remote example.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
ca ca.crt
cert client.crt
key client.key
remote-cert-tls server
tls-auth ta.key 1
cipher AES-256-GCM
verb 3
topology subnet
auth SHA512
data-ciphers AES-256-GCM
```

配置が完了したらタスクトレイにある`OpenVPN GUI`のアイコンを右クリックして`client`メニュー（または`.ovpn`のファイル名）から接続をクリックします。
パスワードを聞かれるので`easy-rsa`での証明書作成作業中`build-client-full`の時に入力したパスワードを入力します。

割り当てられた`IPアドレス`を通知するポップアップが出れば接続成功です。
**接続成功してもインターネットには接続できませんが、問題ありませんので切断して作業を続けます。**

接続失敗したらログなどを見ながらググって試行錯誤して下さい。
ファイアウォールやドメインなどが正しく設定されてサーバーに到達できていれば、サーバー側の（`/var/log/openvpn.log`）にもログが残っているはずです。

# 転送設定

まずはカーネルのパケット転送を許可します。
カーネルパラメータの設定ファイルである`/etc/sysctl.conf`を開いて以下を追記します。

```bash:/etc/sysctl.conf
# Forward packet for VPN
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
```

追記したら以下のコマンドで反映させます。

```
sudo sysctl -p
```

ファイアーウォールにもフォワードとマスカレードの設定を行います。
以下のコマンドを実行します。
さくらの`Rockey Linux`はデフォルトではネットワークインターフェースの名前が`ens3`になりますが、環境が異なる場合`ip a`で調べて適切な名前に置換してください。

```
sudo firewall-cmd --add-masquerade
sudo firewall-cmd --direct --add-rule ipv6 filter INPUT 1 -p icmpv6 -j ACCEPT
sudo firewall-cmd --direct --add-rule ipv6 filter FORWARD 1 -i ens3 -o tun0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo firewall-cmd --direct --add-rule ipv6 filter FORWARD 1 -i tun0 -o ens3 -j ACCEPT
sudo firewall-cmd --direct --add-rule ipv6 nat POSTROUTING 0 -o ens3 -j MASQUERADE

sudo firewall-cmd --runtime-to-permanent
```

少しだけ解説します

- `IPv6`での通信に必須な`IPMPv6`をすべて受け入れるようにして
- `ens3 -> tun0（内向き通信）`では**返答パケット（フラグが`related`または`established`）を許可**
- `tun0 -> ens3（外向き通信）`では**すべて許可**
- `ens3`からの外向きはマスカレードする

というようなことをしています。

マスカレードは簡単に言うと**差出人書き換え**です。
`tun0`配下のAというクライアントデバイスから送られたパケットをそのままVPNを通して外に送ると、帰りのパケットを返信の際にAに直接届けようとして届きません。
マスカレードすることでVPNのアドレスが送信元になりますのでVPNにちゃんと帰ってくるようになるわけです。

# 接続

ここまでの作業でVPNを経由してインターネットに接続できるようになっているはずです。
先の接続と同様に`OpenVPN GUI`で接続してみましょう。
ブラウザなどで確認するか`ping`で確認してもよいでしょう。

ちなみに`IPv6`で接続できているかは、[IPv6テスト](https://test-ipv6.com/index.html.ja_JP)にて確認します。
アクセスした結果の`IPv6アドレス`がVPNサーバーのものになっていれば接続できています。

もし接続できない場合は`VPNサーバー`や`dns.google`に`IPv4`や`IPv6`など色々変えながら`ping`するとよいでしょう。
どこまで接続できていてどこで止まっているのかがわかるとトラブルシュートしやすいです。

中々解決しない場合は`WireShark`での分析もおすすめです。
`ping`が行ったっきり戻ってこないときはマスカレードやフォワードなどの設定が疑わしいです。
頑張ってください。

# まとめ

以上、無事に接続できましたでしょうか？
僕はさんざん試行錯誤して接続しました。
特にマスカレードなど、接続した後が大変でした。
`IPv6`での接続も日本語で情報が少ない気がするので参考になれば幸いです。

それでは皆様、よきセキュアな日々をお過ごし下さい！