LINUX.ORG.RU
решено ФорумAdmin

Libvirt, WireGuard, их скрещивание и проброс портов в ВМ

 , , , ,


0

1

hello world

Имеется (dedicated) сервер с одним публичным IPшником, на сервере руками настроен WireGuard (на serverip:42069, например) и прикручен к systemd-networkd, во внешний интернет кроме самого wg ничего не пробрасывается. Сервер держит виртуальную машину с мастдаем (пока что одну, как разберусь с сетевым цирком - будет больше) через KVM+Libvirt.

Нужно сделать так, чтобы был туннель WireGuard между виртуалкой и, например, моей рабочей машиной, чтобы я с последней подключался по, скажем, serverip:22869 (помимо «основного» соединения рабочая машина => сервер). Вопрос: а как так сделать-то?

Я пробовал кривыми-косыми правилами iptables (честно скомунизженных из интернета и адаптированных для своих систем) и с дефолтной NAT-сетью перенаправлять порты сервера в порты виртуалки, но то ли я не вкурил в правила и сделал всё через одно место, то ли изначально не туда вою.
Пробовал ещё вот это - вроде выхлоп iptables -L меняется, а нужный порт всё ещё closed и контакт между виртуалкой и рабочей системой наладить не получается.
Только что попробовал ради прикола поднять bridge-соединение - на виртуалке теперь вообще нет сети (удивительно). Ну и настроил в Libvirt я его так себе, полагаю.

В виртуалке на время тестирования был отключён Windows Firewall, порт WireGuard тот же 22869 (пытался пробрасывать одинаковые порты с хоста в vm).

Наверное, и не писал бы сюда, но подходящих документации и примеров, которые помогли бы мне сделать что-то нормальное, я не нашёл. Вполне возможно, что я упускаю из виду что-то прям совсем очевидное, списываю это на свою неопытность.

UPD: решение и дополнение к нему.



Последнее исправление: mradermaxlol (всего исправлений: 3)
Ответ на: комментарий от anc

Конкретно по iptables ничего не гуглил, только в контексте сетей libvirt (знаю, что надо идти от понимания к действию, но много часов предварительного пердоллинга утомили ум и тело). Мне бы для начала понять, туда ли я вообще копаю :)
Видимо, правильный вариант в моём случае это всё же дефолтная NAT-сеть плюс правила iptables, правильно понимаю?

mradermaxlol
() автор топика

У меня чуть по-другому все, но скину, может поможет разобраться.

У меня отдельно – wg на одной машине, qemu – на другой. При подключении выдется адрес 10.0.0.х, домашнаяя сетка – 192.168.0.х

Конфиг wg0.conf в целом дефолтный, прописано вот такое правило:

wg0.conf

PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o enpls0 -j MASQUERADE

Ну и в PostDown аналогично, только с -D.

С этим правилом при подключении извне я вижу свою 192.168.0.х как если бы сидел дома.

libvirt на другой машине в этой сети. С ним пришлось повозиться, но только по собственной тупости: делал по их вики и пропустил один символ в скрипте. Правило не создавалось и ничего не работало. В отчаянии тоже пробовал ставить в бридж – судя по всему моя сетевуха не поддерживает или еще что, но не завелось совсем никак. Плюс я планирую и другие виртуалки там гонять, в т.ч. одновременно. В итоге поставил в нат, вот рабочий хук:

/etc/libvirt/hooks/qemu

#!/bin/bash

if [ "$1" = "win7" ]; then
	GUEST_IP=192.168.100.175
	GUEST_PORT=3389
	HOST_PORT=3389

	if [[ "$2" = "stopped"  ]]; then
		iptables -D FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi
	if [[ "$2" = "started" ]]; then
		iptables -I FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi 
fi

У виртуалок своя подсеть 192.168.100.х

B 192.168.0.x пробрасывается порт и я подключаюсь к вирутаке по 192.168.0.x:3389.

Единственное что не доделал – не прибита аренда адресов, т.е. 192.168.100.175 при перезапуске меняется. Поэтому я пока не перезапускаю ничего. :)

Думаю у тебя можно будет упростить как-то первое правило iptables, из конфига wg т.к. все на одной машине.

frunobulax ★★★
()
Ответ на: комментарий от frunobulax

Большое спасибо, буду дома - вникну во всё это в деталях. Обнадёживает то, что этот хук из вики вообще у кого-то правильно работает :)
Насчёт адресов - тебе, случаем, не это нужно?

mradermaxlol
() автор топика
Ответ на: комментарий от frunobulax

Хм, а не нужно ли при этом route’ить хотя бы адрес виртуалки?
Вот wg0.network с хоста виртуалок:

[Match]
Name=wg0

[Network]
Address=10.16.0.228/32

# route для рабочей машины
[Route]
Gateway=10.16.0.228
Destination=10.8.0.69/32

