Firewalld – Understanding Rich Rules on CentOS/RHEL 7

Rich rules are an additional feature of firewalld that allows you create more sophisticated firewall rules. For example:

  • whitelist an ip range with the exception of one ip that’s in this range.
  • reroute incoming request from one port, and forward it to another port. For example, you might want people to ssh into a box using port 4234 instead of the standard port 22, for improved security. then get firewalld to reroute this traffic to sshd daemon which is listening on port 22. In this scenario, there would be no actual services listening on port 4234, but firewalld will intercept them and forward it onto port 22.
  • limit how many incoming requests comes in per second/minute/hour/day.
  • write particular log entries into /var/log/messages when certain requests come through

You can follow along with this article by using our vagrant environment on github. this environment is made up of 2 CentOS boxes, ‘client’ and ‘webserver’.

+-------------------+            +-----------------+
|                   |            |                 |
|     Client        |            |    Webserver    |
|   (10.0.50.10)    |  Port 80   |  (10.0.50.11)   |
|                   +----------->|                 |
|                   |            |                 |
|                   |            |                 |
|                   |            |                 |
|                   |            |                 |
|                   |            |                 |
+-------------------+            +-----------------+

The syntax for creating rich rules is quite complicated, but luckily there’s a man page to help you in case you forget:

$ man firewalld.richlanguage

Furthermore, it’s actually a lot easier to create rich rules using the firewall gui tool:

$ firewall-config

Here’s an example of adding a rich rule via the command line:


$ firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv4 source address=10.0.50.10/32 service name=http log level=notice prefix="firewalld rich rule INFO:   " limit value="100/h" accept' 
success

Here we are creating a rich rule that needs to be applied to ipv4. This rule applies when the source address is 10.0.50.10 and is seeking to access the http service. When these criteria are met notice-level log entry are made to /var/log/messages, and also this rule will limit access to 100 request per hour from this source IP. This in turn looks like this:

[root@client ~]# firewall-cmd --zone=public --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3 enp0s8
  sources:
  services: ssh dhcpv6-client
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
	rule family="ipv4" source address="10.0.50.10" service name="http" log prefix="firewalld rich rule INFO:   " level="notice" limit value="100/h" accept

Port forwarding using rich rules

Now let’s say we want to improve security of webserver by setting it up so that client can only ssh to webserver using port ‘42343’. At the moment this won’t work:

$ [root@client ~]# ssh -p 42343 vagrant@webserver
ssh: connect to host 10.0.50.10 port 42343: No route to host

That’s because by default the sshd daemon on webserver listens on port 22 (if we omitted the ‘-p 42343’ bit then it would have worked). However with firewalld we can fix this issue, by configuring firewalld to:

  • forward traffic from port 42343 to port 22
  • block all traffic directly going to port 22

First we ensure firewalld daemon is running:

[root@webserver ~]# firewall-cmd --state        
running

Next we check which zones are currently active (i.e. have an interfaces/sources assigned to them):

[root@webserver ~]# firewall-cmd --get-active-zones
public
  interfaces: enp0s3 enp0s8 enp0s9

Only the ‘public’ zone is active, so let’s get more details about this zone:

[root@webserver ~]# firewall-cmd --zone=public --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3 enp0s8 enp0s9
  sources:
  services: ssh dhcpv6-client
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

As you can, this zone whitelisted ssh traffic, as long as it’s for port 22. Now we want firewalld to forward traffic from port 42343 to 22, which we can set like this:

$ firewall-cmd --zone=public --add-rich-rule='rule family=ipv4 source address=10.0.50.10 forward-port port=42343 protocol=tcp to-port=22'

This in turn results in the rich rule section becoming populated:

[root@webserver ~]# firewall-cmd --zone=public --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3 enp0s8 enp0s9
  sources:
  services: ssh dhcpv6-client
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
	rule family="ipv4" source address="10.0.50.10" forward-port port="42343" protocol="tcp" to-port="22"

Now if we log into the client, if we try logging in again:

[root@client ~]# ssh -p 42343 vagrant@10.0.50.10
vagrant@10.0.50.10's password:     (in our case, the password is 'vagrant')
Last login: Thu Feb 22 12:52:37 2018 from 10.0.50.10
[vagrant@webserver ~]$

Success! It worked. However at this stage client can still ssh in using the default port 22. So to block that off we do:

[root@webserver ~]# firewall-cmd --remove-service=ssh --zone=public
success

So the end result looks like this:

[root@webserver ~]# firewall-cmd --zone=public --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3 enp0s8 enp0s9
  sources:
  services: dhcpv6-client
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
	rule family="ipv4" source address="10.0.50.10" forward-port port="42343" protocol="tcp" to-port="22"

This now has the desired result:

[root@client ~]# ssh vagrant@10.0.50.10
ssh: connect to host 10.0.50.10 port 22: No route to host

[root@client ~]# ssh -p 42343 vagrant@10.0.50.11
vagrant@10.0.50.10's password:
Last login: Thu Feb 22 12:55:11 2018 from 10.0.50.10
[vagrant@webserver ~]$

Note that ssh daemon on webserver is still listening on port 22, and there is no daemon actually listening on port 42343. However firewalld is monitoring the destination ports of all traffic and reroutes all port 42343 destined data packets and re-routes (aka port-forwards) them.

Once you are happy with the firewall setting, then make them persistant by running:

[root@webserver ~]# firewall-cmd --runtime-to-permanent
success

Real world scenario

In the above example, we restricted ssh access to webserver to only client (10.0.50.10). However in the real world it’s more likely that you want to whitelist an ip range:

[root@webserver ~]# firewall-cmd --zone=public --add-rich-rule='rule family=ipv4 source address=10.0.50.10/24 forward-port port=42343 protocol=tcp to-port=22'
success

…or just do port forwarding without any ip restrictions:

[root@webserver ~]# firewall-cmd --zone=public --add-rich-rule='rule family=ipv4 forward-port port=42343 protocol=tcp to-port=22'
success

Another option is to not use firewalld at all, instead just reconfigure the service in question to listen on a different port number.

Recommended Videos

https://app.pluralsight.com/library/courses/security-network-host-lfce/table-of-contents

Take the RHCSA Quiz

This article is part of our RHCSA Study guide (click on the yellow tab on the far left). By the end of this article you should be able to answer the following questions:


Give four possible use cases for using rich rules?

  • whitelist an ip range with the exception of one ip that’s in that range.
  • reroute incoming request from one port, and forward it to another port.
  • limit how many incoming requests comes in per second/minute/hour/day.
  • write particular log entries into /var/log/messages when certain requests come through


Where can you find more info about rich rules?

$ man firewalld.richlanguage

What's the easiest way to create rich rules?

using the GNOME desktop gui

What is the command to start the gui?

$ firewall-config

What are the 5 mains parts of a rich rule?

– rule (accepts ‘family’ setting)
– source (accepts ‘address’ setting)
– service (accepts ‘name’ setting)
– log (accepts ‘level’ and ‘prefix’ settings)
– limit (accepts ‘value’ setting)

What is the general syntax to adding rich-rules?

$ firewall-cmd –permenent –add-rich-rule='{insert-rich-rule-here}’

What is the command to allow and register access attempt to /var/log/messages (you can use man pages to work this out), with priority info, and log these entries starting with 'firewalld rich rule INFO: '. where source is from ip address 10.0.50.10, access is for http service. Also limit access to 100 access per hour.

$ firewall-cmd –permanent –zone=public –add-rich-rule=’rule family=ipv4 source address=10.0.50.10/32 service name=http log level=notice prefix=”firewalld rich rule INFO: ” limit value=”100/h” accept’
success
$ systemctl restart firewalld
# note, this will give the source access, even if http isn’t added as a service itself.

How do you set up port forwarding from port 12345 to port 22 for the ip range 10.0.50.10/24,?

$ firewall-cmd –add-rich-rule=’rule family=ipv4 source address=10.0.50.10/24 forward-port port=12345 protocol=tcp to-port=22′

What is the command to make non-persistant configuratins permanent?

$ firewall-cmd –runtime-to-permanent

question?

answer

question?

answer

question?

answer

question?

answer