Setting up OpenVPN (free community version) on OpenBSD










This document is getting outdated - for the newest version please go to /openvpn-on-openbsd.html

Click here:

"OpenVPN on OpenBSD (EasyRSA3 and LibreSSL)" - openvpn-on-openbsd.html










Go here for the newest version of this document

2014.11 - by Krzysztof "Chris" Pfaff - guide for OpenBSD 5.6, openvpn-2.3.2, EasyRSA3.0.0rc2 (Initially created for OpenBSD 5.5 then updated)
It should also work with future versions - might just need some modifications.

NO WARRANTY, NO RESPONSIBILITY - you are fully responsible for the systems you configure/maintain/change. This guide is only for information purposes. Some errors and non-working commands or unpredicted side-effects are possible.

BSD License applies for this guide. Feel free to distribute, edit, re-use the document as needed, but please acknowledge the author and source of document in the web.


Go here for the newest version of this document

Highlights of this guide:


TABLE OF CONTENT:


Installing OpenVPN

Install package

pkg_add openvpn

could be installed also from ports, but that is not needed

Server-side initial configuration

In short - it is necessary to create:

Going step by step, first prepare the directories. Execute as root:

install -m 700 -d /etc/openvpn/private
install -m 700 -d /etc/openvpn/private-client-conf
install -m 755 -d /etc/openvpn/certs
install -m 755 -d /var/log/openvpn

Prepare dirs and symlinks allowing to run openvpn chrooted or not-chrooted. Some files should be located under chroot jail directory and /etc/openvpn should have symlinks to them (so you can run chrooted or not and it will open the same files):

install -m 755 -d /var/openvpn/chrootjail/etc/openvpn
install -m 755 -d /etc/openvpn/chrootjail/etc/openvpn/ccd  # client custom configuration dir
install -m 755 -d /var/openvpn/chrootjail/var/openvpn
mv /etc/openvpn/ccd/ /etc/openvpn/crl.pem /var/openvpn/chrootjail/etc/openvpn/
ln -s /var/openvpn/chrootjail/etc/openvpn/crl.pem /etc/openvpn/crl.pem
ln -s /var/openvpn/chrootjail/etc/openvpn/ccd/ /etc/openvpn/
ln -s /var/openvpn/chrootjail/etc/openvpn/replay-persist-file /etc/openvpn/replay-persist-file

ls -alpd /etc/openvpn/certs /etc/openvpn/ccd /var/openvpn/chrootjail /var/openvpn/chrootjail/etc /var/openvpn/chrootjail/etc/openvpn /etc/openvpn/private /etc/openvpn/private-client-conf /var/log/openvpn

The output should look like this:

  lrwxr-xr-x  1 root  bin     40 Nov 30 09:52 /etc/openvpn/ccd -> /var/openvpn/chrootjail/etc/openvpn/ccd/
  drwxr-xr-x  2 root  wheel  512 Nov 23 11:51 /etc/openvpn/certs/
  drwx------  2 root  wheel  512 Nov 23 11:50 /etc/openvpn/private/
  drwx------  2 root  bin    512 Nov 25 20:55 /etc/openvpn/private-client-conf/
  drwxr-xr-x  2 root  wheel  512 Mar  4  2013 /var/log/openvpn/
  drwxr-xr-x  4 root  wheel  512 Nov 30 09:51 /var/openvpn/chrootjail/
  drwxr-xr-x  3 root  wheel  512 Nov 30 09:46 /var/openvpn/chrootjail/etc/
  drwxr-xr-x  3 root  wheel  512 Nov 30 09:25 /var/openvpn/chrootjail/etc/openvpn/

Generating CA and Server certificates and keys with EasyRSA

Install EasyRSA 3

Guide below is using easy-rsa 3. It is no longer included in openvpn package, needs to be downloaded separately
EasyRsa can be downloaded from https://github.com/OpenVPN/easy-rsa/releases

Create directory for easyrsa

install -m 700 -d /etc/openvpn/easy-rsa /etc/openvpn/easy-rsa/3

Put (untar) easy-rsa-3 in /etc/openvpn/easy-rsa/3 then execute:

cd /etc/openvpn/easy-rsa/3/
ls -alpd easyrsa vars*
less vars.example 

Review the vers.example, but defaults should be ok. Make sure that certificate lifetime is set to at least 10 years (unless you want to regenerate them more often):

  #set_var EASYRSA_CA_EXPIRE      3650
  #set_var EASYRSA_CERT_EXPIRE    3650

If it is necessary to change the parameters - then create vars file:

  test -f vars || cp vars.example vars
  vi vars  # set organization parameters, but defaults should be ok. Make sure:

Updating easyrsa

Depending what has changed in easyrsa - unpack the whole package - or just update important files, eg:

tar czvf /ins/easyrsa.old.tgz /etc/openvpn/easy-rsa/3/
cd /etc/openvpn/easy-rsa/3/
tar tzvf /ins/EasyRSA-3.0.0-rc2.tgz
tar tzvsf +EasyRSA[^/]*/++gp /ins/EasyRSA-3.0.0-rc2.tgz '*/easyrsa' '*/openssl*.cnf' '*/vars.example' '*/x509-types'
tar xzvsf +EasyRSA[^/]*/++gp /ins/EasyRSA-3.0.0-rc2.tgz '*/easyrsa' '*/openssl*.cnf' '*/vars.example' '*/x509-types'

Optional - fixing error "Missing or invalid OpenSSL"

If you get an error with easyrsa like Missing or invalid OpenSSL or Expected to find openssl command at: openssl then search forward in this document for Appendix A - fixing error Missing or invalid OpenSSL

That was needed in November 2014 with OpenBsd 5.6 and EasyRSA-3.0.0-rc2

Optional - migrate easy-rsa2.0 certificates to easyrsa3

If you have certificates and keys and client certificates created by easy-rsa2 then you might want to use the existing CA and certificates (because using new CA would make old client certificates useless - and they would need to be regenerated).

In such case search forward in this document for Appendix B - migrate EasyRsa2 certificates to EasyRsa3

Generate ta.key for additional security beyond SSL/TLS, protects from UDP flood.

ls -alp /etc/openvpn/private/vpn-ta.key || openvpn --genkey --secret /etc/openvpn/private/vpn-ta.key

Initialize pki

./easyrsa --batch=0 init-pki # creates empty dirs pki/ pki/private/ pki/reqs/ . batch=1 - overwrite/delete without asking
ls -alpd pki pki/*

Create dh parameters

./easyrsa --batch=1 gen-dh
ls -alpd pki/dh.pem

Create ca and verify

./easyrsa --batch=1 --req-cn=vpn-ca build-ca nopass
ls -alpd pki/ca.crt pki/private/ca.key pki/index.txt pki/serial
openssl x509 -in pki/ca.crt -text -noout
openssl rsa -in pki/private/ca.key -check -noout

Generate server request

alternatively - vpnserver admin does ./easyrsa init-pki; ./easyrsa gen-req _UNIQUE_SERVER_SHORT_NAME_ and send the .req to CA just to sign/accept.

./easyrsa --batch=1 --req-cn=vpnserver gen-req vpnserver nopass
ls -alpd pki/reqs/vpnserver.req pki/private/vpnserver.key
openssl req -in pki/reqs/vpnserver.req -text -noout
openssl rsa -in pki/private/vpnserver.key -check -noout

review and sign the server request

./easyrsa --batch=1 show-req vpnserver
./easyrsa --batch=1 sign server vpnserver # server - only for server
ls -alpd pki/issued/vpnserver.crt pki/certs_by_serial/01.pem 
openssl x509 -in pki/issued/vpnserver.crt -text -noout
echo "Last added cert in db: `cat pki/index.txt|tail -1`"
echo "Next added cert will have number: `cat pki/serial`"

Create empty CRL - Certificate Revocation List.

Revoked certificates can be added later. Initially creating file with no revocated certificates. This file must exist, otherwise openvpn will complain.

./easyrsa --batch=1 gen-crl
# make sure openvpn process can read this file - otherwise it will crash
chown :_openvpn pki/crl.pem; chmod g+r pki/crl.pem 
ls -alF pki/crl.pem
openssl crl -in pki/crl.pem -text -noout

Copy final certs/keys to openvpn config locations (or set them directly in openvpn config)

cp -p pki/ca.crt /etc/openvpn/certs/vpn-ca.crt
cp -p pki/private/ca.key /etc/openvpn/private/vpn-ca.key
cp -p pki/issued/vpnserver.crt /etc/openvpn/certs/vpnserver.crt
cp -p pki/private/vpnserver.key /etc/openvpn/private/vpnserver.key
cp -p pki/dh.pem /etc/openvpn/dh.pem
cp -p pki/crl.pem /etc/openvpn/crl.pem

check certificate data

openssl x509 -in /etc/openvpn/certs/vpn-ca.crt -text -noout
openssl x509 -in /etc/openvpn/certs/vpnserver.crt -text -noout
openssl crl -in /etc/openvpn/crl.pem -text -noout
openssl rsa -in /etc/openvpn/private/vpn-ca.key -check -noout
openssl rsa -in /etc/openvpn/private/vpnserver.key -check -noout

Setup password for management interface

OpenVpn had management interface where admin can connect and check status, active clients, etc. It is nice feature, but the communication is clear text - so do NOT make it available from the network, but only internally for admin on localhost. Limit it on the firewall. Then later, when openvpn is running, connect with ssh to the openvpn server and then connect to openvpn management console using telnet localhost 1195

In the next step the password for such interface will be set-up.

test -f /etc/openvpn/private/mgmt.pwd || touch /etc/openvpn/private/mgmt.pwd
chown root:wheel /etc/openvpn/private/mgmt.pwd; chmod 600 /etc/openvpn/private/mgmt.pwd
vi /etc/openvpn/private/mgmt.pwd  # insert one line of text with clear-text password for management console

Set the password in this file - insert one line of text with clear-text password for management console. You can note the password in safe place - or - if you forget it, look inside this file, password is stored in clear text.

Setup OpenVpn server config

In this example openvpn will run on tun0 with IPs in range 10.8/16 and will provide access to local network 192.168.0/24. Server config filename is server_tun0.conf

test -f /etc/openvpn/server_tun0.conf || touch /etc/openvpn/server_tun0.conf
chown root:_openvpn /etc/openvpn/server_tun0.conf; chmod 640 /etc/openvpn/server_tun0.conf
vi /etc/openvpn/server_tun0.conf  # setup OpenVpn server

Edit the config file according to your needs. The openvpn's example/default server.conf itself contains quite nice descriptions.
You can use the example described below for setting of parameters - this list all parameters that I changed.
You can use that as server_tun0.conf - or edit your server_tun0.conf using below as an example:

cat /etc/openvpn/server_tun0.conf | grep '^[^#;]' | grep CUSTOMIZED

vi /etc/openvpn/server_tun0.conf

ca /etc/openvpn/certs/vpn-ca.crt  # CUSTOMIZED by adminOfMine #
cert /etc/openvpn/certs/vpnserver.crt  # CUSTOMIZED by adminOfMine #
key /etc/openvpn/private/vpnserver.key  # CUSTOMIZED by adminOfMine #
dh /etc/openvpn/dh.pem  # CUSTOMIZED by adminOfMine #
ifconfig-pool-persist /var/openvpn/ipp.txt  # CUSTOMIZED by adminOfMine # put it to var as it is dynamic file
tls-auth /etc/openvpn/private/vpn-ta.key 0 # This file is secret  # CUSTOMIZED by adminOfMine #
replay-persist /etc/openvpn/replay-persist-file  # CUSTOMIZED by adminOfMine #
max-clients 100  # CUSTOMIZED by adminOfMine #
status /var/log/openvpn/openvpn-status.log  # CUSTOMIZED by adminOfMine #
log-append  /var/log/openvpn/openvpn.log  # CUSTOMIZED by adminOfMine #
proto tcp  #or use udp if works better # CUSTOMIZED by adminOfMine #
port 1194 # CUSTOMIZED by adminOfMine #
management 127.0.0.1 1195 /etc/openvpn/private/mgmt.pwd  # CUSTOMIZED by adminOfMine #
daemon openvpn  # CUSTOMIZED by adminOfMine #
chroot /var/openvpn/chrootjail  # CUSTOMIZED by adminOfMine #
crl-verify /etc/openvpn/crl.pem  # CUSTOMIZED by adminOfMine #
float        # CUSTOMIZED by adminOfMine #
persist-key  # CUSTOMIZED by adminOfMine #
persist-tun  # CUSTOMIZED by adminOfMine #
#Additional authorization options - needs to be configured - read further before enabling this
;auth-user-pass-verify /var/openvpn/custom-simple-auth via-env  # CUSTOMIZED by adminOfMine #
;script-security 3  # CUSTOMIZED by adminOfMine #
# The following options were set by default 
keepalive 10 120
comp-lzo
user _openvpn
group _openvpn
verb 3
# If you would prefer to start openvpn directly and NOT via /etc/hostname - then you need to edit and uncomment those parameters
;dev tun0  # CUSTOMIZED by admin-of-mine #
;server 10.8.0.0 255.255.255.0  # Address range for the tun(4)interfaces  # CUSTOMIZED by adminOfMine #
# Add a route to the local network to the client's routing table
;push "route 192.168.0.0 255.255.255.0"  # CUSTOMIZED by adminOfMine #
# If you want to push DNS server setting to VPN clients - uncomment below and see openvpn documentation for more info
;push "dhcp-option DNS 192.168.0.x"   # CUSTOMIZED by adminOfMine #
# If you use samba and want to have wins (windows netbios) name resolution then add WINS (samba nmbd) server IP
;push "dhcp-option WINS 192.168.0.x"  # CUSTOMIZED by adminOfMine #

Edit firewall rules

The example below allows vpn clients to access the whole internal network and gives limited access to the openvpn-host.

It is not complete pf.conf, it is just showing example openvpn related entries.
Adjust the file to your configuration and needs. Restrict the rules if necessary.

    log_opt = "log" # Set to ="log" to enable pflog for rulesfiles that use this macro. Setting to ="" will do no logging to pflog

    int_if="em0"
    tun_if="tun0"   # open-vpn interface
    ext_if="re0"    # external connections are forwarded to this interface
    allowIncoming_ifs="{" $int_if $tun_if "}"  # interfaces where (internal) incoming traffic is allowed

    table <myself> const { self }
    table <myBroadcast> const { self:broadcast }
    table <private> const { 10/8, 172.16/12, 192.168/16 }
    table <internet> const {0.0.0.0/0, !10/8, !172.16/12, !192.168/16 }

    vpnNetwork="10.8/16"
    table <vpnNet> const { $vpnNetwork }
    table <allowedIncomingLan> const { self:network $vpnNetwork }
    table <allowedIncomingExternal> const { 0/0 }   # these will be public IPs from the outside

    SpecialAddressReachableViaVpn="192.168.99.xx"
    lan_tcp_services="{ 22, 53, 80, 139, 445, 113, 1194 }"
    lan_udp_services="{ 53, 137:138,  1194 }"

    #... other rules, scrub, antispoof, etc.

## From LAN and VPN  to OPENVPN-SERVER -- INCOMING
   # allow incoming TCP to certain services - including OPENVPN port 1194
    pass  in  quick $log_opt on $allowIncoming_ifs proto tcp from <allowedIncomingLan> to <myself> port $lan_tcp_services  label "incomingToHost"
   # allow incoming UDP to certain services - including OPENVPN port 1194:
    pass  in  quick $log_opt on $allowIncoming_ifs proto udp from <allowedIncomingLan> to <myself> port $lan_udp_services  label "incomingToHost"
    pass  in  quick $log_opt on $allowIncoming_ifs proto udp from <allowedIncomingLan> to <myBroadcast> port { 137:138 }   # netbios name/datagram broadcasts
   # allow incoming ping
    pass  in  quick $log_opt on $allowIncoming_ifs inet proto icmp from { <allowedIncomingLan> } to <myself> icmp-type 8 code 0  label "icmp-in"
   # anything else is explicitly blocked here (so that rules allowing for routing and vpn will not allow what is not wanted)
    # block access to this host - whatever was not matched earlier
    block in  quick $log_opt on $allowIncoming_ifs from { <allowedIncomingLan> } to <myself>


## From LAN (VPN) to LAN  --  TRAVERSING - INBOUND
   # allow VPN access the LAN
    # Note: there is no network defined on $tun_if, so cannot use syntax like: pass ... from ($tun_if:network:0) to ...
    pass  in  quick $log_opt on $tun_if from <vpnNet> to ($int_if:network) label "vpn-in"
    pass  in  quick $log_opt on $tun_if from <vpnNet> to $SpecialAddressReachableViaVpn label "vpn-in-special"


## From OPENVPN-SERVER -- OUTGOING  - and -  From LAN (VPN) to LAN  --  TRAVERSING - OUTBOUND
   # allow and do NAT for VPN - the VPN clients will connect to other hosts in internal network using IP of this host as source IP
   # nat is not needed if the openvpnserver is the default router for all computers on LAN.
    pass  out quick $log_opt on $int_if to ($int_if:network) received-on $tun_if nat-to ($int_if:0) label "vpn-out-to-network"

   # INTERNET -- OUTGOING - allow LAN hosts to reach internet, etc. Do modulate state for tcp.
    ##match out on egress inet from !(egress:network) to any nat-to (egress:0)
    pass  out quick $log_opt on $ext_if from <hostsAllowedToAccessInternet> to <internet> nat-to ($ext_if:0) modulate state label "lan-out-to-internet"
    pass  out quick $log_opt on $tun_if from <vpnNet> to $SpecialAddressReachableViaVpn label "vpn-out-special"


## INTERNET -- INCOMING -- CAREFULLY
   # allow incoming connection from the internet - allow to connect to OPENVPN on UDP and TCP
    pass  in  quick $log_opt on $allowIncomingExternal_ifs proto udp from <allowedIncomingExternal> to <myself> port { 1194 } label "incoming-external:$srcaddr"
    pass  in  quick $log_opt on $allowIncomingExternal_ifs proto tcp from <allowedIncomingExternal> to <myself> port { 1194 } label "incoming-external:$srcaddr"
    block $log_opt

Enable routing on openvpn server

make sure you have firewall correctly setup to block unwanted traffic

vi /etc/sysctl.conf and enable ip forwarding - change corresponding line to:

net.inet.ip.forwarding=1

The change above will activate only after next reboot, so start ip-forwarding also in currently running system with command:
sysctl net.inet.ip.forwarding=1

First start of openvpn

Execute (adjust parameters if necessary):

/usr/local/sbin/openvpn --daemon --config /etc/openvpn/server_tun0.conf --dev tun0 --server 10.8.0.0 255.255.255.0 --push "route 192.168.0.0 255.255.255.0"

Check:

If something goes wrong, try with

/usr/local/sbin/openvpn --daemon --verb 11 --config /etc/openvpn/server_tun0.conf

and check logs.

Stop openvpn after first start

list running processes: pgrep -fl openvpn pgrep -xl openvpn

stop all running openvpn processes: pkill -x openvpn

Setup openvpn startup on system boot

It is recommended to start openvpn from /etc/hostname.* file. Also IP addresses and other network parameters are normally configured in /etc/hostname.*, so it would be nice to configure it there. On the other hand - it is also comfortable to use rc scripts to start and stop components.

The Solution presented below will combine both. The openvpn will be started and partially configured from /etc/hostname.*. And rc script can be used for starting and stopping openvpn. The rc script will use /etc/hostname.* files to start openvpn(s). Multiple openvpns can be started - with different config - running either as server or client.

Startup step 1 - /etc/hostname.*

Example /etc/hostname.tun for openvpn server

Define openvpn tunnels in /etc/hostname.* following the example below

vi /etc/hostname.tun0

up
group openvpn
description "OpenVPN to local net 192.168.0.x"
!/usr/local/sbin/openvpn --daemon --config /etc/openvpn/server_$if.conf --dev $if --server 10.8.0.0 255.255.255.0 --push "route 192.168.0.0 255.255.255.0" & false
# If you want to push DNS server setting to VPN clients (see openvpn documentation for more info) then add to openvpn parameters above:
#   --push "dhcp-option DNS 192.168.0.x"
# If you use samba and want to have wins (windows netbios) name resolution then add WINS (samba nmbd) server IP the add above also:
#   --push "dhcp-option WINS 192.168.0.x"
#
# WARNING:
# BOOTING process can be blocked, sshd not started and remote access blocked if this script fails!!!
# If you change anything in script, test with: sh /etc/netstart tunX;  echo "exitCode=$?"
# Replace tunX with proper network interface name. Verify that process started.
# Make sure script runs non-interactively and the exit code is 0. 
# Otherwise it will interrupt booting process!!!
# Use '& false' at the end. The '&' prevents interactive data input, eg passwords. 
# The 'false' command makes sure that no error status code will be returned.
# do not use exit command in here - or rc booting would be interrupted.
#
# You can use variable $if = interface, eg $if=tun0, so you can use --config /etc/openvpn/server_$if.conf
#
# The file /etc/openvpn/server_$if.conf must exist
# If you change binary name or --daemon option in command above - also change matching pattern in /etc/rc.d/openvpn script
# 
#
# In case you need more /dev/tun devices, eg tun4, do: cd /dev && { ./MAKEDEV tun4; cd - ; }
# For debug - you can add/uncomment command like:
# !echo "Starting openvpn on $if"
#

Note that --dev and --server and --push route options are defined directly in /etc/hostname file, so that network config is listed in hostname file. It is also possible to move those options into openvpn server.conf if preferred.

WARNING: It is important to add || false so that even if openvpn fails to start, the booting process will continue. Without this command, in case of error (eg. during system update when openvpn package was temporarily uninstalled) - the rc script would switch to admin console during booting - blocking startup of ssh and further network. And that could be very bad on remotely-managed systems.
Also, use || false and do not try to use exit command in /etc/hostname as it would interrupt execution of rc script during startup and sshd would not be started.

Example /etc/hostname.tun for openvpn client

and example for client openvpn to be started during startup or via rc script (but make sure it needs no manual entering of password):

vi /etc/hostname.tun3

up
group openvpnclient
description "OpenVPN client to local openvpn server"
!/usr/local/sbin/openvpn --daemon --config /etc/openvpn/vpnclient-unixlocaltest/vpnclient-unixlocaltest.conf.ovpn --dev $if & false
#!/usr/local/sbin/openvpn --daemon --config /etc/openvpn/server_$if.conf --dev $if --server 10.8.0.0 255.255.255.0 --push "route 192.168.0.0 255.255.255.0" & false
#
# WARNING: 
# BOOTING process can be blocked, sshd not started and remote access blocked if this script fails!!!
# If you change anything in script, test with: sh /etc/netstart tunX;  echo "exitCode=$?"
# Replace tunX with proper network interface name. Verify that process openvpn started.
# Make sure script run NON-interactively and the exit code is 0. 
# Otherwise it will interrupt booting process!!!
# Use '& false' at the end. The '&' prevents interactive data input, eg passwords. 
# The 'false' command makes sure that no error status code will be returned.
# do not use exit command in here - or rc booting would be interrupted.
#
# You can use variable if = interface, eg if=tun0, so you can use --config /etc/openvpn/server_$if.conf
#
# The file /etc/openvpn/server_$if.conf must exist
# If you change binary name or --daemon option in command above - also change matching pattern in /etc/rc.d/openvpn script
# 
#
# In case you need more /dev/tun devices, eg tun4, do: cd /dev && { ./MAKEDEV tun4; cd - ; }
# For debug - you can add/uncomment command like:
# !echo "Starting openvpn on $if"
#

Once this is configured then:

Startup step 2a - /etc/rc.d/openvpn - OPTIONAL

This is optional step - if you want to use /etc/rc.d/openvpn script to start or stop VPNs then continue.

The openvpn script in rc framework can be used to start/stop multiple openvpns.

Create /etc/rc.d/openvpn
In case file exists - review it
If such file does not exist - create it as shown below.

cat /etc/rc.d/openvpn && echo && echo "**** File already exists - just review if it is OK"
vi /etc/rc.d/openvpn

The script will act in similar way like normal rc scripts.
The script might need adjustments in Openbsd versions newer than 5.6, especially if /etc/rc.d/rc.subr has changed. Correct the script if necessary
Put the following content to /etc/rc.d/openvpn:

#!/bin/sh
#
# openvpn rc.d script for OpenBSD - by Chris Pfaff
#
# This script acts like normal rc script if the openvpn_flags in rc.conf.local are set to typical openvpn parameters
# But if openvpn_flags are set to special string "-use-etc-hostname-" then script acts in special way:
#  It starts the openvpns configured in /etc/hostname.if (eg in /etc/hostname.tun0)
#  It will start multiple openvpn - each with its own config defined in /etc/hostname.tun*
#  If script detects that a process is already running - it does not try to start again but returns with ok status
#  It supports usual parameters like: start, check, stop, restart, reload
#
# -d = debug mode - use to see verbose information about executed commands
#


daemon="/usr/local/sbin/openvpn"

. /etc/rc.d/rc.subr

if [ "$daemon_flags" != "-use-etc-hostname-" ]; then 
    # handle as usual
    rc_cmd $1
else
    #special unusual handling
    #start openvpns defined in /etc/hostname.*

    # Edit if necessary
    # This pattern is being used for scanning /etc/hostname.tun*
    # and for finding running openvpn processes
    # and for searching individual openvpn process - then patter is appended with: [^a-zA-Z]${hnif}([^0-9]|$)
    processpattern='openvpn.*--daemon.*'

    [ -n "${_RC_DEBUG}" ] || _n="-n"
    PGREPFLAG="-f"
    PKILLFLAG="-f"
    [ -z "${_RC_DEBUG}" ] && PKILLFLAG="$PGREPFLAG -q"  # quiet if not debug mode
    [ -z "${_RC_DEBUG}" ] && PGREPFLAG="$PGREPFLAG -q"  # quiet if not debug mode
    [ -n "${_RC_DEBUG}" ] && PGREPFLAG="$PGREPFLAG -l"  # list process name in debug mode
    case "$1" in
    start|check)
        [ "$1" == "start" ] && echo $_n "${INRC:+ }${_name} "
        if [ X"${daemon_flags}" = X"NO" ]; then
            _rc_err "$0: need -f to force $1 since ${_name}_flags=NO"
            exit 1
        fi
        allprocstatus=0
        # search all hostname.tun* files which contain openvpn startup command
        hostnamepattern="/etc/hostname.tun*"
        for hostnamefile in $hostnamepattern; do
            if [ "$hostnamefile" = "$hostnamepattern" ]; then
                [ -n "${_RC_DEBUG}" ] && echo "no files $hostnamepattern"
                [ "$2" == "quiet" ] && exit 1
                _rc_exit failed
            fi
            [ -n "${_RC_DEBUG}" ] && echo
            if grep -v "^[  ]*#" "$hostnamefile" | grep -q "$processpattern" ; then
                [ -n "${_RC_DEBUG}" ] && echo "File $hostnamefile is openvpn file - will be started/checked"
            else
                [ -n "${_RC_DEBUG}" ] && echo "File $hostnamefile is not related to openvpn - skipping"
                continue
            fi
            # extract interface name from filename
            hnif=${hostnamefile##/etc/hostname\.}
            if [ -z "$hnif" ]; then
                [ -n "${_RC_DEBUG}" ] && echo "Cannot get interface name from $hostnamefile"
                hnifstatus=1
                allprocstatus=1
                continue
            fi

            # check if process is already running for this network interface - if yes, do nothing and result is ok 
            hnifprocesspattern="${processpattern}[^a-zA-Z]${hnif}([^0-9]|$)"  # eg server_tun0.conf or client_tun0.conf or -dev tun0
            [ -n "${_RC_DEBUG}" ] && echo "Executing: pgrep ${PGREPFLAG} \"$hnifprocesspattern\""
            pgrep ${PGREPFLAG} "$hnifprocesspattern" && continue

            if [ "$1" == "check" ]; then
                [ -n "${_RC_DEBUG}" ] && echo "Missing openvpn process for ${hnif} - see $hostnamefile"
                hnifstatus=1
            else
                echo -n "$hnif "  # write name of started if
                # if process is not running for this interface - start it
                [ -n "${_RC_DEBUG}" ] && echo "Executing: /bin/sh /etc/netstart $hnif "
                /bin/sh /etc/netstart $hnif; hnifstatus=$?
                [ -n "${_RC_DEBUG}" ] && echo " status: $hnifstatus"  # etc/hostname scripts may return 0 even if error occurred
            fi
            [ $hnifstatus -ne 0 ] && allprocstatus=1
        done
        # if ANY failed then return failure - (simple exit if check in quiet mode)
        [ "$2" == "quiet" -a $allprocstatus -ne 0 ] && exit 1
        [ $allprocstatus -ne 0 ] && _rc_exit failed
        [ "$2" == "quiet" ] && exit 0
        _rc_exit ok
        ;;
    stop)
        echo $_n "${INRC:+ }${_name} "
        [ -n "${_RC_DEBUG}" ] && echo "Executing: pkill ${PKILLFLAG} \"$processpattern\""
        pkill ${PKILLFLAG} "$processpattern"
        countdown=10
        while [ "((countdown--))" -gt 0 ]; do
            pgrep ${PKILLFLAG} "$processpattern" >/dev/null || continue
            sleep 1;
        done;
        [ -n "${_RC_DEBUG}" ] && echo "Executing: $0 ${_RC_DEBUG} ${_RC_FORCE} check quiet"
        $0 ${_RC_DEBUG} ${_RC_FORCE} check quiet && _rc_exit failed
        _rc_exit ok
        ;;
    reload)
        echo $_n "${INRC:+ }${_name} "
        [ -n "${_RC_DEBUG}" ] && echo "WARNING: Reload might crash the process if process is chrooted"
        [ -n "${_RC_DEBUG}" ] && echo "Executing: pkill -HUP ${PKILLFLAG} \"$processpattern\""
        pkill -HUP ${PKILLFLAG} "$processpattern" || _rc_exit failed
        sleep 10;
        [ -n "${_RC_DEBUG}" ] && echo "Executing: $0 ${_RC_DEBUG} ${_RC_FORCE} check quiet"
        [ -n "${_RC_DEBUG}" ] && echo "WARNING: Reload might crash the process if process is chrooted"
        $0 ${_RC_DEBUG} ${_RC_FORCE} check quiet || _rc_exit failed
        _rc_exit ok
        ;;
    restart)
        $0 ${_RC_DEBUG} ${_RC_FORCE} stop &&
            $0 ${_RC_DEBUG} ${_RC_FORCE} start
        ;;
    *)
        _rc_usage
        ;;
    esac
fi

Startup step 2b - /etc/rc.conf.local - OPTIONAL

The /etc/rc.d/openvpn rc script works only if openvpn_flags are defined in rc.conf.local

In order to have the rc openvpn script compatible with /etc/hostname.* files, set the openvpn_flags to special hardcoded value -use-etc-hostname-

Add to /etc/rc.conf.local:

openvpn_flags="-use-etc-hostname-"# then rc script is compatible with /etc/hostname.tunX to start openvpn (or check if they are running).

And add (append) openvpn to variable pkg_scripts. Or if such variable does not exist - add it as in example below:

  pkg_scripts="... openvpn"

In case you do not want to use /etc/hostname files, but only use rc script to run one openvpn process, then instead of special string, put normal startup parameters for openvpn, for example:

  #To run openvpn not from /etc/hostname, but directly from rc script - use line below
  # And make sure parameters like dev, server, push "route...", etc are defined in server.conf
  #openvpn_flags="--daemon --config /etc/openvpn/server.conf --dev tun0"  # if there would be only one openvpn and not started from hostname.if

Generate OpenVPN config for clients - action on the server

prepare template for client configs

First create/edit a client config template: vi /etc/openvpn/private/vpnclient.conf.template
Correct the template below to your needs. Especially change the line remote openbsd.example.com 1194 to the address (host.domain or IP) of your openvpn. This address must be reachable from the outside (from Internet).

##############################################
# Sample client-side OpenVPN 2.0 config file #
# for connecting to multi-client server.     #
#                                            #
# This configuration can be used by multiple #
# clients, however each client should have   #
# its own cert and key files.                #
#                                            #
# On Windows, you might want to rename this  #
# file so it has a .ovpn extension           #
##############################################

# Specify that we are a client and that we
# will be pulling certain config file directives
# from the server.
client

# Use the same setting as you are using on
# the server.
# On most systems, the VPN will not function
# unless you partially or fully disable
# the firewall for the TUN/TAP interface.
;dev tap
;dev tun

# Windows needs the TAP-Win32 adapter name
# from the Network Connections panel
# if you have more than one.  On XP SP2,
# you may need to disable the firewall
# for the TAP adapter.
;dev-node MyTap

# Are we connecting to a TCP or
# UDP server?  Use the same setting as
# on the server.
;proto tcp
;proto udp

# The hostname/IP and port of the server.
# You can have multiple remote entries
# to load balance between the servers.
;remote my-server-1 1194
;remote my-server-2 1194

# Choose a random host from the remote
# list for load-balancing.  Otherwise
# try hosts in the order specified.
;remote-random

# Keep trying indefinitely to resolve the
# host name of the OpenVPN server.  Very useful
# on machines which are not permanently connected
# to the internet such as laptops.
resolv-retry infinite

# Most clients don't need to bind to
# a specific local port number.
nobind

# Downgrade privileges after initialization (non-Windows only)
;user _openvpn
;group _openvpn

# Try to preserve some state across restarts.
persist-key
persist-tun

# If you are connecting through an
# HTTP proxy to reach the actual OpenVPN
# server, put the proxy server/IP and
# port number here.  See the man page
# if your proxy server requires
# authentication.
;http-proxy-retry # retry on connection failures
;http-proxy [proxy server] [proxy port #]

# Wireless networks often produce a lot
# of duplicate packets.  Set this flag
# to silence duplicate packet warnings.
;mute-replay-warnings

# SSL/TLS parms.
# See the server config file for more
# description.  It's best to use
# a separate .crt/.key file pair
# for each client.  A single ca
# file can be used for all clients.
;ca ca.crt
;cert client.crt
;key client.key

# Verify server certificate by checking
# that the certificate has the nsCertType
# field set to "server".  This is an
# important precaution to protect against
# a potential attack discussed here:
#  http://openvpn.net/howto.html#mitm
#
# To use this feature, you will need to generate
# your server certificates with the nsCertType
# field set to "server".  The build-key-server
# script in the easy-rsa folder will do this.
#OBSOLETE# ns-cert-type server # this was replaced by: remote-cert-tls server

# If a tls-auth key is used on the server
# then every client must also have the key.
;tls-auth ta.key 1

# Select a cryptographic cipher.
# If the cipher option is used on the server
# then you must also specify it here.
;cipher x

# Enable compression on the VPN link.
# Don't enable this unless it is also
# enabled in the server config file.
comp-lzo

# Set log file verbosity.
verb 3

# Silence repeating messages
;mute 20


# CUSTOMIZATIONS by adminOfMine # all lines below:

#OBSOLETE# ns-cert-type server # this was replaced by: remote-cert-tls server
# remote-cert-tls server - additional server certificate check, replaces obsolete ns-cert-type server
remote-cert-tls server

# Use those settings for UNIX:
#UNIX# daemon openvpn
#UNIX# user _openvpn
#UNIX# group _openvpn
#UNIX# chroot /var/empty

remote openbsd.example.com 1194
;proto udp
proto tcp
dev tun1
;dev-node TAP

float

auth-user-pass

# tls-auth is provided inline, so skip option tls-auth. But it is necessary to define the direction - using key-direction
key-direction 1

#TEMPLATE# do NOT put certificate config lines into the template
#TEMPLATE# such lines will be added later - as commands with proper filename or as inline

ADD VPN USER = generate CLIENT config/key/certificate

The openvpn certificates/keys/config need to be generated for each user.

Note: If you create a separate openvpn certificate+key not only for each user but also for each device that connects - then it is easier to revoke the certificate (=deny access) for just this one device.

generate certificate-request for client:

alternatively - vpn user himself does something like: ./easyrsa init-pki; ./easyrsa gen-req UNIQUE_CLIENT_SHORT_NAME and sends the cert-request to openvpn ca admin just to sign/accept.

 vpnclientuser=vpn-user-uniq-name
./easyrsa --batch=1 --req-cn=${vpnclientuser} gen-req ${vpnclientuser} nopass
 ls -alpd pki/reqs/${vpnclientuser}.req pki/private/${vpnclientuser}.key
 openssl req -in pki/reqs/${vpnclientuser}.req -text -noout
 openssl rsa -in pki/private/${vpnclientuser}.key -check -noout

openvpn administrator must sign the certificate (on the openvpn serer - with proper CA):

 ./easyrsa --batch=1 show-req ${vpnclientuser}
 ./easyrsa --batch=1 sign client ${vpnclientuser} # client - only for client
 openssl x509 -in pki/issued/${vpnclientuser}.crt -text -noout
 #cp -p pki/issued/${vpnclientuser}.crt /etc/openvpn/certs/${vpnclientuser}.crt   # no need to copy if using in-line
 #cp -p pki/private/${vpnclientuser}.key /etc/openvpn/private/${vpnclientuser}.key # no need to copy if using in-line

copy the prepared-before vpnclient.conf.template to new file, eg:

cp -p /etc/openvpn/private/vpnclient.conf.template /etc/openvpn/private-client-conf/vpnclient-abc.conf.ovpn

And then add individual certificate/keyfiles - as described below

OPTION 1: Add client certificates/keys in one file - recommended

It is possible to embed all ca, certificates and keys into the config file - and then just one file is enough.

To embed all files for ca/cert/key/ta inside config - add the following at the end of the client config file (eg to: /etc/openvpn/private-client-conf/vpnclient-abc.conf.ovpn)

put here the content of real vpn-ca.crt/vpnclient.ca/vpnclient.key/vpn-ta.key

# tls-auth is provided inline, so skip option tls-auth. But it is necessary to define the direction - using key-direction
key-direction 1
<ca>
-----BEGIN CERTIFICATE-----
...content from vpn-ca.crt...
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
...content from vpnclient-abc.crt...
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN RSA PRIVATE KEY-----
...content from vpnclient-abc.key...
-----END RSA PRIVATE KEY-----
</key>
<tls-auth>
...content from vpn-ta.key...
</tls-auth>

Send the *.ovpn config file to the vpn user.

OPTION 2: ca/certificate/key/ta in separate files

add the lines below and remember that vpn usr must get the vpn client config file together with separate ca/certificate/key/ta files.

you might also need to remove line key-direction 1 from config file

Add the following at the end of the client config file (eg to: /etc/openvpn/private-client-conf/vpnclient-abc.conf.ovpn):

# tls-auth needs to have direction. For inline certificates it would need to be replaced with key-direction 1
tls-auth vpn-ta.key 1
ca vpn-ca.crt
cert vpnclient.crt
key vpnclient.key

Send the *.ovpn config file and all the files listed in block above to the vpn user.

DELETE VPN USER = revoking client certificate

Example set of commands (for easy-rsa3):

The target certificate must be still existing on the file under easy-rsa, eg in /etc/openvpn/easy-rsa/3/pki/issued/vpnclient-olduser.crt. Keyfile for this user is not needed, but certificate must be present

To revoke (block, disallow) the certificate:

cd /etc/openvpn/easy-rsa/3; 
./easyrsa revoke vpnclient-olduser
./easyrsa gen-crl

after running command above you should see message like Revoking Certificate 04. Data Base Updated

verify that certificate as revoked in pki/index.txt

egrep "vpnclient-olduser" pki/index.txt

gives something like: R 240425145061Z 140428150632Z 05 unknown /CN=vpnclient-olduser

make sure openvpn process can read this file - otherwise it will crash:

chown :_openvpn pki/crl.pem; chmod g+r pki/crl.pem 

make sure CRL is being used in openvpn's server.conf:

egrep '^ *crl-verify.*crl.pem' /etc/openvpn/server.conf

Copy file to location listed in openvpn's server.conf. if you use chroot - copy crl.pem to location visible after chrooting (/etc/openvpn/crl.pem might be a symlink - do not change that)

cp -p pki/crl.pem /etc/openvpn/crl.pem
ls -alF `pwd`/pki/crl.pem /etc/openvpn/crl.pem /var/openvpn/chrootjail/etc/openvpn/crl.pem  # all files should be the same
openssl crl -in /etc/openvpn/crl.pem -text

After you update the CRL file, make sure openvpn process is able to read it.

Test if revoked user is no longer able to connect.
Then test if valid (not-revoked) users can still connect, to make sure everything is ok.


CONFIGURE OPENVPN CLIENTS - Client side installation and configuration (Win/Mac/Unix/Linux)

The creation of config/keys/certificates has been described above in ADD VPN USER = generate CLIENT config/key/certificate

In order to proceed, you need to have the *.ovpn config file with certificates/keyfiles/ta - either embedded in *.ovpn or as separate files (then put these separate files in the same directory as *.ovpn)

Adjust client configuration parameters

Verify *.ovpn and adjust to your system. Especially check the parameters:

An example content of *.ovpn can be something like (below is just an example - adjust the config for each client if necessary):

client
resolv-retry infinite
nobind
persist-key
persist-tun
comp-lzo
verb 3
# remote-cert-tls server - additional server certificate check, replaces obsolete ns-cert-type server
remote-cert-tls server
remote  <server IP or vpnserver.domain.com> 1194  # set proper address of VPN server
proto udp  # tcp or udp. Make sure server-side also supports the protocol
dev tun1   # adjust device name here.
float  # check if/how that works for float. Consider disabling persist-key/tun when having problems with reconnect from different IP.
# ask for username/password
auth-user-pass   # disable if not required by server
  # other options to be considered if needed:
  # auth-user-pass # ask for username/password
  # auth-user-pass /etc/openvpn/user-pass.auth
  # auth-retry nointeract
#Uncomment lines marked with #UNIX# if you run on unix system
#UNIX# daemon openvpn
#UNIX# user _openvpn
#UNIX# group _openvpn
#UNIX# chroot /var/empty

And it should define certificate/keys - either inline:

key-direction 1
<ca>
-----BEGIN CERTIFICATE-----
...content from vpn-ca.crt...
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
...content from vpnclient-abc.crt...
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN RSA PRIVATE KEY-----
...content from vpnclient-abc.key...
-----END RSA PRIVATE KEY-----
</key>
<tls-auth>
...content from vpn-ta.key...
</tls-auth>

Or as separate file - but then you MUST also have those separate files and they need to be copied to openvpn client config directory together with the *.ovpn file.

Definition of separate files in *.ovpn would look like this:

tls-auth vpn-ta.key 1
ca vpn-ca.crt
cert vpnclient.crt
key vpnclient.key

Install OpenVPN client application

Start OpenVPN tunnel on client - connect

  openvpn --config /etc/openvpn/vpnclient-my-user.conf.ovpn  
  ifconfig tun1  # interface should be up and have IPs assigned. Check device that you have put in config file, eg. tun1  
  pgrep -fl openvpn.*client  
  less /var/log/messages  # search for entries from "^openvpn:"  

EXTRAS - Additional Authentication on Server via password (optional)

It is also possible to make openvpn server require username/password (in addition to certificate/keyfiles)
Note: This is NOT password for key-file, this is authentication performed on the openvpn server side

Username/password are independent of certificate CN name and config file name

Authentication can be performed:

Under chroot-ed openvpn it is difficult to perform the unix/bsd or ldap authentication.
So in next examples below there are 2 variants:

  1. Run openvpn chrooted and use openvpn-dedicated (simplified) password for authentication
  2. Run openvpn normally (NOT chrooted) and use UNIX/BSD or LDAP authentication

Authentication Variant 1: simple authentication and (optionally) chrooted

Note: This method can be used with or without chroot-ing

Authentication Variant 1 option A (Auth1A) - compile simple static executable to check password using simple config file

Auth1A, step 1 - Prepare config file with passwords

List accepted password files in /etc/openvpn/custom-simple-auth.conf
There is no verification of vpn certificate username against the usernames here.

prepare the symlink for chrooted env (so openvpn can be started with or without chroot and reach the same file): ln -s /var/openvpn/chrootjail/etc/openvpn/custom-simple-auth.conf /etc/openvpn/custom-simple-auth.conf

vi /etc/openvpn/custom-simple-auth.conf

# This file contains authentication data for simple authentication script
# each line should contain 2 values (separated by spaces or tab):
# username password
#
# auth-check script gets given username/password from environment
# then it reads list of valid username/password from this config file
# If the given username/password matches any of the username/password from this config file then authentication is OK.
#
# comments can be made by adding # as the FIRST character in the line
# commenteduser  commentedpassword
vpn     vpn

Auth1A, step 2 - Prepare executable to perform the check

prepare the symlink for chrooted env (so openvpn can be started with or without chroot and reach the same file): ln -s /var/openvpn/chrootjail/var/openvpn/custom-simple-auth /var/openvpn/custom-simple-auth

Create simple C program to do authentication check

mkdir -p /etc/openvpn/additional-files/
cd /etc/openvpn/additional-files/
vi custom-simple-auth.c

add content as in attached file:

#include <sys/param.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>

/* 
# Chris Pfaff
# This is a simple script to do custom authentication for openvpn.
#
# It gets given username/password from environment
# It reads list of valid usernames/password from config file
# If the given username/password matches any of the username/password from the config file then authentication is OK.
#
# If it is static then it can be executed under chrooted environment
# then move output to final location:
# compile this file using -static option:
# 
gcc -static -o /var/openvpn/chrootjail/var/openvpn/custom-simple-auth /etc/openvpn/additional-files/custom-simple-auth.c
ln -s /var/openvpn/chrootjail/var/openvpn/custom-simple-auth /var/openvpn/custom-simple-auth  # for chrooted env

# Test with command like: (adjust the username, password):
username=vpn password=vpn /var/openvpn/custom-simple-auth # should be ok with valid credentials
username=wrong password=wrong /var/openvpn/custom-simple-auth # should fail with wrong credentials


For openvpn - you need to edit the server.conf and add lines like this:

###############
#### AUTHENTICATION of user - requiring username/password in addition to VPN client certificates
###############
# Use custom static C executable that reads username/passwords from /etc/openvpn/custom-simple-auth.conf
# It works when chrooted (compile as static executable, no libraries needed)
auth-user-pass-verify /var/openvpn/custom-simple-auth via-env
# It is possible to use the script in non-chrooted environment or chrooted env
chroot /var/openvpn/chrootjail

*/

