OpenVPN, как много в этом слове. Мультиплатформенный, гибко настраиваемый, бесплатный VPN сервер с открытым исходным кодом, являющийся фактически стандартом "defacto" для организации доступа к внутренним корпоративным сетям. Большинство администраторов используют его с настройками по умолчанию или с типовыми конфигурациями широко описанными в разных HOW-TO. Но так ли прост OpenVPN, как он кажется на первый взгляд? В данной статье мы рассмотрим скрытые от глаз внутренние механизмы OpenVPN, которые кардинально меняют представление о его возможностях.


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


Большинство конфигураций для связи клиентов с сервером, а также между серверами, предполагает использование связки приватных или приватно/публичных ключей для обеспечения безопасности внутреннего трафика. Для корпоративных сетей в режиме MultiPoint-To-SinglePoint обычно используется свой центр сертификации PKI, который легко строится либо при помощи easy-rsa, либо на основе XCA. Для межсервенной коммуникации типа Point-to-point, в основном используется конфигурация с общим ключом. Вспомним основные, всем известные механизмы и возможности.


Основные механизмы и возможности


  • Сертификатная аутентификация


    Про неё написано огромное количество документации. Суть проста. Создаётся свой центр сертификации, который выпускает пользовательские сертификаты. С помощью центра сертификации обеспечивается контроль за подключением пользователей к OpenVPN серверу. При окончании времени действия сертификата или его отзыве, доступ пользователя блокируется. Приватные ключи с установленым на них паролем, выпускаемые совместно с сертификатом обеспечивают безопасность от несанкционированного подключения к внутренним ресурсам.


  • Приватные point-to-point ключи


    С точки зрения подключения к ресурсам компании только одного пользователя/сервера, используется схема с приватными ключами. На одном из хостов генерируется ключ, который является общим для сервера и клиента.



Во всех случаях подключения для безопасности "рукопожатия" handshake между клиентом и сервером используется протокол Diffie-Hellmann.


  • Внешняя аутентификация пользователей


    Для упрощения контроля за подключением пользователей, вместо схемы со своим PKI, можно использовать схему с внешней аутентификацией пользователей по логину/паролю. Данная схема удобна для аутентификации пользователей скажем по доменному логину/паролю. Для подключения к серверу в конфигурационный файл клиента добавляется сертификат сервера и ключ подписи передаваемых пакетов HARDENING OPENVPN SECURITY.
    пример клиентского конфига


    dev tun
    proto udp
    # Внешний IP сервера OpenVPN
    remote 172.16.111.166
    # Port сервера
    port 1200
    client
    resolv-retry infinite
    tls-client
    key-direction 1
    auth SHA1
    cipher BF-CBC
    #comp-lzo
    persist-key
    persist-tun
    #
    auth-user-pass c:/temp/pass.txt
    #
    # just create a file with name pass.txt
    # and put to it two lines
    # -------------
    #username
    #password
    # -------------
    #auth-user-pass
    verb 3
    <ca>
    -----BEGIN CERTIFICATE-----
    MIIE5jCCA86gAwIBAgIJAOt3kFH7PxA0MA0GCSqGSIb3DQEBCwUAMIGjMQswCQYD
    ....
    -----END CERTIFICATE-----
    </ca>
    <tls-auth>
    -----BEGIN OpenVPN Static key V1-----
    83ddd29fa82212f3059d85a41490134c
    ....
    a4f2c7df3a22364a49093bca102dedeb
    -----END OpenVPN Static key V1-----
    </tls-auth>

    Часть серверного конфига для аутентификации клиентов через файл
    Using alternative authentication methods


    verify-client-cert none
    #client-cert-not-required
    username-as-common-name
    tls-server
    tls-auth /usr/local/etc/openvpn/ssl/tlsauth.key
    key-direction 0
    tls-timeout 120
    auth SHA1
    cipher BF-CBC
    auth-user-pass-verify /usr/local/etc/openvpn/auth/auth-static-file.pl via-file

    Данная схема удобна, но очень небезопасна.


  • PAM


    Для повышения безопасности можно использовать подключаемые модули обеспечивающие проверку логина/пароля во внешних системах. Самым распространённым методом является системный PAM(Pluggable Authentication Modules).
    В конфигурационный файл OpenVPN необходимо добавить строку


    plugin /usr/share/openvpn/plugin/lib/openvpn-auth-pam.so login

  • Маршрутизация


    Т.к. основной задачей сервера является обеспечение доступа удалённых пользователей/серверов к внутренним ресурсам, сервер позволяет определять статическую маршрутизацию от клиентов к серверу и от сервера к клиентам. С точки зрения доступа клиентов к внутренним ресурсам, сервер при помощи протокола DHCP и директив "route" или "push route" позволяет передать клиенту маршруты внутренних сетей. Для оповещения самого сервера об удалённых сетях на стороне клиента используется "client config dir" (ccd), механизм позволяющий описать при помощи директивы "iroute" список внутренних сетей клиента, которые должны попасть в таблицу маршрутизации сервера для транзита в них трафика.



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


Дополнительные возможности OpenVPN


Рассмотрим дополнительные возможности OpenVPN, о которых может кто-то и слышал, но в реальности не видел или не использовал.


  • Безопасность сетей/Packet Filtering


    Т.к. OpenVPN маршрутизирует трафик, у него есть два штатных взаимоисключающих друг друга режима работы. Первый режим это маршрутизация внутри OpenVPN сервера, второй режим это межинтерфейсная ядерная маршрутизация. В первом случае за коммутацию и фильтрацию пакетов между клиентами/сетями отвечает сам OpenVPN, во втором любой поддерживаемый на хосте системный пакетный фильтр(pf, iptables, etc).
    Мало кто знает, что OpenVPN имеет встроенный пакетный фильтр, который позволяет разрешить или изолировать соединения между пользователями и сетями.
    Да, да. Вы правильно прочитали. OpenVPN имеет свой собственный встроенный пакетный фильтр. Возможность фильтрации трафика была реализована еще в далёком 2010 году.
    Пакетный фильтр OpenVPN управляется либо через management-interface, либо через подключаемые к OpenVPN плагины.
    Управление правилами трафика происходит через файл. Формат файла прост.


    [CLIENTS DROP|ACCEPT]
    {+|-}common_name1
    {+|-}common_name2
    . . .
    [SUBNETS DROP|ACCEPT]
    {+|-}subnet1
    {+|-}subnet2
    . . .
    [END]

    Директивы блока (ACCEPT/DENY) задают действие по умолчанию для всех клиентов не указанных внутри блока.
    Например файл для клиента user2


    [CLIENTS DROP]
    +user1
    [SUBNETS DROP]
    [END]

    заблокирует трафик ко всем пользователям и сетям, но разрешит трафик в сторону клиента user1. Если у user1 не будет явным образом описано разрешение передачи трафика в сторону user2, то трафик будет ходить только в одну сторону user2->user1.



Или другой пример.
Отключить всё кроме доступа между пользователями и DNS сервером находящимся в локальной сети и тестовым контуром в сети 192.168.0.0/24


[CLIENTS DROP]
+user1
+user2
[SUBNETS DROP]
+10.150.0.1
+10.150.1.1
+192.168.0.0/24
[END]

