mirror of
https://github.com/sunknudsen/privacy-guides.git
synced 2025-02-22 16:53:56 +00:00
Added PF kill switch guide
This commit is contained in:
parent
9f0f86ca21
commit
7be7972ea8
@ -0,0 +1,508 @@
|
||||
<!--
|
||||
Title: How to configure self-hosted VPN kill switch using PF firewall on macOS
|
||||
Description: Learn how to configure self-hosted VPN kill switch using PF firewall on macOS.
|
||||
Author: Sun Knudsen <https://github.com/sunknudsen>
|
||||
Contributors: Sun Knudsen <https://github.com/sunknudsen>
|
||||
Publication date: 2020-08-21T15:42:23.029Z
|
||||
-->
|
||||
|
||||
# How to configure self-hosted VPN kill switch using PF firewall on macOS
|
||||
|
||||
> Heads up: when following this guide, IKEv2/IPsec VPNs will likely be unresponsive for about 60 seconds at boot and wake. Not sure what causes this issue. Please submit a [PR](https://github.com/sunknudsen/privacy-guides/pulls) if you know how to fix it!
|
||||
|
||||
## Requirements
|
||||
|
||||
- Self-hosted virtual private network (VPN) with public IPv4 address
|
||||
- Computer running macOS Mojave or Catalina
|
||||
|
||||
## Caveats
|
||||
|
||||
- When copy/pasting commands that start with `$`, strip out `$` as this character is not part of the command
|
||||
- When copy/pasting commands that start with `cat << "EOF"`, select all lines at once (from `cat << "EOF"` to `EOF` inclusively) as they are part of the same (single) command
|
||||
|
||||
## Guide
|
||||
|
||||
### Step 1: enable PF
|
||||
|
||||
Open "System Preferences", click "Security & Privacy", then "Firewall" and enable "Turn On Firewall".
|
||||
|
||||

|
||||
|
||||
Then, click "Firewall Options...", disable all options except "Enable stealth mode".
|
||||
|
||||

|
||||
|
||||
### Step 2: confirm PF is enabled
|
||||
|
||||
```console
|
||||
$ sudo pfctl -s info | grep "Status"
|
||||
No ALTQ support in kernel
|
||||
ALTQ related functions disabled
|
||||
Status: Enabled for 0 days 13:02:35 Debug: Urgent
|
||||
```
|
||||
|
||||
Status: Enabled
|
||||
|
||||
👍
|
||||
|
||||
### Step 3: backup and override `/etc/pf.conf`
|
||||
|
||||
```shell
|
||||
sudo cp /etc/pf.conf /etc/pf.conf.backup
|
||||
cat << "EOF" | sudo tee /etc/pf.conf
|
||||
anchor "local.pf"
|
||||
load anchor local.pf from "/etc/pf.anchors/local.pf"
|
||||
EOF
|
||||
```
|
||||
|
||||
### Step 4: list hardware network interfaces
|
||||
|
||||
```console
|
||||
$ networksetup -listallhardwareports
|
||||
|
||||
Hardware Port: Wi-Fi
|
||||
Device: en0
|
||||
Ethernet Address: Redacted
|
||||
|
||||
Hardware Port: Thunderbolt 1
|
||||
Device: en1
|
||||
Ethernet Address: Redacted
|
||||
|
||||
Hardware Port: Thunderbolt 2
|
||||
Device: en2
|
||||
Ethernet Address: Redacted
|
||||
|
||||
Hardware Port: Bluetooth PAN
|
||||
Device: en3
|
||||
Ethernet Address: Redacted
|
||||
|
||||
Hardware Port: iPhone USB
|
||||
Device: en4
|
||||
Ethernet Address: Redacted
|
||||
|
||||
Hardware Port: Thunderbolt Ethernet
|
||||
Device: en5
|
||||
Ethernet Address: Redacted
|
||||
|
||||
Hardware Port: Thunderbolt Bridge
|
||||
Device: bridge0
|
||||
Ethernet Address: Redacted
|
||||
|
||||
VLAN Configurations
|
||||
===================
|
||||
```
|
||||
|
||||
### Step 4: find hardware network interface subnet prefix (example bellow is for `Wi-Fi` interface)
|
||||
|
||||
```console
|
||||
$ networksetup -getinfo "Wi-Fi"
|
||||
DHCP Configuration
|
||||
IP address: 10.0.1.140
|
||||
Subnet mask: 255.255.255.0
|
||||
Router: 10.0.1.1
|
||||
Client ID:
|
||||
IPv6: Off
|
||||
Wi-Fi ID: Redacted
|
||||
```
|
||||
|
||||
Use following table to find bitmask using subnet mask.
|
||||
|
||||
For example, if subnet mask is `255.255.255.0`, bitmask is `/24` and subnet prefix is `10.0.1.0/24`.
|
||||
|
||||
| Subnet mask | Bitmask |
|
||||
| --------------- | ------- |
|
||||
| 0.0.0.0 | /0 |
|
||||
| 128.0.0.0 | /1 |
|
||||
| 192.0.0.0 | /2 |
|
||||
| 224.0.0.0 | /3 |
|
||||
| 240.0.0.0 | /4 |
|
||||
| 248.0.0.0 | /5 |
|
||||
| 252.0.0.0 | /6 |
|
||||
| 254.0.0.0 | /7 |
|
||||
| 255.0.0.0 | /8 |
|
||||
| 255.128.0.0 | /9 |
|
||||
| 255.192.0.0 | /10 |
|
||||
| 255.224.0.0 | /11 |
|
||||
| 255.240.0.0 | /12 |
|
||||
| 255.248.0.0 | /13 |
|
||||
| 255.252.0.0 | /14 |
|
||||
| 255.254.0.0 | /15 |
|
||||
| 255.255.0.0 | /16 |
|
||||
| 255.255.128.0 | /17 |
|
||||
| 255.255.192.0 | /18 |
|
||||
| 255.255.224.0 | /19 |
|
||||
| 255.255.240.0 | /20 |
|
||||
| 255.255.248.0 | /21 |
|
||||
| 255.255.252.0 | /22 |
|
||||
| 255.255.254.0 | /23 |
|
||||
| 255.255.255.0 | /24 |
|
||||
| 255.255.255.128 | /25 |
|
||||
| 255.255.255.192 | /26 |
|
||||
| 255.255.255.224 | /27 |
|
||||
| 255.255.255.240 | /28 |
|
||||
| 255.255.255.248 | /29 |
|
||||
| 255.255.255.252 | /30 |
|
||||
| 255.255.255.254 | /31 |
|
||||
| 255.255.255.255 | /32 |
|
||||
|
||||
### Step 5: set temporary environment variables
|
||||
|
||||
`KILLSWITCH_HARDWARE_INTERFACES` should include all used hardware network interfaces.
|
||||
|
||||
`KILLSWITCH_VPN_INTERFACE` should be set to VPN interface (use `ifconfig` to find interface).
|
||||
|
||||
`KILLSWITCH_TRUSTED_SUBNET_PREFIXES` should include all trusted subnet prefixes such as a home or office subnet prefixes (if trusted).
|
||||
|
||||
`KILLSWITCH_VPN_ENDPOINT_IPS` should include all VPN endpoint IPs.
|
||||
|
||||
```shell
|
||||
KILLSWITCH_HARDWARE_INTERFACES="{ en0, en4, en5 }"
|
||||
KILLSWITCH_TRUSTED_SUBNET_PREFIXES="{ 10.0.1.0/24 }"
|
||||
KILLSWITCH_VPN_INTERFACE=ipsec0
|
||||
KILLSWITCH_VPN_ENDPOINT_IPS="{ 185.193.126.203 }"
|
||||
```
|
||||
|
||||
### Step 6: create PF strict anchor
|
||||
|
||||
This anchor blocks everything except DHCP and VPN requests.
|
||||
|
||||
```shell
|
||||
cat << EOF | sudo tee /etc/pf.anchors/local.pf.strict
|
||||
# Options
|
||||
set block-policy drop
|
||||
set ruleset-optimization basic
|
||||
set skip on lo0
|
||||
|
||||
# Set variables
|
||||
hardware_interfaces = "$KILLSWITCH_HARDWARE_INTERFACES"
|
||||
vpn_endpoint_ips = "$KILLSWITCH_VPN_ENDPOINT_IPS"
|
||||
vpn_interface = "$KILLSWITCH_VPN_INTERFACE"
|
||||
|
||||
# Block everything
|
||||
block all # Use "block log all" to log blocked packets
|
||||
|
||||
# Allow DHCP requests (used to establish Wi-Fi connection)
|
||||
pass on \$hardware_interfaces proto udp from port { 67, 68 } to port { 67, 68 } keep state
|
||||
|
||||
# Allow requests to VPN server (used to establish VPN connection)
|
||||
pass on \$hardware_interfaces proto { tcp, udp } from any to \$vpn_endpoint_ips
|
||||
|
||||
# Allow all requests on VPN interface
|
||||
pass on \$vpn_interface all
|
||||
EOF
|
||||
sudo chmod 600 /etc/pf.anchors/local.pf.strict
|
||||
```
|
||||
|
||||
### Step 7: create PF trusted anchor
|
||||
|
||||
Same as strict but allows multicast DNS and local network requests.
|
||||
|
||||
```shell
|
||||
cat << EOF | sudo tee /etc/pf.anchors/local.pf.trusted
|
||||
# Options
|
||||
set block-policy drop
|
||||
set ruleset-optimization basic
|
||||
set skip on lo0
|
||||
|
||||
# Set variables
|
||||
hardware_interfaces = "$KILLSWITCH_HARDWARE_INTERFACES"
|
||||
trusted_subnet_prefixes = "$KILLSWITCH_TRUSTED_SUBNET_PREFIXES"
|
||||
vpn_endpoint_ips = "$KILLSWITCH_VPN_ENDPOINT_IPS"
|
||||
vpn_interface = "$KILLSWITCH_VPN_INTERFACE"
|
||||
|
||||
# Block everything
|
||||
block all # Use "block log all" to log blocked packets
|
||||
|
||||
# Allow DHCP requests (used to establish Wi-Fi connection)
|
||||
pass on \$hardware_interfaces proto udp from port { 67, 68 } to port { 67, 68 } keep state
|
||||
|
||||
# Allow multicast DNS requests (used to find devices using Bonjour, disable these lines when you don’t trust the network)
|
||||
pass on \$hardware_interfaces from \$trusted_subnet_prefixes to 255.255.255.255 keep state
|
||||
pass on \$hardware_interfaces from 255.255.255.255 to \$trusted_subnet_prefixes keep state
|
||||
pass on \$hardware_interfaces proto udp from \$trusted_subnet_prefixes port 5353 to 224.0.0.251 port 5353 keep state
|
||||
pass on \$hardware_interfaces proto udp from 224.0.0.251 port 5353 to \$trusted_subnet_prefixes port 5353 keep state
|
||||
|
||||
# Allow local network requests (used to access local network, disable this line when you don’t trust the network)
|
||||
pass on \$hardware_interfaces proto { tcp, udp } from \$trusted_subnet_prefixes to \$trusted_subnet_prefixes
|
||||
|
||||
# Allow requests to VPN server (used to establish VPN connection)
|
||||
pass on \$hardware_interfaces proto { tcp, udp } from any to \$vpn_endpoint_ips
|
||||
|
||||
# Allow all requests on VPN interface
|
||||
pass on \$vpn_interface all
|
||||
EOF
|
||||
sudo chmod 600 /etc/pf.anchors/local.pf.trusted
|
||||
```
|
||||
|
||||
### Step 8: create `/etc/pf.anchors/local.pf` symlink
|
||||
|
||||
```shell
|
||||
sudo ln -s /etc/pf.anchors/local.pf.strict /etc/pf.anchors/local.pf
|
||||
```
|
||||
|
||||
### Step 9: restart PF
|
||||
|
||||
```shell
|
||||
sudo pfctl -F all -f /etc/pf.conf
|
||||
```
|
||||
|
||||
### Step 10: create `/usr/local/sbin` folder
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /usr/local/sbin
|
||||
sudo chown $(whoami):admin /usr/local/sbin/
|
||||
```
|
||||
|
||||
### Step 11: source `/usr/local/sbin` folder
|
||||
|
||||
Find which shell is configured using `echo $SHELL`.
|
||||
|
||||
#### Bash (/bin/bash)
|
||||
|
||||
```shell
|
||||
cat << "EOF" >> ~/.bash_profile
|
||||
export PATH=${PATH}:/usr/local/sbin
|
||||
EOF
|
||||
source ~/.bash_profile
|
||||
```
|
||||
|
||||
#### Z Shell (/bin/zsh)
|
||||
|
||||
```shell
|
||||
cat << "EOF" >> ~/.zshrc
|
||||
export PATH=${PATH}:/usr/local/sbin
|
||||
EOF
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
### Step 12: create `/usr/local/sbin/strict.sh` convenience script
|
||||
|
||||
Use `socketfilterfw` to block specific apps.
|
||||
|
||||
```shell
|
||||
cat << "EOF" > /usr/local/sbin/strict.sh
|
||||
#! /bin/sh
|
||||
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
green=$'\e[1;32m'
|
||||
end=$'\e[0m'
|
||||
|
||||
# /usr/libexec/ApplicationFirewall/socketfilterfw --blockapp /Applications/1Password\ 7.app
|
||||
# /usr/libexec/ApplicationFirewall/socketfilterfw --blockapp /usr/local/Cellar/squid/4.8/sbin/squid
|
||||
# printf "\n"
|
||||
|
||||
ln -sfn /etc/pf.anchors/local.pf.strict /etc/pf.anchors/local.pf
|
||||
|
||||
pfctl -e
|
||||
|
||||
printf "\n"
|
||||
|
||||
pfctl -F all -f /etc/pf.conf
|
||||
|
||||
printf "\n%s" "${green}Strict mode enabled${end}"
|
||||
EOF
|
||||
chmod +x /usr/local/sbin/strict.sh
|
||||
```
|
||||
|
||||
### Step 13: create `/usr/local/sbin/trusted.sh` convenience script
|
||||
|
||||
Use `socketfilterfw` to unblock specific apps (useful to allow 1Password’s [local sync](https://www.youtube.com/watch?v=eu3iP1njMRI) or Squid proxy for example).
|
||||
|
||||
```shell
|
||||
cat << "EOF" > /usr/local/sbin/trusted.sh
|
||||
#! /bin/sh
|
||||
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
red=$'\e[1;31m'
|
||||
end=$'\e[0m'
|
||||
|
||||
function disable()
|
||||
{
|
||||
/usr/local/sbin/strict.sh
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap disable EXIT
|
||||
|
||||
# /usr/libexec/ApplicationFirewall/socketfilterfw --unblockapp /Applications/1Password\ 7.app
|
||||
# /usr/libexec/ApplicationFirewall/socketfilterfw --unblockapp /usr/local/Cellar/squid/4.8/sbin/squid
|
||||
# printf "\n"
|
||||
|
||||
ln -sfn /etc/pf.anchors/local.pf.trusted /etc/pf.anchors/local.pf
|
||||
|
||||
pfctl -e
|
||||
|
||||
printf "\n"
|
||||
|
||||
pfctl -F all -f /etc/pf.conf
|
||||
|
||||
printf "\n%s\n\n" "${red}Trusted mode enabled (press ctrl+c to disable)${end}"
|
||||
|
||||
while :
|
||||
do
|
||||
sleep 60
|
||||
done
|
||||
EOF
|
||||
chmod +x /usr/local/sbin/trusted.sh
|
||||
```
|
||||
|
||||
### Step 14: create `/usr/local/sbin/disabled.sh` convenience script
|
||||
|
||||
```shell
|
||||
cat << "EOF" > /usr/local/sbin/disabled.sh
|
||||
#! /bin/sh
|
||||
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
red=$'\e[1;31m'
|
||||
end=$'\e[0m'
|
||||
|
||||
function disable()
|
||||
{
|
||||
/usr/local/sbin/strict.sh
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap disable EXIT
|
||||
|
||||
pfctl -d
|
||||
|
||||
printf "\n%s\n\n" "${red}Firewall disabled (press ctrl+c to enable)${end}"
|
||||
|
||||
while :
|
||||
do
|
||||
sleep 60
|
||||
done
|
||||
EOF
|
||||
chmod +x /usr/local/sbin/disabled.sh
|
||||
```
|
||||
|
||||
### Step 15: test convenience scripts
|
||||
|
||||
```console
|
||||
$ sudo strict.sh
|
||||
Password:
|
||||
No ALTQ support in kernel
|
||||
ALTQ related functions disabled
|
||||
pfctl: pf already enabled
|
||||
|
||||
pfctl: Use of -f option, could result in flushing of rules
|
||||
present in the main ruleset added by the system at startup.
|
||||
See /etc/pf.conf for further details.
|
||||
|
||||
No ALTQ support in kernel
|
||||
ALTQ related functions disabled
|
||||
rules cleared
|
||||
nat cleared
|
||||
dummynet cleared
|
||||
0 tables deleted.
|
||||
64 states cleared
|
||||
source tracking entries cleared
|
||||
pf: statistics cleared
|
||||
pf: interface flags reset
|
||||
|
||||
Strict mode enabled
|
||||
|
||||
$ sudo trusted.sh
|
||||
No ALTQ support in kernel
|
||||
ALTQ related functions disabled
|
||||
pfctl: pf already enabled
|
||||
|
||||
pfctl: Use of -f option, could result in flushing of rules
|
||||
present in the main ruleset added by the system at startup.
|
||||
See /etc/pf.conf for further details.
|
||||
|
||||
No ALTQ support in kernel
|
||||
ALTQ related functions disabled
|
||||
rules cleared
|
||||
nat cleared
|
||||
dummynet cleared
|
||||
0 tables deleted.
|
||||
6 states cleared
|
||||
source tracking entries cleared
|
||||
pf: statistics cleared
|
||||
pf: interface flags reset
|
||||
|
||||
Trusted mode enabled (press ctrl+c to disable)
|
||||
|
||||
$ sudo disabled.sh
|
||||
No ALTQ support in kernel
|
||||
ALTQ related functions disabled
|
||||
pf disabled
|
||||
|
||||
Firewall disabled (press ctrl+c to enable)
|
||||
```
|
||||
|
||||
### Step 16: make sure PF is set to strict at boot
|
||||
|
||||
```shell
|
||||
cat << EOF | sudo tee /Library/LaunchDaemons/local.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>pf</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/sbin/strict.sh</string>
|
||||
</array>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Want things back the way they were before following this guide? No problem!
|
||||
|
||||
### Step 1: restore `/etc/pf.conf` from backup
|
||||
|
||||
```shell
|
||||
sudo cp /etc/pf.conf.backup /etc/pf.conf
|
||||
```
|
||||
|
||||
### Step 2: delete anchors, convenience scripts and launch daemon
|
||||
|
||||
#### Delete anchors
|
||||
|
||||
```shell
|
||||
sudo rm /etc/pf.anchors/local.pf
|
||||
sudo rm /etc/pf.anchors/local.pf.strict
|
||||
sudo rm /etc/pf.anchors/local.pf.trusted
|
||||
```
|
||||
|
||||
#### Delete convenience scripts
|
||||
|
||||
```shell
|
||||
rm /usr/local/sbin/strict.sh
|
||||
rm /usr/local/sbin/trusted.sh
|
||||
rm /usr/local/sbin/disabled.sh
|
||||
```
|
||||
|
||||
#### Delete launch daemon
|
||||
|
||||
```shell
|
||||
sudo rm /Library/LaunchDaemons/local.pf.plist
|
||||
```
|
||||
|
||||
### Step 3: restart PF
|
||||
|
||||
```shell
|
||||
sudo pfctl -F all -f /etc/pf.conf
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 172 KiB |
Binary file not shown.
After Width: | Height: | Size: 120 KiB |
Loading…
x
Reference in New Issue
Block a user