AWS Lightsailを出島にしてWireGuardを使ってDS-Liteの家庭内LANに接続する

AWS Lightsailと家庭内のRaspberry Piを使って、外出先から家庭内LAN（DS-LiteによるIPv4 over IPv6接続）にアクセスする設定をしたので備忘録として手順を書き残しておこうと思った。

# やりたいこと

- 家庭内LANにあるRaspberry PiやNASに外出先からアクセスしたいことがたまにあるので、必要に応じてVPNを張って外部から見えるようにしたい
- クライアントはWindows/Android/iPad
- インターネットアクセスをすべてVPN経由にしたいわけではない

# 背景

- 家庭内LANからは、DS-Liteを使ったIPv4 over IPv6を使ってインターネットに接続している
- DS-Lite方式のため、グローバルIPが付与されず、使えるポートも一定していないため普通のやり方では外部からのアクセスができない
- ルータとして利用しているYAMAHA NVR510はマルチセッションに対応しているため、DS-Liteのセッション以外にPPPoEのIPv4セッションをもう1本張り、特定のプライベートIPをPPPoEに割り当てて外部からVPNセッションを張ろうと試みたが、なぜかうまくいかなかった

# 方針

- 最近、WireGuardというプロトコルを使うと簡単にVPNが設定できて、速度も速いらしいので気になっていた。やってみた系の記事もそれなりに増えてきたように見える
- AWS Lightsailで固定IPを割り当てたVPSを一つ持っているので、Lightsailを出島にして家庭内LANへトンネルを掘ることにする
- 家庭内に置いてある常時起動のRaspberry Piを家庭内サーバにし、ここからLightsailにVPNを張って維持しておく
- 構成はこんな感じになる
    - ![Qiita_wireguard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262640/20b2c109-2d9f-c857-8c27-466d2abe9851.png)



# 関連情報

このあたりの記事をがっつり参考にさせていただきました。各設定項目の意味などは以下のページを参考にされるとよいと思います。

