📜 ⬆️ ⬇️

OpenVPN with Extended Authentication and Authorization

The article discusses the configuration of OpenVPN with additional features:


The configuration of clients under Linux, Windows and MacOS is also described.

Server Tuning


Install OpenVPN


Take the Nyr / openvpn-install script, run as root.

git clone https://github.com/Nyr/openvpn-install.git cd openvpn-install 

A few questions will be asked in the startup process.
')

There is also a security-enhanced version of the original script - github.com/Angristan/OpenVPN-install . It has more encryption settings with explanations why this is so.

user management


Add
If tokens are not used, the user is added via the same script. The script essentially generates a user ovpn-config and inserts a certificate signed by the root certificate.

If tokens are used (see the section on tokens below) then the certificate is written out by hand based on a request for a certificate that is generated on the token. The user config needs to be made by hands from the existing template (from the same one from which the config script generates). The template here is /etc/openvpn/client-common.txt . It is not included in the openvpn distribution and is generated by the script during the configuration process.

Deletion
Users are deleted through the same installation script. The certificate is added to the CRL , the new CRL is being pushed to the vpn server. All certificates that are in the CRL server are considered invalid and refuse to accept.

How to revoke a certificate manually:

 cd /etc/openvpn/easyrsa #   ./easyrsa revoke $CLIENT #   crl ./easyrsa gen-crl #   crl rm -rf /etc/openvpn/crl.pem #    cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem # openvpn    crl,       nobody chown nobody:nobody /etc/openvpn/crl.pem 

Filtering available hosts for clients


Clients should be limited to those hosts that they can go inside the network when they connect to openvpn.

Manually

The idea is to catch packets even on the tun0 interface, in which they come from clients and filter them before they get into NAT. After NAT, filtering them will no longer be why - they all will have the ip-address of the openvpn server in the internal network. Before getting into NAT, the packets for each user have their own unique ip-address (the correspondence of ip-addresses and users can be found in the /etc/openvpn/ipp.txt file).

Packages that pass through the system (do not emanate directly from it and are not incoming, that is, they are essentially routed by the system) are processed by the FORWARD table. Tables in iptables are processed from top to bottom, if none of the rules in the table lead to deciding the fate of the packet, then the default rule is triggered.

FORWARD table preparation:

 #   iptables -F FORWARD #     FORWARD -    iptables -P FORWARD DROP #     iptables -I FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT 

An example of rules for a specific client. Since the default rule for the table is DROP, it remains only to allow those host + port pairs to which you can. Allow access to the port on the host + ping the host itself:

 iptables -I FORWARD -s 10.8.0.3 -i tun0 -d 10.0.2.3 -p tcp --dport 443 -j ACCEPT iptables -I FORWARD -s 10.8.0.3 -i tun0 -d 10.0.2.3 -p icmp --icmp-type echo-request -j ACCEPT 

In the example above, host 10.8.0.3 is allowed access to port 443 of host 10.0.2.3.

How to close access:

 #         iptables -L FORWARD --line-numbers #     iptables -D FORWARD { } 

Then you need to find all the rules for a specific client and delete them.

During debugging it is convenient to look at what rules work. Each rule has a counter for processed packets.

 #  ,      watch iptables -nvL FORWARD #     iptables -Z FORWARD 

Automatically

The openvpn server has the ability to execute scripts for certain actions. In particular, when connecting and disconnecting clients. Scripts can be written on anything, just to be executable. Inside the script, the environment variables are passed all the parameters of the current connection. We are interested in variables:


Root privileges are required to manage iptables. After connecting, Openvpn resets permissions to nobody and executes scripts from it. It's bad to allow nobody to do something from under sudo, and it’s better not to use the asterisk in the rules, but somehow you need to allow the user to manage iptables.

 # /etc/sudoers.d/50_openvpn # #    nobody ALL = NOPASSWD: /sbin/iptables -A FORWARD* #     nobody ALL = NOPASSWD: /sbin/iptables -L FORWARD* #    nobody ALL = NOPASSWD: /sbin/iptables -D FORWARD* 

In the server's configuration, you need to add permission to execute third-party files and enable two hooks responsible for connecting and disconnecting the user.

 script-security 2 client-connect /etc/openvpn/bin/hosts.rb client-disconnect /etc/openvpn/bin/hosts.rb 

The script itself, which reads configs and applies the rules for iptables. The script works on the same principles as described in the previous section.

/openvpn/bin/hosts.rb
 #!/usr/bin/ruby # -*- coding: utf-8 -*- require 'pp' def log(string) puts 'hosts.rb: ' + string end def parse_config_file(name) config_path = "hosts/#{name}" unless File.exist?(config_path) puts "There is no specific configuration for #{name}." p name exit 0 end config_source = IO.read(config_path).split("\n") config = config_source.inject([]) do |result,line| ip, port, protocol = line.split(/\s+/) result << { ip: ip, port: port, protocol: protocol || 'tcp' } end end def get_config(name) user_config = parse_config_file(name) if user_config everybody_config = parse_config_file('everybody') end everybody_config + user_config end def apply_rule(rule) command = "sudo iptables #{rule}" log(command) system(command) end def remove_rule(number) command = "sudo iptables -D FORWARD #{number}" log(command) system(command) end def allow_target(source_ip, options) #         . apply_rule("-A FORWARD -s #{source_ip} -i tun0 -d #{options[:ip]} -p #{options[:protocol]} --dport #{options[:port]} -j ACCEPT") #       apply_rule("-A FORWARD -s #{source_ip} -i tun0 -d #{options[:ip]} -p icmp --icmp-type echo-request -j ACCEPT") end def clear_targets(source_ip) #      FORWARD,  source_ip. rules_exist = true while rules_exist table = `sudo iptables -L FORWARD --line-number`.split("\n") the_line = table.find do |line| fields = line.split(/\s+/) ip = fields[4] ip == source_ip end if the_line number = the_line.split(/\s+/)[0] remove_rule(number) else rules_exist = false end end end ################################################################################ script_type = ENV['script_type'] log(script_type) name = ENV['common_name'] source_ip = ENV['ifconfig_pool_remote_ip'] case script_type when 'client-connect' config = get_config(name) config.each{|target| allow_target(source_ip, target)} when 'client-disconnect' clear_targets(source_ip) else puts "Unknown script type #{script_type}." end 

Rules are stored in files corresponding to the common name of certificates in the /etc/openvpn/hosts folder. They are spelled out which IP addresses are available for a particular client. Separator - an arbitrary number of spaces. Separator records IP address, port and protocol (tcp or udp).

 10.0.0.24 53 udp 10.0.0.25 53 udp 10.0.2.3 443 tcp 

As a result, the following structure should /etc/openvpn in the /etc/openvpn folder

Bin── bin
│ └── hosts.rb
Hosts── hosts
│ ├── user1
│ ├── user2
Body └── everybody
Server── server.conf
└── ...

User1 and user2 are files in the above format. They describe which hosts the user with the corresponding common name has access to.

There is another additional file everybody , it contains rules that apply to all clients, provided that there is a separate configuration file for these clients. That is, if the user is given a list of hosts where he can go, then this list and those hosts that are listed in everybody apply. If not, then everybody does not apply. In this file it is convenient to take out for example a DNS server.

Logging

The installation script only includes logging of current connections ( status) parameter status) . For the usual log to appear, you need to add a line to the server config ( /etc/openvpn/server.conf ):
 log-append /var/log/openvpn.log 


Ldap

There is an openvpn-auth-ldap plugin that allows you to re-authenticate the user via LDAP.

Put the package:

 sudo yum install openvpn-auth-ldap 

Add to server.conf:

 plugin /usr/lib64/openvpn/plugin/lib/openvpn-auth-ldap.so "/etc/openvpn/ldap.conf" 

Create a config for ldap in /etc/openvpn/ldap.conf :
 <LDAP> URL ldaps://{LDAP_DOMAIN_HERE} Timeout 15 TLSEnable no FollowReferrals yes BindDN "BIND_DN_HERE" Password "BIND_PASSWORD_HERE" </LDAP> <Authorization> BaseDN "{BASE_DN_HERE}" SearchFilter "(&(sAMAccountName=%u)(objectClass=organizationalPerson)(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" RequireGroup false </Authorization> 

Add line to user ovpn-config:

 auth-user-pass 

Thus, the user will first be asked for the username and password from the domain, then the PIN code from the token. If one of these steps fails, the connection will not be established.

Description of options for ldap.conf is in the plugin repository . It supports group membership authentication, but I have not tested it.

Speed


The greatest increase in speed gives the inclusion of udp mode. It is advised in all manuals. The point is that there is no point in launching a client tcp connection in a tcp channel. One tcp at the client is enough to make the correct delivery of packets. If packets disappear in the udp channel, the delivery adjustment will be controlled by the client tcp connection.

The speed will increase as a minimum because you do not have to wait for confirmation of the delivery of each packet in the channel. There is a second problem with tcp - one client tcp packet most likely does not fit into one vpn channel packet. The MTU is the same, but you still need to add headers to the client package. As a result, one user packet has to send two packets inside the vpn channel.

TCP makes sense when used differently. For example, when vpn works through ssh channel.

Example of full server configuration


example-server.conf
 port 1194 proto tcp dev tun sndbuf 0 rcvbuf 0 ca ca.crt cert server.crt key server.key dh dh.pem tls-auth ta.key 0 topology subnet server 10.8.0.0 255.255.255.0 ifconfig-pool-persist ipp.txt push "redirect-gateway def1 bypass-dhcp" push "dhcp-option DNS 10.0.0.25" push "dhcp-option DNS 10.0.0.24" keepalive 10 120 cipher AES-256-CBC comp-lzo user nobody group nobody persist-key persist-tun status openvpn-status.log verb 3 crl-verify crl.pem log-append /var/log/openvpn.log script-security 2 client-connect /etc/openvpn/bin/hosts.rb client-disconnect /etc/openvpn/bin/hosts.rb 


Setting tokens


PKCS # 11 Library


To work with tokens need a special library. The library is needed both to create key pairs, and for the actual connection. Download under all platforms can be on the link .

Everywhere, where librtpkcs11ecp.so occurs further, this is the very library that needs to be downloaded and put somewhere in a convenient place.

Creating a certificate on a token


Generate a key pair on the token. The id parameter here is the ordinal number of the slot on the token where the key pair is placed.

 pkcs11-tool --module /usr/lib64/librtpkcs11ecp.so --keypairgen --key-type rsa:2048 -l --id 01 

Make a certificate request for the public key. In the process of creating a certificate request, the certificate’s lifetime and common name are set, which is used to filter available ip-addresses within the network. Common name must match the login in ActiveDirectory so that there is no confusion.

 openssl openssl> engine -t dynamic -pre SO_PATH:/usr/lib64/openssl/engines/pkcs11.so -pre ID:pkcs11 -pre LIST_ADD:1 -pre LOAD -pre MODULE_PATH:/usr/lib64/librtpkcs11ecp.so openssl> req -engine pkcs11 -new -key slot_0-id_01 -keyform engine -out /home/john/good.req 

The received request must be moved to the /etc/openvpn/easy-rsa/pki/reqs/ . The file extension must be req .
Convert request to certificate:

 cd /etc/openvpn/easy-rsa/ ./easyrsa sign-req client good 

After that, in the /etc/openvpn/easy-rsa/pki/issued/ folder /etc/openvpn/easy-rsa/pki/issued/ there will be a certificate with the same name, but with the extension crt .

Before recording, the certificate must be converted to DER:

 openssl x509 -in /home/user/user-cert.pem -out /home/user/user-cert.crt -outform DER 

Token certificate entry:

 pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -l -y cert -w /home/user/user-cert.crt --id 45 --label TEST 

Written on the basis of the article “Using Rutoken EDS with OpenSSL (RSA)” .

Using Token for Authentication


Find the certificate id to show to the server:

 $ openvpn --show-pkcs11-ids /usr/lib64/librtpkcs11ecp.so The following objects are available for use. Each object shown below may be used as parameter to --pkcs11-id option please remember to use single quote mark. Certificate DN: /CN=User1 Serial: 490B82C4000000000075 Serialized id: aaaa/bbb/41545F5349474E415455524581D2A1A1B23C4AA4CB17FAF7A4600 

