PF Firewall best practices / standard ruleset

Hi,

I have been trying to find something like a standard / best practices ruleset for a home router for quite some time, to no avail. Something like you'd expect to find on any standard home router you can buy off the shelf. "Specification":
  • one WAN interface
  • one LAN interface
  • NAT for IPv4
  • Gateway for IPv4
  • All incoming stuff blocked from WAN
  • All incoming stuff allowed from LAN (or maybe only allow certain services here... optional)
  • All outgoing/forwarding stuff allowed
So much seems straightforward and I hand-crafted a config for that purpose on my own. (That machine also runs KEA DHCP and PowerDNS services, just as background info.)

However then come the not so straightforward things:
  • "Do the right thing" for IPv6 (I am new to IPv6 but finally want to tackle this... The full story is probably out of "pf" scope, but as far as "pf" is concerned, do "the right thing" to be a functional IPv6 firewall/gateway)
  • All the fancy special rules you can read in live/production configs on home routers about bogon networks, about martians, about special rules for ICMP, ICMPv6, etc, etc. Prevent flooding attacks, detect attacks, no clue what actually is today's "standard best practice" to setup a standard home firewall/gateway without too much extravagancies like subscribing to services who provide real-time information about botnet IPs or whatever you could imagine here
  • Optionally: do also "the right thing" if I additionally got OpenVPN running to connect the router to the company network
This is probably something they have in the OPNsense project. But I am just interested in configuring the firewall. I want to be in control of a vanilla FreeBSD for everything else. I don't want to run some "applicance" where I need to reverse-engineer how to implant my own services surgically. I want a vanilla FreeBSD with a best-practices configured "pf" firewall for acting as home router. I could reverse-engineer the OPNsense rulesets, but hat also has its own danger of missing things like sysctls, pf anchors, whatever other stuff which could be hidden there beyond the "pf" ruleset. (And additionaly lI haven't yet managed to find out how to tell "pf" to "give me all your configuration", it seems I need to follow any anchor manually myself.)

Is there something like a project which compiles and keeps up to date such a standard configuration? Like the OPNsense project, but not as appliance, just the pf/firewall config? It can't be that everyone hand-crafts his firewall for such a standard purpose on his own (and being his own source of error, in particular if being a beginner in such things, rather than relying on a community-reviewed proven ruleset).

Main interest is pf/FreeBSD, but I appreciate other pointers as well.

Thanks a lot in advance for any hints / pointers.

Best regards
Edvard
 
In this question I am not an expert, but I could recommend ipfw. IPFW is a stateful firewall written for FreeBSD which supports both IPv4 and IPv6. It is comprised of several components: the kernel firewall filter rule processor and its integrated packet accounting facility, the logging facility, NAT, the dummynet(4) traffic shaper, a forward facility, a bridge facility, and an ipstealth facility. Here is documentation and here is minimalistic rule script.
 
Here's my pf ruleset. I have a home Ethernet network with a router between the cable modem and machines, each machine running the same pf ruleset. It may be of some help as reference:

Code:
### Macro name for external interface
ext_if = "em0"
netbios_tcp = "{ 22, 23, 25, 80, 110, 111, 123, 512, 513, 514, 515, 6000, 6010 }"
netbios_udp = "{ 123, 512, 513, 514, 515, 5353, 6000, 6010 }"

### Reassemble fragmented packets
scrub in on $ext_if all fragment reassemble

### Default deny everything
block log all

### Pass loopback
set skip on lo0

### Block spooks
antispoof for lo0
antispoof for $ext_if inet
block in from no-route to any
block in from urpf-failed to any
block in quick on $ext_if from any to 255.255.255.255
block in quick log on $ext_if from { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 255.255.255.255/32 } to any

### Block all IPv6
block in quick inet6 all
block out quick inet6 all

### Block to and from port 0
block quick proto { tcp, udp } from any port = 0 to any
block quick proto { tcp, udp } from any to any port = 0

### Block specific ports
block in quick log on $ext_if proto tcp from any to any port $netbios_tcp
block in quick log on $ext_if proto udp from any to any port $netbios_udp

### Keep and modulate state of outbound tcp, udp and icmp traffic
pass out on $ext_if proto { tcp, udp, icmp } from any to any modulate state


Here's what it does, this being the pfctl command to show all rules:

Code:
root@unmei:/ # pfctl -s rules
scrub in on em0 all fragment reassemble
block drop log all
block drop in on ! lo0 inet from 127.0.0.0/8 to any
block drop in on ! em0 inet from 192.168.1.0/24 to any
block drop in inet from 192.168.1.2 to any
block drop in on ! lo0 inet6 from ::1 to any
block drop in from no-route to any
block drop in from urpf-failed to any
block drop in quick on em0 inet from any to 255.255.255.255
block drop in log quick on em0 inet from 10.0.0.0/8 to any
block drop in log quick on em0 inet from 172.16.0.0/12 to any
block drop in log quick on em0 inet from 192.168.0.0/16 to any
block drop in log quick on em0 inet from 255.255.255.255 to any
block drop in quick inet6 all
block drop out quick inet6 all
block drop quick proto tcp from any port = 0 to any
block drop quick proto tcp from any to any port = 0
block drop quick proto udp from any port = 0 to any
block drop quick proto udp from any to any port = 0
block drop in log quick on em0 proto tcp from any to any port = ssh
block drop in log quick on em0 proto tcp from any to any port = telnet
block drop in log quick on em0 proto tcp from any to any port = smtp
block drop in log quick on em0 proto tcp from any to any port = http
block drop in log quick on em0 proto tcp from any to any port = pop3
block drop in log quick on em0 proto tcp from any to any port = sunrpc
block drop in log quick on em0 proto tcp from any to any port = ntp
block drop in log quick on em0 proto tcp from any to any port = exec
block drop in log quick on em0 proto tcp from any to any port = login
block drop in log quick on em0 proto tcp from any to any port = shell
block drop in log quick on em0 proto tcp from any to any port = printer
block drop in log quick on em0 proto tcp from any to any port = x11
block drop in log quick on em0 proto tcp from any to any port = x11-ssh
block drop in log quick on em0 proto udp from any to any port = ntp
block drop in log quick on em0 proto udp from any to any port = biff
block drop in log quick on em0 proto udp from any to any port = who
block drop in log quick on em0 proto udp from any to any port = syslog
block drop in log quick on em0 proto udp from any to any port = printer
block drop in log quick on em0 proto udp from any to any port = mdns
block drop in log quick on em0 proto udp from any to any port = x11
block drop in log quick on em0 proto udp from any to any port = x11-ssh
pass out on em0 proto tcp all flags S/SA modulate state
pass out on em0 proto udp all keep state
pass out on em0 proto icmp all keep state
root@unmei:/ #

You may not think all those rules necessary, need them all or even think it a good idea to have so many. It makes me happy though.
 
I have been trying to find something like a standard / best practices ruleset for a home router for quite some time, to no avail. Something like you'd expect to find on any standard home router you can buy off the shelf.
That's simple, block everything incoming from the internet and allow everything from the LAN going out. That's the setup of a typical home router. So your basic rule-set looks something like this:
Code:
ext_if="mywan0" # change this

set skip in lo0

nat on $ext_if from any to any -> ($ext_if)

block in on $ext_if
 
This is something I'm also looking into currently as my ISP is one of the few in the UK that natively supports IPv6, handing out a /56 prefix delegation by DHCPv6, and I thought it was time I got it all working on my home network.

So far, I've worked out that certain ICMPv6 types should be permitted between link-local and link-local scoped multicast addresses on all interfaces for Neighbour Discovery Protocol to work properly. Simply blocking most ICMP seems bound to break certain IPv6 functionality.

Code:
ndp_icmp_types = "{ routersol, routeradv, neighbrsol, neighbradv, redir }"

# Allow IPv6 NDP ICMP types
# In from LL to LL
pass in quick inet6 proto ipv6-icmp from fe80::/10 to fe80::/10 icmp6-type $ndp_icmp_types keep state
# In from LL-MC to LL
pass in quick inet6 proto ipv6-icmp from ff02::/16 to fe80::/10 icmp6-type $ndp_icmp_types keep state
# In from LL to LL-MC
pass in quick inet6 proto ipv6-icmp from fe80::/10 to ff02::/16 icmp6-type $ndp_icmp_types keep state
# In from any to LL-MC
pass in quick inet6 proto ipv6-icmp from :: to ff02::/16 icmp6-type $ndp_icmp_types keep state
# Out from LL to LL
pass out quick inet6 proto ipv6-icmp from fe80::/10 to fe80::/10 icmp6-type $ndp_icmp_types keep state
# Out from LL to LL-MC
pass out quick inet6 proto ipv6-icmp from fe80::/10 to ff02::/16 icmp6-type $ndp_icmp_types keep state
(In comments, LL = link-local and LL-MC = link-local scoped multicast)

Also, because DHCPv6 requests are sent to a multicast (ff02::/16) address, but the replies are received from link-local (fe08::/10) addresses, state keeping doesn't automatically allow the reply. I needed an explicit rule to permit the replies back in:

Code:
# Permit inbound DHCP6 replies from link-local addresses
pass in quick on $wan_if inet6 proto udp from fe80::/10 port = dhcpv6-server to fe80::/10 port = dhcpv6-client

Figuring this all out is a work in progress. I'll update with any other useful information I find.
 
Back
Top