Spirula’s iptables Ansible Role

Spirula’s iptables Ansible Role

Ansible is a popular configuration management and IT automation tool, it’s created by Michael DeHaan in 2012, unlike most of the configuration management tools Ansible doesn’t require agent at client server, it is an agentless tool that just requires only ssh access to the remote machine.

Ansible can be used to provision servers, deploy applications, roll updates, and run ad-hoc tasks on remote or local machines, it is a powerful tool that is essential for system administrators and developers.
One of our most concerns here in Spirula Systems is security; Linux security consists of multiple layers, and hardening each layer is essential to every web application. In Linux, A firewall module like iptables is one of the most important layers in network security, in this post I am going to create iptables Ansible role that can be applied to any Linux system.

Ansible Role

Playbook’s role is a clean reusable structure of tasks and variables that can be used in different projects and playbooks, Ansible role should be designed with some concepts in mind like:

  • The role should be changing only one unit in your infrastructure.
  • Different options and directives of this unit  must be customized by variables.

In our case, we will be changing the iptables in init-based linux systems.

iptables role

The role will contain one task file that will configure and start iptables on the server, the task file will be something like that:

[code]
– name: Add iptables init script
copy: src=iptables_init dest=/etc/init.d/iptables mode=0700

– name: Create a directory for iptables
file: path=/opt/spirula/firewall recurse=yes state=directory owner=root group=root mode=0700

– name: Add iptables script
template: src=iptables_rules.sh dest=/opt/spirula/firewall/iptables_rules.sh mode=0700

– name: Add rollback script
copy: src=iptables_rollback.sh dest=/opt/spirula/firewall/iptables_rollback.sh mode=0700

– name: Enable iptables init script
command: update-rc.d iptables defaults

– name: Create /var/lib/iptables
file: path=/var/lib/iptables state=directory

– name: Schedule rollback script
at: command="/opt/spirula/firewall/iptables_rollback.sh" count=3 units="minutes"

– name: Save iptables
command: /etc/init.d/iptables save inactive

– name: Run iptables script
command: /opt/spirula/firewall/iptables_rules.sh

– name: Remove the scheduled rollback
at: command="/opt/spirula/firewall/iptables_rollback.sh" state=absent
[/code]

Let’s breakdown this task file:

 

Add iptables init script

This task will add iptables init script to /etc/init.d directory, the init file will define some functions like (start,stop,reload,restart) iptables ruleset, also it defines where it can store the ruleset of iptables.

Create a directory for iptables

This will create a directory to store iptables script, this directory will be located in /opt/spirula/firewall.

Add iptables script

This will add the iptables script to the directory we created in the previous task, the script will be created from a template that contains iptables rules defined from variables.

Add rollback script

The importance of this script is to ensure you will not be locked out of the server whenever new rules are applied; the script will reset the iptables by changing the chains policies to ACCEPT, also it will be scheduled to run before running the iptables script, and will be terminated if the iptables script ran successfully without any errors.

Enable iptables init script

This will enable the iptables in every runlevel, and ensures that iptables will run on every system restart.

Create /var/lib/iptables

This task will create /var/lib/iptables directory that will store iptables rulesets.

Schedule rollback script

Here is where we will schedule the iptables_rollback.sh script we defined to reset the iptables rules, the iptables_rollback.sh will be scheduled using “at” module that will instruct the script to run after 3 minutes.

Save iptables

This will save the existing iptables ruleset to the inactive ruleset before running the iptables script.

Run iptables script

This is the main task that will run iptables script, the iptables script contain iptables rules, we will discuss the iptables script later in a separate section.

Remove the scheduled rollback

If everything goes well then this Ansible task will remove the scheduled rollback script that was defined earlier.

iptables script

[code]
#!/bin/bash
PATH="/bin:/sbin:/usr/sbin"

#Variables Declaration:
#======================

# Initialization:
#================
iptables -F
iptables -X
iptables -Z
echo {{ ip_forward }} > /proc/sys/net/ipv4/ip_forward
echo "1" > /proc/sys/net/ipv4/ip_dynaddr

iptables -t raw -A PREROUTING -j NOTRACK
iptables -t raw -A OUTPUT -j NOTRACK

# Set default policy to drop:
#============================
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP

## Allow all connections on loopback interface:
##=============================================
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

## Allow Incoming/outgoing Pings:
#================================
# Incoming:
#———-
iptables -A INPUT -p icmp –icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp –icmp-type echo-reply -j ACCEPT
# Outgoing:
#———-
iptables -A OUTPUT -p icmp –icmp-type echo-request -j ACCEPT
iptables -A INPUT -p icmp –icmp-type echo-reply -j ACCEPT

