How to define a custom Firewalld zone

Firewalld is the default high-level firewall manager on the Red Hat family of distributions. One of its peculiarities is that it defines a series of so called firewall zones: each zone can be considered like a different level of trust and can be configured to allow traffic through a specific set of ports. While Firewalld comes with some predefined zones which can be easily examined and modified, sometimes we may want to create our custom zones from scratch.

In this tutorial we see how to define Firewalld zones using the xml markup language and dedicated configuration files.

In this tutorial you will learn:

  • How to list available Firewalld zones
  • How to examine a Firewalld zone
  • How to define a custom Firewalld zone using xml markup language
How to define a custom Firewalld zone
How to define a custom Firewalld zone

Software requirements and conventions used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Distribution-independent
Software Firewalld
Other Root permissions
Conventions # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux-commands to be executed as a regular non-privileged user

Introduction

This is not the first time we talk about Firewalld. In a previous tutorial we discussed the basics of its usage and the associated firewall-cmd utility. We saw how Firewalld revolves around the concept of “zone”: each zone can be set to allow traffic though a specific set of ports, and with different features. Although the application comes installed with a predefined set of zones, new ones can be configured and added by the system administrator. In this tutorial we see how to define a custom zone directly by writing its configuration file using the xml markup language.

The default zones

Obtaining the list of the predefined Firewalld zones is a very easy task. All we need to do is to open up our favorite terminal emulator and issue the following command:

$ sudo firewall-cmd --get-zones



On my system, (latest version of Fedora), the command above returns the following list:

  • FedoraServer
  • FedoraWorkstation
  • block
  • dmz
  • drop
  • external
  • home
  • internal
  • nm-shared
  • public
  • trusted
  • work

Taking a look at the services and ports allowed in a specific zone, is just as simple. Supposing we want to examine the content of the home zone, we would run:

$ sudo firewall-cmd --info-zone=home

Here is the output returned by the command:

home
  target: default
  icmp-block-inversion: no
  interfaces: 
  sources: 
  services: dhcpv6-client mdns samba-client ssh
  ports: 
  protocols: 
  forward: yes
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

By taking a look at the output we can easily see, among the other things, that the dhcpv6-client, mdns, samba-client and ssh services are enabled in the zone (a service is nothing more that a predefined port of set of ports associated with a name).

Defining zones in xml files

One way to add new zones is to use firewall-cmd with the --new-zoneoption, and customize them by adding additional services or ports directly, respectively with --add-port and --add-service, as we saw in the tutorial mentioned above. A quicker way to define and deploy a new zone, however, is by writing its configuration file using a set of dedicated tags and the xml markup language. The default zones, for example, are defined in the /usr/lib/firewalld/zones directory. Inside of it we can find a file for each available zone:

$ ls /usr/lib/firewalld/zones
-rw-r--r--. 1 root root 312 Mar 25 21:31 block.xml
-rw-r--r--. 1 root root 306 Mar 25 21:31 dmz.xml
-rw-r--r--. 1 root root 304 Mar 25 21:31 drop.xml
-rw-r--r--. 1 root root 317 Mar 25 21:31 external.xml
-rw-r--r--. 1 root root 343 Mar 25 21:31 FedoraServer.xml
-rw-r--r--. 1 root root 525 Mar 25 21:31 FedoraWorkstation.xml
-rw-r--r--. 1 root root 382 Mar 25 21:31 home.xml
-rw-r--r--. 1 root root 397 Mar 25 21:31 internal.xml
-rw-r--r--. 1 root root 809 Aug 2 2021 libvirt.xml
-rw-r--r--. 1 root root 729 Sep 22 2021 nm-shared.xml
-rw-r--r--. 1 root root 353 Mar 25 21:31 public.xml
-rw-r--r--. 1 root root 175 Mar 25 21:31 trusted.xml
-rw-r--r--. 1 root root 349 Mar 25 21:31 work.xml



When one of the default zones is modified, changes are not written in its original configuration file directly; a file with the same name is created in the /etc/firewalld/zones directory, instead. By using this strategy, to reset a zone to its default configuration, all we have to do is to delete said file.

The /etc/firewalld/zones directory, however, it’s not only meant to contain modified default zones. If we want to define custom zones, is in this location that we need to create their configurations. Let’s see how.

Defining a custom zone

A Firewalld zone configuration file must have the .xml extension, and the length of its name must not exceed 17 characters. Being zones defined by using the xml markup language, the first thing we should write inside a zone configuration file is the so called xml prologue:

<?xml version="1.0" encoding="utf-8"?>

The xml prologue is not mandatory, but it is used to specify the xml version and the file encoding.

Each zone definition is enclosed in the root tag: <zone>. This tag accept two optional attributes:

  1. version
  2. target

The value of the version attribute must be a string which indicates the version of the defined zone; the target attribute, instead, can be used to define the default action applied on packets which doesn’t match any rule defined in the zone. The target can be one of the following:

  • ACCEPT: a packet not matching any rule is accepted
  • %%REJECT%%:  a packet not matching any rule is rejected (this is the default)
  • DROP: a packet not matching any rule is dropped

As you can see, when using both %%REJECT%% or DROP, packets not matching any rule are discarded. The difference between the two is that when the former is used the source of the traffic is informed with an error message, while when the latter is used, packets are dropped silently.

Two tags we may want to use inside our zone definition are <short> and <description>. These tag although optional, are very useful, since they can be used to better describe the zone and its purpose.

For the sake of this example, we will create a zone called “custom”, provide a brief description for it, and specify the %%REJECT%% target explicitly. In the /etc/firewalld/zones/custom.xml file we write:

<?xml version="1.0" encoding="utf-8"?>
<zone target="%%REJECT%%">
  <short>Custom</short>
  <description>This is a demonstrative custom zone</description>
</zone>

Adding services and ports to the zone

Above we defined a custom zone but we didn’t add any port or service to it. To perform such tasks we use the <port/> and <service/> tags, respectively. Such tags can be repeated multiple times. Supposing we want to allow the “ssh” service in the zone (the service allows traffic though TCP port 22), we would add the following to our definition:

<?xml version="1.0" encoding="utf-8"?>
<zone target="REJECT"> 
  <short>Custom</short>
  <description>This is a demonstrative custom zone</description>
  <service name="ssh"/>
</zone>



Unlike the other tags we used until now, the <service/> tag is self-closing. This tag takes one mandatory attribute, name, the value of which must be a string indicating the name of the service we want to enable in the zone. A list of predefined services can be obtained by using the following command:

$ sudo firewall-cmd --get-services

If we want to add a specific port, instead, we have to use the <port/> tag. This tag, is a self-closing one, and can be used to specify a port directly. The tag takes two attributes, both mandatory: port and protocol. The former is used to specify the port number or port range we want to use, the latter is used to specify the protocol which can be one among tcp, udp, sctp or dccp. Supposing we want to allow traffic though the TCP port 15432, we would write:

<?xml version="1.0" encoding="utf-8"?>
<zone target="%%REJECT%%"> 
  <short>Custom</short>
  <description>This is a demonstrative custom zone</description>
  <service name="ssh"/>
  <port port="15432" protocol="tcp"/>
</zone>

In case we want to specify a range of ports instead, we can report the starting and ending ports separated by an hyphen. To allow traffic though the range of ports which goes from port 15432 to 15435, for example, we would have used the following syntax:

<port port="15432-15435" protocol="tcp"/>

Adding a reach rule to the zone

Rich rules are used to define detailed traffic behavior. If we want to allow only traffic coming from a specific source IP address or subnet to a port, for example, is a rich rule that we need to set. A rich rule is defined by using the <rule> tag in the zone definition. Suppose we want to allow access to the “git” service (this is a service used to open port 9418, for the git-daemon) only from the 192.168.0.39 IP address. Here is what we would add to our zone definition:

<?xml version="1.0" encoding="utf-8"?> 
<zone target="REJECT"> 
  <short>Custom</short> 
  <description>This is a demonstrative custom zone</description>    
  <service name="ssh"/> 
  <port port="15432" protocol="tcp"/> 
  <rule family="ipv4">
    <source address="192.168.0.39" />
    <service name="git"/>
    <accept/>
  </rule>
</zone>



Above we used the optional family attribute of the <rule> tag to limit the rule to ipv4 (if the attribute is omitted the rule, is considered valid both for ipv4 and ipv6), than we used the <source/> tag to specify the source IP which should be matched for the rule to be applied (via the address attribute), the <service/> tag to specify which service should be part of the rule, and finally, the <accept/> tag to specify that the action which should be applied to is “accept”. To learn more about the rich rules syntax, is highly suggested to take a look at the dedicated manual, which can be accessed by running:

$ man firewalld.richlanguage

Binding a zone to a network interface

With Firewalld we can bind a zone to a specific interface. When interfaces are managed by the NetworkManager service (this is the default), binding an interface to a zone is not needed, since it is done automatically. In certain cases, however, we may want to be explicit in our definition. In such cases, to bind the zone to an interface, we can use the <interface/> self-closing tag. This tag takes only one mandatory argument, which is the name of the interface to bind the zone to. Supposing we want to explicitly bind our zone to the ens5f5 interface, we would write:

<?xml version="1.0" encoding="utf-8"?> 
<zone target="%%REJECT%%"> 
  <short>Custom</short> 
  <description>This is a demonstrative custom zone</description> 
  <service name="ssh"/> 
  <port port="15432" protocol="tcp"/> 
  <rule>
    <source address="192.168.0.39" />
    <service name="git"/>
    <accept/>
  </rule>
  <interface name="ens5f5"/>
</zone>

Loading the zone

Once we saved our zone definition, in order for it to be “picked up”, we must reload Firewalld:

$ sudo firewall-cmd --reload

Our zone should now appear in the list returned by the `–get-zones` command:

$ sudo firewall-cmd --get-zones
FedoraServer FedoraWorkstation block custom dmz drop external home internal nm-shared public trusted work

To set our custom-defined zone as the default one, we would run:

$ sudo firewall-cmd --set-default-zone=custom

Conclusions

In this tutorial we saw how to define a custom Firewalld zone in a xml configuration file. Zone configuration files use the xml markup language, and must be saved with the inside the /etc/firewalld/zones directory. We saw some of the tags which can be used in the zone definition to add ports, services and rich rules. Finally, we saw how to reload Firewalld in order for the zone to be picked up, and how to set it as the default one.



Comments and Discussions
Linux Forum