LINUXOR.SK ... open source notes ...

Proxy - firewalld on dc1-a-vcprx001

category: solutionz/proxy · date: 2019-12-31 · updated: 2026-10-03 · author: LALA

Proxy Solution · Config document · referenced from Interfaces and firewalld zones and firewalld rich rules

noteTwo step titles in this set do not match their commands: the "OS Management" IPv6 title says "proxy port (tcp/3128)" while the rule opens port 22, and the Sensu IPv6 title says "on the IPv4 address" for a rule with an IPv6 destination. The commands are what ran; the titles are reproduced in the sentences before the fences and corrected there. The last command, --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.

ItemValue
Hostdc1-a-vcprx001.adm.example.net, RHEL 7.5, the proxy virtual machine of datacenter 1
Interfacesens3 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 asroot 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 internal19: 4 for the proxy port, 1 for ICMP, 4 for Ansible SSH, 4 for management SSH, 6 for Sensu SNMP
Softwarefirewalld as shipped with RHEL 7.5; the notes do not record the package version
Not in itthe 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.

bash
$ ifconfig
output 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 0

The utilities installed first, as root: net-tools for ifconfig, bind-utils for dig, mc and nc. The notes keep no output.

bash
$ 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.

bash
$ firewall-cmd --get-active-zones
output 5 lines
internal
  interfaces: eth0
  sources: 192.168.0.0/16
external
  interfaces: eth1

The zones RHEL 7.5 defines by default.

bash
$ firewall-cmd --get-zones
output 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).

bash
$ 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).

bash
$ firewall-cmd --get-active-zones
output 4 lines
internal
  interfaces: ens3
external
  interfaces: ens4

Steps 1 and 2: the default target of the two zones, both default.

bash
$ firewall-cmd --permanent --zone=internal --get-target
output 1 line
default

The same for external.

bash
$ firewall-cmd --permanent --zone=external --get-target
output 1 line
default

Steps 1 to 3: set the target of internal, external and public (the firewalld default zone) to DROP.

bash
$ 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.

bash
$ firewall-cmd --zone=external --list-all
output 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.

bash
$ firewall-cmd --zone=internal --list-all
output 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.

bash
$ firewall-cmd --zone=public --list-all
output 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.

bash
$ 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.

bash
$ firewall-cmd --permanent --zone=external --remove-masquerade
$ firewall-cmd --reload

Step 1: the logging configuration before the change.

bash
$ firewall-cmd --get-log-denied
output 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.

bash
$ 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.

bash
$ 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.

bash
$ 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.

bash
$ 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.

bash
$ 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.

bash
$ 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.

bash
$ 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.

bash
$ 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.

bash
$ 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.

bash
$ 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.

bash
$ firewall-cmd --reload
$ firewall-cmd --permanent --zone=internal --list-rich-rules

What the rules say

Part of a ruleMeaning
rule family=ipv4 or family=ipv6the 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/16the client network or host; /32 and /128 are single hosts
destination address=10.11.16.113/32the proxy's own address on ens3; the rule matches only traffic to this address, not to the external one
port port=3128 protocol=tcpthe destination port and protocol: 3128 TCP for Squid, 22 TCP for SSH, 161 UDP for SNMP
protocol value=icmpthe IP protocol ICMP, every type
acceptthe action; everything a rule does not accept falls through to the zone target DROP
--permanentwritten 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 builtToday
firewalld-0.4.4.4-14.el7 on RHEL 7.5, iptables backendfirewalld 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=externalUnchanged 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 zoneThe 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 sshThe 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=allUnchanged: 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 acceptGrammar 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 acceptUnchanged. 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-rulesAll documented with the same meaning
ifconfigDeprecated 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.

← solutionz/proxy