Use Ansible to set the default zone for FirewallD?

Hi,

I’m currently configuring FirewallD on a router running Rocky Linux, and so far the configuration looks nice. Here’s my corresponding role :

---  # configure_firewalld/tasks/main.yml

###################
# Basic FirewallD #
###################

- name: Check network parameters
  ansible.builtin.assert:
    that:
      - interface_wan is defined
      - interface_lan is defined
    fail_msg: Missing network parameter

- name: Install FirewallD
  ansible.builtin.dnf:
    name: firewalld
    state: present

- name: Enable and start FirewallD
  ansible.builtin.service:
    name: firewalld
    enabled: true
    state: started

- name: Associate external zone to WAN network interface
  ansible.posix.firewalld:
    zone: external
    interface: "{{interface_wan}}"
    state: enabled
    permanent: true
    immediate: true

- name: Associate internal zone to LAN network interface
  ansible.posix.firewalld:
    zone: internal
    interface: "{{interface_lan}}"
    state: enabled
    permanent: true
    immediate: true

- name: Enable IP masquerading
  ansible.posix.firewalld:
    masquerade: true
    state: enabled
    zone: internal
    permanent: true
    immediate: true

- name: Remove all predefined services except SSH from internal zone
  ansible.posix.firewalld:
    zone: internal
    service: "{{item}}"
    state: disabled
    permanent: true
    immediate: true
  loop:
    - cockpit
    - dhcpv6-client
    - mdns
    - samba-client

# We're doing this here since Dnsmasq is already up & running

- name: Allow DNS & DHCP
  ansible.posix.firewalld:
    zone: internal
    service: "{{item}}"
    state: enabled
    permanent: true
    immediate: true
  loop:
    - dns
    - dhcp

...

So far this configuration works as expected. But there’s only little detail that I couldn’t figure out. I can’t seem to do this:

# firewall-cmd --set-default-zone=internal

Is this a bug in the ansible.posix.firewalld module? It’s not really a showstopper, since I can still display my firewall configuration by specifying the zone explicitly.

Any suggestions?

It doesn’t look like you can set the default zone using the firewalld module. What you would have to do instead is use something like the command or shell module, so:

    - name: Set default firewall zone
      shell:
        cmd: "firewall-cmd --set-default-zone=internal"

it would need someone to add that functionality to the ansible firewalld module for the firewalld module to support that option.

1 Like

How about the rhel-system-roles.firewall that is described in Chapter 12. Configuring firewalld by using RHEL system roles | Red Hat Product Documentation
(and GitHub - linux-system-roles/firewall: Configure firewalld and system-config-firewall )

2 Likes

The RHEL link is similar to how the existing firewalld module works. That said, the github link is much more promising as it does seem to show something that allows configuring the default zone.

Thanks for your suggestions, guys. I fiddled around some more, and here’s an idempotent solution that works perfectly:

- name: Get default zone
  ansible.builtin.command:
    cmd: firewall-cmd --get-default-zone
  changed_when: false
  register: fw_defaultzone

- name: Set default zone to internal
  ansible.builtin.command:
    cmd: firewall-cmd --set-default-zone=internal
  when: fw_defaultzone.stdout.strip() != "internal"

Cheers,

Niki

1 Like

Why masquerade on the internal zone?


There are apparently two ways to put interface to zone (manually).
In the first we set connection.zone for the NetworkManager connection. When connection goes up or down, the NM tells FirewallD that and FD updates ruleset.
The second (which I have not used) is that FirewallD is told about interfaces in its own config. (Earlier, el7?, this was not working reliably.)

A benefit of NM-based approach is that we don’t need to know the name of the interface. NM can bind connection to MAC and pass whatever name the interface device gets to FirewallD. Granted, persistent device names are now much more prevalent than they used to be.

Honestly, I wasn’t sure about this. I knew how to do it back in the old iptables days (though upon reflection I would have to look it up today). Anyway, all I wanted to do was have the IP packets from my local network transported to the Internet, and vice versa. What’s the correct way to do it when you have FirewallD? Normally when I configure it by hand it’s just a matter of firewall-cmd --permanent --add-masquerade. What am I doing wrong here?

I would not say “wrong”. Unnecessary, perhaps.


Lets walk through the life of a packet. Players:

  • Client C in LAN with address lanC. Has router R as “default route”
  • Router R with internal address lanR and external wanR
  • Outside system S (e.g. forums.rockylinux.org) with address wanS

C wants to talk to S. It creates packet with SRC=lanC DST=wanS
The wanS is not in LAN, so C sends the packet to lanR.

R sees that packet is not for R, and knows that wanS is on the external side.
Therefore, R forwards packet out from wanR. In principle the packet could reach
S, if other routers along the route would not refuse to forward packet that has SRC=lanC.
The reply would definitely not get back, as nobody knows where the private lanC is.

That is why we need NAT. Source NAT. Masquerade is SNAT.

The last thing that R does before tossing packet out is to replace the SRC.
The packet that leaves from R to S has SRC=wanR DST=wanS
When S creates reply, that has SRC=wanS DST=wanR. That packet is delivered to R.

R remembers sending a packet and sees the reply to match it. Therefore, R modifies
the reply to have: SRC=wanS DST=lanC. Now R sees that the reply is not for R (not for wanR),
but to C (to lanC). Therefore R forwards the reply out of lanR to LAN, where C does catch it.
Everybody lives happily ever after.


Three firewall rules were involved:

  • Filter: Allow new traffic from internal to external
  • NAT: Masquerade postrouting the packet that goes out of external interface
  • Filter: Allow (valid) replies from external to internal

If you masquerade/SNAT on the internal interface, then shouldn’t the R modify the reply
before in leaves from lanR to contain SRC=lanR DST=lanC? If it does that, then how
does C know that a reply that is “from R” is the reply for the packet sent “to S”?


Even if you open “port forward” on R (a DNAT rule and filter allow) so that outside connections
to wanR port 12345 route to port 21 on C, why should R hide the real client and pretend that
R wants to talk to lanC:21? (That is what the masquerade on internal definitely does.)

1 Like