[Route]
Destination=192.168.122.228/32
Scope=link

Вполне возможно, что я не совсем правильно понял часть с «To route additional subnets […]» вот тут.

mradermaxlol
() автор топика

Вроде разобрался. Что я понял из всей этой истории:

  • Самое главное: я изначально воевал не туда, бонусом ещё и не понял, как должна работать сеть в моём воображении.
    • Изначально я хотел, чтобы был сервер с виртуалками и туннелем WireGuard с машинами извне, и чтобы при этом можно было обращаться к виртуалкам как к полноценным системам со своим айпишником, и чтобы они по этим самым айпишникам могли сами пробрасывать туннели wg во внешний мир, с которыми потом работали бы внешние машины-клиенты.
    • Но для таких извращений нужны свободные айпишники от хостера, которых в моём случае нет (а к этим айпишником, как я понимаю, уже и прикручиваются bridge-соединения). Тогда задача изменилась: нужно с дефолтной NAT-сетью всего-то пробросить требуемый порт виртуалки на какой-нибудь свободный порт сервера-хоста, для чего нужно… Да, всего лишь прочитать туториал по iptables. Я обязательно выживу осилю парочку.
    • По итогу задача оказалась тривиальной. Максимально просто виртуалкам присваиваются нужные адреса, потом для нужных адресов и портов адаптируется хук - shell-надстройка над iptables. И… Всё. В моём случае с указанными ниже конфигами можно подключаться RDP-клиентом по serverip:32101 к ВМ win10n1 (при включённом в ней RDP), т.е. порт 3389 виртуалки пробрасывается в порт 32101 хоста.

Мини-гайд и по совместительству итог моих мучений:

  • Хитрых сетевых конфигов тут нет: в случае с systemd-networkd нужны рабочая сеть (у меня это ethernet без DHCP) и рабочий WireGuard через связку .netdev и .network. Шаблоны для wg, вдруг кому-то пригодятся:
# 99-wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
Description=WireGuard tunnel (wg0)

[WireGuard]
ListenPort=42069
PrivateKey=host_private_key

# client
[WireGuardPeer]
PublicKey=peer_public_key
AllowedIPs=10.69.0.1/32
# 99-wg0.network
[Match]
Name=wg0

# server wg address
[Network]
Address=10.228.0.1/32

# route for client
[Route]
Gateway=10.228.0.1
Destination=10.69.0.1/32

На клиенте нужно указать в свойствах интерфейса приватный ключ и адрес в wg-сети, после чего задать peer с публичным ключом, конечной точкой (public ip:listening port) и wg-адресом этого самого peer’а.

  • Для libvirt нужна дефолтная NAT-сеть с привязкой конкретных IP виртуальным машинам по их MAC’у;
  • Также для libvirt нужен qemu hook, суть которого описана здесь; конкретно в этом случае я адаптировал хук @frunobulax под свои нужды (ещё раз спасибо за помощь), chmod +x /etc/libvirt/hooks/qemu, перезапустить libvirtd и в общем-то всё:
#!/bin/bash

if [ "$1" = "win10n1" ]; then
	GUEST_IP=192.168.122.101
	GUEST_PORT=3389
	HOST_PORT=32101

	if [[ "$2" = "started" ]]; then
		iptables -I FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi
	if [[ "$2" = "stopped"  ]]; then
		iptables -D FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi
fi

if [ "$1" = "win10n2" ]; then
	GUEST_IP=192.168.122.102
	GUEST_PORT=3389
	HOST_PORT=32102

	if [[ "$2" = "started" ]]; then
		iptables -I FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi
	if [[ "$2" = "stopped"  ]]; then
		iptables -D FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi
fi
  • Этого достаточно для конфигурации, расписанной в начале этой простыни: подключение к вм по wg-адресу сервера с портом, который пробрасывается виртуалке.

TL;DR всё гениальное просто. А ещё надо осваивать iptables, а то как лох.

mradermaxlol
() автор топика
Последнее исправление: mradermaxlol (всего исправлений: 1)
Ответ на: комментарий от mradermaxlol

Рад что все вышло. :)

Прости, вчера видел твое сообщение, но что-то забегался и не ответил.

frunobulax ★★★
()
Ответ на: комментарий от mradermaxlol

Видимо, правильный вариант в моём случае это всё же дефолтная NAT-сеть плюс правила iptables, правильно понимаю?

В libvirt NAT-сеть не более чем создает «парочку» правил в iptables. Все тоже самое можно сделать руками.
Например, я не люблю когда кто-то автоматизированно за меня что-то делает с правилами. Для виртуалок отдельный интерфейс бридж virbr0 без включения в мост физических интерфейсов с отдельной подсеткой. Виртуалкам адреса раздает dnsmasq с прибитием гвоздями ip к маку.
Тупо и просто.

anc ★★★★★
()
Ответ на: комментарий от anc

тупо и просто

