📜 ⬆️ ⬇️

Using scripts in Openvpn to integrate it with other system services (Firewall, DB, etc.)

I wanted to collect a VPN processor that would take users from the database, set up a firewall for this user, and write logs to the database.

OpenVPN for each event (connection, disconnection of the client) can cause an external program. We will use this.

I use FreeBSD, but everything described below will work on any Linux, I just need to change the paths. Postgresql will act as a database. Authorization by certificate and password.
I will write the script of interaction with external services in Perl

Create a database:
')
psql -Upgsql template1 ctreate database vpn; \q 

Description of the tables in the database:

users - from the name it is clear that there will be users in it.
columns:


groups - a table with groups


log - log table, I will write the start and end of the session into it


stat - online users table


Create tables:

 psql -Upgsql vpn; create table users ( "id" serial, "login" varchar(32) not null, "name" varchar(32) not null, "password" varchar(32) not null "groups_id" integer not null, "active" boolean not null default true, "hwkey" boolean not null default true, ); create table groups ( "id" serial, "groupname" varchar(32) not null, "active" boolean not null default true ); create table log ( "id" serial, "date" timestamp, "users_id" integer not null, "realaddress" varchar(32), "virtualaddress" varchar(32), "status" varchar(32) ); create table stat ( "id" serial, "date" timestamp, "login" varchar(32); "realaddress" varchar(32), "virtualaddress" varchar(32) ); 

Create a user with read permissions for the users and groups tables, and write to log and stat

 psql -Upgsql template1 create user ovpn WITH PASSWORD 'password'; \q psql -Upgsql vpn grant select on users TO ovpn; grant select on groups to ovpn; grant all on log to ovpn; grant all on stat to ovpn; \q 

Filling tables:

For example, create two groups, admins with full access to the internal network and rdp with access to the servers via RDP.

Accordingly, we will create two users:

100101 - admin
100102 - with access to RDP

 psql -Upgsql vpn insert into groups values(dafault,'admins',true); insert into groups values(default,'rdp',true); insert into users values(default,100101,' ..',md5('password'),(select id from groups where groupname = 'admins'),true,false); insert into users values(default,100102,' ..',md5('password'),(select id from groups where groupname = 'rdp'),true,false); 

As a result, two groups were created with id 1 and 2. In accordance with these id, tables will be created in the firewall, to which corresponding permissions will be determined.

Certificates

If there is no root certificate yet, you need to create it. I have certificates in / root / ca

 cd /root/ca openssl req -x509 -newkey rsa:1024 -keyout /root/ca/ssl.key/ca.key -out /root/ca/ssl.crt/ca.crt -days 9999 -nodes -subj "/C=RU/ST=MSK/L=MSK/O=COMPANY/CN=CA" 

Certificate for server signed with CA certificate:

 openssl req -new -newkey rsa:1024 -nodes -keyout /root/ca/ssl.key/ovpn.key -subj /CN=ovpn.domain.ru -out /root/ca/ssl.csr/ovpn.csr openssl ca -config ca.conf -in /root/ssl.csr/ovpn.csr -out /root/ssl.crt/ovpn.crt -batch 

ovpn.key, ovpn.crt and ca.crt must be copied to the OpenVPN server.

To generate user certificates, I use the following script, it packs the user's key and certificate into a password-protected p12 file.

 #!/usr/bin/perl use Getopt::Long; GetOptions ('a=s' => \$action, 'u=s' => \$user, 'O=s' => \$ou, 'o=s'=> \$options, 'help' => sub {HelpMessage()}); # variables $ca='ca'; $ca_dir='/root/ca/'; $key_id='1000'; if (length($action)==0){ HelpMessage(); exit; } if ($action=~/^help$/){ print "actions: adduser # Add a new User certificate gen_revoke # Generate revoke file\n"; exit; } if ($action=~/^adduser$/){ adduser(); } if ($action=~/^gen_revoke$/){ gen_revoke(); } sub HelpMessage { print "usage: ".$0. " -a <adduser|gen_revoke> -u <login> -O <VPN>\n"; exit; } sub adduser { $p12_password = randomPassword(6); print "~~Add User(Soft)~~\n"; if (length($user)==0 || length($ou)==0){ print "Error, User and OU must be\n"; exit; } # create cert print "create User certificate\n"; system(`openssl req -new -newkey rsa:1024 -nodes -keyout $ca_dir/ssl.key/$user.key -subj /CN=$user/OU=$ou -out $ca_dir/ssl.csr/$user.csr`); # sign USER cert print "sign certificate\n"; system(`openssl ca -config ca.conf -in $ca_dir/ssl.csr/$user.csr -out $ca_dir/ssl.crt/$user.crt -batch`); # p12 print "create p12\n"; system(`openssl pkcs12 -export -in $ca_dir/ssl.crt/$user.crt -inkey $ca_dir/ssl.key/$user.key -certfile $ca_dir/ssl.crt/$ca.crt -out p12/$user.p12 -passout pass:$p12_password`); print "p12_password: ".$p12_password."\n"; # unlink files unlink('$ca_dir/ssl.csr/$user.csr','$ca_dir/ssl.crt/$user.crt','$ca_dir/ssl.key/$user.key'); } sub gen_revoke { # gen CRL system(`openssl ca -config $ca_dir/ca.conf -gencrl -crldays 365 -out $ca_dir/ssl.crl/certPEM.crl`); system(`openssl crl -in $ca_dir/ssl.crl/certPEM.crl -outform DER -out $ca_dir/ssl.crl/certDER.crl`); print "pls copy ./ssl.crl/certDER.crl to OpenVPN server\n"; } sub randomPassword { $password; $_rand; $password_length = $_[0]; if (!$password_length) { $password_length = 10; } @chars = split(" ", "ABCDEFGHIJK LMNOPQRSTUVWXYZ abcdefghijklmnopqrstu vwxyz 0 1 2 3 4 5 6 7 8 9"); srand; for (my $i=0; $i <= $password_length ;$i++) { $_rand = int(rand 41); $password .= $chars[$_rand]; } return $password; } 

Create certificates for our users

 /root/ca/gen_cert.pl -a adduser -U 100101 -O VPN /root/ca/gen_cert.pl -a adduser -U 100102 -O VPN 

the output will be two files /root/ca/p12/100101.p12 and /root/ca/p12/100102.p12 and passwords to them, which the script will type in the console. These files will need to be installed on user computers (tablets / phones) in a secure storage. As a rule, container passwords are not communicated to users, this excludes the possibility for users to transfer their keys to others.

You can immediately create a file of revoked crl certificates

 /root/ca/gen_cert.pl -a gen_revoke 

It must be copied to the OpenVPN server.

OpenVPN server setup

 port 1194 proto udp dev tun ca /usr/local/etc/ssl/ca.crt cert /usr/local/etc/ssl/ovpn.crt key /usr/local/etc/ssl/ovpn.key crl-verify /usr/local/etc/ssl/certPEM.crl dh /usr/local/etc/ssl/dh2048.pem tls-verify "/usr/local/etc/openvpn/scripts/intra.pl tls-verefy" topology subnet server 172.16.40.0 255.255.255.0 push "route 172.16.0.0 255.255.0.0" push "dhcp-option DOMAIN domain.local" push "dhcp-option DNS 172.16.38.10" client-connect /usr/local/etc/openvpn/scripts/intra.pl client-disconnect /usr/local/etc/openvpn/scripts/intra.pl auth-user-pass-verify "/usr/local/etc/openvpn/scripts/intra.pl auth-user-pass-verify" via-env keepalive 10 120 persist-key persist-tun status /var/log/openvpn-status.log log /var/log/openvpn.log log-append /var/log/openvpn.log management localhost 7505 verb 3 

So, in four places in the config, the external script is called:

The tls-verify parameter allows you to verify a client certificate for some compliance. In my case, I check that the same thing is written to CN as the client sends to Login, in other words, I check that CN = Login. This eliminates the possibility that a user having one certificate would try to log in as another user by entering his username and password. I also check that in the OU field there is a VPN value. I add both values ​​when generating user certificates.

client-connect the script is called at the user connection stage. Here I add the user's address to the appropriate firewall table, and make entries in the log and stat tables.

client-disconnect the script is called at the stage of disconnecting the user. I delete records from the firewall, write to the log and stat tables.

auth-user-pass-verify verification of the transmitted login and password in the database.

Actually, the script itself is lower.

 #!/usr/bin/perl use DBI; use Digest::MD5 qw(md5_hex); $dbh=DBI->connect("DBI:Pg:dbname=vpn;host=localhost","ovpn","password"); ($script_type,$common_name,$ifconfig_pool_remote_ip,$untrusted_ip) = ($ENV{'script_type'},$ENV{'common_name'},$ENV{'ifconfig_pool_remote_ip'},$ENV{'untrusted_ip'}); if ($script_type eq "client-connect") { insert_to_firewall_group(); logging('start'); } if ($script_type eq "client-disconnect") { delete_from_firewall_group(); logging('stop'); } if ($script_type eq "tls-verefy"){ tls_verefy(); } if ($script_type eq "auth-user-pass-verify"){ auth_user_pass_verefy(); } sub get_group { my $req="SELECT groups.id FROM groups INNER JOIN users ON (users.groups_id = groups.id) WHERE users.login='$common_name'"; @row = $dbh->selectrow_array($req); } sub insert_to_firewall_group { get_group(); `/sbin/ipfw table 0 add $untrusted_ip`; `/sbin/ipfw table $row[0] add $ifconfig_pool_remote_ip`; } sub delete_from_firewall_group { get_group(); `/sbin/ipfw table 0 delete $untrusted_ip`; `/sbin/ipfw table $row[0] delete $ifconfig_pool_remote_ip`; } sub tls_verefy { ($script_type, $depth, $x509) = @ARGV; @X509=split(",",$x509); $X509[0] =~s/^OU=//g;$ou = $X509[0]; $X509[1] =~s/^ CN=//g; $cn = $X509[1]; @ous=('VPN'); if ($depth == 0) { #verefy OU foreach(@ous){ if ($_ eq $ou) { $ou_status = 1; #exit 0; } } #verefy CN $req = "SELECT login FROM users WHERE login = '$cn' AND active = true"; @row = $dbh->selectrow_array($req); if ($row[0] eq $cn) { $cn_status = 1; } if ($ou_status == 1 && $cn_status == 1){ exit 0; } exit 1; } } sub logging { ($status) = @_; $date = `date '+%Y-%m-%d %H:%M:%S'`; chop $date; $req = "INSERT INTO log VALUES(DEFAULT,'$date',(SELECT id FROM Users WHERE login='$common_name'),'$untrusted_ip','$ifconfig_pool_remote_ip','$status')"; $dbh->do($req); if ($status eq "start"){ $st = "INSERT INTO stat VALUES(DEFAULT,'$date','$common_name','$untrusted_ip','$ifconfig_pool_remote_ip')"; $dbh->do($st); } else { $st = "DELETE FROM stat WHERE login='$common_name'"; $dbh->do($st); } } sub auth_user_pass_verefy { $username = $ENV{'username'}; $password = $ENV{'password'}; $common_name = $ENV{'common_name'}; $q_hw = "SELECT hwkey FROM users WHERE login = '$common_name' AND active = true"; @row = $dbh->selectrow_array($q_hw); if ($row[0] == 1 ){ exit 0; } $password = md5_hex($password); $req = "SELECT login FROM users WHERE login = '$username' AND password = '$password' AND active = true"; @row = $dbh->selectrow_array($req); if ($row[0] eq $username) { exit 0; } exit 1; } 

Firewall

The firewall created tables where the table number = group id from the groups table

 #!/bin/sh ipfw='/sbin/ipfw -q' clients_real_ip="table(0)" #     ,    admins="table(1)" #    admins rdp="table(2)" #    rdp ${ipfw} flush # --    -- ${ipfw} add allow ip from ${admins} to any #     ${ipfw} add allow tcp from ${rdp} to any 3389 #   rdp   RDP ${ipfw} add deny all from ${rdp} to any #     rdp # --   -- 

it remains to install p12 on the user device, copy ca.crt and such a config

 client dev tun proto udp remote ovpn.domain.ru 1194 resolv-retry infinite nobind persist-key persist-tun script-security 3 ca "C:\\Program Files\\OpenVPN\\config\\ca.crt" cryptoapicert "SUBJ:100101" auth-user-pass comp-lzo log "C:\\Program Files\\OpenVPN\\log\\client.log" log-append "C:\\Program Files\\OpenVPN\\log\\client.log" verb 3 route-delay 5 30 tap-sleep 5 

Finally, delicious for those who use dashboard dashboard . The script must be put in the dashboard / jobs

vpn_stat.rb

 require 'pg' require 'geoip' $conn = PG.connect( :hostaddr=>'ovpn.domain.local', :user=>'ovpn',:password=>'password',:dbname=>'vpn') def getVPNstat all = Hash.new({ value: 0 }) result = $conn.exec( "SELECT COALESCE(users.name,stat.login) AS name,stat.realaddress AS ip FROM stat,users WHERE stat.login = users.login") result.each do | row | user = row['name'] user = user[0..8].downcase ip = row['ip'] country = geoip(ip) all[user] = {label: user,value: country } end send_event('vpnstat', { items: all.values }) end def geoip(ip) c = GeoIP.new('/usr/local/www/ruby/dashboard/geoip/GeoIP.dat').country(ip) country = c[4] return country end SCHEDULER.every '20s' do getVPNstat end 

Download GeoIP database:

 wget -N http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz 

and unpack it in the dashboard / geoip /.

Add tiles to dashboards:

  <li data-row="1" data-col="5" data-sizex="1" data-sizey="2"> <div data-id="vpnstat" data-view="Currency" data-unordered="true" data-title="VPN" style="background-color:green"></div> </li> 

It will look like this:



That's all. It remains only to write a simple web admin area for the most lazy.
Thanks for attention.

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


All Articles