IPSec всемогущий

от автора

Добрый день, друзья. Не секрет, что многим из нас хоть раз, но пришлось столкнуться с необходимостью настройки VPN. Являясь активным читателем Хабра я заметил, что несмотря на обилие статей про IPSec, многим он всё равно представляется чем-то сложным и перегруженным. В данной статье я попытаюсь развеять данные мифы на примере собственной полностью рабочей конфигурации. В четырех примерах мы полностью пройдемся по настройке наиболее популярного решения под Linux (Strongswan) начиная от простого туннеля с аутентификацией сторон PSK-ключами до установки host-to-host соединения с аутентификацией обеих сторон на базе сертификатов от Let’s Encrypt. Интересно? Добро пожаловать под кат!

История вопроса

Изначально VPN планировался только для организации канала между мини-роутером родителей и домашним «подкроватным» сервером, по совместительству выступающим в роли маршрутизатора.

Спустя небольшой промежуток времени к этой компании из двух устройств добавился Keenetic.
Но единожды начав, остановиться оказалось сложно, и вскоре на схеме появились телефоны и ноутбук, которым захотелось скрыться от всевидящего рекламного ока MT_Free и прочих нешифрованных WiFi-сетей.

Потом у всеми любимого РКН наконец-то окреп банхаммер, которым он несказанно полюбил прилюдно размахивать во все стороны, и для нейтрализации его заботы о простых смертных пришлось поддержать иностранный IT-сектор приобрести VPS за рубежом.
К тому же некоей гражданке, внешне напоминающей Шапокляк, всюду бегающей со своим ридикюлем Пакетом и, вероятно, считающей что «Кто людям помогает — тот тратит время зря. Хорошими делами прославиться нельзя», захотелось тайком подглядывать в чужой трафик и брать его на карандаш. Придется тоже защищаться от такой непрошенной любви и VPN в данном случае именно то, что доктор прописал.

Подведем небольшой итог. Нужно было подобрать решение, которое в идеале способно закрыть сразу несколько поставленных задач:

  • Объединить сети между Linux-маршрутизаторами
  • Построить туннель между Linux и бытовым Keenetic
  • Дать доступ к домашним ресурсам и интернету носимым устройствам (телефоны, ноутбуки) из недоверенных сетей
  • Создать надежно зашифрованный туннель до удаленной VPS

Не стоит также забывать про прекрасный принцип KISS — Keep It Simple, Stupid. Чем меньше компонентов будет задействовано и чем проще настройка каждого из них — тем надежнее.

Обзор существующих решений

Коротко пройдемся по тому что есть сейчас:

PPTP

Дедушка Ленин всех протоколов. Умер, «Разложился на плесень и липовый мёд».

L2TP

Кто-то, кроме одного провайдера, это использует?

Wireguard

Проект развивается. Активно пилится. Легко создать туннель между двумя пирами, имеющими статический IP. В остальных случаях на помощь всегда готовы придти костыли, велосипеды с квадратными колёсами и синяя изолента, но это не наш путь.

OpenVPN

Плюсы:

  • Поддержка множества платформ — Windows, Linux, OpenWRT и её производные, Android
  • Стойкое шифрование и поддержка сертификатов.
  • Гибкость настройки.

И минусы:

  • Работа целиком и полностью в user-space.
  • Ограниченная поддержка со стороны домашних машрутизаторов — кривенько-косенько на Mikrotik (не умаляя остальных достоинств железок) и нормально в OpenWRT.
  • Сложности с настройкой мобильных клиентов: нужно скачивать, либо создавать свой инсталлятор, копировать куда-то конфиги.
  • В случае наличия нескольких туннелей ждут танцы с правкой systemd-юнитов на сервере.

OpenConnect (open-source реализация протокола Cisco Anyconnect)
Очень интересное решение о котором, к сожалению, довольно мало информации.

Плюсы:

  • Относительно широкая поддержка различных платформ — Windows, Android, Mac на базе родного приложения Cisco Anyconnect из магазина — идеальный вариант предоставить доступ ко внутренней сети носимым устройствам.
  • Стойкое шифрование, поддержка сертификатов, возможность подключения 2FA
  • Сам протокол полностью TLS-based (в отличие от OpenVPN, который легко детектится на 443 порту). Кроме TLS поддерживается и DTLS — во время установленного сеанса клиент может переключится на передачу данных через UDP и обратно.
  • Прекрасное сосуществование на одном порту как VPN, так и полноценного web-сервера при помощи sniproxy.
  • Простота настройки как сервера, так и клиентов.

Здесь тоже не обошлось без минусов:

  • Работа целиком и полностью в user-space.
  • TCP поверх TCP плохая идея.
  • Поддержки со стороны customer-grade оборудования нет.
  • Сложность установки туннелей между двумя Linux: теоретически можно, практически — лучше потратить время на что-то более полезное.
  • В случае наличия нескольких туннелей ждут танцы с несколькими конфигами и правкой systemd-юнитов.

