PF is the preferred packet filtering/firewalling solution on BSD derivatives (similar to Linux's IPTables). This document describes PF in general, and a particular project to create a corporate firewall.
We'll repurposed a 1U server to replace a very aged firewall appliance with an OpenBSD PF firewall. This makes our firewall more secure, more flexible, and easier to service or replace in the event of a hardware failure.
We'll call this new firewall box `argus`.
------------------- | | NIC0 (LAN) <---> [= =] <---> NIC2 (T1 Internet) inside | argus | outside NIC1 (DMZ) <---> [= =] <---> NIC3 (Cable Internet) | | -------------------
We'll have four network interface cards:
The outside internet connections (NIC2 and NIC3) will be configured for mutual failover—if one goes down, the other one will take over its duties as much as possible. Core internet services like mail and vendor integration will default to the more reliable T1 (NIC2). Less critical traffic like employee web access will default to the faster cable connection (NIC3).
Network address translation will occur for clients on the inside LAN and the DMZ, with a couple of exceptions. The T1 (NIC2) will have its own static IP address, and also be aliased with the IP address of our public web server.
PF is OpenBSD's Packet Filter subsystem. PF inspects and handles network packets at the protocol and port level. PF classifies packets based on protocol, port, packet type, source or destination address. With a reasonable degree of certainty it is also able to classify packets based on source operating system.
Although, strictly speaking, NAT is not packet filtering, for practical reasons PF handles NAT too. The same if true of load balancing and traffic shaping—they've been rolled into PF for practical reasons.
PF does not inspect packet _contents_ (i.e.—application level stuff), although PF can hand off such application level filtering to other applications in some cases.
All of these technologies are configured in the `/etc/pf.conf` config file, and controlled with the `pfctl` command line tool.
PF performs as well or better under stress than OpenBSD's former IPFilter system or Linux iptables on the same hardware. The filtering overhead of PF is negligible. A Pentium III with 512MB RAM should be an adequate PF firewall.
export PKG_PATH=ftp://ftp.openbsd.org/pub/OpenBSD/5.2/packages/i386/ pkg_add vim
Unlike Linux, where network devices are named like `eth0`, `eth1`, etc., BSD network devices are named after their driver (`em0`, `em1`, `sk0`, and `sk1` on argus).
On recent versions of OpenBSD, PF is enabled by default, so we don't need to turn it on or start it.
Turn on packet forwarding so we act like a gateway. The command sudo sysctl net.inet.ip.forwarding=1
turns on forwarding, but for the change to persist after reboot, add these lines to /etc/sysctl.conf:
net.inet.ip.forwarding=1 net.inet6.ip6.forwarding=1
To check the rules of the /etc/pf.conf file without actually loading them:
pfctl -nf /etc/pf.conf
To actually load the rules in /etc/pf.conf:
sudo pfctl -f /etc/pf.conf
Report on pf's status:
sudo pfctl -s info
View each rule:
sudo pfctl -vgs rulesN.B.: PF reads rules from top to bottom; the last rule in a rule set that matches a packet or connection is the one that is applied. However, the
quick
keyword (e.g.—pass quick in...
) tells pf to not look any further down the list of rules if the packet matches this rule.A Minimal pf.conf
block in all pass out allThe first line (
block in all
) blocks all inbound traffic. The second line (pass out all
) allows all outbound traffic.Lists and Macros
Lists and macros increase the readability of a rule set. A list is two or more things of the same type contained in curly brackets. A macro is a variable—a variable assigned with an equal sign and dereferenced with a dollar sign.
pass proto tcp to port {21 80 8080} my_special_host = 10.0.190.1 pass proto tcp to $my_special_host port 8080PF automatically understands the mappings between ports and service names defined in /etc/services, so anywhere in pf.conf you can use a port number like "22" it's also valid to use a service name like "ssh".
Final pf.conf for argus
######## Macros ######## lan_if = "nic0" dmz_if = "nic1" t1_if = "nic2" cable_if = "nic3" lan_network = $lan_if:network dmz_network = $dmz_if:network t1_network = $t1_if:network cable_network = $cable_if:network public_internet = {$t1_network, $cable_network} public_webserver = "***.***.215.142" public_mailserver = "***.***.204.214" vital_udp_services = "{domain, ntp}" ######## NAT ######## match out on $t1_if from $lan_network nat-to ($t1_if) match out on $cable_if from $lan_network nat-to ($cable_if) match out on $t1_if from $dmz_network nat-to ($t1_if) match out on $cable_if from $dmz_network nat-to ($cable_if) ######## Rules ######## block all pass from {lo0, $lan_network} pass quick inet proto {tcp, udp} from {$lan_network, $dmz_network} to port $vital_udp_services pass proto tcp to $public_webserver port http pass from $dmz_network to $public_internet port {http, https} pass inet proto tcp from $lan_network to $dmz_network port sshFurther Reading
If you're a serious/professional PF user, I strongly recommend purchasing _The Book of PF_ by Peter N.M. Hansteen.
© Paul Gorman