char configFile[] = "/etc/openvpn/custom-simple-auth.conf";
int DEBUG=3;


int verifyAgainstFile(char* fileName, char* u, char* p) {  // check password against the strings configured in config file
    FILE *fp;
    char ch;
    const int maxLineLen=255;
    char line[maxLineLen];
    char ru[maxLineLen];
    char rp[maxLineLen];

    fp = fopen(fileName,"r"); // read mode

    if( fp == NULL ) {
        printf("Cannot open config file: %s\n",fileName);
        perror("Error while opening the config file.\n");
        exit(EXIT_FAILURE);
    }

    (DEBUG >= 3) && printf("The contents of %s file are :\n", fileName);

    while (!feof(fp)) {
        (DEBUG >= 3) && printf("Processing line... ");
        if (fgets(line, maxLineLen, fp) ) {
            (DEBUG >= 3) && printf(" '%s'  ", line);
            if (line[0] == '#') {
                (DEBUG >= 3) && printf(" - this line is a comment - ignoring\n");
            } else if (sscanf(line, "%s %s", ru, rp) == 2) {
                (DEBUG >= 3) && printf("%s %s  - ", ru, rp);
                if ( (strncmp(u, ru, maxLineLen) == 0) && (strncmp(p, rp, maxLineLen) == 0) ) {
                    (DEBUG >= 1) && printf("auth ok\n");
                    return 1;  // auth ok
                } else {
                    (DEBUG >= 1) && printf("failed auth\n");
                }
            } else {
                (DEBUG >= 2) && printf("failed scanf: %s %s\n", ru, rp);
                // ru , rp - still might contain values from previous line, do not use them if sscanf failed
            }
        } else {
            (DEBUG >= 3) && printf("failed with fgets - end of file\n");
        }
    }

    fclose(fp);
    return 0;
}

int main() {
    int result;
    char *username;
    char *password;

    if(getenv("username") != NULL) {
        username = getenv("username");
    } else {
        printf("no username environmental variable set\n");
        return 1;
    }

    if(getenv("password") != NULL) {
        password = getenv("password");
    } else {
        printf("no password environmental variable set\n");
        return 1;
    }

    // EXAMPLE: Use system BSD authentication
    // This requires also the includes:
    // #include <login_cap.h>
    // #include <bsd_auth.h>
    //result = auth_userokay(username, NULL, NULL, password); // add auth libraries


    // Check given username/password against list of username/passwords stored in config file
    result = verifyAgainstFile(configFile, username, password);

    if(result == 0) {
        printf("authentication failed\n");
    } else {
        return 0;
    }
    return 1;
}

then compile using static option (-static = no libraries dependencies - needed for chrooted env):

gcc -static -o /var/openvpn/chrootjail/var/openvpn/custom-simple-auth /etc/openvpn/additional-files/custom-simple-auth.c
ln -s /var/openvpn/chrootjail/var/openvpn/custom-simple-auth /var/openvpn/custom-simple-auth  # for chrooted env

Test with command like: (adjust the username, password):

  username=vpn password=vpn /var/openvpn/custom-simple-auth # should be ok with valid credentials
  username=wrong password=wrong /var/openvpn/custom-simple-auth # should fail with wrong credentials

WARNING: Remember - after each OpenBsd Operating System upgrade:


Also, create informational file to explain what is this binary for:

vi /var/openvpn/chrootjail/var/openvpn/custom-simple-auth.txt

# Chris Pfaff
# This is a simple script to do custom authentication for openvpn.
#
# It gets given username/password from environment
# It reads list of valid usernames/password from config file
# If the given username/password matches any of the username/password from the config file then authentication is OK.
#
# If it is static then it can be executed under chrooted environment
# then move output to final location:
# compile this file using -static option:
#

gcc -static -o /var/openvpn/chrootjail/var/openvpn/custom-simple-auth /etc/openvpn/additional-files/custom-simple-auth.c
ln -s /var/openvpn/chrootjail/var/openvpn/custom-simple-auth /var/openvpn/custom-simple-auth  # for chrooted env


# Test with command like: (adjust the username, password):
username=vpn password=vpn /var/openvpn/custom-simple-auth # should be ok with valid credentials
username=wrong password=wrong /var/openvpn/custom-simple-auth # should fail with wrong credentials

ln -s /var/openvpn/chrootjail/var/openvpn/custom-simple-auth.txt /var/openvpn/custom-simple-auth.txt # for chrooted env

Auth1A, step 3 - Enable additional authentication in openvpn server

vi /etc/openvpn/server.conf

###############
#### AUTHENTICATION of user - requiring username/password in addition to VPN client certificates
# VARIANT 1A - using static executable
# Use custom static C executable that reads username/passwords from /etc/openvpn/custom-simple-auth.conf
# It works also when chrooted (this is static executable, no libraries needed)
script-security 3  # CUSTOMIZED by adminOfMine #
auth-user-pass-verify /var/openvpn/custom-simple-auth via-env  # CUSTOMIZED by adminOfMine #
# It is possible to use the script in non-chrooted environment or chrooted env
chroot /var/openvpn/chrootjail  # CUSTOMIZED by adminOfMine #

after changing the server.conf - restart openvpn and test if it works
check /var/log/openvpn/openvpn.log

Auth1A, step 4 - enable authentication support in client config

If you require authentication then also enable it on the client side by changing the openvpn client config file (*.ovpn) and add:

# ask for username/password
auth-user-pass  # CUSTOMIZED by adminOfMine #

Auth1A, step 5 - comments

This authentication method can be used with chroot-ing or without.

With this setup the username/password for openvpn is independent form system user/passwords and independent of the openvpn certificate (so all certificates can use the same passwords).
There is room for improvement, but I find it good enough (and better than just keyfile with no password at all):

Keep in mind that openvpn stores these username/passwords in its config files (in cleartext) and if anyone compromises openvpn then the passwords are visible, so - in worst case - better compromise different vpn password and not real unix users/passwords.

The script and username/password handling can be extended.
It is possible to store sha/md5 of user's password (or export is from ldap/samba/master.passwd).
eg with: cat /etc/master.passwd | grep "^$user" | cut ...
Then check user's password given to script in cleartext, make the same md5/sha and verify against stored value.
If they match then authentication is ok.
But there is not much point in that, because openvpn gives passwords to the authentication-checking binary in cleartext (via env or file).

Authentication Variant 1 option B (Auth1B) - using csh script to check password

Auth1B, step 1 - prepare necessary files for chrooted environment

Execute the following commands to prepare basic files under chrooted env:

This step might need adjustments - depending on the OpenBSD version

cd /var/openvpn/chrootjail
filesToCopy="
/bin/csh
/usr/bin/head
/usr/bin/tail
/usr/bin/ldd
/bin/echo
/usr/lib/libc.so.65.0
/usr/libexec/ld.so
"
for f in $filesToCopy; do
    f2=${f:1:255}
    f2dir=${f2%/*}
    echo "mkdir -p /var/openvpn/chrootjail/$f2dir; cp $f /var/openvpn/chrootjail/$f2; cp $f /var/openvpn/chrootjail/"
    mkdir -p /var/openvpn/chrootjail/$f2dir; cp $f /var/openvpn/chrootjail/$f2; cp $f /var/openvpn/chrootjail/
done

cat '#!/bin/csh
setenv username "vpn"
setenv password "vpn"

/var/openvpn/custom-simple-auth-script.csh && echo "OK" || echo "error"
' > test-chroot-auth.csh
chmod u+x test-chroot-auth.csh

Auth1B, step 2 - create the authorization-check script

Prepare script to check authentication: custom-simple-auth-script.csh ln -s /var/openvpn/chrootjail/var/openvpn/custom-simple-auth-script.csh /var/openvpn/custom-simple-auth-script.csh vi /var/openvpn/chrootjail/var/openvpn/custom-simple-auth-script.csh

Put the following content in the file

#! /bin/csh

# AUTH via-env - sets following variables
# username=user
# password=pass

# AUTH via-file
# filename is given in $1
# file contains 2 lines with username (1st line) and with cleartext password (2nd line)
#username
#password

# read expected (required) username/password from config file. VPN user must give the same username/password.
set requiredUsername=`egrep -v '^#|$^' /etc/openvpn/custom-simple-auth.conf | head -1 | cut -f 1`
set requiredPassword=`egrep -v '^#|$^' /etc/openvpn/custom-simple-auth.conf | head -1 | cut -f 2`

# if via-file then load file content into internal variables
if ( "$1" != "" ) then
    if ( -e "$1" ) then
        #read from file
        set username=`head -1 $1`
        set password=`head -2 $1 | tail -1`
        # echo username=$username
        # echo password=$password
    else
        exit 2  # no file given in $1
        # echo no file found
    endif
endif

# next part is common for via-file (variables loaded from file) or via-env (variables set by calling script)

if ( "$username" == "$requiredUsername" && "$password" == "$requiredPassword" ) then
    # echo ok
    exit 0   # auth ok
else
    # echo err
    exit 1  # file auth failed
endif
exit 10  # shall never happen, but just in case

Auth1B, step 3 - Prepare config file with passwords

List accepted password files in /etc/openvpn/custom-simple-auth.conf
There is no verification of vpn certificate username against the usernames here.

ln -s /var/openvpn/chrootjail/etc/openvpn/custom-simple-auth.conf /etc/openvpn/custom-simple-auth.conf

vi /etc/openvpn/custom-simple-auth.conf

# The required username/password given in /etc/openvpn/custom-simple-auth.conf will be required by all the vpn users (the same for all users). 
# Only first line from that file will be taken into account
vpn     vpn

edit parameters (username password) in /etc/openvpn/custom-simple-auth.conf

Auth1B, step 4 - Enable additional authentication in openvpn server

vi /etc/openvpn/server.conf

###############
#### AUTHENTICATION of user - requiring username/password in addition to VPN client certificates
# VARIANT 1B - authenticate under chroot using custom script
# This script could work with chroot but it would need csh under chrooted dir and all necessary binaries and libraries
# It is using simple hardcoded authentication
script-security 3  # CUSTOMIZED by adminOfMine #
# script must be available under that path after chrooting and necessary shell/libs must be present under chrooted dir
auth-user-pass-verify /var/openvpn/custom-simple-auth-script.csh via-env  # CUSTOMIZED by adminOfMine #
#
# and via file
#tmp-dir /tmp/openvpnauth  # CUSTOMIZED by adminOfMine #
#auth-user-pass-verify /var/openvpn/custom-simple-auth-script.csh via-file  # CUSTOMIZED by adminOfMine #
# It is possible to use the script in both non-chrooted environment or chrooted env

after changing the server.conf - restart openvpn and test if it works
check /var/log/openvpn/openvpn.log

Auth1B, step 5 - enable authentication support in client config

If you require authentication then also enable it on the client side by changing the openvpn client config file (*.ovpn) and add:

# ask for username/password
auth-user-pass  # CUSTOMIZED by adminOfMine #

Authentication Variant 2: bsd or ldap authentication - works only in non-chrooted environment

Note: This method seems to be less secure:

Authentication Variant 2 option A (Auth2A) - bsd authentication

This also requires that the authenticating user be put into the _openvpnusers group.

More information can be found here: man openvpn_bsdauth

To use this variant - prepare directory to store data (need to be done in startup script to create dir under fresh /tmp):

run as root: install -d -m 700 -o _openvpn -g _openvpn /tmp/openvpnauth;

Directory should exist and be empty and readable for _openvpn group: ls -alpd /tmp/openvpnauth; ls /tmp/openvpnauth

Add the following lines to /etc/openvpn/server.conf: vi /etc/openvpn/server.conf

###############
# VARIANT 2 - authenticate using system defined users - BSD and/or LDAP users:

###############
# VARIANT 2A - authenticate using bsd authentication
# # It does NOT work with chrooted openvpn
#
# This also requires that the authenticating user be put into the _openvpnusers group.
#
# # For via-env use script-security 3, for via-file 2 should be enough
script-security 3  # CUSTOMIZED by adminOfMine #
auth-user-pass-verify /usr/local/libexec/openvpn_bsdauth via-env  # CUSTOMIZED by adminOfMine #
#
# # You can switch to authentication via file (then no-one can peek password in openvpn_bsdauth process environment)
# # But make sure no-one can read that file except root and _openvpn user.
# # If you intend to use via-file then make sure directory is created (if in /tmp/ then it must be created after each reboot)
# # use command: install -d -m 700 -o _openvpn -g _openvpn /tmp/openvpnauth; ll -d /tmp/openvpnauth
#auth-user-pass-verify /usr/local/libexec/openvpn_bsdauth via-file  # CUSTOMIZED by adminOfMine #
#tmp-dir /tmp/openvpnauth  # CUSTOMIZED by adminOfMine #
#script-security 2  # CUSTOMIZED by adminOfMine #

Remember that if you use auth via file then create directory in /tmp/ - and it needs to be recreated every time after boot. And use install -d -m ... command (listed above) so that files under the directory cannot be read by other users

Keep in mind that openvpn uses cleartext passwords and if anyone compromises openvpn then the passwords are visible
And remember that authenticating user might need to belong to the _openvpnusers group.

To test authentication, use command like this (give username/password in environment variables):

username=someuser password=passstring /usr/local/libexec/openvpn_bsdauth; echo $?

Authentication Variant 2 option B (Auth2B) - ldap authentication and no chrooted environment

I could not make it work under chrooted env, although it could be possible - connection to ldap is done via TCP connection. But this would need to setup the environment for ldap client, etc under chroot-ed environment.

To use this variant - first prepare directory to store data (need to be done in startup script to create dir under fresh /tmp):

run as root: install -d -m 700 -o _openvpn -g _openvpn /tmp/openvpnauth;

Directory should exist and be empty and readable for _openvpn group: ls -alpd /tmp/openvpnauth; ls /tmp/openvpnauth

create auth-ldap.conf

test -e /etc/openvpn/auth-ldap.conf || cp -p /usr/local/share/examples/openvpn-auth-ldap/auth-ldap.conf /etc/openvpn/auth-ldap.conf

Adjust the /etc/openvpn/auth-ldap.conf. Set section LDAP based on /etc/openldap/ldap.conf, set Authorization based on /etc/login.conf

Here showing just a short simple example
It is recommended to extend it and add setup for TLS, requirements for users to belong to certain group, etc.
Run vi /etc/openvpn/auth-ldap.conf and edit based on example below:

  <LDAP>
      URL             ldap://localhost
      Timeout         15
      TLSEnable       no
  </LDAP>

  <Authorization>
      BaseDN          "ou=Users,dc=domainofmine,dc=local"
      SearchFilter    "(&(objectclass=posixAccount)(uid=%u))"
      RequireGroup    false
  </Authorization>

Add the following lines to /etc/openvpn/server.conf: vi /etc/openvpn/server.conf

###############
# VARIANT 2B - authenticate using ldap authentication plugin
# # It does NOT work with chrooted openvpn
#
# # script-security 3, 2 should be enough if doing via file
script-security 3  # CUSTOMIZED by adminOfMine #
# give full plugin path and then ldap config file. Edit this config file to your environment
plugin /usr/local/lib/openvpn-auth-ldap.so /etc/openvpn/auth-ldap.conf  # CUSTOMIZED by adminOfMine #
# # If you intend to use via-file then make sure directory is created (if in /tmp/ then it must be created after each reboot)
# # use command: install -d -m 700 -o _openvpn -g _openvpn /tmp/openvpnauth; ll -d /tmp/openvpnauth
tmp-dir /tmp/openvpnauth  # CUSTOMIZED by adminOfMine #

Remember that if you create directory in /tmp/ - then it needs to be recreated every time after boot. And use install -d -m ... command (listed above) so that files under the directory cannot be read by other users
Keep in mind that openvpn uses cleartext passwords and if anyone compromises openvpn then the passwords are visible

COMMON PART to Authentication Variants 1 and 2

after changing the server.conf - restart openvpn and test if it works, eg:

/etc/rc.d/openvpn stop; sleep 1;  /etc/rc.d/openvpn start; less /var/log/openvpn/openvpn.log

ifconfig tun0               # put device defined in server.conf
pgrep -fl openvpn.*server
netstat -an|grep '\.1194 '  # put port defined in server.conf
ls -al /var/log/openvpn/*   # check logs

Remember - if you require authentication on server side then also enable it on the client side by changing the openvpn client config file (*.ovpn) - add: # ask for username/password auth-user-pass # other options to be considered if needed: # auth-user-pass /etc/openvpn/user-pass.auth # auth-retry nointeract

Test connecting the client, eg by using:

# test openvpn connection - establish vpn connection locally on the server
openvpn --config /etc/openvpn/vpnclient-unixlocaltest/vpnclient-unixlocaltest.conf.ovpn; 
# then stop local vpn tunnel with Ctrl+C if in foreground 

In case vpn client runs in background:
tail -f /var/log/messages    # if daemonized - check logfile
# if no longer necessary then stop, eg. with:
pgrep -fl 'openvpn'; pkill -f 'openvpn.*client'; echo "---"; sleep 1; pgrep -fl 'openvpn';

Appendix A - fixing error Missing or invalid OpenSSL

If you get an error with easyrsa like:

  Easy-RSA error:

  Missing or invalid OpenSSL
  Expected to find openssl command at: openssl

Check the openssl version

openssl version
  LibreSSL 2.0

If the version does not start with OpenSSL - then might be not recognized by easyrsa script and then needs correction. That was the case for OpenBsd version 5.6 and EasyRSA-3.0.0-rc2.
The fix is quite simple (but verify that all further executions of easyrsa are working correctly).

cp -p easyrsa easyrsa.old
## cat easyrsa.old | perl -pe 's/(\[ "\$\{val.. \*\}" = ")OpenSSL(" ] \|\| die)/$1LibreSSL$2/' > easyrsa
cat easyrsa.old | perl -pe 's/(\[)( "\$\{val.. \*\}" = ")(OpenSSL)(")( ] \|\| die)/$1$2$3$4 -o $2LibreSSL$4$5/' > easyrsa; 
diff easyrsa easyrsa.old
  291c291
  <               [ "${val%% *}" = "LibreSSL" ] || die "\
  ---
  [ "${val%% *}" = "OpenSSL" -o  "${val%% *}" = "LibreSSL" ] || die "\

Appendix B - migrate EasyRsa2 certificates to EasyRsa3

If you have certificates and keys and client certificates created by easy-rsa2 then you might want to use the existing CA and certificates (because using new CA would make old client certificates useless - and they would need to be regenerated).

Assumptions:

To migrate the keys from easy-rsa2 to easy-rsa3, use the guide below.

cd /etc/openvpn/easy-rsa/3
./easyrsa --batch=1 init-pki
./easyrsa --batch=1 --req-cn=vpn-ca build-ca nopass; rm pki/ca.crt pki/private/ca.key  # this removes unwanted new ca but leaves dir structure
ll -d pki pki/* pki/*/*
    #  drwx------  6 root  bin  512 Apr 28 23:17 pki/
    #  drwx------  2 root  bin  512 Apr 28 23:11 pki/certs_by_serial/
    #  -rw-------  1 root  bin    0 Apr 28 23:11 pki/index.txt
    #  drwx------  2 root  bin  512 Apr 28 23:11 pki/issued/
    #  drwx------  2 root  bin  512 Apr 28 23:17 pki/private/
    #  drwx------  2 root  bin  512 Apr 28 23:11 pki/reqs/
    #  -rw-------  1 root  bin    3 Apr 28 23:11 pki/serial

#move files from easyrsa20 to easyrsa30
cp -p /etc/openvpn/dh.pem pki/  # move current openvpn dh file in there
mv ../2.0/keys/crl.pem pki/  # move if file exists
mv ../2.0/keys/index.txt*  pki/
mv ../2.0/keys/serial*  pki/
mv ../2.0/keys/*.key  pki/private/
mv ../2.0/keys/ca.crt pki/
mv ../2.0/keys/*.crt  pki/issued/
mv ../2.0/keys/*.csr  pki/reqs/
mv ../2.0/keys/??.pem pki/certs_by_serial/

In case you would need to re-create the server certificate (and still using deprecated ns-cert option for clients that still require this) then use commands like:

  ./easyrsa --batch=1 --ns-cert="yes" --ns-comment="Easy-RSA Generated Server Certificate" --req-cn=vpnserver gen-req vpnserver nopass
  openssl req -in pki/reqs/vpnserver.req -text -noout
  ./easyrsa --batch=1 --ns-cert="yes" --ns-comment="Easy-RSA Generated Server Certificate" sign server vpnserver # server - only for server
  openssl x509 -in pki/issued/vpnserver.crt -text -noout
  cp -p pki/issued/vpnserver.crt /etc/openvpn/certs/vpnserver.crt; cp -p pki/private/vpnserver.key /etc/openvpn/private/vpnserver.key

And remember to restart openvpn so that it reads new certificate. Then test with existing clients!!!

Further links

http://www.openbsdsupport.org/

https://openvpn.net/index.php/open-source/documentation/howto.html

https://community.openvpn.net/openvpn/wiki/EasyRSA3-OpenVPN-Howto

http://www.kernel-panic.it/openbsd/vpn/vpn4.html

Final note

NO WARRANTY, NO RESPONSIBILITY - you are fully responsible for the systems you configure/maintain/change. This guide is only for information purposes. Some errors and non-working commands or unpredicted side-effects are possible.

BSD License applies for this guide. Feel free to distribute, edit, re-use the document as needed, but please acknowledge the author and source of document in the web.


Go here for the newest version of this document