Казалось бы тупик, но присмотревшись внимательнее и потратив немного времени на изучение я понял, что IPSec на базе IKEv2 способен заменить всё остальное.

IKEv2 IPSEC

Плюсы:

  • С появлением IKEv2 сам протокол стал проще в настройке, в сравнении с редыдущей версией, правда ценой потери обратной совместимости.
  • Благодаря стандартизации обеспечивается работа где угодно и на чём угодно — список можно вести до бесконечности. Linux, Mikrotik (в последних версиях RouterOS), OpenWRT, Android, iPhone. В Windows также есть нативная поддержка начиная с Windows 7.
  • Высокая скорость: обработка трафика полностью в kernel-space. User-space часть нужна только для установки параметров соединения и контроля работоспособности канала.
  • Возможность использовать несколько методов аутентификации: используя как PSK, так и сертификаты, причем в любых сочетаниях.
  • Несколько режимов работы: туннельный и транспортный. Чем они отличаются можно почитать в том числе и на Хабре.
  • Нетребовательность к настройкам промежуточных узлов: если в первой версии IKE были проблемы, вызванные NAT, то в IKEv2 есть встроенные механизмы для преодоления NAT и нативная фрагментация IKE-сообщений, позволяющая установить соединение на каналах с кривым MTU. Забегая вперед скажу, что на практике я еще ни разу не сталкивался с WiFi сетью, где бы клиенту не удалось установить соединение.

Минусы, впрочем, тоже есть:

  • Необходимо потратить немного времени на изучение и понять как это работает
  • Особенность, которая может сбить с толку новичка: IPSec, в отличие от привычных VPN решений, не создает сетевые интерфейсы. Задаются только политики обработки трафика, всё остальное разруливается средствами firewall.

Прежде чем приступить к настройке будем считать что читатель уже немного знаком с базовыми понятиями и терминами. В помощь новичку можно посоветовать статью с Википедии и сам Хабр, на котором уже достаточно интересных и полезных статей по данной тематике.

Приступаем к настройке

Определившись с решением приступаем к настройке. Схема сети в моем случае имеет следующий вид (убрал под спойлер)

Схема сети

ipsecgw.example.com — домашний сервер, являющийся центром сети. Внешний IP 1.1.1.1. Внутренняя сеть 10.0.0.0/23 и еще один адрес 10.255.255.1/30 для установки приватной BGP-сессии с VPS;
mama — Linux-роутер на базе маленького беззвучного неттопа, установленный у родителей. Интернет-провайдер выдает динамический IP-адрес. Внутренняя сеть 10.0.3.0/24;
keenetic — маршрутизатор Keenetic с установленным модулем IPSec. Интернет-провайдер выдает динамический IP-адрес. Внутренняя сеть 10.0.4.0/24;
road-warriors — переносные устройства, подключающиеся из недоверенных сетей. Адреса клиентам выдаются динамически при подключении из внутренного пула (10.1.1.0/24);
rkn.example.com — VPS вне юрисдикции уважаемого РКН. Внешний IP — 5.5.5.5, внутренний адрес 10.255.255.2/30 для установки приватной BGP-сессии.

Первый шаг. От простого к сложному: туннели с использованием pre-shared keys (PSK)

На обоих Linux-box устанавливаем необходимые пакеты:

sudo yum install strongswan

На обоих хостах открываем порты 500/udp, 4500/udp и разрешаем прохождение протокола ESP.
Правим файл /etc/strongswan/ipsec.secrects (на стороне хоста ipsecgw.example.com) и вносим следующую строку:

mama@router.home.local: PSK "Very strong PSK"

На второй стороне аналогично:

root@root.mama.local: PSK "Very strong PSK"

В данном случае в качестве ID выступает вымышленный адрес элестронной почты. Больше информации можно подчерпнуть на официальной вики.

Секреты сохранены, движемся дальше.

На хосте ipsecgw.example.com редактируем файл /etc/strongswan/ipsec.conf:

config setup //Настройки самого демона charon     charondebug = "dmn 0, mgr 0, ike 0, chd 0, job 0, cfg 0, knl 0, net 0, asn 0, enc 0, lib 0, esp 0, tls 0, tnc 0, imc 0, imv 0, pts 0" //Отключаем избыточное логирование conn %default //Общие настройки для всех соединений     reauth = yes     rekey = yes     keyingtries = %forever     keyexchange = ikev2 //Протокол обмена ключами для всех соединений - IKEv2     dpdaction = hold     dpddelay = 5s //Каждые 5 секунд шлем DPD (Dead Peer Detection) удаленной стороне      mobike = yes //Включаем Mobile IKE - пир может менять свой IP без необходимости переустановки тоннеля conn mama //Описываем конкретное соединение     left = %defaultroute //Left - наш пир. Директива %defaultroute указывает демону слушать на предмет установки IKE-сессии интрейфейс, котороый смотрит в default route     right = %any //Удаленный пир может иметь любой IP-адрес     authby = psk //Механизм проверки подлинности - используя секретый ключ     leftid = mama@router.home.local //Наш ID, указанный в ipsec.secrets     rightid = root@router.mama.local //ID удаленного пира     leftsubnet = 10.0.0.0/23,10.1.1.0/24      rightsubnet = 10.0.3.0/24     type = tunnel      ike = aes256-aes192-aes128-sha256-sha384-modp2048-modp3072-modp4096-modp8192,aes128gcm16-sha384-x25519!     esp = aes256-aes192-aes128-sha256-sha384-modp2048-modp3072-modp4096-modp8192,aes128gcm16-sha256-sha384-x25519!      auto = add //При старте charon просто добавляем соединение и ждем подключения удаленной стороны 

Аналогично редактируем на удаленном пире /etc/strongswan/ipsec.conf:

config setup     charondebug = "dmn 0, mgr 0, ike 0, chd 0, job 0, cfg 0, knl 0, net 0, asn 0, enc 0, lib 0, esp 0, tls 0, tnc 0, imc 0, imv 0, pts 0" conn %default     reauth = yes     rekey = yes     keyingtries = %forever     keyexchange = ikev2     dpdaction = restart     dpddelay = 5s     mobike = yes conn mama     left = %defaultroute     right = ipsecgw.example.com     authby = psk     leftid = root@router.mama.local     rightid = mama@router.home.local     leftsubnet = 10.0.3.0/24     rightsubnet = 10.0.0.0/23,10.1.1.0/24     type = tunnel     ike = aes128gcm16-sha384-x25519!     esp = aes128gcm16-sha384-x25519!     auto = route 

Если сравнить конфиги, то можно увидеть что они почти зеркальные, перекрёстно поменяны местами только определения пиров.

Директива auto = route заставляет charon установить ловушку для трафика, подпадающего в заданные директивами left/rightsubnet (traffic selectors). Согласование параметров туннеля и обмен ключами начнутся немедленно после появления трафика, попадающего под заданные условия.

На сервере ipsecgw.example.com в настройках firewall запрещаем маскарадинг для сети 10.0.3.0/24. Разрешаем форвардинг пакетов между 10.0.0.0/23 и 10.0.3.0/24 и наоборот. На удаленном узле выполняем аналогичные настройки, запретив маскарадинг для сети 10.0.0.0/23 и настроив форвардинг.

Рестартуем strongswan на обоих серверах и пробуем выполнить ping центрального узла:

sudo systemctl restart strongswan ping 10.0.0.1 
Убеждаемся что все работает:

sudo strongswan status Security Associations (1 up, 0 connecting):         mama[53]: ESTABLISHED 84 minutes ago, 1.1.1.1[mama@router.home.local]...2.2.2.2[root@router.mama.local]         mama{141}:  INSTALLED, TUNNEL, reqid 27, ESP in UDP SPIs: c4eb45fe_i ca5ec6ca_o         mama{141}:   10.0.0.0/23 10.1.1.0/24 === 10.0.3.0/24 

Нелишним будет так же убедиться что в файле /etc/strongswan/strongswan.d/charon.conf на всех пирах параметр make_before_break установлен в значение yes. В данном случае демон charon, обслуживающий протокол IKEv2, при выполнении процедуры смены ключей не будет удалять текущую security association, а сперва создаст новую.

Шаг второй. Появление Keenetic

Приятной неожиданностью оказался встроенный IPSec VPN в официальной прошивке Keenetic. Для его активации достаточно перейти в Настройки компонентов KeeneticOS и добавить пакет IPSec VPN.

Готовим настройки на центральном узле, для этого:

Правим /etc/strongswan/ipsec.secrects и добавляем PSK для нового пира:

keenetic@router.home.local: PSK "Keenetic+PSK"

Правим /etc/strongswan/ipsec.conf и добавляем в конец еще одно соединение:

conn keenetic     left = %defaultroute     right = %any     authby = psk     leftid = keenetic@router.home.local     rightid = root@router.keenetic.local     leftsubnet = 10.0.0.0/23     rightsubnet = 10.0.4.0/24     type = tunnel     ike = aes256-aes192-aes128-sha256-sha384-modp2048-modp3072-modp4096-modp8192,aes128gcm16-sha384-x25519!     esp = aes256-aes192-aes128-sha256-sha384-modp2048-modp3072-modp4096-modp8192,aes128gcm16-sha256-sha384-x25519!     auto = add 

Со стороны Keenetic настройка выполняется в WebUI по пути: Интернет -> Подключения ->
Другие подключения
. Всё довольно просто

(3 изображения)

Если планируется через тоннель гонять существенные объемы трафика, то можно попробовать включить аппаратное ускорение, которое поддерживается многими моделями. Включается командой crypto engine hardware в CLI. Для отключения и обработки процессов шифрования и хеширования при помощи инструкций CPU общего назначения — crypto engine software