- [WireGuard VPNを利用しVPSを介して外部から自宅サーバへアクセスする](https://qiita.com/fluo10/items/78e91884042645b08fb9)
- [WireGuard で VPS 経由で家庭内に入る](https://qiita.com/false-git@github/items/bf3884030a954751cd09)
- [WireGuardを使って自宅環境とVPSとクライアント端末を接続する](https://qiita.com/onodes/items/6c62bd3f1a4d2350f8e8)

# 各種設定

- 各種ネットワークアドレス
    - 家庭内LAN: 192.168.0.0/24
    - Lightsail⇔Raspberry Pi間のVPN: 10.0.10.0/24
    - Lightsail⇔各種クライアント間のVPN: 10.0.11.0/24
- WireGuardのインターフェースとポート
    - Lightsail⇔Rasbperry Pi: wg0/51820
    - Lightsail⇔各種クライアント: wg1/51821
- VPN上のIPアドレス
    - Lightsail (wg0): 10.0.10.1
    - Pi(wg0): 10.0.10.2
    - Lightsail (wg1): 10.0.11.1
    - Windows client (wg1): 10.0.11.2
    - iPad client (wg1): 10.0.11.3
- システム
    - Lightsail: Ubuntu 20.04LTS
    - Raspberry Pi: Raspbian-buster Lite

# やったこと

## Raspberry PiにWireGuardをインストールする

あらかじめRaspbianにSSHログインできるようにしておく。ローカルでSSHログインして、

```bash
$ sudo apt install wireguard
```

## Raspberry Piに固定IPを割り付ける

ルータによって方法はいろいろだが、NVR510の場合は管理ページのコマンド入力から、

```
dhcp scope bind 1 [Piに割り当てる固定IP] ethernet [PiのMACアドレス]
```

## Lightsailのファイヤウォールに穴を開ける

AWSの管理コンソールにログインして、51820/**UDP** と 51821/**UDP** のアクセスを許可する。（**TCPじゃなくてUDP**）

## LightsailにWireGuardをインストールする

LightsailにSSHログインして、

```bash
$ sudo apt install wireguard
```

## Lightsail/PiにPort Forwardingの設定

この設定をしておかないとVPSに来た家庭内LANあてのパケットが止められてしまうので、Raspberry Piに転送できるようにする。
Pi側でも家庭内LANの他マシンに転送するためにこの設定が必要。

- Lightsail/PiにSSHログインして、`/etc/sysctl.conf`の`net.ipv4.ip_forward=1`と`net.ipv6.conf.all.forwarding=1`のコメントアウトを外す
- `$ sudo sysctl -p`で設定を反映させる
- **Lightsail/Piを再起動する（再起動しないと反映されない）**

## 鍵ペアの作成

LightsailとPi両方でやっておく。umaskを先に設定しておかないと鍵のパーミッションでエラーになる。実行例ではマスクを0002に戻しているが、設定を0002から変えている場合はその値に戻す。

Lightsail/PiにSSHログインして、適当なフォルダに移動して（`~/wg/`など）以下を実行する。
ファイル名はPi上では`pi.key`など適当に変更。
`vps-pi.psk`は事前共有鍵なのでLightsail側のみで作成しておく。（面倒ならこれは無くてもいいらしい）

```bash
$ umask 0077
$ wg genkey > vps.key
$ wg pubkey < vps.key > vps.pub
$ wg genpsk > vps-pi.psk
$ umask 0002
```

## 初期設定と立ち上げ（Lightsail側）

- インターフェースwg0を仮に作成する

```
$ sudo ip link add dev wg0 type wireguard
$ sudo ip addr add 10.0.10.1/24 dev wg0
$ sudo wg set wg0 listen-port 51820 private-key /home/ubuntu/wg/vps.key
$ sudo wg set wg0 \
> peer [Piの公開鍵の文字列] \
> preshared-key /home/ubuntu/wg/vps-pi.psk \
> allowed-ips 10.0.10.2/32,192.168.0.0/24
$ sudo ip link set wg0 up
```

- 設定をファイルに落とす。ファイル作成が面倒なので上記手順を踏んだが、最初から設定ファイルを書いても結果は同じ。

```
$ sudo wg showconf wg0 | sudo tee /etc/wireguard/wg0.conf
```

- 設定ファイルを編集して、自身のアドレス情報を追加
    - `Address = 10.0.10.1/24` を `wg0.conf` の `[Interface]` に追加

```
[Interface]
Address = 10.0.10.1/24
```

- 設定の読み込みとWireGuardの再起動

```bash
$ sudo wg-quick down wg0
$ sudo wg-quick up /etc/wireguard/wg0.conf
```

- 次回起動時に自動でWireGuardが立ち上がるようにする

```bash
$ sudo systemctl enable wg-quick@wg0.service
```

- 同様の手順でwg1も作成する。クライアントがWindowsやiPadの場合、クライアントアプリ内で鍵ペアが自動的に生成されるのでPeerの項目はとりあえず後回し
    - wg1では各種クライアントの数だけPeerが並ぶ形になる



## 初期設定と立ち上げ（Pi側）

基本はLightsail側と同じだが、唯一グローバルIPを持っているLightsailにPi側からセッションを張って、さらにその接続を維持しなければいけないので `endpoint` と `persistent-keepalive` の設定を追加する。
さきほどLightsailで作った事前共有鍵ファイル `vps-pi.psk` はLightsailからダウンロードしてPiにコピーしておく。
（面倒なら事前共有鍵は使わなくてもいいらしい）

```bash
$ sudo ip link add dev wg0 type wireguard
$ sudo ip addr add 10.0.10.2/24 dev wg0
$ sudo wg set wg0 private-key /home/pi/wg/pi.key
$ sudo wg set wg0 \
> peer [Lightsailの公開鍵の文字列] \
> preshared-key /home/pi/wg/vps-pi.psk \
> endpoint [LightsailのグローバルIPアドレス]:51820 \
> persistent-keepalive 25 \
> allowed-ips 10.0.10.0/24,10.0.11.0/24
```
- Lightsailと同様に、設定をファイルに落とす
- 設定ファイルを編集して、自身のアドレス情報とパケットフォワードの情報を追加

```
[Interface]
Address = 10.0.10.2/24
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT
```

- 設定の読み込みとWireGuardの再起動

```bash
$ sudo wg-quick down wg0
$ sudo wg-quick up /etc/wireguard/wg0.conf
```

- 次回起動時に自動でWireGuardが立ち上がるようにする

```bash
$ sudo systemctl enable wg-quick@wg0.service
```

# ルータに静的ルーティングを追加

このままだとVPN経由で外部からPiに届いたパケットの戻りが家庭内LANのデフォルトゲートウェイ（NVR510）に行ってしまうので、VPNネットワーク（10.0.10.0/24 および 10.0.11.0/24）宛のパケットはPiに飛ばすように静的ルーティングを設定する。

NVR510の管理ページから、

```
ip route 10.0.10.0/24 gateway [Piに割り当てる固定IP]
ip route 10.0.11.0/24 gateway [Piに割り当てる固定IP]
```

# 初期設定と立ち上げ（Windowsクライアント）

ようやくクライアントの設定に入れる。まずはWindowsクライアントから。

- Windows版クライアントを落としてくる https://www.wireguard.com/
- クライアントを立ち上げてCreate Empty Tunnelとかやると勝手に鍵が生成されるので、公開鍵の文字列をLightsailのwg1設定ファイルに書き込む
- クライアントの設定を行う
    - `[Interface]` に `Address = 10.0.11.2/24` を追記
    - `[Peer]` に 
        - `PublicKey = [Lightsailの公開鍵]`
        - `AllowedIPs = 10.0.10.0/24, 10.0.11.0/24, 192.168.0.0/24`
        - `Endpoint = [LightsailのグローバルIPアドレス]:51821` を追記


- Lightsail側でwg1を再起動する
- クライアントを立ち上げてActivateボタンを押せばOK！
- 192.168.0.***で家庭内LANの各種サーバやPiにアクセスできることを確認する

## 初期設定と立ち上げ（iPadクライアント）

- AppStoreからクライアントアプリを入れる
- Windowsクライアントと同様に、アプリを立ち上げて空のトンネルを作成、鍵ペアを作成、公開鍵をLightsail上のwg1設定ファイルに転記する
- Lightsailの公開鍵をiPadアプリ上のPeer設定に転記、AllowedIPsとEndpointの設定をする
- WireGuard設定がiPadのVPS接続リストに追加されるので、必要に応じてVPNをONにする


# 感想

やってみると簡単のようでハマりどころがいくつかあったが、つながってしまえば快適に動いている模様。