Ansible for firewalld Rules
ansible.posix.firewalld changes either the runtime or the permanent configuration, depending on two flags. This is the most common way to lose marks: permanent: true alone writes the rule to disk but the running firewall does not have it until a reload; leaving out permanent gives a rule that vanishes at the next reload or reboot. Use both:
yaml - ansible.posix.firewalld: service: http permanent: true immediate: true state: enabled disabled removes it
One rule type per task: service, port (8080/tcp), richrule, source, interface, masquerade or icmpblock. Add zone: when the rule is not for the default zone. Several services: loop.
yaml - ansible.posix.firewalld: service: "{{ item }}" permanent: true immediate: true state: enabled loop: [http, https]
- ansible.posix.firewalld: richrule: 'rule family="ipv4" source address="10.10.10.0/24" service name="mysql" accept' permanent: true immediate: true state: enabled
- ansible.posix.firewalld: source: 192.168.50.0/24 zone: trusted permanent: true immediate: true state: enabled
firewalld itself must be installed and running; a play usually starts with dnf: name: firewalld and service: name: firewalld state: started enabled: true.
Checking your work. Compare runtime and permanent views from control: ansible webservers -m command -a 'firewall-cmd --list-services' and the same with --permanent. They must match. firewall-cmd --reload on a node is a good test: whatever disappears after it was never permanent. For a zone other than the default, list it with --zone=trusted --list-all. Also check the hosts that must not have the rule, because graders do. If the firewalld service itself is stopped on a node, every task with immediate: true fails there, so start and enable it in the same play before adding rules; with offline: true the module can write permanent rules even while firewalld is down, but nothing becomes active until it starts.
Pitfalls. Missing ansible.posix (install the collection first). Writing the rich rule with different quoting than firewalld stores it: the module compares strings, so copy the documented form. Opening the port on every host instead of the named group. Using command: firewall-cmd, which is not idempotent.