Zero Trust доступ через WireGuard без классического VPN
Классический офисный VPN устроен просто и потому опасно: подключился — получил маршрут во всю внутреннюю сеть. Если ноутбук с конфигом украли или скомпрометировали одного разработчика, атакующий видит базу данных, бэкапы и админку так же свободно, как и он сам. Zero Trust решает эту проблему не новым продуктом, а сменой модели: вместо одного широкого туннеля в сеть — множество узких туннелей, каждый к одному конкретному сервису. WireGuard для этого не нужно ничем дополнять — нужная логика уже встроена в его архитектуру, просто ей обычно не пользуются.
Содержание
- Чем плохо доверие «всё или ничего» в классическом VPN
- Принцип: один сервис — один узкий туннель
- Архитектура: hub с точечными AllowedIPs вместо отдельной сети на сервис
- Второй слой: nftables как подстраховка на случай ошибки в AllowedIPs
- Выдача и отзыв доступа: делаем это скриптом, а не руками
- Что WireGuard не решает сам по себе
- Мониторинг: кто и когда реально проходил через туннели
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Чем плохо доверие «всё или ничего» в классическом VPN
В типичной схеме VPN-сервер раздаёт клиентам маршрут вида 10.0.0.0/24 — весь внутренний диапазон. Дальше сегментация, если вообще есть, держится на честном слове внутреннего файрвола, который тоже настраивают не всегда аккуратно. Получается плоская сеть за одним периметром: попал внутрь — двигайся куда хочешь.
Это прямое нарушение принципа Zero Trust «never trust, always verify» на сетевом уровне: сам факт установленного VPN-туннеля трактуется как достаточное основание для доверия. На практике это означает:
- один утёкший
.ovpnили.confфайл открывает доступ ко всей инфраструктуре; - нет возможности точечно отозвать доступ конкретного человека к конкретному сервису — только полностью выключить его из VPN;
- аудит превращается в вопрос без ответа: кто теоретически мог достучаться до базы, если у всех есть маршрут в её подсеть.
Zero Trust не отменяет VPN как транспорт — он меняет то, что именно этот транспорт разрешает.
Принцип: один сервис — один узкий туннель
Идея простая: доступ выдаётся не «в сеть», а к конкретному хосту и порту. Разработчику, которому нужен только staging-сервер приложения, туннель не должен вести никуда, кроме этого сервера. DBA получает туннель только до порта СУБД. Админу бэкапов — только до хранилища бэкапов. Пересечений нет, и это не соглашение «так договорились», а факт, зафиксированный в конфигурации.
У WireGuard для этого есть встроенный механизм — AllowedIPs, который выполняет двойную роль:
- На стороне отправителя (клиент или сервер) — это таблица крипто-маршрутизации: пакет шифруется и уходит конкретному пиру только если адрес назначения входит в его
AllowedIPs. Если адреса там нет — пакет вообще не попадёт в туннель, интерфейс просто не знает, куда его послать. - На стороне получателя — это фильтр источника: расшифрованный пакет принимается только если его IP отправителя совпадает с
AllowedIPs, заявленным для этого пира. Подделать источник, оставаясь в рамках протокола, нельзя.
Из этого следует важный вывод: сегментацию не обязательно городить поверх WireGuard отдельным слоем ACL — она уже встроена в сам протокол, если правильно заполнить AllowedIPs. Ошибка почти всех типовых гайдов по установке в том, что там AllowedIPs = 0.0.0.0/0 или 10.0.0.0/24 — то есть весь трафик или вся подсеть. Для Zero Trust этот параметр должен быть скальпелем, а не топором.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверАрхитектура: hub с точечными AllowedIPs вместо отдельной сети на сервис
Есть два способа реализовать модель «сервис — туннель»:
- Несколько WireGuard-интерфейсов (
wg-app,wg-db,wg-admin), каждый со своим портом и ключами. Плюс — удобно логировать и отзывать доступ пачкой («уволили DBA — снёс интерфейс wg-db целиком, если он был единственным её доступом»). Минус — больше движущихся частей: порты, конфиги, systemd-юниты. - Один интерфейс-хаб, но у каждого пира — предельно узкий
AllowedIPs, ограниченный ровно тем хостом и, где возможно, диапазоном адресов, который нужен. Проще эксплуатировать, меньше портов светить наружу.
Для большинства небольших и средних инфраструктур второй вариант практичнее. Ниже — он.
Схема: один VPS с публичным IP — хаб, внутренняя сеть туннеля 10.20.0.0/24. У хаба «за спиной» — приложение (10.20.0.10), СУБД (10.20.0.11), бэкап-хранилище (10.20.0.12). Если у вас ещё нет самого VPS под хаб, базовую установку WireGuard разумно сделать по отдельной инструкции — здесь эта часть предполагается пройденной: как установить и настроить WireGuard на VPS.
Серверный /etc/wireguard/wg0.conf:
[Interface]
Address = 10.20.0.1/24
ListenPort = 51820
PrivateKey = <server_private_key>
PostUp = nft -f /etc/wireguard/nft/wg0.nft
PostDown = nft delete table inet wg0filter
# Иван, разработчик — доступ нужен только к app-серверу
[Peer]
PublicKey = <ivan_public_key>
AllowedIPs = 10.20.0.2/32
# Мария, DBA — доступ нужен только к СУБД
[Peer]
PublicKey = <maria_public_key>
AllowedIPs = 10.20.0.3/32
Обратите внимание: AllowedIPs в секции [Peer] на сервере — это адрес самого пира (его туннельный IP, 10.20.0.2/32), а не адрес сервиса, к которому его пускают. Это защита от подмены источника. Ограничение по *назначению* задаётся в конфиге клиента и дублируется в файрволе — до этого дойдём ниже.
Конфиг клиента у Ивана (ivan.conf):
[Interface]
PrivateKey = <ivan_private_key>
Address = 10.20.0.2/32
DNS = 10.20.0.1
[Peer]
PublicKey = <server_public_key>
Endpoint = vpn.example.com:51820
AllowedIPs = 10.20.0.10/32
PersistentKeepalive = 25
Здесь AllowedIPs = 10.20.0.10/32 — это уже маршрут: единственное, что клиент вообще увидит через этот туннель, это app-сервер. Ни СУБД, ни бэкапы, ни другие рабочие станции в этой сети для него не существуют — маршрута к ним просто нет ни в таблице клиента, ни в разрешениях пира. Если нужен доступ к двум сервисам — через запятую перечисляются оба /32, а не диапазон целиком.
Второй слой: nftables как подстраховка на случай ошибки в AllowedIPs
Крипто-маршрутизация WireGuard надёжно защищает от простой подмены адреса, но если сервер настроен как обычный роутер (net.ipv4.ip_forward=1) без дополнительных правил, ядро всё равно перешлёт транзитный пакет по обычной таблице маршрутизации — а не только туда, куда «положено по духу» конфига. Поэтому вторым, независимым слоем стоит явный forward-файрвол с политикой «запретить всё, что не разрешено».
/etc/wireguard/nft/wg0.nft:
table inet wg0filter {
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
# Иван -> только app-сервер, порты 80/443
ip saddr 10.20.0.2 ip daddr 10.20.0.10 tcp dport { 80, 443 } accept
# Мария -> только СУБД, порт 5432
ip saddr 10.20.0.3 ip daddr 10.20.0.11 tcp dport 5432 accept
}
}
Теперь даже если кто-то по ошибке впишет в клиентский конфиг AllowedIPs = 10.20.0.0/24, пакет к чужому сервису дойдёт до хаба, но будет сброшен на этапе forward — правила по умолчанию запрещают всё, что явно не перечислено. Это тот самый принцип deny-by-default, без которого Zero Trust остаётся лозунгом на слайде.
Сравнение двух моделей в одной таблице:
| Классический VPN (hub, общая подсеть) | Zero Trust на WireGuard | |
|---|---|---|
| Маршрут клиента | Вся подсеть 10.0.0.0/24 | Один хост /32 на сервис |
| Кто решает, что доступно | Внутренний файрвол (если есть) | AllowedIPs + nftables, deny-by-default |
| Отзыв доступа к одному сервису | Обычно недоступен без отключения VPN целиком | Правка одной строки AllowedIPs + одного правила nft |
| Аудит «кто мог достучаться до X» | Нужно поднимать логи файрвола за период | Читается прямо из конфига |
| Издержки на настройку | Ниже на старте | Выше на старте, ниже в эксплуатации при росте команды |
Выдача и отзыв доступа: делаем это скриптом, а не руками
Ручное редактирование wg0.conf на 20+ пиров быстро становится источником ошибок — лишняя строка в AllowedIPs, забытое правило nft. Минимальный скрипт выдачи:
#!/usr/bin/env bash
set -euo pipefail
NAME=$1 # ivan
SERVICE_IP=$2 # 10.20.0.10
NEXT_IP=$3 # 10.20.0.2
wg genkey | tee "/etc/wireguard/peers/${NAME}.key" | wg pubkey > "/etc/wireguard/peers/${NAME}.pub"
PUB=$(cat "/etc/wireguard/peers/${NAME}.pub")
cat >> /etc/wireguard/wg0.conf <<EOF
[Peer]
# ${NAME}
PublicKey = ${PUB}
AllowedIPs = ${NEXT_IP}/32
EOF
wg syncconf wg0 <(wg-quick strip wg0)
echo "Добавь правило forward для ${NEXT_IP} -> ${SERVICE_IP} в wg0.nft и перезагрузи nft -f"
wg syncconf применяет изменения без разрыва существующих соединений — остальные пиры не замечают, что кому-то выдали доступ. Отзыв — зеркальная операция:
wg set wg0 peer <PUBLIC_KEY> remove
Плюс — удалить соответствующую строку из wg0.conf (иначе она вернётся при следующем wg-quick up) и убрать правило из nftables. Ключи стоит ротировать не только по факту увольнения, но и планово — раз в 90–180 дней для активных сотрудников, это снижает ценность утёкшего конфига, который просто лежал где-то забытый.
Что WireGuard не решает сам по себе
Здесь стоит быть честным: WireGuard — это транспортный и сетевой слой. Он прекрасно решает «кто может проложить пакет от точки A до точки B» и делает это криптографически надёжно. Но полноценный Zero Trust (в духе BeyondCorp) требует ещё и:
- Проверки личности, а не только владения ключом — сам по себе
.conf-файл не знает, кто его открыл. Если это критично, добавляйте поверх сервисов собственную аутентификацию: SSH — по сертификатам, веб-панели — за обратным прокси с OAuth/SSO, а сам туннель имеет смысл дополнительно защитить двухфакторной аутентификацией на уровне ОС клиента. - Проверки состояния устройства (device posture) — патчи, антивирус, диск зашифрован ли. WireGuard об этом ничего не знает, это компетенция MDM или отдельных агентов.
- Динамического пересмотра доступа без ручной правки конфига — коммерческие Zero Trust платформы и mesh-решения вроде Tailscale со своими ACL решают это через центральный контроль-плейн; если такой уровень автоматизации важнее самостоятельного администрирования, стоит сравнить подходы — Tailscale или WireGuard: mesh против классики.
Описанная здесь схема — это Zero Trust на сетевом уровне: она честно закрывает главную дыру классического VPN («подключился — увидел всё»), но не заменяет прикладную аутентификацию там, где она нужна. Комбинация того и другого даёт результат, сравнимый с дорогими managed-решениями, но на инфраструктуре, которую вы полностью контролируете.
Мониторинг: кто и когда реально проходил через туннели
Сегментация без наблюдаемости половинчата — стоит видеть, кто фактически пользуется выданным доступом, а не только кому он теоретически выдан. Базовый набор:
# активные рукопожатия и трафик по каждому пиру
wg show wg0
# последний handshake в читаемом виде
wg show wg0 latest-handshakes
Пир, у которого не было рукопожатия месяцами, — кандидат на отзыв: доступ не используется, а поверхность атаки от него остаётся. Для журналирования отброшенных forward-пакетов добавьте в конец цепочки log prefix "wg0-drop: " перед drop (или полагайтесь на неявный policy drop, если логи не нужны в таком объёме) — так видно попытки выйти за пределы выданного AllowedIPs, что само по себе сигнал: либо ошибка в конфиге клиента, либо что-то менее безобидное.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Не проще ли просто завести отдельную VLAN на каждый сервис?
Технически можно, но это требует управляемого коммутатора, а WireGuard даёт ту же изоляцию программно, без физической инфраструктуры и одинаково — что для VPS в облаке, что для железа в офисе.
Что если пользователю нужен доступ к трём сервисам сразу?
Просто три адреса через запятую в AllowedIPs клиента и три отдельных правила forward на сервере — это не три туннеля, а один туннель с тремя разрешёнными маршрутами.
Ломает ли это PersistentKeepalive и NAT-обход?
Нет, keepalive и пробивание NAT работают на уровне UDP-сессии между пирами и не зависят от того, насколько узкий у пира AllowedIPs.
Нужно ли это, если в команде три человека?
Скорее нет — накладные расходы на администрирование могут не окупиться. Модель раскрывается на командах от 5–10 человек с разными ролями, где утечка одного конфига не должна означать компрометацию всего.
А как быть с MTU и разрывами туннеля при таком количестве правил?
На саму сегментацию число правил в nftables не влияет, но общие проблемы WireGuard — фрагментация, потеря пакетов — решаются отдельно, см. настройку MTU для WireGuard.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →