Re: Firewall rules in a directory

From: Dan Mahoney (Ports) <freebsd_at_gushi.org>
Date: Mon, 05 Sep 2022 02:18:02 UTC

> On Aug 31, 2022, at 10:47 AM, Ian Smith <smithi@nimnet.asn.au> wrote:
> 
> On 30 August 2022 2:40:34 pm AEST, "Dan Mahoney (Ports)" <freebsd@gushi.org> wrote:
>> Note, this wasn’t intended to be “here’s a diff, please put it in”,
>> just an illustration of how trivial an addition it is.
>> 
>>> On Aug 29, 2022, at 9:36 PM, Dan Mahoney (Ports)
>> <freebsd@gushi.org> wrote:
>>> 
>>> All,
>>> 
>>> At the dayjob, we’ve taken to putting our ipfw rules into a
>> directory using rcorder’able files.  This way, each of our puppet
>> manifests can drop its own rules into place without having to manage
>> a monolithic file.
>>> 
>>> It’s a simple patch to rc.firewall, where if you set firewall_type
>> to a file, it just runs it, but if it’s a directory, it would treat
>> it as such:
>>> 
>>> *)
>>> if [ -r "${firewall_type}" ]; then
>>>   if [ -f "${firewall_type}" ]; then
>>>     ${fwcmd} ${firewall_flags} ${firewall_type}
>>>   else
>>>     if [ -d "${firewall_type}" ]; then
>>>       for fwfile in `rcorder $firewall_type/*`
>>>         do
>>>           ipfw -q $fwfile;
>>>       done
>>>     fi
>>>   fi
>>> 
>>> Is there a possibility of getting this into base?
>>> 
>>> -Dan
> 
> Getting code into rc.firewall has proven difficult over the years, for me impossible. It even took julian@ a couple of years to get a sensible use of tables into firewall_type 'simple' - but things may have changed.
> 
> I've tried rendering your code into the usual format below, saving a level of indenting with 'elif', and noting that '-q' and path is included in ${fwcmd} earlier in rc.firewall.
> 
> If it's really intended to launch multiple instances of ipfw, it may win more favour - as a bug / feature request as Kevin suggests - if you're sure how things like 'service ipfw status' or 'restart' handle them in /etc/rc.d/ipfw?
> 
> Good Luck, Ian
> 
> <code>
> *)
> 	if [ -r "${firewall_type}" ]; then
> 		if [ -f "${firewall_type}" ]; then
> 			${fwcmd} ${firewall_flags} ${firewall_type}
> 		elif [ -d "${firewall_type}" ]; then
> 			for fwfile in `rcorder ${firewall_type}/*`
> 				do
> 					${fwcmd} ${firewall_flags} ${fwfile}
> 				done
> 		fi
> 	fi
> 	;;
> </code>

So, right now, there are two knobs you can tweak in /etc/rc.conf -- firewall_type, and firewall_script.  Firewall_script defaults to /etc/rc.firewall which understands things like "open" or "client" or "unknown", or a file.  The last bit of the stock rc.firewall looks like this:

[Cc][Ll][Oo][Ss][Ee][Dd])
        ${fwcmd} add 65000 deny ip from any to any
        ;;
[Uu][Nn][Kk][Nn][Oo][Ww][Nn])
        ;;
*)
        if [ -r "${firewall_type}" ]; then
                ${fwcmd} ${firewall_flags} ${firewall_type}
        fi
        ;;
esac

Two problems there.  1) -r only checks for readability, not for it being an actual file.  and 2) For us, we *like* it being a directory.

Here's an output of rcorder:

rcorder /etc/ipfw.d/*
/etc/ipfw.d/setup
/etc/ipfw.d/production_networks
/etc/ipfw.d/production_static
/etc/ipfw.d/routing
/etc/ipfw.d/services
/etc/ipfw.d/snmp_agent
/etc/ipfw.d/ssh_service
/etc/ipfw.d/tftp_service
/etc/ipfw.d/mosh_service
/etc/ipfw.d/node_exporter_agent
/etc/ipfw.d/nrpe_agent
/etc/ipfw.d/ssh_vpn
/etc/ipfw.d/outbound
/etc/ipfw.d/local
/etc/ipfw.d/krb5_client
/etc/ipfw.d/dns_client
/etc/ipfw.d/syslog_client
/etc/ipfw.d/ntp_client
/etc/ipfw.d/final

And...as an example, here's some of /etc/ipfw.d/setup (note the PROVIDE and BEFORE entries at top, like rcorder wants).

#
# PROVIDE: setup blocked bogons
# BEFORE: services routing outbound final
#

# remove all existing tables
table all destroy
table blocked create

# standard (non-service specific) tables
table bogons create
table bogons add 0.0.0.0/8
table bogons add 10.0.0.0/8
table bogons add 172.12.0.0/12
table bogons add 192.168.0.0/16
table bogons add 169.254.0.0/16
table bogons add 240.0.0.0/4

# permit existing TCP sessions
add allow tcp from any to any established

# permit existing stateful traffic
add check-state :default // permit stateful traffic

# permit internal loopback traffic
add allow ip from any to any via lo0
add allow ip from any to any via lo1

# deny directed loopback traffic
add deny ip from any to 127.0.0.0/8 in
add deny ip from any to ::/64 in

# deny unexpected sources
add deny ip from table(bogons) to me in // unexpected sources

# deny explicitly disabled (non-persistent) sources
add deny ip from table(blocked) to me in // emergency (non-persistent) blocklist

# allow bsd-standard-port traceroutes
add allow udp from me to any 33434-33600 // traceroute in
add allow udp from any to me 33434-33600 // traceroute out

# moderately permissive ICMPv4
add allow icmp from any to any icmptypes 0,3,8,11,13,14 // safe ICMPv4

# link-local ICMPv6 (RS, RA, NS, NA) - per FreeBSD standard rules
add allow ipv6-icmp from :: to fe80::/10 // ICMPv6 DAD
add allow ipv6-icmp from fe80::/10 to fe80::/10 // ICMPv6 NDP
add allow ipv6-icmp from fe80::/10 to ff02::/16 // ICMPv6 NDP
add allow ipv6-icmp from any to any icmp6types 1,2,3,128,129,135,136 // safe ICMPv6

....

And here's a service entry...

more /etc/ipfw.d/ssh_service
# REQUIRE: services
# PROVIDE: ssh_service ssh_clients
# BEFORE: outbound

table ssh_clients create

table ssh_clients add 1.2.3.4
table ssh_clients add 2001:dead:beef:cafe::d00d

add allow tcp from table(ssh_clients) to me 22 in setup         // inbound SSH

==

Also unique to our setup: the "local" script is created by puppet but not managed by it, so if you need to drop an emergency override in there for something (i.e. block an attacker, or open a port that you haven't added to automation yet, add a counter to debug an issue, you can, quickly).

Some of our scripts are placeholders, just existing as a no-op to anchor things like BEFORE.

If people wanted things to put in /usr/share/examples (say /usr/share/examples/ipfw/client, or /usr/share/examples/ipfw/closed?) that mimic'd the main setup, I'd be happy to contribute them.

(I'm also not thrilled with the fact that the stock firewall script adds rules before it determines what kind of firewall you want, and then applies your rules...that could perhaps be a different bug though).

If there's a diffferent list I should be posting this to, let me know.

-Dan