Харденинг strongSwan на всякий постквантовый

strongSwan — опенсорсная имплементация IPsec, фреймворка VPN. Несмотря на двадцатилетний стаж, проект продолжает развиваться: последняя на сегодня версия приложения вышла в декабре. У него подробная документация, есть блог с CVE и публичная база тестов. По полезной пропускной способности, задержке и утилизации CPU strongSwan превосходит Wireguard, но остаётся в тени — из-за сложности и малой пригодности для обхода блокировок. Зато перед теми, кто не ленится, он открывает широкий простор для экспериментов.

strongSwan щедр на плагины и криптографические алгоритмы. В версии 6.0.0 к ним добавили ML-KEM — механизм инкапсуляции ключа с постквантовой стойкостью, почитать о котором можно тут. «Постквантовой» предполагает, что воспроизвести ключ ML-KEM для расшифровки трафика не получится даже на квантовом компьютере — даже потом. Пока от таких атак защищаются эфемерными ключами на эллиптических кривых, но однажды уязвимы, вслед за RSA, станут и они. В статье я расскажу, как подготовиться к этому, настроив strongSwan с ML-KEM и постквантовым предопределённым ключом (PPK).

Мои вводные: сервер на Debian 12 и сотрудница с Arch, которую надо подключить к удалённому рабочему месту в подсети 10.0.0.0/22 и панели администратора в облаке. Адрес сервера — 1.2.3.4, админки — 5.6.7.8, доступ из офиса в интернет ограничен. Аутентифицировать друг друга сервер и клиент будут по сертификатам ECDSA. Сертификаты — ещё одна сильная сторона strongSwan: приложение среди прочего поддерживает взаимную аутентификацию по TLS 1.3, чем я и воспользуюсь.

В репозиториях большинства дистрибутивов, когда я приступал к задаче, нужная сборка 6.0.0 отсутствовала: у Arch — совсем, в нестабильной ветке Debian был отключён ML-KEM, а в официальных образах Docker (раз, два) устарела библиотека. Поэтому я скомпилировал файлы из исходников: взял скрипт для 5.9.14 и добавил флаг --enable-ml. Перед компиляцией убедился, что IPv6 включён, и установил следующие пакеты:

python3-setuptools ruby3.3-dev libcurl4-gnutls-dev systemd-dev libsystemd-dev libnm-dev libgmp3-dev libssl-dev libwolfssl-dev libbotan-2-dev libgcrypt20-dev libpam0g-dev libip4tc-dev libcap-dev

Сервер

strongSwan не создаёт виртуальный интерфейс в системе, ядро которой поддерживает IPsec, но может добавлять адрес туннеля к физическому, а трафиком управляет на основе политик — определить их надо на фаерволе сервера. Я возьму для этого встроенный скрипт _updown: открою порты NAT-T и IKEv2, разрешу ESP и создам таблицы nat и mangle. nat будет заменять адрес клиента на публичный адрес сервера для доступа к облаку, mangle — ограничивать MSS: в зависимости от алгоритмов IPsec по ESP может добавлять к заголовку пакета около ста байт — обычно это приводит к фрагментации, но если на пути попадутся узлы, которые отбрасывают ICMP 3 (Destination unreachable), фрагментация сломается и TCP-сессия клиента завершится по таймауту. Уменьшение MSS помогает это предотвратить. Ещё я форсирую адаптивный MTU, заодно включу форвардинг пакетов:

bob ~ sudo cat << EOF >> /etc/sysctl.d/99-sysctl.conf
net.ipv4.ip_forward = 1
net.ipv4.ip_no_pmtu_disc = 1
EOF

bob ~ sudo sysctl -qp

Скопирую /usr/lib/strongswan/_updown в /etc/swanctl и в копии допишу:

up-client:)
    	iptables -I INPUT 1 -p udp --dport 500 --j ACCEPT
    	iptables -I INPUT 2 -p udp --dport 4500 --j ACCEPT
    	iptables -I FORWARD 1 -m policy --pol ipsec --dir in -p esp -s 10.0.0.0/22 -j ACCEPT
    	iptables -I FORWARD 2 -m policy --pol ipsec --dir out -p esp -d 10.0.0.0/22 -j ACCEPT
    	iptables -t nat -I POSTROUTING 1 -m policy --pol ipsec --dir out -j ACCEPT
    	iptables -t nat -I POSTROUTING 2 -s 10.0.2.0/23 -d 5.6.7.8/32 -o ens224 -m policy --pol ipsec --dir out -j ACCEPT
    	iptables -t nat -I POSTROUTING 3 -s 10.0.2.0/23 -d 5.6.7.8/32 -o ens224 -j MASQUERADE
    	iptables -t mangle -I FORWARD 1 -m policy --pol ipsec --dir in -s 10.0.0.0/22 -o ens224 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
    	iptables -t mangle -I FORWARD 2 -m policy --pol ipsec --dir out -s 10.0.0.0/22 -o ens224 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
    	;;
