Мой блог - Разработка собственного модуля авторизации для OpenVPN

Разработка собственного модуля авторизации для OpenVPN

Как вы наверное знаете помимо стандартной авторизации по ключам и сертификатам вы можете дополнительно использовать парольную защиту как дополняющий механизм к модели сертификатов или полностью перейти исключительно на парольную авторизацию. Стоит отметить, что защищенность OpenVPN с авторизацией с использованием Login/Password будет гораздо выше чем использование механизмов PPTP например.

Фотография автора

Автор: Антон Черноусов
Опубликовано: 3 недели (последние правки: 0 минут назад) - 0 комментариев
Категории записи: Linux, Networking, OpenVPN, Python, Ubuntu, Системное администрирование


Если вы интересовались вопросами настройки авторизации в OpenVPN, то вы наверное обращали внимание, что все статьи описывающие авторизацию с использованием например PAM относятся к коммерческой реализации OpenVPN-сервера или в редких случаях рассказывается о использовании плагина для интеграции с механизмами OpenLDAP или Windows Active Directory.

Авторизация с использованием логин-пароль в OpenVPN

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

Для использования внешнего скрипта проверки авторизации, используется параметр конфигурации auth-user-pass-verify, но этот параметр честно говоря не очень хорошо (от слова совсем никак) не описан в документации и для того, чтобы понять как он работает требуется изучить документацию и исходные коды серверной части OpenVPN.

Для реализации режима проверки авторизации пользователя с использованием внешнего скрипта вам необходимо задать параметр конфигурации:

auth-user-pass-verify /etc/openvpn/chek_auth.py via-file

Соответственно /etc/openvpn/chek_auth.py это скрипт который будет выполнять проверку переданных данных авторизации, а via-file или via-env, это метод которым скрипту будут переданы данные о логине и пароле которые ввел пользователь (через переменные окружения или через временный файл).

Дополнительно, необходимо задать еще два параметра конфигурации:

username-as-common-name
script-security 3

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

Тестовая конфигурация сервера OpenVPN

Для тестирования механизма авторизации мы будем использовать следующую конфигурацию сервера:

port 1194
proto udp
dev tun
ca ca.crt
cert vpn-unlock-01.crt
key vpn-unlock-01.key
dh dh2048.pem
server 10.249.0.0 255.255.0.0
ifconfig-pool-persist ipp.txt
# External auth block
username-as-common-name
script-security 3
auth-user-pass-verify ./check_auth.py via-env
# End of external auth
keepalive 10 60
comp-lzo
persist-key
persist-tun
verb 3

Как вы наверное поняли, параметр script-security задает минимальный уровень безопасности, согласно документации существует 3 уровня безопасности вызываемых из конфигурации скриптов:

0 - Запрещено вызывать внешние скрипты и приложения.
1 - Вызываются стандартные скрипты конфигурации сетевой подсистемы ifconfig, ip, route, или netsh.
2 - Разрешены пользовательские скрипты.
3 - Разрешено передавать параметры авторизации через переменные окружения скрипту (потенциально небезопасно).

Параметр username-as-common-name подменяет механизм передачи CN-имени от сертификата на имя пользователя и сейчас я немного подробнее на этом заострю внимание. Как вы знаете можно задать для каждого клиента в отдельности параметры конфигурации которые описаны в ccd (client config dir) и вот именно в ccd хранятся конфигурационный файлы привязанные к имени сертификата клиента, а в случае использования одинаковых сертификатов, мы получаем одинаковые CN. Так вот как раз этот параметр и привязывает файлы ccd не к имени сертификата а к логину пользователя.

И наконец, самый главный параметр, это auth-user-pass-verify и как я уже говорил, мы передаем ему два доп параметра, первый это собственно скрипт который будет выполнять проверку авторизации, а второй параметр, это режим работы. Если скрипт возвращает код отличный от нуля, то это значит, что подключающийся клиент проверку не прошел.

Для тестирования работоспособности нашей конструкции создадим скрипт проверки авторизации который всегда будет сообщать, что клиент прошел проверку:

#!/usr/bin/python3
import sys

sys.exit(1)

Тестовая конфигурация клиента

Тестовый клиент довольно типовой и отличается от рассмотренных ранее, только параметром auth-user-pass:

client
proto udp
remote 127.0.0.1 1194
dev tun
resolv-retry infinite
nobind
persist-key
persist-tun
ns-cert-type server
script-security 2
comp-lzo
verb 3
auth-user-pass