После сохранения настроек рестрартуем strongswan и даём подумать полминуты Keenetic-у. После чего в терминале видим успешную установку соединения:

Все работает:

sudo strongswan status Security Associations (2 up, 0 connecting):     keenetic[57]: ESTABLISHED 39 minutes ago, 1.1.1.1[keenetic@router.home.local]...3.3.3.3[root@router.keenetic.local]     keenetic{146}:  INSTALLED, TUNNEL, reqid 29, ESP SPIs: ca8f556e_i ca11848a_o     keenetic{146}:   10.0.0.0/23 === 10.0.4.0/24         mama[53]: ESTABLISHED 2 hours ago, 1.1.1.1[mama@router.home.local]...2.2.2.2[root@router.mama.local]         mama{145}:  INSTALLED, TUNNEL, reqid 27, ESP in UDP SPIs: c5dc78db_i c7baafd2_o         mama{145}:   10.0.0.0/23 10.1.1.0/24 === 10.0.3.0/24 

Шаг третий. Защищаем мобильные устройства

После чтения стопки мануалов и кучи статей решено было остановиться на связке бесплатного сертификата от Let’s Encrypt для проверки подлинности сервера и классической авторизации по логину-паролю для клиентов. Тем самым мы избавляемся от необходимости поддерживать собственную PKI-инфраструктуру, следить за сроком истечения ключей и проводить лишние телодвижения с мобильными устройствами, устанавливая самоподписанные сертификаты в список доверенных.

Устанавливаем недостающие пакеты:

sudo yum install epel-release sudo yum install certbot

Получаем standalone сертификат (не забываем предварительно открыть 80/tcp в настройках iptables):

sudo certbot certonly --standalone -d ipsecgw.example.com

После того как certbot завершил свою работу мы должны научить Strongswan видеть наш сертификат:

  • в директории /etc/strongswan/ipsec.d/cacerts создаем 2 символические ссылки: одну на корневое хранилище доверенных сертификатов в /etc/pki/tls/certs; и вторую с названием ca.pem, указывающую на /etc/letsencrypt/live/ipsecgw.example.com/chain.pem
  • В директории /etc/strongswan/ipsec.d/certs также создаются два симлинка: первый, с именем certificate.pem, ссылается на файл /etc/letsencrypt/live/ipsecgw.example.com/cert.pem. И второй, с именем fullchain.pem, ссылающийся на /etc/letsencrypt/live/ipsecgw.example.com/fullchain.pem
  • В директории /etc/strongswan/ipsec.d/private размещаем симлинк key.pem, указывающий на закрытый ключ, сгенерированный certbot и лежащий по пути /etc/letsencrypt/live/ipsecgw.example.com/privkey.pem

Добавляем в ipsec.secrets аутентификацию через RSA и связку логинов/паролей для новых пользователей:

ipsecgw.example.com     : RSA key.pem username phone          : EAP "Q1rkz*qt" username notebook       : EAP "Zr!s1LBz" 

Перезапускаем Strongswan и при вызове sudo strongswan listcerts мы должны видеть информацию о сертификате:

List of X.509 End Entity Certificates    subject:  "CN=ipsecgw.example.com"   issuer:   "C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3"   validity:  not before May 23 19:36:52 2020, ok              not after  Aug 21 19:36:52 2020, ok (expires in 87 days)   serial:    04:c7:70:9c:a8:ce:57:cc:bf:6f:cb:fb:d3:a9:cf:06:b0:a8   altNames:  ipsecgw.example.com   flags:     serverAuth clientAuth   OCSP URIs: http://ocsp.int-x3.letsencrypt.org   certificatePolicies:              2.23.140.1.2.1              1.3.6.1.4.1.44947.1.1.1              CPS: http://cps.letsencrypt.org 

После чего описываем новое соединение в ipsec.conf:

conn remote-access     dpddelay = 30s //Переопределяем частоту посылок DPD запросов, чтобы не сажать батарейку телефона     left = %defaultroute     leftid = "CN=ipsecgw.example.com"     leftcert = fullchain.pem //При подключении отдаем клиенту наш сертификат с полной цепочкой доверия     leftsendcert = always     leftsubnet = 0.0.0.0/0 //Инструктируем клиента заворачивать весь трафик в туннель     right = %any     rightid = %any     rightauth = eap-mschapv2 //Аутентифицируем пир, используя EAP-MSCHAP2     rightsendcert = never     eap_identity = %identity      rightsourceip = 10.1.1.0/24 //Strongswan будет выдавать адреса клиентам из данного пула     rightdns = 10.0.0.1,10.0.0.3 //И отдавать указанные DNS     type = tunnel     ike = aes256-aes192-aes128-sha256-sha384-modp2048-modp3072-modp4096-modp8192,aes128gcm16-sha384-x25519!     esp = aes256-aes192-aes128-sha256-sha384-modp2048-modp3072-modp4096-modp8192,aes128gcm16-sha256-sha384-x25519!     auto = add //Добавляем соединение и ждем подключения удаленного пира     dpdaction = restart //Рестартуем соединение, если пир перестал отвечать на DPD

Не забываем отредактировать файл /etc/sysconfig/certbot указав, что обновлять сертификат тоже будем как standalone, внеся в него CERTBOT_ARGS="—standalone".

Так же не забываем включить таймер certbot-renew.timer и установить хук для перезапуска Strongswan в случае выдачи нового сертификата. Для этого либо размещаем простенький bash-скрипт в /etc/letsencrypt/renewal-hooks/deploy/, либо еще раз редактируем файл /etc/sysconfig/certbot.

Перезапускаем Strongswan, включаем в iptables маскарадинг для сети 10.1.1.0/24 и переходим к настройке мобильных устройств.

Android

Устанавливем из Google Play приложение Strongswan.

Запускаем и создаем новый

профиль

Сохраняем профиль, подключаемся и, спустя секунду, можем не переживать о том, что кто-то сможет подсматривать за нами.

Проверяем:

sudo strongswan statusall Security Associations (3 up, 0 connecting): remote-access[109]: ESTABLISHED 2 seconds ago, 1.1.1.1[CN=ipsecgw.example.com]...4.4.4.4[phone] remote-access{269}:  INSTALLED, TUNNEL, reqid 55, ESP in UDP SPIs: c706edd1_i e5c12f1d_o remote-access{269}:   0.0.0.0/0 ::/0 === 10.1.1.1/32         mama[101]: ESTABLISHED 34 minutes ago, 1.1.1.1[mama@router.home.local]...2.2.2.2[root@router.mama.local]         mama{265}:  INSTALLED, TUNNEL, reqid 53, ESP in UDP SPIs: c8c83342_i c51309db_o         mama{265}:   10.0.0.0/23 10.1.1.0/24 === 10.0.3.0/24     keenetic[99]: ESTABLISHED 36 minutes ago, 1.1.1.1[keenetic@router.home.local]...3.3.3.3[root@router.keenetic.local]     keenetic{263}:  INSTALLED, TUNNEL, reqid 52, ESP SPIs: c3308f33_i c929d6f1_o     keenetic{263}:   10.0.0.0/23 === 10.0.4.0/24

Windows

Windows актуальных версий приятно удивил. Вся настройка нового VPN происходит путем вызова двух командлетов PowerShell:

Add-VpnConnection -Name "IKEv2" -ServerAddress ipsecgw.example.com -TunnelType "IKEv2" Set-VpnConnectionIPsecConfiguration -ConnectionName "IKEv2" -AuthenticationTransformConstants SHA256128 -CipherTransformConstants AES128 -EncryptionMethod AES128 -IntegrityCheckMethod SHA256 -PfsGroup PFS2048 -DHGroup Group14 -PassThru -Force

И еще одного, в случае если Strongswan настроен на выдачу клиентам IPv6 адреса (да, он это тоже умеет):

Add-VpnConnectionRoute -ConnectionName "IKEv2" -DestinationPrefix "2000::/3"

Часть четвертая, финальная. Прорубаем окно в Европу

Насмотревшись провайдерских заглушек «Сайт заблокирован по решению левой пятки пятого зампрокурора деревни Трудовые Мозоли Богозабытского уезда» появилась и жила себе одна маленькая неприметная VPS (с благозвучным доменным именем rkn.example.com) в тысяче километров от обезьянок, любящих размахивать банхаммером и блокировать сети размером /16 за раз. И крутилось на этой маленькой VPS прекрасное творение коллег из NIC.CZ под названием BIRD. Птичка первой версии постоянно умирала в панике от активности обезьянок с дубинками, забанивших на пике своей трудовой деятельности почти 4% интернета, уходя в глубокую задумчивость при реконфиге, поэтому была обновлена до версии 2.0.7. Если читателям будет интересно — опубликую статью по переходу с BIRD на BIRD2, в котором кардинально изменился формат конфига, но работать новая вервия стала намного быстрее и нет проблем с реконфигом при большом количестве маршрутов. А раз у нас используется протокол динамической маршрутизации, то должен быть и сетевой интерфейс, через который нужно роутить трафик. По умолчанию IPSec интерфейсов не создает, но за счет его гибкости мы можем воспользоваться классическими GRE-туннелями, которые и будем защищать в дальнейшем. В качестве бонуса — хосты ipsecgw.example.com и rkn.example.com будут аутентифицировать друга друга, используя самообновляемые сертификаты Lets Encrypt. Никаких PSK, только сертификаты, только хардкор, безопасности много не бывает.

Считаем что VPS подготовлена, Strongswan и Certbot уже установлены.

На хосте ipsecgw.example.com (его IP — 1.1.1.1) описываем новый интерфейс gif0:

