Категория > Новости > Trickster VPN. Разбираемся с WireGuard и делаем свой умный VPN - «Новости»
Trickster VPN. Разбираемся с WireGuard и делаем свой умный VPN - «Новости»10-09-2022, 00:00. Автор: Наталья |
родными средствами можно настроить автоматизацию, которая будет запускать VPN, когда открываешь определенные приложения (например, Twitter), а когда выходишь из них — выключать обратно. Но это костыль, а хочется все сделать красиво, да еще и прокачать навык работы с сетью. Поэтому мы сейчас попробуем «включать чуть‑чуть VPN». infoПри таком использовании это скорее прокси, а не VPN, но любые методы обхода блокировок теперь называют VPN, так что, сделав эту оговорку, дальше расслабимся и будем делать как все. Заодно чуть улучшим качество связи с локальными ресурсами: необходимость таскать трафик сначала до VPN вне страны, а потом обратно до сервера внутри нее драматично сказывается если не на скорости, то на задержке точно. Даже на проводном интернете пинг в 4 мс до Яндекса легко превращается в 190 мс, а на мобильном интернете — из 80 мс в 240 мс. Дополнительный хоп внутри страны чуть ухудшит дело, но далеко не так драматично. Делать все мы будем на основе WireGuard — это относительно новая (разрабатывается с 2016 года, в отличие от OpenVPN и IPsec: первый — это двухтысячные, а второй еще раньше) технология VPN. Создал ее, по сути, один человек — zx2c4, которого в миру зовут Джейсоном Доненфельдом. Плюсы WG — скорость (особенно для Linux, где он может работать как модуль ядра, начиная с Kernel 5.6, и Windows, где модуль для ядра выпустили около недели назад), низкие задержки, современная криптография и простая настройка и использование конечным юзером. Ах да, еще UDP. UDP для туннелей — это хорошо, потому что у TCP уже есть механизмы, которые позволяют ему работать на неидеальных соединениях, а UDP представляет собой именно такое соединение. А когда ты засовываешь TCP в TCP, то отказываешься от большей части этих механизмов (инкапсулированный TCP-пакет будет гарантированно доставлен другой стороне, хотя протокол допускает недоставку), но все еще несешь весь оверхед вида «хендшейк соединения для отправки хендшейка». Читайте также - SuperMega Market — агрегатор и сервис поиска товаров в интернет-магазинах и сравнения цен. Наш портал охватывает самые разнообразные категории товаров: одежда, обувь, электроника, компьютеры, бытовая техника, автотовары, оборудование для ремонта и строительства, туристическое снаряжение, детские товары и многое другое, интернет магазин supermega market по доступным ценам. Не говоря уж о том, что инкапсулировать UDP в TCP — ничуть не лучшая идея, потому что сразу рушит все предположения всяких скайпов о том, что лучше пропустить пару пакетов, чем уменьшить задержку: каждый UDP-пакет в этом случае будет принудительно послан заново и доставлен корректно, не считаясь с затратами времени. Особенно для одинокого пользователя‑хакера приятна работа с шифрованием: нет необходимости ни в сертификатах и удостоверяющих центрах, ни в логинах‑паролях, все, что нужно, — обменяться публичными ключами с пиром, с которым ты хочешь установить соединение. Для больших компаний это, конечно, будет скорее минусом, как и то, что WG — это только базовая часть полноценной большой инфраструктуры VPN. Но именно WireGuard использовали, к примеру, в Cloudflare для своего WARP, правда, написав его собственную реализацию — boringtun. Еще один минус WG — то, что трафик не обфусцирован и глубокая инспекция пакетов (DPI) выявит и позволит заблокировать такое соединение (не говоря уж о блокировке UDP совсем, что почти не мешает работать с вебом, но гарантированно ломает WireGuard). Для скрытия трафика рекомендуется использовать специализированное ПО — Cloak, Obfsproxy, Shadowsocks, Stunnel, SoftEther, SSTP или, в конце концов, простой SSH. Часть из этих инструментов может работать совместно с WG, а часть способна его заменять в качестве инструмента стеганографии: WG изначально создавался под скорость и криптографическую защищенность. Если очень упрощать, ключи работают следующим образом: у нас есть закрытый (приватный) ключ, из которого можно сгенерировать открытый, или публичный. Наоборот, из открытого ключа получить закрытый мы никак не можем. Затем мы шифруем с помощью закрытого ключа какую‑то строку, а при помощи открытого расшифруем ее и тем самым убедимся, что у собеседника точно есть закрытый ключ, а значит, он тот, за кого себя выдает. Таким образом, мы можем без проблем передавать открытый ключ — он всего лишь позволяет проверить подлинность автора, но не притвориться им. Это как в SSH — публичный ключ лежит на сервере, где его потеря — небольшая беда: все, что сможет сделать с ним злоумышленник, — это положить его на свой сервер, чтобы ты мог подключиться к нему с помощью закрытого ключа. Так вот, в WG на первом этапе подключения каждая сторона с помощью зашифрованного приватным ключом сообщения доказывает собеседнику, что она именно она: это проверяется публичным ключом. Второй этап — создание с помощью этих ключей и матана симметричных ключей для шифрования самого трафика. Благодаря тому что расшифровать зашифрованное публичным ключом нельзя без приватного, мы сможем создать ключ для симметричного шифрования и отправить его по защищенному каналу. Этот шаг необходим потому, что симметричное шифрование гораздо менее ресурсоемкая операция, а минус у нее только один: необходимо синхронизировать ключ между сторонами, при том что перехват ключа третьей стороной ведет к возможности расшифровки трафика. Но эта проблема решается с помощью асимметричной схемы. Это называется протокол Диффи — Хеллмана — способ защищенного получения общего секретного ключа. В WG используется ECDH — вариация Диффи — Хеллмана на эллиптических кривых. Первые два этапа в терминах WG называются рукопожатием или хендшейком. Разумеется, в реальности все немного сложнее: например, отправляются не сами ключи, а сгенерированные на их основе эфемерные ключи, которые удаляются сразу после операции. Заинтересовавшихся подробностями отправляю к краткому описанию на сайте WireGuard. Мы же перейдем к более практическим действиям. Шаг 1. Создаем и настраиваем два сервераОдин сервер будет внутри страны — через него трафик пойдет на локальные ресурсы, а второй — за границей. Дальше я их буду называть local и external. Идеально, если local будет в твоей домашней сети, потому что при этом трафик на внешние ресурсы не отличается от твоего домашнего трафика. Но для этого нужен какой‑то хост дома, белый IP и возможность пробросить порт. У меня это виртуалка на домашнем сервере, но, наверное, подойдет и Raspberry Pi или аналогичный одноплатник. infoВариант с одноплатником я не тестировал и не уверен, что он сработает. Raspberry Pi придется маршрутизировать весь трафик с устройств и держать в памяти около 11 тысяч маршрутов; ресурсов на это может не хватить. Если дома хоста нет, можно взять любой сервер у хостера VDS. Увы, некоторые сайты блокируют подсети хостеров, опасаясь ботов, так что это менее предпочтительный вариант. А вот внешнюю машину можно арендовать и у хостера. К примеру, у RU VDS и VDSina есть зарубежные площадки. А можно выбрать иностранного хостера, если найдешь способ оплачивать его услуги. Например, я использую исландский 1984.hosting, созданный специально для параноиков. Считаем, что на обоих серверах у нас Debian 11. Ставим нужные нам пакеты: apt update && apt install -ywireguard iptables ipcalc qrencode curl jq traceroute dnsutils ufw Включаем перенаправление трафика: в этом случае сервер, получив пакет, который не предназначается ни одному из его IP-адресов, не отбросит его, а попытается перенаправить в соответствии со своими маршрутами. 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 Шаг 2. Настраиваем WireGuard для связи двух серверовДля начала генерируем ключи. Запускаем два раза wg genkey и получаем два приватных ключа: root@trikster-internal:~# wg genkey kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8= root@trikster-internal:~# wg genkey 6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28= Утилита wg genkey не делает ничего волшебного, это просто аналог чего‑то в таком духе: Только наверняка более случайное: мы просто генерируем 32 байта случайных значений и представляем их в виде Base64. Создаем два конфига. Один на internal: /etc/wireguard/wg-internal.conf Address = 10.20.30.1/32ListenPort = 17968PrivateKey = kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostUp = ip rule add from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table mainPostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostDown = ip rule del from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table main Второй на external: /etc/wireguard/wg-external.conf Address=10.20.30.2/32PrivateKey=6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE Секция [Interface] — это настройки конкретного сетевого интерфейса WireGuard, того, что будет виден в Но никто не мешает, если хочется, сделать для каждого пира отдельный конфиг и отдельный интерфейс (правда, на сотнях клиентов это будет неудобно). Управляются интерфейсы обычно при помощи утилиты wg-quick: infoУтилита Именно этим занимается
Кроме публичных и приватных ключей, есть еще опция PresharedKey, которая обеспечивает дополнительное шифрование симметричным шифром. Ключ можно сгенерировать, например, командой А чтобы по‑настоящему обеспечить постквантовую безопасность (невозможность расшифровки данных квантовыми компьютерами), разработчики рекомендуют дополнительный внешний квантово‑устойчивый механизм хендшейка, например SIDH, который Microsoft пиарит именно в таком контексте. Созданный им общий ключ можно использовать в качестве PresharedKey. Заклинания в PostUp достаточно просты. Вот команда для подстановки имени сетевого интерфейса, куда по умолчанию выполняется маршрутизация: Как правило, это интерфейс, обращенный к провайдеру или роутеру. Таким образом, страшная команда превращается в такую: iptables -t nat -APOSTROUTING -oeth0 -jMASQUERADE Здесь происходит включение NAT в режиме маскарада: сервер будет отправлять пришедшие ему пакеты во внешнюю сеть, подменяя адрес отправителя своим, чтобы ответы на эти пакеты тоже приходили ему, а не исходному отправителю. Вторая команда уже немного сложнее, но она подставляет IP-адрес дефолтного маршрута. Сначала мы получаем, как и выше, сетевой интерфейс маршрута по умолчанию: enp1s0 Потом данные о состоянии этого интерфейса: inet 192.168.88.70/24 brd 192.168.88.255 scope global dynamic enp1s0 И дальше вытаскиваем оттуда адрес, в данном случае 192.168.88.70. ip rule add from 95.93.219.123 table mainё Это необходимо для сервера internal, потому что иначе при активации маршрута Естественно, при включенном rp_filter пакет отбрасывается. В этом случае сервер перестает быть доступным, например по SSH снаружи. К нему придется коннектиться только по внутреннему IP WireGuard. Отключать rp_filter — это стрелять из пушки по воробьям, а вот дополнительное правило исправляет ситуацию. infoЯ намеренно не привожу готовые конфиги, потому что хочу показать механизм создания конфигов в ручном режиме. В свое время я генерировал конфиги утилитами типа easy-wg-quick или веб‑сервисами, которые спрашивают тебя о названии клиента и красиво показывают QR-код. Это отнюдь не способствует пониманию того, как работает WG на самом деле, и может вызвать проблемы. Теперь в оба конфига надо добавить секцию Peer, чтобы связать серверы друг с другом. Генерируем из приватного ключа публичный (вот в root@:~# 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, оно будет отброшено.
В этих примерах AllowedIPs можно читать как «адреса, трафик на которые будет маршрутизироваться в туннель этого пира и с которых пир сможет отправить что‑то в туннель». То есть пункт
В данном случае Повторяем генерацию публичного ключа с external: root@:~# 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 тут Поэтому по умолчанию весь трафик будет направляться через нее, так как зарубежных маршрутов больше, чем российских, и логичнее фильтровать именно российские, а зарубежный трафик пустить по умолчанию через ноду в другой стране. Итак, два конфига. /etc/wireguard/wg-internal.conf Address = 10.20.30.1/32ListenPort = 17968PrivateKey = kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostUp = ip rule add from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table mainPostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostDown = ip rule del from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table main#external node[Peer]PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=AllowedIPs = 10.20.30.2/32, 0.0.0.0/0 /etc/wireguard/wg-external.conf Address = 10.20.30.2/32PrivateKey = 6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostDown = 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/24Endpoint = 195.2.79.13:17968PersistentKeepalive = 25 Теперь можно поднять туннели на обоих серверах: root@trikster-external:~# wg-quick down wg-external ;wg-quick up wg-external root@trikster-internal:~# wg-quick down wg-internal ;wg-quick up wg-internal Проверяем, что туннели активны, командой 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: ... seconds ago» и байты и в received и в sent, значит, все хорошо. Если байты только в send, без хендшейка и полученных данных, значит, где‑то в конфиге ошибка или серверы недоступны друг для друга. Если что‑то пошло не так и отвалился SSH, то достаточно перезагрузить сервер — активные туннели сбросятся. Если же все хорошо и доступ к серверам сохранился, ставим туннели в автозапуск: root@trikster-internal:~# systemctl enable wg-quick@wg-internal.service root@trikster-external:~# systemctl enable wg-quick@wg-external.service Попробуем посмотреть маршрут (рекомендую замечательную утилиту root@trikster-internal:~# wg-quick down wg-internal && sleep 10 && mtr -rgoogle.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 -rgoogle.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, который у нас назначен выходной нодой, а потом через его маршрутизаторы. У нас получилась примерно следующая схема. Шаг 3. Добавляем конфиг клиентаСоздаем конфиг клиента, конечного устройства — пользователя VPN. За основу берем Перейти обратно к новости |