С недавних пор заинтересовал меня вопрос, как можно попадать в свою домашнюю сеть из большого Интернета.
Нет, конечно, проброшенные ssh и rdp-порты выручают, но всё-таки это как-то неизящно.
Есть, правда, ещё pptp, он он, говорят, небезопасный... В общем, захотелось что-нибудь посолиднее, и выбор пал на OpenVPN.
Сама установка трудностей не представляет:
sudo apt install openvpn
Основная интрига - как этот самый openvpn-сервер сконфигурировать.
В сети полно инструкций, как это сделать с использованием easy-rsa, но на поверку оказалось, что этот самый easy-rsa - просто набор bash-скриптов для работы с openssl, так что особого смысла в использовании этой дополнительной прослойки я не увидел и насоздавал ключи и сертификаты прямо с использованием openssl.
Делается это в два присеста. Во-первых, организуется удостоверяющий центр, и, во-вторых, создаются и с его помощью подписываются сертификаты для сервера и для клиента.
В результате образуется следующая структура:
Удостоверяющий центр:
-- приватный ключ УЦ (бережно хранится и никому не показывается)
-- сертификат УЦ (раздаётся всем кому попало)
эта пара используется для подписи сертификатов сервера и клиента
Сервер OpenVPN:
-- сертификат УЦ
-- приватный ключ сервера (бережно хранится и никому не показывается)
-- сертификат сервера, подписанный УЦ (передаётся клиентам при установлении подключения)
Клиент, подключающийся к серверу:
-- сертификат УЦ
-- приватный ключ клиента (бережно хранится и никому не показывается)
-- сертификат клиента, подписанный УЦ (передаётся серверу при установлении подключения)
При подключении клиент и сервер обмениваются своими сертификатами.
Сервер проверяет, что сертификат клиента подписан тем же УЦ, что и его собственный, а клиент делает такую же проверку с сертификатом сервера.
Если оба сертификата прошли проверку, соединение устанавливается, сервер вытаскивает из сертификата клиента ComnmonName и использует его для идентификации клиента.
Создание удостоверяющего центра (УЦ)
Можно, конечно, использовать сторонние УЦ, но там, как правило, за работу требуют денежку.
Поэтому логично поднять свой УЦ.
Фактически, весь УЦ - это пара файлов, ключ и самоподписанный сертификат.
Единственная тонкость - из соображений безопасности эту пару файлов желательно запрятать поглубже: вынести в docker, на отдельную машину, отправить на Луну...
Так или иначе, создаются эти файлы следующим образом.
1. Создаём приватный ключ УЦ:
openssl genrsa -des3 -out huhmuh-CA.key 2048
Поскольку тут фигурирует опция "-des3", то ключ будет зашифрован, поэтому потребуется ввести пароль.
В дальнейшем этот пароль потребуется вводить при использовании этого ключа, например, при подписывании сертификатов, так что пароль лучше сделать помудрёнее и не забыть.
2. Создаём самоподписанный сертификат УЦ:
openssl req \
-x509 \
-days 3650 \
-key huhmuh-CA.key \
-out huhmuh-CA.crt \
-subj "/emailAddress=huhmuh@example.com/C=RU/ST=Oblastnaya oblast/L=Default City/O=Home/OU=Living room/CN=example.com" \
-addext "subjectAltName = DNS:example.com"
Можно "-subj" и не указывать, тогда при создании сертификата потребуется ответить на кучку вопросов, кто вы и откуда. Ну, и, поскольку используется huhmuh-CA.key, программа спросит для него пароль.
В результате этих манипуляций у нас на руках окажутся два файла, huhmuh-CA.key и huhmuh-CA.crt - это и будет наш удостоверяющий центр.
Создание сертификата сервера
1. Создаём приватный ключ сервера:
openssl genrsa -out huhmuh-openvpn.key 2048
Этот ключ получается незашифрованным.
Наверно, это не очень хорошо, но дело в том, что сервер openvpn будет этот ключ активно использовать, и для того, чтобы ему рассказать пароль, потребуются дополнительные телодвижения.
Так что в данном случае победила лень.
2. Создаём запрос сертификата для сервера:
openssl req \
-new \
-key huhmuh-openvpn.key \
-out huhmuh-openvpn.csr \
-subj "/emailAddress=huhmuh@example.com/C=RU/ST=Oblastnaya oblast/L=Default City/O=Home/OU=Living room/CN=server"
Тут стоит обратить внимание на поле /CN=server.
Его значение отличается от соответствующего поля, использовавшегося при создании сертификата УЦ.
Если эти поля совпадают, то клиенты почему-то считают сертификат сервера самоподписанным, насколько я понял.
Поэтому лучше CommonName сертификатов УЦ и сервера сделать различными, во избежание недоразумений.
3. Подписываем сертификат в удостоверяющем центре:
Как выяснилось в процессе эксплуатации, некоторые клиенты openvpn, в частности, 2.3, требуют, чтобы сертификат содержал расширения x509v3, в которых было бы явно указано, для чего его можно использовать. Делается это при помощи следующих расширений: keyUsage и extendedKeyUsage. Можно было бы их указывать на этапе создания запроса сертификата, но почему-то на этапе подписания они теряются. Но есть положительный момент: при подписании можно добавить нужные расширения. И момент отрицательный: соответствующий параметр командной строки "-addext" срабатывает только в openssl начиная с версии 1.1.1а.
Поэтому создадим на УЦ два файла с расширениями для серверных и клиентских сертификатов.
Файл server-extensions.txt:
[default]
keyUsage = digitalSignature,keyAgreement
extendedKeyUsage = serverAuth
Файл client-extensions.txt:
[default]
keyUsage = digitalSignature,keyAgreement
extendedKeyUsage = clientAuth
И будем их использовать при подписании.
Сам процесс подписания прост: передаём на Луну файл *.csr, подписываем его в УЦ командой:
openssl x509 \
-req \
-days 3650 \
-in huhmuh-openvpn.csr \
-out huhmuh-openvpn.crt \
-CA huhmuh-CA.crt \
-CAkey huhmuh-CA.key \
-CAcreateserial \
-extfile server-extensions.txt
И возвращаем готовый сертификат huhmuh-openvpn.crt с Луны обратно на сервер, где будет жить OpenVPN.
4. Дальше потребуется сгенерировать ещё два файла, они нужны при настройке сервера. Эти файлы создаются так:
Создаём файл с параметрами для протокола Диффи-Хеллмана:
openssl dhparam -out dh.pem 2048
Создаём приватный ключ для tls-аутентификации:
openvpn --genkey --secret ta.key
Тут интересно, что используется не команда openssl, а сама программа openvpn. У этой самой openvpn оказалось вообще много всяких параметров командной строки, посмотреть их можно командой openvpn --help.
Программа живёт в каталоге /sbin или /usr/sbin, эти каталоги в $PATH простых смертных не прописаны, так что если вызывать её из-под непривилегированного пользователя, возможно потребуется указать полный путь: /sbin/openvpn.
Итак, по завершении этого этапа, в нашем распоряжении будут файлы:
huhmuh-CA.crt
huhmuh-openvpn.key
huhmuh-openvpn.crt
dh.pem
ta.key
Эти файлы потребуется сгрузить в каталог /etc/openvpn, туда же скопировать из /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz образец конфигурации, распаковать его командой
gunzip server.conf.gz
При этом получится файл server.conf, и этот файл уже можно править под свои нужды.
Файл server.conf богат на комментарии, между ними запрятаны настройки, которые должны быть примерно такими:
# тут можно указать, какой адрес использовать,
# но, поскольку мой сервер находится "в глубине" сети,
# то у него один адрес, поэтому я оставил эту настройку закомментированной
;local a.b.c.d
# порт по умолчанию 1194, но можно и сменить
port 32194
# протокол рекомендуют udp, но пусть уж так
proto tcp
dev tun
# наши ключи и сертификаты
ca huhmuh-CA.crt
cert huhmuh-openvpn.crt
key huhmuh-openvpn.key # This file should be kept secret
dh dh.pem
tls-auth ta.key 0
topology subnet
# задаём, какой диапазон адресов будет использоваться клиентскими подключениями
# первый адрес, 10.8.0.1, сервер openvpn заберёт себе
server 10.8.0.0 255.255.255.0
# логи и прочая полезная информация.
# в частности, в ipp.txt показываются сопоставления CN клиентских сертификатов и выделенные для них адреса
ifconfig-pool-persist /var/log/openvpn/ipp.txt
status /var/log/openvpn/openvpn-status.log
log-append /var/log/openvpn/openvpn.log
# насколько подробным будет лог
verb 6
# с этой опцией клиент будет вынужден весь свой трафик завернуть через openvpn
push "redirect-gateway def1 bypass-dhcp"
keepalive 10 120
# версия 2.3 знает лишь AES-256-CBC, с версии 2.4 лучше применять AES-256-GCM, он надёжнее, говорят
;cipher AES-256-CBC
cipher AES-256-GCM
comp-lzo
# чтобы сервер не работал под рутом - для пущей безопасности
user nobody
group nogroup
persist-key
persist-tun
С готовым файлом конфигурации можно попытаться, наконец, взлететь. Под убунтой это делается командой:
systemctl start openvpn@server
Вот это дополнение, @server, задаёт имя файла конфигурации, который будет использован, т.е., server.conf. Если же взлететь с первого раза не получилось, может выручить запуск сервера командой:
openvpn --config /etc/openvpn/server.conf
И медитация над логами из /var/log/openvpn/ в попытке разобраться, что же пошло не так...
Создание файла подключения клиента
Если сервер, наконец, удалось каким-то чудом запустить, пришло время для клиента.
Все настройки клиента - и конфигурация, и ключ, и сертификаты - удобно сливаются в единый файл *.ovpn.
1. Создаём приватный ключ клиента:
openssl genrsa -des3 -out huhmuh-alice.key 2048
Ключ шифруем (опция "-des3") - пусть клиент при установлении подключения вводит пароль.
2. Создаём запрос сертификата для клиента:
openssl req \
-new \
-key huhmuh-alice.key \
-out huhmuh-alice.csr \
-subj "/emailAddress=alice@example.com/C=RU/ST=Oblastnaya oblast/L=Default City/O=Home/OU=Kitchen/CN=alice"
3. Подписываем сертификат клиента в удостоверяющем центре:
openssl x509 \
-req \
-days 3650 \
-in huhmuh-alice.csr \
-out huhmuh-alice.crt \
-CA huhmuh-CA.crt \
-CAkey huhmuh-CA.key \
-CAcreateserial \
-extfile client-extensions.txt
В итоге для клиентского подключения мы приготовили файлы:
huhmuh-CA.crt
huhmuh-alice.key
huhmuh-alice.crt
Из этих файлов и файла /etc/openvpn/ta.key можно собрать файл подключения. Проще всего это сделать примерно таким скриптом:
#!/bin/sh
FNAME=huhmuh-alice.ovpn
echo "client
dev tun
proto tcp
remote example.com 32194
resolv-retry infinite
remote-cert-tls server
auth SHA1
cipher AES-256-GCM
tls-client
key-direction 1
persist-key
persist-tun
resolv-retry infinite
nobind
comp-lzo
verb 3" > $FNAME
echo "<ca>" >> $FNAME
cat ./huhmuh-CA.crt >> $FNAME
echo "</ca>" >> $FNAME
echo "<cert>" >> $FNAME
cat ./huhmuh-alice.crt >> $FNAME
echo "</cert>" >> $FNAME
echo "<key>" >> $FNAME
cat ./huhmuh-alice.key >> $FNAME
echo "</key>" >> $FNAME
echo "<tls-auth>" >> $FNAME
cat /etc/openvpn/ta.key >> $FNAME
echo "</tls-auth>" >> $FNAME
Тут надо помнить пару моментов. Во-первых, /etc/openvpn/ta.key - секретный (хотя как секретный, клиенту же передаётся?), поэтому доступен лишь под рутом. И, во-вторых, соответствующие настройки должны совпадать с настройками сервера. Например, если в конфиге сервера стоит "cipher AES-256-CBC", то и у клиента должно быть то же самое, "cipher AES-256-CBC". Про "remote example.com 32194", думаю, упоминать смысла нет - и так понятно, что это адрес сервера и открытый на нем порт.
Если всё прошло нормально, то в нашем распоряжении оказывается файл huhmuh-alice.ovpn, который можно отдавать клиенту, чтобы он с ним подключался к нашему серверу.
Клиентов для OpenVPN хватает, даже под андроидом есть некий OpenVPN Connect, так что тип операционной системы использование этого инструмента не лимитирует.
(NB: для WindowsXP, как оказалось, последняя версия клиента: 2.3.18. При её настройке из скопированного в C:\Program Files\OpenVPN\config файла .ovpn пришлось удалить строку set CLIENT_CERT 0. Также эта версия начала кочевряжиться при отсутствии Key Usage в расширениях серверного сертификата, о чем было упомянуто выше. Помогает либо добавление требуемого расширения, либо убирание строки remote-cert-tls server в файле *.ovpn)
Настройка netfilter на сервере
В случае успеха клиенты, подключенные к серверу, смогут добраться лишь до этого самого сервера.
Попытка попасть на какие-то другие хосты в локальной сети окажется неудачной.
Для того, чтобы клиенты получили возможность пройти дальше сервера, требуется использовать nat.
В принципе, можно сконфигурировать openvpn так, чтобы он позволил своим клиентам получать адреса из диапазона локальной сети.
Но, поскольку в вышеприведенной конфигурации клиенты получают адреса из диапазона 10.8.0.0/24, а остальная сеть живёт в диапазоне 192.168.0.0/24, то в данном случае придётся воспользоваться iptables.
Минимальная конфигурация, позволяющая это осуществить, выглядит так:
#!/bin/sh
# подключаем nf_conntrack и разрешаем проброс пакетов
modprobe nf_conntrack
sysctl net.ipv4.ip_forward=1
# очищаем таблицы nat и filter
iptables -F -t nat
iptables -F -t filter
iptables -X -t nat
iptables -X -t filter
# разрешаем работать исходящим и уже установленным подключениям
iptables -t filter -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -t filter -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -t filter -A OUTPUT -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
# разрешаем доступ к серверу по перечисленным портам (не один же openvpn у нас тут крутится)
iptables -t filter -A INPUT -p tcp -m multiport --dports 22,67,68,80,443,32194 -j ACCEPT
iptables -t filter -A INPUT -p udp -m multiport --dports 67,68 -j ACCEPT
# 22 -ssh, 67 - dhcp, 80,443 - https, 32194 - openvpn
# в трафик из подсети 10.8.0.0/24 подставляем адрес сервера в локальной сети, 192.168.0.2, в качестве источника
iptables -t filter -A FORWARD -s 10.8.0.0/24 -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -j SNAT --to-source 192.168.0.2
# остальные пакеты, не прошедшие правила, отбрасываем (кроме исходящих)
iptables -t filter -P INPUT DROP
iptables -t filter -P FORWARD DROP
iptables -t filter -P OUTPUT ACCEPT
Настройка клиента на ubuntu
Подключение к серверу с использованием имеющегося файла huhmuh-alice.ovpn выглядит так:
sudo openvpn --config ./huhmuh-alice.ovpn
При этом, если на сервере прописана опция redirect-gateway, весь трафик пойдёт через этот vpn-канал. Иногда это не очень удобно - например, мы сидим в большом инете и хотим подцепться к корпоративной сети, но при этом не желаем, чтобы развлекательные сайты заворачивались через служебную прокси. Это решается следующим образом.
Оказывается, в openvpn есть две внутренние константы, vpn_gateway и net_gateway. Они содержат адрес vpn-шлюза и шлюза локальной сети соответственно, и с их помощью можно задавать нужные маршруты. В файл *.ovpn добавляем строку:
route 192.168.0.0 255.255.255.0 vpn_gateway
Клиента запускаем командой:
sudo openvpn --pull-filter ignore redirect-gateway --config ./huhmuh-alice.ovpn
И весь трафик будет ходить по-старому, за исключением сети 192.168.0.0/24 - общение с ней пойдёт через vpn.
Литература
Установка и настройка OpenVPN-сервера в Debian
Конспект по установке OpenVPN
Установка и настройка сервера OpenVPN в Ubuntu 20.04
OpenSSL Quick Reference Guide
Create SSL certificate non-interactively
Генерирование сертификатов для OpenVPN с помощью Easy-RSA 3
x509v3_config
OpenVPN Certificate does not have key usage extension
OpenSSL CA keyUsage extension
IX509ExtensionEnhancedKeyUsage interface (certenroll.h)
Class ExtendedKeyUsage
Connecting to Access Server with Linux
Ignoring redirect-gateway