Automating Iptables With Ansible

I discovered a way to configure the iptables firewall on CentOS with Ansible. I’ve been struggling with this for a while and was pleased when I discovered a cool utility called ferm. ferm is a set of scripts that you can use to manage iptables from a simple text configuration file. I’ve adopted it and am very pleased with how well it works. It even has good primary documentation. In the rest of this post I will explain why you need ferm and how to automate it with Ansible.

Why ferm?

Most of the applications we automate using Ansible can be configured using a text file. This paradigm makes management easy, we just edit the text file, upload it, and then restart the application. The problem is that iptables doesn’t work that way. That is why ferm is so useful. Yes, iptables does save its rules in a text file (at /etc/sysconfig/iptables) but this file isn’t a true configuration file, its more of an internal shorthand. Editing it directly, as I tried, produces odd errors and doesn’t really work.

Another approach is to use the iptables command line API directly through statements like:

iptables -P OUTPUT DROP

This almost works but is very tedious to do in Ansible. You have to enter each line as a command. The problem is that changing iptables can easily disconnect you from the server you have SSH’ed into. The declarative nature of Ansible means that it tries to recreate your ruleset each time so the rules must ‘flush’ iptables and add all the rules back in from scratch. It’s a mess.

Chef and Puppet use a Perl script to manage iptables. Their script is very similar to ferm but I think ferm has better features.

Using ferm

To use ferm with Ansible on CentOS 6.5 you must take the following steps:

1. Ensure target server has Perl installed. CentOS comes with it so it doesn’t need to be installed separately using yum.

2. Install ferm on the target server. Unfortunately ferm does not have a CentOS package in the default repository so you must install it from EPEL.

3. Create a configuration file called /etc/ferm/ferm.conf. This configuration file uses unique syntax that is largely based on iptables own rule syntax. If you know iptables ferm won’t be a challenge. However, ferm comes with a very cool and time-saving tool called import-ferm. This will convert your existing iptables rules into the ferm format. Just type import-ferm > ferm.conf and move the resulting file to your Ansible workstation for automated copying.

4. Run ferm /etc/ferm/ferm.conf.

5. Save your new iptables configuration so it survives a reboot by:  service iptables save.

And that’s it. ferm will rebuild your ruleset and does not disconnect your SSH session.

ferm and Ansible

I recommend that you install ferm using your core role. Mine is called “centos”. Then, install role-specific firewall rules using those roles. For convenience I will put all the Ansible tasks in a single file. Put the ferm.conf file in templates\ferm.conf.j2. You will eventually want to add custom variables to your firewall rules.


# These commands are common to all CentOS 6 servers
- name: Install EPEL and then ferm repository
   yum: name={{ item }} state=installed
     - yum-plugin-protectbase.noarch
     - ferm

- name: Set iptables to autostart on boot
  command: chkconfig iptables on

- name: iptables running
  service: name=iptables state=started

- name: Add ferm conf directory
  file: path=/etc/ferm owner=root group=root mode=0700 state=directory

# These commands go in each role tasks/main.yml
- name: Set iptables configuration
  template: src=ferm.conf.j2 dest=/etc/ferm/ferm.conf owner=root group=root 

- name: Load iptables configuration
  command: /usr/sbin/ferm /etc/ferm/ferm.conf

- name: Save iptables
  command: service iptables save

Ferm conf Example

Here is a simple example for a DHCP server:

domain ip {
    table filter {
        chain INPUT {
           policy DROP;

           # connection tracking
           mod state state INVALID DROP;
           mod state state (RELATED ESTABLISHED) ACCEPT;

           # allow local connections
           interface lo ACCEPT;

           # respond to ping
           proto icmp icmp-type echo-request ACCEPT;

           # remote administration allowed from the admin VLAN only
           saddr {{ admin_vlan }}/{{ CIDR }} proto tcp dport ssh ACCEPT;

           # Allow DHCP client requests
           protocol udp dport 67:68 ACCEPT;

        chain FORWARD policy DROP;
        chain OUTPUT policy ACCEPT;

In the SSH rule I restrict access to a certain subnet. This templated version uses my Ansible global variables (i.e., those found in the file group_vars/all.

[Update: February 24,2014. See part 2 for an improved method.]

Categories: DevOps

Tags: , ,

Share Your Ideas

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: