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
- http://openvpn.net/
- http://sourceforge.net/projects/securepoint/
- http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/System_management/SitetoSiteVPN
- http://www.linuxsysadmintutorials.com/setup-pam-authentication-with-openvpns-auth-pam-module/
- http://possiblelossofprecision.net/?p=1746
- http://www.tecmint.com/use-pam_tally2-to-lock-and-unlock-ssh-failed-login-attempts/
- http://encodo.com/en/blogs.php?entry_id=196