System Engineering

Install OpenVPN on openSUSE

OpenVPN

0. Plan

  • Public IF : eth1 (210.1.1.1)
  • Private IF : eth0 (10.1.0.0/16)
  • Virtual Tunneling IF : tun0 (172.16.1.0/24)

1. Download and install packages

zypper install openvpn openvpn-auth-pam-plugin

2. Install EasyRSA and create server ertificates

cd /usr/local/src
wget -O easy-rsa-2.x.tar.gz https://github.com/OpenVPN/easy-rsa/archive/release/2.x.tar.gz
tar xvf easy-rsa-2.x.tar.gz
cp -r easy-rsa-release-2.x/easy-rsa /etc/openvpn/

cd /etc/openvpn/easy-rsa/2.0/
vi /etc/openvpn/easy-rsa/2.0/vars
# Fill KEY_COUNTRY, KEY_PROVINCE, KEY_CITY, KEY_ORG, and KEY_EMAIL 

source ./vars
./clean-all 
# Don't run clean-all after this time...

./build-ca
./build-key-server server
./build-dh

# Certificate create & revoke & create CRL
./build-key client1
./revoke-full client1

cd /etc/openvpn/easy-rsa/2.0/keys
cp ca.crt ca.key dh2048.pem server.crt server.key crl.pem /etc/openvpn

3. Configuration for PAM-plugin

groupadd -g 180 openvpn
vi /etc/pam.d/openvpn

auth    required        pam_env.so
auth    required        pam_unix.so        shadow    nodelay
auth    requisite       pam_succeed_if.so  uid >= 1000 quiet
auth    requisite       pam_succeed_if.so  user ingroup openvpn quiet
auth    required        pam_tally2.so      deny=5 lock_time=2 unlock_time=1200 even_deny_root audit
account required        pam_unix.so
account required        pam_tally2.so

4. Configuration for rsyslog

mkdir /var/log/openvpn && chown root:admin /var/log/openvpn && chmod 750 /var/log/openvpn
vi /etc/rsyslog.d/openvpn.conf

if      (($programname == 'openvpn') and ($msg contains 'authentication succeeded')) or \
        (($programname == 'openvpn') and ($msg contains 'pool returned IPv4')) or \
        (($programname == 'openvpn') and ($msg contains 'Inactivity timeout')) or \
        (($syslogfacility-text == 'kern') and ($msg contains 'IN=tun')) \
then    -/var/log/openvpn/openvpn-user.log

if      ($programname == 'openvpn')
then    -/var/log/openvpn/openvpn.log
&       ~

service rsyslog restart

5. Configure SuSEfirewall

vi /etc/sysconfig/SuSEfirewall2

FW_CUSTOMRULES="/etc/sysconfig/scripts/SuSEfirewall2-custom


vi /etc/sysconfig/scripts/SuSEfirewall2-custom

fw_custom_after_finished() {

    # openvpn config
    # 2014-12 Aiden
    
    /usr/sbin/iptables -t nat -D POSTROUTING -o tun0 -j MASQUERADE
    /usr/sbin/iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -o eth0 -j MASQUERADE

    /usr/sbin/iptables -N forward_openvpn
    /usr/sbin/iptables -A forward_openvpn -m conntrack --ctstate NEW -m limit --limit 3/min -j LOG --log-prefix "SFW2-FWDvpn-REJECT_DEFLT " --log-tcp-options --log-ip-options
    /usr/sbin/iptables -A forward_openvpn -j reject_func

    /usr/sbin/iptables -D FORWARD -i tun0 -j forward_ext
    /usr/sbin/iptables -I FORWARD 4 -i tun0 -o eth0 -j forward_openvpn    # before LOG


    /usr/sbin/iptables -I INPUT -i tun0 -p tcp -m tcp --dport 22 -j ACCEPT
    /usr/sbin/iptables -I INPUT 2 -i tun0 -j DROP
    /usr/sbin/iptables -D forward_ext -i tun0 -o eth0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    /usr/sbin/iptables -I forward_ext 1 -i eth1 -m conntrack --ctstate NEW -j DROP
    /usr/sbin/iptables -D forward_int -i eth0 -o tun0 -m conntrack --ctstate NEW,RELATED,ESTABLISHED -j ACCEPT
    /usr/sbin/iptables -I forward_int 10 -i eth0 -o tun0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT    # before DROP
    
    /usr/sbin/iptables -N openvpn_subnet
    /usr/sbin/iptables -A openvpn_subnet -p icmp -m conntrack --ctstate RELATED,ESTABLISHED -m icmp --icmp-type 0 -j ACCEPT
    /usr/sbin/iptables -A openvpn_subnet -p icmp -m conntrack --ctstate RELATED,ESTABLISHED -m icmp --icmp-type 3 -j ACCEPT
    /usr/sbin/iptables -A openvpn_subnet -p icmp -m conntrack --ctstate RELATED,ESTABLISHED -m icmp --icmp-type 11 -j ACCEPT
    /usr/sbin/iptables -A openvpn_subnet -p icmp -m conntrack --ctstate RELATED,ESTABLISHED -m icmp --icmp-type 12 -j ACCEPT
    /usr/sbin/iptables -A openvpn_subnet -p icmp -m conntrack --ctstate RELATED,ESTABLISHED -m icmp --icmp-type 14 -j ACCEPT
    /usr/sbin/iptables -A openvpn_subnet -p icmp -m conntrack --ctstate RELATED,ESTABLISHED -m icmp --icmp-type 18 -j ACCEPT
    /usr/sbin/iptables -A openvpn_subnet -p icmp -m conntrack --ctstate RELATED,ESTABLISHED -m icmp --icmp-type 3/2 -j ACCEPT
    /usr/sbin/iptables -A openvpn_subnet -p icmp -m conntrack --ctstate RELATED,ESTABLISHED -m icmp --icmp-type 5 -j ACCEPT
    /usr/sbin/iptables -A openvpn_subnet -i tun0 -o eth0 -m conntrack --ctstate NEW -m limit --limit 10/min -j LOG --log-prefix "SFW2-FWDvpn-ACC " --log-tcp-options --log-ip-options
    /usr/sbin/iptables -A openvpn_subnet -i tun0 -o eth0 -m conntrack --ctstate NEW,RELATED,ESTABLISHED -j ACCEPT
    /usr/sbin/iptables -A openvpn_subnet -m pkttype --pkt-type multicast -j DROP
    /usr/sbin/iptables -A openvpn_subnet -m pkttype --pkt-type broadcast -j DROP
    /usr/sbin/iptables -A openvpn_subnet -p tcp -m limit --limit 3/min -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j LOG --log-prefix "SFW2-FWDvpn-DROP-DEFLT " --log-tcp-options --log-ip-options
    /usr/sbin/iptables -A openvpn_subnet -p icmp -m limit --limit 3/min -j LOG --log-prefix "SFW2-FWDvpn-DROP-DEFLT " --log-tcp-options --log-ip-options
    /usr/sbin/iptables -A openvpn_subnet -p udp -m limit --limit 3/min -m conntrack --ctstate NEW -j LOG --log-prefix "SFW2-FWDvpn-DROP-DEFLT " --log-tcp-options --log-ip-options
    /usr/sbin/iptables -A openvpn_subnet -j reject_func

    if [[ -f /etc/openvpn/openvpn-status.log ]]; then
        conns=`/usr/bin/egrep -e "^CLIENT_LIST,.+$" /etc/openvpn/openvpn-status.log`
        while read -r line; do
            if [[ $line =~ ^CLIENT_LIST,([^,]+),[0-9.]+:[0-9]+,([0-9.]+),[0-9]+,[0-9]+,.+$ ]]; then
                /bin/bash /etc/openvpn/learn-address.sh add ${BASH_REMATCH[2]} ${BASH_REMATCH[1]}
            fi
        done <<< "$conns"
    fi
    
    true
}

6. Configure IP setting shell script

vi /etc/openvpn/learn-address.sh

#!/bin/bash
# <author>Aiden</author>
# <modified>2014/12/08</modified>

function update_current_status
{
  /usr/bin/env python /etc/openvpn/update-status.py
}

function get_next_matching_firewall_rule
{
  ip_address=$1
  RULE=`/usr/sbin/iptables -L forward_openvpn -n --line-numbers | grep $ip_address | head -n 1`
}

function drop_rule_from_iptables
{
  rule=$1
  echo "  Drop rule [$rule]"
  line_number=`echo "$rule" | awk '{print $1}'`
  /usr/sbin/iptables -D forward_openvpn $line_number
}

function close_firewall
{
  echo "Closing firewall for [$CLIENTIP]"
  get_next_matching_firewall_rule $CLIENTIP

  while [ -n "$RULE" ]
  do
    drop_rule_from_iptables "$RULE"
    get_next_matching_firewall_rule $ip_address
  done
}

function open_firewall
{
  echo "Opening firewall for $CLIENTCERT @ [$CLIENTIP]"
  SERVICE=`echo $CLIENTCERT| cut -d'-' -f 2`
  case $SERVICE in
    "admin")
      /usr/sbin/iptables -I forward_openvpn -s $CLIENTIP -d 10.1.0.0/16 -j openvpn_subnet
      return 0
      ;;
    "subadmin")
      /usr/sbin/iptables -I forward_openvpn -s $CLIENTIP -d 10.1.1.0/24 -j openvpn_subnet
      /usr/sbin/iptables -I forward_openvpn -s $CLIENTIP -d 10.1.2.0/24 -j openvpn_subnet
      /usr/sbin/iptables -I forward_openvpn -s $CLIENTIP -d 10.1.3.0/24 -j openvpn_subnet
      return 0
      ;;
    "member01")
      /usr/sbin/iptables -I forward_openvpn -s $CLIENTIP -d 10.1.1.0/24 -j openvpn_subnet
      return 0
      ;;
    "member02")
      /usr/sbin/iptables -I forward_openvpn -s $CLIENTIP -d 10.1.2.0/24 -j openvpn_subnet
      return 0
      ;;
    "member03")
      /usr/sbin/iptables -I forward_openvpn -s $CLIENTIP -d 10.1.3.0/24 -j openvpn_subnet
      return 0
      ;;
    *)
      echo "  Error, Unknown certification [$CLIENTCERT]"
      return 1
      ;;
  esac
}

# Main
RET=-1
OPERATION=$1
CLIENTIP=$2
CLIENTCERT=$3

if [[ ! $CLIENTIP =~ ^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ ]]; then
  echo "Error, Illegal ip address [$CLIENTIP]"
  exit 1
fi
if [[ ! $CLIENTCERT =~ ^[0-9A-Za-z]{1,10}-[0-9A-Za-z]{1,10}-[0-9]{2}$ ]] && [ $OPERATION != "delete" ]; then
  echo "Error, Illegal certification [$CLIENTCERT]"
  exit 1
fi

case $OPERATION in
  add)
    close_firewall
    open_firewall
    RET=$?
    update_current_status    
    ;;
  update)
    close_firewall
    open_firewall
    RET=$?
    update_current_status    
    ;;
  delete)
    close_firewall
    RET=$?
    update_current_status    
    ;;
  *)
    echo "Error, Unknown operation"
    RET=1
esac

exit $RET


chmod 700 /etc/openvpn/learn-address.sh

7. Status file script and add to cron job

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: set noexpandtab:ts=4

# <author>Aiden</author>
# <modified>2014/12/17</modified>

import os, os.path
import sys
import re
import time
import datetime
import sqlite3 as lite


# Constants

sqlite_file = 'openvpn-client_log'
pid_path = '/var/run/openvpn/server.pid'
status_path = '/etc/openvpn/openvpn-status.log'
sqlite_path = '/var/log/openvpn'
summary_path = '/etc/openvpn/openvpn-status'


# Function definition

def ts2str(timestamp):
	return datetime.datetime.fromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M:%S')
def str2ts(timestr):
	return time.mktime(datetime.datetime.strptime(timestr,'%Y-%m-%d %H:%M:%S').timetuple())


# Main

cur_time = int(time.time())
sqlite_full_path = '{0}-{1}.sqlite'.format(os.path.join(sqlite_path,sqlite_file), datetime.datetime.fromtimestamp(cur_time).strftime('%Y%m'))
client_pat = re.compile(r'^CLIENT_LIST,([^,]+),([0-9.]+):([0-9]+),([0-9.]+),([0-9]+),([0-9]+),([^,]+),([0-9]+),(\w+)$')
route_pat = re.compile(r'^ROUTING_TABLE,([0-9.]+),([^,]+),([0-9.]+):([0-9]+),([^,]+),([0-9]+)$')
global_pat = re.compile(r'^GLOBAL_STATS,[^,]+,([0-9]+)$')

conn = None
client_cnt = 0
route_cnt = 0
bmcast_queue = 0

try:
	
	conn = lite.connect(sqlite_full_path)
	cur = conn.cursor()
	
	cur.executescript("""
		CREATE TABLE IF NOT EXISTS "client_log" ("start_time" DATETIME NOT NULL , "certificate" TEXT NOT NULL , "virtual_ip" TEXT NOT NULL , "account" TEXT, "remote_ip" TEXT, "remote_port" INTEGER, "received" INTEGER NOT NULL  DEFAULT 0, "sent" INTEGER NOT NULL  DEFAULT 0, "end_time" DATETIME, "update_time" DATETIME NOT NULL  DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("start_time", "certificate", "virtual_ip"));
		CREATE TABLE IF NOT EXISTS "status_log" ("time" DATETIME PRIMARY KEY  NOT NULL  UNIQUE  DEFAULT CURRENT_TIMESTAMP, "client_cnt" INTEGER NOT NULL  DEFAULT 0, "route_cnt" INTEGER NOT NULL  DEFAULT 0, "bmcast_queue" INTEGER NOT NULL  DEFAULT 0, "update_time" DATETIME NOT NULL  DEFAULT CURRENT_TIMESTAMP);
		""")
	conn.commit()

	if (os.path.exists(pid_path) and os.path.isfile(pid_path)):

		with open(status_path,'rt') as fd:
			for line in fd:
				client_mat = client_pat.match(line)
				global_mat = global_pat.match(line)
				if client_mat:
					client_cnt += 1
					(cert_name,r_address,r_port,v_address,received,sent,c_time,c_timeval,username) = client_mat.groups()
					cur.execute("INSERT OR REPLACE INTO client_log (start_time,certificate,virtual_ip,account,remote_ip,remote_port,received,sent,end_time,update_time) VALUES ('{0}','{1}','{2}','{3}','{4}',{5},{6},{7},NULL,'{8}')"
								.format(ts2str(c_timeval),cert_name,v_address,username,r_address,r_port,received,sent,ts2str(cur_time)))
				elif route_pat.match(line):
					route_cnt += 1
				elif global_mat:
					bmcast_queue = int(global_mat.group(1))

		cur.execute("UPDATE client_log SET end_time='{0}', update_time='{1}' WHERE end_time IS NULL AND update_time<'{2}'".format(ts2str(cur_time),ts2str(cur_time),ts2str(cur_time)))
		cur.execute("SELECT time, client_cnt,route_cnt,bmcast_queue FROM status_log ORDER BY time DESC LIMIT 1")
		row = cur.fetchone()
		if (row == None) or ((str2ts(row[0])+1800) <= cur_time) or (int(row[1]) != client_cnt) or (int(row[2]) != route_cnt) or (int(row[3]) != bmcast_queue):
			cur.execute("INSERT OR REPLACE INTO status_log (time,client_cnt,route_cnt,bmcast_queue,update_time) VALUES ('{0}',{1},{2},{3},'{4}')".format(ts2str(cur_time),client_cnt,route_cnt,bmcast_queue,ts2str(cur_time)))
		else:
			cur.execute("UPDATE status_log SET update_time='{0}' WHERE time='{1}'".format(ts2str(cur_time),row[0]))
		conn.commit()			

		with open(summary_path,'wt') as sfd:
			sfd.write('connections:{0}\nroutes:{1}\nmaxqueue:{2}'.format(client_cnt,route_cnt,bmcast_queue))

	else:

		os.remove(summary_path) 