sudo vi /etc/sysconfig/network-scripts/ifcfg-gif0 DEVICE="gif0" MY_OUTER_IPADDR="1.1.1.1" PEER_OUTER_IPADDR="5.5.5.5" MY_INNER_IPADDR="10.255.255.1/30" PEER_INNER_IPADDR="10.255.255.2/30" TYPE="GRE" TTL="64" MTU="1442" ONBOOT="yes" 

Зеркально на хосте vps.example.com (его IP — 5.5.5.5):

sudo vi /etc/sysconfig/network-scripts/ifcfg-gif0 DEVICE="gif0" MY_OUTER_IPADDR="5.5.5.5" PEER_OUTER_IPADDR="1.1.1.1" MY_INNER_IPADDR="10.255.255.2/30" PEER_INNER_IPADDR="10.255.255.1/30" TYPE="GRE" TTL="64" MTU="1442" ONBOOT="yes" 

Поднимаем интерфейсы, но поскольку в iptables нет правила, разрешающего GRE-протокол, трафик ходить не будет (что нам и надо, поскольку внутри GRE нет никакой защиты от любителей всяких законодательных «пакетов»).

Готовим VPS

Первым делом получаем еще один сертификат на доменное имя rkn.example.com. Создаем симлинки в /etc/strongswan/ipsec.d как описано в предыдущем разделе.

Правим ipsec.secrets, внося в него единственную строку:

rkn.example.com     : RSA key.pem

Правим ipsec.conf:

config setup     charondebug = "dmn 0, mgr 0, ike 0, chd 0, job 0, cfg 0, knl 0, net 0, asn 0, enc 0, lib 0, esp 0, tls 0, tnc 0, imc 0, imv 0, pts 0"     strictcrlpolicy = yes conn %default     reauth = yes     rekey = yes     keyingtries = %forever     keyexchange = ikev2     dpdaction = restart     dpddelay = 5s     mobike = yes conn rkn     left = %defaultroute     right = ipsecgw.example.com     authby = pubkey     leftcert = fullchain.pem     leftsendcert = always     leftauth = pubkey     rightauth = pubkey     leftid = "CN=rkn.example.com"     rightid = "CN=ipsecgw.example.com"     rightrsasigkey = /etc/strongswan/ipsec.d/certs/ipsecgw.example.com.pem     leftsubnet = %dynamic     rightsubnet = %dynamic     type = transport     ike = aes256gcm16-sha384-x25519!     esp = aes256gcm16-sha384-x25519!     auto = route

На стороне хоста ipsecgw.example.com тоже добавляем в ipsec.conf в секцию setup параметр strictcrlpolicy = yes, включающий строгую проверку CRL. И описываем еще одно соединение:

conn rkn     left = %defaultroute     right = rkn.example.com     leftcert = fullchain.pem     leftsendcert = always     leftauth = pubkey     rightauth = pubkey     rightrsasigkey = /etc/strongswan/ipsec.d/certs/rkn.exapmle.com.pem     leftid = "CN=ipsecgw.example.com"     rightid = "CN=rkn.example.com"     leftsubnet = %dynamic     rightsubnet = %dynamic     type = transport     ike = aes256gcm16-sha384-x25519!     esp = aes256gcm16-sha384-x25519!     auto = route     dpdaction = restart 

Конфиги почти зеркальные. Внимательный читатель мог сам уже обратить внимание на пару моментов:

  • left/rightsubnet = %dynamic — инструктирует Strongswan применять политики ко всем типам трафика между пирами
  • В каждом из конфигов указан параметр rightrsasigkey. Без него попытка установки IKE SA всегда будет оканчиваться ошибкой IKE AUTH ERROR в логе, поскольку Strongswan не сможет подписать сообщение без знания открытой части RSA-ключа удаленного пира. Для получения открытых ключей мы можем воспользоваться openssl. На каждом из хостов (ipsecgw и RKN) выполняем sudo /usr/bin/openssl rsa -in /etc/letsencrypt/live/ipsecgw.example.com/privkey.pem -pubout > ~/ipsecgw.example.com.pem и sudo /usr/bin/openssl rsa -in /etc/letsencrypt/live/rkn.example.com/privkey.pem -pubout > ~/rkn.example.com.pem, после чего при помощи scp перекрестно копируем их между серверами в расположения, указаные в конфиге

Не забываем настроить файрвол и автообновление сертификатов. После перезапуска Strongswan на обоих серверах, запустим ping удаленной стороны GRE-туннеля и увидим успешную установку соединения. На VPS (rkn):

sudo strongswan status Routed Connections:          rkn{1}:  ROUTED, TRANSPORT, reqid 1          rkn{1}:   5.5.5.5/32 === 1.1.1.1/32 Security Associations (1 up, 0 connecting):          rkn[33]: ESTABLISHED 79 minutes ago, 5.5.5.5[CN=rkn.example.com]...1.1.1.1[CN=ipsecgw.example.com]          rkn{83}:  INSTALLED, TRANSPORT, reqid 1, ESP SPIs: cb4bc3bb_i c4c35a5a_o          rkn{83}:   5.5.5.5/32 === 1.1.1.1/32