down-client:)
    	iptables -D INPUT -p udp --dport 500 --j ACCEPT
    	iptables -D INPUT -p udp --dport 4500 --j ACCEPT
    	iptables -D FORWARD -m policy --pol ipsec --dir in -p esp -s 10.0.0.0/22 -j ACCEPT
    	iptables -D FORWARD -m policy --pol ipsec --dir out -p esp -d 10.0.0.0/22 -j ACCEPT
    	iptables -t nat -D POSTROUTING -m policy --pol ipsec --dir out -j ACCEPT
    	iptables -t nat -D POSTROUTING -s 10.0.2.0/23 -d 5.6.7.8/32 -o ens224 -m policy --pol ipsec --dir out -j ACCEPT
    	iptables -t nat -D POSTROUTING -s 10.0.2.0/23 -d 5.6.7.8/32 -o ens224 -j MASQUERADE
    	iptables -t mangle -D FORWARD -m policy --pol ipsec --dir in -s 10.0.0.0/22 -o ens224 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
    	iptables -t mangle -D FORWARD -m policy --pol ipsec --dir out -s 10.0.0.0/22 -o ens224 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
    	;;

Настрою PKI: с помощью одноимённой утилиты выпущу ключ и сертификат удостоверяющего центра и подпишу сертификат сервера.

bob ~ sudo pki --gen --type ecdsa --size 521 \
    --outform pem > /etc/swanctl/private/ca-key.pem

bob ~ sudo pki --self --ca --lifetime 1825 \
    --in /etc/swanctl/private/ca-key.pem \
    --type ecdsa --dn "C=RU, O=Bob LLC, CN=strongSwan CA" \
    --outform pem > /etc/swanctl/x509ca/ca-cert.pem

bob ~ sudo pki --gen --type ecdsa --size 521 \
    --outform pem > /etc/swanctl/private/bob-key.pem

bob ~ sudo pki --pub --in /etc/swanctl/private/bob-key.pem \
    --type ecdsa | pki --issue --lifetime 912 \
    --cacert /etc/swanctl/x509ca/ca-cert.pem \
    --cakey /etc/swanctl/private/ca-key.pem \
    --dn "С=RU, O=Bob LLC, CN=bob.com" --san bob.com \
    --flag serverAuth --flag ikeIntermediate \
    --outform pem > /etc/swanctl/x509/bob-cert.pem

Значения --dn и --san сервера важны. Если его FQDN не резолвится в публичный адрес, подойдёт сам адрес. Сертификат клиента выпускается так же, но его алиас может быть любым, а --serverAuth и --ikeIntermediate не требуются. Будь у сотрудницы клиент на Windows, macOS, Android или iOS, её ключ и сертификат пришлось бы ещё поместить в контейнер PKCS#12.

Теперь сертификат клиента:

bob ~ sudo pki --gen --type ecdsa --size 521 \
    --outform pem > /etc/swanctl/private/alice-key.pem

bob ~ sudo pki --pub --in /etc/swanctl/private/alice-key.pem \
    --type ecdsa | pki --issue --lifetime 912 \
    --cacert /etc/swanctl/x509ca/ca-cert.pem \
    --cakey /etc/swanctl/private/ca-key.pem \
    --dn "C=RU, O=Bob LLC, CN=alice@bob.com" --san "alice@bob.com" \
    --outform pem > /etc/swanctl/x509/alice-cert.pem

Закончив с PKI, уберу с сервера ключ удостоверяющего центра — выпускать новые сертификаты станет неудобно, зато уменьшу риск компрометации инфраструктуры. Ключ и сертификат сотрудницы удалю после передачи.

Сертификаты и ключ, которые остаются на сервере:

bob ~ ( cd /etc/swanctl && ls private x509 x509ca )
private/:
bob-key.pem

x509/:
bob-cert.pem

x509ca/:
ca-cert.pem