Если оно ещё и лаконично - вообще супер. Я пока что не обладаю достаточными познаниями в сетевых технологиях, чтобы сделать это руками и сделать хорошо, но, надеюсь, скоро это изменится.

А так libvirt… мда, разница в выхлопе iptables -L до и после его запуска солидная.

mradermaxlol
() автор топика
Ответ на: комментарий от mradermaxlol

А так libvirt… мда, разница в выхлопе iptables -L до и после его запуска солидная.

1. FYI iptables -L - показывает только таблицу filter. Смотрите выхлоп iptables-save. Либо по отдельности указывайте таблицы, iptables -t nat -L... и т.д.
2. Ну в моем случае достаточно «полутора» правил. Разрешили input, forward и добавили masq. Остальное от лукавого.

anc ★★★★★
()
Ответ на: комментарий от mradermaxlol

UPD: после правок отсюда итоговый хук выглядит так:

#!/bin/bash

if [ "$1" = "win10n1" ]; then
	GUEST_IP=192.168.122.101
	GUEST_PORT=3389
	HOST_PORT=32101

	if [[ "$2" = "started" ]]; then
		iptables -I FORWARD -o virbr0 -i wg0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -I PREROUTING -i wg0 -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi
	if [[ "$2" = "stopped"  ]]; then
		iptables -D FORWARD -o virbr0 -i wg0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -D PREROUTING -i wg0 -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi
fi

if [ "$1" = "win10n2" ]; then
	GUEST_IP=192.168.122.102
	GUEST_PORT=3389
	HOST_PORT=32102

	if [[ "$2" = "started" ]]; then
		iptables -I FORWARD -o virbr0 -i wg0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -I PREROUTING -i wg0 -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi
	if [[ "$2" = "stopped"  ]]; then
		iptables -D FORWARD -o virbr0 -i wg0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
		iptables -t nat -D PREROUTING -i wg0 -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
	fi
fi

Таким образом порт пробрасывается только для интерфейса wg0. Наверное, есть смысл по рекомендациям из темы добавить и другие цепочки iptables, но это уже другая история.

mradermaxlol
() автор топика
Последнее исправление: mradermaxlol (всего исправлений: 1)
Ответ на: комментарий от mradermaxlol

1. А есть смысл в добавлении/удалении правил с учетом того что переменные не меняются?
2. А вот INSERT может по какой-то причине оказаться «вредным». Я не говорю конкретно про текущие правила. Но аппетит приходит во время еды. Если уж использовать изменения при старте/остановке VMs, то я бы создал отдельную цепочку и писал в неё.

anc ★★★★★
()
Последнее исправление: anc (всего исправлений: 1)
Ответ на: комментарий от zemidius

Выглядит как что-то более удобное для большого количества вм.

👍👍👍

mradermaxlol
() автор топика
Ответ на: комментарий от mradermaxlol

М? Не совсем понял, о каких переменных речь.

Посмотрите на свои правила, те которые вы добавляете и удаляете используя хук. У вас при добавлении или удалении правил нет переменных, только константы. То есть например вот это правило, в первом вызове
iptables -I FORWARD -o virbr0 -i wg0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
всегда будет таким
iptables -I FORWARD -o virbr0 -i wg0 -p tcp -d 192.168.122.101 --dport 3389 -j ACCEPT
Далее. Судя по тому что вы проверяете название VM, то у вас VM всегда получает или у неё пописан статиком один и тот же IP адрес. Например: GUEST_IP=192.168.122.101.
То есть совершенно точно, что vm==«win10n1» имеет ip==«192.168.122.101». vm==«win10n2» ip==«192.168.122.102» и так далее. Далее проброшенные порты, на первую vm 32101, на вторую 32102... то есть тоже константы. Вы не подразумеваете, что например «сегодня» порт 32101 будет проброшен на 192.168.122.101:3389/tcp, а «завтра» на 192.168.122.153:3389/tcp.
Так вот какой смысл:
1. вставлять/удалять эти правила, если: Они никаким образом не мешают чему либо. В случае если vm остановлена, эффект что при наличии правила, что без него будет одинаков.
2. Вставка правил в начало цепочки. Не очень хорошая идея. Например у вас присутствуют общие правила которые должны быть выполнены в любом случае при прохождении цепочки FORWARD. Вы вставляете свое правило в начало цепочки
iptables -I FORWARD -o virbr0 -i wg0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
после ACCEPT обработка цепочки завершается и все остальные правила в цепочке не будут выполнены. Проходит время, вы уже не помните что там наколдовали, впихиваете в начало нужное правило, все работает, проходит не определенное время, перестает работать, добавляете ещё раз, работает, проходит время не работает. Понятно что притянуто за уши, но не известно кто именно будет биться головой, зато приблизительно известен перечень слов и выражений которые будут использованы в отношении создавшего такое даже если это будете вы сами ;)

anc ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.