Wireguard – обалденный, простой VPN, который уже включён в состав ядра. Очень мощный и простой инструмент. Ставиться в разы проще чем OpenVPN. Для его простой настройки используется утилита wg-quick, которая входит в состав wireguard tools. Для более полного понимания, дополнительно уточню, что из моего опыта большинство решений, таких как WGDashboard и wireguard-ui генерируют клиентские конфиги именно под wg-quick.
Переносить wireguard интерфейс в отдельное сетевое пространство имён, чтобы потом в нём запускать приложения. Зачем это делать? Это избавляет от необходимости проксировать трафик на заблокированные сайты через Wireguard какими-то сложными решениями. Нужно зайти куда-то в такое место - просто запустил ещё один браузер в пространстве Wireguard и всё! Я так, к примеру, переписал Exec строку в ярлыке Thunderbird, которому альтернативы пока столь же функциональной нет, и теперь он всегда запускается через VPN и может спокойно собирать почту с любых SMTP.
Использовать нужно ванильный wg-quick без доработок, чтобы это работало с ходу.
В документации, есть 4 пути к дополнительной настройке wg-quick интерфейса. PreUp, PostUp, PreDown, PostDown. Значит нужно написать скрипты таким образом, чтобы они делали всё что нужно. Готовое решение представлено в начале статьи, а теперь его разберём. Я преобразую этот ужатый код в человекочитаемый вид, выглядит иначе, но содержательно буква в букву. Замечание: в коде встречается %i, это макрос самого wg-quick, везде где он встретит этот макрос, подставит имя создаваемого интерфейса.
if ! ip netns list | grep -qF -- %i;then# создаём сетевое пространство если его ещё нет (а его и не должно быть) ip netns add %i
fi# запускаем там loopback интерфейс (127.0.0.1 который)ip -n %i link set up lo
# сохраняем список сетевых адресов которые wg-quick навесил на интерфейс при создании# и кодируем это в base64, потому что это бинарный поток в формате утилиты ipIPS="$(ip address save dev %i | base64)"# переносим интерфейс в наше специальное пространство имёнip link set %i netns %i
# навешиваем адреса обратно, раскодируя их из переменной созданной ранееecho"$IPS"| base64 -d | ip -n %i address restore dev %i
# запускаем сетевой интерфейс в том пространстве имёнip -n %i link set up %i
# ищем маршрут на 0.0.0.0/0 в списке путей нашего пространства имёнif ! ip -n %i route show default | grep -qF default;then# добавляем если не нашли ip -n %i route add default dev %i
fi# тут был скрипт в base64, я его прямо для вас в тексте раскодировал, чтобы просто было понятно что там# скрипт этот раскодируется и в него с помощью printf вставляется имя интерфейса с которым всегда будет# работать скриптcat <<EOT | xargs -0 -I{} printf {} %i > /usr/local/bin/wg-%i-exec
#!/usr/bin/env sh
set -e
# вот сюда это имя интерфейса залетит в переменную
NETNS='%s'
# а вот эту лабуду я отдельно поясню
cat <<EOF | xargs -0 sudo -E nsenter "--net=/var/run/netns/$NETNS" unshare --mount sh -c
mount --bind "/var/run/resolvconf/interfaces/$NETNS" /etc/resolv.conf &&
sudo -E -u '#${SUDO_UID:-$(id -u)}' -g '#${SUDO_GID:-$(id -g)}' -- $@
EOF
EOT# вот такой же скрипт, но мы там выполняем не пользовательскую команду, а wg-quick downcat <<EOT | xargs -0 -I{} printf {} %i > /usr/local/bin/wg-%i-down
#!/usr/bin/env sh
set -e
NETNS='%s'
cat <<EOF | xargs -0 sudo -E nsenter "--net=/var/run/netns/$NETNS" unshare --mount sh -c
mount --bind "/var/run/resolvconf/interfaces/$NETNS" /etc/resolv.conf &&
sudo -E -u '#${SUDO_UID:-$(id -u)}' -g '#${SUDO_GID:-$(id -g)}' -- wg-quick down "${NETNS}"
EOF
EOT# меняем собственника и права на файлыchmod 0755 /usr/local/bin/wg-%i-*
chown root:root /usr/local/bin/wg-%i-*
# просто выводим текст на экран в момент поднятия интерфейса - справка для пользователя,# которая рассказывает о двух скриптах созданных ранее. Этот текст раскодируется и прямо# кидается в sh, то есть выполняется на ходу, а там только и есть что один catcat <<EOT | xargs -0 -I{} printf {} %i | sh -
NAME='%s'
cat <<EOF
# ┐ ┬o┬─┐┬─┐┌─┐┬ ┐┬─┐┬─┐┬─┐ ┌┐┐┬─┐┌┐┐┌┐┐┐─┐
# │││││┬┘├─ │ ┬│ ││─┤│┬┘│ │ │││├─ │ │││└─┐
# └┴┘┘┘└┘┴─┘┘─┘┘─┘┘ ┘┘└┘┘─┘ ┘└┘┴─┘ ┘ ┘└┘──┘
Thanks for using netns wg-quick wrapper!
/usr/local/bin/wg-${NAME}-exec COMMAND - execute shell command in ${NAME} network namespace
/usr/local/bin/wg-${NAME}-down - use it instead of wg-quick down ${NAME}
EOF
EOT
А вот тут самое сумашествие. Поясняю основные моменты.
Когда переносишь сетефой интерфейс в другое пространство, с него, к сожалению, слетают все IP адреса. По-этому, мы их в начале сохраняем, а потом восстанавливаем, после переноса.
Loopback интерфейс в новом пространстве по умолчанию выключен, по-этому мы его включаем сами
Добавляется маршрут только на 0.0.0.0/0 потому что у нас в принципе трафику кроме как на wireguard ходить некуда, по-этому весь туда пускаем и без разницы, что в AllowedIPs на интерфейсе прописано. Так скрипт проще, работает так же, уязвимостей не создаёт.
Создаются два скрипта: wg-%i-exec, wg-%i-down. Первый нужен чтобы ним запускать программы в нашем сетевом пространстве, а второй нужен чтобы просто сделать wg-quick down, потому что просто выполнив wg-quick down остановить интерфейс не выйдет, нужно wg-quick выполнять внутри нашего пространства имён
Внутри пространства имён нет resolv.conf-а, или может не быть. Всегда его создаёт себе сам wg-quick через resolvconf, даже без всех наших ухищрений. То есть он создаётся для интерфейса. Это нужно для разрешение DNS имён.
Нужно сделать так, чтобы даже запукая exec пользователь, не получал права root в подарок, а оставался со своими привилегиями.
Я раскидаю скрипт по строкам в, возможно, даже не рабочий shell код, но так удобнее будет коментировать.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# выполняем от имени судо, сохраняя env текущего пользователя, это будет всё равно с правами рутаsudo -E
# nsenter запустит нам в этом пространстве имёнnsenter "--net=/var/run/netns/$NETNS"# а потом размонтирует, всё что монтировалосьunshare --mount
# команду shell-a, который выполнит скриптsh -c
'
# где мы монтируем resolv.conf созданный wg-quick-ом
mount --bind "/var/run/resolvconf/interfaces/$NETNS" /etc/resolv.conf &&
# и с помощью sudo сохраняя всё тот же env текущего пользователя
sudo -E
# не не от имени рута, а уже от имени пользователя, который запустил exec скрипт
-u '#${SUDO_UID:-$(id -u)}' -g '#${SUDO_GID:-$(id -g)}'# выполняем то, что он попросил-- $@'