WireGuard で Android スマートフォンと自宅の間で VPN 回線を構築する

本稿では、最近流行の兆しを見せている、新規なL3 VPNソフトウェアであるWireGuardを用いた導入モデルケースを検討します。

日本語文献はまだまだ少ないですが、WireGuardの詳細は「作って理解するWireGuard」や「WireGuard:次世代耐乱用性カーネルネットワークトンネル」あたりに良く纏まっている通り、主なメリットとして、

  • 最新の暗号化スイート・鍵交換プロトコルを用いている
  • 他のVPNソフトウェアに比べ、コードベースが非常にコンパクト(であるためコードの見通しが良く、よりセキュアかつ動作が軽量)
  • 一時期出血多量だった OpenSSL 等の他のソフトウェアに依存しない

ということが挙げられます。

また、設定項目が著しく少なくて済むというのも大きなメリットであり、最低限、接続先のIPアドレスと公開鍵情報、および自端末の秘密鍵さえあればセッションが成立します。

デメリットとしては、プロトコルUDPを用いるため、例えばL2TP/IPsecSSL-VPNと比べるとファイヤーウォールの貫通力が弱い、という点でしょうか。

ただ、最近の4GキャリアであればUDPパケットも普通に通りますし(例: イカテザリングキッズ)、町中のWifiでもまずフィルタされることはないでしょう。QUICも普通に使われるようになってきましたし、UDP:80や443であれば塞がれる可能性は少ないと思われます。企業内ファイヤーウォールを通すのは難しいでしょうけど(とはいえ、それは倫理的に…)。

最近LKMLでLinusが「ええやんこれ、はよマージできへんの?」と発言するなど、メインライン入りを目指して急ピッチで開発が進められていますが、セキュリティソフトウェアの常として、まだ正式に監査を受けていないこと、実績ベースがほとんどないこと、開発中のため仕様変更の可能性が常にあること、から、本番環境での導入には留意が必要です。

動作環境

公式サイトに記載の通り、メジャーなLinuxディストリビューションであればほぼ動作します。Raspberry Pi(Raspibian)、OpenWrt/LEDEなどの組み込み系でも既にパッケージが配布されており、Androidアプリも開発版ながらGoogle Playで配布されています。ただし、iOS版は未だ手つかずなのと追記:iOS版、Mac版ともにAppStoreで公開されましたWindowsについてはサードパーティ製のものが配布されていますが、脆弱性があるようで、導入を控え公式版を待つように、とのこと。

構築モデル

本稿では、AndroidスマートフォンにWireGuardを導入し、自宅に設置したサーバー(Ubuntu Linux)との間でVPN回線を構築し、スマートフォンの通信はすべて自宅回線を経由してインターネットに繋ぐというモデルを検討します。各箇所に割り振られるIPアドレスは下図の通りとします。

インストールと設定

WireGuardはPeer-to-Peerでの接続を行いますが、上図の通り、本稿ではサーバーが常時待ち受け、スマートフォン側から接続要求を出す、という観点での設定を行います。

サーバー上でのインストール

まず、サーバー側にWireGuardを導入します。Ubuntuであれば

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

で入ります。上述の通り、プロダクション環境への導入は控えるべき、という位置づけのため、PPA経由での導入となります。もしくはgitでコードを落としてきてビルド。

$ git clone https://git.zx2c4.com/WireGuard
$ cd WireGuard/src
$ make
# make install

なお、ビルドの際、

$ make debug

でビルドすれば、dmesgにデバッグメッセージを吐き出してくれます(鍵交換したよとか、keep alive投げたよ、とか)。

サーバー側の設定

WireGuardの本体はカーネルモジュールであり、外部との通信を行うインターフェース周りの設定は原則 ip コマンドを通じて行う必要があります。WireGuradの制御コマンドもありますが、設定の大半となるIP周りの設定には使用しません。

まず、WireGuardで使用するインターフェースを作成し、IPアドレスを割り当てます(サブネットは /24 にしています)。

# ip link add dev wg0 type wireguard
# ip address add dev wg0 172.16.15.1/24

インターフェース名は専用の wg0 としており、これがVPNの起点になります。なお、この実体は仮想 TUN インターフェースですので、名前付けは自由です。

次いで、最もキモになる秘密鍵と公開鍵をそれぞれ作成します。以下のコマンドでサーバーとスマートフォンAndroid)それぞれの秘密鍵(*.key)と公開鍵(*.pub)が作成されます。

# wg genkey | tee server.key | wg pubkey > server.pub
# wg genkey | tee android.key | wg pubkey > android.pub

公開鍵と秘密鍵を間違えないように注意!

ここからWireGuradの接続端末について設定を行います。WireGuard の設定ファイルは

/etc/wireguard/

以下に配置します。先に決めたインターフェース名+「.conf」で設定ファイルを作成すると後々便利(例: wg0.conf)。作成した公開鍵、秘密鍵を用いて以下のように設定ファイルを作成し、/etc/wireguard/wg0.conf として保存します。

設定に必要な情報は以下の通り。

  • Interface セクション
  • Peer セクション
    • PublicKey: 接続相手の公開鍵をBase64エンコードしたもの(必須)
    • AllowedIPs: VPNを経由して送受信するパケットの送り先(必須)*1。0.0.0.0/0 であれば端末の全パケットがVPN経由になる。
    • PresharedKey: 事前共有鍵(オプション)
    • Endpoint: 接続先の端末アドレスとポート(オプション)*2
    • PersistentKeepalive: Keep Alive パケットの送信間隔(秒)(オプション、0または10〜3600で指定)*3
[Interface]
ListenPort = 1234
PrivateKey = <サーバー側秘密鍵>

[Peer]
PublicKey = <Andorid 公開鍵>
AllowedIPs = 172.16.15.200/32

接続用の秘密鍵をテキストファイルに直接記入することになるため、必要に応じてファイルパーミッションを適切に設定する等の対応を検討する必要があります。また、設定ファイルの読み取り時にパスワードを打ち込むことで秘密鍵を読み出すようにするテクニックがArch LinuxのWikiに記載がありますので、そちらも参考に。

なお、複数のスマートフォンタブレットを接続する場合はPeerセクションを接続台数分作成すれば対応可能です。あるいは、接続ごとに wg1, wg2 ... とインターフェースを区切っても構いません。ただし、複数のPeerを設定する場合は AllowedIPs で指定するネットワークセグメントは他のPeerと重複しないように設定する必要があるようなので、ここでは /32 と最も狭い範囲で固定しています。

設定ファイルができあがれば、WireGuardに読み込ませます。

# wg setconf wg0 /etc/wireguard/wg0.conf
# ip link set dev wg0 up

ルーティングテーブルを念のため確認しておきます。以下のように 172.16.15.0/24 のみ wg0 へ向いていればOK。

$ route -n
カーネルIP経路テーブル
受信先サイト    ゲートウェイ    ネットマスク   フラグ Metric Ref 使用数 インタフェース
0.0.0.0         192.168.1.1     0.0.0.0         UG    0      0        0 enp5s0
172.16.15.0     0.0.0.0         255.255.255.0   U     100    0        0 wg0
192.168.1.0     0.0.0.0         255.255.255.0   U     100    0        0 enp5s0

ルーター/Firewallの設定

ルーター側の設定で、UDP 1234 へのパケットをサーバー(192.168.1.2:1234)へ振り分け、通信を許可するように設定します。

また、スマートフォンから送られてきたパケットをサーバー側でルーティングするため、サーバー上でIPマスカレードを有効化します。
/etc/sysctl.conf を編集し、net.ipv4.ip_forward = 1 とします。加えて、iptablesにてNATを有効化します(必要に応じて FORWARD チェインも許可してください)。

# iptables -t nat -A POSTROUTING -s 172.16.15.0/24 -o enp5s0 -j MASQUERADE
# #Forward chain も設定しないと多分繋がらない気がする
# vi /etc/sysctl.conf
 net.ipv4.ip_forward = 1
# sysctl -p
または
# reboot

この部分は本筋ではないので割愛します。詳細はググってください。

スマートフォン側の設定

スマートフォンアプリの設定は、外部で作成したファイルをQRコードに変換した形で読み取って設定することが可能ですので、先にサーバー上で作成してしまいます(鍵情報の入力が面倒なので)。なお、qrencodeコマンドを使えない環境で作業している場合は、適宜テキストファイルをQRコードに変換する作業が必要です。

先に作成した wg0.conf ファイルを適宜コピーし、公開鍵、秘密鍵を入れ替えたものを作ります。

[Interface]
PrivateKey = <Android 秘密鍵>
Address = 172.16.15.200

[Peer]
PublicKey = <サーバー側公開鍵>
AllowedIPs = 0.0.0.0/0
Endpoint = 203.0.113.90:1234

サーバー側の設定ファイルとの違いとして、以下の項目を変更します。

  • Address はスマートフォン側のVPNアドレス。サーバー上の wg0.conf で記載した AllowedIPs の範囲と一致しないと通信は成立しません。
  • ListenPort は度々変わるので削除
  • AllowedIPs は全パケットを転送するなら 0.0.0.0/0 を指定。一方、サーバーとの通信のみ転送するのであれば 172.16.15.1/32 とか 172.16.15.0/24 とか。
  • Endpoint はサーバー側の Internet 側 IP を指定。DDNSによるFQDNを指定しても可。

これを android.conf として保存し、

$ qrencode -t ansiutf8 < android.conf

QRコード化します。公式アプリを立ち上げ、画面右下の「+」ボタンをクリックし、「Create from QR code」からQRコードを読み取ります。プロファイル名を適当に入力すると設定値が反映されます。

なお、このアプリ側設定画面上でDNSを別途指定することも可能です。VPNサーバー上でDNSサーバーも稼働している場合、同じIPアドレスを指定すれば名前解決はVPN網の先で行われますが、指定しない場合、スマートフォン側のネットワークにて名前解決が行われるため、セキュリティ上のリスクとして認識しておいた方が良いでしょう。

接続確認

設定は以上です。Andoridアプリから接続を開始し、正常に接続できていればサーバー上でコマンドを叩くと以下のように表示されるはずです。

# wg
interface: wg0
  public key: yJN1nv7Cu.......
  private key: (hidden)
  listening port: 1234

peer: uFSetu.......
  endpoint: 198.51.100.5:46128
  allowed ips: 172.16.15.200/32
  latest handshake: 1 minutes, 24 seconds ago
  transfer: 32.62 KiB received, 36.75 KiB sent

スマートフォン側から接続元IPアドレスの確認サイトを見ると、自宅のアドレスで繋がっているはずです。

*1:マニュアル上は「ピア相手から送られてくるパケットの許容宛先もしくはピア相手に送信するパケットの宛先の一覧」となっていますが、実質的にはルーティング相当のことを行っています

*2:オプションではあるが、一方のピア(接続を開始する側)での指定は必須

*3:規定では0(OFF)。WireGuardは非常に寡黙なプロトコルでもあるので、通信が発生しない場合はほぼパケットが飛び交いません。なので、NAT/ファイアーウォール環境下でステートフルパケットインスペクション(SPI)が機能している場合、本設定を有効化して定期的に通信を継続させることで、新規通信と見なされセッションを妨害される可能性を下げることも検討した方がよいでしょう。