## Outgoing Connections:
##======================

# DNS resolving:
#===============
iptables -A INPUT -p udp –sport 53 -j ACCEPT
iptables -A OUTPUT -p udp –dport 53 -j ACCEPT

{% for service in outgoing %}
{{‘##’|e }} {{ service.name }}
{{‘##’|e }} {{‘=’ * service.name|length }}
iptables -A OUTPUT -p {{ service.protocol | default(‘tcp’) }} {{ ‘-d ‘+service.destination if service.destination is defined else ” }} –dport {{ service.port }} -j ACCEPT
iptables -A INPUT -p {{ service.protocol | default(‘tcp’) }} {{ ‘-s ‘+service.destination if service.destination is defined else ” }} –sport {{ service.port }} {{ ” if service.protocol is defined and service.protocol == ‘udp’ else ‘! –syn ‘ }} -j ACCEPT
{% endfor %}

# Incoming Connections:
#======================

{% for service in incoming %}
{{‘##’|e }} {{ service.name }}
{{‘##’|e }} {{‘=’ * service.name|length }}
iptables -A INPUT -p {{ service.protocol | default(‘tcp’) }} {{ ‘-s ‘+service.source if service.source is defined else ” }} –dport {{ service.port }} -j ACCEPT
iptables -A OUTPUT -p {{ service.protocol | default(‘tcp’) }} {{ ‘-d ‘+service.source if service.source is defined else ” }} –sport {{ service.port }} {{ ” if service.protocol is defined and service.protocol == ‘udp’ else ‘! –syn ‘ }} -j ACCEPT
{% endfor %}

# Save active ruleset:
#=====================
/etc/init.d/iptables save active
[/code]

The iptables template consists of different sections that configure iptables rules through two main variables:

  • incoming
  • outgoing

Each variable is a list itself; each item in the list consists of 5 variables:

  • name: Name of the service.
  • port: Port that will be enabled.
  • protocol: Protocol of the service (default: tcp).
  • source (in incoming): Defines the the source address.
  • destination (in outgoing): Defines the destination address.

A sample configuration will look like:

 

[code]
# Incoming Ports
incoming:
– name: SSH
port: 22
– name: HTTP
port: 80
– name: HTTPS
port: 443

# Outgoing Ports
outgoing:
– name: SSH
port: 22
– name: HTTP
port: 80
– name: HTTPS
port: 443
– name: SMTP1
port: 25
– name: SMTP2
port: 587
– name: SMTP3
port: 465
[/code]

The iptables script will use those variables to set the rules of the firewall, the script consists of several sections; Variables declaration, Initialization, defining the common rules that will be applied including the DNS, Pings, and allow all connections on the loopback interface, also it will set ip forwarding with the value of ip_forward variable.

Afterwards the template will generate the incoming and outgoing rules according to the two variable lists discussed earlier; the template uses “for” loop to iterate on each item in the list, and generate two rules for each item.

For instance, assuming that the incoming list contains an item with the following information:

[code]
incoming:
– name: SSH
port: 22
[/code]

First the template will generate a comment with the service name:

[code]
{{‘##’|e }} {{ service.name }}
{{‘##’|e }} {{‘=’ * service.name|length }}
[/code]

Then it will execute the following statement:

[code]
iptables -A INPUT -p {{ service.protocol | default(‘tcp’) }} {{ ‘-s ‘+service.source if service.source is defined else ” }} –dport {{ service.port }} -j ACCEPT
[/code]

which will check for protocol and and then puts “-s” combined with the source address if source variable is defined, otherwise it will continue by adding –dport in addition to the port for the rule, the second rule is to accept the outgoing requests after establishing the connection:

[code]
iptables -A OUTPUT -p {{ service.protocol | default(‘tcp’) }} {{ ‘-d ‘+service.source if service.source is defined else ” }} –sport {{ service.port }} {{ ” if service.protocol is defined and service.protocol == ‘udp’ else ‘! –syn ‘ }} -j ACCEPT
[/code]

The same concept will apply here, and eventually the rule will look something like:

[code]
## SSH
## ===
iptables -A INPUT -p tcp –dport 22 -j ACCEPT
iptables -A OUTPUT -p tcp –sport 22 ! –syn -j ACCEPT

–syn is a shorthand for –tcp-flags FIN,SYN,RST,ACK SYN.
[/code]

The outgoing will work the same way as the incoming rules, of course more rules can be added as preferred; yet, the sample is sufficient as a start.

Finally the script marks the applied rules as active ruleset. The entire role can be found in the following github link, the role is also available on Ansible galaxy.