Сгенерирую PPK — строку из 64 случайных символов ASCII c энтропией 330 бит: при обмене ключом IKEv2 смешает её идентификатор с результатом ML-KEM. Сочетать последний с PPK — скорее перебор: в усилении классических криптосистем эти инструменты, хоть и не равноценны, самодостаточны, но мой наниматель — пессимист. PPKs появились в strongSwan раньше — в версии 5.7.0, поэтому в отсутствие ML-KEM можно взять их и обменяться ключом с помощью X25519.

bob ~ echo 0x$( dd if=/dev/urandom count=32 bs=1 status=none | xxd -p -c 32 )
0xba8bd35f2ae876d60a781ff19b46580ac0b31c77ae27487324a131dc690a836e

Перехожу к настройке приложения. Меня интересуют два файла: /etc/strongswan.conf и /etc/swanctl/swanctl.conf.

Указывать префикс ppk в secrets последнего обязательно, а id должен соответствовать --san сертификата. Селекторы трафика local_ts и remote_ts в директиве children отвечают за маршрутизацию: сервер ожидает из туннеля пакеты, адресат которых входит в его local_ts — у клиента те же подсети будут в remote_ts. Конфиг ниже выдаёт клиенту адрес из 10.0.2.0/23 и туннелирует трафик к 10.0.0.0/22 и 5.6.7.8/32.

bob ~ sudo mv /etc/swanctl/swanctl.conf{,.bak} 2> /dev/null
bob ~ sudo sed '1d;s/^	//' <<< "
connections {
	remote-access {
    	version = 2
    	send_certreq = no
    	pools = home-office
    	ppk_required = yes
    	proposals = camellia256ctr-sha512-mlkem768
    	local {
            auth = eap-tls
        	id = bob.com
        	certs = bob-cert.pem
    	}
    	remote {
            auth = eap-tls
        }
    	children {
        	office {
            	local_ts = 10.0.0.0/22, 5.6.7.8/32
            	esp_proposals = chacha20poly1305-mlkem768
    			updown = _updown iptables
        	}
    	}
    }
}

pools {
	home-office {
    	addrs = 10.0.2.0/23
	}
}

secrets {
	ppk-alice {
        id = alice@bob.com
		secret = 0xba8bd35f2ae876d60a781ff19b46580ac0b31c77ae27487324a131dc690a836e
	}
}" > /etc/swanctl/swanctl.conf

proposals и esp_proposals позволяют захардкодить набор алгоритмов шифрования, аутентификации, проверки целостности и обмена ключами. Хардкод — палка о двух концах: обе опции можно опустить, но возникает риск того, что сервер согласится на слабый алгоритм клиента. С другой стороны, если набор слишком строгий и короткий, клиенту, который его не поддерживает, вернётся ошибка аутентификации. На деле чем разнообразнее клиенты, тем разнообразнее должен быть набор — но я не оставляю пирам выбора: обменяться ключом им придётся по mlkem768.

Очередь /etc/strongswan.conf:

