Мотивация
Приватная сеть вещь удобная и приятная, а особенно удобно и приятно когда она еще и шифрованная. Большая часть хостеров так или иначе предлагают такую опцию (и зачастую в приватной сети еще и ширина канала больше), однако иногда возникает необходимость связности в приватной сети между разными хостерами. Вариантов сделать такое не один и не два (OpenVPN, WireGuard, IPSec, GRE или вовсе тяжелая артиллерия в виде iBGP). Я же решил остановиться на WireGuard. Причин тому несколько:
во-первых это красивостильно-модно-молодежно и что-то новенькое для меня (поддержка wg появилась в относительно свежем linux kernel 5.6)- производительность - накладные расходы на ее работу околонулевые. Белые мишки радуются меньшему нагреву CPU и выбросам CO для генерации электроэнергии, а Грета Тунберг может спокойно пойти в школу (на самом деле уже в универ). А если серьезно - по сравнению с openvpn или IPSec wireguard позволяет строить сеть даже на кофеварке, лишь бы ядро было свежим
- простота работы с технологией - конфиг крайне простой, понятный и лаконичный
Однако самое важное что нужно понимать про WireGuard заключается том что его нужно воспринимать как статический, шифрованный туннель. Не больше и не меньше. Не надо ждать от него роутинга или прописывания дополнительных DNS - этого всего тут нет.
Подход №1
отринем все недостатки и приумножим достоинства (c)
Как я писал выше - никаких дополнительных фич в Wireguard нет а хочется же. Поэтому я пошел искать какие-то удобные обертки для построения гибкой приватной сети на базе Wireguard. Первое на что вы скорее всего наткнетесь это Netmaker.
С виду эта штука как-будто бы закрывает все проблемы - удобный UI, который позволяет, сделать много разных подсетей, автоматически пушит конфиги на на ноды в сети, предоставляет DNS-ы и даже имеет интеграцию в k8s.
Однако по состоянию на Q1 2023 выглядит это творение следующим образом:
- Это клиент-сервер. Причем у тебя есть единый нераспределенный сервер и клиенты, которые без этого сервера теряют связность между друг другом. Впрочем, возможно, это такой баг
- Обратная совместимость и крайняя сырость решения. Актуальная на момент написания статьи версия Netmaker
v0.15.1
и меняется сейчас фактически все и находу: компоненты, конфиги. Фактичеси калечащие изменения между версиями это сейчас скорее норма, чем исключения. Я сдался в тот момент, когда меня просто заставили при миграции на свежую версию пересоздать сеть заново потому что формат хранения персиста поменялся и никакого способа миграции предложено не было. - Громоздкость серверной части. Создатели замахнулись на все и сразу в попытках сделать универсальный швейцарский нож, но пока что получается скорее швейцарский сыр. Просто так взять и поднять это решение задача не для слабых духом. Только список комонентов серверной части выглядит следующим образом:
- netmaker - ядро всей этой балалайки
- netmaker-ui - web-морда
- coredns - DNS
- traefik - в документации написано, что может быть заменен на любой другой reverse-proxy, но по факту вы запаритесь конфигурировать под что-то другое netmaker-ui
- eclipse-mosquitto - message-broker. Теоретически я даже могу представить себе кейсы, когда реально потребуются очереди, но желание воскликнуть “рили!? Вы щас серьезно!?” возникает постоянно.
По части просто эксплуатации также все не без проблем. Ноды в сети постоянно теряют связность безо всякой на то видимой причины.
В целом, я не против таких комбайнов, однако для pet-проекта это определенно точно перебор, а для enterpirse здесь и сейчас это точно очень сыро.
В общем пока что не могу не процитировать заставку South Park - IT SHOULD NOT BE VIEWED BY ANYONE
Среди SaaS есть для такого рода вещей tailscale, ребята серьезные, удобные, однако SaaS со всеми вытекающими последствиями за вполне приятный прайс
По технической части никаких нареканий нет и быть не может - ребята крайне серьезные и делают свое дело хорошо. Все что требуется это зарегистрироваться у них на сайте и поставить агенты на нужные вам девайсы (поддерживаются все известные мне актуальные платформы). Настройка и администрирование требуются минимальные. В общем, если SaaS-ы не вызывают у вас отвращения, вы можете и готовы заплатить, а на территории РФ готовы мириться с рисками отключения без предупреждения. - крайне рекомендую.
Однако, я все же решил пойти дальше и мне отчаянно хотелось сделать проще. И я сделал.
Подход №2
подумай и сделай проще
По сути я решил вернуться к исходной задаче и понять что же я хочу. Хотел я простого - удобно конфигурируемую плоскую полносвязную приватную сеть.
Базово, с технологией я определился - WireGuard. Что дальше? Для создания сети в простейшем случае достаточно сгенерировать конфиг, в котором нужно иметь приватный ключ, публичный ключ и IP адрес ноды. Фактически конфиг на каждом хосте в приватной сети выглядит как-то так:
[Interface]
Address = 10.50.0.8 #private IP
PrivateKey = {priv_key_of_node}
ListenPort = {port}
[Peer]
PublicKey=pub_key_of_peer
AllowedIPs=10.50.0.5/32 #private IP
Endpoint={public_ip_of_peer]:{port}
PersistentKeepalive=25
[Peer]
PublicKey=pub_key_of_peer
AllowedIPs=10.50.0.7/32 #private IP
Endpoint={public_ip_of_peer]:{port}
PersistentKeepalive=25
Связность регулируется только тем какие в конфиге пиры присутсвуют. Peer - это любой узел в приватной сети. Все участники сети в терминологии WireGuard друг другу пиры. По-умолчанию wireguard поднимает интерфейс wg0, но название можно переопределить, а при желании один демон wireguard может обслуживать несколько интерфейсов (но на разных портах).
По сути, достаточно просто правильно раскидать публичные ключи по конфигам, скормить их демону wireguard и если нет никаких проблем со сетевой связностью между публичными адресами все будет работать. Однако что делать если нужно ввести еще одну ноду в эксплуатацию или вывести старую, поменять адресацию или внести любые другие изменения? Иными словами, как этим всем управлять?
Подумав немного я понял, что по сути задача сводиться к трем несложным шагам:
- Сгенерить конфиги
- Запушить конфиги
- Рестартануть демон wireguard
Вариантов для такого немало (в конце концов можно и своего несложного демона для этого написать), однако на мой взгляд, самым простым и удобным является ansible. Скорее всего, если вы дошли до необходимости манипулировать несколькими хостами - какой-то ansible с какими-то хостами и переменными у вас уже есть, а значит переиспользуем этот инструмент в наших целях, тем более, что все необходимые примитивы для решения задачи есть буквально в стандартной поставке.
Устанавливаем wireguard и генерим ключи
Не смотря на то, что wireguard имеет поддержку в ядре это не значит, что демон есть в стандартной поставке с дистрибутивом. А значит надо его установить и сразу же сгенерим приватный и публичный ключи на пирах:
- hosts: all
become: yes
vars:
wireguard_mask_bits: 24
wireguard_port: 51871
tasks:
- name: Install wireguard
apt:
name: wireguard
update_cache: yes
- name: Generate Wireguard keypair
shell: wg genkey | tee /etc/wireguard/privatekey | wg pubkey | tee /etc/wireguard/publickey
args:
creates: /etc/wireguard/privatekey
- name: register private key
shell: cat /etc/wireguard/privatekey
register: wireguard_private_key
changed_when: false
- name: register public key
shell: cat /etc/wireguard/publickey
register: wireguard_public_key
changed_when: false
Генерим конфиги
- name: Setup wg0 device
copy:
content: |
[NetDev]
Name=wg0
Kind=wireguard
Description=WireGuard tunnel wg0
[WireGuard]
ListenPort={{ wireguard_port }}
PrivateKey={{ wireguard_private_key.stdout }}
{% for peer in ansible_play_batch %}
{% if peer != inventory_hostname %}
[WireGuardPeer]
# "{{ hostvars[peer].ansible_hostname }}"
PublicKey={{ hostvars[peer].wireguard_public_key.stdout }}
AllowedIPs={{ hostvars[peer].wireguard_ip }}/32
Endpoint={{ hostvars[peer].ansible_host }}:{{ wireguard_port }}
PersistentKeepalive=25
{% endif %}
{% endfor %}
[WireGuardPeer]
#home gw
PublicKey=PFLmU/MRGgbGwzCBZ71se25wS58bUgDl4luvA+s/ugI=
AllowedIPs=10.50.0.8/32
dest: /etc/systemd/network/99-wg0.netdev
owner: root
group: systemd-network
mode: 0640
remote_src: yes
notify: systemd network restart
По сути небольшой трюк с jinja-темплейтом. Единственное, что требует отдельного пояснения это тот факт, что я решил сразу генерить конфиг для systemd (да - поддержка wireguard есть прямо в systemd, что решает для нас сразу ряд вопросов), а также вот этот кусок:
#home gw
PublicKey=PFLmU/MRGgbGwzCBZ71se25wS58bUgDl4luvA+s/ugI=
AllowedIPs=10.50.0.8/32
Это кусок предназначен для моего роутера с серым внешним IP и вы можете и вовсе его удалить, я обращаю на него внимание по одной причине - у пира не обязательно указывать внешний IP. Правда это означает и тот факт, что инициировать соединение сможет только этот пир, однако это удобно, когда нужно сгенерить конфиг для мобильного устройства.
В hostvars не забываем добавлять перменную wireguard_ip
, которая по сути будет являться IP пира в приватной сети
wireguard_ip: 10.51.1.10
Последние приготовления
Тут мы просто генерим конфиг для Systemd и рестартим его.
- name: Setup wg0 network
copy:
content: |
[Match]
Name=wg0
[Network]
Address={{ wireguard_ip }}/{{ wireguard_mask_bits }}
dest: /etc/systemd/network/99-wg0.network
owner: root
group: systemd-network
mode: 0640
remote_src: yes
notify: systemd network restart
handlers:
- name: systemd network restart
service:
name: systemd-networkd
state: restarted
enabled: yes
Ну а далее просто запускаем созданный нами playbook и получаем рабочую приватную сеть.
Заключение
Честно говоря, с учетом того, сколько времени я всадил в попытки работать с Netmaker (а я реально эту штуку тестил около 3-х месяцеов) - я могу лишь сказать, что временные затраты на написание этого решения были просто ничтожны (пара часов с тестированием.) Наверное это еще раз подтверждает тот факт, что иногда велосипеды писать самое разумное решение, ну а красивости можно доделать самостоятельно.