https://www.lisenet.com/2016/firewalld-rich-and-direct-rules-setup-rhel-7-server-as-a-router/
We are going to configure RHEL server as a router. Masquerading, port forwarding, rich and direct rules will be covered.
The Lab
We have three RHEL 7.0 servers available in our lab:
- ipa (10.10.1.79, 10.8.8.70) – will be configured as a router,
- srv1 (10.8.8.71) – a server on our DMZ network, will be used to test masquerading,
- pub (10.10.1.10) – a server on our public network, will be used to test port forwarding.
The ipa server has SELinux set to enforcing mode.
Network Interfaces on the Router Server
The ipa server has two network interfaces attached and configured:
# nmcli d DEVICE TYPE STATE CONNECTION enp0s17 ethernet connected enp0s17 enp0s8 ethernet connected enp0s8 lo loopback unmanaged --
The enp0s8 is connected to our “public” network 10.10.1.0/24, where enp0s17 is connected to DMZ 10.8.8.0/24.
# ip -4 ad 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 inet 10.10.1.79/24 brd 10.10.1.255 scope global dynamic enp0s8 valid_lft 86376sec preferred_lft 86376sec 3: enp0s17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 inet 10.8.8.70/24 brd 10.8.8.255 scope global dynamic enp0s17 valid_lft 3583sec preferred_lft 3583sec
Routing table can be seen below:
# ip ro default via 10.10.1.1 dev enp0s8 proto static metric 1024 10.8.8.0/24 dev enp0s17 proto kernel scope link src 10.8.8.70 10.10.1.0/24 dev enp0s8 proto kernel scope link src 10.10.1.79
Exclude all iptables-based services from ever being started:
# systemctl mask iptables ip6tables ebtables ln -s '/dev/null' '/etc/systemd/system/iptables.service' ln -s '/dev/null' '/etc/systemd/system/ip6tables.service' ln -s '/dev/null' '/etc/systemd/system/ebtables.service'
There is apparently a bug in RHEL 7.1 and RHEL 7.2 that prevents the iptables service from being masked if the package iptables-services is not installed:
[rhel7.1]# systemctl mask iptables Failed to issue method call: Access denied
[rhel7.1]# cat /var/log/audit/audit.log type=USER_AVC msg=audit(1468920066.358:447): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='avc: denied { disable } for auid=0 uid=0 gid=0 cmdline="systemctl mask iptables ip6tables ebtables" scontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tcontext=system_u:system_r:init_t:s0 tclass=service exe="/usr/lib/systemd/systemd" sauid=0 hostname=? addr=? terminal=?'
The version of the policy that has a bug is this:
[rhel7.1]# rpm -q selinux-policy-targeted selinux-policy-targeted-3.13.1-23.el7.noarch
[rhel7.2]# rpm -q selinux-policy-targeted selinux-policy-targeted-3.13.1-60.el7.noarch
Installing the iptables-services package, or putting SELinux in to permissive mode, allows masking of iptables service on RHEL 7.1 and RHEL 7.2.
Firewalld Default Zone
Both network interfaces should be added to the zone public:
# firewall-cmd --get-active-zones public interfaces: enp0s17 enp0s8
It should be the default zone:
# firewall-cmd --get-default-zone public
We still want to set the default zone to public (so that we know how to change it), and assign the interface enp0s17 to the zone dmz.
Set the default firewall zone to public, remove the network interface enp0s17 from the zone public and add to the zone dmz.
# firewall-cmd --set-default-zone=public # firewall-cmd --remove-interface=enp0s17 --zone=public # firewall-cmd --permanent --add-interface=enp0s17 --zone=dmz
A permanent configuration requires a reload of the firewall configuration to work as expected:
# firewall-cmd --reload
Let us see the actives zones:
# firewall-cmd --get-active-zones dmz interfaces: enp0s17 public interfaces: enp0s8
Make sure that zones are correctly set in the network scripts:
# nmcli con mod enp0s8 connection.zone public # nmcli con mod enp0s17 connection.zone dmz # nmcli c reload
Custom Firewalld Services
This part shows the way we can create custom firewalld services. We are going to create one for iSCSI target.
Copy one of the existing firewalld service configuration files, say for SSH:
# cp /usr/lib/firewalld/services/ssh.xml /etc/firewalld/services/iscsi-target.xml
Open the file /etc/firewalld/services/iscsi-target.xml
for editing, and put the following:
<?xml version="1.0" encoding="utf-8"?> <service> <short>iSCSI Target</short> <description>iSCSI target.</description> <port protocol="tcp" port="3260"/> </service>
The newly created firewalld service can be added to the zone dmz this way:
# firewall-cmd --permanent --zone=dmz --add-service iscsi-target
More services depending on requirements can be also added, for example:
# firewall-cmd --permanent --zone=dmz --add-service={http,https,ldap,ldaps,kerberos,dns,kpasswd,ntp,ftp} # firewall-cmd --reload
List all services that are added to the zone dmz:
# firewall-cmd --list-services --zone=dmz dns ftp http https iscsi-target kerberos kpasswd ldap ldaps ntp ssh
List all services that are added to the zone public:
# firewall-cmd --list-services --zone=public dhcpv6-client
Packet Forwarding
To configure routing, the server needs to forward incoming packets from one interface to another interface.
Create a new file /etc/sysctl.d/ip_forward.conf
and add the following:
net.ipv4.ip_forward=1
The above makes the change permanent. Now change the runtime value:
# sysctl -w net.ipv4.ip_forward=1
Routing with Rich Rules
In general, all we have to do is to enable masquerading on the public interface. The command below masquerades packets coming from all hosts in the zone public:
# firewall-cmd --permanent --zone=public --add-masquerade # firewall-cmd --reload
In other words, the above allows all traffic to leave from the zone dmz to the zone public.
The easy way to test this is to try to access some public website from any VM that resides in the dmz network 10.8.8.0/24. We have a server srv1 that’s on 10.8.8.71, and it has the default route going via 10.8.8.70:
[srv1]# ip ro
default via 10.8.8.70 dev mybond0 proto static metric 1024
10.8.8.0/24 dev mybond0 proto kernel scope link src 10.8.8.71
If we were to try to get access to example.com with masquerading disabled, we would have the following:
[srv1]# wget -O - http://example.com
--2016-06-18 17:47:30-- http://example.com/
Resolving example.com (example.com)... 93.184.216.34
Connecting to example.com (example.com)|93.184.216.34|:80... failed: No route to host.
When masquerading on the zone public is enabled, we can access the site:
[srv1]# wget http://example.com Resolving example.com (example.com)... 93.184.216.34 Connecting to example.com (example.com)|93.184.216.34|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 1270 (1.2K) [text/html] Saving to: 'index.html' 100%[================================>] 1,270 --.-K/s in 0s 2016-06-18 17:50:19 (129 MB/s) - 'index.html' saved [1270/1270]
If we need some more control over which packets exactly are masqueraded, we can add a rich rule that matches packets coming from specific source addresses only, for example:
# firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv4 source address=10.8.8.0/24 masquerade' # firewall-cmd --reload
Let us check the rich rule:
# firewall-cmd --zone=public --list-all public (default, active) interfaces: enp0s8 sources: services: dhcpv6-client ports: masquerade: yes forward-ports: icmp-blocks: rich rules: rule family="ipv4" source address="10.8.8.0/24" masquerade
Routing with Direct Rules
Routing can also be achieved with direct rules. However, the firewalld man page says that direct options should be used only as a last resort when it’s not possible to use for example –add-rich-rule=’rule’.
The syntax is below:
[--permanent] --direct --add-rule { ipv4 | ipv6 | eb } table chain priority args
Add a rule with the arguments args to chain chain in table table with priority priority.
The priority is used to order rules. Priority 0 means add rule on top of the chain, with a higher priority
the rule will be added further down. Rules with the same priority are on the same level and the order of
these rules is not fixed and may change. If you want to make sure that a rule will be added after another
one, use a low priority for the first and a higher for the following.
To allow our dmz (enp0s17) network VMs with private IP addresses to communicate with external networks, we have to configure firewall for IP masquerading:
# firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -o enp0s8 -j MASQUERADE
Forward all ICMP requests from the zone dmz (enp0s17) to the zone public (enp0s8):
# firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 \ -p icmp -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
Do the same for all HTTP and HTTPS traffic:
# firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 \ -p tcp -m multiport --dport 80,443 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
Also allow access to public SMTP and SMTPS servers:
# firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 \ -p tcp -m multiport --dport 25,465 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
Allow to SSH into public servers:
# firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 \ -p tcp --dport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
Log everything else:
# firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 \ -j LOG --log-prefix "forward_fw "
Optional, but not really required:
# firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 -j REJECT
Reload:
# firewall-cmd --reload
Check the direct rules:
# firewall-cmd --direct --get-all-rules ipv4 nat POSTROUTING 0 -o enp0s8 -j MASQUERADE ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 -p icmp -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 -p tcp -m multiport --dport 80,443 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 -p tcp -m multiport --dport 25,465 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 -p tcp --dport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT ipv4 filter FORWARD 0 -i enp0s17 -o enp0s8 -j LOG --log-prefix 'forward_fw '
We can use the same server srv1 to test the rules above. Anything that doesn’t match should be blocked and logged. For example, let us try to access example.com via FTP:
[srv1]# wget -O - ftp://example.com
--2016-06-18 18:13:49-- ftp://example.com/
=> ‘.listing’
Resolving example.com (example.com)... 93.184.216.34
Connecting to example.com (example.com)|93.184.216.34|:21... failed: No route to host.
And the log entry should be:
forward_fw IN=enp0s17 OUT=enp0s8 MAC=08:00:27:ff:70:00:08:00:27:ff:81:00:08:00 SRC=10.8.8.71 DST=93.184.216.34 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=10304 DF PROTO=TCP SPT=34579 DPT=21 WINDOW=14600 RES=0x00 SYN URGP=0
Rich Rules and Port Forwarding
We may want to access servers that reside on the dmz network (10.8.8.0/24) from the public (10.10.1.0/24) over SSH. To do so, we need to put some port forwarding rules.
The rules below configure port forwarding so that connections to 10.10.1.79:2271 are forwarded to 10.8.8.71:22, and SSH logging.
# firewall-cmd --permanent --zone=public --add-forward-port='port=2271:proto=tcp:toport=22:toaddr=10.8.8.71' # firewall-cmd --permanent --zone=public --add-rich-rule='rule service name=ssh log prefix="SSH_" level="debug" limit value=1/m reject' # firewall-cmd --reload
Let us see the rules:
# firewall-cmd --list-all --zone=public public (default, active) interfaces: enp0s8 sources: services: dhcpv6-client ports: masquerade: yes forward-ports: port=2271:proto=tcp:toport=22:toaddr=10.8.8.71 icmp-blocks: rich rules: rule service name="ssh" log prefix="SSH_" level="debug" limit value="1/m" reject
To test port forwarding, we need some server that’s in the public network (10.10.1.0/24):
[pub]$ ssh root@10.10.1.79 -p2271 The authenticity of host '[10.10.1.79]:2271 ([10.10.1.79]:2271)' can't be established. ECDSA key fingerprint is 32:e6:80:ec:ec:3e:d0:6a:7b:78:bf:6e:79:90:8f:2d. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '[10.10.1.79]:2271' (ECDSA) to the list of known hosts. root@10.10.1.79's password: Last login: Sat Jun 18 17:29:59 2016 from 10.8.8.1