пятница, 12 октября 2012 г.

Все со всеми (tinc mesh vpn)


Новый vpn, теперь банановый..

Получается так, что в одной организации происходят постоянные незначительные пертрубации. До того незначительные и до того постоянные, что становятся головной болью из-за их постоянного сопровождения. То директор работает дома, то бухгалтер, иногда компьютер унесут в другой офис, или, что еще круче, захотят развернуть сервер в облаке.
Плюс, огромное желание начальства, чтобы некоторые пользователи не могли физически видеть некоторые серверы, при этом обеспечив возможность видимости двух любых машин из абсолютно разных филиалов.
Решалось все это применением vpn технологий, и работало на openvpn + pptpd + ospfd. В общем мешанина жуткая, требовательная, нагруженная и не поддающаяся профилактике в рабочее время, причем имеющая точку отказа в виде впн-серверов.
В общем поносившись с ними некоторое время, было решено что-нибудь придумать. Этим что-нибудь стал mesh-vpn. Муки выбора были недолгими, однозначно победил tinc (tinc-vpn.org), как самый легкий, кроссплатформенный и бесплатный (тем более, что отрицательных отзывов о нем я не нашел). Из аналогов - hamachi, плюс еще куча сервисов, разных по цене и возможностям.
Попробуем все это упаковать в законченное решение.

Для начала: tinc не смотря на его маленький размер может выступать как клиент, так и сервер, включает в себя сервис маршрутизации и проверки подлинности. Настраивается текстовыми конфигами и всеяден по платформам. Может работать как маршрутизатор, так и в режиме коммутатора.
Далее опишу требования к сети vpn подробно:

  • связать рабочие станции и сервера друг с другом, вне зависимости от их физического местонахождения.
  • подключаться компьютеры должны при наличии любого соединения с другой машиной (wi-fi, lan, wan, патчкорд)
  • обходить хитрые наты и файерволы провайдеров и горе-админов
  • работать стабильно и без лишней суеты по администрированию
  • обеспечивать безопасность подключения клиентов
  • обеспечивать непрерывную работу при наличии нескольких вариантов подключения (два провайдера например), при минимальном вмешательстве администратора
Ну, вот такие требования у нас есть, будем реализовывать.
Для начала что нам нужно настроить:
1. Задать имя сети (в моем примере vpn-white)
2. Задать имя хоста
3. Определиться с портом по которому будет работать tinc
4. Задать адреса, по которым будет доступен каждый узел
Вот тут и кроется засада, а именно в 4 пункте. Ведь бывают и провайдеры с динамическими адресами, да и в локалке dhcp может выдать новый адрес. Значит надо как-то это все автоматизировать и сообщать другим нодам новый адрес. В голову приходили очень странные мысли - irc, jabber, сайт в инете. Но вспомнив, что у меня есть аккаунт в assembla, и svn замечательно подходит для хранения маленьких текстовых файлов, на том и остановился.
Теперь сами настройки (для начала для Linux):
В папке с конфигурацией tinc (/etc/tinc) есть файл - nets.boot он описывает те сети vpn, которые должны автоматически стартовать при запуске службы.
Должна существовать папка, соответствующая имени сети vpn в которой будут лежать ее конфигурационные файлы.
В этой папке лежат четыре файла: tinc.conf содержащий настройки локальной службы vpn, tinc-up скрипт выполняемый при старте vpn сети, и tinc-down выполняемый при завершении, а также файл закрытого ключа шифрования, отвечающий за аутентификацию нод в vpn сети.
И еще одна вложенная папка hosts в которой лежат все настройки узлов, по одному файлу на узел с именем соответствующим имени узла.

Примерная логика работы:
При старте службы vpn получить обновления файлов узлов в сети (вдруг их стало больше, либо у какого-то узла изменился адрес)
Получить список собственных ip адресов и сравнить их с заданными в файле описания узла, если были изменения, внести их в файл и выполнить синхронизацию.
Поднять сетевой интерфейс.
Периодически проверять обновления файлов описания узлов.

С логикой разобрались, начнем конфигурять (простите 1с-ники)
Идем на сайт assembla.com, создаем аккаунт и простейший приватный svn хостинг. Получаем ссылку на него вида : https://subversion.assembla.com/svn/***/ (под звездочками имя вашего пространства. Для опытных пользователей можно использовать как свой svn хостинг, так и любой другой приватный хостинг, например git

