Наш рецепт отказоустойчивого VPN-сервера на базе tinc, OpenVPN, Linux

f3a851a68dfd4f228fa3a204ded89173.png

Один из наших клиентов попросил разработать отказоустойчивое решение для организации защищенного доступа к его корпоративному сервису. Решение должно было:

  • обеспечивать отказоустойчивость и избыточность;
  • легко масштабироваться;
  • просто и быстро решать задачу добавления и блокировки пользователей VPN;
  • балансировать нагрузку между входными нодами;
  • одинаково хорошо работать для клиентов на GNU/Linux, Mac OS X и Windows;
  • поддерживать клиентов, которые находятся за NAT.


Готовых решений, удовлетворяющих всем поставленным условиям, не нашлось. Поэтому мы собрали его на базе популярных Open Source-продуктов, а теперь с удовольствием делимся полученным результатом в этой статье.

Разработка концепции


В качестве базовой VPN-технологии со стороны клиента мы выбрали OpenVPN: он прекрасно работает через NAT и поддерживает все требуемые платформы.

OpenVPN было решено развернуть в режиме TLS-сервера, а добавление и блокировку пользователей в нем сделать с помощью пакета easy-rsa, который позволяет создавать ключ и сертификат, а затем отзывать их при необходимости.

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

Итоговое решение вышло простым и изящным. Мы решили использовать N входных нод, адреса которых с помощью round-robin DNS выдаются клиентам. Все ноды и узлы сервиса клиентов включены в единое L2-пространство tinc VPN. Клиентские подключения (тоже L2) объединяются с tinc-интерфейсом в мост. Таким образом, получается, что, подключаясь по OpenVPN, клиент попадает на случайную ноду и оказывается в единой L2-сети со всеми остальными клиентами, нодами и сервисом клиента.

ca17be2fe165454a9863679695398336.png

Для реализации этой схемы были выделены 3 VPS в различных дата-центрах, на которых и требовалось развернуть «точки входа» в сеть (ep1, ep2 и ep3). Кроме того, в сети присутствовал гипервизор с сервисами клиента (hpv1). На всех машинах установили Ubuntu Server 16.04.

Строим tinc VPN


Для начала устанавливаем пакеты:

$ sudo apt-get update && sudo apt-get install tinс


На этом этапе нам нужно определиться с названием сети — пусть будет l2vpnnet. Создаем структуру каталогов:

$ sudo mkdir -p /etc/tinc/l2vpnnet/hosts


В каталоге /etc/tinc/l2vpnnet создаем файл tinc.conf и наполняем его следующим содержимым:

# Имя текущей машины
Name = ep1
# Тип сети, в нашем случае — L2
Mode = switch
# Интерфейс, который мы будем использовать
Interface = tap0
# По умолчанию используется протокол UDP
Port = 655

# Записываем имена всех остальных хостов, к которым мы будем подключаться
ConnectTo = ep2
ConnectTo = ep3
ConnectTo = hpv1


Создаем файл /etc/tinc/l2vpnnet/ep1 и вносим в него параметры:

# Публичный адрес и порт
Address = 100.101.102.103 655
# Используемые алгоритмы шифрования и аутентификации
Cipher = aes-128-cbc
Digest = sha1
# Для уменьшения задержек рекомендуем также выключать сжатие
Compression = 0


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

$ cd /etc/tinc/l2vpnnet && sudo tincd -n l2vpnnet -K2048
Generating 2048 bits keys:
............................................+++ p
.................................+++ q
Done.
Please enter a file to save private RSA key to [/etc/tinc/l2vpnnet/rsa_key.priv]: 
Please enter a file to save public RSA key to [/etc/tinc/l2vpnnet/hosts/ep1]: 


На остальных машинах проделываем аналогичные действия. Файлы с открытым ключом и параметрами подключения (/etc/tinc/l2vpnnet/hosts/ep1|ep2|ep3|hpv1) необходимо разместить у всех участников сети в каталоге /etc/tinc/l2vpnnet/hosts.

Название сети необходимо внести в файл /etc/tinc/nets.boot, чтобы tinc запускал VPN к нашей сети автоматически при загрузке:

$ sudo cat nets.boot 
#This file contains all names of the networks to be started 
#on system startup.
l2vpnnet


При настройке как tinc VPN, так и OpenVPN в нашей компании принято использовать стандартные механизмы управления сетью Ubuntu. Добавим в /etc/network/interfaces описание параметров устройства tap0:

# Устройство запускается автоматически при старте системы
auto tap0
# Указываем режим конфигурации manual, так как IP мы назначим уже на bridge
iface tap0 inet manual
        # Создание устройства перед запуском tinc
        pre-up ip tuntap add dev $IFACE mode tap
        # ... и его удаление после остановки
        post-down ip tuntap del dev $IFACE mode tap
        # Собственно, запуск tinc с настроенной нами сетью
        tinc-net l2vpnnet


Такая настройка позволит нам управлять tinc с помощью ifup/ifdown-скриптов.

Для единого L2-пространства нужно выбрать и L3-пространство. Для примера мы будем использовать сеть 10.10.10.0/24. Настроим bridge-интерфейс и назначим ему IP — для этого внесем в /etc/network/interfaces такую информацию:

auto br0
iface br0 inet static
        # Естественно, IP должен быть разным для хостов
        address 10.10.10.1 
        netmask 255.255.255.0
        # Указываем, что в бридже наш интерфейс tinc vpn
        bridge_ports tap0

        # Отключаем протокол spanning tree для bridge-интерфейса
        bridge_stp off
        # Максимальное время ожидания готовности моста
        bridge_maxwait 5
        # Отключаем задержку при форвардинге
        bridge_fd 0


После этого последовательно стартуем оба устройства на всех серверах и проверяем связанность любым средством диагностики (ping, mtr и т.п.):

$ sudo ifup tap0 && sudo ifup br0
$ ping -c3 10.10.10.2
PING 10.10.10.2 (10.10.10.2) 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_seq=1 ttl=64 time=3.99 ms
64 bytes from 10.10.10.2: icmp_seq=2 ttl=64 time=1.19 ms
64 bytes from 10.10.10.2: icmp_seq=3 ttl=64 time=1.07 ms

--- 10.10.10.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 1.075/2.087/3.994/1.349 ms


Отлично: L2-пространство для входных нод и целевого сервера построено. Теперь нужно добавить в него удаленных клиентов.

Настраиваем OpenVPN


Для начала устанавливаем необходимые пакеты на всех серверах:

$ sudo apt-get update && sudo apt-get install openvpn easy-rsa


Настроим DNS-зону, добавим 3 A-записи с одинаковым именем VPN-сервиса:

vpn.compa.ny.        IN     A    100.101.102.103
vpn.compa.ny.        IN     A    50.51.52.53
vpn.compa.ny.        IN     A    1.1.1.1


DNS будет выступать первым механизмом балансировки нагрузки в нашей системе. Согласно документации, OpenVPN разрешает имя точки подключения и будет последовательно совершать попытки подключиться ко всем IP, в которые разрешается имя. DNS при этом будет отдавать список IP в случайном порядке.

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

Node 1 10.10.10.100-10.10.10.129
Node 2 10.10.10.130-10.10.10.159
Node 2 10.10.10.160-10.10.10.189


Создадим окружение для CA:

$ cd /etc/openvpn
$ sudo -s
# make-cadir ca
# mkdir keys 
# chmod 700 keys
# exit


Теперь отредактируем файл с переменными vars, установив следующие значения:

# Каталог с easy-rsa
export EASY_RSA="`pwd`"
# Путь к openssl, pkcs11-tool, grep 
export OPENSSL="openssl"
export PKCS11TOOL="pkcs11-tool"
export GREP="grep"
# Конфиг openssl
export KEY_CONFIG=`$EASY_RSA/whichopensslcnf $EASY_RSA`
# Каталог с ключами
export KEY_DIR="$EASY_RSA/keys"
export PKCS11_MODULE_PATH="dummy"
export PKCS11_PIN="dummy"
# Размер ключа
export KEY_SIZE=2048
# CA-ключ будет жить 10 лет
export CA_EXPIRE=3650
# Описываем нашу организацию: страна, регион,
# город, наименование, e-mail и подразделение
export KEY_COUNTRY="RU"
export KEY_PROVINCE="Magadan region"
export KEY_CITY="Susuman"
export KEY_ORG="Company"
export KEY_EMAIL="info@compa.ny"
export KEY_OU="IT"
export KEY_NAME="UnbreakableVPN"


Сохраняем и начинаем генерацию ключей:

