Proxy - firewalld on dc1-a-vcprx001
Proxy Solution · Config document · referenced from Interfaces and firewalld zones and firewalld rich rules
--list-rich-rules, has no output in the notes.The whole firewalld command set of the datacenter 1 proxy dc1-a-vcprx001 in the order of my working notes: the network state, the two interfaces bound to their zones, target DROP on internal, external and public, the default services and masquerading removed, logging of denied traffic, then the nineteen rich rules on internal and the reload. Everything ran as root on dc1-a-vcprx001.
| Item | Value |
|---|---|
| Host | dc1-a-vcprx001.adm.example.net, RHEL 7.5, the proxy virtual machine of datacenter 1 |
| Interfaces | ens3 internal, 10.11.16.113/28 and 2001:db8:a1:b96::f:1/64, zone internal; ens4 external, 10.11.17.145/28 and 2001:db8:a3:b95::f:1/64, zone external |
| Run as | root on the host |
| Activated with | --permanent changes, systemctl restart firewalld after the interface bindings, firewall-cmd --reload after each group of changes and once at the end |
Rich rules on internal | 19: 4 for the proxy port, 1 for ICMP, 4 for Ansible SSH, 4 for management SSH, 6 for Sensu SNMP |
| Software | firewalld as shipped with RHEL 7.5; the notes do not record the package version |
| Not in it | the output of the final --list-rich-rules, which is empty in the notes |
The commands
The network state before anything was changed. ifconfig as root on dc1-a-vcprx001; the interfaces are ens3 and ens4, each with an IPv4 and a global IPv6 address.
$ ifconfigoutput 28 lines
ens3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.11.16.113 netmask 255.255.255.240 broadcast 10.11.16.127
inet6 2001:db8:a1:b96::f:1 prefixlen 64 scopeid 0x0<global>
inet6 fe80::94ea:30ff:fe12:3456 prefixlen 64 scopeid 0x20<link>
ether 96:ea:30:12:34:56 txqueuelen 1000 (Ethernet)
RX packets 863319 bytes 191610146 (182.7 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2519857 bytes 4773287282 (4.4 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
ens4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.11.17.145 netmask 255.255.255.240 broadcast 10.11.17.159
inet6 2001:db8:a3:b95::f:1 prefixlen 64 scopeid 0x0<global>
inet6 fe80::fec4:a1ff:fe12:3457 prefixlen 64 scopeid 0x20<link>
ether fc:c4:a1:12:34:57 txqueuelen 1000 (Ethernet)
RX packets 2193899 bytes 2789877533 (2.5 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 467379 bytes 110390430 (105.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 5623 bytes 19039258 (18.1 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5623 bytes 19039258 (18.1 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0The utilities installed first, as root: net-tools for ifconfig, bind-utils for dig, mc and nc. The notes keep no output.
$ yum install net-tools $ yum install bind-utils $ yum install mc $ yum install nc
Which zones were active. My NOTE: this is wrong, because the interfaces eth0 and eth1 are not in the system, which uses ens3 and ens4. Where this configuration came from the notes do not say; it was already on the machine.
$ firewall-cmd --get-active-zonesoutput 5 lines
internal interfaces: eth0 sources: 192.168.0.0/16 external interfaces: eth1
The zones RHEL 7.5 defines by default.
$ firewall-cmd --get-zonesoutput 1 line
block dmz drop external home internal public trusted work
Steps 1 to 3: assign ens4 to zone external, ens3 to zone internal, restart firewalld. My NOTE: this method works also when NetworkManager is disabled (NM_CONTROLLED=no).
$ firewall-cmd --permanent --change-interface=ens4 --zone=external $ firewall-cmd --permanent --change-interface=ens3 --zone=internal $ systemctl restart firewalld
Step 4: which zones are active now. The eth0/eth1 bindings and the 192.168.0.0/16 source are gone without a command removing them; the notes do not say why (the datacenter 2 host, in firewalld on dc2-a-vcprx001, needed --remove-interface for them).
$ firewall-cmd --get-active-zonesoutput 4 lines
internal interfaces: ens3 external interfaces: ens4
Steps 1 and 2: the default target of the two zones, both default.
$ firewall-cmd --permanent --zone=internal --get-targetoutput 1 line
default
The same for external.
$ firewall-cmd --permanent --zone=external --get-targetoutput 1 line
default
Steps 1 to 3: set the target of internal, external and public (the firewalld default zone) to DROP.
$ firewall-cmd --permanent --zone=internal --set-target=DROP $ firewall-cmd --permanent --zone=external --set-target=DROP $ firewall-cmd --permanent --zone=public --set-target=DROP
Step 1: what is allowed for zone external. The notes show target: DROP here although the --set-target above was --permanent and no reload had been done; the datacenter 2 notes show target: default at the same point. Whether the runtime listing should still have said default here is discussed in the Article.
$ firewall-cmd --zone=external --list-alloutput 13 lines
external (active) target: DROP icmp-block-inversion: no interfaces: ens4 sources: services: ssh ports: protocols: masquerade: yes forward-ports: source-ports: icmp-blocks: rich rules:
Step 2: zone internal, with the four default services.
$ firewall-cmd --zone=internal --list-alloutput 13 lines
internal (active) target: DROP icmp-block-inversion: no interfaces: ens3 sources: services: ssh mdns samba-client dhcpv6-client ports: protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules:
Step 3: zone public, not active, with ssh only.
$ firewall-cmd --zone=public --list-alloutput 13 lines
public target: DROP icmp-block-inversion: no interfaces: sources: services: ssh ports: protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules:
Steps 1 to 5: remove the services mdns, samba-client and dhcpv6-client from internal, dhcpv6-client from public, and reload. The step titles write the service as "dhcp6-client"; the commands use the real service name dhcpv6-client. ssh stays in both zones.
$ firewall-cmd --permanent --zone=internal --remove-service=mdns $ firewall-cmd --permanent --zone=internal --remove-service=samba-client $ firewall-cmd --permanent --zone=internal --remove-service=dhcpv6-client $ firewall-cmd --permanent --zone=public --remove-service=dhcpv6-client $ firewall-cmd --reload
Steps 1 and 2: remove masquerading from external, in the words of my step title "we are not firewall, but proxy", and reload. The ssh service on external was left in place on this host.
$ firewall-cmd --permanent --zone=external --remove-masquerade $ firewall-cmd --reload
Step 1: the logging configuration before the change.
$ firewall-cmd --get-log-deniedoutput 1 line
off
Steps 2 and 3: log all denied traffic (unicast, broadcast, multicast) and show the setting again. The command has two spaces in the notes; it is kept as typed. There is no --permanent on it: as I understand firewall-cmd, --set-log-denied changes the runtime and the permanent configuration at once.
$ firewall-cmd --set-log-denied=all $ firewall-cmd --get-log-denied
output 1 line
all
Rich rules, zone internal, proxy. For the IPv4 networks 192.168.0.0/16, 172.16.0.0/12 and 10.0.0.0/8 allow the proxy port TCP 3128 on the IPv4 address 10.11.16.113.
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=192.168.0.0/16 destination address=10.11.16.113/32 port port=3128 protocol=tcp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=172.16.0.0/12 destination address=10.11.16.113/32 port port=3128 protocol=tcp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.0.0.0/8 destination address=10.11.16.113/32 port port=3128 protocol=tcp accept'
For the IPv6 network 2001:db8::/32 allow the proxy port TCP 3128 on the IPv6 address 2001:db8:a1:b96::f:1.
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8::/32 destination address=2001:db8:a1:b96::f:1/128 port port=3128 protocol=tcp accept'
Rich rules, zone internal, ICMP. One rule without an address family, as in the lab; the datacenter 2 host got family=ipv4 and a separate direct rule for ICMPv6.
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule protocol value=icmp accept'
Rich rules, zone internal, Ansible. For the addresses 10.11.17.241 and 10.11.17.242 allow SSH, TCP 22, on the IPv4 address 10.11.16.113. Only the Ansible hosts of datacenter 1 appear on this host.
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.11.17.241/32 destination address=10.11.16.113/32 port port=22 protocol=tcp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.11.17.242/32 destination address=10.11.16.113/32 port port=22 protocol=tcp accept'
For the addresses 2001:db8:a1:b89::f:1 and 2001:db8:a1:b89::f:2 allow SSH on the IPv6 address 2001:db8:a1:b96::f:1.
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8:a1:b89::f:1/128 destination address=2001:db8:a1:b96::f:1/128 port port=22 protocol=tcp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8:a1:b89::f:2/128 destination address=2001:db8:a1:b96::f:1/128 port port=22 protocol=tcp accept'
Rich rules, zone internal, OS management. For the IPv4 networks 192.168.0.0/16, 172.16.0.0/12 and 10.0.0.0/8 allow the SSH port TCP 22 on the IPv4 address 10.11.16.113.
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=192.168.0.0/16 destination address=10.11.16.113/32 port port=22 protocol=tcp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=172.16.0.0/12 destination address=10.11.16.113/32 port port=22 protocol=tcp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.0.0.0/8 destination address=10.11.16.113/32 port port=22 protocol=tcp accept'
For the IPv6 network 2001:db8::/32 allow SSH, TCP 22, on the IPv6 address 2001:db8:a1:b96::f:1. The step title in the notes says "the proxy port (tcp/3128)", copied from the proxy group; the rule says port 22 and is right.
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8::/32 destination address=2001:db8:a1:b96::f:1/128 port port=22 protocol=tcp accept'
Rich rules, zone internal, Sensu. For the IPv4 addresses 10.11.16.129, 10.11.16.130 and 10.11.16.131 allow SNMP, UDP 161, on the IPv4 address 10.11.16.113. Only the Sensu hosts of datacenter 1 appear on this host.
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.11.16.129/32 destination address=10.11.16.113/32 port port=161 protocol=udp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.11.16.130/32 destination address=10.11.16.113/32 port port=161 protocol=udp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.11.16.131/32 destination address=10.11.16.113/32 port port=161 protocol=udp accept'
For the IPv6 addresses 2001:db8:a1:bb1::f:1, 2001:db8:a1:bb1::f:2 and 2001:db8:a1:bb1::f:3 allow SNMP on the IPv6 address 2001:db8:a1:b96::f:1. The step title says "on the IPv4 address 10.11.16.113"; the rules have the IPv6 destination and are right.
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8:a1:bb1::f:1/128 destination address=2001:db8:a1:b96::f:1/128 port port=161 protocol=udp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8:a1:bb1::f:2/128 destination address=2001:db8:a1:b96::f:1/128 port port=161 protocol=udp accept' $ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8:a1:bb1::f:3/128 destination address=2001:db8:a1:b96::f:1/128 port port=161 protocol=udp accept'
Steps 1 and 2: reload, then list the rich rules of zone internal. The notes end here, with no output under the listing; what the nineteen rules should have looked like can be seen in the datacenter 2 listing, rich rules on dc2-a-vcprx001, read with this host's addresses in place of datacenter 2's and without the datacenter 2 Ansible and Sensu rules.
$ firewall-cmd --reload $ firewall-cmd --permanent --zone=internal --list-rich-rules
What the rules say
| Part of a rule | Meaning |
|---|---|
rule family=ipv4 or family=ipv6 | the address family the rule is written for; a rule with addresses needs one. The ICMP rule has none; as I understand the rich rule syntax, a rule without a family is added for IPv4 and IPv6 both, but the notes do not test this |
source address=192.168.0.0/16 | the client network or host; /32 and /128 are single hosts |
destination address=10.11.16.113/32 | the proxy's own address on ens3; the rule matches only traffic to this address, not to the external one |
port port=3128 protocol=tcp | the destination port and protocol: 3128 TCP for Squid, 22 TCP for SSH, 161 UDP for SNMP |
protocol value=icmp | the IP protocol ICMP, every type |
accept | the action; everything a rule does not accept falls through to the zone target DROP |
--permanent | written to the permanent configuration (the zone file under /etc/firewalld/zones/, as I understand firewalld; the notes never open it), active after --reload |
The reload at the end is the only one after the rich rules were added; until then the nineteen rules existed only in the permanent configuration.
Checked against firewalld 2.5.2
| As built | Today |
|---|---|
firewalld-0.4.4.4-14.el7 on RHEL 7.5, iptables backend | firewalld 2.5.2 (September 2026); RHEL 7.9 ended with 0.6.3, RHEL 8 ships 0.9.x, RHEL 9 1.3.4, RHEL 10 2.3.1 and later 2.4.3. nftables has been the default backend since 0.6.0 and the iptables backend is deprecated since 1.0.0. RHEL 7 left Maintenance Support on 30 June 2024; only 7.9 is patched under ELS, until 31 May 2029 |
firewall-cmd --permanent --change-interface=ens4 --zone=external | Unchanged in meaning: "basically --remove-interface followed by --add-interface". For an interface under NetworkManager the zone is set on the connection first; for others firewalld tries to change ZONE= in the ifcfg file. The ifcfg format itself is gone in RHEL 10; the binding is nmcli connection modify <profile> connection.zone <zone> |
--set-target=DROP, and target: default on an untouched zone | The four targets default, ACCEPT, DROP, REJECT remain. Since 1.0.0 default "is identical to reject with one caveat: default allows ICMP packets"; the 0.4.4.4 man page said only that every packet not matching a rule is rejected. In 0.4.4.4 --set-target was documented as "Set the target of a permanent zone", a permanent-only option |
external with ssh and masquerade: yes, internal with ssh mdns samba-client dhcpv6-client, public with ssh | The shipped zone files have the same services and masquerade and add <forward/>: intra-zone forwarding is on by default for all shipped zones since 1.0.0, --remove-forward turns it off |
--set-log-denied=all | Unchanged: values all, unicast, broadcast, multicast, off; the default is still off (LogDenied=off in firewalld.conf) |
rule family=ipv4 source address=… destination address=… port port=3128 protocol=tcp accept | Grammar unchanged; a priority= attribute (lower first) and the port protocols sctp and dccp were added. The man page still requires a family whenever a source or destination address is used |
rule protocol value=icmp accept | Unchanged. On a zone with the default target it would be redundant since 1.0.0, because that target accepts ICMP; on these DROP zones it is still what lets ICMP through |
--get-active-zones, --list-all, --reload, --remove-service, --remove-masquerade, --list-rich-rules | All documented with the same meaning |
ifconfig | Deprecated in favour of ip since RHEL 7; net-tools is nevertheless still packaged in RHEL 9 and 10 |
The command set would be accepted by a current firewalld as typed. What has changed is the platform under it, the way an interface is bound to a zone and the backend behind the rules.