Wireguard でサーバーと家のPCをつなぐ

インターネットの向こうにある検証用サーバーを使うときに、検証なのでインターネットにさらしたくないが、自分のPCのブラウザからは見られるようにしたい。自宅のIPアドレスは固定ではないのでIPで制限かけるのは微妙だし、HTTPSならBASIC認証とか設定すればいいけど検証なので設定増えると面倒。LANにあるマシンのように気楽に使いたい。

そういうことで、PCとサーバーをVPNでつなぐことにした。サーバーのグローバルインターフェースはVPNのポートだけ許可し、VPNインターフェースは制限無しにすることで、ローカル環境のサーバーのように気楽に扱える。

簡単に設定できるWireguardを使った。設定後も普通のインターフェースのように扱えるので楽ちん。

環境

f:id:naokton:20201128055956p:plain
ネットワーク図

サーバーの設定

Wireguardをインストールする

$ sudo apt install wireguard
$ sudo reboot

Wireguardの鍵を作る

private keyはroot以外読めないようにする。

$ sudo sh -c 'cd /etc/wireguard; wg genkey | tee privatekey | wg pubkey > publickey'
$ sudo chmod 660 /etc/wireguard/privatekey

Wiregaurd設定ファイルを作る

システム起動時に自動的にupするよう、wg-quickサービスを使う。パラメーターっぽいものは以下。

  • インターフェース名: wg0
  • VPN InterfaceのIP: 10.0.0.1
  • Listen port: 47474
  • PCのVPN InterfaceのIP: 10.0.0.2
  • AllowedIPsは/32でピアのIPを指定
    • Hub-and-SpokeではなくPeer-to-Peer
$ sudo vi /etc/wireguard/wg0.conf
[Interface]
SaveConfig = false
Address = 10.0.0.1/24
ListenPort = 47474
PrivateKey = {/etc/wiregaurd/privatekeyの内容}
PostUp = /etc/wireguard/postup.sh %i 47474
PostDown = /etc/wireguard/postdown.sh %i 47474

[Peer]
PublicKey = {PC側で設定したIFのpublic key}
AllowedIPs = 10.0.0.2/32

Peer (自宅PC)はNAPTの向こうにいるので、ポートは書かない。サーバー側のポートが固定なのでOK。

PostUp, PostDownで使うスクリプトを作る。GWインターフェース(グローバルIPが振られているインターフェース)にWireguardのlisten portを追加し、Wiregaurdのインターフェース(wg0)は全ての通信を許可する。

$ sudo vi /etc/wireguard/postup.sh
IF=$1
PORT=$2
GW_IF=$(ip -j route list default | python3 -c 'import sys,json; print(json.load(sys.stdin)[0]["dev"])')

ufw allow in on $GW_IF to any port $PORT proto udp
ufw allow in on $IF
ufw status verbose
$ sudo vi /etc/wireguard/postdown.sh
IF=$1
PORT=$2
GW_IF=$(ip -j route list default | python3 -c 'import sys,json; print(json.load(sys.stdin)[0]["dev"])')

ufw delete allow in on $GW_IF to any port $PORT proto udp
ufw delete allow in on $IF
ufw status verbose
$ sudo chmod 755 /etc/wireguard/post{up,down}.sh

サービスを有効にし、起動する

$ sudo systemctl enable --now wg-quick@wg0.service

wg-quick@{hoge}.service は /etc/wireguard/{hoge}.conf を読み込む。そのへんの仕組みは /lib/systemd/system/wg-quick@.service とか man systemd.unit の template unit あたりを参照。

状態確認

$ sudo wg show wg0
interface: wg0
  public key: xxxx
  private key: (hidden)
  listening port: 47474

peer: xxxx
  endpoint: 198.51.100.1:56243
  allowed ips: 10.0.0.2/32
  latest handshake: 1 hour, 35 minutes, 45 seconds ago
  transfer: 26.16 KiB received, 24.44 KiB sent

PCの設定

Wiregaurdアプリケーションでトンネル設定

Add Empty Tunnel から設定を作成する。自動的にprivate/public keyが作られるので、public keyの文字列をサーバーの設定ファイル wg0.conf にコピペする。

[Interface]
PrivateKey = {自動で作られる}
Address = 10.0.0.2/24

[Peer]
PublicKey = {サーバーのpublic key}
AllowedIPs = 10.0.0.1/32
Endpoint = 203.0.113.1:47474

接続

サーバーとPC側でインターフェースを有効にする/activateするだけでは何も通信は発生せず、実際に通信が発生したときにハンドシェイクするようだ。PCはNAPTの奥にいるので、PCからサーバーに通信を投げる必要がある。

補足: dockerとiptatbles

dockerでportをpublishすると、ufwで許可していないはずなのに、外からアクセスできてしまう。これは、dockerのポート宛ての通信はINPUTチェーンではなく、FORWARDチェーンを通るため。

dockreのドキュメントに書いてあるように( Docker and iptables | Docker Documentation

)、DOCKER-USERチェーンにルールを追加する必要がある。グローバルインターフェース(eno1)からdockerのポート宛ての通信を全て拒否する場合、以下の設定を入れる。

$ sudo iptables -I DOCKER-USER -i eno1 -o docker0 -j DROP

Wireguardでつないでいる場合、10.0.0.1へのアクセスはwg0インターフェース経由での通信になるので、上の制限は効かず、アクセスできる。