At home I have a zone dedicated to running an OpenVPN server. With this I can connect to my home network securely on both my laptop and phone when I’m away.

I followed this guide for getting the zone ready to route properly for OpenVPN, and then used Easy-RSA to generate the certificates needed.

Zone Setup

To get started, make sure the zone is created with allow_ip_spoofing enabled on the NIC of the zone. I used a payload similar to this to create the vpn zone.

{
  "brand": "joyent",
  "image_uuid": "221635c4-3b85-11e8-b6ba-23f68c9bf2c4",
  "autoboot": true,
  "alias": "vpn",
  "hostname": "vpn.rapture.com",
  "dns_domain": "rapture.com",
  "resolvers": [
    "10.0.1.2",
    "10.0.1.3"
  ],
  "ram": 512,
  "nics": [
    {
      "nic_tag": "admin",
      "ip": "10.0.1.41",
      "allow_ip_spoofing": true,
      "netmask": "255.255.255.0",
      "gateway": "10.0.1.1",
      "primary": true
    }
  ]
}

If the zone is already created, you can update the NIC with this option with:

vmadm update $uuid <<< '{"update_nics":[{"mac":"00:00:00:00:00:00","allow_ip_spoofing":true}]}'

Where $uuid is the zones’ UUID and 00:00:00:00:00:00 is the MAC address of the NIC you want to enable spoofing on.

Zone Networking

IPv4 forwarding must be enabled on the zone with the following command:

routeadm -ue ipv4-forwarding

You can verify this worked by running:

# routeadm -p ipv4-forwarding
persistent=enabled default=disabled current=enabled

And seeing that persistent and current are both set to enabled.

The final part of the networking setup for the zone is to rewrite packets from the clients. To do this, create /etc/ipf/ipnat.conf with this single line.

map * from 192.168.1.0/24 to any -> 0.0.0.0/32

And then enable ipfilter:

svcadm enable ipfilter

Now the zone is setup to properly rewrite and forward packets received by the OpenVPN server.

OpenVPN Setup

Install the OpenVPN server as well as the easy-rsa tool we’ll use to generate certificates.

pkgin in easy-rsa openvpn

Just in case the server tries to come up upon being installed, disable it for now until we configure it properly.

svcadm disable openvpn

Next, create the config file for OpenVPN at /opt/local/etc/openvpn/openvpn.conf

# OpenVPN Server Config
port 1194
proto udp
management localhost 7505
dev tun
comp-lzo
persist-key
persist-tun
verb 3

# keys and certs
ca      certs/ca.crt
cert    certs/issued/vpn-server.crt
key     certs/private/vpn-server.key
dh      certs/dh.pem

# This will be the internal tun0 connection IP - should match ipnat.conf
server 192.168.1.0 255.255.255.0
ifconfig-pool-persist ipp.txt

# This will send all of a client's traffic to the private vlans through the tunnel
# XXX Configure these to match your local setup - rapture.com is my internal domain
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 10.0.1.2"
push "dhcp-option DNS 10.0.1.3"
push "dhcp-option DOMAIN-SEARCH rapture.com"
push "dhcp-option DOMAIN rapture.com"
keepalive 10 120

# connect script
script-security 2
client-connect ./client-connect

At the bottom of the config is client-connect and script-security (optional). These lines make it so a command will be run whenever a client is connected to the VPN server. I use this script below to send a push notification to my phone whenever a new client connects:

/opt/local/etc/openvpn/client-connect

#!/usr/bin/env bash
pushover OpenVPN "$common_name connected from $trusted_ip"
exit 0

You’re free to put whatever logic you’d like in here, or comment out the 2 lines to disable this functionality.

Server Certificates

With this config in place, we’ll create the files that the config requires to work using easyrsa. To get started, I find it easiest to make a wrapper script for easyrsa that exports some of the environmental variables we’ll need to make this process easy.

