mirror of
https://github.com/vvzvlad/trickster-vpn.git
synced 2024-12-26 02:41:00 +03:00
converting to md
This commit is contained in:
parent
897314cf58
commit
261f631e7a
567
article/article_draft.md
Normal file
567
article/article_draft.md
Normal file
@ -0,0 +1,567 @@
|
||||
Разные IP
|
||||
|
||||
https://www.reg.ru/web-tools/myip
|
||||
https://2ip.ru/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Разные реализации WG для macos:
|
||||
https://github.com/cloudflare/boringtun
|
||||
https://git.zx2c4.com/wireguard-go/about/
|
||||
https://tunsafe.com/user-guide/linux
|
||||
|
||||
|
||||
|
||||
---------
|
||||
|
||||
# Разбираемся с WireGuard и делаем свой умный VPN
|
||||
IMG_1589.PNG
|
||||
|
||||
Сейчас в РФ сложилась забавная ситуация: с одной стороны, РКН блокирует довольно много сайтов, к которым иногда нужен доступ, а с другой стороны, из-за DDOS-атак, многие крупные компании ограничили доступность своих сайтов из-за границы: например, pochta.ru, leroymerlin.ru, rt.ru, avito.ru, не открываются через VPN.
|
||||
Получается ситуация из мема:
|
||||
— Нужен твиттер!
|
||||
— Включить VPN!
|
||||
— А теперь заказать еду!
|
||||
— Выключить VPN!
|
||||
— А теперь выложить еду в инстаграм!
|
||||
— Включить VPN!
|
||||
|
||||
Каждый с этим борется как может. Например, на iphone [родными средствами](https://superg.ru/kak-nastroit-avtomaticheskoe-vklyuchenie-vpn-na-iphone/) можно настроить автоматизацию, которая будет запускать VPN при открытии определенных приложений (например, твиттера), а при выходе из них — выключать обратно.
|
||||
|
||||
Но на одной из картинок я видел, что последнюю реплику заменяют на "Включить VPN, но чуть-чуть!". Вот тем, что сейчас будем "включать чуть-чуть VPN" мы и займемся.
|
||||
Примечание душнилы: я прекрасно понимаю, что при таком использовании это скорее прокси, а не VPN, но с языком ничего не поделать — если все называют программы для обхода блокировок VPN, то лучшее, что можно тут сделать — постараться привыкнуть и получать удовольствие.
|
||||
Заодно чуть улучшим качество связи с локальными ресурсами: необходимость таскать трафик сначала до VPN вне страны, а потом обратно до сервера внутри ее драматично сказывается если не на скорости, то на задержке точно: даже на проводном интернете пинг в 4мс до яндекса легко превращается в 190мс, а на мобильном интернете — из 80мс в 240. Дополнительный хоп чуть ухудшит ситуацию, но далеко не так драматично.
|
||||
|
||||
Делать мы это будем на основе WireGuard — это относительно новая (разрабатыавется с 2016 года в отличии от OpenVPN (2002) и IPsec (где-то в том же районе)) технология VPN, которая была создана, по сути, одним человеком — zx2c4, которого в миру зовут Джейсоном Доненфельдом. Плюсы WG — скорость (особенно для Linux, где он может работать как модуль ядра начиная с 5.6 и Windows, где модуль для ядра выпустили порядка недели назад), низкие задержки, современная криптография, и простое использование конечным юзером.
|
||||
Ах да, еще UDP. UDP для туннелей это хорошо, потому что у TCP уже есть механизмы, которые позволяют ему работать на неидеальных соединениях, а UDP представляет из себя именно такое соединение. А когда вы засовываете TCP в TCP, то отказываетесь от большей части этих механизмов (инкапсулированный TCP-пакет будет гарантированно доставлен другой стороне, хотя протокол допускает недоставку), но все еще несете весь оверхед вида "хендшейк соединения для отправки хендшейка".
|
||||
|
||||
Особенно для одинокого пользователя-хакера приятна работа с шифрованием: нет ни необходимости в сертификатах и удостоверяющих центрах, ни в логинах-паролях, все, что нужно — это с тем пиром, с которым хотите установить соедиение, передать друг другу публичные ключи вашего интерфейса WG. Для больших компаний, это, конечно, будет скорее минусом, как и то, что WG — это только базовая часть полноценной большой инфраструктуры VPN.
|
||||
Но, например, именно WireGuard использовали в Cloudflare для своего WARP (https://blog.cloudflare.com/announcing-warp-plus/, https://blog.cloudflare.com/warp-technical-challenges/), правда, написав его собственную реализацию — boringtun.
|
||||
|
||||
Еще одним минусом WG является то, что трафик не обфусцирован — DPI может обнаружить трафик WireGuard, так что его можно довольно легко заблокировать (не говоря уж о блокировке UDP совсем, что почти не мешает вебу, но гарантированно ломает работу WireGuard). Для скрытия трафика рекомендуется использовать специализированное ПО — Cloak, Obfsproxy, Shadowsocks, Stunnel, SoftEther, SSTP, в конце-концов, SSH.
|
||||
|
||||
|
||||
Если очень упрощать, ключи работают следующим образом: у нас есть закрытый (приватный) ключ, из которого можно сгенерировать открытый, или публичный. Наоборот — нельзя, из открытого ключа мы получить закрытый никак не можем. После чего, мы можем зашифровать с помощью закрытого ключая какую-то строку, а при помощи открытого — расшифровать ее и тем самым убедиться, что у собеседника точно есть закрытый ключ, а значит, он тот, за кого себя выдает. Таким образом, мы можем без проблем публиковать открытый ключ — он всего лишь позволяет проверить подлинность автора, но не притвориться им. Это как в SSH — публичный ключ лежит на сервере, где его потеря небольшая беда: все что сможет сделать с ним злоумышленник это положить его на свой сервер, чтобы вы к нему могли подключиться с помощью закрытого ключа.
|
||||
Так вот, в WG первый этап подключения заключается в том, что каждая сторона с помощью зашифрованного приватным ключом сообщения доказывает собеседнику, что она именно она: это проверяется публичным ключом.
|
||||
Второй этап — это создание симметричных ключей для шифрования самого трафика.
|
||||
|
||||
|
||||
## Шаг первый: создаем и настраиваем два сервера.
|
||||
Один внутри страны — через него будет идти трафик на локальные ресурсы, а второй за границей. Далее я их буду называть **local** и **external**.
|
||||
Идеально, если **local** будет в вашей домашней сети, потому что при этом трафик на локальные ресурсы не будет отличаться от вашего домашнего трафика. Но для этого нужен какой-то хост дома, белый IP и возможность пробросить порт. У меня это виртуалка на домашнем сервер, но навереное, подойдет и малина (не пробовал, ей придется маршрутизировать весь трафик с устройств и держать в памяти ~11к маршрутов). Если дома хоста нет, то можно взять любой сервер у VDS-хостера (vdsina, ruvds), но могут быть проблемы у ресурсов-параноиков, которые блокируют подсети хостеров, полагая, что серверам их ресурс не нужен: на vdsina я такое ловил.
|
||||
Внешний сервер можно взять у тех же хостеров VDS, что и выше: у них есть зарубежные площажки, а можно выбрать иностранного хостера. Например, у меня 1984.hosting.
|
||||
Считаем, что на обоих серверах у нас Debian 11.
|
||||
|
||||
Ставим нужные нам пакеты:
|
||||
```apt update && apt install wireguard iptables ipcalc qrencode curl jq traceroute dnsutils ufw -y```
|
||||
|
||||
Включаем перенаправление трафика: в этом случае сервер, получив пакет, который предназначается ни одному из его адресов, не отбросит его, а попытается перенаправить в соответствии со своими маршрутами.
|
||||
```bash
|
||||
echo "net.ipv4.ip_forward=1" > > /etc/sysctl.conf```
|
||||
echo "net.ipv4.conf.all.forwarding=1" > > /etc/sysctl.conf
|
||||
sysctl -p /etc/sysctl.conf
|
||||
```
|
||||
Опционально (но очень удобно) сразу поменять hostname обоих серверов, чтобы не запутаться, где какая консоль:
|
||||
```bash
|
||||
hostnamectl set-hostname trickster-internal
|
||||
hostnamectl set-hostname trickster-external
|
||||
```
|
||||
|
||||
### Шаг второй: настраиваем WireGuard для связи двух серверов:
|
||||
Для начала генерируем ключи. Запускаем два раза **wg genkey** и получаем два приватных ключа:
|
||||
```bash
|
||||
root@trikster-internal:~# wg genkey
|
||||
kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=
|
||||
root@trikster-internal:~# wg genkey
|
||||
6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
|
||||
```
|
||||
|
||||
Утилита wg genkey не делает никакой магии, это просто аналог чего-то типа "```echo $RANDOM | md5sum | head -c 32 | base64```", только наверняка более криптостойкое: мы просто генерируем 32 байта случайных значений и представляем их в виде base64.
|
||||
|
||||
Создаем два конфига:
|
||||
На **internal**: ```/etc/wireguard/wg-internal.conf```
|
||||
```ini
|
||||
[Interface]
|
||||
Address = 10.20.30.1/32
|
||||
ListenPort = 17968
|
||||
PrivateKey = kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=
|
||||
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostUp = ip rule add from `ip route | awk '/default/ {print $3; exit}'` table main
|
||||
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostDown = ip rule del from `ip route | awk '/default/ {print $3; exit}'` table main
|
||||
```
|
||||
|
||||
На **external**: ```/etc/wireguard/wg-external.conf```
|
||||
```ini
|
||||
[Interface]
|
||||
Address=10.20.30.2/32
|
||||
PrivateKey=6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
|
||||
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
```
|
||||
|
||||
Секция Interface — это настройки конкретного сетевого интерфейса Wireguard, того, что будет виден в ```ip a```. Название интерфейса берется из название текущего файла конфигурации. У одного интерфейса всегда одна ключевая пара: у пиров этого интерфейса одинаковый публичный ключ.
|
||||
Но никто не мешает, если хочется, сделать под каждого пира отдельный конфиг-файл, и отдельный интерфейс (правда, на сотнях клиентов это будет неудобно).
|
||||
|
||||
Управляются интерфейсы обычно при помощи утилиты **wg-quick**:
|
||||
```wg-quick down wg-external``` и ```wg-quick up wg-external```
|
||||
|
||||
Утилита **wg-quick** — это, на самом деле, 400 строк на баше, которые автоматизируют частоиспользуемые вещи: например, установку маршрутов. Сам факт туннеля не делает ничего, кроме создания "трубы" за которой находится другой пир. Для того, чтобы ваш запрос в браузере попал в интерфейс, системе надо явно сказать "маршрутизируй, пожалуйста, пакеты с таким-то адресом назначения вот в этот сетевой интерфейс".
|
||||
|
||||
Именно этим занимается **wg-quick**. Ну еще и настройкой DNS, указанных в конфиге и установкой MTU. Но ничего сложного в этом нет, достаточно сделать "```cat /usr/bin/wg-quick```", чтобы посмотреть на эту логику, и если надо, сделать тоже самое руками.
|
||||
|
||||
|
||||
**Interface-Address** — это IP текущего пира. Вся адерсация в WG статическая. С одной стороны, это упрощает настройку и бутстрап, с другой стороны, усложняет работу, если у вас очень много клиентов.
|
||||
**ListenPort** — это UDP-порт для подключения извне. Если не указать, будет прослушивать 51820. Если этот пир будет только подключаться к другим клиентам, можно и не использовать.
|
||||
**Interface-PostUp** и **PostDown** — это скрипты, выполняющиеся после поднятия и после остановки интерфейса. Есть еще **PreUP** и **PreDown**.
|
||||
|
||||
Кроме публичных и приватных ключей есть еще опция **PresharedKey**, которая обеспечивает дополнительное шифрование симметричным шифром. Ключ генерируется командой ```wg genpsk``` и кладется в **PresharedKey** в секциях **Peer** на обоих пирах. Неиспользование этой опции не снижает нагрузку по шифровке-расшифровке: если ключ не указан, используется нулевое значение ключа. Для действительного обеспечения пост-квантовой безопасности (невозможности расшифровки данных квантовыми компьютерами) разработчики рекомендуют дополнительный внешний квантово-устойчивый механизм хендшека (например, Microsoft SIDH, который они пиарят именно в таком контексте), чей найденный общий ключ можно использовать в качестве **PresharedKey**.
|
||||
|
||||
Заклинания в PostUp достаточно просты. ````ip route | awk '/default/ {print $5; exit}'```` — это команда для подстановки имени сетевого интерфейса, куда по-умолчанию выполняется маршрутизация: как правило, это тот интерфейс, в который воткнут провайдер или роутер.
|
||||
````ip route | awk '/default/ {print $3; exit}'```` — тоже самое, но подставляет IP-адрес дефолтного маршрута. Таким образом, первая страшная команда упрощается и превращается в ```iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE```, которая представляет собой включение NAT в режиме маскарада: сервер будет отправлять пришедшие ему пакеты пакеты во внешнюю сеть, подменяя в них адрес отправителя на свой, чтобы ответы на эти пакеты тоже приходили ему, а не исходному отправителю.
|
||||
Вторая страшная команда превращается в ```ip rule add from 95.93.219.123 table main``` — это необходимо для сервера **internal**, потому что иначе при активации маршрута 0.0.0.0/0 он начинает пересылать ответы на пакеты, приходящие ему на внешние адреса через туннель WG. Сервер на том конце, конечно, пересылает их по назначению, но тут уже не готов отправитель пакета: он присылает что-то на внешний адрес сервера **internal**, а ответ ему приходит с **external**. Естественно, при включенном rp_filter пакет отбрасывается. В этом случае сервер перестает быть доступен, например, по SSH снаружи, к нему надо коннектиться только по внутреннему IP wireguard-а. Отключать rp_filter у сервера это из пушки по воробьям, а вот дополнительное правило исправляет ситуацию.
|
||||
|
||||
Продолжим писать конфиг: в него надо добавить секцию **Peer**, чтобы связать их с друг-другом.
|
||||
Я намеренно не привожу сразу готовые конфиги, потому что хочу показать механизм создания конфигов в ручном режиме — в свое время у меня были проблемы с тем, что я генерировал конфиги утилитами типа ```easy-wg-quick```, которые спрашивают тебя о названии клиента и красиво показывают QR-код прям в консоли, но отнюдь не способствуют пониманию того, как работает WG на самом деле.
|
||||
|
||||
Итак, добавляем в каждый по секции **Peer**, для чего генерируем из приватного ключа публичный (вот в pubkey как раз происходит крипто-магия):
|
||||
```bash
|
||||
echo "kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=" | wg pubkey
|
||||
MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
```
|
||||
|
||||
Это публичный ключ сервера internal, его мы помещаем в секцию **peer** на **external**:
|
||||
```/etc/wireguard/wg-external.conf```
|
||||
```ini
|
||||
[Peer]
|
||||
PublicKey=MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
AllowedIPs=10.20.30.0/24
|
||||
Endpoint=195.2.79.13:17968
|
||||
PersistentKeepalive=25
|
||||
```
|
||||
|
||||
Там же, в **Endpoint** указываем адрес сервера **internal** и порт, который мы задали в **ListenPort**.
|
||||
С **AllowedIPs** при использовании ```wg-quick``` возникает небольшая путаница: это изначально именно, то как оно называется — список разрешенных IP-адресов к приему из туннеля: если что-то прилетает с другим src, оно будет отброшено. Но при использовании ```wg-quick``` она разумно считает, что если там есть какие-то устройства, которые могут послать пакет, то значит пакеты к этим устройствам надо маршрутизировать туда же, и создает маршруты на эти адреса, указывающие на туннель пира. В данных примерах **AllowedIPs** можно читать как "адреса, трафик на которые будут маршрутизироваться в туннель этого пира и с которых пир сможет отправить что-то в туннель".
|
||||
|
||||
Т.е. пункт "```AllowedIPs = 10.20.30.3/32```" означает, буквально, "только запросы на 10.20.30.3 (адрес пира WG) отправлять в туннель", т.е. дать доступ только до машины этого клиента.
|
||||
Пункт "```AllowedIPs = 192.168.88.0/24```" означает, что при запросе адреса из этой подсети, этот запрос уйдет в туннель клиента, и если у него включен форвардинг и ему доступна эта подсеть, то к ней можно будет получить доступ.
|
||||
А "```AllowedIPs = 0.0.0.0/0```" означает, что в туннель надо маршрутизировать весь трафик вообще. Правда, это не относится к трафику, например, локальной сети: приоритет у маршрута, который создастся из маски подсети и адреса шлюза, выше чем у 0.0.0.0/0. Также, маршрут 0.0.0.0/0 перебьют маршруты других пиров, если они будут в конфиге.
|
||||
В данном случае "```AllowedIPs=10.20.30.0/24```" — означает что трафик с **external** в подсеть 10.20.30.0-10.20.30.255 будет уходить в туннель к **internal**. В принципе, нужды в этом особо нет, **external** у нас исключительно выходная нода. Но вдруг мы как-нибудь захотим зайти оттуда по ssh на какую-нибудь другую машину.
|
||||
|
||||
Повторяем генерацию публичного ключа с **external**:
|
||||
```bash
|
||||
echo "6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=" | wg pubkey
|
||||
FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
||||
```
|
||||
Мы получаем публичный ключ сервера **external** и помещаем его в секцию peer сервера internal:
|
||||
```/etc/wireguard/wg-internal.conf```
|
||||
```ini
|
||||
[Peer] #external node
|
||||
PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
||||
AllowedIPs = 10.20.30.2/32, 0.0.0.0/0
|
||||
```
|
||||
|
||||
|
||||
AllowedIPs тут ```10.20.30.2/32, 0.0.0.0/0``` — указываем, что за туннелем находится конкретный IP 10.20.30.2 и помимо этого, пробрасываем весь трафик, не связанный другими маршрутами, в этот туннель: **external** у нас это основная выходная нода нашего VPN, так что по умолчанию весь трафик будет направляться через нее, т.к. зарубежных маршрутов больше, чем российских, и логичнее фильтровать именно российские, а зарубежный трафик пустить по умолчанию через ноду в другой стране.
|
||||
|
||||
Итак, два конфига:
|
||||
`/etc/wireguard/wg-internal.conf`
|
||||
```ini
|
||||
[Interface]
|
||||
Address = 10.20.30.1/32
|
||||
ListenPort = 17968
|
||||
PrivateKey = kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=
|
||||
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostUp = ip rule add from `ip route | awk '/default/ {print $3; exit}'` table main
|
||||
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostDown = ip rule del from `ip route | awk '/default/ {print $3; exit}'` table main
|
||||
|
||||
#external node
|
||||
[Peer]
|
||||
PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
||||
AllowedIPs = 10.20.30.2/32, 0.0.0.0/0
|
||||
|
||||
#mobile-client node
|
||||
[Peer]
|
||||
PublicKey = 26Vhud00ag/bdB9molvSxfBzZTlzdO+aZgrX3ZDncSg=
|
||||
AllowedIPs = 10.20.30.3/32
|
||||
```
|
||||
|
||||
|
||||
```/etc/wireguard/wg-external.conf```
|
||||
```ini
|
||||
[Interface]
|
||||
Address=10.20.30.2/32
|
||||
PrivateKey=6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
|
||||
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
|
||||
#internal node
|
||||
[Peer]
|
||||
PublicKey=MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
AllowedIPs=10.20.30.0/24
|
||||
Endpoint=195.2.79.13:17968
|
||||
PersistentKeepalive=25
|
||||
```
|
||||
|
||||
|
||||
Теперь можно поднять туннели на обоих серверах:
|
||||
```bash
|
||||
wg-quick down wg-external ; wg-quick up wg-external
|
||||
wg-quick down wg-internal ; wg-quick up wg-internal
|
||||
```
|
||||
|
||||
|
||||
Проверяем, что туннели активны командой wg:
|
||||
```bash
|
||||
root@trikster-internal:~# wg
|
||||
...
|
||||
peer: FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
||||
endpoint: 51.159.187.77:36276
|
||||
allowed ips: 10.20.30.2/32, 0.0.0.0/0
|
||||
latest handshake: 13 seconds ago
|
||||
transfer: 180 B received, 92 B sent
|
||||
|
||||
root@trikster-external:~# wg
|
||||
...
|
||||
peer: MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
endpoint: 195.2.79.13:17968
|
||||
allowed ips: 10.20.30.0/24
|
||||
latest handshake: 10 seconds ago
|
||||
transfer: 92 B received, 180 B sent
|
||||
persistent keepalive: every 25 seconds
|
||||
```
|
||||
|
||||
|
||||
Если видим "latest handshake" и байты и в received и в sent, значит, все ок. Если ,байты только в send, без хендшейка и полученных данных, где-то в ошибка в конфиге или сервера недоступны друг для друга.
|
||||
|
||||
Если что-то пошло не так, и отвалился ssh, то достаточно перезагрузить сервер. Если все хорошо, и доступ к серверам сохранился, ставим туннели в автозапуск:
|
||||
```bash
|
||||
systemctl enable wg-quick@wg-external.service
|
||||
systemctl enable wg-quick@wg-internal.service
|
||||
```
|
||||
|
||||
|
||||
Попробуем посмотреть маршрут (рекомендую замечательную утилиту ```mytraceroute```, ```mtr```) без туннеля:
|
||||
```bash
|
||||
root@trikster-internal:~# wg-quick down wg-internal && sleep 10 && mtr -r google.com
|
||||
HOST: trikster-internal.local Loss% Snt Last Avg Best Wrst StDev
|
||||
1.|-- host-89-22-232-243.hosted 0.0% 10 0.3 5.4 0.3 49.8 15.6
|
||||
2.|-- 172.31.0.1 0.0% 10 0.3 19.8 0.3 122.2 42.6
|
||||
3.|-- 109.239.138.90 0.0% 10 1.5 1.9 1.4 3.0 0.6
|
||||
4.|-- 91.108.51.4 0.0% 10 11.4 11.4 11.3 11.7 0.1
|
||||
5.|-- 178.18.227.12.ix.dataix.e 0.0% 10 11.0 17.9 11.0 77.0 20.8
|
||||
```
|
||||
|
||||
И с туннелем:
|
||||
```bash
|
||||
root@trikster-internal:~# wg-quick up wg-internal && sleep 10 && mtr -r google.com
|
||||
HOST: trikster-internal.local Loss% Snt Last Avg Best Wrst StDev
|
||||
1.|-- 10.20.30.2 0.0% 10 51.3 51.3 51.2 51.4 0.1
|
||||
2.|-- 10.200.100.0 0.0% 10 51.4 51.4 51.2 51.6 0.1
|
||||
3.|-- 10.197.37.65 0.0% 10 52.5 52.2 52.0 52.5 0.2
|
||||
4.|-- 10.197.0.41 0.0% 10 52.2 52.2 52.1 52.5 0.1
|
||||
5.|-- 10.197.0.44 0.0% 10 52.0 52.2 51.9 52.4 0.1
|
||||
```
|
||||
|
||||
Все хорошо, трафик идет через внешний сервер — сначала на 10.20.30.2, который у нас выходная нода, а потом через его маршрутизаторы.
|
||||
|
||||
|
||||
### Шаг третий: добавляем конфиг клиента
|
||||
Создаем конфиг клиента, конечного устройства-пользователя VPN. За основу берем ```wg-external.conf```, потому что он такой же точно клиент, который подключается к internal, разница только в том, что **external** получает пакеты, а наш клиент будет отправлять.
|
||||
|
||||
Генерируем ему сразу пару публичный-приватный ключ:
|
||||
```bash
|
||||
prk=`wg genkey` && pbk=`echo $prk | wg pubkey` && printf "Private: $prk\nPublic: $pbk\n"
|
||||
Private: iPK7hYSU8TLVRD+w13nd3aGSYNLfnTx6zwdRzKcGb1o=
|
||||
Public: 26Vhud00ag/bdB9molvSxfBzZTlzdO+aZgrX3ZDncSg=
|
||||
```
|
||||
|
||||
|
||||
Конфиг почти такой же:
|
||||
```/etc/wireguard/wg-notebook-client.conf```
|
||||
```ini
|
||||
[Interface]
|
||||
Address = 10.20.30.3/32
|
||||
PrivateKey = iPK7hYSU8TLVRD+w13nd3aGSYNLfnTx6zwdRzKcGb1o=
|
||||
DNS = 1.1.1.1, 8.8.8.8
|
||||
|
||||
#internal node
|
||||
[Peer]
|
||||
PublicKey = MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = 195.2.79.13:17968
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
Тут у нас добавилась опция **PersistentKeepalive**. Дело в том, что роутеры в цепочке между двумя пирами ничего не знают о сессии WG, а знают только о потоке UDP-пакетов. Для маршрутизации UDP-пакетов за NAT они создают у себя табличку, в которой записывают, кто куда и на какой порт отправил пакет. И если с destination-адреса/порта приходит UPD-пакет, то они определяют, куда его отправить по это таблице, делая вывод, что если сервер B недавно отправил пакет серверу А, то ответ от сервера А на этот же адрес и порт скорее всего надо переслать серверу B.
|
||||
А в отличии от TCP в UDP нет никаких договоренностей о поддержании сессии, т.к. нет и самого понятия сессии. WG же построен таким образом, что при отсуствии трафика, попадающего в туннель, не будет и трафика между пирами, только хедшейки раз в две минуты. Опция **PersistentKeepalive** заставлет его посылать пустые пакеты каждые 25 секунд, предовращая потерю маршрута на промежуточных роутерах, потому что иначе возможна ситуация, когда мы будем раз за разом отправить пакеты, а до второго пира они доходить не будут, а он об этом и не будет знать.
|
||||
|
||||
|
||||
Дальше мы для нашего клиента добавляем еще одну секцию peer в конфиг на internal:
|
||||
```ini
|
||||
#notebook-client node
|
||||
[Peer]
|
||||
PublicKey = 26Vhud00ag/bdB9molvSxfBzZTlzdO+aZgrX3ZDncSg=
|
||||
AllowedIPs = 10.20.30.3/32
|
||||
```
|
||||
|
||||
Перезапускаем туннель на internal (```wg-quick down/up```), подключаемся.. Оп, хендшейк есть, данные пошли.
|
||||
Открываем какой-нибудь https://www.reg.ru/web-tools/myip, видим IP external ноды, и другую страну.
|
||||
|
||||
Таким же образом создаем конфиги для других клиентов. Если это мобильные устройства, то удобнее показать им QR. Он делается следующим образом: создаем в текущей папке конфиг как обычно, конечно, с новыми ключами и другим IP, какой-нибудь ```wg-moblie-client.conf``` и дальше командой ```qrencode -t ansiutf8 < wg-moblie-client.conf``` показываем прям в консоли QR, который сканируем с телефона.
|
||||
Это удобнее копирования файлов, но вам так же никто не мешает скинуть ```wg-moblie-client.conf``` на телефон или вообще ввести значения 7 полей вручную.
|
||||
|
||||
В целом, готово: мы только что сделали очень странный двуххоповый VPN. Желаем это отметить и заказать себе пива, открываем сбермаркет..
|
||||
"СберМаркет не открывается. Если у вас работает VPN, отключите его". Ах да, мы же с этой проблемой и собирались бороться... Неловко.
|
||||
Давайте доделаем.
|
||||
|
||||
## Шаг четвертый: добавляем регион-зависимую маршрутизацию.
|
||||
Как мы помним, мы отправляем все данные с клиента на **internal**, а тот все данные отправляет на external, а тот уже своему провайдеру. Так же, мы помним, что у нас на **internal** "слабый" маршрут 0.0.0.0/0, который перебивается любыми другими маршрутами, а сам **internal** находится в российском сегменте. Значит, все, что нам надо — это как-то перехватить запросы на российские IP на уровне **internal** и перенаправить их не в туннель WG до **external**, а напрямую в сетевой порт самого сервера, в тот, через который он получает доступ в православный, российский интернет со скрепами и девицами в кокошниках.
|
||||
|
||||
Давайте проверим предположение. На клиенте получим IP того же сбермаркета (```nslookup sbermarket.ru```), и посмотрим, как туда идет трафик (```traceroute 212.193.158.175```):
|
||||
```bash
|
||||
HOST: vvzvladMBP14.local Loss% Snt Last Avg Best Wrst StDev
|
||||
1.|-- 10.20.30.1 0.0% 10 3.9 4.3 3.2 6.5 1.1
|
||||
2.|-- 10.20.30.2 0.0% 10 55.7 56.0 54.6 59.2 1.2
|
||||
3.|-- 10.200.100.0 0.0% 10 55.5 56.1 54.9 58.6 1.1
|
||||
4.|-- 10.197.37.65 0.0% 10 56.0 56.9 55.4 60.1 1.7
|
||||
5.|-- 10.197.0.41 0.0% 10 56.1 57.0 55.7 60.9 1.6
|
||||
```
|
||||
|
||||
Ага, как и ожидалось, через **external**.
|
||||
Теперь создадим маршрут до этого адреса через дефолтный шлюз и устройство. Их можно узнать в ```ip r```:
|
||||
```bash
|
||||
root@trikster-internal:~# ip r
|
||||
default via 195.2.79.1 dev ens3 onlink
|
||||
10.20.30.2 dev wg-internal scope link
|
||||
...
|
||||
```
|
||||
|
||||
Вот 195.2.79.1 и ens3 и есть нужные нам данные. Используем уже знакомые нам подстановочные команды и создадим новый маршрут такой командой:
|
||||
```bash
|
||||
target_ip="212.193.158.175/32"
|
||||
gateway=`ip route | awk '/default/ {print $3; exit}'`
|
||||
gateway_device=`ip route | awk '/default/ {print $5; exit}'`
|
||||
ip route add $target_ip via $gateway dev $gateway_device
|
||||
```
|
||||
|
||||
Проверяем:
|
||||
```bash
|
||||
root@trikster-internal:~# ip r
|
||||
default via 195.2.79.1 dev ens3 onlink
|
||||
10.20.30.2 dev wg-internal scope link
|
||||
10.20.30.3 dev wg-internal scope link
|
||||
195.2.79.0/24 dev ens3 proto kernel scope link src 195.2.79.13
|
||||
==> 212.193.158.175 via 195.2.79.1 dev ens3 <==
|
||||
```
|
||||
|
||||
Да, на последнем месте у нас нужный маршрут.
|
||||
|
||||
Теперь повторяем команду ```traceroute -r 212.193.158.175``` на клиенте, и видим, что трейс другой:
|
||||
```bash
|
||||
HOST: vvzvladMBP14.local Loss% Snt Last Avg Best Wrst StDev
|
||||
1.|-- 10.20.30.1 0.0% 10 4.3 7.9 3.7 29.1 7.9
|
||||
2.|-- host-89-22-232-243.hosted 0.0% 10 4.6 4.9 3.8 9.2 1.6
|
||||
3.|-- 172.31.0.1 0.0% 10 25.9 8.4 3.3 25.9 6.9
|
||||
4.|-- sw1-m9p2-msk.ip.ngenix.ne 0.0% 10 6.2 5.7 4.0 7.3 1.0
|
||||
5.|-- cdn.ngenix.net 0.0% 10 3.8 5.0 3.8 8.4 1.3
|
||||
```
|
||||
|
||||
Сбермаркет, правда, все еще не открываемся: видимо, проверяет на наличие VPN какой-то другой сервер, а не тот, в адрес которого ресолвится имя домена. Можно сходить на https://asnlookup.com/, вбить туда адрес, и получить принадлежность адреса к AS и заодно список подсетей этой AS (AS34879, OOO Sovremennye setevye tekhnologii). С большой вероятностью для более-менее крупных компаний это и будет их сетевая инфраструктура (ну или по крайней мере, инфраструктура, относящаяся к конкретному сайту), прописав для которой маршруты, вы обеспечите доступ на нужный вам сайт/сервис. Для мелких сайтов вы скорее всего получите AS хостера или дата-центра, но, во-первых, это тоже сработает, а во-вторых, мелкие сайты обычно и не закрывают иностранные диапазоны, потому что не испытывают проблем с DDOSом из-за границы.
|
||||
|
||||
Но можно сделать проще: засунуть в маршруты все адреса российского сегмента (спасибо [статье](https://habr.com/en/post/659655/) на хабре) и не париться о ручном добавлении.
|
||||
RIPE отдает их все в виде JSON вот по этому адресу: https://stat.ripe.net/data/country-resource-list/data.json?resource=ru
|
||||
Утилита jq преобразует из json в список подсетей: ```curl https://stat.ripe.net/data/country-resource-list/data.json?resource=ru | jq -r ".data.resources.ipv4[]"```
|
||||
Правда, почему-то некоторые адреса там в формате ```195.85.234.0-195.85.236.255```, а не подсети, поэтому для них нам необходима еще утилита ipcalc:
|
||||
```bash
|
||||
root@trikster-internal:~# ipcalc 195.85.234.0-195.85.236.255 |grep -v "deaggregate"
|
||||
195.85.234.0/23
|
||||
195.85.236.0/24
|
||||
```
|
||||
|
||||
Выделить эти адреса из базового списка можно банально через ```grep '-'``` или ```grep -v '/'```. Но их там немного, и на них, в принципе, можно забить.
|
||||
|
||||
Скрипт на баше выглядит как-то так (я не удержался и добавил туда еще и прогрессбар):
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
function ProgressBar {
|
||||
let _progress=(${1}*100/${2}*100)/100
|
||||
let _done=(${_progress}*4)/10
|
||||
let _left=40-$_done
|
||||
_fill=$(printf "%${_done}s")
|
||||
_empty=$(printf "%${_left}s")
|
||||
printf "\rAddind routes : [${_fill// /#}${_empty// /-}] ${_progress}%%"
|
||||
}
|
||||
|
||||
#Variables
|
||||
file_raw="russian_subnets_list_raw.txt"
|
||||
file_user="subnets_user_list.txt"
|
||||
file_for_calc="russian_subnets_list_raw_for_calc.txt"
|
||||
file_processed="russian_subnets_list_processed.txt"
|
||||
gateway_for_internal_ip=`ip route | awk '/default/ {print $3; exit}'`
|
||||
interface=`ip link show | awk -F ': ' '/state UP/ {print $2}'`
|
||||
|
||||
#Get addresses RU segment
|
||||
echo "Download RU subnets..."
|
||||
curl --progress-bar "https://stat.ripe.net/data/country-resource-list/data.json?resource=ru" | jq -r ".data.resources.ipv4[]" > $file_raw
|
||||
|
||||
echo "Deaggregate subnets..."
|
||||
cat $file_raw |grep "-" > $file_for_calc
|
||||
cat $file_raw |grep -v "-" > $file_processed
|
||||
for line in $(cat $file_for_calc); do ipcalc $line |grep -v "deaggregate" >> $file_processed; done
|
||||
|
||||
if [ -e $file_user ]; then echo "Add user subnets..."; cat $file_user >> $file_processed; fi
|
||||
|
||||
#Flush route table
|
||||
echo "Flush route table (down and up interface)..."
|
||||
ifdown $interface && ifup $interface
|
||||
|
||||
#Add route
|
||||
routes_count_in_file=`wc -l $file_processed`
|
||||
routes_count_current=0
|
||||
for line in $(cat $file_processed); do ip route add $line via $gateway_for_internal_ip dev $interface; let "routes_count_current+=1" ; ProgressBar ${routes_count_current} ${routes_count_in_file}; done
|
||||
echo ""
|
||||
|
||||
echo "Remove temp files..."
|
||||
rm $file_raw $file_processed $file_json $file_for_calc
|
||||
|
||||
routes_count=`ip r | wc -l`
|
||||
echo "Routes in routing table: $routes_count"
|
||||
```
|
||||
|
||||
|
||||
Добавим строчки в крон (```export EDITOR=nano; crontab -e```), чтобы он запускался каждую неделю (для того, чтобы обновить список адресов, если они поменялись) и после перезагрузки:
|
||||
```
|
||||
@reboot sleep 30 && bash /root/update_ru_routes.sh > /root/update_routes_log.txt 2>&1
|
||||
0 3 * * mon bash /root/update_ru_routes.sh > /root/update_routes_log.txt 2>&1
|
||||
```
|
||||
|
||||
Если вам принудительно надо маршрутизовать какую-то сеть через **internal**, то можно рядом со скриптом создать файлик ```subnets_user_list.txt``` в который поместить список подсетей, тогда они каждый раз будут добавляться к общему списку при обновлении, в bash-скрипте это есть.
|
||||
|
||||
## Шаг пятый: настраиваем фаервол
|
||||
Для начала на обоих серверах редактируем файл ```/etc/default/ufw```, изменяя значение "**DEFAULT_FORWARD_POLICY**" на **ACCEPT**.
|
||||
|
||||
Теперь выполняем следующие команды на **internal**:
|
||||
```bash
|
||||
ufw reset
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw allow ssh
|
||||
ufw allow 17968/udp
|
||||
ufw allow in on wg-internal
|
||||
systemctl enable ufw --now
|
||||
ufw enable
|
||||
```
|
||||
Что происходит, думаю, понятно — запретить все, разрешить исходящие, входящие ssh и подключения к WG, а что приходит из туннеля — разрешить.
|
||||
|
||||
На **external** тоже самое, но открывать порт для WG не надо — он подключается сам.
|
||||
```bash
|
||||
ufw reset
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw allow ssh
|
||||
ufw allow in on wg-external
|
||||
systemctl enable ufw --now
|
||||
ufw enable
|
||||
```
|
||||
|
||||
Еще хорошо бы поставить и настроить fail2ban, или хотя бы перенести ssh на другой порт. В любом случае, отключение парольной авторизации на ssh вообще и переход только на ключи — это базовая операция.
|
||||
|
||||
## Шаг шестой, бонусный и необязательный: кеширующий защищенный DNS over HTTPS
|
||||
Теперь нам нужна еще одна вещь: DNS. Можно, конечно, жить с DNS 1.1.1.1, но надо учитывать две вещи:
|
||||
Трафик на него пойдет через **external**, что автоматически означает задержку порядка 100мс при каждом запросе. Можно, конечно, добавить 1.1.1.1/32 в ```subnets_user_list.txt```, и тогда трафик пойдет через локальную ноду и локальный сервер 1.1.1.1, что уменьшит задержку до 10-20мс, но ваши DNS-запросы будет доступны вашему провайдеру, что в случае локальной ноды может быть для кого-то неприемлимо. Несколькими командами можно легко сделать кеширующий DNS, который еще и будет работать с DNS over HTTPS, а значит, провайдеру будет доступен только сам факт использовани DoH, но не сами запросы. Но это, конечно, не обязательно: у меня **internal** находится в домашней сети, и я просто использую DNS микротика, который находится в той же сети. Но если у вас **internal** сервер это VPS, то можно сделать там и DNS сервер. Использовать будем ```cloudflared```.
|
||||
|
||||
Добавляем репозитарий:
|
||||
```bash
|
||||
mkdir -p --mode=0755 /usr/share/keyrings
|
||||
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | tee /usr/share/keyrings/cloudflare-main.gpg > /dev/null
|
||||
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared buster main' | tee /etc/apt/sources.list.d/cloudflared.list
|
||||
sudo apt-get update && sudo apt-get install cloudflared dnsmasq -y
|
||||
```
|
||||
|
||||
Пишем конфиг: ```/etc/cloudflared/config.yml```:
|
||||
```yml
|
||||
logfile: /var/log/cloudflared.log
|
||||
proxy-dns: true
|
||||
proxy-dns-upstream:
|
||||
- https://1.0.0.1/dns-query
|
||||
- https://1.1.1.1/dns-query
|
||||
- https://2606:4700:4700::1111/dns-query
|
||||
- https://2606:4700:4700::1001/dns-query
|
||||
proxy-dns-port: 5353
|
||||
proxy-dns-address: 127.0.0.1
|
||||
```
|
||||
|
||||
Либо, можете заменить в proxy-dns-upstream записи на https://security.cloudflare-dns.com/dns-query или https://9.9.9.9/dns-query — первое это блокировка всякой малвари от cloudflare, второе это Quad9.
|
||||
|
||||
Создаем сервис:
|
||||
```/etc/systemd/system/cloudflared.service```
|
||||
```ini
|
||||
[Unit]
|
||||
Description=DNS over HTTPS (DoH) proxy client
|
||||
Wants=network-online.target nss-lookup.target
|
||||
Before=nss-lookup.target
|
||||
|
||||
[Service]
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
DynamicUser=yes
|
||||
ExecStart=/usr/local/bin/cloudflared --config /etc/cloudflared/config.yml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
|
||||
Активируем и запускаем
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl enable cloudflared
|
||||
systemctl start cloudflared
|
||||
systemctl status cloudflared
|
||||
```
|
||||
|
||||
Проверяем:
|
||||
```dig @127.0.0.1 -p5353 google.com```
|
||||
|
||||
У cloudflared есть один минус — у него странное кеширование, которое держится совсем немного, поэтому для кеширования дополнительно настроим dnsmasq, указав ему в качестве сервера cloudflared.
|
||||
|
||||
Добавляем в ```/etc/dnsmasq.conf``` что-то вроде
|
||||
```ini
|
||||
server=127.0.0.1#5353
|
||||
no-poll
|
||||
no-resolv
|
||||
listen-address=10.20.30.1, 127.0.0.1
|
||||
cache-size=1500
|
||||
stop-dns-rebind
|
||||
clear-on-reload
|
||||
no-negcache
|
||||
```
|
||||
|
||||
Запускаем:
|
||||
```systemctl restart dnsmasq.service```
|
||||
|
||||
Проверяем:
|
||||
```dig @127.0.0.1 google.com```
|
||||
|
||||
И уже можно сделать это с клиента:
|
||||
```dig @10.20.30.1 google.com```
|
||||
|
||||
Если все ок, то можно проверить несколько раз — при повторных запросах Query time: должен стать 0 msec или около, если запрашиваете локалхост, или будет близко к пингу до **internal**, если делаете это с клиента.
|
||||
|
||||
Теперь можно добавить в конфиги клиентов в секцию **Interface**:
|
||||
```ini
|
||||
DNS = 10.20.30.1, 1.1.1.1
|
||||
```
|
||||
|
||||
P.S. особые параноики могут запустить cloudflared на **external**, и скрыть от локального провайдера даже сам факт использования DoH. Для этого в proxy-dns-address в конфиге cloudflared и в dnsmasq.conf надо просто указать 10.20.30.2.
|
||||
|
||||
Кстати, в качестве альтернативы можно поставить рядом на сервер [pi-hole](https://github.com/pi-hole/pi-hole/), который делает примерно тоже самое, но еще блокирует рекламу и показывает красивую статистику.
|
||||
|
||||
Пары ключей в статье — действующие, так что вы можете, ничего не исправляя в конфигах (только IP и имена адаптеров), залить их на два своих сервера и клиент и поиграться. Но для боевого применения ключи надо перегенерить, конечно.
|
||||
|
||||
Если кто-то захочет это все красиво обернуть в два докера и прикрутить к этому веб-интерфейс (потому что конфиги клиентов все же удобнее создавать в нем) — добро пожаловать в issues на гитхабе.
|
@ -1,486 +0,0 @@
|
||||
Разные IP
|
||||
|
||||
https://www.reg.ru/web-tools/myip
|
||||
https://2ip.ru/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Разные реализации WG для macos:
|
||||
https://github.com/cloudflare/boringtun
|
||||
https://git.zx2c4.com/wireguard-go/about/
|
||||
https://tunsafe.com/user-guide/linux
|
||||
|
||||
|
||||
|
||||
---------
|
||||
Разбираемся с WireGuard и делаем свой умный VPN
|
||||
IMG_1589.PNG
|
||||
|
||||
Сейчас в РФ сложилась забавная ситуация: с одной стороны, РКН блокирует довольно много сайтов, к которым иногда нужен доступ, а с другой стороны, из-за DDOS-атак, многие крупные компании ограничили доступность своих сайтов из-за границы: например, pochta.ru, leroymerlin.ru, rt.ru, avito.ru, не открываются через VPN.
|
||||
Получается ситуация из мема:
|
||||
— Нужен твиттер!
|
||||
— Включить VPN!
|
||||
— А теперь заказать еду!
|
||||
— Выключить VPN!
|
||||
— А теперь выложить еду в инстаграм!
|
||||
— Включить VPN!
|
||||
|
||||
Каждый с этим борется как может. Например, на iphone родными средствами можно настроить автоматизацию(https://superg.ru/kak-nastroit-avtomaticheskoe-vklyuchenie-vpn-na-iphone/), которая будет запускать VPN при открытии определенных приложений (например, твиттера), а при выходе из них — выключать обратно.
|
||||
|
||||
Но на одной из картинок я видел, что последнюю реплику заменяют на "Включить VPN, но чуть-чуть!". Вот тем, что сейчас будем "включать чуть-чуть VPN" мы и займемся.
|
||||
-- Примечание душнилы: я прекрасно понимаю, что при таком использовании это скорее прокси, а не VPN, но с языком ничего не поделать — если все называют программы для обхода блокировок VPN, то лучшее, что можно тут сделать — постараться привыкнуть и получать удовольствие. --
|
||||
Заодно чуть улучшим качество связи с локальными ресурсами: необходимость таскать трафик сначала до VPN вне страны, а потом обратно до сервера внутри ее драматично сказывается если не на скорости, то на задержке точно: даже на проводном интернете пинг в 4мс до яндекса легко превращается в 190мс, а на мобильном интернете — из 80мс в 240. Дополнительный хоп чуть ухудшит ситуацию, но далеко не так драматично.
|
||||
|
||||
Делать мы это будем на основе WireGuard — это относительно новая (разрабатыавется с 2016 года в отличии от OpenVPN(2002) и IPsec(где-то в том же районе)) технология VPN, которая была создана, по сути, одним человеком — zx2c4, которого в миру зовут Джейсоном Доненфельдом. Плюсы WG — скорость (особенно для Linux, где он может работать как модуль ядра начиная с 5.6 и Windows, где модуль для ядра выпустили порядка недели назад), низкие задержки, современная криптография, и простое использование конечным юзером. Ах да, еще UDP. UDP для туннелей это хорошо, потому что у TCP уже есть механизмы, которые позволяют ему работать на неидеальных соединениях, а UDP представляет из себя именно такое соединение. А когда вы засовываете TCP в TCP, то отказываетесь от большей части этих механизмов (инкапсулированный TCP-пакет будет гарантированно доставлен другой стороне, хотя протокол допускает недоставку), но все еще несете весь оверхед вида "хендшейк соединения для отправки хендшейка".
|
||||
|
||||
Особенно для одинокого пользователя-хакера приятна работа с шифрованием: нет ни необходимости в сертификатах и удостоверяющих центрах, ни в логинах-паролях, все, что нужно — это с тем пиром, с которым хотите установить соедиение, передать друг другу публичные ключи вашего интерфейса WG. Для больших компаний, это, конечно, будет скорее минусом, как и то, что WG — это только базовая часть полноценной большой инфраструктуры VPN.
|
||||
Но, например, именно WireGuard использовали в Cloudflare для своего WARP (https://blog.cloudflare.com/announcing-warp-plus/, https://blog.cloudflare.com/warp-technical-challenges/), правда, написав его собственную реализацию — boringtun.
|
||||
|
||||
Еще одним минусом WG является то, что трафик не обфусцирован — DPI может обнаружить трафик WireGuard, так что его можно довольно легко заблокировать (не говоря уж о блокировке UDP совсем, что почти не мешает вебу, но гарантированно ломает работу WireGuard). Для скрытия трафика рекомендуется использовать специализированное ПО — Cloak, Obfsproxy, Shadowsocks, Stunnel, SoftEther, SSTP, в конце-концов, SSH.
|
||||
|
||||
|
||||
Если очень упрощать, ключи работают следующим образом: у нас есть закрытый (приватный) ключ, из которого можно сгенерировать открытый, или публичный. Наоборот — нельзя, из открытого ключа мы получить закрытый никак не можем. После чего, мы можем зашифровать с помощью закрытого ключая какую-то строку, а при помощи открытого — расшифровать ее и тем самым убедиться, что у собеседника точно есть закрытый ключ, а значит, он тот, за кого себя выдает. Таким образом, мы можем без проблем публиковать открытый ключ — он всего лишь позволяет проверить подлинность автора, но не притвориться им. Это как в SSH — публичный ключ лежит на сервере, где его потеря небольшая беда: все что сможет сделать с ним злоумышленник это положить его на свой сервер, чтобы вы к нему могли подключиться с помощью закрытого ключа.
|
||||
Так вот, в WG первый этап подключения заключается в том, что каждая сторона с помощью зашифрованного приватным ключом сообщения доказывает собеседнику, что она именно она: это проверяется публичным ключом.
|
||||
Второй этап — это создание симметричных ключей для шифрования самого трафика.
|
||||
|
||||
|
||||
#Шаг первый: создаем и настраиваем два сервера.
|
||||
Один внутри страны — через него будет идти трафик на локальные ресурсы, а второй за границей. Далее я их буду называть local и external.
|
||||
Идеально, если local будет в вашей домашней сети, потому что при этом трафик на локальные ресурсы не будет отличаться от вашего домашнего трафика. Но для этого нужен какой-то хост дома, белый IP и возможность пробросить порт. У меня это виртуалка на домашнем сервер, но навереное, подойдет и малина (не пробовал, ей придется маршрутизировать весь трафик с устройств и держать в памяти ~11к маршрутов). Если дома хоста нет, то можно взять любой сервер у VDS-хостера (vdsina, ruvds), но могут быть проблемы у ресурсов-параноиков, которые блокируют подсети хостеров, полагая, что серверам их ресурс не нужен: на vdsina я такое ловил.
|
||||
Внешний сервер можно взять у тех же хостеров VDS, что и выше: у них есть зарубежные площажки, а можно выбрать иностранного хостера. Например, у меня 1984.hosting.
|
||||
Считаем, что на обоих серверах у нас Debian 11.
|
||||
|
||||
Ставим нужные нам пакеты:
|
||||
apt update && apt install wireguard iptables ipcalc qrencode curl jq traceroute dnsutils -y
|
||||
|
||||
Включаем перенаправление трафика: в этом случае сервер, получив пакет, который предназначается ни одному из его адресов, не отбросит его, а попытается перенаправить в соотвествии со своими маршрутами.
|
||||
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
|
||||
echo "net.ipv4.conf.all.forwarding=1" >> /etc/sysctl.conf
|
||||
sysctl -p /etc/sysctl.conf
|
||||
Опционально (но очень удобно) сразу поменять hostname обоих серверов, чтобы не запутаться, где какая консоль:
|
||||
hostnamectl set-hostname trickster-internal
|
||||
hostnamectl set-hostname trickster-external
|
||||
|
||||
#Шаг второй: настраиваем WireGuard для связи двух серверов:
|
||||
Для начала генерируем ключи. Запускаем два раза wg genkey и получаем два приватных ключа:
|
||||
root@trikster-internal:~# wg genkey
|
||||
kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=
|
||||
root@trikster-internal:~# wg genkey
|
||||
6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
|
||||
|
||||
Утилита wg genkey не делает никакой магии, это просто аналог чего-то типа "echo $RANDOM | md5sum | head -c 32 | base64", только наверняка более криптостойкое: мы просто генерируем 32 байта случайных значений и представляем их в виде base64.
|
||||
|
||||
Создаем два конфига:
|
||||
На internal: /etc/wireguard/wg-internal.conf
|
||||
[Interface]
|
||||
Address = 10.20.30.1/32
|
||||
ListenPort = 17968
|
||||
PrivateKey = kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=
|
||||
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostUp = ip rule add from `ip route | awk '/default/ {print $3; exit}'` table main
|
||||
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostDown = ip rule del from `ip route | awk '/default/ {print $3; exit}'` table main
|
||||
|
||||
На external: /etc/wireguard/wg-external.conf
|
||||
[Interface]
|
||||
Address=10.20.30.2/32
|
||||
PrivateKey=6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
|
||||
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
|
||||
Секция Interface — это настройки конкретного сетевого интерфейса Wireguard, того, что будет виден в "ip a". Название интерфейса берется из название текущего файла конфигурации. У одного интерфейса всегда одна ключевая пара: у пиров этого интерфейса одинаковый публичный ключ. Но никто не мешает, если хочется, сделать под каждого пира отдельный конфиг-файл, и отдельный интерфейс (правда, на сотнях клиентов это будет неудобно).
|
||||
|
||||
Управляются интерфейсы обычно при помощи утилиты wg-quick:
|
||||
wg-quick down wg-external и wg-quick up wg-external
|
||||
|
||||
Утилита wg-quick — это, на самом деле, 400 строк на баше, которые автоматизируют частоиспользуемые вещи: например, установку маршрутов. Сам факт туннеля не делает ничего, кроме создания "трубы" за которой находится другой пир. Для того, чтобы ваш запрос в браузере попал в интерфейс, системе надо явно сказать "маршрутизируй, пожалуйста, пакеты с таким-то адресом назначения вот в этот сетевой интерфейс". Именно этим занимается wg-quick. Ну еще и настройкой DNS, указанных в конфиге и установкой MTU. Но ничего сложного в этом нет, достаточно сделать "cat /usr/bin/wg-quick", чтобы посмотреть на эту логику, и если надо, сделать тоже самое руками.
|
||||
|
||||
Interface-Address — это IP текущего пира. Вся адерсация в WG статическая. С одной стороны, это упрощает настройку и бутстрап, с другой стороны, усложняет работу, если у вас очень много клиентов.
|
||||
ListenPort — это UDP-порт для подключения извне. Если не указать, будет прослушивать 51820. Если этот пир будет только подключаться к другим клиентам, можно и не использовать.
|
||||
Interface-PostUp и PostDown — это скрипты, выполняющиеся после поднятия и после остановки интерфейса. Есть еще PreUP и PreDown.
|
||||
|
||||
Кроме публичных и приватных ключей есть еще опция PresharedKey, которая обеспечивает дополнительное шифрование симметричным шифром. Ключ генерируется командой wg genpsk и кладется в PresharedKey в секциях Peer на обоих пирах. Неиспользование этой опции не снижает нагрузку по шифровке-расшифровке: если ключ не указан, используется нулевое значение ключа. Для действительного обеспечения пост-квантовой безопасности (невозможности расшифровки данных квантовыми компьютерами) разработчики рекомендуют дополнительный внешний квантово-устойчивый механизм хендшека (например, Microsoft SIDH, который они пиарят именно в таком контексте), чей найденный общий ключ можно использовать в качестве PresharedKey.
|
||||
|
||||
Заклинания в PostUp достаточно просты. `ip route | awk '/default/ {print $5; exit}'` — это команда для подстановки имени сетевого интерфейса, куда по-умолчанию выполняется маршрутизация: как правило, это тот интерфейс, в который воткнут провайдер или роутер. `ip route | awk '/default/ {print $3; exit}'` — тоже самое, но подставляет IP-адрес дефолтного маршрута. Таким образом, первая страшная команда упрощается и превращается в "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE", которая представляет собой вклчение NAT в режиме маскарада: сервер будет отправлять пришедшие ему пакеты пакеты во внешнюю сеть, подменяя в них адрес отправителя на свой, чтобы ответы на эти пакеты тоже приходили ему, а не исходному отправителю.
|
||||
Вторая страшная команда превращается в "ip rule add from 95.93.219.123 table main" — это необходимо для сервера internal, потому что иначе при активации маршрута 0.0.0.0/0 он начинает пересылать ответы на пакеты, приходящие ему на внешние адреса через туннель WG. Сервер на том конце, конечно, пересылает их по назначению, но тут уже не готов отправитель пакета: он присылает что-то на внешний адрес сервера internal, а ответ ему приходит с external. Естественно, при включенном rp_filter пакет отбрасывается. В этом случае сервер перестает быть доступен, например, по SSH снаружи, к нему надо коннектиться только по внутреннему IP wireguard-а. Отключать rp_filter у сервера это из пушки по воробьям, а вот дополнительное правило исправляет ситуацию.
|
||||
|
||||
|
||||
Продолжим писать конфиг: в него надо добавить секцию Peer, чтобы связать их с друг-другом.
|
||||
Я намеренно не привожу сразу готовые конфиги, потому что хочу показать механизм создания конфигов в ручном режиме — в свое время у меня были проблемы с тем, что я генерировал конфиги утилитами типа easy-wg-quick, которые спрашивают тебя о названии клиента и красиво показывают QR-код прям в консоли, но отнюдь не способствуют пониманию того, как работает WG на самом деле.
|
||||
|
||||
Итак, добавляем в каждый по секции Peer, для чего генерируем из приватного ключа публичный:
|
||||
echo "kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=" | wg pubkey
|
||||
MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
|
||||
Это публичный ключ сервера internal, его мы помещаем в секцию peer на external:
|
||||
/etc/wireguard/wg-external.conf
|
||||
[Peer]
|
||||
PublicKey=MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
AllowedIPs=10.20.30.0/24
|
||||
Endpoint=195.2.79.13:17968
|
||||
PersistentKeepalive=25
|
||||
|
||||
Там же, в Endpoint указываем адрес сервера internal и порт, который мы задали в ListenPort.
|
||||
С AllowedIPs при использовании wg-quick возникает небольшая путаница: это изначально именно, то как оно называется — список разрешенных IP-адресов к приему из туннеля: если что-то прилетает с другим src, оно будет отброшено. Но при использовании wg-quick она разумно считает, что если там есть какие-то устройства, которые могут послать пакет, то значит пакеты к этим устройствам надо маршрутизировать туда же, и создает маршруты на эти адреса, указывающие на туннель пира. В данных примерах AllowedIPs можно читать как "адреса, трафик на которые будут маршрутизироваться в туннель этого пира и с которых пир сможет отправить что-то в туннель".
|
||||
|
||||
Т.е. пункт "AllowedIPs = 10.20.30.3/32" означает, буквально, "только запросы на 10.20.30.3 (адрес пира WG) отправлять в туннель", т.е. дать доступ только до машины этого клиента.
|
||||
Пункт "AllowedIPs = 192.168.88.0/24" означает, что при запросе адреса из этой подсети, этот запрос уйдет в туннель клиента, и если у него включен форвардинг и ему доступна эта подсеть, то к ней можно будет получить доступ.
|
||||
А "AllowedIPs = 0.0.0.0/0" означает, что в туннель надо маршрутизировать весь трафик вообще. Правда, это не относится к трафику, например, локальной сети: приоритет у маршрута, который создастся из маски подсети и адреса шлюза, выше чем у 0.0.0.0/0. Также, маршрут 0.0.0.0/0 перебьют маршруты других пиров, если они будут в конфиге.
|
||||
В данном случае "AllowedIPs=10.20.30.0/24" — означает что трафик с external в подсеть 10.20.30.0-10.20.30.255 будет уходить в туннель к internal. В принципе, нужды в этом особо нет, external у нас исключительно выходная нода. Но вдруг мы как-нибудь захотим зайти оттуда по ssh на какую-нибудь другую машину.
|
||||
|
||||
Повторяем генерацию публичного ключа с external:
|
||||
echo "6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=" | wg pubkey
|
||||
FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
||||
Мы получаем публичный ключ сервера external и помещаем его в секцию peer сервера internal:
|
||||
|
||||
/etc/wireguard/wg-internal.conf
|
||||
[Peer] #external node
|
||||
PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
||||
AllowedIPs = 10.20.30.2/32, 0.0.0.0/0
|
||||
|
||||
|
||||
AllowedIPs тут "10.20.30.2/32, 0.0.0.0/0" — указываем, что за туннелем находится конкретный IP 10.20.30.2 и помимо этого, пробрасываем весь трафик, не связанный другими маршрутами, в этот туннель: external у нас это основная выходная нода нашего VPN, так что по умолчанию весь трафик будет направляться через нее, т.к. зарубежных маршрутов больше, чем российских, и логичнее фильтровать именно российские, а зарубежный трафик пустить по умолчанию через ноду в другой стране.
|
||||
|
||||
Итак, два конфига:
|
||||
/etc/wireguard/wg-internal.conf
|
||||
[Interface]
|
||||
Address = 10.20.30.1/32
|
||||
ListenPort = 17968
|
||||
PrivateKey = kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=
|
||||
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostUp = ip rule add from `ip route | awk '/default/ {print $3; exit}'` table main
|
||||
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostDown = ip rule del from `ip route | awk '/default/ {print $3; exit}'` table main
|
||||
|
||||
#external node
|
||||
[Peer]
|
||||
PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
||||
AllowedIPs = 10.20.30.2/32, 0.0.0.0/0
|
||||
|
||||
#mobile-client node
|
||||
[Peer]
|
||||
PublicKey = 26Vhud00ag/bdB9molvSxfBzZTlzdO+aZgrX3ZDncSg=
|
||||
AllowedIPs = 10.20.30.3/32
|
||||
|
||||
|
||||
|
||||
/etc/wireguard/wg-external.conf
|
||||
[Interface]
|
||||
Address=10.20.30.2/32
|
||||
PrivateKey=6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
|
||||
PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
PostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
|
||||
|
||||
#internal node
|
||||
[Peer]
|
||||
PublicKey=MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
AllowedIPs=10.20.30.0/24
|
||||
Endpoint=195.2.79.13:17968
|
||||
PersistentKeepalive=25
|
||||
|
||||
|
||||
Теперь можно поднять туннели на обоих серверах:
|
||||
wg-quick down wg-external ; wg-quick up wg-external
|
||||
wg-quick down wg-internal ; wg-quick up wg-internal
|
||||
|
||||
|
||||
Проверяем, что туннели активны командой wg:
|
||||
root@trikster-internal:~# wg
|
||||
...
|
||||
peer: FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
|
||||
endpoint: 51.159.187.77:36276
|
||||
allowed ips: 10.20.30.2/32, 0.0.0.0/0
|
||||
latest handshake: 13 seconds ago
|
||||
transfer: 180 B received, 92 B sent
|
||||
|
||||
|
||||
|
||||
root@trikster-external:~# wg
|
||||
...
|
||||
peer: MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
endpoint: 195.2.79.13:17968
|
||||
allowed ips: 10.20.30.0/24
|
||||
latest handshake: 10 seconds ago
|
||||
transfer: 92 B received, 180 B sent
|
||||
persistent keepalive: every 25 seconds
|
||||
|
||||
|
||||
Если видим "latest handshake" и байты и в received и в sent, значит, все ок. Если только "send", без хендшейка и полученных данных, где-то в ошибка в конфиге или сервера недоступны друг для друга.
|
||||
|
||||
Если что-то пошло не так, и отвалился ssh, то достаточно перезагрузить сервер. Если все хорошо, и доступ к серверам сохранился, ставим туннели в автозапуск:
|
||||
systemctl enable wg-quick@wg-external.service
|
||||
systemctl enable wg-quick@wg-internal.service
|
||||
|
||||
|
||||
Попробуем посмотреть маршрут (рекомендую замечательную утилиту mytraceroute, mtr) без туннеля:
|
||||
root@trikster-internal:~# wg-quick down wg-internal && sleep 10 && mtr -r google.com
|
||||
HOST: trikster-internal.local Loss% Snt Last Avg Best Wrst StDev
|
||||
1.|-- host-89-22-232-243.hosted 0.0% 10 0.3 5.4 0.3 49.8 15.6
|
||||
2.|-- 172.31.0.1 0.0% 10 0.3 19.8 0.3 122.2 42.6
|
||||
3.|-- 109.239.138.90 0.0% 10 1.5 1.9 1.4 3.0 0.6
|
||||
4.|-- 91.108.51.4 0.0% 10 11.4 11.4 11.3 11.7 0.1
|
||||
5.|-- 178.18.227.12.ix.dataix.e 0.0% 10 11.0 17.9 11.0 77.0 20.8
|
||||
|
||||
И с туннелем:
|
||||
root@trikster-internal:~# wg-quick up wg-internal && sleep 10 && mtr -r google.com
|
||||
HOST: trikster-internal.local Loss% Snt Last Avg Best Wrst StDev
|
||||
1.|-- 10.20.30.2 0.0% 10 51.3 51.3 51.2 51.4 0.1
|
||||
2.|-- 10.200.100.0 0.0% 10 51.4 51.4 51.2 51.6 0.1
|
||||
3.|-- 10.197.37.65 0.0% 10 52.5 52.2 52.0 52.5 0.2
|
||||
4.|-- 10.197.0.41 0.0% 10 52.2 52.2 52.1 52.5 0.1
|
||||
5.|-- 10.197.0.44 0.0% 10 52.0 52.2 51.9 52.4 0.1
|
||||
|
||||
Все хорошо, трафик идет через внешний сервер — сначала на 10.20.30.2, который у нас выходная нода, а потом через его маршрутизаторы.
|
||||
|
||||
|
||||
#Шаг третий: добавляем конфиг клиента
|
||||
Создаем конфиг клиента, конечного устройства-пользователя VPN. За основу берем wg-external.conf, потому что он такой же точно клиент, который подключается к internal, разница только в том, что external получает пакеты, а наш клиент будет отправлять.
|
||||
|
||||
Генерируем ему сразу пару публичный-приватный ключ:
|
||||
prk=`wg genkey` && pbk=`echo $prk | wg pubkey` && printf "Private: $prk\nPublic: $pbk\n"
|
||||
Private: iPK7hYSU8TLVRD+w13nd3aGSYNLfnTx6zwdRzKcGb1o=
|
||||
Public: 26Vhud00ag/bdB9molvSxfBzZTlzdO+aZgrX3ZDncSg=
|
||||
|
||||
|
||||
Конфиг почти такой же:
|
||||
/etc/wireguard/wg-notebook-client.conf
|
||||
[Interface]
|
||||
Address = 10.20.30.3/32
|
||||
PrivateKey = iPK7hYSU8TLVRD+w13nd3aGSYNLfnTx6zwdRzKcGb1o=
|
||||
DNS = 1.1.1.1, 8.8.8.8
|
||||
|
||||
#internal node
|
||||
[Peer]
|
||||
PublicKey = MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = 195.2.79.13:17968
|
||||
PersistentKeepalive = 25
|
||||
|
||||
Тут у нас добавилась опция PersistentKeepalive. Дело в том, что роутеры в цепочке между двумя пирами ничего не знают о сессии WG, а знают только о потоке UDP-пакетов. Для маршрутизации UDP-пакетов за NAT они создают у себя табличку, в которой записывают, кто куда и на какой порт отправил пакет. И если с destination-адреса/порта приходит UPD-пакет, то они определяют, куда его отправить по это таблице, делая вывод, что если сервер B недавно отправил пакет серверу А, то ответ от сервера А на этот же адрес и порт скорее всего надо переслать серверу B.
|
||||
А в отличии от TCP в UDP нет никаких договоренностей о поддержании сессии, т.к. нет и самого понятия сессии. WG же построен таким образом, что при отсуствии трафика, попадающего в туннель, не будет и трафика между пирами, только хедшейки раз в две минуты. Опция PersistentKeepalive заставлет его посылать пустые пакеты каждые 25 секунд, предовращая потерю маршрута на промежуточных роутерах, потому что иначе возможна ситуация, когда мы будем раз за разом отправить пакеты, а до второго пира они доходить не будут, а он об этом и не будет знать.
|
||||
|
||||
|
||||
Дальше мы для нашего клиента добавляем еще одну секцию peer в конфиг на internal:
|
||||
|
||||
#notebook-client node
|
||||
[Peer]
|
||||
PublicKey = 26Vhud00ag/bdB9molvSxfBzZTlzdO+aZgrX3ZDncSg=
|
||||
AllowedIPs = 10.20.30.3/32
|
||||
|
||||
Перезапускаем туннель на internal (wg-quick down/up), подключаемся.. Оп, хендшейк есть, данные пошли.
|
||||
Открываем какой-нибудь https://www.reg.ru/web-tools/myip, видим IP external ноды, и другую страну.
|
||||
|
||||
Таким же образом создаем конфиги для других клиентов. Если это мобильные устройства, то удобнее показать им QR. Он делается следующим образом: создаем в текущей папке конфиг как обычно, конечно, с новыми ключами и другим IP, какой-нибудь wg-moblie-client.conf и дальше командой qrencode -t ansiutf8 < wg-moblie-client.conf показываем прям в консоли QR, который сканируем с телефона. Это удобнее копирования файлов, но вам так же никто не мешает скинуть wg-moblie-client.conf на телефон или вообще ввести значения 7 полей вручную.
|
||||
|
||||
|
||||
Готово, мы только что сделали очень странный двуххоповый VPN. Желаем это отметить и заказать себе пива, открываем сбермаркет.. "СберМаркет не открывается. Если у вас работает VPN, отключите его". Ах да, мы же с этой проблемой и собирались бороться... Неловко.
|
||||
Давайте доделаем.
|
||||
|
||||
# Шаг четвертый: добавляем регион-зависимую маршрутизацию.
|
||||
Как мы помним, мы отправляем все данные с клиента на internal, а тот все данные отправляет на external, а тот уже своему провайдеру. Так же, мы помним, что у нас на internal "слабый" маршрут 0.0.0.0/0, который перебивается любыми другими маршрутами, а сам internal находится в российском сегменте. Значит, все, что нам надо — это как-то перехватить запросы на российские IP на уровне internal и перенаправить их не в туннель WG до external, а напрямую в сетевой порт самого сервера, в тот, через который он получает доступ в православный, российский интернет со скрепами и девицами в кокошниках.
|
||||
|
||||
Давайте проверим предположение. На клиенте получим IP того же сбермаркета (nslookup sbermarket.ru), и посмотрим, как туда идет трафик (traceroute 212.193.158.175):
|
||||
HOST: vvzvladMBP14.local Loss% Snt Last Avg Best Wrst StDev
|
||||
1.|-- 10.20.30.1 0.0% 10 3.9 4.3 3.2 6.5 1.1
|
||||
2.|-- 10.20.30.2 0.0% 10 55.7 56.0 54.6 59.2 1.2
|
||||
3.|-- 10.200.100.0 0.0% 10 55.5 56.1 54.9 58.6 1.1
|
||||
4.|-- 10.197.37.65 0.0% 10 56.0 56.9 55.4 60.1 1.7
|
||||
5.|-- 10.197.0.41 0.0% 10 56.1 57.0 55.7 60.9 1.6
|
||||
|
||||
Ага, как и ожидалось, через external.
|
||||
Теперь создадим маршрут до этого адреса через дефолтный шлюз и устройство. Их можно узнать в ip r:
|
||||
root@trikster-internal:~# ip r
|
||||
default via 195.2.79.1 dev ens3 onlink
|
||||
10.20.30.2 dev wg-internal scope link
|
||||
...
|
||||
|
||||
Вот 195.2.79.1 и ens3 и есть нужные нам данные. Используем уже знакомые нам подстановочные команды и создадим новый маршрут такой командой:
|
||||
target_ip="212.193.158.175/32"
|
||||
gateway=`ip route | awk '/default/ {print $3; exit}'`
|
||||
gateway_device=`ip route | awk '/default/ {print $5; exit}'`
|
||||
ip route add $target_ip via $gateway dev $gateway_device
|
||||
|
||||
|
||||
Проверяем:
|
||||
root@trikster-internal:~# ip r
|
||||
default via 195.2.79.1 dev ens3 onlink
|
||||
10.20.30.2 dev wg-internal scope link
|
||||
10.20.30.3 dev wg-internal scope link
|
||||
195.2.79.0/24 dev ens3 proto kernel scope link src 195.2.79.13
|
||||
==> 212.193.158.175 via 195.2.79.1 dev ens3 <==
|
||||
|
||||
Да, на последнем месте у нас нужный маршрут.
|
||||
|
||||
Теперь повторяем команду traceroute -r 212.193.158.175 на клиенте, и видим, что трейс другой:
|
||||
HOST: vvzvladMBP14.local Loss% Snt Last Avg Best Wrst StDev
|
||||
1.|-- 10.20.30.1 0.0% 10 4.3 7.9 3.7 29.1 7.9
|
||||
2.|-- host-89-22-232-243.hosted 0.0% 10 4.6 4.9 3.8 9.2 1.6
|
||||
3.|-- 172.31.0.1 0.0% 10 25.9 8.4 3.3 25.9 6.9
|
||||
4.|-- sw1-m9p2-msk.ip.ngenix.ne 0.0% 10 6.2 5.7 4.0 7.3 1.0
|
||||
5.|-- cdn.ngenix.net 0.0% 10 3.8 5.0 3.8 8.4 1.3
|
||||
|
||||
Сбермаркет, правда, все еще не открываемся: видимо, проверяет на наличие VPN какой-то другой сервер, а не тот, в адрес которого ресолвится имя домена. Можно сходить на https://asnlookup.com/, вбить туда адрес, и получить принадлежность адреса к AS и заодно список подсетей этой AS (AS34879, OOO Sovremennye setevye tekhnologii). С большой вероятностью для более-менее крупных компаний это и будет их сетевая инфраструктура (ну или по крайней мере, инфраструктура, относящаяся к конкретному сайту), прописав для которой маршруты, вы обеспечите доступ на нужный вам сайт/сервис. Для мелких сайтов вы скорее всего получите AS хостера или дата-центра, но, во-первых, это тоже сработает, а во-вторых, мелкие сайты обычно и не закрывают иностранные диапазоны, потому что не испытывают проблем с DDOSом из-за границы.
|
||||
|
||||
Но можно сделать проще: засунуть в маршруты все адреса российского сегмента (спасибо статье на хабре:https://habr.com/en/post/659655/) и не париться о ручном добавлении.
|
||||
RIPE отдает их все в виде JSON вот по этому адресу: https://stat.ripe.net/data/country-resource-list/data.json?resource=ru
|
||||
Утилита jq преобразует из json в список подсетей: curl https://stat.ripe.net/data/country-resource-list/data.json?resource=ru | jq -r ".data.resources.ipv4[]"
|
||||
Правда, почему-то некоторые адреса там в формате "195.85.234.0-195.85.236.255", а не подсети, поэтому для них нам необходима еще утилита ipcalc:
|
||||
root@trikster-internal:~# ipcalc 195.85.234.0-195.85.236.255 |grep -v "deaggregate"
|
||||
195.85.234.0/23
|
||||
195.85.236.0/24
|
||||
|
||||
Выделить эти адреса из базового списка можно банально через "grep '-' " или "grep -v '/' ". Но их там немного, и на них, в принципе, можно забить.
|
||||
|
||||
Скрипт на баше выглядит как-то так (я не удержался и добавил туда еще и прогрессбар):
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
function ProgressBar {
|
||||
let _progress=(${1}*100/${2}*100)/100
|
||||
let _done=(${_progress}*4)/10
|
||||
let _left=40-$_done
|
||||
_fill=$(printf "%${_done}s")
|
||||
_empty=$(printf "%${_left}s")
|
||||
printf "\rAddind routes : [${_fill// /#}${_empty// /-}] ${_progress}%%"
|
||||
}
|
||||
|
||||
#Variables
|
||||
file_raw="russian_subnets_list_raw.txt"
|
||||
file_user="subnets_user_list.txt"
|
||||
file_for_calc="russian_subnets_list_raw_for_calc.txt"
|
||||
file_processed="russian_subnets_list_processed.txt"
|
||||
gateway_for_internal_ip=`ip route | awk '/default/ {print $3; exit}'`
|
||||
interface=`ip link show | awk -F ': ' '/state UP/ {print $2}'`
|
||||
|
||||
#Get addresses RU segment
|
||||
echo "Download RU subnets..."
|
||||
curl --progress-bar "https://stat.ripe.net/data/country-resource-list/data.json?resource=ru" | jq -r ".data.resources.ipv4[]" > $file_raw
|
||||
|
||||
echo "Deaggregate subnets..."
|
||||
cat $file_raw |grep "-" > $file_for_calc
|
||||
cat $file_raw |grep -v "-" > $file_processed
|
||||
for line in $(cat $file_for_calc); do ipcalc $line |grep -v "deaggregate" >> $file_processed; done
|
||||
|
||||
if [ -e $file_user ]; then echo "Add user subnets..."; cat $file_user >> $file_processed; fi
|
||||
|
||||
#Flush route table
|
||||
echo "Flush route table (down and up interface)..."
|
||||
ifdown $interface && ifup $interface
|
||||
|
||||
#Add route
|
||||
routes_count_in_file=`wc -l $file_processed`
|
||||
routes_count_current=0
|
||||
for line in $(cat $file_processed); do ip route add $line via $gateway_for_internal_ip dev $interface; let "routes_count_current+=1" ; ProgressBar ${routes_count_current} ${routes_count_in_file}; done
|
||||
echo ""
|
||||
|
||||
echo "Remove temp files..."
|
||||
rm $file_raw $file_processed $file_json $file_for_calc
|
||||
|
||||
routes_count=`ip r | wc -l`
|
||||
echo "Routes in routing table: $routes_count"
|
||||
|
||||
|
||||
|
||||
Добавим строчки в крон (export EDITOR=nano; crontab -e), чтобы он запускался каждую неделю (для того, чтобы обновить список адресов, если они поменялись) и после перезагрузки:
|
||||
@reboot sleep 30 && bash /root/update_ru_routes.sh > /root/update_routes_log.txt 2>&1
|
||||
0 3 * * mon bash /root/update_ru_routes.sh > /root/update_routes_log.txt 2>&1
|
||||
|
||||
Если вам принудительно надо маршрутизовать какую-то сеть через internal, то можно рядом со скриптом создать файлик subnets_user_list.txt в который поместить список подсетей, тогда они каждый раз будут добавляться к общему списку при обновлении, в bash-скрипте это есть.
|
||||
|
||||
# Шаг пятный: настраиваем фаервол
|
||||
|
||||
|
||||
|
||||
|
||||
PostUp = ufw route allow in on wg0 out on eth0
|
||||
PreDown = ufw route delete allow in on wg0 out on eth0
|
||||
|
||||
ufw allow 17968/udp
|
||||
ufw allow OpenSSH
|
||||
ufw disable
|
||||
ufw enable
|
||||
|
||||
# Шаг шестой, бонусный и необязательный: кеширующий защищенный DNS over HTTPS
|
||||
Теперь нам нужен еще один шаг: DNS. Можно, конечно, жить с DNS 1.1.1.1, но надо учитывать две вещи:
|
||||
Трафик на него пойдет через external, что автоматически означает задержку порядка 100мс при каждом запросе. Можно его добавить в subnets_user_list.txt, и тогда трафик пойдет через локальную ноду и локальный сервер 1.1.1.1, что уменьшит задержку до 10-20мс, но ваши DNS-запросы будет доступны вашему провайдеру, что в случае локальной ноды может быть для кого-то неприемлимо. Несколькими командами можно легко сделать кеширующий DNS, который еще и будет работать с DNS over HTTPS, а значит, провайдеру будет доступен только сам факт использовани DoH, но не сами запросы. Но это, конечно, не обязательно: у меня internal находится в домашней сети, и я просто использую DNS микротика, который находится в той же сети. Но если у вас internal сервер это VPS, то можно сделать там и DNS сервер. Использовать будем cloudflared.
|
||||
|
||||
Добавляем репозитарий:
|
||||
mkdir -p --mode=0755 /usr/share/keyrings
|
||||
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | tee /usr/share/keyrings/cloudflare-main.gpg > /dev/null
|
||||
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared buster main' | tee /etc/apt/sources.list.d/cloudflared.list
|
||||
sudo apt-get update && sudo apt-get install cloudflared dnsmasq -y
|
||||
|
||||
Пишем конфиг: /etc/cloudflared/config.yml:
|
||||
logfile: /var/log/cloudflared.log
|
||||
proxy-dns: true
|
||||
proxy-dns-upstream:
|
||||
- https://1.0.0.1/dns-query
|
||||
- https://1.1.1.1/dns-query
|
||||
- https://2606:4700:4700::1111/dns-query
|
||||
- https://2606:4700:4700::1001/dns-query
|
||||
proxy-dns-port: 5353
|
||||
proxy-dns-address: 127.0.0.1
|
||||
|
||||
Либо, можете заменить в proxy-dns-upstream записи на https://security.cloudflare-dns.com/dns-query или https://9.9.9.9/dns-query — первое это блокировка всякой малвари от cloudflare, второе это Quad9.
|
||||
|
||||
Создаем сервис:
|
||||
/etc/systemd/system/cloudflared.service:
|
||||
[Unit]
|
||||
Description=DNS over HTTPS (DoH) proxy client
|
||||
Wants=network-online.target nss-lookup.target
|
||||
Before=nss-lookup.target
|
||||
|
||||
[Service]
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
DynamicUser=yes
|
||||
ExecStart=/usr/local/bin/cloudflared --config /etc/cloudflared/config.yml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
|
||||
Активируем и запускаем
|
||||
systemctl daemon-reload
|
||||
systemctl enable cloudflared
|
||||
systemctl start cloudflared
|
||||
systemctl status cloudflared
|
||||
|
||||
Проверяем:
|
||||
dig @127.0.0.1 -p5353 google.com
|
||||
|
||||
У cloudflared есть один минус — у него странное кеширование, которое держится совсем немного, поэтому для кеширования дополнительно настроим dnsmasq, указав ему в качестве сервера cloudflared.
|
||||
|
||||
Добавляем в /etc/dnsmasq.conf что-то вроде
|
||||
server=127.0.0.1#5353
|
||||
no-poll
|
||||
no-resolv
|
||||
listen-address=10.20.30.1, 127.0.0.1
|
||||
cache-size=1500
|
||||
stop-dns-rebind
|
||||
clear-on-reload
|
||||
no-negcache
|
||||
|
||||
Запускаем:
|
||||
systemctl restart dnsmasq.service
|
||||
|
||||
Проверяем:
|
||||
dig @127.0.0.1 google.com
|
||||
|
||||
И уже можно сделать это с клиента:
|
||||
dig @10.20.30.1 google.com
|
||||
|
||||
Если все ок, то можно проверить несколько раз — при повторных запросах Query time: должен стать 0 msec или около, если запрашиваете локалхост, или будет близко к пингу до internal, если делаете это с клиента.
|
||||
|
||||
Теперь можно добавить в конфиги клиентов в секцию Interface:
|
||||
DNS = 10.20.30.1, 1.1.1.1
|
||||
|
||||
P.S. особые параноики могут запустить cloudflared на external, и скрыть от локального провайдера даже сам факт использования DoH. Для этого в proxy-dns-address в конфиге cloudflared и в dnsmasq.conf надо указать 10.20.30.2.
|
||||
|
||||
Пары ключей в статье — действующие, так что вы можете, ничего не исправляя в конфигах (только IP и имена адаптеров), залить их на два своих сервера и клиент и поиграться. Но для боевого применения ключи надо перегенерить, конечно. Так же, чтобы не усложнять статью, я не касался настройки файервола на серверах — если они торчат голой задницей в интернет, то это стоит сделать. О необходимости отключать авторизацию в ssh по паролю думаю, напоминать и так не надо.
|
||||
|
||||
Если кто-то захочет это все красиво обернуть в два докера и прикрутить к этому веб-интерфейс (потому что конфиги клиентов все же удобнее создавать в нем) — добро пожаловать в issues на гитхабе.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user