bob ~ sudo mv /etc/strongswan.conf{,.bak} 2> /dev/null
bob ~ sudo sed '1d;s/^	//' <<< "
charon {
	load_modular = yes
	install_routes = no
	plugins {
        socket-default {
            use_ipv6 = no
        }
        include strongswan.d/charon/*.conf
	}
}

libtls {
    version_max = 1.3
}

include strongswan.d/*.conf" > /etc/strongswan.conf

Запускаю службу и добавляю её в автозагрузку. strongswan-starter — артефакт метапакета strongswan на Debian, который задействует легаси-интерфейс приложения.

bob ~ sudo systemctl disable --now strongswan-starter 2> /dev/null; \
    sudo systemctl enable --now strongswan

Клиент

Одноразовой ссылкой передаю сотруднице корневой сертификат, сертификат и ключ клиента, клиентские swanctl.conf и strongswan.conf:

alice ~ cat /etc/swanctl/swanctl.conf
connections {
	remote-access {
    	remote_addrs = 1.2.3.4
    	vips = 10.0.2.0/23
    	send_certreq = no
    	ppk_required = yes
    	ppk_id = alice@bob.com
    	proposals = camellia256ctr-sha512-mlkem768
    	local {
            auth = eap-tls
        	certs = alice-cert.pem
    	}
    	remote {
            auth = eap-tls
        	id = bob.com
    	}
    	children {
        	office {
                remote_ts = 10.0.0.0/22, 5.6.7.8/32
            	start_action = start
            	esp_proposals = chacha20poly1305-mlkem768 
        	}
    	}
    }
}

secrets {
	ppk-alice {
		id = alice@bob.com
		secret = 0xba8bd35f2ae876d60a781ff19b46580ac0b31c77ae27487324a131dc690a836e
	}
}

alice ~ cat /etc/strongswan.conf
charon {
	load_modular = yes
	half_open_timeout = 30
	plugins {
        include strongswan.d/charon/*.conf
	}
}

libtls {
    version_max = 1.3
}

include strongswan.d/*.conf

В strongswan.conf клиента отсутствует install_routes = no, поэтому демон выделит свои маршруты в локальную таблицу 220. На сервере я отказался от неё ради производительности.

Сертификаты и ключ на клиенте:

alice ~ ( cd /etc/swanctl && ls private x509 x509ca )
private/:
alice-key.pem

x509/:
alice-cert.pem

x509ca/:
ca-cert.pem

Сотрудница подтверждает, что на домашнем роутере разрешён IPsec, и запускает strongswan.service — на Arch служба одна. В логе сервера:

bob ~ journalctl -fu strongswan | grep -E '(select|establish)'
Feb 26 08:16:38 bob.com charon-systemd[693470]: selected proposal: IKE:CAMELLIA_CTR_256/HMAC_SHA2_512_256/PRF_HMAC_SHA2_512/ML_KEM_768
Feb 26 08:16:38 bob.com charon-systemd[693470]: selected peer config 'remote-access'
Feb 26 08:16:38 bob.com charon-systemd[693470]: IKE_SA remote-access[1] established between 1.2.3.4[С=RU, O=Bob LLC, CN=bob.com]...9.10.11.12[С=RU, O=Bob LLC, CN=alice@bob.com]
Feb 26 08:16:38 bob.com charon-systemd[693470]: selected proposal: ESP:CHACHA20_POLY1305/NO_EXT_SEQ
Feb 26 08:16:38 bob.com charon-systemd[693470]: CHILD_SA remote-access{1} established with SPIs c04da127_i cad1369a_o and TS 10.0.0.0/22, 5.6.7.8/32 === 10.0.2.1/32

Статус физического интерфейса и таблицы 220 на клиенте:

alice ~ ip -4 -br a s dev wlo0 && ip r l t 220
wlo0        	UP         	192.168.0.2/24 metric 20 10.0.2.1/32
10.0.0.0/22 via 192.168.0.1 dev wlo0 proto static src 10.0.2.1
5.6.7.8 via 192.168.0.1 dev wlo0 proto static src 10.0.2.1
throw 192.168.0.0/24 proto static
throw 192.168.0.1 proto static

Готово. Осталось автообновление сертификатов — но об этом пусть заботится будущий, постквантовый я.

@jhoag
03.03.2025 17:15 UTC
Первоисточник

Комментарии

@AndreyRz
06.03.2025 15:01 UTC
0

Приветствую, если не сложно можете подсказать какой материал можно изучить для большего понимания темы настройки сервера strongswan чем официальная документация? К сожалению она не поясняет углубленно параметры настройки и некоторые моменты. Два месяца пытаюсь разобраться как сделать сервер на openwrt, вики openwrt имеет устаревшие материалы, а по форуму года три как на темы с подобными вопросами нет ответов, будто все кто знал забрали знания с собой в мир иной.

@jhoag
06.03.2025 15:07 UTC
0

Боюсь, если документации недостаточно, а случай не гуглится, вариантов не много. Можете спросить на гитхабе проекта или в их IRC-канале.

@rendik7Sur
09.03.2025 10:09 UTC
+3

Половина статьи написана ИИ. Качество оставляет желать лучшего.

@jhoag
09.03.2025 10:20 UTC
0

Весь ваш комментарий — чушь, и мы оба этом знаем.

09.03.2025 10:24 UTC
0

К сожалению, как вы говорите "чушь" - это то, что вы выложили, назвав статьей. Перед тем как копировать , надо проверять. По данным, которые выложили вы, я вижу вашу полную некомпетентность. Статья не выдерживает критики, вы учите плохому.

09.03.2025 11:05 UTC
0

Всё ещё не вижу критику — только дешёвый байт и обиду. Жду конкретику: откуда и что копировалось, в чём выражается некомпетентность и плохое.

@rendik7Sur
09.03.2025 13:54 UTC
0

Сертификат описан отвратительно. Про connections тут вообще говорить нечего. Бесплатные уроки грамотности по таким элементарным вещам вам никто давать не будет. Прежде чем освещать такую тему для начала вам бы не помешало прочитать несколько учебников по сетевым технологиям.

@jhoag
09.03.2025 14:02 UTC
0

По-прежнему никакой конкретики. Попробуйте ещё раз, но подумав.