cd
cat <<-EOF > ./easyrsa
#!/usr/bin/env bash
export EASYRSA=/opt/local/etc/easyrsa
export EASYRSA_PKI=/opt/local/etc/openvpn/certs
exec easyrsa "$@"
EOF

And then

chmod +x ./easyrsa
  • EASYRSA is the location of the easy-rsa config files and such
  • EASYRSA_PKI is the location to store the certs - this could be anything. I personally set it to /var/tmp/certs when I want to test it without interfering with my existing setup.

With this wrapper, we can start building out the cert infrastructure. Run these 3 commands to get started (gen-dh will take a while).

./easyrsa init-pki
./easyrsa gen-dh
./easyrsa build-ca

Enter a password when prompted by build-ca and then confirm the rest of the command with the defaults. This password will be used whenever you sign a certificate that has been generated.

Create the certificate and key for the vpn server itself:

 ./easyrsa gen-req vpn-server nopass
 ./easyrsa sign-req server vpn-server

sign-req will ask you to confirm the selection by typing yes, and then it will also prompt you for a password - this is the password created when build-ca was run above.

nopass is used when generating the certificate so the server can start automatically without human intervention to enter the password.

Start OpenVPN

Now, we are ready to start the server! Start it with:

svcadm enable openvpn

To ensure that everything is working you can look at the logs here

# svcs -L openvpn
/var/svc/log/pkgsrc-openvpn:default.log
# tail /var/svc/log/pkgsrc-openvpn:default.log
Fri Jul  6 01:53:02 2018 OpenVPN 2.4.5 x86_64-sun-solaris2.11 [SSL (OpenSSL)] [LZO] [LZ4] [MH/PKTINFO] [AEAD] built on Apr  7 2018
Fri Jul  6 01:53:02 2018 library versions: OpenSSL 1.0.2o  27 Mar 2018, LZO 2.10
Fri Jul  6 01:53:02 2018 setsockopt(IPV6_V6ONLY=0)
Fri Jul  6 01:53:02 2018 MANAGEMENT: TCP Socket listening on [AF_INET6]::1:7505
Fri Jul  6 01:53:02 2018 NOTE: the current --script-security setting may allow this configuration to call user-defined scripts
Fri Jul  6 01:53:02 2018 Diffie-Hellman initialized with 2048 bit key
Fri Jul  6 01:53:02 2018 open_tun: got dynamic interface 'tun2'
Fri Jul  6 01:53:02 2018 TUN/TAP device tun2 opened
Fri Jul  6 01:53:02 2018 do_ifconfig, tt->did_ifconfig_ipv6_setup=0
Fri Jul  6 01:53:02 2018 /sbin/ifconfig tun2 192.168.1.1 192.168.1.2 mtu 1500 up
Fri Jul  6 01:53:02 2018 /sbin/ifconfig tun2 netmask 255.255.255.255
Fri Jul  6 01:53:02 2018 /sbin/route add 192.168.1.0 -netmask 255.255.255.0 192.168.1.2
add net 192.168.1.0: gateway 192.168.1.2
Fri Jul  6 01:53:02 2018 Could not determine IPv4/IPv6 protocol. Using AF_INET6
Fri Jul  6 01:53:02 2018 Socket Buffers: R=[57344->57344] S=[57344->57344]
Fri Jul  6 01:53:02 2018 setsockopt(IPV6_V6ONLY=0)
Fri Jul  6 01:53:02 2018 UDPv6 link local (bound): [AF_INET6][undef]:1194
Fri Jul  6 01:53:02 2018 UDPv6 link remote: [AF_UNSPEC]
Fri Jul  6 01:53:02 2018 MULTI: multi_init called, r=256 v=256
Fri Jul  6 01:53:02 2018 IFCONFIG POOL: base=192.168.1.4 size=62, ipv6=0
Fri Jul  6 01:53:02 2018 IFCONFIG POOL LIST
Fri Jul  6 01:53:02 2018 Initialization Sequence Completed

Also, the config line management localhost 7505 starts a management socket on port 7505 that we can use to verify its working:

# nc localhost 7505 <<< 'pid'
>INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info
SUCCESS: pid=26128

Note that it’s possible to do a lot of things using this management port - including kill the vpn daemon. There is no authentication and any user on the system can connect to this port (it can be disabled in the configuration). The management interface runs on localhost, the zone I’m running this server on only has the root user, has no SSH access, and only runs this daemon so that’s secure enough for my setup - your situation may differ so be mindful of that.

Finally, it should be noted that a tunnel interface is created for this (as per the config). In the log you can see tun2, and using ifconfig we can see:

# ifconfig tun2
tun2: flags=10011008d1<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST,ROUTER,IPv4,FIXEDMTU> mtu 1500 index 6
    inet 192.168.1.1 --> 192.168.1.2 netmask ffffffff
    ether 40:5a:ea:9c:2f:fe

Client Certificates

The server is running and ready for new clients to connect, now we just need to make some certificates for clients to use.

To make a new client cert and key run the following commands:

./easyrsa gen-req dave-laptop-client
./easyrsa sign-req client dave-laptop-client

Note that gen-req will ask for a password to use to protect the private key being generated, and sign-req will ask for the password created during the build-ca step at the beginning of this whole process.

Once these 2 steps are done we now have a cert and key for a new client that can be used to connect to the server! The final step is to use these new files to create an ovpn file - these files can be easily imported into OpenVPN clients. On my iPhone I can use the OpenVPN app with this file to set it up and on my Arch Linux laptop I can just import the file using NetworkManager.

A simple ovpn template looks like this:

client
remote 10.0.1.41 1194
proto udp
dev tun
persist-key
persist-tun
resolv-retry infinite
nobind
comp-lzo
verb 3
<ca>
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN ENCRYPTED PRIVATE KEY-----
...
-----END ENCRYPTED PRIVATE KEY-----
</key>

The remote line should be set to the IP (or hostname) and port of the VPN server.

To create the file, I use a helper script I call gen-ovpn.

~/gen-ovpn

#!/usr/bin/env bash
#
# Generate an ovpn file using an existing easy-rsa pki structure
#
# Author: Dave Eddy <dave@daveeddy.com>
# Date: July 06, 2018
# License: MIT

name=$1
pki=${EASYRSA_PKI:-/opt/local/etc/openvpn/certs}

[[ -n $name ]] || exit 1
cd "$pki" || exit 1

ca=$(< ca.crt)
cert=$(< issued/$name.crt)
key=$(< private/$name.key)

[[ -n $ca && -n $cert && -n $key ]] || exit 1

cat <<-EOF
client
remote 10.0.1.41 1194
proto udp
dev tun
persist-key
persist-tun
resolv-retry infinite
nobind
comp-lzo
verb 3
<ca>
$ca
</ca>
<cert>
$cert
</cert>
<key>
$key
</key>
EOF

This way, I can simply specify the basename of the client and it will create the file with the data in the right place.

./gen-ovpn dave-laptop-client > dave-laptop-client.ovpn

And that’s it! That file can be sent to a client that wants to connect, and as long as they know the password (used during gen-req for the cert) they can connect to the server.

Conclusion

You can now generate certs, keys, and ovpn files for any new clients you want to have access to your VPN server!

You can see the active connections by checking the status of the management interface

# nc localhost 7505 <<< 'status'
>INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info
OpenVPN CLIENT LIST
Updated,Fri Jul  6 05:40:52 2018
Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since
dave-laptop-client,10.0.1.165,22256,3086,Fri Jul  6 05:40:18 2018
ROUTING TABLE
Virtual Address,Common Name,Real Address,Last Ref
192.168.1.6,dave-laptop-client,10.0.1.165,Fri Jul  6 05:40:51 2018
GLOBAL STATS
Max bcast/mcast queue length,0
END