Manage firewalld with Ansible

Managing firewalld can be a tricky. Especially if you have many servers to manage. Ansible can help manage firewalld rules for you!

Background on firewalld

Firewalld is a firewall which runs on many Linux distributions. It is software which runs on a server, and is used to locally secure the server. For example, it can restrict which ports are available for all source IPs, or it can further restrict access by allowing only certain IPs to a specific port (and zone). Check out the documentation to read more about the features of firewalld.

Everything is blocked when firewalld starts! The only exception is for ssh on port 22. This is normally what you want on a new server.

Problem

However, firewalld isn’t with out it’s limitations. To run commands to configure which ports you want to allow through firewalld, you need to have the firewalld service running. But, what if you are adding firewalld to a server that has an application which is already in use? As soon as you start firewalld, it will be blocked!

There are two ways around this.

  1. Copy your firewalld config files in the right place, with all the right ports, zones, etc. And then start firewalld. This can work. But having Ansible copy files around and deal with all that is a challenge since firewalld behaves a certain way.
  2. Use the “offline” firewalld commands!

Let’s explore options 2. And lucky for us, we want to use Ansible to help us manage this configuration. And, Ansible can make use of “offline” mode.
But, unlucky for us, the Ansible module which handles firewalld can’t do everything. (At least not everything that I want it to do).

I want the configuration for firewalld to be as simple as possible. I don’t want the configuration for services in one place, and the configuration for ports in another.
But more importantly, I don’t want any service interruption.

Solution

Create my own Ansible role that does what I want.

If you’re new to Ansible, check out my Ansible Getting Started Guide.

I want firewalld to behave in a certain way. Meaning if I have an application already running, and I want firewalld to start restricting access, I don’t want an outage on that application as soon as I start up the firewalld service on the server.

Firewalld Limitations

Firewalld does have some limitations. You should be aware of this before using it.
Anything firewalld is not aware of in the back-end (for example if using iptables), will be removed since firewalld wants to be the single source of truth for firewall rules.
This is why firewalld and Docker do not get along.
See details from: here, here, and here.

In case you missed that..
Do not use firewalld and Docker.
Update: Instead, if you need a solution to work with Docker try using iptables directly. See my post about securing Docker with iptables and Ansible.

Firewalld Ansible Role

Ansible manage firewalld

You can find my firewalld Ansible Role on GitHub or in Ansible Galaxy.

This Ansible Role manages firewalld in offline and persistent mode only. I don’t want to mess around with non-persistent changes. And using online mode is not an option.

It uses the Ansible module for firewalld for a few things, but most changes have to be done using the command module. Specifically: “firewall-offline-cmd”.

Why? What’s the benefit doing it this way? It let’s you create your own custom service in firewalld. Even built-in firewalld services can be used, and easily modified. For example, moving the ssh port from 22 to something else. (Warning, be careful changing your SSH port. You could lock yourself out!)

Here’s a short example of the configuration:

rhel_firewalld_zone_source:
  - zone: internal
    state: enabled
    source:
      - "192.168.22.64/26"
      - "192.168.23.64/26"

rhel_firewalld_custom_service:
  - name: zabbix-agent
    zone: public
    state: enabled
    port_protocol:
    # - 10050/tcp
      - 3333/tcp
  - name: openvpn
    zone: public
    state: enabled
  - name: app123-public
    zone: public
    state: enabled
    description: app123 firewall rules for public zone
    port_protocol:
      - 5000/tcp
  - name: app123-internal
    zone: internal
    state: enabled
    description: app123 firewall rules for internal zone
    port_protocol:
      - 8080/tcp
      - 9000/tcp

In the variable rhel_firewalld_zone_source:
This will configure the Zone named internal to only allow IP ranges that are in source.

In the variable rhel_firewalld_custom_service:
This will configure the zabbix-agent service (which is actually built-in) for the zone public, but not use the default port (of 10050, commented out for demonstration). Instead it’s using port 3333.
The openvpn service is added to the zone public, and will just use the default configuration (since this is also a built-in firewalld service). You need to know it’s built-in, if you don’t specify a port. So it’s usually better to just specify a port all the time!
Last, there are two custom services, app123-public and app123-internal, which are added to their defined zones. They are also adding port 5000 using tcp (attached to the public zone), and adding ports 8080 and 9000 both using tcp (attached to the internal zone).

If you decide you want to change a port, just remove it from the configuration.

More details are on GitHub. Be sure to read the README!

Getting Started – Ansible firewalld

Install from Ansible Galaxy:

ansible-galaxy install ryandaniels.firewalld

Or, clone the GitHub project:

git clone https://github.com/ryandaniels/ansible-role-firewalld.git roles/firewalld

Create the Ansible Playbook, called firewalld.yml:

---
- hosts: '{{ inventory }}'
  become: yes
  vars:
    # Use this role
    rhel_firewalld_managed: true
  roles:
  - firewalld

Make configuration changes add desired firewalld services and ports.

Warning: Be sure you have everything needed in your configuration. Once firewalld is started it blocks anything that wasn’t added!

Disclaimer: Keep in mind, you should test all of this in your lab or staging environments. I can’t guarantee this will be 100% safe and can’t be held responsible for anything going wrong!

Then run the playbook:

ansible-playbook firewalld.yml --extra-vars "inventory=centos7" -i hosts-dev

Conclusion

In conclusion, Ansible is now being used to manage firewalld rules! We have a custom firewalld service created using two different zones. The built-in firewalld services are also enabled, but we don’t really care they are built-in, since we can change the port they use if we want.
We have a nice and simple configuration, and this was all done in “offline” mode so there was no impact to any existing services using the ports.

Again, your mileage may vary. Always test in your lab or staging environment first! I’m not responsible for anything you break.

Stay safe!