📜 ⬆️ ⬇️

OpenVPN, about which you knew so little

OpenVPN, as much in this word. A multiplatform, flexible, customizable, free open-source VPN server that is actually the "defacto" standard for accessing internal corporate networks. Most administrators use it with default settings or with typical configurations widely described in different HOW-TO. But is OpenVPN as simple as it seems at first glance? In this article, we will look at the internal OpenVPN mechanisms that are hidden from view, which radically change the perception of its capabilities.


OpenVPN server is distributed as source code or ready-to-install compiled packages for various operating systems. OpenSSL is used as an encryption library.


Most of the configurations for connecting clients to the server, as well as between servers, involve the use of a bunch of private or private / public keys to ensure the security of internal traffic. Corporate networks in MultiPoint-To-SinglePoint mode typically use their own PKI certificate authority, which is easily built using either easy-rsa or XCA . For inter-communication point-to-point communication, the common key configuration is used mainly. Recall the main, well-known mechanisms and opportunities.


Basic mechanisms and capabilities



In all cases, the connection for the security handshake handshake between the client and the server uses the Diffie-Hellmann protocol.



At this "regular" widely used features end and local customization begins for each specific case.


Additional OpenVPN features


Consider the additional features of OpenVPN, about which someone may have heard, but in reality did not see or did not use.



Or another example.
Disable everything except access between users and a DNS server located on the local network and a test circuit on the network 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] 

The filtering mechanism is activated via the configuration file, or when connecting the plugin that has set the flag "OPENVPN_PLUGIN_ENABLE_PF".
We will discuss this opportunity later.
The second traffic filtering mode is a packet filter built into the system. To activate it, the "client-to-client" directive should not be in the config. From the point of view of automating the on / off of the necessary rules when connecting / disconnecting clients, it is most convenient to use separate inserts into the list of rules implemented either through CHAINS in Iptables (Linux) or in Anchors in PF (FreeBSD). Activation / deactivation of rules is usually carried out through client-connect / client-disconnect directives in the server configuration file, which cause corresponding scripts when a user connects / disconnects.



Let's try to understand how plugins work on the part of OpenVPN.
Functions and parameters used for working with plugins are described in a separate file.
The main task of the plug-in, when it is initialized by the OpenVPN server, is to transfer the list of functions supported by the plug-in and, when calling any of the functions, return the correct response code that will be understood by the server.


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

Let us dwell on each group. The logic of the work we will consider on the basis of user password authentication.
When the server starts, after reading the configuration file, the server calls the OPENVPN_PLUGIN_UP and OPENVPN_PLUGIN_ROUTE_UP functions. In the variable environment of the called functions, the main parameters of the running server are passed.


 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" } 

These functions can be used for notifications when starting the server or changing the configuration.
When a client connects, OpenVPN requests the ability to activate an internal packet filter.


 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" } 

As you can see from the dump, the variable pf_file appeared. This file should contain the internal packet filter rules for the current session being processed.
Next, check the username and password of the user in the function 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" } 

This is the only place where the password in a variable environment is present in the clear.
The result of this function should be three response options.


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

If the server receives the response OPENVPN_PLUGIN_FUNC_DEFERRED, then the mechanism of "delayed" authentication comes into play. As we can see, the variable "auth_control_file" appeared in the variable environment, the contents of this variable contain the name of the file in which the response from the authentication system will be expected. The answer is the character 0 placed in the specified file (to allow access), 1 (to deny access). The server parameter "hand-window" determines the timeout in seconds during which the server will wait for a response. While waiting for traffic from other clients is not interrupted.


Since we work with password authentication, the certificate verification function OPENVPN_PLUGIN_TLS_VERIFY is not called. Instead, OPENVPN_PLUGIN_TLS_FINAL is immediately called, confirming session establishment.


 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" } 

Next, the OPENVPN_PLUGIN_IPCHANGE call is triggered, which is called before changing the client's ip address.


 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" } 

The function OPENVPN_PLUGIN_CLIENT_CONNECT_V2, called when the IP address is set by the internal DHCP server.


 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" } 

In a variable environment, variables containing the tunnel parameters "ifconfig_pool_local_ip" and "ifconfig_pool_remote_ip" appear.


The OPENVPN_PLUGIN_LEARN_ADDRESS function is called when the OpenVPN server learns a bunch of IP addresses and routes to them. After exiting this function, the procedure for applying the packet filter settings from a file is activated. The variable environment OPENVPN_PLUGIN_LEARN_ADDRESS corresponds to the phase 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 -------------------- 

When the client is disabled, the function OPENVPN_PLUGIN_CLIENT_DISCONNECT is called.


 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" } 

In a variable environment, the connection duration and user traffic are added.


As you have noticed, due to the abundance of data in different calls, writing and debugging a plug-in in the C programming language (C ++) will be a rather laborious task.
To expand the functionality, it was decided to make a "miracle" first for the internal project, and then put it into free access :)
After a long reading of the OpenVPN source codes and various examples of highly specialized plug-ins, a project was written that uses Python as the programming language of the session processing logic. The code is a plug-in in C language that is connected to OpenVPN, which sends all requests to the plugin, sends to Python a module via c-api reference .


OpenVPN plugin python proxy


Why python module?


Python c-api reference working with python files directly, does not work correctly with loading python libraries.


How it works ?


When initializing a plugin in OpenVPN, the plugin returns a masked list of all the functions it can serve. When the next phase of the connection or internal event occurs, OpenVPN calls the corresponding functions from the plugin. The plugin converts the variable environment and the parameters of the functions passed to the structure, initializes python and passes the structure to the corresponding python module procedure. The procedure returns one of the three responses to the plugin (0 - Success, 1 - Error, 2 - Deferred). The answer is transformed and returned to OpenVPN.


Please note that all calls to the module are "stateless", which means that the procedures do not remember and do not know what happened earlier in other calls. You can only focus on the variable environment passed to the plugin from OpenVPN.


Inside the python module you can implement any logic by connecting the necessary libraries and resources. If you are not sure about the speed of checks, use the "pending" confirmation.


Using the grouping of users connected to the service, through pf_file, you can finely configure the network interaction between users and other resources. In turn, by connecting the monitoring plugin, it will always be possible to manage client sessions through the management interface of OpenVPN.


During the testing of the project, a password generation mechanism was developed, similar to jwt tokens, but having a smaller size.


The bottom line is simple. The token contains the client ID and the access end date. To sign a token, use HMAC_SHA1 with a private key. After signing the token, the text content is signed by the signature and converted to base64. Thus it turns out "sealing" the token. A "sealed" token is used as a user password. When an unauthorized change of the data block, xor breaks, if xor breaks, the signature verification will break. Without a private key, the signature cannot be changed.


If you do not want to control the time of the password with your hands, then generate such a token, and check it for validity inside the plugin, without calling external services. This scheme is very convenient for session generation of passwords for a certain time. In this case, you can transfer the contents of the token to the external control system and it will tune itself to disconnect the user after the token expires.


I hope the information in this article was useful to you.
Thank you for taking the time to read it.
If you have questions, I will try to answer what I can.


© Aborche 2019
Aborche


')

Source: https://habr.com/ru/post/435802/


All Articles