home | hardware | software | misc | about | rss

Rolling Back OpenBSD PF Changes

05-June-2021

If you have ever done something dumb in a firewall config and locked yourself out of a machine, you will appreciate the ability to automatically roll a configuration back to a previous version after a timeout period. This is something that commercial routers and firewall devices from Cisco, Juniper, and others have implemented, though their solutions cover more than just the firewall itself.

My typical workflow with PF looks something like this:

1.) Make a backup copy of /etc/pf.conf

2.) Open /etc/pf.conf in my favorite text editor.

3.) Make bad decisions.

4.) Save and exit.

5.) Run 'pfctl -f /etc/pf.conf' and pray

The obvious fault in this is that if I lock myself out or cause some sort of calamity, there is no built-in ability to revert to a previous config automatically with OpenBSD's pfctl(8). You can accomplish something like this with a shell one-liner:


# pfctl -f /etc/pf.conf && sleep 60 && pfctl -d

This will load the /etc/pf.conf ruleset, wait 60 seconds, and then disable PF so you can get back in and fix whatever you broke. If you wanted instead to load a different ruleset, you could do something like this:


# pfctl -f /etc/pf.conf && sleep 60 && pfctl -f /etc/pf.conf.backup

However, there are some disadvantages to one liners like this. They are not very extensible. If you want to take other actions on timeout, the one-liner can get messy quickly.

They aren't very flexible. If you want to send the whole rollback process to the background so you can do other tasks, like testing your config, you can't really do this nicely or cleanly in a one liner, though it is possible. You can run it in tmux and switch back to it to send it a Ctrl-C as sort of a workaround.

It also is not easy to add logging. When changing PF configs it can be nice to see exactly which one was loaded, or if the process was interrupted.

Sometimes it can be useful to copy the backup config over to /etc/pf.conf so that it persists across a reboot. Doing this in a one liner is certainly possible, but involves more typing, and I am too lazy to do that.

I wanted a somewhat more robust and permanent solution than this that I could modify in the future, that also did not involve cron(8) in any way. Enter rpfload, a simple Perl wrapper around pfctl(8) that handles all of the above.

rpfload can be used to load /etc/pf.conf and also automatically rollback to the backup file after a configurable period of time:


# rpfload -f /etc/pf.conf -b /etc/pf.conf.backup

This will load /etc/pf.conf, wait 60 seconds by default (this timeout is configurable with the -t flag), then load /etc/pf.conf.backup. If the configuration is good, killing the process will prevent the rollback. rpfload will print out its own PID to stdout make it easier to do this, especially if it is run in the background.

Another way to do this would be to use the -o flag, which will additionally overwrite /etc/pf.conf with the backup config if the timeout is reached without interruption. One possible utility of this would be so that you don't forget and end up rebooting with the broken /etc/pf.conf, which would be loaded on boot.

If all you want to do is disable PF rather than load a backup config, you can do this with the -d flag:


# rpfload -d -f /etc/pf.conf

rpfload will also log what it does to syslog, so you can see which PF config was applied by checking /var/log/messages.

There are valid criticisms. It may be unnecessary if you don't care about logging, switching configs, or other facy things. It is written in Perl, which some people hate. Worse, it was written in Perl by me, a non-Perl-programmer, so the code is not perfect. Doubtless you can think of others.

But it solves one of my problems exactly how I wanted, has no external dependencies in OpenBSD, and I think I might even understand it. That's all I wanted. You can get it here