Привет всем,

Думаю многие уже пробовали OpenVPN в своих приватных корпоративных сетях. Задача заметки на сегодня продемонстрировать, как можно прикрутить авторизацию в OpenVPN через OTP-токен в приложении Google Authenticator.

В качестве OTP-сервера будем использовать открытый сервис PrivacyIdea. Поэтому у вас уже должен быть предварительно настроенный инстанс с PrivacyIdea. Либо можете посмотреть мои старые заметки, относительно инсталяции сервиса.

Для интеграции взаимодействия OpenVPN c PrivacyIdea, на стороне сервера OpenVPN собирается radius-плагин. Через этот плагин происходит взаимодействие с сервером Radius, который в свою очередь валидирует пользователей через http-запрос в PrivacyIdea.

Ниже представлена схемка взаимосвязи сервисов: ovpn-2fa-shemes.png

Установка OpenVPN

OpenVPN сервер будет разворачивать на базе Almalinux8, поэтому ставим пакеты - openvpn и easy-rsa.
Пакет easy-rsa нужен нам для создания цепочек сертификатов и упрощения работы выпуска сертификатов.

[root@ovpn ~]# yum install openvpn easy-rsa

В каталоге /usr/share/easy-rsa/3 будет содержаться бинарный файл с утилитой и ее конфиг.

Создадим симлинк на бинарь в каталоге для исполняемых файлов:

[root@ovpn ~]# ln -s /usr/share/easy-rsa/3/easyrsa /usr/local/sbin/easyrsa

Под хранение объектов нашей маленькой PKI инфраструктуры создадим отдельный каталог:

[root@ovpn ~]# mkdir /etc/openvpn/keys

Проваливаемся внутрь этого каталога:

[root@ovpn ~]# cd /etc/openvpn/keys/

Проинициализируем новый PKI инстанс:

[root@ovpn keys]# easyrsa init-pki

init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /etc/openvpn/keys/pki

Теперь нужно сгенерить сертификат нашего рутового CA. Что бы упростить процесс создания цепочки ключей в переменные окружение импортнем переменные с объектами сертификата:

[root@ovpn keys]# vi .vars
---
export KEY_COUNTRY="RU"
export KEY_PROVINCE="MOSCOW"
export KEY_CITY="MOSCOW"
export KEY_ORG="NIXHUB"
export KEY_EMAIL="ADMIN@NIXHUB.RU"
export KEY_CN="OVPN.NIXHUB.RU"
export KEY_OU="ADMINS"
export KEY_NAME="ovpn.nixhub.ru"
export KEY_ALTNAMES="openvpn.nixhub.ru"

И импортируем переменные в наше окружение:

[root@ovpn keys]# . ./.vars

Далее генерим рутой сертификат:

[root@ovpn keys]# easyrsa build-ca

После запуска утилита запросит password фразу (Passphrase), вводим ее. В запрос ввода Common Name я указываю имя vpn-сервера.

Теперь сгенерим ключ Диффи-Хеллмана:

[root@ovpn keys]# easyrsa gen-dh

Для выпуска сертификата vpn-сервера сначала нужно сгенерить для него запрос на выпуск сертификата:

[root@ovpn keys]# easyrsa gen-req ovpn-server nopass

В запросе ввода Common Name опционально указываем имя нашего vpn-сервера.

Подписываем запрос и выпускаем сертификат:

[root@ovpn keys]# easyrsa sign-req server ovpn-server

В процессе выполнения утилита запросит подтвердить выпуск сертификата:

Confirm request details: yes

Далее будет представлено окно для ввода секретной фразы ключа, которую мы указывали при создании корневого сертификата. Указываем ее и жмем enter.

Создадим дополнительный сертификат для усиления безопасности, который будет использоваться для TLS Control channel.:

[root@ovpn keys]# openvpn --genkey --secret /etc/openvpn/server/tc.key

По итогу мы получаем базовый состав объектов, необходимых для работы openvpn-сервера:

  • pki/ca.crt - Публичный ключ нашего CA, им можно делиться с нашими клиентами;
  • pki/private/ca.key - Закрытый ключ нашего CA, ни с кем не делимся им;
  • pki/dh.pem - ключ Диффи Хелмана;
  • pki/issued/ovpn-server.crt - открытый сертификат для нашего vpn-сервера;
  • pki/private/ovpn-server.key - закрый ключ от сертфиката vpn-сервера.

Настройка сервера почти завершена, остается только написать конфиг vpn-сервера:

[root@ovpn ~]# vim /etc/openvpn/server/server.conf
---
port 59182
proto udp
dev tun

