Allowing network connections in OS X with active VPN only

There is a risk of data leakage through the default network connection that may occur while reconnecting to VPN servers or before a VPN connection is established. Therefore, it is necessary to allow accessing the network only when the VPN is up. This can be implemented with PF firewall (PF is available since OS X Lion).

Firewall rules

Add the following lines to the end of /etc/pf.conf:

anchor "org.vpnonly.pf"
load anchor "org.vpnonly.pf" from "/etc/pf.anchors/org.vpnonly.pf.rules"

Create /etc/pf.anchors/org.vpnonly.pf.rules file with the following lines:

# Options
set block-policy drop
set fingerprints "/etc/pf.os"
set ruleset-optimization basic
set skip on lo0

# Interfaces
vpn_intf = "{utun0 utun1 utun2 utun3}"

# Ports
allowed_vpn_ports = "{1:65535}"

# Table with allowed IPs
table <allowed_vpn_ips> persist file "/etc/pf.anchors/vpn.list" file "/etc/pf.anchors/custom.list"

# Block all outgoing packets
block out all

# Antispoof protection
antispoof for $vpn_intf

# Allow outgoing packets to specified IPs only
pass out proto icmp from any to <allowed_vpn_ips>
pass out proto {tcp udp} from any to <allowed_vpn_ips> port $allowed_vpn_ports

# Allow traffic for VPN interfaces
pass out on $vpn_intf all

allowed_vpn_ports helps to restrict access to only allowed ports. For example:

allowed_vpn_ports = "{22:443 50000:60000}"

to allow to connect only to ports in ranges 22-443 and 50000-60000.

Allowed IP addresses list

Create /etc/pf.anchors/vpn.list and /etc/pf.anchors/custom.list files:

re_ip_addr='[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
vpn_servers=$(host -T -W 2 -t A -4 all.vpn.zorrovpn.com | grep 'has address' | grep -E -o "$re_ip_addr")
dns_servers=$(grep -s "nameserver" "/etc/resolv.conf" | grep -E -o "$re_ip_addr" | sort | uniq)
if [[ ! -z $vpn_servers ]]
then 
  echo "$vpn_servers" | sudo tee /etc/pf.anchors/vpn.list > /dev/null
else
  echo Error creating vpn.list
fi
[[ ! -z $dns_servers ]] && echo "$dns_servers" | sudo tee /etc/pf.anchors/custom.list > /dev/null

Add a list of allowed IP addresses (addresses of VPN servers) to /etc/pf.anchors/vpn.list
Only one IP address per line, for example:

22.0.0.1
33.0.0.1

You can add Google Public DNS addresses and any other IP addresses (not related to VPN servers) to a custom allowed list - /etc/pf.anchors/custom.list:

8.8.8.8
8.8.4.4

Enabling PF

sudo pfctl -e -f /etc/pf.conf

Any connections to VPN server by hostname (instead of IP address) will be available only if you added system DNS to custom.list file. But if a configuration file has an option to connect by IP address also (true for ZorroVPN), connection to VPN server will start in a few seconds.

While using ZorroVPN you can download configuration file with "Do not use server's hostname in addition to IP address" option for preventing a delay.

Testing

ping -n -c 3 4.2.2.2
PING 4.2.2.2 (4.2.2.2): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
--- 4.2.2.2 ping statistics ---
3 packets transmitted, 0 packets received, 100.0% packet loss

Now access to the Internet is available only over VPN connection (e.g. over OpenVPN)

Disable PF

At the end of working with VPN connection you can disable PF with the followiing command:

sudo pfctl -d

Automatically start up

For modern OS X versions (e.g. El Capitan) with "Rootless" mode it's needed to turn this mode off before adding PF to automatically start up. Here you can see how to do it.

Then open /System/Library/LaunchDaemons/com.apple.pfctl.plist and add an -e (enable) to the pfctl ProgramArgument like this:

<key>ProgramArguments</key>
<array>
    <string>pfctl</string>
    <string>-e</string>
    <string>-f</string>
    <string>/etc/pf.conf</string>
</array>

Reboot the system and get back "Rootless" mode if it's needed, then boot the system normally. PF should be run automatically.


show comments