Механизм фильтрации активируется через конфигурационный файл, либо при подключении плагина "выставившего" флаг "OPENVPN_PLUGIN_ENABLE_PF".
Данную возможность мы обсудим позже.
Вторым режимом фильтрации трафика является встроенный в систему пакетный фильтр. Для его активации в конфиге не должно быть директивы "client-to-client". С точки зрения автоматизации включения/выключения необходимых правил при подключении/отключении клиентов удобнее всего использовать отдельные вставки в список правил, реализуемые либо через CHAINS в Iptables(Linux), либо в Anchors в PF(FreeBSD). Активация/деактивация правил обычно осуществляется через директивы client-connect/client-disconnect в конфигурационном файле сервера, вызывающие соответствующие скрипты при подключении/отключении пользователя.


  • Расширенная PAM аутентификация


    Под расширенной PAM аутентификацией подразумевается изменение логики работы проверки логина и пароля пользователя. Достигается это либо установкой соответствующих плагинов для OpenVPN, обеспечивающих чтение и проверку данных во внешних источниках, либо подключением в систему библиотек позволяющих скриптовать любую логику. Одной из такой библиотек является pam_python, которая помогает скриптовать любую логику проверки логина/пароля через Python скрипты.
    В случае её использования строка проверки пользователя меняется следующим образом.


    plugin openvpn-plugin-auth-pam.so pam_python login USERNAME password PASSWORD domain mydomain.com

    Так как "под капотом" PAM находятся алгоритмы диалогов системы с пользователем или внешними библиотеками, то этими диалогами можно управлять. Например подключить OTP токены в систему. Библиотека LinOTP взята просто для примера, т.к. свою самописную библиотеку написанную во время тестирования я где-то потерял ?\(?)/?
    Также примеры легко гуглятся по слову "pam_python".
    Основной проблемой при работе с внешними PAM модулями является невозможность получить сессионное окружение OpenVPN внутри вызываемого Python или любого другого скрипта, вызываемого через системный pam. Т.е. скрипт обеспечивает только те функции по проверке логина/пароля, которые на него возложены.


  • "Отложенная" аутентификация


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


  • Плагины OpenVPN


    Это отдельная параллельная вселенная, про которую может быть и знают, но из-за некоторой запутанности не умеют или боятся использовать. Да действительно, написание функционального плагина для OpenVPN требует программирования на C со всеми вытекающими. Примеры простых плагинов включены в исходное дерево OpenVPN или например есть плагин для демонстрации вызова методов из OpenVPN.



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


#define OPENVPN_PLUGIN_FUNC_SUCCESS  0
#define OPENVPN_PLUGIN_FUNC_ERROR    1
#define OPENVPN_PLUGIN_FUNC_DEFERRED 2

Остановимся поподробнее на каждой группе. Логику работы мы будем рассматривать на основе парольной аутентификации пользователя.
При старте сервера, после чтения конфигурационного файла, сервер вызывает функции OPENVPN_PLUGIN_UP и OPENVPN_PLUGIN_ROUTE_UP. В переменном окружении вызываемых функций передаются основные параметры запущенного сервера.


OPENVPN_PLUGIN_UP
{
   "route_netmask_1":"255.255.0.0",
   "daemon_start_time":"1545994898",
   "ifconfig_remote":"10.150.0.2",
   "local_1":"172.16.100.139",
   "script_context":"init",
   "config":"/usr/local/etc/openvpn/server150.conf",
   "link_mtu":"1622",
   "ifconfig_local":"10.150.0.1",
   "tun_mtu":"1500",
   "verb":"2",
   "daemon_pid":"626",
   "route_vpn_gateway":"10.150.0.2",
   "proto_1":"udp",
   "daemon_log_redirect":"1",
   "daemon":"1",
   "route_net_gateway":"172.16.100.1",
   "dev_type":"tun",
   "route_gateway_1":"10.150.0.2",
   "remote_port_1":"1200",
   "dev":"tun150",
   "pluginid":"0",
   "local_port_1":"1200",
   "route_network_1":"10.150.0.0"
}

OPENVPN_PLUGIN_ROUTE_UP
{
   "route_netmask_1":"255.255.0.0",
   "daemon_start_time":"1545994898",
   "redirect_gateway":"0",
   "ifconfig_remote":"10.150.0.2",
   "local_1":"172.16.100.139",
   "script_context":"init",
   "config":"/usr/local/etc/openvpn/server150.conf",
   "link_mtu":"1622",
   "ifconfig_local":"10.150.0.1",
   "tun_mtu":"1500",
   "verb":"2",
   "daemon_pid":"626",
   "route_vpn_gateway":"10.150.0.2",
   "proto_1":"udp",
   "daemon_log_redirect":"1",
   "daemon":"1",
   "route_net_gateway":"172.16.100.1",
   "dev_type":"tun",
   "route_gateway_1":"10.150.0.2",
   "remote_port_1":"1200",
   "dev":"tun150",
   "pluginid":"2",
   "local_port_1":"1200",
   "route_network_1":"10.150.0.0"
}

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


OPENVPN_PLUGIN_ENABLE_PF
{
   "route_netmask_1":"255.255.0.0",
   "daemon_start_time":"1545994898",
   "redirect_gateway":"0",
   "ifconfig_remote":"10.150.0.2",
   "local_1":"172.16.100.139",
   "script_context":"init",
   "config":"/usr/local/etc/openvpn/server150.conf",
   "link_mtu":"1622",
   "pf_file":"/tmp/openvpn_pf_b7a18ca8fac838679ca87ada6b8a356.tmp",
   "ifconfig_local":"10.150.0.1",
   "tun_mtu":"1500",
   "verb":"2",
   "daemon_pid":"626",
   "route_vpn_gateway":"10.150.0.2",
   "proto_1":"udp",
   "route_net_gateway":"172.16.100.1",
   "daemon":"1",
   "daemon_log_redirect":"1",
   "dev_type":"tun",
   "route_gateway_1":"10.150.0.2",
   "remote_port_1":"1200",
   "dev":"tun150",
   "pluginid":"11",
   "local_port_1":"1200",
   "route_network_1":"10.150.0.0"
}

Как видно из дампа, появилась переменная pf_file. В данном файле должны находиться правила внутреннего пакетного фильтра для текущей обрабатываемой сессии.
Далее проверяется логин и пароль пользователя в функции OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY


OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
{
   "route_netmask_1":"255.255.0.0",
   "route_gateway_1":"10.150.0.2",
   "IV_NCP":"2",
   "IV_COMP_STUB":"1",
   "daemon_start_time":"1545994898",
   "IV_LZ4":"1",
   "redirect_gateway":"0",
   "ifconfig_remote":"10.150.0.2",
   "untrusted_port":"1200",
   "IV_LZ4v2":"1",
   "local_1":"172.16.100.139",
   "script_context":"init",
   "untrusted_ip":"172.16.111.168",
   "config":"/usr/local/etc/openvpn/server150.conf",
   "username":"fa56bf61-90da-11e8-bf33-005056a12a82-1234568",
   "link_mtu":"1622",
   "pf_file":"/tmp/openvpn_pf_b7a18ca8fac838679ca87ada6b8a356.tmp",
   "ifconfig_local":"10.150.0.1",
   "tun_mtu":"1500",
   "auth_control_file":"/tmp/openvpn_acf_a3d0650a43b88ca1b5f305ce2c8f682.tmp",
   "daemon":"1",
   "IV_COMP_STUBv2":"1",
   "verb":"2",
   "IV_PLAT":"win",
   "daemon_pid":"626",
   "password":"12312312312312",
   "route_vpn_gateway":"10.150.0.2",
   "proto_1":"udp",
   "route_net_gateway":"172.16.100.1",
   "IV_PROTO":"2",
   "daemon_log_redirect":"1",
   "dev_type":"tun",
   "IV_VER":"2.4.3",
   "IV_LZO":"1",
   "remote_port_1":"1200",
   "dev":"tun150",
   "pluginid":"5",
   "local_port_1":"1200",
   "IV_TCPNL":"1",
   "route_network_1":"10.150.0.0"
}

Это единственное место где пароль в переменном окружении присутствует в открытом виде.
Результатом работы данной функции должны быть три варианта ответа.


#define OPENVPN_PLUGIN_FUNC_SUCCESS  0
#define OPENVPN_PLUGIN_FUNC_ERROR    1
#define OPENVPN_PLUGIN_FUNC_DEFERRED 2

Если сервер получает ответ OPENVPN_PLUGIN_FUNC_DEFERRED, то в работу вступает механизм "отложенной" аутентификации. Как мы видим, в переменном окружении появилась переменная "auth_control_file", содержимое данной переменной содержит имя файла, в котором будет ожидаться ответ от системы аутентификации. Ответом является помещённый в указанный файл символ 0(для разрешения доступа), 1(для запрета доступа). Параметр сервера "hand-window" определяет таймаут в секундах, в течении которого сервер будет ожидать ответ. Во время ожидания трафик от других клиентов не прерывается.


Так как мы работаем с парольной аутентификацией, то функции проверки сертификатов OPENVPN_PLUGIN_TLS_VERIFY не вызывается. Вместо этого сразу вызывается OPENVPN_PLUGIN_TLS_FINAL, подтверждающий установление сессии.


OPENVPN_PLUGIN_TLS_FINAL
{
   "route_netmask_1":"255.255.0.0",
   "route_gateway_1":"10.150.0.2",
   "IV_NCP":"2",
   "IV_COMP_STUB":"1",
   "daemon_start_time":"1545994898",
   "IV_LZ4":"1",
   "redirect_gateway":"0",
   "ifconfig_remote":"10.150.0.2",
   "untrusted_port":"1200",
   "IV_LZ4v2":"1",
   "local_1":"172.16.100.139",
   "script_context":"init",
   "untrusted_ip":"172.16.111.168",
   "config":"/usr/local/etc/openvpn/server150.conf",
   "username":"fa56bf61-90da-11e8-bf33-005056a12a82-1234568",
   "link_mtu":"1622",
   "pf_file":"/tmp/openvpn_pf_b7a18ca8fac838679ca87ada6b8a356.tmp",
   "ifconfig_local":"10.150.0.1",
   "tun_mtu":"1500",
   "auth_control_file":"/tmp/openvpn_acf_a3d0650a43b88ca1b5f305ce2c8f682.tmp",
   "daemon":"1",
   "IV_COMP_STUBv2":"1",
   "verb":"2",
   "IV_PLAT":"win",
   "daemon_pid":"626",
   "route_vpn_gateway":"10.150.0.2",
   "proto_1":"udp",
   "route_net_gateway":"172.16.100.1",
   "IV_PROTO":"2",
   "daemon_log_redirect":"1",
   "dev_type":"tun",
   "IV_VER":"2.4.3",
   "IV_LZO":"1",
   "remote_port_1":"1200",
   "dev":"tun150",
   "pluginid":"10",
   "local_port_1":"1200",
   "IV_TCPNL":"1",
   "route_network_1":"10.150.0.0"
}

Далее срабатывает вызов OPENVPN_PLUGIN_IPCHANGE, вызываемый перед сменой ip адреса клиента.


OPENVPN_PLUGIN_IPCHANGE
{
   "route_netmask_1":"255.255.0.0",
   "route_gateway_1":"10.150.0.2",
   "trusted_ip":"172.16.111.168",
   "link_mtu":"1622",
   "IV_COMP_STUB":"1",
   "daemon_start_time":"1547319280",
   "IV_LZ4":"1",
   "redirect_gateway":"0",
   "common_name":"fa56bf61-90da-11e8-bf33-005056a12a82-1234568",
   "ifconfig_remote":"10.150.0.2",
   "IV_NCP":"2",
   "untrusted_port":"1200",
   "IV_LZ4v2":"1",
   "local_1":"172.16.100.139",
   "script_context":"init",
   "untrusted_ip":"172.16.111.168",
   "config":"/usr/local/etc/openvpn/server150.conf",
   "username":"fa56bf61-90da-11e8-bf33-005056a12a82-1234568",
   "trusted_port":"1200",
   "pf_file":"/tmp/openvpn_pf_4fcad505693b33f97c4fe105df8681cb.tmp",
   "ifconfig_local":"10.150.0.1",
   "tun_mtu":"1500",
   "auth_control_file":"/tmp/openvpn_acf_321bb12075dc0e1b5440d227220bac5d.tmp",
   "daemon":"1",
   "IV_COMP_STUBv2":"1",
   "verb":"3",
   "IV_PLAT":"win",
   "daemon_pid":"52435",
   "route_vpn_gateway":"10.150.0.2",
   "proto_1":"udp",
   "route_net_gateway":"172.16.100.1",
   "IV_PROTO":"2",
   "daemon_log_redirect":"1",
   "dev_type":"tun",
   "IV_VER":"2.4.3",
   "IV_LZO":"1",
   "remote_port_1":"1200",
   "dev":"tun150",
   "pluginid":"3",
   "local_port_1":"1200",
   "IV_TCPNL":"1",
   "route_network_1":"10.150.0.0"
}

Функция OPENVPN_PLUGIN_CLIENT_CONNECT_V2, вызывается при установке IP адреса внутренним DHCP сервером.


OPENVPN_PLUGIN_CLIENT_CONNECT_V2
{
   "route_netmask_1":"255.255.0.0",
   "route_gateway_1":"10.150.0.2",
   "trusted_ip":"172.16.111.168",
   "link_mtu":"1622",
   "IV_COMP_STUB":"1",
   "daemon_start_time":"1547319280",
   "IV_LZ4":"1",
   "dev":"tun150",
   "common_name":"fa56bf61-90da-11e8-bf33-005056a12a82-1234568",
   "time_ascii":"Sat Jan 12 18:54:48 2019",
   "ifconfig_remote":"10.150.0.2",
   "IV_NCP":"2",
   "untrusted_port":"1200",
   "IV_LZ4v2":"1",
   "local_1":"172.16.100.139",
   "script_context":"init",
   "untrusted_ip":"172.16.111.168",
   "config":"/usr/local/etc/openvpn/server150.conf",
   "username":"fa56bf61-90da-11e8-bf33-005056a12a82-1234568",
   "trusted_port":"1200",
   "pf_file":"/tmp/openvpn_pf_4fcad505693b33f97c4fe105df8681cb.tmp",
   "ifconfig_local":"10.150.0.1",
   "tun_mtu":"1500",
   "auth_control_file":"/tmp/openvpn_acf_321bb12075dc0e1b5440d227220bac5d.tmp",
   "daemon":"1",
   "IV_COMP_STUBv2":"1",
   "verb":"3",
   "IV_PLAT":"win",
   "daemon_pid":"52435",
   "time_unix":"1547319288",
   "redirect_gateway":"0",
   "route_vpn_gateway":"10.150.0.2",
   "proto_1":"udp",
   "route_net_gateway":"172.16.100.1",
   "IV_PROTO":"2",
   "daemon_log_redirect":"1",
   "dev_type":"tun",
   "IV_VER":"2.4.3",
   "IV_LZO":"1",
   "remote_port_1":"1200",
   "ifconfig_pool_local_ip":"10.150.0.5",
   "pluginid":"9",
   "ifconfig_pool_remote_ip":"10.150.0.6",
   "local_port_1":"1200",
   "IV_TCPNL":"1",
   "route_network_1":"10.150.0.0"
}

В переменном окружении появляются переменные содержащие параметры туннеля "ifconfig_pool_local_ip" и "ifconfig_pool_remote_ip".


Функция OPENVPN_PLUGIN_LEARN_ADDRESS вызывается при обучении OpenVPN сервером, связки IP адресов и маршрутов к ним. После выхода из этой функции активируется процедура применения настроек пакетного фильтра из файла. Переменное окружение OPENVPN_PLUGIN_LEARN_ADDRESS при этом соответствует фазе OPENVPN_PLUGIN_CLIENT_CONNECT_V2.


fa56bf61-.../172.16.111.168:1200 ----- pf_check_reload : struct pf_context -----
fa56bf61-.../172.16.111.168:1200 enabled=1
fa56bf61-.../172.16.111.168:1200 filename='/tmp/openvpn_pf_343330698e4acdea34c8a8c7fb87d861.tmp'
fa56bf61-.../172.16.111.168:1200 file_last_mod=1547319124
fa56bf61-.../172.16.111.168:1200 n_check_reload=1
fa56bf61-.../172.16.111.168:1200 reload=[1,15,1547319125]
fa56bf61-.../172.16.111.168:1200  ----- struct pf_set -----
fa56bf61-.../172.16.111.168:1200  kill=0
fa56bf61-.../172.16.111.168:1200   ----- struct pf_subnet_set -----
fa56bf61-.../172.16.111.168:1200   default_allow=ACCEPT
fa56bf61-.../172.16.111.168:1200   ----- struct pf_cn_set -----
fa56bf61-.../172.16.111.168:1200   default_allow=DROP
fa56bf61-.../172.16.111.168:1200    12345678-90da-11e8-bf33-005056a12a82-1234567 ACCEPT
fa56bf61-.../172.16.111.168:1200    fa56bf61-90da-11e8-bf33-005056a12a82-1234567 ACCEPT
fa56bf61-.../172.16.111.168:1200   ----------
fa56bf61-.../172.16.111.168:1200    fa56bf61-90da-11e8-bf33-005056a12a82-1234567 ACCEPT
fa56bf61-.../172.16.111.168:1200    12345678-90da-11e8-bf33-005056a12a82-1234567 ACCEPT
fa56bf61-.../172.16.111.168:1200 --------------------

При отключении клиента вызывается функция OPENVPN_PLUGIN_CLIENT_DISCONNECT.


OPENVPN_PLUGIN_CLIENT_DISCONNECT
{
   "route_netmask_1":"255.255.0.0",
   "route_gateway_1":"10.150.0.2",
   "trusted_ip":"172.16.111.168",
   "link_mtu":"1622",
   "IV_COMP_STUB":"1",
   "daemon_start_time":"1547319280",
   "IV_LZ4":"1",
   "dev":"tun150",
   "common_name":"fa56bf61-90da-11e8-bf33-005056a12a82-1234568",
   "time_ascii":"Sat Jan 12 18:54:48 2019",
   "bytes_received":"30893",
   "IV_NCP":"2",
   "untrusted_port":"1200",
   "ifconfig_remote":"10.150.0.2",
   "IV_LZ4v2":"1",
   "local_1":"172.16.100.139",
   "script_context":"init",
   "untrusted_ip":"172.16.111.168",
   "config":"/usr/local/etc/openvpn/server150.conf",
   "username":"fa56bf61-90da-11e8-bf33-005056a12a82-1234568",
   "trusted_port":"1200",
   "pf_file":"/tmp/openvpn_pf_4fcad505693b33f97c4fe105df8681cb.tmp",
   "ifconfig_local":"10.150.0.1",
   "tun_mtu":"1500",
   "auth_control_file":"/tmp/openvpn_acf_4bdddbada2885cde42cd3cb1b85d77e5.tmp",
   "daemon":"1",
   "IV_COMP_STUBv2":"1",
   "verb":"3",
   "IV_PLAT":"win",
   "daemon_pid":"52435",
   "time_unix":"1547319288",
   "redirect_gateway":"0",
   "route_vpn_gateway":"10.150.0.2",
   "proto_1":"udp",
   "route_net_gateway":"172.16.100.1",
   "IV_PROTO":"2",
   "daemon_log_redirect":"1",
   "time_duration":"3781",
   "dev_type":"tun",
   "IV_VER":"2.4.3",
   "IV_LZO":"1",
   "bytes_sent":"22684",
   "remote_port_1":"1200",
   "ifconfig_pool_local_ip":"10.150.0.5",
   "pluginid":"7",
   "ifconfig_pool_remote_ip":"10.150.0.6",
   "local_port_1":"1200",
   "IV_TCPNL":"1",
   "route_network_1":"10.150.0.0"
}

В переменном окружении добавляются длительность соединения и трафик пользователя.


Как вы успели заметить, в связи с обилием данных в разных вызовах, написание и отладка плагина на языке программирования C(C++) будет довольно трудоёмкой задачей.
Для расширения функционала было решено сделать "чудо" сначала для внутреннего проекта, а потом выложить его в свободный доступ :)
После долгого чтения исходных кодов OpenVPN и различных примеров узкоспециализированных плагинов, был написан проект, который в качестве языка программирования логики обработки сессии использует Python. Код представляет собой подключаемый к OpenVPN плагин на языке C, который все запросы поступающие в плагин, отправляет в Python модуль через c-api reference.


OpenVPN plugin python proxy


Почему Python модуль ?


Python c-api reference работая с python файлами напрямую, некорректно работает с загрузкой python библиотек.


Как это работает ?


При инициализации плагина в OpenVPN, плагин возвращает масочный список всех функций, которые может обслужить. При наступлении очередной фазы подключения или внутреннего события, OpenVPN вызывает соответствующие функции из плагина. Плагин преобразует переменное окружение и параметры переданные функции в структуру, инициализирует python и передаёт структуру в соответствующую процедуру python модуля. Процедура возвращает плагину один из трёх ответов (0 — Success, 1 — Error, 2 — Deferred). Ответ транформируется и возвращается OpenVPN.


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


Внутри python модуля вы можете реализовать любую логику, подключая нужные библиотеки и ресурсы. Если вы не уверены в скорости выполнения проверок, то используйте "отложенные" подтверждения.


Используя группировку пользователей подключаемых к сервису, через pf_file можно довольно тонко настроить сетевое взаимодействие между пользователями и другими ресурсами. В свою очередь подключив плагин на мониторинг, всегда можно будет через management интерфейс OpenVPN управлять сессиями клиентов.


Во время тестирования проекта был разработан механизм генерации паролей, аналогичный jwt токенам, но имеющим меньший размер.