ca /etc/openvpn/keys/pki/ca.crt
cert /etc/openvpn/keys/pki/issued/ovpn-server.crt
key /etc/openvpn/keys/pki/private/ovpn-server.key
dh /etc/openvpn/keys/pki/dh.pem

auth SHA256
cipher AES-256-CBC
tls-version-min 1.2
tls-crypt /etc/openvpn/server/tc.key

server 10.8.1.0 255.255.255.0
route 10.8.2.0 255.255.255.0

client-to-client

keepalive 10 120
comp-lzo
explicit-exit-notify 1
persist-key
persist-tun
status /var/log/openvpn/openvpn-status.log
log /var/log/openvpn/openvpn.log
user nobody
group nobody
verb 3

Конфигурационный файл содержит директивы:

  • port - в значение указывается порт, на котором слушает сервер;
  • proto - используемый протокол - tcp/udp;
  • ca/cert/key/dh - в значении этих директив указываются соответствующие сертификаты;
  • auth - тип алгоритма шифрования;
  • cipher - тип алгоритма шифрования данных, передаваемых через vpn;
  • tls-version-min - указывается минимальная используемая версия;
  • tls-crypt - ключ шифрования TLS control channel;
  • server - тут в формате указывается подсеть туннеля;
  • route - тут в аналогичном формате - указывается маршрут к подсети, к которой будем ходить из под vpn.
  • client-to-client - эта опция разрешает взаимодействие клиентов, друг с другом;

Создаем каталог под хранение логов:

[root@ovpn]# mkdir /var/log/openvpn

И открываем порт на фаерволе:

[root@ovpn keys]# firewall-cmd --add-port=59182/udp --permanent
[root@ovpn keys]# firewall-cmd --reload

Ну и запускаем openvpn-сервер:

[root@ovpn]# systemctl enable --now openvpn-server@server

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

С одного не мало известного сисадминского сайта я стянул скриптик, который автоматом генерит сертификаты и клиентский конфиг и немного изменил его подправив пути к файлам и папкам.

В виме открываем новым файл и вставляем в него содержимое:

#!/bin/bash

proto="udp"
port="59182"
server="ovpn.nixhub.ru"
confdir="/etc/openvpn/client"

echo -n "Enter user name: "
read user

echo "Protect the private key with a password?"
echo " 1) No, passwordless client"
echo " 2) Yes, use a password for the client"

until [[ $pass =~ ^[1-2]$ ]]; do
     read -rp "Select an option [1-2]: " -e pass
done

clientexist=$(tail -n +2 /etc/openvpn/keys/pki/index.txt | grep -c -E "/CN=$user\$")
     if [[ $clientexist == '1' ]]; then
       echo ""
       echo "The specified client name was already found in easy-rsa"
     exit
     else
       cd /etc/openvpn/keys/ || return
       case $pass in
            1)
                easyrsa build-client-full "$user" nopass
                ;;
            2)
                easyrsa build-client-full "$user"
                ;;
            esac
            echo "Client $user added."
     fi

touch /etc/openvpn/ccd/$user
mkdir -p $confdir

echo "dev tun
proto $proto
remote $server $port
client
resolv-retry infinite
remote-cert-tls server
auth SHA256
cipher AES-256-CBC
persist-key
persist-tun
resolv-retry infinite
nobind
comp-lzo
verb 3" > $confdir/$user.ovpn
{ echo "<ca>"
  cat "/etc/openvpn/keys/pki/ca.crt"
  echo "</ca>"

  echo "<cert>"
  awk '/BEGIN/,/END/' "/etc/openvpn/keys/pki/issued/$user.crt"
  echo "</cert>"

  echo "<key>"
  cat "/etc/openvpn/keys/pki/private/$user.key"
  echo "</key>"

  echo "<tls-crypt>"
  cat "/etc/openvpn/server/tc.key"
  echo "</tls-crypt>"
} >> $confdir/$user.ovpn

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

Сохраняемся и выходим.

Делаем скрипт исполняемым:

[root@ovpn]# chmod +x /etc/openvpn/add_vpn_user.sh

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

[root@ovpn]# mkdir /etc/openvpn/client/

Ну и запускаем скриптец:

[root@ovpn openvpn]# ./add_vpn_user.sh

После запуска, по условию этого скриптика у нас попросят ввести имя пользователя, и выбрать действие которое добавляет секретный ключ. Ну и последним, нужно будет ввести phrase от рутового CA.

Готовая конфига будет лежать в каталоге - /etc/openvpn/client. Берем ее, и переносим на устройство нашего клиента. Затем пробуем подключится.

Настройка Radius-плагина

К сожалению, готового билда я не нашел. Единственный выход это самостоятельная сборка. Для сборки требуется предварительно поставить пакет libgcrypt/libgcript-devel:

[root@ovpn openvpn]# yum install libgcrypt libgcrypt-devel gcc-c++ make

Затем с гитхаба качаем исходники:

[root@ovpn openvpn]# cd /tmp
[root@ovpn tmp]# wget https://github.com/ValdikSS/openvpn-radiusplugin/archive/refs/tags/v2.2.tar.gz

Распаковываем архив:

[root@ovpn tmp]# tar -zxvf v2.2.tar.gz

Переходим в каталог с исходниками и запускаем сборку:

[root@ovpn tmp]# cd openvpn-radiusplugin-2.2
[root@ovpn openvpn-radiusplugin-2.2]# make

Плагин собирается буквально за 3 минуты. Далее готовый исходник либы и конфигу плагина, закидываем в каталог - /etc/openvpn/:

[root@ovpn openvpn-radiusplugin-2.2]# cp radiusplugin.so /etc/openvpn/
[root@ovpn openvpn-radiusplugin-2.2]# cp radiusplugin.cnf /etc/openvpn/

Теперь нужно перенастроить работу openvpn–сервера на использование плагина. Открываем конфиг и добавляем:

[root@ovpn openvpn-radiusplugin-2.2]# vim /etc/openvpn/server/server.conf
---
plugin /etc/openvpn/radiusplugin.so /etc/openvpn/radiusplugin.cnf

Настраиваем конфиг плагина:

[root@ovpn openvpn-radiusplugin-2.2]# vim /etc/openvpn/radiusplugin.cnf
---
...
NAS-IP-Address=10.8.5.10 
...
OpenVPNConfig=/etc/openvpn/server/server.conf
...
nonfatalaccounting=true

server
{
        acctport=1813
        authport=1812
        name=10.8.5.45
        retry=1
        wait=5
        sharedsecret=PASSWORD
}

В этом конфиге важные для нас параметры:

  • NAS-IP-Address - в значении указывается ip адрес радиус-клиента;
  • OpenVPNConfig - тут прописывается путь к конфигу openvpn-сервера;
  • nonfatalaccounting - значение true, включает игнорирование ошибок при radius accounting. В нашем случаи accounting не нужен;
  • server - в этом блоке описываются параметры подключения к радиус-серверу;
    • acctport - тут указывается порт, для accounting событий (но в нашем случаи можно упустить);
    • authport - тут указывается порт сервера для авторизации
    • name - ip адрес сервера
    • wait/retry - опции указывающие количество повторных запросов на radius сервер. И время ожидания;
    • sharedsecret - пароль для подключения клиента.

Сохраняемся и перезапускаем openvpn-сервер:

[root@ovpn openvpn-radiusplugin-2.2]# systemctl restart openvpn-server@server

На данном этапе идем на радиус сервер, который крутиться на том же серваке что и сервис PrivacyIdea.

На сервере редактируем файл с клиентами:

[root@mfa01 ~]# vim /etc/raddb/clients.conf
---
client OpenVPN1 {
        ipaddr  = 10.8.5.10 # OpenVPN Server tablets
        netmask = 32
        secret  = 'PASSWORD' #shared secret
}

И перезапускаем radius-сервер:

[root@mfa01 ~]# systemctl restart radiusd

На этом все, теперь остается только в конфигу клиента добавить опцию:

auth-user-pass

После добавления этой опции, при подключении у пользователя будет отображаться окно с вводом otp-кода. Также не забываем в скриптец add_vpn_user.sh тоже добавить эту директиру.

Тестируем связку

Подключаемся в WebUI PrivacyIdea и создаем новый токен для тестового пользователя: ovpn-2fa-test1.jpg

Полученный qr-code сканируем в приложении google-authenticator.

Далее создаем новый клиентский конфиг для тестового пользователя. Напомню конфиг генерим через ранее предоставленный мной скриптец:

[root@ovpn ~]# cd /etc/openvpn/
[root@ovpn openvpn]# ./add_vpn_user.sh
Enter user name: testtest

Полученный конфиг переносим на клиентское устройство, затем добавляем новый профиль в openvpn-клиенте: ovpn-2fa-test2.jpg

В созданном профиле указываем логин тестового пользователя и жмем на кнопку подключиться: ovpn-2fa-test3.jpg

В окне ввода пароля вводим пинкод+код из приложения Google Authenticator: ovpn-2fa-test4.jpg

После ввода пина, мы подключаемся: ovpn-2fa-test5.jpg

В аудит логах на стороне PrivacyIdea, будут события об успешности авторизации пользователя: ovpn-2fa-test6.jpg

Если мы удаляем токен пользователя или блокируем, то доступ к vpn у пользователя закрывается. Что полезно..