Wednesday, May 2, 2018

Managing FirewallD ipsets and services using Ansible

Ansible's FirewallD module (2.4, 2.5 and at least up to 1.4.0 of the ansible.posix collection) supports managing a subset of FirewallD functionality.

Currently, the creation and management of services and ipsets are not supported.

The module is being refactored to allow for support of additional functionality.

However, since FirewallD's permanent config is stored in XML files, it is possible to deploy services and ipsets using Ansible's template module instead.

For the functionality that I need (services consisting of just ports) and ipsets containing networks or IPs, I use these templates:

firewalld-ipset.xml.j2
<?xml version="1.0" encoding="utf-8"?>
<ipset type="hash:{{ item.type }}">
  <description>{{ item.description }}</description>
{% if item.options is defined %}
{% for option in item.options %}
  <option name="{{ option.name }}" value="{{ option.value }}"/>
{% endfor %}
{%endif %}
{% for entry in item.entries if entry != "" %}
  <entry>{{ entry }}</entry>
{% endfor %}
</ipset>
firewalld-service.xml.j2
<?xml version="1.0" encoding="utf-8"?>
<service>
  <description>{{ item.description }}</description>
{% if item.ports is defined %}
{% for port in item.ports %}
<port protocol="{{ port.type }}" port="{{ port.port }}"/>
{% endfor %}
{%endif %}
{% if item.protocols is defined %}
{% for proto in item.protocols %}
<protocol value="{{ proto }}"/>
{% endfor %}
{%endif %}
</service>

Variables need to be set up to configure something using these tasks. Additional entries can be added to deploy multiple services / ipsets with a single task.

sample_ipsets:
- filename: private-ips.xml
  description: Private IPs IPset
  type: net
  entries:
  - 10.0.0.0/8
  - 192.168.0.0/16
  - 172.16.0.0/12
- filename: monitoring-servers.xml
  description: Monitoring server IPs
  type: ip
  entries:
  - 192.168.0.1
  - 10.2.3.4
- filename: monitoring-servers-ipv6.xml
  description: Monitoring server IPv6s
  type: ip
  options:
  - name: family
    value: inet6
  entries:
  - 2001:0db8:85a3:0000:0000:8a2e:0370:7334
  - 2001:db8::2:1

sample_services:
- filename: nrpe.xml
  description: Nagios NRPE service
  ports:
    - type: tcp
      port: 5666
- filename: ip-in-ip.xml
  description: IP-in-IP encapsulation
  protocols:
    - ipencap
- filename: dns-and-ntp.xml
  description: Service for easily opening NTP and DNS
  ports:
    - type: udp
      port: 53
    - type: udp
      port: 123

Sample tasks used to deploy the configs based on these templates:

- name: FirewallD services
  ansible.builtin.template:
     src: firewalld-service.xml.j2
     dest: /etc/firewalld/services/{{ item.filename }}
     owner: root
     group: root
     mode: 0644
  with_items: "{{ sample_services }}"

- name: FirewallD IPsets
  ansible.builtin.template:
     src: firewalld-ipset.xml.j2
     dest: /etc/firewalld/ipsets/{{ item.filename }}
     owner: root
     group: root
     mode: 0644
  with_items: "{{ sample_ipsets }}"

# You might want to use a handler for this instead
# It might be possible to do with the systemd module as well instead
# The will cause any non-permanent changes to be lost
- name: Reload FirewallD
  ansible.builtin.command: firewall-cmd --reload