Суть проста. Токен содержит в себе идентификатор клиента и срок окончания доступа. Для подписи токена используется HMAC_SHA1 с закрытым ключом. После подписи токена, текстовое содержимое ксорится подписью и конвертится в base64. Таким образом получается "запечатывание" токена. "Запечатанный" токен используется в качестве пароля пользователя. При несанкционированном изменении блока с данными, поломается xor, если поломается xor, значит поломается проверка подписи. Без закрытого ключа подпись изменить не получится.


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


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


© Aborche 2019
Aborche

Комментарии (18)


  1. leschenko
    13.01.2019 19:45
    +3

    То что написано в статье, это потрясающе. Но с чего вы взяли что openvnp это дефакто стандарт? Чтобы соединение было установлено требуется стороннее по на клиенте. Я бы сказал, openvnp это хорошая альтернатива стандартным l2tp и ikev2.


    1. sborisov
      13.01.2019 22:24

      Да, всё-таки стандарт это L2TP.
      Хотя, на одном из мест работы, намучились с ним, очень долго настраивали клиентский Linux, помогла вот эта статья:
      https://github.com/nm-l2tp/network-manager-l2tp/wiki/Known-Issues


      OpenVpn в Ubuntu (клиенте) было бы настроить значительно проще.


      1. Amihailov
        13.01.2019 23:55
        +1

        Стандарт стандартом, но когда два и более клиентов пытаются построить l2tp туннель из одной сети к одному серверу, то происходит ОЙ. И, к сожалению, не каждый маршрутизатор умеет этот ОЙ разруливать.


        1. leschenko
          14.01.2019 01:12

          Первый раз слышу о подобном. Если не секрет, то кто именно это делать не умеет? Что-бы на будущее знать.
          PS: ESP (50 IP) не все умеют прокидывать внутрь сети, но чтобы наружу кого-то не пускать…


          1. icCE
            14.01.2019 02:02

            Проблема там, когда два клиента за одним NAT, то сервер обслуживает пслд подключение. Зависит все от реализации. Было проблемой например в routeros/mikrotik.


            1. stavinsky
              14.01.2019 10:52

              Точно не PPTP? Обычно у него такие проблемы


              1. Pinkerton42
                15.01.2019 09:01

                Там на самом деле проблема в ipsec. Если его отключить, то все работает.


                1. Andrusha
                  15.01.2019 13:28

                  Без IPsec получается нешифрованный туннель.


                  1. Pinkerton42
                    15.01.2019 13:38

                    Да, я знаю и написал это не как руководство к действию, а назвал причину неработающего ipsec+l2tp у занатовых клиентов.


            1. Andrusha
              15.01.2019 13:35

              Это фишка Windows-клиента, у него исходящий порт всегда одинаковый, соответственно, последний подключившийся «перетягивает одеяло» на себя.


        1. vesper-bot
          14.01.2019 10:38

          Там просто дефолтные правила для L2TP заставляли транслировать исходящий порт 1701 в 1701 вне зависимости от существующей трансляции. Отключить — заработает.


          1. LESHIY_ODESSA
            15.01.2019 18:58

            А вы не могли бы более подробно объяснить что и где отключать?


      1. leschenko
        14.01.2019 01:17

        С Linux всегда всё не просто. Не скажу что это плохо, просто там все по другому. И когда люди привыкают к этому другому, то Windows им кажется «по другому».


        1. sborisov
          14.01.2019 10:26

          Сервер настраивали администраторы windows, поэтому на ней не было проблем с подключением, ну а тем кто пользуется linux пришлось подбирать протоколы шифрования.
          Но ничего, справились…


  1. saipr
    13.01.2019 20:15

    Для корпоративных сетей в режиме MultiPoint-To-SinglePoint обычно используется свой центр сертификации PKI, который легко строится либо при помощи easy-rsa, либо на основе XCA.

    Для выпуска сертификатов можно еще использовать и этот УЦ, созданное на базе OpenSSL и SQLite3. А для настройки клиента можно посмотреть это GUI.


    1. vanyas
      14.01.2019 16:08

      Ужаснее GUI сложно даже представить…
      Ну и GUI для OpenVPN rkbtynf есть практически в любом дистрибутиве в виде модуля к NetworkManager


  1. jamm1985
    14.01.2019 09:30

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

    remote-cert-tls server

    для защиты от MitM акт это мастхэв.


  1. toxi_roman
    14.01.2019 11:48

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