Firewall Configuration
The configure_firewall task uses the linux-system-roles.firewall role to manage firewalld on both RHEL and Ubuntu hosts. On Ubuntu, UFW is automatically disabled in favor of firewalld.
Zone design
Two custom zones are created:
| Zone | Services | Purpose |
|---|---|---|
perimeter |
HTTPS (443), SSH (22), 7327/tcp | Default zone — handles all traffic |
perimeter-mgmt |
— | Reserved for source-restricted SSH overrides |
perimeter uses target: DROP — traffic not explicitly allowed is silently dropped. It is set as the default zone, so it handles all interface traffic not matched by a more specific source binding.
perimeter-mgmt exists as an empty zone. To restrict SSH to a management subnet, override the firewall variable in host_vars and move SSH there with a source binding (see below).
Source bindings in firewalld are routing rules: traffic from a bound source is directed to that zone, and the most specific match wins. Binding 0.0.0.0/0 is not valid — the default zone already covers all unmatched traffic without a source entry.
Default configuration (open to all)
The role's default allows all sources to reach all services. SSH and HTTPS are both in the perimeter zone with no source binding:
firewall:
- zone: perimeter
state: present
- zone: perimeter
target: DROP
service:
- https
- ssh
port:
- 7327/tcp
state: enabled
- set_default_zone: perimeter
state: enabled
Restricting access by source
To restrict SSH to specific subnets, override the firewall variable in host_vars and move SSH from perimeter into perimeter-mgmt with a source binding:
# host_vars/epic-pauth-sapph.sapphire.dev.yml
firewall:
- zone: perimeter
state: present
- zone: perimeter
target: DROP
service:
- https
port:
- 7327/tcp
state: enabled
- zone: perimeter-mgmt
state: present
- zone: perimeter-mgmt
target: DROP
service:
- ssh
source:
- 10.197.0.0/24
- 10.198.0.0/24
state: enabled
- set_default_zone: perimeter
state: enabled
source accepts a list of CIDR ranges. Traffic from those subnets is routed to perimeter-mgmt; everything else hits the default perimeter zone (which no longer allows SSH).
Verifying firewall state
Run these on the nginx host to inspect the active configuration:
# Show the default zone
firewall-cmd --get-default-zone
# List all zones that have any configuration
firewall-cmd --list-all-zones
# Show active rules for a specific zone
firewall-cmd --zone=perimeter --list-all
firewall-cmd --zone=perimeter-mgmt --list-all
# Show which zones are active (have an interface or source binding)
firewall-cmd --get-active-zones
Example output for --zone=perimeter --list-all on a correctly configured host:
Known limitations
TODO: Firewall rules are additive
The current implementation uses linux-system-roles.firewall, which only adds rules — it does not remove services that are no longer in the variable list. If a service (e.g. http) is added and later removed from the configuration, it will remain enabled in the zone until manually removed with firewall-cmd --zone=perimeter --remove-service=http --permanent && firewall-cmd --reload.
The planned fix is to manage the firewalld zone XML file directly via a Jinja2 template, which is fully declarative — only services present in the template exist in the zone.
Variables
| Variable | Default | Description |
|---|---|---|
firewall_disable_conflicting_services |
true |
Disables UFW on Ubuntu before enabling firewalld. |
firewall |
See configure_firewall.yml |
Full zone configuration passed to linux-system-roles.firewall. Override in host_vars or group_vars to customize per host. |