И на стороне хоста ipsecgw

под спойлером

Routed Connections:          rkn{1}:  ROUTED, TRANSPORT, reqid 1          rkn{1}:   1.1.1.1/32 === 5.5.5.5/32 Security Associations (4 up, 0 connecting): remote-access[10]: ESTABLISHED 5 seconds ago, 1.1.1.1[CN=ipsecgw.example.com]...4.4.4.4[phone] remote-access{12}:  INSTALLED, TUNNEL, reqid 7, ESP in UDP SPIs: c7a31be1_i a231904e_o remote-access{12}:   0.0.0.0/0 === 10.1.1.1/32     keenetic[8]: ESTABLISHED 22 minutes ago, 1.1.1.1[keenetic@router.home.local]...3.3.3.3[root@router.keenetic.local]     keenetic{11}:  INSTALLED, TUNNEL, reqid 6, ESP SPIs: cfc1b329_i c01e1b6e_o     keenetic{11}:   10.0.0.0/23 === 10.0.4.0/24         mama[4]: ESTABLISHED 83 minutes ago, 1.1.1.1[mama@router.home.local]...2.2.2.2[root@router.mama.local]         mama{8}:  INSTALLED, TUNNEL, reqid 3, ESP in UDP SPIs: c4a5451a_i ca67c223_o         mama{8}:   10.0.0.0/23 10.1.1.0/24 === 10.0.3.0/24          rkn[3]: ESTABLISHED 83 minutes ago, 1.1.1.1[CN=ipsecgw.example.com]...5.5.5.5[CN=rkn.example.com]          rkn{7}:  INSTALLED, TRANSPORT, reqid 1, ESP SPIs: c4c35a5a_i cb4bc3bb_o          rkn{7}:   1.1.1.1/32 === 5.5.5.5/32 

Туннель установлен, пинги ходят, в tcpdump видно что между хостами ходит только ESP. Казалось бы можно радоваться. Но нельзя расслабляться не проверив всё до конца. Пробуем перевыпустить сертификат на VPS и…

Шеф, всё сломалось

Начинаем разбираться и натыкаемся на одну неприятную особенность прекрасного во всём остальном Let’s Encrypt — при любом перевыпуске сертификата меняется так же ассоциированный с ним закрытый ключ. Изменился закрытый ключ — изменился и открытый. На первый взгляд ситуация для нас безвыходная: если даже открытый ключ мы можем легко извлечь во время перевыпуска сертификата при помощи хука в certbot и передать его удаленной стороне через SSH, то непонятно как заставить удаленный Strongswan перечитать его. Но помощь пришла откуда не ждали — systemd умеет следить за изменениями файловой системы и запускать ассоциированные с событием службы. Этим мы и воспользуемся.

Создадим на каждом из хостов служебного пользователя keywatcher с максимально урезанными правами, сгенерируем каждому из них SSH-ключи и обменяемся ими между хостами.

На хосте ipsecgw.example.com создадим каталог /opt/ipsec-pubkey в котором разместим 2 скрипта.

sudo vi /opt/ipsec-pubkey/pubkey-copy.sh

#!/bin/sh if [ ! -f /home/keywatcher/ipsecgw.example.com.pem ]; then   /usr/bin/openssl rsa -in /etc/letsencrypt/live/ipsecgw.example.com/privkey.pem -pubout > /home/keywatcher/ipsecgw.example.com.pem;   /usr/bin/chown keywatcher:keywatcher /home/keywatcher/ipsecgw.example.com.pem;   /usr/bin/chmod 0600 /home/keywatcher/ipsecgw.example.com.pem;   sudo -u keywatcher /usr/bin/scp /home/keywatcher/ipsecgw.example.com.pem rkn.example.com:/home/keywatcher/ipsecgw.example.com.pem;   status=$?;   if [ $status -eq 0 ]; then     rm -f /home/keywatcher/ipsecgw.example.com.pem;     logger "Public key ipsecgw.example.com.pem has been successfully uploaded to remote host";   else     logger "Public key ipsecgw.example.com.pem has not been uploaded to remote host due to error";   fi   else     logger "Public key ipsecgw.example.com.pem already exist on /home/keywatcher directory, something went wrong"; fi exit 0

sudo vi /opt/ipsec-pubkey/key-updater.sh

#!/bin/sh /usr/bin/cp /home/keywatcher/rkn.example.com.pem /etc/strongswan/ipsec.d/certs/rkn.example.com.pem /usr/bin/chown root:root /etc/strongswan/ipsec.d/certs/rkn.example.com.pem /usr/bin/chmod 0600 /etc/strongswan/ipsec.d/certs/rkn.example.com.pem logger "Public key of server rkn.example.com has been updated, restarting strongswan daemon to re-read it" /usr/bin/systemctl restart strongswan exit 0 

