I’ve created an Ansible project that can be used to configure and manage an OpenBSD firewall running pf and dhcpd. The project can be found here on Github. The playbooks can be used to bootstrap a fresh install, do all of the setup and configuration tasks and make changes later to a running system. All of the state is put in the variables under host_vars.
The playbooks assume a fresh install of OpenBSD with two network interfaces and SSH reachable on one of them. You run the playbooks from another machine. The result will be a working firewall running pf with NAT to the outside interface and a DHCP server on the internal interface. This will be a very minimal configuration though, as the aim of this project was to be a proof of concept. I consider the PoC a success and will be building it out further for personal use. Feel free to take the ideas and code from this project to do your own thing with if you’re interested.
The basic structure of the project is as follows:
The first thing to do when using the playbooks is to set the variables to the values that you need to make sure that Ansible can reach your OpenBSD server. Then you run the bootstrap playbook (see the project readme) which sets up package management, installs Python and sets the proper symlinks. After this step, Python is available for use, unlocking the true capacity of Ansible on the system. At this moment, the playbook play_setup can be used. This playbook actually calls a role called setup that takes care of all the rest of the deployment.
To illustrate the idea of the playbooks further, let’s take an excerpt of my interface variables:
vmx1: name: lan fw: internal dhcp_client: no dhcp_server: yes ip: 192.168.1.1 netmask: 255.255.255.0 subnet: 192.168.1.0 dhcp_range_begin: 192.168.1.100 dhcp_range_end: 192.168.1.199
These variables get used by the setup role to do multiple things:
/etc/hostname.vmx1gets placed with content to set it to a static IP
/etc/dhcpd.confto enable a DHCP server on this interface
In the example above I’ve taken a single interface but the playbooks are written to extend this idea. Adding a new interface is as simple as adding some more variables to describe the state you wish to have on this new interface, run the playbook again and your system will be set up for it. Under the hood, Jinja2 gets used to parse the variables and to apply some logic to make sure everything ends up in the right place.
Overall I expected more pain when trying to manage OpenBSD with Ansible, but the basic modules really work quite well. This project relies on the template module a lot and I really like that Ansible determines whether a file has changed for me. Generate a config file that turns out to be the same as the file already on the host and nothing happens. On top of that, Ansible keeps track of changed stuff. This allows you to do things like restart networking only if interface configuration has been changed.
I will continue to develop this project. The main progress will be in a private code base but I expect to port the general cool stuff and improvements back to the code on Github. The next hard thing to do is think of some way to describe firewall rule contents in variables, then take those variables and parse them to become actual rules.