Skip to content

Instantly share code, notes, and snippets.

@magicgoose
Forked from sonicdoe/README.md
Last active June 3, 2016 11:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save magicgoose/9a05cc9ae10bfd249675ba6d6ef22962 to your computer and use it in GitHub Desktop.
Save magicgoose/9a05cc9ae10bfd249675ba6d6ef22962 to your computer and use it in GitHub Desktop.
My OS X “VPN only” Setup For Everyday Life. Credits: @scy, @SonicHedgehog

My OS X “VPN only” Setup For Everyday Life

Black Hat Hackers, ISPs, evil governments, script kiddies, etc. get bored easily, and when they’re bored, they’re starting to look for things to play with. And a network with several million connected users is certainly an interesting thing to play with. Some of them might start intercepting the data on the network or do other nasty things with the packets that they can get.

If these packets are encrypted, messing with them is much harder (but not impossible! – see the end of this article). So you want your packets to be always encrypted. And the best way to do that is by using a VPN.

Of course, if your VPN server is located in some unsafe place, this guide won't magically save you from all bad things. So make sure to get a good server first.

Target audience

Please note that this guide is written for users who are at least a bit tech-savvy. In case you don’t know what a VPN is or don’t feel comfortable doing the things I suggest in this guide (for example because you don’t understand anything at all), please contact a hacker you trust and ask them to make your connection more secure. Please don’t ask me (except if we know each other personally), because I don’t have the time to do user support for thousands of guests.

What’s being used?

The machine I’m using is a MacBook Pro with OS X El Capitan, and the software I use is Tunnelblick for the OpenVPN connection and PF (“packet filter”) to transmit as little unencrypted traffic as possible when the VPN goes down. And the focus of this guide is definitely that second part, because setting up the VPN is pretty easy, but if the connection breaks, is blocked or for some other reason not running, your system will by default transmit data anyway, and unencrypted data will be interceptable. So what I basically do is to forbid nearly all network traffic (using PF), except for the things that are required to connect to the VPN. PF is OS X’s recommended way of filtering packets and available in Lion or higher, afaik.

The basic idea

So, what do we need in order to connect to the VPN?

  1. Internet access. This means we should allow DHCP (for getting an IP address).
  2. DNS, if we don’t want to connect to the VPN by IP address. I’m using some server with static IP, so I don't need DNS.
  3. A connection to the VPN server(s) on whatever port the VPN uses. I’m using OpenVPN on an UDP port, and I highly recommend that you don’t use a TCP-based VPN connection. (Else, people might(?) be able to do evil things with DNS spoofing and SRV records.)

The config I use has several drawbacks:

  1. I completely disable IPv6. Yes, some of you will be outraged about that, and rightly so. But I find it rarely used, and I don't want to spend time configuring and testing it. Maybe after a few years I'll update this for IPv6.
  2. There’s no automatic detection whatsoever whether OpenVPN is running, so if you want to use the Internet without the VPN again, you’ll need to explicitly disable PF.
  3. It will most likely not work with so-called "Captive portals" used in many public Wi-Fi access points, for obvious reasons. I currently don't see an easy fix for that, other than temporarily manually disabling PF (dangerous!).

Resources

There’s a nice guide that explains the PF setup on OS X, and I’m not doing anything more than that here. A tutorial on PF itself, which is a OpenBSD project, is available as well.

Set up OpenVPN and Tunnelblick

I’m using Tunnelblick as my OpenVPN GUI. Set up your OpenVPN server, get a client certificate and the ovpn config, create the one-file client config (*.ovpn) so that it will have certificate bundled, open that in Tunnelblick, make sure that you enable “route all traffic through the VPN” in the “while connected” tab of the advanced settings. According to the Tunnelblick documentation, this is equivalent to the OpenVPN option --redirect-gateway def1.

I found Tunnelblick (3.3) to be kind of unstable on my machine. Sometimes I had to terminate OpenVPN myself (using sudo killall openvpn) because it wouldn’t reconnect and Tunnelblick wasn’t able to terminate it. But since the PF rules protect me from unencrypted communication, I don’t really care.

Having an exit strategy

