Firewalld says no to drifting

Car 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.

Take Note

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.

A configuration which relies on zone drifting
A configuration which relies on zone drifting

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.

Tip

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:

  1. ssh enabled so I can manage my host
  2. https 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.

management zone

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.

internet service zone

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.

default zone

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.