This page looks best with JavaScript enabled

Managing iptables rules with Ansible

 ·  🎃 kr0m

If we have a large group of servers, it is important to be able to centrally manage firewall rules. Ansible is a good option since we can create groups and apply specific rules to one group or another.

We install the iptables userlands:

emerge -av net-firewall/iptables

We load the iptables rules at startup:

rc-update add iptables default

We install the necessary python libraries:

emerge -av dev-python/python-iptables

We create the necessary directories for Ansible:

mkdir /etc/ansible/
chown -R root:kr0m /etc/ansible/
chmod 775 /etc/ansible/

We create a server group called test:

vi /etc/ansible/hosts

[test]
kr0mtest

We write the playbook:

vi kr0mtest.yml

- hosts: test
  vars:
    allowed_hosts:
      - 1.1.1.1
      - 2.2.2.2
      - 3.3.3.3

  tasks:
  - name: Firewall rule - allow INPUT custom hosts
    iptables:
      action: insert
      chain: INPUT
      rule_num: 1
      source: "{{ item }}"
      jump: ACCEPT

    with_items:
      - "{{ allowed_hosts }}"

  - name: Firewall rule - allow OUTPUT custom hosts
    iptables:
      action: insert
      chain: OUTPUT
      rule_num: 1
      destination: "{{ item }}"
      jump: ACCEPT

    with_items:
      - "{{ allowed_hosts }}"

  - name: Save iptables rules
    shell: /etc/init.d/iptables save

We execute it:

ansible-playbook ./kr0mtest.yml

We check that the config has been applied correctly:

kr0mtest ~ # iptables -L -n

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  3.3.3.3              0.0.0.0/0           
ACCEPT     all  --  2.2.2.2              0.0.0.0/0           
ACCEPT     all  --  1.1.1.1              0.0.0.0/0           

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  0.0.0.0/0            3.3.3.3             
ACCEPT     all  --  0.0.0.0/0            2.2.2.2             
ACCEPT     all  --  0.0.0.0/0            1.1.1.1      

NOTE: I have not been able to apply a default DROP, the only way I have been able to do it is by leaving DROP as the last ACL in both INPUT and OUTPUT.

It is very common to have some generic rules and some more specific ones. To be able to reuse playbooks, we will use includes:

vi genericFw.yml

- hosts: "{{hostlist}}"
  vars:
    input_ports:
      ssh32002:
        port: 32002
        protocol: tcp
      ssh22:
        port: 22
        protocol: tcp
      http:
        port: 80
        protocol: tcp
      https:
        port: 443
        protocol: tcp
      dns:
        port: 53
        protocol: tcp
      dnsUdp:
        port: 53
        protocol: udp
        
    output_ports:
      http:
        port: 80
        protocol: tcp
      https:
        port: 443
        protocol: tcp
      dns:
        port: 53
        protocol: tcp
      dnsUdp:
        port: 53
        protocol: udp

    traffic_direction:
      - INPUT
      - OUTPUT

  tasks:
  - name: Firewall rule - flush previous INPUT ACLs
    iptables:
      flush: true
      chain: "{{ item }}"

    with_items: "{{ traffic_direction }}"

  - name: Firewall rule - allow all INPUT loopback traffic
    iptables:
      action: append
      chain: INPUT
      in_interface: lo
      jump: ACCEPT

  - name: Firewall rule - allow all OUTPUT loopback traffic
    iptables:
      action: append
      chain: OUTPUT
      out_interface: lo
      jump: ACCEPT

  - name: Firewall rule - allow INPUT ICMP traffic
    iptables:
      action: append
      chain: "{{ item }}"
      protocol: icmp
      jump: ACCEPT

    with_items: "{{ traffic_direction }}"

  - name: Firewall rule - allow INPUT ports
    iptables:
      action: append
      chain: INPUT
      protocol: "{{ item.value.protocol }}"
      destination_port: "{{ item.value.port }}"
      jump: ACCEPT

    loop: "{{ lookup('dict', input_ports) }}"

  - name: Firewall rule - allow INPUT2 ports
    iptables:
      action: append
      chain: OUTPUT
      protocol: "{{ item.value.protocol }}"
      source_port: "{{ item.value.port }}"
      jump: ACCEPT

    loop: "{{ lookup('dict', input_ports) }}"

  - name: Firewall rule - allow OUTPUT ports
    iptables:
      action: append
      chain: INPUT
      protocol: "{{ item.value.protocol }}"
      source_port: "{{ item.value.port }}"
      jump: ACCEPT

    loop: "{{ lookup('dict', output_ports) }}"

  - name: Firewall rule - allow OUTPUT2 ports
    iptables:
      action: append
      chain: OUTPUT
      protocol: "{{ item.value.protocol }}"
      destination_port: "{{ item.value.port }}"
      jump: ACCEPT

    loop: "{{ lookup('dict', output_ports) }}"

  - name: Firewall rule - deny all traffic
    iptables:
      action: append
      chain: "{{ item }}"
      jump: DROP

    with_items: "{{ traffic_direction }}"

Now we include the generic playbook in our playbook:

vi kr0mtest.yml

- name: Load generic fw rules
  import_playbook: genericFw.yml hostlist=test

- hosts: test
  vars:
    allowed_hosts:
      - 1.1.1.1
      - 2.2.2.2
      - 3.3.3.3

  tasks:
  - name: Firewall rule - allow INPUT custom hosts
    iptables:
      action: insert
      chain: INPUT
      rule_num: 1
      source: "{{ item }}"
      jump: ACCEPT

    with_items:
      - "{{ allowed_hosts }}"

  - name: Firewall rule - allow OUTPUT custom hosts
    iptables:
      action: insert
      chain: OUTPUT
      rule_num: 1
      destination: "{{ item }}"
      jump: ACCEPT

    with_items:
      - "{{ allowed_hosts }}"

  - name: Save iptables rules
    shell: /etc/init.d/iptables save

We run the playbook:

ansible-playbook ./kr0mtest.yml

We check that it has worked:

kr0mtest ~ # iptables -L -n --line-numbers

Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    ACCEPT     all  --  3.3.3.3              0.0.0.0/0           
2    ACCEPT     all  --  2.2.2.2              0.0.0.0/0           
3    ACCEPT     all  --  1.1.1.1              0.0.0.0/0           
4    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
5    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
6    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:32002
7    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80
8    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:53
9    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:22
10   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:443
11   ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:53
12   ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp spt:53
13   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp spt:80
14   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp spt:53
15   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp spt:443
16   DROP       all  --  0.0.0.0/0            0.0.0.0/0           

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    ACCEPT     all  --  0.0.0.0/0            3.3.3.3             
2    ACCEPT     all  --  0.0.0.0/0            2.2.2.2             
3    ACCEPT     all  --  0.0.0.0/0            1.1.1.1             
4    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
5    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
6    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp spt:32002
7    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp spt:80
8    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp spt:53
9    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp spt:22
10   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp spt:443
11   ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp spt:53
12   ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:53
13   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80
14   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:53
15   ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:443
16   DROP       all  --  0.0.0.0/0            0.0.0.0/0           

Indeed, the ports from the generic playbook and the IPs from the normal playbook are there.

If you liked the article, you can treat me to a RedBull here