securi」タグアーカイブ

お手軽VPNのWireGuard

WireGuardの概要

WireGuard(https://www.wireguard.com/)はオープンソースのVPNです。まず最初にLinuxのカーネル向けに、そして、MacOSやWindows,iOS,Andoroidなど、多くのOSがサポートされています。Linuxではカーネルレベルで動作しており、WireGuardはアプリケーションではなく、インターフェースとして認識され、eth0やwlan0と同じく、wg0と言った新しいインターフェースを追加してVPNを扱うことが出来ます。したがって、ifconfigやip,iptablesといったコマンドがそのまま使えます。

以下に公式ページを参考にしつつ特徴を書いておきます。

ネットワーク

ネットワーク的には上記の通りインターフェースを追加するので、レイヤー3上に暗号化トンネルを作ることになります。使用するプロトコルはUDPで好きなポート番号を選ぶことが出来ます。トンネルの中を通すことが出来るのはIPプロトコルでv4もv6もどちらも行けます。

認証鍵

認証は2種類のキーを使います。サーバ側が持つプライベートキーとパブリックキーのキーペアーと、クライアント側のプライベートキーとパブリックキーのペアーです。このあたりはSSHの鍵の考え方とよく似ています。VPNでよくある、シェアードキーの設定も出来ます。

暗号化技術

暗号化やハッシュは最新の物が使われています。Curve25519, ChaCha20, Poly1305, BLAKE2, SipHash24, HKDFが使われていますが、難しすぎてよく分かりませんので、飛ばします。それぞれ、鍵交換、暗号化、認証、ハッシュ、ハッシュテーブル鍵、鍵導出だと思います。

攻撃の最小化

ソースコードや設定ファイルの行数が少ないのも特徴です。行数を減らすことによって、脆弱性やミスの入り込む余地が減り、レビューも簡単になります。VPNの設定はネットワーク機器でもアプリでも複雑になりがちですが、非常に簡単で分かりやすくなっています。

ハイパフォーマンス

カーネルの一部となっているため、高速に動作します。OpenVPN等と比較してもスループットの割にCPUの使用率が少なくなっています。

インストール

今回はうちで余っていたRasPi3にインストールしてみます。もともと自宅内DNSとして使ってましたが、OSの入れ替えなどでDNSを別RasPiにした際の、あまりRasPiです。

$ cat /proc/device-tree/model
Raspberry Pi 3 Model B Rev 1.2
$ cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
NAME="Raspbian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

Raspberry pi OSはbusterまでアップグレードしてあります。

WireGuardはDebian系は、busterの次のバージョンであるbullseyeからの対応になりますが、パッケージのソースにbackportsを指定するとbusterでも使えるようになります。/etc/apt/source.listに追加をしておきます。2行目の記述です。

$ cat /etc/apt/sources.list
deb http://ftp.jaist.ac.jp/raspbian/ buster main contrib non-free rpi
deb http://deb.debian.org/debian buster-backports main

ソースを追加すると鍵がないと言われたので、gpgコマンドでキーサーバから取得しました。(普通は必要ないと思われる)
gpg –keyserver キーサーバ –recv-keys 鍵、gpg -a –expprt 鍵 |sudo apt-key add -、といったコマンドですね。

あとは普通にaptでインストールです。カーネルヘッダーが同時にインストールされなかったので、個別に入れました。

$ sudo apt-get update
$ sudo apt install wireguard
$ sudo aptitude install raspberrypi-kernel-headers

インストール時に終わっているかも知れませんが、今回インストールしたWireGuardをカーネルモジュールとして取り込みます。modprobeコマンドですね。確認はlsmodで見ることができます。

$ sudo modprobe wireguard

サーバの設定

インストールされるとwgコマンドとwg-quickコマンドが使えるようになります。wgがWireGuardの鍵を作ったり、インターフェースやピアの設定をするコマンドになります。wg-quickは設定ファイルに基づいてインターフェースのアップ、ダウンなどが出来るようになります。

鍵の作成

WireGuardはサーバ、クライアントではなくsite-to-siteのVPNが出来るのですが、便宜上おうちの中にあるRasPiをサーバ、外で使うPCやスマートフォンをクライアントとして扱います。鍵はどこでも作れるのですが、今回はサーバの中で作るということにします。

鍵ペアの作成はプライベートキーを作ってからパブリックキーを作る流れになります。プライベートキーは他の人に知られてはいけないので、パーミッションを600にしておく必要があるのですが、デフォルトでファイルを作ると644のパーミッションになってしまいwgコマンドに拒否されるので、umaskで作られるファイルのパーミッションを600になるようにしておきます。先にtouchコマンドでファイルを作っておいてファイルのパーミッションを600にしておけば怒られないし、umaskを変える必要はありません。

$ umask 077
$ wg genkey > privatekey

umaskを元に戻す場合は、umask 033です。

プライベートキーからパブリックキーを作成します。

$ wg pubkey < privatekey > publickey

同様にクライアントの鍵も作ります。

$ wg genkey > cl-privatekey
$ wg pubkey < cl-privatekey > cl-publickey

サーバ側のインターフェースとピアの設定

この先、ipコマンドやwgコマンドを駆使してインターフェースの作成やピアの設定など出来るのですが、最終的に設定ファイルを作って自動起動するので、最初から設定ファイルを作ることにします。サーバ側の設定ファイルは/etc/wireguard/wg0.confとします。wg0はWireGuard用に作る新規のインターフェース名です。

[Interface]
ListenPort = 51820
Address = 192.168.1.1/24, fdfd:abcd:0001::1/64
PrivateKey = (サーバのプライベートキー)

[Peer]
PublicKey = (クライアントのパブリックキー)
AllowedIPs = 192.168.1.100/32, fd00:abcd:0001::100/128

[Interface]は自ホストの設定です。
ListenPortは文字通り受付用のポート番号です。ファイヤウォールなり、ポートフォワーディングなりで、サーバのListenポートを開けておく必要があります。tcpではなくudpなので注意です。
Addressはインターフェースに振るアドレスで、IPv4とIPv6の両方が使えます。適当なプライベートアドレスとユニークローカルアドレスをつけます。IPv4であれば、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16のプライベートアドレスの範囲から使います。サブネットも同時にしてしておきます。家庭用だったら/24で十分かと思います。IPv6であれば、fd00::/8のユニークローカルIPv6ユニキャストアドレス(ULA)から使います。サブネットは/64が適当かと思います。
PrivateKeyは先ほど作ったサーバのプライベートキー(privatekey)です。直接書き込む場合は設定ファイルのパーミッションを600にします。

[Peer]はクライアントの設定です。鍵ペア毎に作ります。複数クライアントがある場合は[Peer]が沢山並びます。
PublicKeyはクライアントのパブリックキー(cl-publickey)です。いわゆるユーザIDは存在せず、PublickKeyがその代わりとなります。
AllowedIPsはサーバの設定ファイルで書く場合は、受信可能なクライアントのアドレスとなります(クライアントの設定では意味が異なります)。今回の場合はサブネットがv4とv6でそれぞれ/32,/128となっていますので、1台のみの接続しか出来ません。アドレスを変えての鍵の使い回しによる同時接続が出来ません。

サーバ内部のネットワークの設定

上記の設定だけだとクライアントはWireGuardをインストールしたホストのみしか通信が出来ません。実際の運用では、サーバを踏み台にしてLAN内の他のホストと通信したり、インターネットと通信したいのが普通かと思います。そのため、wg0とeth0の間で通信が出来るようにし、追加のルーティングの設定をしなくて言いように、NAPT(IPマスカレード)の設定を追加します。

まずカーネルパラメータを変更して、パケットフォワードが出来るように/etc/sysctl.confに以下の記述を追加します。(コメントアウトします)

# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
# Uncomment the next line to enable packet forwarding for IPv6
net.ipv6.conf.all.forwarding=1

sysctl.confの一部抜粋をしています。sysctl -p コマンドで有効化します。(再起動してもフォワーディングされます)

次にwg-quickの機能を使ってインターフェースがアップした際にiptablesをつかってクライアントがNAPTされるようにします。設定ファイル(wg0.conf)の[Interface]セクションのPostUP,PostDownにiptablesの記述を行います。(抜粋)

PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT; ip6tables -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; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

PostUpにインターフェースがアップした際の動作を書きます。iptablesでwg0へ入ってきたパケットを許可してフォワーディング、そして、eth0へ出て行く場合はNAPTによるアドレス変換を行います。
同様にIPv6の設定もip6tablesで行います。ipv6はipv4と同じ設定に加えて、wg0へ向かうパケットの許可(ip6tables -A FORWARD -o wg0 -j ACCEPT)も必要でした。
PostDownはインターフェースがダウンした際の動作で、iptablesによる設定を消す動作をしています。

当然ですが、もともとiptablesの設定があった場合はインターフェースやWireGuardのListenPortの追加設定は必要です。

サーバ側の設定ファイル

[Interface]
ListenPort = 51820
Address = 192.168.1.1/24, fdfd:abcd:0001::1/64
PrivateKey = (サーバのプライベートキー)
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT; ip6tables -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; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
PublicKey = (クライアントのパブリックキー)
AllowedIPs = 192.168.1.100/32, fd00:abcd:0001::100/128

WireGuardの自動起動

手動でWireGuardを起動したり終了したりするには以下のコマンドを使います。

$ sudo wg-quick up wg0
$ sudo wg-quick down wg0

これを行うと、wg0のいた-フェースの作成、アドレスの設定、ルーティング、リゾルバの設定、PostUpなどのコマンド実行などをやってくれます。

wg0のインターフェースが作成されれば、ifconfigやipなどのコマンドで見ることができます。

$ ifconfig wg0
wg0: flags=209<UP,POINTOPOINT,RUNNING,NOARP>  mtu 1420
        inet 192.168.1.1  netmask 255.255.255.0  destination 192.168.1.1
        inet6 fd00:abcd:1::1  prefixlen 64  scopeid 0x0<global>
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 1000  (UNSPEC)
        RX packets 178459  bytes 143465460 (136.8 MiB)
        RX errors 23  dropped 0  overruns 0  frame 23
        TX packets 175183  bytes 173639084 (165.5 MiB)
        TX errors 0  dropped 87 overruns 0  carrier 0  collisions 0

$ ip address show dev wg0
4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none
    inet 192.168.1.1/24 scope global wg0
       valid_lft forever preferred_lft forever
    inet6 fd00:abcd:1::1/64 scope global
       valid_lft forever preferred_lft forever
$ ip route
default via 192.168.0.1 dev eth0 src 192.168.0.87 metric 202
192.168.1.0/24 dev wg0 proto kernel scope link src 192.168.1.1
192.168.0.0/24 dev eth0 proto dhcp scope link src 192.168.0.87 metric 202
$ ip -6 route
::1 dev lo proto kernel metric 256 pref medium
2001:db8:abcd:abcd::/64 dev eth0 proto ra metric 202 pref medium
fd00:abcd::/64 dev eth0 metric 202 pref medium
fd00:abcd:1::/64 dev wg0 proto kernel metric 256 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
default via fe80::aaaa:bbbb:cccc:dddd dev eth0 proto ra metric 202 pref medium

2001:db8:abcd:abcd::/64はプロバイダから割り当てられたアドレス。192.168.0.0/24とfd00:abcd::/64がLAN内のアドレスです。これらはeth0に割り当てられています。

systemctlが使えるならば systemctl start wg-quick@wg0 が使えます。最後にインターフェース名をつけます。自動起動は同様にsystemctl enable wg-quick@wg0です。

クライアントの設定

クライアントの設定はクライアント側で個別に設定しても良いのですが、今回はサーバ側で設定ファイルを作り、配布する想定とします。設定ファイル名はcl.confとします。WireGuardにはユーザIDという概念はないので、クライアントの鍵がユーザの代わりとなります。

クライアントのインストール

クライアントも入れるアプリケーションは同じです。設定が変わるだけです。OS毎のインストール方法は、https://www.wireguard.com/install/ にあります。

鍵の作成(再掲)

クライアントの鍵を作ります。サーバの設定で既に作っているはずです。

$ wg genkey > cl-privatekey
$ wg pubkey < cl-privatekey > cl-publickey

クライアントの設定ファイルの作成

[Interface]
PrivateKey = (クライアントのプライベートキー)
Address = 192.168.1.100/24, fdfd:abcd:1::100/64
DNS = 192.168.0.53, fdfd:abcd::53

[Peer]
PublicKey = (サーバのパブリックキー)
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = vpn.example.jp:51820
#Endpoint = [2001:db8:abcd:abcd:aaaa:bbbb:cccc:dddd]:51820

[Interface]は自クライアントの設定です。
PrivateKeyは先ほど作ったクライアントのプライベートキー(cl-privatekey)です。
Addressはインターフェースに振るアドレスで、IPv4とIPv6の両方が使えます。サーバに合わせたプライベートアドレスとULAをつけます。今回クライアントのホストアドレスは100としています。
DNSは名前解決に使うサーバです。wg-quickで自動的にサーバのリゾルバが選ばれるはずですが、明示的に示したい場合や、リゾルバの設定が上手くいかない場合は直接記述も可能です。

[Peer]はサーバの設定です。サーバと対になっています。
PublicKeyはサーバのパブリックキー(publickey)です。
AllowedIPsはクライアントの設定ファイルで書く場合は、VPNのトンネルに転送するアドレス範囲となります(サーバの設定では受信するアドレスでした)。今回の場合はサブネットがv4とv6でそれぞれ/0となっているので全てのパケットはVPNのトンネルに転送することになります。トンネルの先のアドレスだけにすれば、それ以外の通信先(例えばインターネット)はVPNを通らずに直接出て行きます。
EndPointはVPNサーバの外部の(インターネットに直接接続可能な)アドレスとポート番号です。IPv6の場合はアドレスを[]で囲む必要があります。FQDNも可です。

クライアントへの設定

クライアントへの設定方法はOS毎に様々なので割愛しますが、スマートフォンではQRコードで設定ファイルを読み込む方法が用意されています。Linuxでは以下のコマンドでQRコードが作れます。

$ qrencode -t ansiutf8 < cl.conf

qrencodeはaptで入手可能です。

テストなど

ピアが作られないとき

設定が間違っているのだと思います。wg show コマンドで今の設定が見られます。wgコマンドだけでもピアの状態が確認できます。鍵の設定に間違いが無いか見直しましょう。

インターフェースにpingが通らない 

アドレス間違いの可能性が高いです。ip a コマンドなどでアドレスを確認しましょう。

LAN内と通信できない

フォワーディングの設定が出来ていない可能性があります。tracerouteも確認に使えます。ネットマスクがおかしい場合もあり得ます。ルーティングテーブルも見てみましょう。

VPN越しにインターネットに接続できない

NAPTの設定に問題があるかもしません。iptables -t nat -nvL等で確認。ルータやホストのiptablesなどのファイヤウォールなどの設定も確認します。

出来たこと

ラズパイでもVPNを作ることが出来ました。スループットはモバイルでしか試していないので、上限が50Mbpsほどですが、CPU使用率は50%ぐらいです。完全個人用になってしまいますが、ラズパイでも実用になりそうです。勿論まともなサーバでやれば大規模にも使えそうです。

普段使っているモバイルキャリアはOCNなのですが、未だにIPv6が降ってきません。このWireGuardを使えばIPv4でトンネルを作って、中にIPv6を通せるので、外出先からも自宅経由でIPv6環境が使えるようになりました。IPv6は test-ipv6.com 辺りで確認すると良いと思います。

また、自宅のDNSが使えるのも良いです。広告サイトやマルウェア配布サイトなどをまとめて0.0.0.0送りにしているので、モバイルでも快適な環境が手に入りそうです。