[ OpenBSD 4.3, OpenVPN 2.0.9 ]

Verified on OpenBSD 4.4 amd64 with OpenVPN 2.1 rc7; OpenBSD 4.6 i386 with OpenVPN 2.1rc22

Install

Install from Port Packages.

The base server / client software are in a single package that can simply be installed from the OpenBSD ports system. The real challenge is configuring the program to execute as we expect.

Note:

If the server is to route traffic from the OpenVPN Private Network through other networks configured on the machine, remember to enable IP Forwarding (routing) on the physical server.

In the /etc/sysctl.conf file:

net.inet.ip.forwarding=1

On the command-line

/sbin/sysctl net.inet.ip.forwarding=1

Base System Configuration

To retain consistency with how other OpenBSD applications are installed, and configured we pre-select a standard configuration for our installation as per the below structure.

The following discussion configures files in the following structure.

Directory Location Description
/usr/local/sbin Location for openvpn executable. This is the standard install directory for the OpenBSD package.
/etc/openvpn Configuration files, a clean install should contain few files: server.conf, openvpn.cnf, ipp.txt
/etc/openvpn/keys generated certificates, keys, and index information
/usr/local/bin/openvpn scripts used during the installation process. We are modifying the scripts and keeping them in this location ensures we can rebuild the same configuration during software updates etc.

We'll first create the appropriate directories and files.

$ sudo mkdir -p /usr/local/bin/openvpn
$ sudo mkdir -p /var/log/openvpn
$ sudo touch /var/log/openvpn/status.log 
$ sudo touch /var/log/openvpn/access.log
$ sudo touch /var/log/openvpn/ipp.txt

Copy the sample distribution configuration files to a staging area where we can modify the files, but leave our configuration directory relatively clean.

Note: Post OpenBSD 5.x, easy-rsa is installed as a separate package into /usr/local/share/examples/easy-rsa

