WireGuardを利用したVPN環境を構築し、社外クライアントから社内サーバまでアクセスする

# 目的
リモートワークが盛り上がっている昨今ですが、外部から社内ネットワークにアクセスする必要があるパターンは多いと思います。今回はWireGuardを使ってVPN環境を構築しました。

# 構成
以下のような構成です。
![wireguard_qiita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/3fb93f74-ec72-f441-fc30-dc742492cb11.png)

- Linuxのカーネルモジュールとして動作するオープンソースのVPN「WireGuard」にて構築します。特徴は以下の通りです。
  - 公開鍵を利用してクライアントとサーバ間の暗号/復号化を行います。
  - 広く普及したOSSのVPNであるOpenVPNよりも軽く、設定が簡単です。
  - UDPで通信します。
  - クライアントとサーバ間で公開鍵を用いて認証し、VPNトンネルを構成します。このVPNトンネルは拠点ごとのルータを越えてVPN用のアドレスで通信可能になります。
- 自宅/社内LAN側と重複しないアドレスをWireGuardトンネル用に確保する必要があります。今回はVPNセグメントとして`10.0.0.0/24`を使用しています。
- クライアントからは社内向けのセグメント`192.168.1.0/24`のみVPNトンネルを通してアクセスさせ、他の通信はVPNを利用せずにデフォルトゲートウェイに向けてアクセスします。
- VPNサーバと境界サーバ上で、物理インターフェースとVPN用の仮想インターフェースをIPフォワーディングしています。
- 図と記事内表記の紐付け
  - client → 自宅クライアント
  - VPN server → VPNサーバー
  - Gateway server → 境界サーバー
  - physical server → 物理サーバー
  - target server → 標的サーバー

### 本構成のメリット
- **ネットワークベンダーに設定変更を依頼せずにVPN環境を構築可能**  
会社側のルータは外からの中へのアクセスを禁止し、中から外へのアクセスの制限は厳密にしていない(通信ポートが流動的なため制限しにくい)ケースが多いため、境界サーバとのVPNサーバとの通信は制限されていないケースが多いです。要は、ネットワークベンダーに設定変更を依頼せずにVPN環境を構築できます。

- **接続可能なクライアント端末を限定できる**  
VPNサーバ側でpeerを個々に登録することで、不審なクライアントが社内にアクセスすることを防ぐことができます。また、不要になればVPNサーバ側でpeerを削除することも容易です。

# 手順
以下のような手順で構築します。

- AWS上にVPNサーバを構築し、社内向けのパケットをルーティングする設定を行う。
- 社内の物理サーバにVirtualBoxをインストールし、仮想サーバ(Ubuntu)を構築して境界VPNサーバとして設定する。AWS上のVPNサーバから受け取ったパケットをVPNサーバから社内のネットワーク宛にルーティングする設定を行う。
- 自宅クライアントPCにVPNクライアントをインストールし、社内向けのパケットをVPNサーバ宛にルーティングする設定を行う。

## VPNサーバ
AWSでt2.microでubuntuサーバ(16.04.6)を立ち上げ、WireGuardをインストールします。

```
$ sudo add-apt-repository ppa:wireguard/wireguard
$ sudo apt-get update
$ sudo apt-get install wireguard
$ sudo apt-get install wireguard-dkms wireguard-tools
```

wireguardの設定ファイルを`/etc/wireguard/wg0.conf`に作成し、VPN用のインターフェースwg0を新設して起動させます。続いて秘密鍵を生成します。

```
$ (umask 077 && printf "[Interface]\nPrivateKey = " | sudo tee /etc/wireguard/wg0.conf > /dev/null)
$ wg genkey | sudo tee -a /etc/wireguard/wg0.conf | wg pubkey | sudo tee /etc/wireguard/publickey
```

`/etc/wireguard/wg0.conf`の詳細を設定します。

```
[Interface]
Address = 10.0.0.1/24
SaveConfig = true
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
ListenPort = 5555
PrivateKey = 生成したVPNサーバの秘密鍵

[Peer]
PublicKey = クライアントの公開鍵
AllowedIPs = 10.0.0.2/32
Endpoint = 1.2.3.4:63275
```