Установим необходимые пакеты:
Ubuntu: apt-get install subversion tinc (если вы на убунте, не забудьте везде добавить sudo, в том чисте и к командам ниже)
rhel, centos, scientific: yum install subversion tinc (предварительно подключить репозиторий repoforge)
cd /etc/tinc (для rhel совместимых создать папку tinc)
mkdir -p vpn-white/hosts
cd vpn-white
nano tinc.conf  - прописываем имя локального узла (Name = node01) разрешены только цифры, буквы и подчеркивание (проверено практикой)

копируем скрипт в tinc-up:

#!/bin/bash
basedir="/etc/tinc"
netdir="vpn-white"

cd $basedir
cd $netdir
cd hosts
needcommitsvn=0
versvn=`svn up`

# Проверяем, есть ли файлы не под svn и добиваем их
notsvn=`svn status|grep "?"`
filenotsvn=${notsvn//"? "/}
if  [ $filenotsvn ]
then
svn add $filenotsvn
needcommitsvn=1
fi

#Получим имя нашего компа
hsnametmp=`cat $basedir/$netdir/tinc.conf|grep "Name"`
hsname=${hsnametmp//"Name = "/}

#Пробежимся по всем хостам и добавим отсутствующие в ConnectTo
for i in *
do
 if [ $hsname != $i ]
  then
   if grep $i $basedir/$netdir/tinc.conf &> /dev/null
    then
     echo ""
    else
     echo "ConnectTo = "$i >> $basedir/$netdir/tinc.conf
   fi
 fi
done

# Получаем все ip хоста и заносим куда следует (исключая loop интерфейсы, интерфейс самой сети vpn и машин с гипервизором kvm(есть у меня такие))
ips=$(ifconfig | grep 'inet addr:' | sed 's/:/ /' | awk '{print $3}' | grep -v '127.0.0' | grep -v '10.3.1'| grep -v ‘192.168.122’)
for i in $ips; do
if grep $i $basedir/$netdir/hosts/$hsname &> /dev/null
 then
  echo ""
 else
  sed -i -e "1i Address = $i" $basedir/$netdir/hosts/$hsname
  needcommitsvn=1
 fi
done

#Записываем порт, если забыли
if grep "Port = 666" $basedir/$netdir/hosts/$hsname &> /dev/null
then
 echo ""
else
 sed -i -e "1i Port = 666" $basedir/$netdir/hosts/$hsname
 needcommitsvn=1
fi

#заносим имя сети в автостарт
if grep $netdir $basedir/nets.boot &> /dev/null
then
 echo ""
else
 echo $netdir >> $basedir/nets.boot
fi

# Проверяем, нужно ли коммитить изменения
if [ $needcommitsvn == 1 ]
then
svn ci -m 'need commit'
fi

hsvpnip=$(grep "Subnet" $basedir/$netdir/hosts/$hsname |sed 's/Subnet = //')
ifconfig $INTERFACE $hsvpnip netmask 255.255.255.0

копируем скрипт в tinc-down

#!/bin/sh
ifconfig $INTERFACE down

Обязательно chmod +x tinc-*

Теперь необходимо подключить папку hosts к svn
cd hosts
svn co https://subversion.assembla.com/svn/***/ ./ --username=user (user - ваш логин в ассембле)
Вводим пароль, соглашаемся на хранение пароля, и получаем локальную копию svn.
Запускаем генерацию ключевой пары
tincd -n vpn-white -K
Ждем пока сгенерируется, два раза жмем enter
В результате в папке vpn-white будет лежать закрытый ключ, а в папке hosts будет создан файл описания локального узла, содержащий открытый ключ.
Отредактируем его (nano node01) и добавим Subnet = 10.79.83.1 (где сеть 10.79.83.x - наша впн сеть, и адрес выбран такой, чтобы по возможности не было пересечения с существующими локальными сетями и сетями провайдера)
В данном случае локальный узел будет отзываться по этому ip. Я не использую здесь динамические адреса, поскольку хочу жестко зафиксировать клиентов vpn и в дальнейшем использовать их для iptables серверов.
Обязательное условие, добавить строчку с Subnet в начало файла, перед отпечатком ключа, иначе tinc его не увидит.
Вернемся чуть ниже, сделав первый прогон скрипта
cd ..
./tinc-up

Он обнаружит свой файлик описания узла, добъет в него порт, собственные ip адреса, настроит tinc.conf для подключения к узлам, еще немного полезного... и поднимет интерфейс vpn с именем сети и адресом, указанным в Subnet. А также скинет все наши изменения на assembla.
Точнее, при первом прогоне, он его не сможет поднять интерфейс, так что на эту ошибку не обращайте внимания.
Все, сервис готов к работе
на убунту ставится init-скрипт вместе с пакетом, на rhel он отсутствует, так что добавьте в rc.d строку /usr/bin/tincd -n vpn-white
Запускаем
tincd -n vpn-white -D -d2 (-D не демонизировать, выводить отладку в консоль, -d2 указать уровень отладки)
Если все хорошо он будет висеть в консоли показывая попытки соединения, если они есть, или есть с кем.
На втором компьютере повторяем все шаги изменив только имя узла в tinc.conf и ip в Subnet.
Запустив tinc на втором компьютере, возвращаемся на первый, и видим попытки соединения от второго узла, но пока мы о нем ничего не знаем.
Идем в hosts, делаем там svn update, к нам приходит описание второго узла и машины начинают видеть друг-друга примерно через минуту (может немного больше). Перезапуска демона не требуется.
Теперь ставим в cron задание по обновлению папки hosts через svn update. Слишком часто не надо ставить, иначе assembla обидится, и перестанет нас пускать к себе (вроде как не больше 30 commit в час)
Продолжаем так со всем парком машин и у нас наступает счастье все видят всех, пакеты бегают по кратчайшему маршруту, при пропадании какой-либо машины из сети, пакеты выбирают другой маршрут. И все это не зависит от того, где физически они находятся, и откуда подключаются.

Все просто и замечательно, но как же быть с багами?
1. Самый большой баг - Windows. На XP sp2 не работает, вообще (есть одна такая)
2. На XPsp3, Vista, 7, 8, 2008Server работает как часы (месяц без “разрывов”)
3. На windows и rhel падает по ошибке Segmentation fault. Долго разбирался, но разобрался - оказывается это происходит если более 2/3 узлов отвергли соединение из-за ошибки в проверке ключей. Так что достаточно сделать svn up на нескольких узлах и ошибка пропадает. Бывает только с новыми узлами.
4. На windows server 2008 sp1 пропадает служба. Вроде была, а после ребутf, уже нет. Правится назначенным заданием на запуск системы.

Кстати, по поводу windows. Со скриптами не заморачивался (откровенно - лень), у меня этой системы где-то 4-5%, поэтому сделал все руками. Клиент svn - tortoisesvn, tin с офф.сайта. Устанавливаем адаптер скриптом в папке программы, обзываем его vpn-white, в файле tinc.conf дописываем Interface = vpn-white, так же указываем имя узла.
Черепахой синхронизируем hosts. Скрипты писать не надо, vpn ip забиваем в свойства сетевого адаптера.
Ключ генерируется той же командой. Правим файлик описания локального узла, добивая туда все ip адреса сервера. Commit изменений на ассемблу. Правим tinc.conf указывая те узлы с которыми хотим соединиться в формате ”ConnectTo = node01”. Можно указать всех, можно только важных, как решите.
Запускаем той-же командой. В результате создается служба, которой можно управлять из командной строки, консоли mmc, или .bat. Имя службы в нашем случае - tinc.vpn-white.

P.S. По поводу безопасности. В ассембле лежат только открытые ключи. Для того чтобы скомпрометировать вашу сеть vpn одного закрытого ключа недостаточно, надо похитить как минимум два (т.е. получить доступ к двум машинам одновременно). Что уже достаточно сложно. Правда их адреса тоже лежат в асембле.
P.P.S. По поводу скриптов - чукча не писатель. На самом деле у меня сейчас все немножко по другому, но в том скрипте много конф.информации, да и завязан он на puppet.
Кстати с puppet настройка выполняется еще проще и быстрее.
P.P.P.S. сеть называется vpn-white, так как есть еще одна, в которой только доверенные машинки. Так сказать реализовал vlan “по своему”.




5 комментариев:

  1. "Я кончил и закурил" ©анекдот
    А если серьезно - огромное спасибо тебе, дорогой человечище, за этот труд. Давно руки чесались сделать mesh на тинке, а тут прямо все на блюдечке.
    Поскольку сегодня 20-е декабря, моим пожеланием будет - да минует тебя конец света %)

    ОтветитьУдалить
  2. большое спасибо за эту статью, однако на практике возник вопрос.запустив tinc на 2-х машинах в локольной сети - все заработало, а то же самое на 3-х географически разнесенных (разные города) машинах расположенных за nat - соединиться не получилось не видят друг друга. или я делаю что-то не так или tinc не проходит через nat/ не поможете разобраться?

    ОтветитьУдалить
    Ответы
    1. Тинк не использует центральный сервер, поэтому для инициализации соединения необходимо, чтобы компьютеры узнали о возможности пройти нат. Для этого необходимо иметь хотя бы один компьютер с реальным ip, или прописанным правилом port-forwardю

      Удалить