IPSec = айписекс

Начало

Взбрела в мою непокойную голову идея пощупать на практике что можно выжать из такой штуки как IPSec. Воображение нарисовало следующую картину, которую и будем считать финальной целью: есть некоторое количество компьютеров, часть из них напрямую подключено к интернету, часть через NATы, у некоторых адреса статичные, у некоторых динамические в пределах некоторого диапазона, у некоторых очень динамические (например ноутбук который может быть подключен как к собственной локалке, так и по wifi/gprs/… в произвольном месте), некоторые с линухом, а некоторые под виндой. И все это в произвольных комбинациях. Причем компы не только мои, но и некоторого неопределенного круга лиц (и естественно друг о друге далеко не все они знают). Хочется чтобы соединения между этими компьютерами шифровались (ну естественно не между всеми, но чтобы имелась возможность включить такое для любых двух). И чтобы добавлять новые компы к этой группе было просто и быстро, и по возможности автоматически без моего вмешательства (естественно с сохранением возможности ручной фиксации некоторых ключей/сертификатов дабы что-то свое было не подменить). И вот возникла мысля сделать это через IPSec, благо он, казалось бы, такое аккурат позволяет. И вот что из этого вышло…

В первую очередь настраивались linux машины, поскольку и все сервера и мои личные компы используют именно его. Винда, памятуя о наличии в ней GUI для задания политик IPSec, оставлена на сладкое.

Часть первая: openswan

Прочитав кучу документации по ипсеку (могу порекомендовать вики, lartc, и An Illustrated Guide to IPSec) обнаружилось что в основном все доки довольно старые, и зачастую устаревшие. Общим теоретическим сведениям конечно не старость не помеха, но технические реализации в 2.4 (а то и 2.2) ядрах ныне малоактуальны. Как выяснилось в 2.4 и ранее IPSec реализовывался патчами к ядру от проекта freeswan. В 2.6 вошла реализация от KAME и freeswan благополучно помер. Патчить ядро теперь не надо, вся поддержка инфораструктуры оказалась доступной в дебиане все из коробки.

Вкратце, из чего состоит настройка IPSec'а:

  • SPD — security policy database — набор правил, описывающий что хочется получить. Когда применять ipsec, когда нет, какой режим применять и т.п.
  • SAD — security association database — правила, описывающие как это хочется получить.

Политики прописываются вручную, и передаются ядру программкой setkey. Ассоциации можно прописывать вручную, а можно генерировать автоматически на основе политик и дополнительных правил с помощью IKE (Internet Key Exchange). Статическое назначение не наш метод, т.к. неудобно и ненадежно. Реализаций же IKE оказалось доступно несколько. В lartc'е была описана racoon, но начал я с openswan, т.к. описание пакета утверждало что оно поддерживает Opportunistic Encryption (OE) — автоматическое включение шифрования без дополнительных настроек между хостами которые так умеют.

Как довольно скоро выяснилось OE только в описании столь хорошо, в реальности же требуется прописывать публичный ключ хоста в DNS. Причем и в прямую и в обратную зону. Так и представляю себе запрос к моему провайдеру с просьбой вписать такую дуру себе в обратку. Плюс хосты за натом в пролете. Для IPv6 вроде можно получить доступ к своей обратке, но ведь это надо автоматизировать для динамических адресов… А если teredo? В общем весьма сомнительная фича.

Теоретически openswan поддерживает x509 сертификаты, однако.он так и не смог обнаружить ключ по умолчанию, когда он был прописан через сертификат. Пришлось воспользоваться raw RSA ключами. Но и тут выясняется непонятная вещь: для поддержки соединения между двумя хостами нужно вписывать соответствующее правило ему в конфиг. С указанием ключей. RSA ключи, как обычно, состоят из двух частей — публичной и приватной. Тут их почему-то назвали левой и правой (left, right). Как я понял из документации left часть — это приватный ключ. Соответственно right — это открытый. Соответственно нужно вписать в конфиг свой left ключ, и right ключ пира. Нормально. А теперь начинается бред: эти настройки без изменения (и ключи тоже!) необходимо скопировать к пиру. Да, я не шучу — в доке так и указано. И оно так даже работает.

Далее выяснилось что openswan больше предназначен для туннелирования. Да и прописывание всех ключей на всех тачках, с учетом что по сути они превратились в обычные shared ключи… В общем на этой ноте openswan был послан в печь. И было решено воспользоваться racoon, т.к. в lartc'е как раз он и упоминался.

Часть вторая: racoon

Ну что, сразу понравилось что можно авторизироваться по x509 сертификатам и прописать правила «для всех». В итоге конфиги на всех хостах получились почти одинаковые и постоянные. Единственное что авторизация сертификатом происходит в рамках одного CA. Т.е. надо генерировать всем хостам сертификаты в одном месте, так что никаких левых машин среди пиров не будет. Но с этим можно смириться, посчитав такое даже за плюс. Конфиг получился такой, может будет полезно:

path certificate "/etc/racoon/certs";      

remote anonymous
{
    exchange_mode aggressive,main;
    my_identifier asn1dn;
    peers_identifier asn1dn;

    nat_traversal off;

    ca_type x509 "полный путь к сертификату CA";
    certificate_type x509 "имя файла сертификата этого компа" "имя файла ключа этого компа";
    verify_cert on;

    proposal {
        encryption_algorithm aes;
        hash_algorithm sha256;
        authentication_method rsasig;
        dh_group 5;
    }
}

sainfo anonymous {
        pfs_group 5;
        lifetime time 1 hour;
        encryption_algorithm aes;
        authentication_algorithm hmac_sha256;
        compression_algorithm deflate;
}

Небольшая ремарка: по результатам эксплуатации некоторое время выяснилась маленькая неприятность: если перезагрузить политики (с удалением ассоциаций через flush) на одном хосте, то другие хосты об этом далеко не сразу догадываются. В результате, отправляя, например, пинг на другой хост он уходит нормально (и не зашифрованно), удаленный хост его ловит, и ответ посылает зашифрованный. А т.к. на локальной тачке той ассоциации (и соответственно ключа расшифровки) больше нет, то наступает облом. Автоматическая переинициализация ключей происходит только через некоторое время. Поэтому для нейтрализации подобных проблем в sainfo желательно указать параметр lifetime: с ним ключи будут меняться по истечении примерно 80% указанного времени. Соответственно старые ключи сами удаляются, и глобальных проблем с зависшими ключами быть не должно.

Такое IKE заработало нормально, теперь пришла пора нормально расписать политики. Требуемая логика такая: «если что-то просит шифрования — попробовать обменяться ключами, а если целевой адрес среди специально указанных — запросить шифрование». После множества попыток, настроек и разочарований выянилось следующее:

  • Если отключен NAT-T:
    • хосты за натами в пролете с IPSec/IPv4;
    • когда IPSec/IPv4 устанавливалось, то почему-то не работало ничего кроме пингов: ни tcp ни udp пакеты (даже маленькие) не проходили;
    • IPSec/IPv6 работает как по маслу.
  • Если NAT-T включен:
    • удалось создать зашифрованные соединения между хостом за натом и хостом напрямую подключенным к инету;
    • работают и пинги и tcp и udp;
    • однако если подключение производится и из-за ната и с роутера (реализующего этот нат) к одному хосту, то получается путаница. Например с роутера отправляется пинг, удаленный хост его ловит, видит что он с известного адреса, заворачивает ответ в ESP, отправляет обратно, роутер видит ESP пакет (не зная что в нем, но т.к. он благодаря NAT-T завернут в UDP понимает что предназначался он для компа за натом и отправляет туда), и в итоге ответ пинга получает совсем другая машина. И так не только с пингами, так что работоспособность считаем крайне ограниченной;
    • IPSec/IPv6 сломался, т.к. эти пакеты зачем-то тоже заворачиваются NAT-T в IPv4/UDP датаграммы. Со всеми перечисленными выше проблемами, внеся в указанную путаницу еще и свою лепту.

В итоге пришлось остановиться на варианте шифровать только IPv6 пакеты. Благо так все работает. И прописать политики следующим образом:

#!/usr/sbin/setkey -f                                            

flush;                  
spdflush;

# Включить обязательное шифрование для отдельных хостов/сетей (при необходимости):
spdadd -6 <адрес нужного хоста> ::/0 any -P in ipsec esp/transport//require;
spdadd -6 ::/0 <адрес нужного хоста> any -P out ipsec esp/transport//require;

# Включить обязательное шифрование для отдельных сервисов отдельных хостов/сетей (при необходимости):
spdadd -6 <адрес нужного хоста>[<нужный порт>] ::/0 any -P in ipsec esp/transport//require;
spdadd -6 ::/0 <адрес нужного хоста>[<нужный порт>] any -P out ipsec esp/transport//require;

# Политика по умолчанию — использовать IPSEC
spdadd -6 ::/0 2000::/3 any -P out ipsec esp/transport//use;
spdadd -6 ::/0 ::/0 any -P in ipsec esp/transport//use;

# Отключаем IPSEC для Neighbor Solicitation, иначе при малейшей рассинхронизации политик
# (например одна машина перезагрузилась) машины в локальной сети могут «теряться» c диагностикой
# Destination unreachable: Address unreachable.
spdadd ::/0 ::/0 icmp6 135,0 -P in none;
spdadd ::/0 ::/0 icmp6 135,0 -P out none;

В такой настройке все заработало нормально. Политики с require правилом не обязательны, но следует иметь в виду, что соединение из незашифрованного состояния в зашифрованное по каким-то причинам не переходит никогда (а обратно может, например в результате удаления SA командой flush). Поэтому когда вы соединяетесь с хостом в первый раз (когда IPSec-соединения нет), а политика стоит с «use», то оно будет не зашифровано. Параллельно установится зашифрованный канал, но уже готовое соединение в него не попадет, будут шифроваться только новые соединения.

Но тут ВНЕЗАПНО появилась еще одна проблема: в TCP соединениях (например по SSH) как только удаленная машина посылала большой пакет (например реакцию на команду ls) это соединение „зависало“, а в логах сервера-отправителя появлялось «pmtu discovery on SA ESP/xxxxxxxx/nnnn:nnnn:nnnn:nnnn:nnnn:nnnn:nnnn:nnnn» в больших количествах. Очевидно пакет по какой-то причине не пролезал в MTU. Погуглив выяснилось что проблеме уже много лет, и как оказалось она до сих пор нормально не решена. Есть только обходные пути. Самый оптимальный, я считаю, такой: указать на всех машинах следующие правила в ip6tables, т.к. в них есть даже специальный матч для пакетов, которые будут подвергнуты IPSec инкапсуляции:

ip6tables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -m policy --dir out --pol ipsec -j TCPMSS --set-mss 1370
ip6tables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -m policy --dir out --pol ipsec -j TCPMSS --set-mss 1370

Откуда цифра 1370: обычный MTU составляет 1500, эксперименты со сниффером в руках показали что ESP-инкапсулированный пакет получается на 122 байта больше чем MSS. Таким образом максимальный MSS, пролезающий в MTU оказывается 1378. Оставляем небольшой запас на всякий пожарный, можно даже еще уменьшить MSS, например до 1350.

При таких поправках зависания пропали, и все пакеты стали нормально пролезать в MTU.

Мое предположение почему так происходит, и почему это так и не исправлено: пакеты в ESP инкапсулируются 1:1, какой пакет сгенерировался тот и зашифруется/завернется в соответствующие заголовки. Далее содержимое пакета более недоступно, т.к. зашифровано и перепаковать его уже невозможно. В IPv6 фрагментации на роутерах нет (а ipsec уровень видимо себя таковым и считает), и соответственно если отправитель изначально сгенерировал большой пакет, то он после инкапсуляции еще вырастет и в MTU уже не влезет. О том что пакет будет инкапсулироваться отправитель в принципе не знает, и соответственно расчитывает на имеющееся MTU. Теоретически конечно можно производить PMTU, но когда поверх накладывается IPSec такое уже нетривиально. Так что приходится пользоваться таким фиксом.

Таким образом linux ⇔ linux соединения стали шифроваться. С указанными ограничениями: только IPv6.

Часть третья: strongswan

Также как и openswan этот проект развитие почившего freeswan, но, в отличии от перечисленных ранее вариантов, поддерживает IKEv2, почему собственно и заинтересовал. Изучив документацию, обнаружил что он очень похож на openswan (что и неудивительно), но без таких явных проблем.

Настройка в итоге выразилась в распихивание сертификатов/ключей по нужным каталогам, вписывание приватного ключа в ipsec.secrets, и следующей универсальной конфигурации в ipsec.conf (за вычетом тонких настроек разрешенных методов шифрования и т.п.):

conn my
    left=%defaultroute
    leftcert=сертификат_клиента.pem
    right=%any
    type=transport
    auto=add

Но тут выяснилась маленькая, но значительная проблема: т.к. в свойствах политики соединения необходимо прописать локальный IP адрес, то удобно использовать макро %defaultroute, которое автоматически его определит. Но. Оно определяет IPv4 адрес, и соответственно вся политика работает только для IPv4. Для IPv6 необходимо вручную вписать IPv6 адрес, тогда все работает (и нормально соединяется с racoon серверами). Но… В условиях динамического адреса такое невозможно.

Как вариант, разработчики strongswan порекомендовали воспользоваться IKEv2, демон которого воспримет и left=%any. К сожалению такой подход потребует перевода всех пиров на IKEv2, т.е. на strongswan. И более того — он и вовсе не заработал: демон IKEv2 не только не понимает динамические локальные адреса, но и удаленные тоже. Точнее демон то понимает, но до ядра донести не может. А разработчики сделали вид что их нет. Так что strongswan оказался таким же неюзабельным как и openswan.

Часть последняя: винда

В винде настройка IPSec производится через «локальные политики безопасности». Однако относительно полноценная поддержка IPSec/IPv6 присутствует только начина с Vista и Windows Server 2008. В XP же такая поддержка весьма ограничена, можно даже считать что ее нет вообще. Подробней здесь: http://technet.microsoft.com/en-us/library/bb726956.aspx

Также у венды проблемы с поддерживаемыми алгоритмами шифрования: только DES/3DES, никакого AES, Blowfish и прочих прелестей.

Я не пытался скрещивать IKE Racoon’а и висты, но теоретически это возможно. Если кто может поделиться опытом — велкам.

Результаты

Итак программы-максимум не получилось. Получилось только linux ⇔ linux и только по IPv6. Вероятно можно так настроить не только линух, но и BSD. Но все равно приятно.

Комментарии

А что понимается под "всеми

А что понимается под "всеми хостами"? У меня есть две сети (/64), хочу на двух роутерах настроить ipsec. Что-то типа:

A<-plain->Ra<-enc->Rb<-plain->B (A, B - хосты, Ra, Rb - роутеры)

будет работать в режиме transport? Я ракуна уже лет восемь не видел, но с IPv4 наимелся в своё время по самое не балуйся...

Возможно

Теоретически так сделать можно, это получается VPN по IPSEC. Но я не пробовал так. Но должно работать, почему бы и нет. Штатная заявленная фича же.