WIP!
Most of this site is incomplete, and the current state is available as an open draft. Most of the text here is likely incomplete, misinformed, or just plain wrong. I'm looking for feedback on my website, so that I can:
- Fill in what I'm missing
- Take out what's unnecessary
- Figure out my target audience
- Find the right way to structure the site
- Filter out any errors
To anyone who wants to send me feedback, thank you, and shoot me an email!
OpenBSD’s Packet Filter controls the routing of individual packets sent between the local machine and the world. We can use PF to restrict outsiders from only accessing public-facing services (HTTPS, SSH etc), prevent local programs from accessing parts of the internet it shouldn’t, and block abusive addresses from connecting to the machine.
Packet filtering is the process of managing policies to handle network packets based on the source/destination address, interface, port, protocol, and other packet metadata.
It provides an extremely inexpensive layer of security, and we can restrict machines on the Internet to only access the services (SSH, websites etc) you want them to, and it can also be used to prevent programs on the server itself from accessing parts of the internet that it shouldn’t.
OpenBSD provides pf(8)
, a packet filter that runs within the kernel itself.
Initial Setup
By default, PF is already enabled on startup. You are able to interact with it via pfctl to enable, disable, get the status of, and generally control the filter:
$ doas pfctl -e # enable pf
$ doas pfctl -d # disable pf
$ doas pfctl -s rules # list the rules it currently knows
$ doas pfctl -s info # list general filter info
$ doas pfctl -vs info # list even more filter info
On a fresh 7.0 machine, pfctl -s rules
may look like:
block return all
pass all flags S/SA
block return in on ! lo0 proto tcp from any to any port 6000:6010
block return out log proto tcp all user = 55
block return out log proto udp all user = 55
These rules are taught to PF from the file /etc/pf.conf:
# $OpenBSD: pf.conf,v 1.55 2017/12/03 20:40:04 sthen Exp $
#
# See pf.conf(5) and /etc/examples/pf.conf
set skip on lo
block return # block stateless traffic
pass # establish keep-state
# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp to port 6000:6010
# Port build user does not need network
block return out log proto {tcp udp} user _pbuild
The first line, set skip on lo
, is not a filter rule per se, but a directive to tell pf that all packets moving around locally (through the lo
-titled “loopback” network device) should not get filtered.
Every packet going through the filter is reviewed by each rule, each rule in order tags the packet as “block”, “pass”, and other details.
For example, a packet from anywhere for HTTP (default port 22) will have the block return
and pass
rules apply to them.
Since the second rule is the last one applied, any HTTP traffic is passed through.
However, any outgoing tcp/udp traffic from the user _pbuild
is blocked, since that system user does not need traffic – and if it tried to access the network, it’s possible that something compromised the user, so the packets are logged as well.
Because rules are applied in order, the general pattern here is to put the broadest rules in the beginning, and then follow up with gradually more specific rules.
Limit Incoming Traffic to Public Services
The default filter rules for incoming traffic act as a blacklist – pass by default, and then explicitly block X11 ports. Let’s instead create a whitelist, so that pf only allows incoming connections to the services we explicitly list.
set skip on lo
block return in all # block all incoming traffic by default
pass out all # allow all outgoing traffic by default
# This rule allows incoming HTTP and HTTPS traffic,
# which comes through ports 80 and 443 respectively.
# The phrase "on egress" matches traffic coming in from the "egress" networking
# group, which is usually configured to connect to the outside world.
pass in on egress proto tcp to port {80 443}
# This rule is equivalent, but uses the human names
# for ports described in /etc/services:
pass in on egress proto tcp to port {http https}
# Let's still block outgoing traffic from _pbuild
block return out log proto {tcp udp} user _pbuild
Replace the current ruleset with the new rules saved:
$ doas pfctl -f /etc/pf.conf
You can also syntax-check the ruleset without applying any changes:
$ pfctl -nf /etc/pf.conf
If the ruleset is valid, PF will be completely silent. Otherwise, it will report every line number it doesn’t like, with the reason why.
Using Address Tables
PF allows the use of tables to deal with large sets of addresses:
# Declare a table to hold a collection of spammer addresses.
# The keyword "persist" keeps the spamlist in memory even when no rules refer to it.
table <spamlist> persist
# Immediately block any addresses from the spamlist, no exceptions.
block in quick from <spamlist>
To manually add or remove addresses from a table:
$ doas pfctl -t TABLE -T add IP_ADDRESS
$ doas pfctl -t TABLE -T delete IP_ADDRESS
Fine-Tuning Rules
You can use two most common types of blocking:
# Ghost the sender; don't tell them that they're rejected
block …
block drop …
# Tell the sender that their packets aren't reached
block return …
To deny outgoing tcp ports:
block proto tcp to port PORT
To block hardcoded IP addresses:
block proto tcp from IP_ADDRESS
To rate-limit connections to a port:
table <abusive_hosts> persist
block in quick from <abusive_hosts>
# The specific settings here rate-limit the port to 100 max connections
# per machine, at a maximum rate of 15 connections per 5 minutes.
# Any machine that breaks these rate-limits are put in the abusive hosts table.
pass in on egress proto tcp to port PORT flags S/SA keep state \
(max-src-conn 100, max-src-conn-rate 15/5, \
overload <abusive_hosts> flush)