Вы можете передать параметру auth-user-pass дополнительный суб-параметр "имя файла" где будет указаны логин и пароль пользователя, для того чтобы не вводить их каждый раз в интерактивном режиме, что актуально для серверных реализаций.

Тестирование передаваемых скрипту авторизации OpenVPN параметров

Стенд для тестирования мы подготовили и можем наконец, то проверить какие параметры передаются переменным окружения и как мы можем их использовать, для этого мы модифицируем скрипт проверки авторизации, который у нас сейчас работает в режиме "всегда говори да", для того чтобы он вывел все переменные окружения с которыми он был вызван:

#!/usr/bin/python3
import sys
import os

print ('Check script environment')
print(os.environ)

sys.exit(0)

Теперь запустим наше тестовую среду и проведем тестовую авторизацию с именем пользователя USER-USER-TEST и паролем Passwoed123.

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

Я честно говоря думал, что там будет передан только логин и пароль, но так даже лучше:

Check script environment
environ({'tls_serial_hex_0': '14', 'X509_1_CN': 'GITA-DEV-RU CA', 'local_port_1': '1194', 'route_gateway_1': '10.249.0.2',
'X509_1_emailAddress': 'anton@gita-dev.ru', 'tls_digest_1': '1c:7d:a4:23:32:c8:c7:93:ac:cc:01:d7:6f:53:01:39:31:76:fb:49',
'daemon_log_redirect': '0', 'X509_0_C': 'RU', 'remote_port_1': '1194', 'daemon_start_time': '1527480067', 'X509_1_OU': 'VPN-ROUTER',
'common_name': 'client-17', 'ifconfig_remote': '10.249.0.2', 'X509_1_ST': 'NSK', 'tun_mtu': '1500', 'untrusted_port': '56283',
'ifconfig_local': '10.249.0.1', 'route_vpn_gateway': '10.249.0.2', 'tls_serial_1': '16592554913958741180', 'X509_0_ST': 'NSK',
'X509_1_C': 'RU', 'dev': 'tun0', 'X509_0_CN': 'client-17', 'link_mtu': '1542', 'route_net_gateway': '10.1.1.254',
'daemon_pid': '22371', 'X509_1_O': 'GITA-DEV-RU', 'verb': '3', 'tls_id_1': 'C=RU, ST=NSK, L=Novosibirsk, O=GITA-DEV-RU,
OU=VPN-ROUTER, CN=GITA-DEV-RU CA, name=EasyRSA, emailAddress=anton@gita-dev.ru', 'X509_0_O': 'GITA-DEV-RU',
'tls_serial_0': '20', 'route_netmask_1': '255.255.0.0', 'redirect_gateway': '0', 'X509_1_L': 'Novosibirsk', 'X509_0_OU':
'VPN-ROUTER', 'dev_type': 'tun', 'username': 'USER-USER-TEST', 'password': 'Password123', 'tls_digest_0':
'9d:2e:cf:2c:e7:b5:bf:d0:fc:0b:a5:9f:8e:32:84:2a:cd:f8:45:c4', 'tls_id_0': 'C=RU, ST=NSK, L=Novosibirsk,
O=GITA-DEV-RU, OU=VPN-ROUTER, CN=client-17, name=EasyRSA, emailAddress=anton@gita-dev.ru', 'X509_0_L': 'Novosibirsk',
'X509_0_name': 'EasyRSA', 'X509_1_name': 'EasyRSA', 'daemon': '0', 'untrusted_ip': '127.0.0.1', 'proto_1': 'udp',
'X509_0_emailAddress': 'anton@gita-dev.ru', 'script_context': 'init', 'route_network_1': '10.249.0.0', 'tls_serial_hex_1':
'e6:44:98:c8:7a:ac:44:bc', 'config': './server.conf', 'script_type': 'user-pass-verify'})

Настройка проверки переданных логина и пароля

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

#!/usr/bin/python3
import sys
import os

if os.environ['username']=='USER1':
        if os.environ['password']=='PASSWORD1':
                sys.exit(0)
        else:
                sys.exit(1)
else:
        sys.exit(1)

Выше я привел пример простого скрипта который разрешает доступ для пользователя:

  • login: USER1
  • password: PASSWORD1

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

Пожалуйста, оцените мою статью (всего оценок 2, средняя оценка 4.00):

Комментарии к статье:

Пока комментариев нет

Оставьте комментарий:

обязательно

обязательно (не публикуется)

необязательно

обязательно

обязательно

Последние записи

Архив

2018

Категории

Ленты

RSS / Atom