Всем доброго времени суток!
Захотелось поделиться реализацией Hub-and-Spoke VPN между Cisco ISR (в качестве споков) и Linux+StrongSwan (в качестве хабов).
Небольшая предыстория.
На данный момент, в нашей инфраструктуре используется моновендорная среда, базирующаяся на решениях Cisco. В свете последних событий, начали подыскивать возможные варианты замещения как на стороне бранчей (споков), так и на стороне хабов.
Хабы располагаются в ДЦ в виде Cisco CSR. И вот с ними и была основная проблема, так как отечественные решения не могут пока предложить что-то наподобие полностью готового виртуального роутера (поправьте в комментариях, если я ошибаюсь).
В итоге, пока остановились на решении Linux+StrongSwan+FRR.
И первая задача, которую пришлось решить, это как переводить текущую инфраструктуру споков на новый виртуальный роутер, в части построения туннелей.
Все туннели у нас строятся поверх интернета.
На стороне CSR используются virtual-template с шифрованием IKEv2+IPSec, и так-же с помощью IKEv2 пушим ip в сторону споков. Соответственно, эту схему хотелось сохранить.
Для того, чтобы воспроизвести похожий функционал на Linux воспользуемся StrongSwan и утилитой swanctl, так как ipsec.conf самим StrongSwan считается устаревшим методом. Будем делать route-based VPN на базе XFRM интерфейсов.
Нам понадобится, по рекомендациям StrongSwan:
Версия StrongSwan не ниже 5.8.0;
Linux Kernel начиная с 4.19;
Iproute2 версии с 5.1.0+
У меня:
Strongswan - 5.9.6;
Linux kernel - 5.4.17;
Iproute2 - 5.12.0;
Будем считать, что у вас уже открыты порты 500,4500 udp и разрешен протокол ESP (50)
Заливаем конфиг swanctl.conf:
swanctl.conf
connections {
dmvpn {
include /etc/strongswan/swanctl/conf.d/ike_sa_default.conf # Можно включать элементы конфигурации
pools = dmvpn # Пул для "пушинга" адресов на удаленные узлы
keyingtries = 1 # Кол-во попыток установить соединение при первоначальном конекте
mobike = no # Выключаем mobike, во избежание !возможных! проблем. Клиенты у нас статичны
version = 2 # Версия IKE
remote_addrs = %any # Разрешаем коннект с любых адресов
remote-all { # Указываем метод аутентификации удаленной стороны
auth = psk
}
local_addrs = 198.51.100.1 # Указываем наш паблик адрес для данного конекшена
local-dmvpn { # Указываем метод аутентификации нашей стороны
auth = psk
}
proposals = aes256-sha512-modp2048, default # Proposal для постороение IKE туннеля
dpd_delay = 10s # Переодичность проверки, что пир "живой"
children {
dmvpn {
include /etc/strongswan/swanctl/conf.d/child_sa_default.conf # Можно включать элементы конфигурации
updown = /etc/strongswan/swanctl/scripts/auto-interface-xfrm # Указываем директорию где лежит скрипт, который будет выполняться по событию CHILD_SA up и down
start_action = start # Сразу активируем соединение
esp_proposals = aes256-sha512, default # Proposal для постороение ipsec
local_ts = 0.0.0.0/0 # Указываем трафик-селекторы для построения route-based VPN.
remote_ts = 0.0.0.0/0 # Указываем трафик-селекторы для построения route-based VPN.
if_id_out = %unique # Для того, чтобы создавались интерфейсы с уникальными xfrm политиками
if_id_in = %unique # Для того, чтобы создавались интерфейсы с уникальными xfrm политиками
dpd_action = clear # Что делать, когда срабатывает DPD. В данном случае, закрывать коннект
}
}
}
}
pools { # Создание пулов
dmvpn {
addrs = 192.0.2.2-192.0.2.255
}
}
authorities {
}
secrets { # Создание типов аутентификации
ike-dmvpn {
secret = "VERY-STRONG-PSK"
}
}
Для того, чтобы интерфейсы создавались и удалялись автоматически и им присваивались уникальные идентификаторы IPSec политик, воспользуемся встроенным функционалом swanctl — updown. Он срабатывает на событие CHILD_SA up или down. Скрипт будет вызываться автоматически. Кладем его в директорию, например, /etc/strongswan/swanctl/scripts/auto-interface-xfrm и делаем исполняемым.
Сам скрипт:
auto-interface-xfrm
#!/bin/bash
# set charon.install_virtual_ip = no to prevent the daemon from also installing the VIP
set -o nounset
set -o errexit
VTI_IF="Virtual-Access${PLUTO_IF_ID_OUT}" # Лучше использовать id xfrm interface, чтобы уникальные идентификаторы не слетали. И демон не терял менеджмент над интерфейсами
case "${PLUTO_VERB}" in
up-client)
ip link add "${VTI_IF}" type xfrm dev uplink-1 \
if_id "${PLUTO_IF_ID_OUT%%/*}"
ip addr add 192.0.2.1/32 dev "${VTI_IF}"
ip link set "${VTI_IF}" up
ip route add "${PLUTO_PEER_SOURCEIP}" dev "${VTI_IF}" # Прописываем маршрут до клиента
sysctl -w "net.ipv4.conf.${VTI_IF}.disable_policy=1" # По рекомендациям strongswan, для избежания дублирующихся лукапов к политикам
;;
down-client)
ip link del "${VTI_IF}"
;;
esac
Со стороны Cisco isr все стандартно:
Cisco ISR
crypto ikev2 proposal IKE2-PROPOSAL
encryption aes-cbc-256
integrity sha512
group 14
crypto ikev2 policy IKE2-POLICY
proposal IKE2-PROPOSAL
crypto ikev2 keyring FLEX-KEYIRING-FRR
peer HUB-FRR-01
address 198.51.100.1
pre-shared-key VERY-STRONG-PSK
crypto ikev2 profile FLEX-PROFILE-FRR
match identity remote address 198.51.100.1 255.255.255.255
authentication remote pre-share
authentication local pre-share
keyring local FLEX-KEYIRING-FRR
dpd 10 3 on-demand
crypto ipsec transform-set TSET-FRR esp-aes 256 esp-sha512-hmac
mode tunnel
crypto ipsec profile FLEX-IPSEC-FRR
set transform-set TSET-FRR
set ikev2-profile FLEX-PROFILE-FRR
interface Tunnel32
description HUB-FRR-01-TEST
ip address negotiated
ip mtu 1300
ip tcp adjust-mss 1260
tunnel source GigabitEthernet0/0/0
tunnel mode ipsec ipv4
tunnel destination 198.51.100.1
tunnel protection ipsec profile FLEX-IPSEC-FRR
ip route 192.0.2.1 255.255.255.255 Tunnel32
Установку IKE и IPSec туннелей можно проверить командой swanctl -l
Командой ip xfrm policy можно проверить какие политики, к каким идентификаторам интерфейсов привязаны
Команда ip -d link поможет проверить и соотнести политику xfrm к интерфейсу
Теперь туннели на хабе будут создаваться и удаляться автоматически, после установки IPSec. Останется только поднять FRR, настроить BGP dynamic neighbors и часть рутинных операций мы решили.
Готов услышать ваши предложения, комментарии, опыт, конструктивную критику.
И может кому окажется полезен данный небольшой how-to.
UPD: В скрипте "updown" необходимо уменьшить название создаваемого интерфейса xfrm, так как Linux позволяет использовать только 15 символов. Как пример, использовать "vti-${PLUTO_IF_ID_OUT}"