OpenVPN VPN Server on OS X Server with Mavericks, pfctl, and Tunnelblick
Here are notes on how to build an OpenVPN VPN server on OS X Server with Mavericks, pfctl, and Tunnelblick. Previous OpenVPN server configurations on OS X Server rely upon using the now deprecated natd and ipfw to route VPN traffic, and this solution no longer works. This post describes a replacement using the now preferred pfctl OpenBSD packet filter, which comes with its own NAT. This setup will provide a TLS-based VPN server using 4096-bit certificates and UDP port 443, accessible by any OpenVPN client, especially iOS with the OpenVPN app.
Why would you want to build your own VPN server when OS X server already comes with a VPN service? First, the latest Server.app version 3 breaks VPN to mobile devices. This problem is known and will undoubtedly be fixed soon; however, the VPN technology used by OS X Server is broken and should be avoided altogether (Microsoft’s PPTP: ("PPTP traffic should be considered unencrypted"), or is under a cloud (L2TP/Ipsec with pre-shared keys and MS-CHAPv2 authentication: "IPSEC-PSK is arguably worse than PPTP ever was for a dictionary-based attack vector"). So if you’re going to use OS X Server’s native VPN service, make sure that you use a really long *random* PSK. If you want secure certificate-based VPN between OS X Server and iOS, OpenVPN is the only option.
Furthermore, OS X Server has its firewall turned off by default, assuming that the server lives behind the router's firewall and NAT. Integrating OpenVPN access within a working OS X Server firewall provides greater security than OS X Server's default configuration.
Here’s how to build a VPN Server on OS X Mavericks:
Step 1. Get OpenVPN on OSX and configure it.
1A. I like Macports, so assuming that you've downloaded and installed Xcode from the App Store, installed Macports, run:
sudo port selfupdate
sudo port install openvpn
Step 2. Get Tunnelblick on OS X and configure it.
2A. <http://code.google.com/p/tunnelblick/>
2B. Create your certificates with Easy-RSA
mkdir -p ~/Backups/OpenVPN/easy-rsa-tunnelblick
sudo rsync -va /Applications/Tunnelblick.app/Contents/Resources/easy-rsa-tunnelblick/ ~/Backups/OpenVPN/easy-rsa-tunnelblick
cd ~/Backups/OpenVPN/easy-rsa-tunnelblick
su root
# edit script defaults like KEY_CN == Common Name
vi ./vars
export KEY_SIZE=4096
export KEY_EMAIL="admin@domainname.com"
export KEY_CN=Domainname
mkdir -m go-rwx ./keys
touch ./keys/index.txt
echo 1 > ./keys/serial
. ./vars
./clean-all
./build-ca --pass
./build-key-server server-domainname
# choose a unique Common Name (CN) for each client
./build-key client-domainname
./build-dh
openvpn2 --genkey --secret ./keys/ta.key
# Notes:
# Use the domain name "domainname.com" for the common name
# Contact email "admin@domainname.com" must match name in CA;
# otherwise, there will be some X509 error.
# For the server-domainname cert, use the default common name
# "server-domainname".This must also match the client configuration
# setting:
# tls-remote domainname.com
# Unnecessary if you already signed with ./build-key[-server]
# ./sign-req server-domainname
# ./sign-req client-domainname
cd keys
openssl verify -CAfile ca.crt ca.crt
openssl verify -CAfile ca.crt server-domainname.crt
openssl verify -CAfile ca.crt client-domainname.crt
exit # root
2C. Create a .tblk directory for your VPN server with all the necessary files. I'll demo a tun setup. The LAN here looks like a router on 10.0.1.1 and a OS X Server on 10.0.1.3.
mkdir ~/Desktop/Domainname_tun.tblk
cd ~/Backups/OpenVPN/easy-rsa-tunnelblick/keys
sudo cp -p ca.crt dh4096.pem server-domainname.crt server-domainname.key ta.key ~/Desktop/Domainname_tun.tblk
sudo chown -R username ~/Desktop/Domainname_tun.tblk
cd ~/Desktop/Domainname_tun.tblk
vi config.ovpn
local 10.0.1.3
port 443
proto udp
dev tun
;dev tap
ca ca.crt
cert server-domainname.crt
key server-domainname.key
tls-auth ta.key 0
dh dh4096.pem
server 10.2.0.0 255.255.255.0
;server-bridge 10.0.1.3 255.255.255.0 10.0.1.50 10.0.1.90
;topology subnet
# Push routes to the client to allow it
# to reach other private subnets behind
# the server. Remember that these
# private subnets will also need
# to know to route the OpenVPN client
# address pool (10.2.0.0/255.255.255.0)
# back to the OpenVPN server.
;push "route 10.0.1.0/255.255.255.0"
push "redirect-gateway def1"
push "dhcp-option DNS 8.8.8.8"
;push "dhcp-option DNS 10.0.1.3”
;push "dhcp-option DOMAIN domainname.com."
client-to-client
keepalive 10 120
;cipher BF-CBC
comp-lzo
max-clients 10
user nobody
group nobody
persist-key
persist-tun
ifconfig-pool-persist ipp.txt
verb 3
2D. Forward UDP port 443 from your router to you server at 10.0.1.3. (You may also wish to forward the default OpenVPN port 1194 as well to test other configurations.)
2E. Doubleclick on ~/Desktop/Domainname_tun.tblk within Finder, which should start Tunnelblick and import your configuration. Then,
Tunnelblick>VPN Details ... >Configuration>Settings>Set Nameserver (3.1)
You may also wish to launch this service at boot.
Hit connect and Tunnelblick should launch your OpenVPN server on UDP port 443.
2F. Get OpenVPN from the iOS App store and securely transfer a client.ovpn file to it:
client.ovpn:
####
## Domain Mac UDP client configuration version 2.0
####
client
dev tun
proto udp
remote server.domainname.com 443
redirect-gateway def1
resolv-retry infinite
nobind
;user nobody
;group nobody
persist-key
persist-tun
;ca ca.crt
;cert iPad-domainname.crt
;key iPad-domainname.key
<ca>
-----BEGIN CERTIFICATE-----
MY CERT from ca.crt
-----END CERTIFICATE-----
</ca>
ns-cert-type server
;tls-auth ta.key 1
key-direction 1
<tls-auth>
#
# 4096 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
MY CERT from ta.key
-----END OpenVPN Static key V1-----
</tls-auth>
;auth-user-pass
;tls-remote server-domainname
tls-ciphe
r DHE-RSA-AES256-SHA
;cipher BF-CBC
;client-http-proxy 10.0.1.3 3128
comp-lzo
verb 3
Step 3. Configure pfctl on OS X to route your VPN packets correctly.
3.A. Set up sysctl variables at boot:
sudo mkdir -p /Library/Application\ Support/vpn
sudo vi /Library/Application\ Support/vpn/enable-vpn-forward-nat.sh
#!/bin/bash
#
# References: The Book of PF, p. 21; https://forums.openvpn.net/topic11401.html
#
# Sleep is necessary cause network has to be up at the time of following commands
# Otherwise the network will not work at all
#
sleep 15
#
/usr/sbin/sysctl -w net.inet.ip.fw.enable=1
/usr/sbin/sysctl -w net.inet.ip.forwarding=1
/usr/sbin/sysctl -w net.inet6.ip6.forwarding=1
# natd and ipfw are DEPRECATED. Use pfctl(8) instead with nat, e.g.
# nat on en0 from 10.0.0.0/8 to any -> (en0)
###/usr/sbin/natd -interface en0
###/sbin/ipfw add divert natd ip from any to any via en0
sudo vi /Library/LaunchDaemons/net.openvpn.enable-vpn-forward-nat.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd >
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.openvpn.enable-vpn-forward-nat</string>
<key>ProgramArguments</key>
<array>
<string>/Library/Application Support/vpn/enable-vpn-forward-nat.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
sudo launchctl load -w /Library/LaunchDaemons/net.openvpn.enable-vpn-forward-nat.plist
3.B. Integrate a NAT within the existing OS X Server pfctl rules in /etc/pf.conf and set up a launch daemon for pf. The essential pfctl NAT and filter rules are
nat on en0 from 10.0.0.0/8 to any -> (en0)
pass from { lo0, 10.0.0.0/8 } to any keep state
sudo vi /etc/pf.conf
# References for modifications:
# The Book of PF by Peter N.M. Hansteen
# http://hints.macworld.com/article.php?story=20121011004626997
# http://blog.scottlowe.org/2013/05/15/using-pf-on-os-x-mountain-lion/
# http://krypted.com/mac-security/a-cheat-sheet-for-using-pf-in-os-x-lion-and-up/
# Options
set block-policy drop
set fingerprints "/etc/pf.os"
set ruleset-optimization basic
set skip on lo0
# Normalization
# Scrub incoming packets
scrub in all no-df
#
# com.apple anchor point
#
scrub-anchor "com.apple/*"
# Queueing
# Translation
# OpenVPN Server NAT
#
# The Book of PF, p. 21
int_if = "en0" # macro for internal interface
localnet = "10.0.0.0/8"
nat on $int_if from $localnet to any -> ($int_if)
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"
# Filtering
lan_server = 10.0.1.3
# Antispoof
antispoof log quick for { lo0 en0 }
# Block by default
block in log
# Allow outgoing traffic from NAT'd { lo0, $localnet }
# The Book of PF, p. 21
pass from { lo0, $localnet } to any keep state
# Block to/from illegal destinations or sources
block in log quick from no-route to any
# Allow critical system traffic
pass in quick inet proto udp from any port 67 to any port 68
# Allow ICMP from home LAN
pass in log proto icmp from $lan_server:network
# Allow outgoing traffic
pass out inet proto tcp from any to any keep state
pass out inet proto udp from any to any keep state
# Internet services
internet_udp_services = "{ https, 500, 1194, 1701, 4500, 5060, 5190, 5297, 5298, 5678, 16384 }"
internet_tcp_services = "{ ssh, smtp, https, 143, 587, 993, 995, 1640, 2170, 2195, 2196, 4190,\
5218, 5223, 5190, 5220, 5222, 5298, 8008, 8443, 8800, 8843 }"
pass in quick inet proto tcp from any to { lo0, $lan_server } port $internet_tcp_services
pass in quick inet proto udp from any to { lo0, $lan_server } port $internet_udp_services
# LAN services: block access, except from localnet
lan_udp_services = "{ 5001 }"
lan_tcp_services = "{ domain, auth, nntp, www, 311, 3128, 5001, 5900:5909, 8118, 8123 }"
pass in quick inet proto tcp from { lo0, $localnet } to { lo0, $lan_server } port $lan_tcp_services
pass in quick inet proto udp from { lo0, $localnet } to { lo0, $lan_server } port $lan_udp_services
sudo vi /Library/LaunchDaemons/net.openbsd.pf.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.openbsd.pf.plist</string>
<key>Program</key>
<string>/sbin/pfctl</string>
<key>ProgramArguments</key>
<array>
<string>/sbin/pfctl</string>
<string>-e</string>
<string>-f</string>
<string>/etc/pf.conf</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ServiceDescription</key>
<string>OpenBSD Packet Filter (pf)</string>
<key>StandardErrorPath</key>
<string>/var/log/pf.log</string>
<key>StandardOutPath</key>
<string>/var/log/pf.log</string>
</dict>
</plist>
sudo launchctl load -w /Library/LaunchDaemons/net.openbsd.pf.plist
3.C. Finally, turn on OS X Server’s adaptive firewall if you like:
sudo /Applications/Server.app/Contents/ServerRoot/usr/sbin/serverctl enable service=com.apple.afctl
sudo /Applications/Server.app/Contents/ServerRoot/usr/libexec/afctl -f
Mac mini Server (Mid 2010), OS X Mountain Lion (10.8.2), OS X Server, EyeTV HD, Turbo.264 HD