We are interested in serialized id here.

Options that need to be entered in the ovpn-config to pick up tokens:

 pkcs11-providers /usr/lib64/librtpkcs11ecp.so pkcs11-id 'aaaa/bbb/41545F5349474E415455524581D2A1A1B23C4AA4CB17FAF7A4600' 

The pkcs11-id option must be enclosed in single quotes.

This instruction makes sense on all platforms. You need to specify the path to the library and the id of the certificate on the token. The library may be called a little differently, be .dll , not .so , but the meaning is the same.

At the same time, the cert and key sections must be removed from the ovpn file, because the certificate and private key will be taken from the token.

Fully client config (for windows) looks like this:

client.ovpn
client
dev tun
proto tcp
sndbuf 0
rcvbuf 0
remote 78.47.37.247 22222
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-CBC
comp-lzo
setenv opt block-outside-dns
key-direction 1
verb 3

pkcs11-providers "c://Windows//System32//rtPKCS11ECP.dll"
pkcs11-id 'Aktiv\x20Co\x2E/Rutoken\x20ECP/342b871d/Rutoken/01'

-----BEGIN CERTIFICATE-----
{CERT_HERE}
-----END CERTIFICATE-----


<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
{KEY_HERE}
-----END OpenVPN Static key V1-----
</tls-auth>


Written on the basis of “OpenVPN configuration using client-side smart cards” .

Customer setup


Linux


In openvpn there is a bug that does not allow the user to enter the PIN from the token, if the package is built with systemd support. Since recently systemd is everywhere, all packages that are already available in the repositories are built with its support. Linux customers need to build the package themselves. Here is an example of the configuration that earned me on Arch Linux:

 ./configure \ --prefix=/usr \ --sbindir=/usr/bin \ --enable-iproute2 \ --enable-pkcs11 \ --enable-plugins \ --enable-x509-alt-username 

You can check whether openvpn is built with or without systemd using the following command:

 openvpn --version | grep --color enable_systemd 

Mas os


Under Mac OS, there is only one free client - Tunnelblink .

He does not know how to enter a pin code from a token from gui. The bug is described for example here - https://groups.google.com/forum/#!topic/tunnelblick-discuss/f_Rp_2nV-x8 It is circumvented by running openvpn from the console. This is not surprising, given that the official client under windows is also not able to.

Also under Mac OS (unlike windows) additional scripts are needed to configure the network. If you just run openvpn from the console, then DNS will not work (maybe something else, only DNS is manifested).

TunnelBlick has these network configuration scripts, they only need to be called when a connection is established and disconnected. What you need to add in the ovpn-config:

 script-security 2 up "/Applications/Tunnelblick.app/Contents/Resources/client.up.tunnelblick.sh -9 -d -f -m -w -ptADGNWradsgnw" down "/Applications/Tunnelblick.app/Contents/Resources/client.down.tunnelblick.sh -9 -d -f -m -w -ptADGNWradsgnw" 

An example of a script for running an openvpn connection that can be put on the desktop and poking the mouse:

 #!/bin/bash tunnelblick=/Applications/Tunnelblick.app/Contents/Resources/openvpn/openvpn-2.4.2-openssl-1.0.2k sudo $tunnelblick/openvpn --config $tunnelblick/user.ovpn 

Windows


Under windows everything seems to work. The official client does not know how to enter the pin-code from the token, it costs by launching openvpn by hand from the console.

The most important thing is to do everything from an administrator. Run from the administrator installer client. To start the terminal in which openvpn starts, also with the admin rights, otherwise it will not be able to control the network interface.

Under Windows, the path to the library to work with tokens should be recorded through double slashes. This applies to both the ovpn-config and the --show-pkcs11-ids option on the command line.

 pkcs11-providers "c://Windows//System32//rtPKCS11ECP.dll" pkcs11-id 'Aktiv\x20Co\x2E/Rutoken\x20ECP/342b871d/Rutoken/01' 

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


All Articles