except lite.Error, e:
    
    print "Error %s:" % e.args[0]
    if conn:
    	conn.rollback()    
    sys.exit(1)

finally:
    
    if conn:
        conn.close()
chmod 700 /etc/openvpn/update-status.py
mkdir -p /var/log/openvpn


vi /etc/cron.d/update-openvpn-status 

*/1 * * * * root /usr/bin/env python /etc/openvpn/update-status.py 2>&1

chmod 644 /etc/cron.d/update-openvpn-status
service cron restart

8. Configure OpenVPN and Start service

Run yast
Add network device “tun0” type TUN
Assign tun0 to External Network at Firewall

9. Configure OpenVPN and Start service

cp /usr/share/doc/packages/openvpn/sample-config-files/server.conf /etc/openvpn/
vi /etc/openvpn/server.conf

local 210.1.1.1
port 1194
proto udp
;dev tap
dev tun0

ca /etc/openvpn/ca.crt
crl-verify /etc/openvpn/crl.pem
cert /etc/openvpn/server.crt
key /etc/openvpn/server.key
dh /etc/openvpn/dh2048.pem

server 172.16.1.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "route 10.1.0.0 255.255.0.0"
learn-address /etc/openvpn/learn-address.sh
script-security 2
;push "redirect-gateway def1 bypass-dhcp"

comp_lzo
max-clients 20
;user nobody
;group nobody

persist-key
persist-tun

status /etc/openvpn/openvpn-status.log 20
status-version 2

verb 2

# Aiden
syslog openvpn
plugin /usr/lib64/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn
;client-connect /etc/openvpn/client-connect.sh
;client-disconnect /etc/openvpn/client-disconnect.sh


systemctl start openvpn@server.service
systemctl enable openvpn@server.service
chmod o+x /var/run/openvpn

 

∗ Create new client certificate

cd /etc/openvpn/easy-rsa/2.0
source ./vars 
./build-key ZZZZZ-XXXXX-YY

# ........ 

Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]: y

Certificate : /etc/openvpn/easy-rsa/2.0/keys/ZZZZZ-XXXXX-YY.crt
Key file : /etc/openvpn/easy-rsa/2.0/keys/ZZZZZ-XXXXX-YY.key
Root Certificate : /etc/openvpn/easy-rsa/2.0/keys/ca.crt

∗ Revoke client certificate

cd /etc/openvpn/easy-rsa/2.0
source ./vars 
./revoke-full ZZZZZ-XXXXX-YY

# View CRL(Certificate Revocation List)
openssl crl -in keys/crl.pem -text

cp -a keys/crl.pem /etc/openvpn/
systemctl reload openvpn@server.service

∗ Add VPN account

useradd -c "COMPANY NAME" -d /var/lib/nobody -g openvpn -s /bin/false ACCOUNT
passwd ACCOUNT

∗ Check password auto-lock

pam_tally2 --user=ACCOUNT

∗ Reset password auto-lock

pam_tally2 --user=ACCOUNT --reset

 

∗ References

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.