If you fuck up your firewall rules, you might end up in a situation where you can’t even google how to fix things again. Therefore, keep this command in mind: sudo pfctl -d. It will completely disable PF.

The PF config

There’s a default PF configuration file, /etc/pf.conf, and I suggest you don’t modify it. Instead, write your own. Mine is /etc/pf.magicgoose.conf.

In that file, you need to define one or more anchors which contain the actual rules. Therefore, my config file only contains these two lines:

anchor "magicgoose.pf"
load anchor "magicgoose.pf" from "/etc/pf.anchors/magicgoose"

Use something unique for the anchor name. The suggested way is to use a reversed domain, so if you own flauschmett.de, your anchor could be named de.flauschmett.pf. I decided to violate this rule, though.

The really interesting part of the configuration is what’s written in the anchor file, /etc/pf.anchors/magicgoose in my case. Since that file is a bit longer and contains lots of comments (which is a good thing), it's in a separate file in the gist (the next file).

Loading it

Run sudo pfctl -v -n -f /etc/pf.magicgoose.conf to check the config for errors, and sudo pfctl -e -f /etc/pf.magicgoose.conf to actually load it.

Making sure it will be loaded after rebooting

I guess you’ll want your PF settings to survive a reboot, so you need to add a launchd item for it. This is simply an XML file that you place in /Library/LaunchDaemons. Note that it must be owned by root in order to do anything at all.

Do something like sudo nano -w /Library/LaunchDaemons/magicgoose.pf.plist (pay attention to the .plist extension) and paste the text from the third file in the gist (same name) there.

Please note that this might not work. The article I’ve linked to above suggests doing it this way, but on my machine /var/log/pf.log contained the error message

pfctl: DIOCADDRULE: Resource busy

and the rules were not loaded. So please double-check if your PF rules have been loaded, and load them manually, if necessary. If you reboot and something starts syncing without the VPN running, you’re doing it wrong. ;)

I currently don’t know why this is happening, and certainly not how to fix it. Sorry.

# This is my PF config for everyday life. Its purpose is to make sure that my system uses
# the OpenVPN connection for everything (this is controlled by
# OpenVPN/Tunnelblick's routing) and not to communicate unencrypted when the VPN
# connection goes down. Therefore, I block everything on the physical interfaces
# except for DHCP and the VPN connection.
# Define interfaces. I'm on a MacBook Pro, so en0 is WiFi.
wifi=en0
vpn=utun0
# Default block policy is sending a RST. This allows applications to quickly
# notice that they are not allowed to connect.
set block-policy return
# Don't filter on local loopback or the VPN interface.
set skip on { lo0 $vpn }
# Scrub all incoming packets.
scrub in all
# Don't allow IPv6 at all.
block quick inet6
# By default, don't allow anything on the actual physical links and on the interfaces I don't use in everyday life.
block on { $wifi en1 en2 p2p0 awdl0 gif0 stf0 }
# If you want to allow ICMP, uncomment this.
# pass quick proto icmp
# Allow DHCP.
# I could probably be more specific than "from any", but didn't find a way to
# specify the link's local network universally. "from $wifi:network" works, but
# "from $ether:network" for example doesn't as long as $ether does not exist.
pass quick on { $wifi } proto udp from any port 67:68
# Uncomment this if you want to allow DNS to Google, although on a rogue network they could still easily
# redirect you to another DNS server.
# pass out quick proto udp to { 8.8.8.8 8.8.4.4 } port 53
# Allow VPN. Replace YOUR_SERVER_IP and YOUR_SERVER_PORT with the actual values, of course.
# Also if you need to access it by SSH, you might need to alter this rule. This is an excercise left for the reader.
pass quick proto udp to YOUR_SERVER_IP port YOUR_SERVER_PORT
# vim: ft=pf
<?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>magicgoose.pf</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>ipconfig waitall &amp;&amp; /sbin/pfctl -e -f /etc/pf.magicgoose.conf</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/Library/Logs/magicgoose.pf.log</string>
<key>StandardOutPath</key>
<string>/Library/Logs/magicgoose.pf.log</string>
</dict>
</plist>
anchor "magicgoose.pf"
load anchor "magicgoose.pf" from "/etc/pf.anchors/magicgoose"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment