TL;DR ¶
1
2
3
| PreUp = ip netns list | grep -qF -- %i && ip netns delete %i || true
PostUp = (ip netns list | grep -qF -- %i || ip netns add %i) && ip -n %i link set up lo && IPS="$(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 && (ip -n %i route show default | grep -qF default || ip -n %i route add default dev %i) && echo 'IyEvdXNyL2Jpbi9lbnYgc2gKc2V0IC1lCgpORVROUz0nJXMnCgpjYXQgPDxFT0YgfCB4YXJncyAtMCBzdWRvIC1FIG5zZW50ZXIgIi0tbmV0PS92YXIvcnVuL25ldG5zLyRORVROUyIgdW5zaGFyZSAtLW1vdW50IHNoIC1jCm1vdW50IC0tYmluZCAiL3Zhci9ydW4vcmVzb2x2Y29uZi9pbnRlcmZhY2VzLyRORVROUyIgL2V0Yy9yZXNvbHYuY29uZiB8fCBcCm1vdW50IC0tYmluZCAiL3Zhci9ydW4vcmVzb2x2Y29uZi9pbnRlcmZhY2UvdHVuLiRORVROUyIgL2V0Yy9yZXNvbHYuY29uZiAmJiBcCmV4cG9ydCBEQlVTX1NFU1NJT05fQlVTX0FERFJFU1M9dW5peDpwYXRoPS9kZXYvbnVsbCAmJiBcCnN1ZG8gLUUgLXUgJyMke1NVRE9fVUlEOi0kKGlkIC11KX0nIC1nICcjJHtTVURPX0dJRDotJChpZCAtZyl9JyAtLSAkQApFT0YK' | base64 -d | xargs -0 -I{} printf {} %i > /usr/local/bin/wg-%i-exec && echo 'IyEvdXNyL2Jpbi9lbnYgc2gKc2V0IC1lCgpORVROUz0nJXMnCgpjYXQgPDxFT0YgfCB4YXJncyAtMCBzdWRvIC1FIG5zZW50ZXIgIi0tbmV0PS92YXIvcnVuL25ldG5zLyRORVROUyIgdW5zaGFyZSAtLW1vdW50IHNoIC1jCm1vdW50IC0tYmluZCAiL3Zhci9ydW4vcmVzb2x2Y29uZi9pbnRlcmZhY2VzLyRORVROUyIgL2V0Yy9yZXNvbHYuY29uZiB8fCBcCm1vdW50IC0tYmluZCAiL3Zhci9ydW4vcmVzb2x2Y29uZi9pbnRlcmZhY2UvdHVuLiRORVROUyIgL2V0Yy9yZXNvbHYuY29uZiAmJiBcCnN1ZG8gLUUgLXUgJyMke1NVRE9fVUlEOi0kKGlkIC11KX0nIC1nICcjJHtTVURPX0dJRDotJChpZCAtZyl9JyAtLSB3Zy1xdWljayBkb3duICIke05FVE5TfSIKRU9GCg==' | base64 -d | xargs -0 -I{} printf {} %i > /usr/local/bin/wg-%i-down && chmod 0755 /usr/local/bin/wg-%i-* && chown root:root /usr/local/bin/wg-%i-* && echo 'TkFNRT0nJXMnCmNhdCA8PEVPRgojICDilJAg4pSsb+KUrOKUgOKUkOKUrOKUgOKUkOKUjOKUgOKUkOKUrCDilJDilKzilIDilJDilKzilIDilJDilKzilIDilJAgIOKUjOKUkOKUkOKUrOKUgOKUkOKUjOKUkOKUkOKUjOKUkOKUkOKUkOKUgOKUkAojICDilILilILilILilILilILilKzilJjilJzilIAg4pSCIOKUrOKUgiDilILilILilIDilKTilILilKzilJjilIIg4pSCICDilILilILilILilJzilIAgIOKUgiDilILilILilILilJTilIDilJAKIyAg4pSU4pS04pSY4pSY4pSY4pSU4pSY4pS04pSA4pSY4pSY4pSA4pSY4pSY4pSA4pSY4pSYIOKUmOKUmOKUlOKUmOKUmOKUgOKUmCAg4pSY4pSU4pSY4pS04pSA4pSYIOKUmCDilJjilJTilJjilIDilIDilJgKVGhhbmtzIGZvciB1c2luZyBuZXRucyB3Zy1xdWljayB3cmFwcGVyIQoKL3Vzci9sb2NhbC9iaW4vd2ctJHtOQU1FfS1leGVjIENPTU1BTkQgICAtIGV4ZWN1dGUgc2hlbGwgY29tbWFuZCBpbiAke05BTUV9IG5ldHdvcmsgbmFtZXNwYWNlCi91c3IvbG9jYWwvYmluL3dnLSR7TkFNRX0tZG93biAgICAgICAgICAgLSB1c2UgaXQgaW5zdGVhZCBvZiB3Zy1xdWljayBkb3duICR7TkFNRX0KRU9G' | base64 -d | xargs -0 -I{} printf {} %i | sh -
PreDown = rm -f /usr/local/bin/wg-%i-* || true
|
Введение ¶
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, везде где он встретит этот макрос, подставит имя создаваемого интерфейса.
PreUp ¶
Тут всё просто.
1
2
3
4
5
| # в списке сетевых пространств ищем наше
if ip netns list | grep -qF -- %i; then
# если нашли - удаляем
ip netns delete %i
fi
|
PostUp ¶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| 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, потому что это бинарный поток в формате утилиты ip
IPS="$(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 down
cat <<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, то есть выполняется на ходу, а там только и есть что один cat
cat <<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 выполнять внутри нашего пространства имён
Как работает wg-%i-exec ¶
Тут есть ряд проблем, которые решает скрипт:
- Внутри пространства имён нет 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)}'
# выполняем то, что он попросил
-- $@
'
|
Итог ¶
Просто вставляем эти 3 строки в конфиг и всё! Никакого Ansible, танцев с бубном, сложной настройки и поддержки.