各項目の意味は以下の通りです。

| 項目 | 内容 |
|:--|:--|
| PrivateKey | サーバー側の秘密鍵|
| Address | VPN用のIPアドレス。ここでは10.0.0.1/24を指定しています。 |
| ListenPort | WireGuardがListenするポート番号|
| SaveConfig | trueにすることでWireGuardの起動時、停止時にこの設定ファイルの内容を保存します。 |
| PostUp, PostDown | WireGuardの起動直前（PostUp）、停止直前（PostDown）に実行する処理を記述します。ここでは、WireGuardの起動、停止それぞれの直前にiptablesのフォワーディング用の設定を追加しています。上記のiptablesルール内にあるeth0はVPNサーバーが持つ外部IP（WAN側IP）です。 |

AWSのVPNサーバのセキュリティグループにて、インバウンドルールにクライアントのグローバルIPを指定し、VPN用のUDP通信のみ許可する設定を追加します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/269c1d20-8cd7-9dc4-dd8f-efe9371f3cd6.png" width=90%>

再起動しても元にもどらないように、`/proc/sys/net/ipv4/ip_forward`ファイルでIPフォワードの設定を追加します。`/proc/sys/`以下のファイル操作によって、カーネル設定を有効化／無効化することができます。
これにより、既存のインターフェースとVPN用のインターフェース間でパケットを転送できるようになります。

```
$ sudo echo 1 >  /proc/sys/net/ipv4/ip_forward
```

また、起動時に参照される`/etc/sysctl.conf`ファイルにてフォワーディングを有効にします。

```
# 修正前
net.ipv4.ip_forward = 0
# 修正後
net.ipv4.ip_forward = 1
```

社内のセグメント`192.168.1.0/24`宛のパケットをVPN用のインターフェース`wg0`経由で配送するようにルーティングを追加します。

```
sudo ip route add 192.168.1.0/24 via 10.0.0.1 dev wg0
```

`netstat -nr`コマンドで`192.168.1.0/24`宛のルートが追加されていることを確認します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/33216572-e640-e08f-c20e-b75e39fe0ac7.png" width=60%>

## 物理サーバ
物理サーバがwindows serverしかなくVPNクライアントに使うのが辛かったので、VirtualBoxをインストールし、仮想OSとしてubuntu(64-bit)を構築して境界サーバとしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/c10f2baa-42cb-50bf-9dcf-8d6f6a7b5bb5.png)

また、社内の同じセグメント上にある実際にアクセスしたいサーバ（標的サーバとしてます）にVPN経由で通信可能とするため、「Routing and Remote Access」サービスを有効にしてWindows Serverをルータ化させます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/0b4eb6db-47c3-7397-e5f8-d9880cb92337.png)

## 境界サーバ
VPNサーバと同じコマンドでWireGuardをインストールします。
続いて秘密鍵を生成し、VPN用のインターフェース`wg0`を新設して起動させます。
`/etc/wireguard/wg0.conf`にてVPNサーバをPeerとして設定します。

```
[Interface]
Address = 10.0.0.X/24 #境界サーバ用IPアドレス
SaveConfig = true
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s8 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s8 -j MASQUERADE
ListenPort = ランダム
PrivateKey = 生成した境界サーバの秘密鍵

[Peer]
PublicKey = VPNサーバの公開鍵
AllowedIPs = 10.0.0.0/24,192.168.1.0/24 #VPNと社内セグメント
Endpoint = VPNサーバのグローバルIPアドレス:5555
```

以下の`wg`コマンドで上記で設定したPeerと同じ内容を追加することも可能です。
同様に、VPNサーバ側にも生成した境界サーバの公開鍵を元にPeerの設定を追加してください。

```
# 境界サーバ側で実行
sudo wg set wg0 peer VPNサーバの公開鍵 endpoint VPNサーバのグローバルIPアドレス:5555 allowed-ips 10.0.0.0/24,192.168.1.0/24

# VPNサーバ側で実行
sudo wg set wg0 peer 境界サーバの公開鍵 endpoint 境界サーバのグローバルIPアドレス allowed-ips 10.0.0.5/32,192.168.1.0/24
```

Peer追加がうまくいくと、`wg`コマンドでPeerとの疎通状態を確認することができます。handshakeに成功し、VPNサーバと境界サーバ間でVPNが確立していることが分かります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/b4c8a62c-026c-1e37-46a5-7b60d7e9c7dc.png" width=60%>

VPNサーバと同様に`/proc/sys/net/ipv4/ip_forward`ファイルでIPフォワードの設定を追加します。

```
$ sudo echo 1 >  /proc/sys/net/ipv4/ip_forward
```

VPNサーバと同様にルーティングの設定を追加しますが、今回は社内セグメントの通信を、VPNトンネルを通じて仮想OS上に作成した社内セグメント`192.168.1.0/24`に属する仮想インターフェース`enp0s8(環境によって変わります)`に配送するように設定します。

```
sudo ip route add 192.168.1.0/24 via 0.0.0.0 dev enp0s8
```

## クライアント
WireGuradには本来サーバとクライアントという概念がなく、それぞれがそれぞれのPeerという考え方なのですが、クライアント用には簡易に設定できるアプリが提供されているのでそれを使います。

- 公式サイトからクライアントアプリ(Mac、Windows、Android、iOS)をインストールします。
https://www.wireguard.com/install/
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/351beca9-5dac-fd8a-10cb-bc12a898a43a.png" width=80%>

- インストールしたアプリを起動し、新しいトンネル設定を追加します。
Peerの情報はVPNサーバであり、全てのクライアントに対して同じ設定を追加します。
自動でクライアントの公開鍵/秘密鍵が作成されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/096a2b70-7aba-b4b8-21f2-c6b4f9ca7018.png" width=60%>

- VPN用アドレスを`10.0.0.X`で採番し、2. で生成したクライアントの公開鍵をVPNサーバに登録します。

```
$ wg set wg0 peer クライアントの公開鍵 endpoint 自宅グローバルIPアドレス:ポート番号 allowed-ips 10.0.0.X
```

- VPNサーバでコマンド`sudo wg`を実行し、クライアントがPeerとして登録され、handshakeでコネクションが確立しているか確認します。 
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/86fa018a-e905-296b-6cd9-6def7a4a8ff7.png" width=60%>

- クライアント側ではVPN用インターフェースが自動で追加されるので、コマンド（Mac：ifconfig　Win：ipconfig）で10.0.0.xのアドレスを持つインターフェースがあるか確認して下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/e54f2786-099f-06b9-c22b-f62b6f0b7af5.png)
- クライアントアプリのStatusがactiveになり、Data sent/receivedが動き始めればVPN接続が確立します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/a6e90dfe-d01d-752f-1c5b-69d93e2f6503.png" width=80%>

- 各種OSに合わせたコマンドで、クライアントに対して社内ネットワーク宛のルーティング(192.168.1.0/24)を追加します。

```
Mac:$ sudo route add -net 192.168.1.0/24 10.0.0.x
Win：route add -p 192.168.1.0 mask 255.255.255.0 10.0.0.x
```

- クライアントから境界サーバ(192.168.1.7)や社内の標的サーバ(192.168.1.x)まで疎通出来れば完了です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252317/246c92d0-baff-b1bc-d103-a10c5b33bda2.png" width=80%>

- クライアントでVPNをオフにする時は、アプリのActive/Deactiveのボタンによりオフにします。

# まとめ
自宅から社内の標的サーバまでVPN経由でアクセスが可能となりました。
クライアント数台で利用していますが、特に遅延なくアクセス可能です。

なお、今回の構成で自宅クライアントから標的サーバまでパケットが通過する経路は以下のとおりです（応答パケットは逆になります）

```
自宅クライアント.wg0->(wg tunnel)->VPNサーバ.wg0->(forward)->VPNサーバ.wg0->(wg tunnel)->境界サーバ.wg0->(forward)->境界サーバ.enp0s8->標的サーバ.eth0
```