Firewalld says no to drifting
What's in This Post
If you've used firewalld on a Red Hat or derivative system, you are probably used to some behaviour that will soon go away. The feature I'm taking about is zone drifting. Despite the fact that many common configurations for firewalld depended on this behaviour, it was never an intentional feature. Now the bug which allowed drifting has now been removed.
For the time being a feature which replicates the old behaviour has been added in order to aid transition. But that workaround won't last forever.
Although the new behaviour has been set as the default for the upstream firewalld project, Red Hat has retained the original behaviour as the default for Redhat Enterprise Linux(Red Hat login required) and CentOS for now.
What is Zone Drifting?
The link above to the Firewalld project blog has a detailed explanation of the behaviour. Check it out if you want all the messy details.
In simple terms, when a packet arrives at a host and matches a source zone, it can continue to another interface zone, even if the packet did not match any of the rules in the original ingress zone. This normally happens when you have a default zone set and a source zone is configured with --set-target default
.
I think this has lead people to consider a default
zone as a zone of last resort. Unfortunately, although that is how Firewalld behaved, it's not intentional and represents a flawed configuration for a correctly functioning zone based firewall.
Drifting in Action
Consider the following scenario:
I have an internal webserver that only staff within my network perimeter should be able to access. All my servers are managed by ssh, so I'll need to ensure that's allowed through as well.
First I create my_default_zone
to allow ssh traffic in to the host.
1#/etc/firewalld/zones/my_default_zone.xml
2<?xml version="1.0" encoding="utf-8"?>
3<zone>
4 <short>my_default_zone</short>
5 <description>My default zone for hosts within my organisation</description>
6 <service name="ssh"/>
7</zone>
I make that new zone the default.
1...
2# /etc/firewalld/firewalld.conf
3DefaultZone=my_default_zone
4...
I also create a zone for my internal web users
1#/etc/firewalld/zones/my_web_zone.xml
2<?xml version="1.0" encoding="utf-8"?>
3<zone>
4 <short>my_web_zone</short>
5 <description>My web zone for internal users</description>
6 <source address="192.168.0.0/24"/>
7 <source address="fd01:1:1:1::/64"/>
8 <service name="https"/>
9</zone>
With older versions of Firewalld or if you've set AllowZoneDrifting=yes
in a newer version, the above configuration will allow your internal users to reach the host using HTTPS and anyone can reach the host for SSH. With the new functionality (AllowZoneDrifting=no
) our internal web users will be unaffected, but only those users not in the networks 192.168.0.0/24 fd01:1:1:1::/64
would be able to reach the server via ssh. That isn't what we want so we'll need to change some things.
Using Zones Correctly
If you can accept that the idea of a zone of last resort is not compatible with predictable behaviour in a zone based firewall, you'll start to re-think how best to structure your rules. Zones should represent consumers, not applications. If you have a collection of users or systems that need access to your host, you have the makings of a source based zone. While we can easily group internal users or systems within our organisation, for internet services, you'll want a zone for everyone.
Pre-existing Zones
Depending on which distro and which version you're using, you'll have some, all or perhaps even more zones than those listed here:
drop, block, public, external, dmz, work, home, internal
and trusted
.
For an endpoint server (not acting as a router or firewall), the public
and internal
zones are going to be the most useful. Those two zones offer good examples to start with and adding services to them wont dilute the principle behind their configuration. The public
zone does not have any sources configured. If you configure public
as your default zone or add it to an interface's configuration (ZONE=public
in the ifcfg
file for the interface) then it will apply if no source based zones are matched. This is the standard configuration for CentOS
servers. A better approach is discussed later in this article.
Just remember that when you add a service, but do not specify the zone it applies to, you'll add it only to the default zone. So with zone drifting off, if you want to have a service for both internal
and public
you'll need to add it to both zones explicitly.
If you want to deploy a hardened server then drop
is worth considering, but I'd avoid diluting it with allowed services. So you may find that a custom zone is better.
I recommend avoiding the trusted
zone entirely. In my opinion it has no place on a server, even in a development environment.
Group by Source
Taking another look at my example above, we have:
ssh
enabled so I can manage my hosthttps
enabled so my internal users can reach my web server.
I don't really want the world to be able to ssh to my host. With AllowZoneDrifting=no
if my management hosts come from one of the internal networks listed as a source for my_web_zone
then connection attempts for ssh will fail. I could add the ssh service to the my_web_zone
configuration and in some organisations that may be acceptable. But allowing all internal users to ssh to my host is not really best practice.
I know the addresses of my management hosts, so let's change the name of my zone to something more meaningful and update it's configuration.
1#/etc/firewalld/zones/my_management_zone.xml
2<?xml version="1.0" encoding="utf-8"?>
3<zone>
4 <short>my_management_zone</short>
5 <description>My mgt zone for hosts within my organisation</description>
6 <service name="ssh"/>
7 <service name="https"/>
8 <source address="192.168.1.10/32"/>
9 <source address="fd01:1:1:2::10/128"/>
10</zone>
The new my_management_zone
allows only 2 IP addresses to ssh to this host. I've also added the https service to the zone so that my management hosts can monitor and troubleshoot any web server issues. Without explicitly listing https, the management hosts would be restricted to ssh only, since they would match the more precise sources within my_management_zone
rather than the broader subnets defined in my_web_zone
.
The zone my_web_zone
remains unchanged
Default Zone
Now that I have two source based zones, what should I use as the default? With zone drifting disabled, the default zone will apply when no other zone is applicable for a packet, but will not apply to any packet that matches any other zone. I recommend that the default zone become either the drop
or block
builtin zone, depending on whether you want to silently drop packets or reply with a rejection. Since my host is an internal server I'll use block
, which will reply to connection attempts with ICMP host prohibited messages.
1...
2# /etc/firewalld/firewalld.conf
3DefaultZone=block
4...
Internet Facing Services
My examples so far have consisted of hosts for which all consumers are known. Any unknown connection was therefore explicitly rejected in my example. That's a very strong approach for an internal system, but for any host which services the public, I'm not going to scrape IANA's IP space allocations in order to write a firewalld rule.
In this case, you should use an interface zone rather than a source zone. I recommend against relying on a default zone for public facing services. Many hosts are multihomed, including those in cloud environments where they may have an interface into a private VPC in addition to any public network. Using the default zone for public services may result in poor rule management and a tendency to add internal rules into public facing zones.
For Internet servicing hosts the following guidelines should result in a stronger and more maintainable Firewalld configuration.
A zone which contains <source>
entries for your management hosts. Larger organisations or in those cases where management hosts are not part of a contiguous network should consider using ipsets. All services required for management, including any related to health monitoring or troubleshooting, should go here. The my_management_zone
in this article is an example.
A zone which contains no <source>
entries. Only the service intended for public consumption is included in the configuration. The zone should drop packets which do not match it's service/s. An example is provided below.
This should be the builtin drop
zone with no additional rules.
Example Internet Service Zone
This configuration shows a hardened public facing zone for a web server, applied to the interface via which our host connects to public networks (ens1 in this example).
1#/etc/firewalld/zones/public_web_zone.xml
2<?xml version="1.0" encoding="utf-8"?>
3<zone target="DROP">
4 <short>public_web_zone</short>
5 <description>Only HTTPS traffic, every thing else is dropped.</description>
6 <service name="https"/>
7</zone>
1# /etc/sysconfig/network-scripts/ifcfg-ens1
2ZONE=public_web_zone
3DEVICE="ens1"
4...
The example above will accept only HTTPS. The line <zone target="DROP">
tells firewalld to drop non matching packets rather than replying with ICMP responses which could be abused.
{: .notice--info}
Summary
The eventual removal of zone drifting is likely to be disruptive to many organisations. However it does provide an opportunity to re-think how your firewalld rules are managed and applied.
There's no doubt that a firewalld configuration not reliant on zone drifting is more secure. Adapting now will improve your security posture immediately as well as avoiding a future unpleasant configuration surprise.