# . vars
# ./clean-all 
# ./build-ca
Generating a 2048 bit RSA private key
..........................+++
.+++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [RU]:
State or Province Name (full name) [Magadan region]:
Locality Name (eg, city) [Susuman]:
Organization Name (eg, company) [Company]:
Organizational Unit Name (eg, section) [IT]:
Common Name (eg, your name or your server's hostname) [Company CA]:
Name [UnbreakableVPN]:
Email Address [info@compa.ny]:
# ./build-dh
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
…
# ./build-key-server server
# openvpn --genkey --secret keys/ta.key


Создадим тестового пользователя и сразу отзовем его сертификат, чтобы создать список отзыва:

# ./build-key testuser
# ./revoke-full testuser


Скопируем все необходимые для настройки сервера ключи в каталог с ключевой информацией OpenVPN:

# cd keys
# mkdir /etc/openvpn/.keys
# cp ca.crt server.crt server.key dh2048.pem ta.key crl.pem /etc/openvpn/.keys
# exit


Подготовим конфигурацию OpenVPN-сервера, для чего создадим файл /etc/openvpn/server.conf:

# Устанавливаем подробность ведения журнала
verb 4
# Порт и протокол подключения
port 1194
proto tcp-server
# Режим и способ аутентификации 
mode server
tls-server
# Определяем MTU 
tun-mtu 1500
# Определяем имя и тип интерфейса, который будет обслуживать клиентов
dev ovpn-clients
dev-type tap
# Указываем, что TA-ключ используется в режиме сервера
key-direction 0
# Описываем ключевую информацию
cert /etc/openvpn/.keys/server.crt
key /etc/openvpn/.keys/server.key
dh /etc/openvpn/.keys/dh2048.pem
tls-auth /etc/openvpn/.keys/ta.key
crl-verify /etc/openvpn/.keys/crl.pem
# Определяем протоколы аутентификации и шифрования
auth sha1
cipher AES-128-CBC 
# Опция, указывающая, что устройство будет создаваться единожды
# на все время работы сервера
persist-tun
# Указываем тип топологии и пул
topology subnet
server-bridge 10.10.10.1 255.255.255.0 10.10.10.100 10.10.10.129
# Указываем маршрут по умолчанию через туннель и определяем
# внутренние DNS
push "redirect-gateway autolocal"
push "dhcp-option DNS 10.10.10.200"
push "dhcp-option DNS 10.20.20.200"
# Проверяем доступность подключенного клиента раз в 10 секунд,
# таймаут подключения — 2 минуты
keepalive 10 120
# То самое ограничение в 30 клиентов
max-clients 30
# Локальные привилегии демона openvpn
user nobody
group nogroup
# Позволяет удаленному клиенту подключаться с любого IP и порта
float
# Путь к файлу журнала
log    /var/log/openvpn-server.log


Для второго и третьего сервера будем использовать тот же набор ключевой информации — конфигурационные файлы будут отличаться только пулом выдаваемых IP-адресов.

По аналогии с tinc настроим управление OpenVPN через стандартные ifup/ifdown-скрипты, добавив описание устройства в /etc/network/interfaces:

auto ovpn-clients
iface ovpn-clients inet manual 
        pre-up ip tuntap add dev $IFACE mode tap 
        post-up systemctl start openvpn@server.service 
        pre-down systemctl stop openvpn@server.service 
        post-down ip tuntap del dev $IFACE mode tap


Включим интерфейс в мост вместе с tinc, изменив настройки интерфейса br0:

        ...
        netmask 255.255.255.0
        bridge_ports tap0
        bridge_ports ovpn_clients
        bridge_stp off
        ...


Приведем все в рабочее состояние:

$ sudo ifup ovpn-clients && sudo ifdown br0 && sudo ifup br0


Серверная конфигурация готова. Теперь создадим клиентские ключи и ovpn-файл:

$ sudo -s
# cd /etc/openvpn/ca
# ./build-key PetrovIvan
# exit


Для упрощения использования мы создадим клиентский ovpn-файл c ключевой информацией INLINE:

$ vim PetrovInan.ovpn
# Указываем тип подключения, тип устройства и протокол
client
dev tap
proto tcp
# Определяем MTU такой же, как и на сервере
tun-mtu 1500
# Указываем узел и порт подключения
remote vpn.compa.ny 1194
# Отказываемся от постоянного прослушивания порта
nobind
# Опция, которая позволяет не перечитывать ключи для каждого соединения
persist-key
persist-tun
# Корректируем MSS 
mssfix
# Указываем, что будем использовать TA как TLS-клиент
key-direction 1
ns-cert-type server
remote-cert-tls server

auth sha1
cipher AES-128-CBC
verb 4
keepalive 10 40


### Сюда вставляем содержимое ca.crt


 
### Сюда вставляем содержимое ta.key



### Сюда вставляем содержимое PetrovIvan.crt



### Сюда вставляем содержимое PetrovIvan.key


Сохраняем и отдаем клиенту, который просто подключается к VPN, используя ovpn-файл. На этом настройка OpenVPN закончена.

Блокировка клиента


В случае, когда нам необходимо запретить подключение к VPN одному из клиентов (например, при увольнении сотрудника), мы просто отзываем сертификат:

$ ./revoke-all PetrovIvan


После отзыва обновляем на всех серверах crl.pem и выполняем:

$ sudo service openvpn reload


Обратите внимание, что в server.conf отсутствует опция persist-key. Это позволяет обновить ключевую информацию во время выполнения reload — иначе бы для этого потребовался рестарт демона.

Для распространения списка отзыва и выполнения действия reload для OpenVPN мы используем Chef. Очевидно, для этой цели подойдут любые другие средства автоматического развертывания конфигураций (Ansible, Puppet…) или даже простой shell-скрипт.

Кроме того, мы поместили каталог с CA в Git, что позволило и нам, и клиенту совместно работать с ключевой информацией, избегая коллизий.

Заключение


Конечно, описанное решение в ходе эксплуатации развивается. В частности, мы дописали простые скрипты, которые автоматически создают клиентские ovpn-файлы во время генерации ключей, а также работаем над системой мониторинга VPN.

Если у вас есть мысли о слабых местах в этом решении или идеи/вопросы по дальнейшему развитию конфигурации — буду рад увидеть их комментариях!

P.S.


Читайте также в нашем блоге:

  • «Наш рецепт отказоустойчивого Linux-роутера»;
  • «Настройка основного и двух резервных операторов на Linux-роутере с NetGWM».

© Habrahabr.ru