WireGuard: перспективный VPN
VPN придуман давно (IPSec - в 1998 году, например) и имеет множество областей применения - безопасный доступ для удаленных сотрудников, прозрачное объединение корпоративных сетей, безопасный доступ в интернет поверх небезопасных каналов, даже - уклонение от корпоративной и государственной цензуры. Протоколов VPN - целый выводок, а реализации (программы, ПАК, даже чистые аппаратные решения без ПО есть) - еще больше. При этом каждый каждый стандарт имеет свои недостатки. WireGuard - это очередной протокол VPN, попытка эти проблемы решить. При всех плюсах WG (про них - ниже) - он мало известен и на удивление плохо документирован. Эта статья - попытка устранить эти недостатки.
WireGuard - это протокол VPN, который был придуман и реализован полностью с нуля - с применением последних, самых свежих концепций в программировании и криптографии. Главная задача, которую ставил перед собой автор (Джейсон Доненфелд) - простота протокола и реализации и высокая скорость работы. Протокол предельно простой, по-моему даже PPP сложнее. Очень простое взаимодействие операционной системы с VPN - WireGuard добавляет в систему сетевой интерфейс типа wireguard (по умолчанию - wgX), маршрутизация трафика идет через него. VPN полностью реализован на уровне ядра (для Linux, для других операционных систем поддерживается работа в виде обычной программы) - он очень быстро работает. Трафик упаковывается в совершенно типовой UDP, не нужны специфичные IP-протоколы (как это сделано в PPP/IPSec). Автор решил не использовать классические legacy-протоколы шифрования и взял самые свежие, но доказанно безопасные протоколы:
- ChaCha20 (salsa) - симметричное потоковое шифрование данных
- Curve25519 ECDH - ассиметричное шифрование для авторизации и аутентификации
- Blake2s - для хеширования
По скорости работы WireGuard обгоняет IPSec, даже с учетом того, что IPSec жульничает - современные CPU часть криптопримитивов могут выполнять в одну инструкцию.
При этом по простоте и удобству настройки WireGuard проще OpenVPN.
В качестве бонуса - Торвальдс очень хвалил код WireGuard (а Торвальдс известен нетерпимостью к плохому коду и не станет раздавать незаслуженные комплименты) и обещал, что код войдет в базовое ядро в ближайшем будущем. Звучит очень интересно, не так ли?
Разумеется, минусы у него тоже есть, и не упомянуть их было неправильно:
- wireguard формально находится в стадии активной разработки. До сих пор не вышло ни одной формальной версии, то есть даже wireguard 0.0.0.1 - нет в природе. Авторы не обещают и не гарантируют обратной совместимости протокола, то есть при одном удачном обновлении у вас легко может рассыпаться сеть. За пол-года, что я использовал WG - у меня такого не было ни разу, но в теории такая возможность сохраняется до выхода стабильной версии.
- wireguard не является стандартным и стабильным решением, не существует физического устройства, которое его поддерживает официально (как IPSec, например). Частично можно компенсировать тем, что все современные linux-based прошивки для маршрутизаторов (dd-wrt, OpenWRT, tomato) - поддерживают его с минимальным набором телодвижений. OpenVPN это обстоятельство, кстати, никак не мешает - из железяк его поддерживает разве что mikrotik и делает это настолько отвратительно, что лучше бы не поддерживал вовсе.
- wireguard не проходил формального аудита безопасности. Протоколы, которые он используют - аудит проходили, реализация - нет. Прохождение аудита у авторов запланировано после стабилизации кодовой базы. Как следствие - сертифицировать решение на базе wireguard невозможно. Если вы работаете в банке - пожалуйста, не используйте wireguard.
- криптопримитивы в wireguard прибиты гвоздиками и поменять их невозможно. Только ECDH для авторизации, только salsa20 для потоковых шифров, только UDP в качестве транспорта. С одной стороны - это здорово упрощает жизнь – невозможно перепутать настройки протоколов и провести пару вечеров за отладкой. С другой - если в каком-то из этих примитивов найдут серьезную дыру - придется переписать пол-протокола или вообще все выкинуть. Частично компенсируется тем, что весь Bitcoin (и не только он) построен вокруг ECDH, так что если там найдут дыру - проблемы wiregaurd будут мелочью.
В целом я могу уверенно рекомендовать wireguard для личного использования или как VPN-протокол в малокритичных сетях (без SLA и с командой, которая готова с ним разбираться). В общем случае, если у вас есть OpenVPN на linux - WireGuard идеально может его заменить. WireGuard напоминает мне OpenVPN в те времена, когда тот еще только появился и не успел стать legacy - простой, удобный, быстрый в настройке VPN.
Установка
Debian/Ubuntu
Так, как стабильных релизов wireguard нет - нам потребуется DKMS для сборки модулей ядра. Пакеты для WireGuard для debian stretch/buster не выпускались, но они есть для debian sid. Добавим репозиторий unstable и создадим pin. Если добавить репозиторий и не создавать пин - все пакеты из sid приедут в ваш сервер. Удачно запущенный apt-get install превратит систему в кашу:
# echo "deb http://deb.debian.org/debian/ sid main" > /etc/apt/sources.list.d/unstable.list
# printf 'Package: *\nPin: release a=unstable\nPin-Priority: 90\n' > /etc/apt/preferences.d/limit-unstable.pref
К счастью, в ubuntu все необходимые пакеты уже есть и дополнительные телодвижения не нужны. Ставим модуль:
# apt-get update
# apt-get install wireguard wireguard-dkms wireguard-tools
Проверим, что все установилось:
# ip link add dev wg0 type wireguard
# ip link print
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[...]
25: wg0: <POINTOPOINT,NOARP> mtu 1420 qdisc noop state DOWN mode DEFAULT group default qlen 1
link/none
CentOS/RHEL/OracleUBL
Владельцам rpm-based дистрибутивов повезло больше - есть готовый отдельный репозиторий, так что достаточно поставить его и EPEL (хотя мне сложно представить rpm-based дистрибутив без EPEL):
# curl -Lo /etc/yum.repos.d/wireguard.repo https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo
# yum makecache
# yum install epel-release
# yum install wireguard-dkms wireguard-tools
Настройка
Сервер
Wireguard использует авторизацию только по ключам (keypair). Ключи генерирует сам. Авторизация клиента производится по его публичному ключу, то есть на сервере должны быть публичные ключи всех клиентов, а на клиентах - публичный ключ сервера. Создадим такой ключ:
server# umask 077
server# wg genkey > server.priv
server# wg pubkey < server.priv > server.pub
На выходе получим два ключа, приватный и публичный. В принципе wireguard можно полностью управлять через команду ip, но это не сказать, чтобы сильно удобно. Для того, чтобы упростить настройку, авторы wireguard написали утилиту wg-quick
. Она читает конфиг из /etc/wireguard/wgX.conf
и создает соответствующий интерфейс. Пример конфига /etc/wireguard/wg0.conf
:
[Interface]
Address = 192.168.254.1/24
SaveConfig = false
ListenPort = 25968
PrivateKey = [...]
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Что тут написано:
address
- адресное пространство VPN-сети. Этот адрес будет присвоен интерфейсу wg0. Все клиенты, которые подключены к одному интерфейсу - должны иметь адреса в одной подсети, иначе они не смогут общаться.saveconfig = false
- если что-то было изменено после wg-quick вручную (командой ip link) - по умолчанию изменения запишутся в конфиг. Побочный эффект - если во время работы wireguard вы что-то в конфиге поменяете и перезапустите wireguard - все изменения будут стерты.ListenPort
- на какой порт должны приходить UDP-датаграммы, предназначенные для этого конкретного интерфейса wireguard. Никто не запрещает создать несколько интерфейсов wireguard - каждый с отдельным ключом, адресным пространством и списком клиентов. Этот порт должен быть открыт снаружи для UDPPrivateKey
- ключ из server.priv (из шага выше).PostUp
- добавляет правила в фаерволл - разрешает форвардинг для интерфейса и включает nat. Эта строка вам не нужна, если правила добавлены вручную. В данном примере считается, что внешний интерфейс сервера - eth0.PostDown
- удаляет правила, который добавилPostUp
На всякий случай включим форвардинг пакетов:
server# sysctl -w net.ipv4.ip_forward=1
server# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
Клиент
Точно так же ставим wireguard и генерируем ключи, как в примере выше:
client# umask 077
client# wg genkey > client.priv
client# wg pubkey < client.priv > client.pub
А вот содержимое конфига будет слегка иным:
[Interface]
PrivateKey = <from client.priv>
Address = 192.168.254.2/24
ListenPort = 25967
Table=wire
[Peer]
PublicKey = <from server.pub>
Endpoint = <server ip>:25968
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 30
Что появилось в конфиге нового. Секция Interface как и раньше - отвечает за настройку самого сервиса wiregurad:
Address
- из той же подсети, что и адрес сервера, но уникальный.Table = wire
- нужно, чтобы wireguard знал, в какую таблицу записывать маршруты. Они описаны в разделе peer (о нем ниже) и в момент, когда связь с peer-ом (сервером) будет установлена - маршруты будут добавлены. Если таблицу не указывать - будет использована системная. Это может вызвать проблему, если VPN выдает маршрут по умолчанию (как здесь). Чуть ниже я покажу, как с этим жить.
Секция peer настраивает параметры подключения к другой стороне:
PublicKey
- публичный ключ сервера. Именно по нему клиент авторизует сервер.Endpoint
- внешний адрес и порт сервера.AllowedIPs
- обращения к каким адресам можно отправлять в этот туннель. При поднятии туннеля маршрут ко всем адресам (сетям), перечисленным тут - будет добавлен в таблицу маршрутизации из секции Interface. Если какой-то сети или адреса тут нет - ручное добавление маршрута не поможет, wireguard отклонит такой маршрут. В данном примере я использую wireguard-сервер как шлюз по умолчанию, то есть хожу в интернет через него. Если сетей нужно добавить несколько - их можно перечислить через запятую.PersistentKeepalive
- интервал keepalive в секундах. По умолчанию данные в туннеле есть только тогда, когда кто-то что-то через него передает. KeepAlive периодически пингует удаленную сторону и поддерживает соединение. Это важно в тех случаях, если вы сидите за nat - сервер не может поддерживать соединение с клиентом, nat gateway разорвет неактивное соединение. Если после этого сервер захочет передать пакет клиенту - он не сможет этого сделать.
Включаем wiregurad на клиенте и идем настраивать на сервер:
client# wg-quick up wg0
Чтобы клиент запускал wireguard автоматически - добавим его в автозагрузку:
client# systemctl enable wg-quick@wg0
Снова сервер
В /etc/wireguard/wg0.conf
нужно вписать клиента, которого мы только что создали выше. Добавим секцию:
[Peer]
PublicKey = <from client.pub>
AllowedIPs = 192.168.254.2/32, 192.168.1.1/24
Endpoint = <client public IP>:25967
Обратите внимание на то, что в AllowedIPs указан, в том числе, адрес интерфейса wireguard на клиенте. Это очень важно, без этого трафик ходить не будет. Про него часто забывают. В примере выше через VPN маршрутизируется сеть 192.168.1.0/24
, потому она указана тоже.
Поднимем VPN на сервере тоже и проверим:
server# wg-quick up wg0
server# wg show
interface: wg0
public key: [...]
private key: (hidden)
listening port: 25968
peer: [...]
endpoint: 1.2.3.4:25967
allowed ips: 192.168.254.2/32, 192.168.1.0/24
latest handshake: 1 minute, 35 seconds ago
transfer: 1.84 KiB received, 2.84 KiB sent
Настройка VPN, в общем-то, закончена. Как я и говорил выше - восхитительно просто.
Лирическое отступление: маршрутизация через таблицы и маркировка трафика
Как я уже упоминал выше - по умолчанию wireguard добавляет все маршруты, указанные в AllowedIPs в основную таблицу маршрутизации. Linux не поддерживает policy based routing, соответственно там нельзя создать “условные маршруты” вида “если клиент из офисной сети - маршрут в google пустить через VPN”. Default gateway через VPN вообще кончится потерей связи, так как сервер потеряет маршрут до VPN-сервера. Выход есть - и называется он “таблицы маршрутизации”. Linux поддерживает 255 таблиц маршрутизации (точнее - 251, так как есть зарезервированные таблицы, которые нельзя удалить). Для решения перечисленных выше проблем мы просто создадим отдельную таблицу маршрутизации, а потом с помощью iptables будем ловить трафик, предназначенный для VPN и отправлять его в нужную нам таблицу. При этом клиентская сеть сохранит доступ к серверу и мы сможем гибко настраивать потоки трафика. К примеру - пустить через VPN только трафик для google/youtube, а трафик в Яндекс пустить по старому маршруту по умолчанию. Создадим таблицу:
client# echo "200 wire" >> /etc/iproute2/rt_tables
Теперь создадим правило iptables, которое будет помечать нужный нам трафик. Этот трафик должен быть отправлен в VPN:
client# iptables -I PREROUTING -t mangle -i eth0 -j MARK --set-mark 1
В данном примере я отправляю в VPN весь трафик, который приходит на интерфейс eth0 (в моем случае это внутренний интерфейс)
Теперь нам нужно правило (ip rule), которое перенаправит трафик с меткой 0х1 в таблицу wire:
ip rule add fwmark 0x1 lookup wire
Чтобы правило не потерялось при перезагрузке сервера или пересоздании интерфейса - я набросал простейший скрипт и положил его в /etc/network/if-up.d
(это пример для debian). Таким образом правило будет добавляться каждый раз, когда интерфейс переходит в активное состояние (UP):
#!/bin/bash
if [[ ! $(ip rule list | grep "fwmark 0x1 lookup wire") ]]; then
/bin/ip rule add fwmark 0x1 lookup wire
fi
Этому скрипту нужно обязательно дать права на выполнение (chmod +x). Название скрипта не важно совершенно, главное - место, где файл лежит и права.
Еще одну проблему подарит rpfilter. В нормальной ситуации это полезная настройка - она позволяет отсекать фальшивые (forged) пакеты, которые пришли не на тот интефейс, с которого уходили. К примеру, у нас есть eth0 (192.168.1.1) и eth1 (172.16.1.1). Пакет отправляется с source address 192.168.1.2 (интерфейс eth0) а ответ приходит на eth1. Это или что-то странное (что редкость) или попытка взлома (что более вероятно). RPFilter отсекает такие пакеты. Проблема в том, что с точки зрения rpfilter пакеты, уходящие в другую таблицу - будут “проваливаться в никуда”, а ответные - “возникать из ниоткуда”. Чудеса телепортации rpfilter не любит, так что его придется отключить, иначе не видать вам трафика через vpn:
client# sysctl -w net.ipv4.conf.eth0.rp_filter=2
client# sysctl -w net.ipv4.conf.wg0.rp_filter=2
Чтобы эти изменения не пропали при перезагрузке - добавим их в /etc/sysctl.conf
:
client# printf "net.ipv4.conf.eth0.rp_filter=2\nnet.ipv4.conf.wg0.rp_filter=2" >> /etc/sysctl.conf
Лирическое отступление номер два: добавляем исключения. IPSET.
Иногда хочется пустить через VPN только часть трафика или наоборот - исключить часть трафика из прохождения через VPN. В принципе все это можно сделать исключительно правилами чистого iptables. Например, VPN для нескольких серверов будет выглядеть примерно так:
iptables -I PREROUTING -t mangle -i eth0 -d 1.2.3.4 -j MARK --set-mark 1
iptables -I PREROUTING -t mangle -i eth0 -d 1.2.5.5 -j MARK --set-mark 1
iptables -I PREROUTING -t mangle -i eth0 -d 1.2.5.6 -j MARK --set-mark 1
iptables -I PREROUTING -t mangle -i eth0 -d 1.2.6.7 -j MARK --set-mark 1
iptables -I PREROUTING -t mangle -i eth0 -d 1.2.7.8 -j MARK --set-mark 1
Выглядит, честно говоря, ужасно. Что намного хуже - работать будет довольно медленно, поскольку каждый пакет проходит все правила iptables по очереди - пока не наткнется на правило, которое его обработает. Пара тысяч правил застави задуматься даже весьма приличный сервер. Пара миллионов - уложит машину на лопатки, пакеты будут путешествовать по недрам netfilter минутами - это вечность. Для решения этой проблемы был придуман ipset. IPSet - это простой и очень эффективный способ хранить множество адресов в памяти компактно, а искать в этом списке - быстро. Память выделяется для ipset в момент создания set (даже, если set пуст). IPSet в кратчайшие сроки ответит на вопрос - есть ли в нем тот или иной адрес - время ответа приближено к O(1), то есть скорость проверки наличия адреса в ipset в общем-то не зависит от размера. В штатную поставку операционной системы ipset не входит, но ставится очень легко:
client# apt-get install ipset
IPSet предоставляет разные варианты sets - они позволяют хранить разные вещи, но требуют разного количества памяти на одну запись. Для примера создадим set, который хранит подсети (адрес + маску):
client# ipset create novpn hash:net hashsize 65535
это создаст set novpn емкостью 65535 записей. Изменить размер или тип сета после создания будет нельзя, так что выбирать придется с умом.
Удалим старое правило (которое отправляет в VPN весь трафик из внутренней сети) и добавим новое - оно будет отправлять трафик в VPN, если адреса нет в set-е novpn:
client# iptables -t mangle -D PREROUTING 1
client# iptables -I PREROUTING -t mangle -i eth0 -m set ! --match-set novpn dst -j MARK --set-mark 1
Теперь добавим какое-нибудь исключение. Например, avito.ru очень не любит дешевые VPS, на которых часто делают VPN-сервера - мошенники активно используют такие же сервера, чтобы воровать с авито данные, а потому большинство сетей крупнейших облачных провайдеров на авито просто забанены.
client# ipset add novpn 185.89.12.0/24
Проверим:
client# ipset list
Name: novpn
Type: hash:net
Revision: 6
Header: family inet hashsize 65536 maxelem 65536
Size in memory: 1376
References: 1
Members:
185.89.12.0/24
Теперь трафик к avito (185.89.12.0/24) пойдет напрямую, а весь остальной - через VPN.
К слову - можно сделать и наоборот, то есть пустить через VPN трафик, который попадает в ipset. Правило будет выглядеть чуть-чуть иначе:
client# iptables -I PREROUTING -t mangle -i eth0 -m set --match-set tovpn dst -j MARK --set-mark 1
Все остальные принципы работы с ipset сохраняются. Рекомендую.
Клиент: отдельный ноутбук на linux (roadwarrior)
Wireguard ставится ровно так же, как в примере выше:
notebook# apt-get install wireguard wireguard-dkms wireguard-tools
С ключами тоже никаких сюрпризов:
notebook# umask 077
notebook# wg genkey > notebook.priv
notebook# wg pubkey < notebook.priv > notebook.pub
Ключи создаются точно так же. Пример конфига ноутбука (/etc/wireguard/wg0.conf
):
[Interface]
PrivateKey = <from notebook.priv>
Address = 192.168.254.2/24
[Peer]
PublicKey = <from server.pub>
Endpoint = 1.2.3.4:25967
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 21
Включаем командой wg-quick up wg0
. Выключаем, соответственно - wg-quick down wg0
. Интеграции с netmanager пока что нет, так что включать-выключать конфиг графической кнопкой не выйдет.
Не забудьте добавить содержимое notebook.pub
на сервер - в секцию [Peer]
.
Задача со звездочкой - подключаем мобильное устройство
Wireguard есть как для iOS, так и для android. В первом случае он сделан как userspace процесс, но протокол реализует тот же. Работает, конечно, медленнее, но не думаю, что это будет заметно. Ставится как любое типичное приложение, а вот настройки создаются интересно.
Создадим на сервере ключи для мобильного:
server# wg genkey | tee mobile.priv | wg pubkey > mobile.pub
Добавим еще один peer в конфиг сервера:
[Peer]
PublicKey = <from mobile.pub>
AllowedIPs = 192.168.254.3/32
Перезапустим wg:
server# wg-quick down wg0
server# wg-quick up wg0
создадим конфиг для мобильного с примерно таким содержимым:
[Interface]
PrivateKey = <from mobile.priv>
Address = 192.168.254.3/32
DNS = 8.8.8.8
[Peer]
PublicKey = <from server.pub>
Endpoint = 1.2.3.4:25967
AllowedIPs = 0.0.0.0/0
В целом все выглядит знакомо. Теперь нужно передать конфиг на мобильное устройство. Проще всего это сделать, закодировав конфиг в QR-code:
server# qrencode -t ansiutf8 < mobile.conf
На выходе будет картинка - QR-код. Открываем wireguard на мобильном, жмем + -> from QR code -> показываем картинку. Готово, мобильное устройство получило конфиг и теперь VPN можно включать штатными средствами.
Ключи mobile и конфиг рекомендуется удалить с сервера - чтобы никто их не украл.
Выводы
WireGuard - прекрасная альтернатива openvpn уже сейчас. Простой, легко и быстро настраиваемый сервис. Работает очень стабильно, при этом - очень быстро. Я горячо рекомендую его как замену openvpn - как в корпоративном секторе, так и для личного использования.
Безопасного вам интернета!