$ cd /usr/local/bin/openvpn
$ sudo cp /usr/local/share/examples/openvpn/easy-rsa/2.0/* .
$ sudo mv openssl.cnf /etc/openvpn

Pre-install Modifications

The default configurations from the openvpn distribution is not 'sane' for OpenBSD, and the following instructions are to put the deployment into a 'sane' configuration

./vars default environment variables

Fix-up errors with the install files and default data into our working files.

# diff -u /usr/local/share/examples/openvpn/easy-rsa/2.0/vars \
     /usr/local/bin/openvpn/vars
--- /usr/local/share/examples/openvpn/easy-rsa/2.0/vars Sun Mar 16 09:22:52 2008
+++ /usr/local/bin/openvpn/vars       Thu Aug 21 12:55:48 2008
@@ -26,7 +26,7 @@
 # This variable should point to
 # the openssl.cnf file included
 # with easy-rsa.
-export KEY_CONFIG=`$EASY_RSA/whichopensslcnf $EASY_RSA`
+export KEY_CONFIG=/etc/openvpn/openssl.cnf

 # Edit this variable to point to
 # your soon-to-be-created key
@@ -36,7 +36,7 @@
 # a rm -rf on this directory
 # so make sure you define
 # it correctly!
-export KEY_DIR="$EASY_RSA/keys"
+export KEY_DIR="/etc/openvpn/keys"

 # Issue rm -rf warning
 echo NOTE: If you run ./clean-all, I will be doing a rm -rf on $KEY_DIR
@@ -46,7 +46,7 @@
 # down TLS negotiation performance
 # as well as the one-time DH parms
 # generation process.
-export KEY_SIZE=1024
+export KEY_SIZE=4096

 # In how many days should the root CA key expire?
 export CA_EXPIRE=3650
@@ -57,8 +57,9 @@
 # These are the default values for fields
 # which will be placed in the certificate.
 # Don't leave any of these fields blank.
-export KEY_COUNTRY="US"
-export KEY_PROVINCE="CA"
-export KEY_CITY="SanFrancisco"
-export KEY_ORG="Fort-Funston"
-export KEY_EMAIL="me@myhost.mydomain"
+export KEY_COUNTRY="AU"
+export KEY_PROVINCE="NSW"
+export KEY_CITY="Sydney"
+export KEY_ORG="My Company Pty Ltd"
+export KEY_EMAIL="vpn_admin@example.com"

We're paranoid, so let's just set the basis for all encryption to be 4096, even if it does take a long time to generate keys.

pkitool
openssl.cnf

Create Certificates

The following process builds the basic set of keys for operating the VPN. Keys are built (and cleared,) for this configuration, in the /etc/openvpn/keys directory. For those who can't wait and don't mind destroying their configuration, the annotated steps are:

  1. Prep the environment, Clean Up
  2. Build the Certificate Authority
  3. Build the Server Keys, Self Sign
  4. Build a Control Channel Shared Key
  5. Build Client Keys/Certificates
_ $ sudo su
a # cd /usr/local/bin/openvpn
- # . ./vars
- # ./clean-all
b # ./build-ca
c # ./build-key-server EXAMPLE.COM
d # /usr/local/sbin/openvpn --genkey --secret $KEY_DIR/ta.key
e # ./build-key client_username

Depending on the shell of choice, the above 'vars' configuration may not work correctly. '/usr/local/bin/openvpn/vars' exports variables used in the build scripts. EXAMPLE.COM is the variable we added to the ./vars script above.

a. Preparing the environment, Cleaning Up

"Install" the 'environment' used as the default for the whole process by sourcing the ./vars script. As the subsequent scripts need to use these environment variables, and the process needs to write files in the root only access /etc/openvpn/keys directory, we can either run the scripts from root or from sudo -E (but with this you would need to ensure that your sudoers configuration is compatible) so for simplicity we choose to install as root. When correctness prevails we'll update this document.

$ cd /usr/local/bin/openvpn
$ sudo su
# . ./vars
NOTE: If you run ./clean-all, I will be doing a rm -rf on /etc/openvpn/keys

Following instructions, the next thing is to clear out the destination directory for our keys and certificates

# ./clean-all

b. Build the Certificate Authority

Build our local Certificate Authority signing key for our domain.

# ./build-ca
Generating a 4096 bit RSA private key
............................................++
..............................................................................
.............................................................++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [NSW]:
Locality Name (eg, city) [City]:
Organization Name (eg, company) [Organisation Name]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) [Organisation Name CA]:
Email Address [vpnmeister@example.com]:
# ls $KEY_DIR
ca.crt ca.key index.txt          serial

c. Build and Authenticate our Server Certificate

Build and sign a certificate for our server EXAMPLE.COM. We will sign the new certificate (EXAMPLE.COM.key) using the certificate authority created in the above step ca.

#  ./build-key-server EXAMPLE.COM
Generating a 4096 bit RSA private key
........................................................................................................................
.........................................................................................................................++
....++
writing new private key to 'example.com.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [NSW]:
Locality Name (eg, city) [City]:
Organization Name (eg, company) [Organisation Name]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) [example.com]:
Email Address [vpnmeister@example.com]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /etc/openvpn/openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'AU'
stateOrProvinceName   :PRINTABLE:'NSW'
localityName          :PRINTABLE:'City'
organizationName      :PRINTABLE:'Organisation Name'
commonName            :PRINTABLE:'example.com'
emailAddress          :IA5STRING:'vpnmeister@example.com'
Certificate is to be certified until Jun 11 01:55:01 2018 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
# ls $KEY_DIR
01.pem             ca.key example.com.csr    index.txt          index.txt.old      serial.old
ca.crt example.com.crt    example.com.key    index.txt.attr     serial

d. Build a control channel shared key

If we've set up clients with their own (reverse) DNS entry, then it may be appropriate to specify clients with these domain names. Another example of where the above naming convention will be useful is when you have clients accessing from multiple locations to multiple (separate) OpenVPN servers.

To help with security he final server key to be built is the tls-auth key.

$ sudo /usr/local/sbin/openvpn --genkey --secret $KEY_DIR/ta.key
$ sudo cat $KEY_DIR/ta.key
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
8ca2fef6b3453a8b08b295b4b636736d
27409ddb496ea42f2491a9b0a877679f
94c94394513deb6af5d0e39c418fcc47
fd78a401ffbcbb5c40d367dcc165bd52
8b5c0dc97fca762921afbdf6b6bd459b
0c0c0dd272d52de00f8905dd9f36b5ad
c75f310ad55ab034f7bebc9f097f10a6
abd05323dcaf6204e52024f327c3cd59
9dc83889d8596cfbf1d7790fb27189ab
25f965d5ba0f9fc367b9f12ed418dbaa
b9e44bc97d06a7712d6402f9868e48ac
3122e26d0700d1b71d4b51ee7e739e7b
9bd79fecc83e53a4e4faacda27fe7884
cbcf93a4327ea20c5833719d288658da
cd3e4ff253484a6825c2ed4de8b5e21e
1027887af54343f9919321648f9e072b
-----END OpenVPN Static key V1-----

That covers most of our server certificate requirements and we can continue with creating keys/certificates for our long list of clients.

e. Build Client Keys/Certificates

# ./build-key client_username1
Generating a 4096 bit RSA private key
..........................................................................................................................
..........................................................................................................................
.............................................................................++
................++
writing new private key to 'client_username1.key'
-----
You are about to be asked to enter information that will be i:qncorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [NSW]:
Locality Name (eg, city) [City]:
Organization Name (eg, company) [Organisation Name]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) [client_username1]:
Email Address [vpnmeister@example.com]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /etc/openvpn/openssl.cnf
DEBUG[load_index]: unique_subject = "yes"
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'AU'
stateOrProvinceName   :PRINTABLE:'NSW'
localityName          :PRINTABLE:'City'
organizationName      :PRINTABLE:'Organisation Name'
commonName            :PRINTABLE:'client_username1'
emailAddress          :IA5STRING:'vpnmeister@example.com'
Certificate is to be certified until Jun 11 02:08:28 2018 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
# ls $KEY_DIR
01.pem                     client_username1.key    example.com.csr            index.txt.attr.old
02.pem                     ca.crt         example.com.key            index.txt.old
client_username1.crt    ca.key         index.txt                  serial
client_username1.csr    example.com.crt            index.txt.attr             serial.old

To create more client keys, run the ./build-key script again

# ./build-key client_username2

Building the DH key

There's a reason why this is the last key construction for the setup. Warning: The above keys, at KEY_SIZE = 4096, takes sometime to build. Building the DH key at 4096 takes a really long time to build.

# ./build-dh

The above should output a file $KEY_DIR/dh4096.pem

Sample Configuration File

For our sample configuration, we are taking private IPs from a known working configuration, and it is a matter of adapting to the situation that fits your new installation.

  • InternalLAN_IP=10.0.2.0
  • InternalLAN_IP_MASK=255.255.255.0
  • InternalLAN_DNS_SERVER= 10.0.2.1
  • VirtualLAN_IP=10.8.0.0
  • VirtualLAN_IP_MASK=255.255.255.0

Remember that EXAMPLE.COM is a placeholder for whatever your preferred domain name is going to be.

# File: /etc/openvpn/server.conf
port 1194
proto udp
dev tun0
ca   /etc/openvpn/keys/ca.crt
cert /etc/openvpn/keys/EXAMPLE.COM.crt
key  /etc/openvpn/keys/EXAMPLE.COM.key
dh   /etc/openvpn/keys/dh4096.pem
tls-auth /etc/openvpn/keys/ta.key 0

server 10.8.0.0 255.255.255.0
push "route 10.0.2.0 255.255.255.0"
push "dhcp-option DNS 10.0.2.1"

client-to-client

keepalive 10 120
comp-lzo
user _openvpn
group _openvpn
persist-key
persist-tun
ifconfig-pool-persist /var/log/openvpn/ipp.txt
status  /var/log/openvpn/status.log
log-append /var/log/openvpn/access.log
verb 3

Further explanations of our option selection with server.conf

#server $VPN_IP $VPN_IP_MASK
server 10.8.0.0 255.255.255.0

server 10.8.0.0 255.255.255.0 becomes the IP range for the virtual network. Clients connecting to our OpenVPN will be given an IP address from within this range, and the server itself will get an address from this range.

push "route 10.0.2.0 255.255.255.0"
push "dhcp-option DNS 10.0.2.1"

push "route 10.0.2.0 255.255.255.0" instructs the client to route traffic bound for the stated IP Range (10.0.2.0/24), through OpenVPN.

To redirect ALL traffic through the VPN, use: push "redirect-gateway def1". Refer OpenVPN HOWTO for scenarios and issues relevant to redirect such as all machines being on the same segment, but some being on less secure transports (such as wireless), or all clients are connected wirelessly.

Example modifications

Thinking through the route table facilities above, we have the Virtual LAN working on the 10.8.0.0/24 ip range, giving access to remote clients to a local 10.0.2.0/24 range. We are adding specific routes to the client to the Virtual LAN and to specific services accessible to the OpenVPN Server. We can restrict/open client access to server-side services, by controlling the routing (finegrained controls require firewall rules.)

Key aspects of our example server configuration file includes:

  • Be explicit about file locations

Server to Server Configuration File

If the client is a server, acting as a gateway for subnets gatewayed through it, then the configuration changes to require the use of:

client-config-dir /etc/openvpn/ccd

Where /etc/openvpn/ccd is the client configuration directory. When a server/gateway client connects to the OpenVPN server, the daemon will check this directory for a file which matches the common name of the connecting client. If a matching file is found, it will be read and processed for additional configuration file directives to be applied to the named client.

Create a file hostname.example.org in the /etc/openvpn/ccd directory to contain the routing information for the external gateway, for example:

iroute 10.9.10.0 255.255.255.0

This will tell the OpenVPN server that the 10.9.10.0/24 subnet should be routed to client hostname.example.org.

Next, add the following line to the main server config file server.conf

route 10.9.10.0 255.255.255.0

Configure Interfaces

dev tun0 tells OpenVPN to use the tun0 interface, and sets the session to be routing as opposed to bridging. We need to create the tun0 tunnel interface.

OpenVPN re-creates the tun(4) interface at startup; compatibility with PF is improved by starting it from hostname.if(5). For example:

# cat << EOF > /etc/hostname.tun0
up
!/usr/local/sbin/openvpn --daemon --config /etc/openvpn/server.conf
EOF

The above updated documentation improves compatibility with PF.

Starting the Server

To start the server, restart network services with

# sh /etc/netstart

Debugging the /etc/openvpn/server.conf Configuration

Visit the files: /var/log/openvpn/[status|access].log

Configuring PF

The following is a sample configuration for OpenVPN.

ext_if  =  "em0"
lan_if  =  "em1"
vpn_if  =  "tun0"
vpn_port = "1194"

table <vpn_network> { 10.8.0.0/24 }

icmp_types   = "{ echoreq, echorep }"

set skip on lo
scrub in

nat on $ext_if from $lan_if:network to any -> ($ext_if)

# Default rules
block log

# Traffic allowed from any direction
pass in inet proto icmp icmp-type $icmp_types keep state
pass in inet proto tcp from any to any port ssh

# External Interface
pass in on $ext_if inet proto udp from any to $ext_if port $vpn_port keep state
pass out on $ext_if keep state

# Internal Interface
pass out on $lan_if from <vpn_network> to $lan_if:network keep state

# VPN IF
pass in on $vpn_if inet proto { tcp, udp } \
        from <vpn_network> to $lan_if:network keep state
pass out on $vpn_if from $lan_if:network keep state

For a bridging example, see BlogFish post