На VPS (хосте rkn.example.com) аналогично создаем каталог с тем же именем, в котором тоже создаем аналогичные скрипты, изменяя только название ключа. Код, чтобы не загромождать статью, под

спойлером

sudo vi /opt/ipsec-pubkey/pubkey-copy.sh

#!/bin/sh if [ ! -f /home/keywatcher/rkn.example.com.pem ]; then   /usr/bin/openssl rsa -in /etc/letsencrypt/live/rkn.example.com/privkey.pem -pubout > /home/keywatcher/rkn.example.com.pem;   /usr/bin/chown keywatcher:keywatcher /home/keywatcher/rkn.example.com.pem;   /usr/bin/chmod 0600 /home/keywatcher/rkn.example.com.pem;   sudo -u keywatcher /usr/bin/scp /home/keywatcher/rkn.example.com.pem ipsecgw.example.com:/home/keywatcher/rkn.example.com.pem;   status=$?;   if [ $status -eq 0 ]; then     rm -f /home/keywatcher/rkn.example.com.pem;     logger "Public key rkn.example.com.pem has been successfully uploaded to remote host";   else     logger "Public key rkn.example.com.pem has not been uploaded to remote host";   fi   else     logger "Public key rkn.example.com.pem already exist on /home/keywatcher directory, something went wrong"; fi exit 0 

sudo vi /opt/ipsec-pubkey/key-updater.sh

 #!/bin/bash /usr/bin/cp /home/keywatcher/ipsecgw.example.com.pem /etc/strongswan/ipsec.d/certs/ipsecgw.example.com.pem; /usr/bin/chown root:root /etc/strongswan/ipsec.d/certs/ipsecgw.example.com.pem /usr/bin/chmod 0600 /etc/strongswan/ipsec.d/certs/ipsecgw.example.com.pem logger "Public key of server ipsecgw.example.com has been updated, restarting connection" /usr/bin/systemctl restart strongswan exit 0 

Скрипт pubkey-copy.sh нужен для извлечения открытой части ключа и копирования его удаленному хосту во время выпуска нового сертификата. Для этого в каталоге /etc/letsencrypt/renewal-hooks/deploy на обоих серверах создаем еще один микроскрипт:

 #!/bin/sh /opt/ipsec-pubkey/pubkey-copy.sh > /dev/null 2>&1 /usr/bin/systemctl restart strongswan exit 0

Половина проблемы решена, сертификаты перевыпускаются, публичные ключи извлекаются и копируются между серверами и пришло время systemd с его path-юнитами.

На сервере ipsecgw.example.com в каталоге /etc/systemd/system создаем файл keyupdater.path

[Unit] Wants=strongswan.service [Path] PathChanged=/home/keywatcher/rkn.example.com.pem [Install] WantedBy=multi-user.target

Аналогично на VPS хосте:

[Unit] Wants=strongswan.service [Path] PathChanged=/home/keywatcher/ipsecgw.example.com.pem [Install] WantedBy=multi-user.target

И, напоследок, на каждом сервере создаем ассоциированную с данным юнитом службу, которая будет запускаться при выполнении условия (PathChanged) — изменении файла и его закрытии его после записи. Создаем файлы /etc/systemd/system/keyupdater.service и прописываем:

[Unit] Description= Starts the IPSec key updating script Documentation= man:systemd.service [Service] Type=oneshot ExecStart=/opt/ipsec-pubkey/key-updater.sh [Install] WantedBy=multi-user.target

Не забываем перечитать конфигурации systemd при помощи sudo systemctl daemon-reload и назначить path-юнитам автозапуск через sudo systemctl enable keyupdater.path && sudo systemctl start keyupdater.

Как только удаленный хост запишет файл, содержащий публичный ключ, в домашний каталог пользователя keywatcher и файловый дескриптор будет закрыт, systemd автоматически запустит соответствующую службу, которая скопирует ключ в нужное расположение и перезапустит Strongswan. Туннель будет установлен, используя правильный открытый ключ второй стороны.

Можно выдохнуть и наслаждаться результатом.

Вместо заключения

Как мы только что увидели чёрт IPSec не так страшен, как его малюют. Всё, что было описано — полностью рабочая конфигурация, которая используется в настоящее время. Даже без особых знаний можно настроить VPN и надежно защитить свои данные.

Конечно за рамками статьи остались моменты настройки iptables, но и сама статья уже получилась и без того объемная и про iptables написано много.

Есть в статье и моменты, которые можно улучшить, будь-то отказ от перезапусков демона Strongswan, перечитывая его конфиги и сертификаты, но у меня не получилось этого добиться.

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

Надеюсь коллеги в комментариях подскажут правильное решение.

ссылка на оригинал статьи https://habr.com/ru/post/504484/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *