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

Proxy - firewalld on dc2-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

noteSeveral step titles in this set do not match their commands, and one rule is listed differently from the way it was entered: the IPv6 rule for port 3128 is entered with this host's address 2001:db8:a2:b96::f:1 and appears in the listing with datacenter 1's 2001:db8:a1:b96::f:1. The commands are reproduced as typed; the mismatches are named in the sentences before the fences. Do not copy the Ansible and Sensu addresses of datacenter 1 without asking whether they belong on your host.

The whole firewalld command set of the datacenter 2 proxy dc2-a-vcprx001 in the order of my working notes: the network state, the leftover eth0/eth1 bindings found and removed, the two interfaces bound to their zones, target DROP on internal, external and public, the default services, four ports and the squid service removed from internal, masquerading and ssh removed from external, logging of denied traffic, then the twenty-nine rich rules and one direct rule, the reload and the listings. Everything ran as root on dc2-a-vcprx001.

ItemValue
Hostdc2-a-vcprx001.adm.example.net, RHEL 7.5, the proxy virtual machine of datacenter 2
Interfacesens3 internal, 10.12.16.113/28 and 2001:db8:a2:b96::f:1/64, zone internal; ens4 external, 10.12.17.145/28 and 2001:db8:a4:b95::f:1/64, zone external
Run asroot on the host
Activated with--permanent changes, systemctl restart firewalld after each change of the interface bindings, firewall-cmd --reload after each group of changes and once at the end
Rich rules on internal29: 4 for the proxy port, 1 for ICMP, 8 for Ansible SSH, 4 for management SSH, 12 for Sensu SNMP; plus 1 direct rule for ICMPv6
Softwarefirewalld as shipped with RHEL 7.5; the notes do not record the package version
Final statethe --list-rich-rules output of internal is its own Config document, rich rules on dc2-a-vcprx001; external and public listed no rich rules

The commands

The network state before anything was changed. ifconfig as root on dc2-a-vcprx001. The MAC addresses and the link-local addresses are the same as on dc1-a-vcprx001, which the notes do not explain; I do not derive anything from it.

bash
$ ifconfig
output 28 lines
ens3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.12.16.113  netmask 255.255.255.240  broadcast 10.12.16.127
        inet6 fe80::94ea:30ff:fe12:3456  prefixlen 64  scopeid 0x20<link>
        inet6 2001:db8:a2:b96::f:1  prefixlen 64  scopeid 0x0<global>
        ether 96:ea:30:12:34:56  txqueuelen 1000  (Ethernet)
        RX packets 4768864  bytes 1293914724 (1.2 GiB)
        RX errors 4152  dropped 0  overruns 0  frame 4152
        TX packets 7406259  bytes 7004042893 (6.5 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.12.17.145  netmask 255.255.255.240  broadcast 10.12.17.159
        inet6 fe80::fec4:a1ff:fe12:3457  prefixlen 64  scopeid 0x20<link>
        inet6 2001:db8:a4:b95::f:1  prefixlen 64  scopeid 0x0<global>
        ether fc:c4:a1:12:34:57  txqueuelen 1000  (Ethernet)
        RX packets 3189163  bytes 3667930988 (3.4 GiB)
        RX errors 3822  dropped 0  overruns 0  frame 3822
        TX packets 992919  bytes 217685651 (207.6 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 4579  bytes 9584042 (9.1 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4579  bytes 9584042 (9.1 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

The utilities installed first, as root: net-tools, bind-utils, mc and nc. No output in the notes.

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. The notes do not say where these bindings and the 192.168.0.0/16 source came from; it was a configuration that 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 new interfaces were added next to the old names, not instead of them.

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

Steps 1 to 3: remove eth1 from external and eth0 from internal, restart firewalld. The same NOTE about NM_CONTROLLED=no stands above this step too.

bash
$ firewall-cmd --permanent --remove-interface=eth1 --zone=external
$ firewall-cmd --permanent --remove-interface=eth0 --zone=internal
$ systemctl restart firewalld

Step 4: which zones are active now. The sources: 192.168.0.0/16 line on internal stays; nothing in the notes removes it.

bash
$ firewall-cmd --get-active-zones
output 5 lines
internal
  interfaces: ens3
  sources: 192.168.0.0/16
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 listing shows target: default: the --set-target was --permanent and nothing had been reloaded. The lab and datacenter 1 notes show target: DROP at the same point; which of the two is what firewalld prints is discussed in the Article.

bash
$ firewall-cmd --zone=external --list-all
output 13 lines
external (active)
  target: default
  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. Besides the four default services this host had the squid service and the ports 443, 3128, 80 and 22 open, and the 192.168.0.0/16 source. The service and the ports are not on the datacenter 1 host; its first active-zones listing had the same sources: line, which had gone by this point there, after --change-interface and the restart, without a step that removed it.

bash
$ firewall-cmd --zone=internal --list-all
output 13 lines
internal (active)
  target: default
  icmp-block-inversion: no
  interfaces: ens3
  sources: 192.168.0.0/16
  services: mdns squid dhcpv6-client samba-client ssh
  ports: 443/tcp 3128/tcp 80/tcp 22/tcp
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

Step 3: zone public, not active, with dhcpv6-client, ssh and the port 3128/tcp.

bash
$ firewall-cmd --zone=public --list-all
output 13 lines
public
  target: default
  icmp-block-inversion: no
  interfaces:
  sources:
  services: dhcpv6-client ssh
  ports: 3128/tcp
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

Steps 1 to 9: remove the services mdns, samba-client and dhcpv6-client, the ports 443/tcp, 80/tcp, 3128/tcp and 22/tcp, and the service squid from internal, then reload. The step titles write "dhcp6-client"; the command uses the real name. Unlike datacenter 1, dhcpv6-client was not removed from public here, and 3128/tcp stayed open in public too; public has no interface, so it carries nothing.

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=internal --remove-port=443/tcp
$ firewall-cmd --permanent --zone=internal --remove-port=80/tcp
$ firewall-cmd --permanent --zone=internal --remove-port=3128/tcp
$ firewall-cmd --permanent --zone=internal --remove-port=22/tcp
$ firewall-cmd --permanent --zone=internal --remove-service=squid
$ firewall-cmd --reload

Steps 1 to 3: remove masquerading from external, in the words of my step title "we are not firewall, but proxy", remove the ssh service from external, reload. This is the only host of the three where ssh was taken off the external zone.

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

Step 1: the logging configuration before the change. This section is indented with a tab in the notes; that is formatting.

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 two spaces in the command are 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.12.16.113.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=192.168.0.0/16 destination address=10.12.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.12.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.12.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 of this host. The step title in the notes says "on the IPv6 address 2001:db8:a1:b96::f:1", datacenter 1's address; the command has 2001:db8:a2:b96::f:1, this host's; the listing after the reload shows a1 again. Which address the rule had I cannot tell from the notes.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8::/32 destination address=2001:db8:a2:b96::f:1/128 port port=3128 protocol=tcp accept'

Rich rules, zone internal, ICMP for IPv4. Entered with family=ipv4; the listing after the reload shows it as rule protocol value="icmp" accept, without the family.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 protocol value=icmp accept'

ICMP for IPv6, as a direct rule in the INPUT_direct chain of the IPv6 filter table rather than a rich rule. Direct rules belong to no zone, so the --zone=internal on the command line has nothing to act on; the rule accepts ICMPv6 on every interface. That reading is mine, from the firewalld direct interface as I understand it; the notes only give the command.

bash
$ firewall-cmd --permanent --zone=internal --direct --add-rule ipv6 filter INPUT_direct -p icmp6 -j ACCEPT

Rich rules, zone internal, Ansible, datacenter 1. For the addresses 10.11.17.241 and 10.11.17.242 allow SSH, TCP 22, on the IPv4 address 10.12.16.113.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.11.17.241/32 destination address=10.12.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.12.16.113/32 port port=22 protocol=tcp accept'

Ansible, datacenter 2. For the addresses 10.12.17.241 and 10.12.17.242 allow SSH on the IPv4 address 10.12.16.113.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.12.17.241/32 destination address=10.12.16.113/32 port port=22 protocol=tcp accept'
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.12.17.242/32 destination address=10.12.16.113/32 port port=22 protocol=tcp accept'

Ansible IPv6, datacenter 1. For the addresses 2001:db8:a1:b89::f:1 and 2001:db8:a1:b89::f:2 allow SSH on the IPv6 address of this host. The step title says "on the IPv6 address 2001:db8:a1:b96::f:1"; the rules have 2001:db8:a2:b96::f:1 and are right for this host.

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:a2: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:a2:b96::f:1/128 port port=22 protocol=tcp accept'

Ansible IPv6, datacenter 2. For the addresses 2001:db8:a2:b89::f:1 and 2001:db8:a2:b89::f:2 allow SSH on the IPv6 address of this host. The step title again says 2001:db8:a1:b96::f:1; the rules say a2.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8:a2:b89::f:1/128 destination address=2001:db8:a2: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:a2:b89::f:2/128 destination address=2001:db8:a2: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.12.16.113.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=192.168.0.0/16 destination address=10.12.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.12.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.12.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:a2:b96::f:1. The step title 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:a2:b96::f:1/128 port port=22 protocol=tcp accept'

Rich rules, zone internal, Sensu, datacenter 1. 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.12.16.113.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.11.16.129/32 destination address=10.12.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.12.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.12.16.113/32 port port=161 protocol=udp accept'

Sensu, datacenter 2. For the IPv4 addresses 10.12.16.129, 10.12.16.130 and 10.12.16.131 allow SNMP on the IPv4 address 10.12.16.113. The step title lists 10.11.16.131/32, a datacenter 1 host, as the third address; the third rule has 10.12.16.131 and is right.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.12.16.129/32 destination address=10.12.16.113/32 port port=161 protocol=udp accept'
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.12.16.130/32 destination address=10.12.16.113/32 port port=161 protocol=udp accept'
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address=10.12.16.131/32 destination address=10.12.16.113/32 port port=161 protocol=udp accept'

Sensu IPv6, datacenter 1. 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:a2:b96::f:1.

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:a2: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:a2: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:a2:b96::f:1/128 port port=161 protocol=udp accept'

Sensu IPv6, datacenter 2. For the IPv6 addresses 2001:db8:a2:bb1::f:1, 2001:db8:a2:bb1::f:2 and 2001:db8:a2:bb1::f:3 allow SNMP on the IPv6 address 2001:db8:a2:b96::f:1.

bash
$ firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv6 source address=2001:db8:a2:bb1::f:1/128 destination address=2001:db8:a2: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:a2:bb1::f:2/128 destination address=2001:db8:a2: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:a2:bb1::f:3/128 destination address=2001:db8:a2:b96::f:1/128 port port=161 protocol=udp accept'

Steps 1 and 2: reload, then list the rich rules of zone internal. The twenty-nine lines it printed are the Config document rich rules on dc2-a-vcprx001.

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

Steps 3 and 4: list the rich rules of external and of public. Both printed nothing; the notes write "no output = ok" under each.

bash
$ firewall-cmd --permanent --zone=external --list-rich-rules
$ firewall-cmd --permanent --zone=public --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
source address=…the client network or host; /32 and /128 are single hosts
destination address=10.12.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
--direct --add-rule ipv6 filter INPUT_direct -p icmp6 -j ACCEPTan iptables rule handed to ip6tables as written: table filter, chain INPUT_direct (the chain firewalld keeps for direct rules in INPUT), protocol ICMPv6, accept
acceptthe action; everything a rule does not accept falls through to the zone target DROP
--permanentwritten to the permanent configuration, active after --reload

Two things need care. The sources: 192.168.0.0/16 on internal, left over from the earlier configuration, was never removed, so traffic from that range is placed in internal by its source address as well as by its interface; the notes do not mention it after the active-zones listing. And the direct rule does not appear in --list-rich-rules; as I understand firewalld, it would show in --direct --get-all-rules, which the notes did not run.

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
--direct --add-rule ipv6 filter INPUT_direct -p icmp6 -j ACCEPTThe direct interface "has been deprecated. It will be removed in a future release. It is superseded by policies" (firewalld.direct(5), since 1.0.0). Red Hat adds that the nftables backend does not support passing custom nftables rules through --direct and that direct rules "are not future-proof". The same thing without it: the rich rule rule family="ipv6" protocol value="ipv6-icmp" accept, or --add-protocol=ipv6-icmp on the zone; --add-protocol did not exist in 0.4.4.4
sources: 192.168.0.0/16 left next to interfaces: ens3 on internalStill dispatched in the same order: source-based zone bindings are matched before interface-based ones ("Old behavior dictates that source based must dispatch before interface based" in the nftables backend). firewalld 2.0 added an explicit ingress and egress priority per zone; Red Hat documents that overlapping source zones are ordered by name and only the first is considered
--permanent --change-interface, --remove-interfaceUnchanged in meaning; for an interface under NetworkManager the zone is set on the connection, for others firewalld edits ZONE= in the ifcfg file. ifcfg files are gone in RHEL 10; the binding is nmcli connection modify <profile> connection.zone <zone>
--set-target=DROP, and target: default in the listingThe four targets remain. Since 1.0.0 default "is identical to reject with one caveat: default allows ICMP packets". In 0.4.4.4 --set-target was documented as "Set the target of a permanent zone", a permanent-only option, which fits a runtime --list-all still printing default
external with ssh and masquerade, internal with ssh mdns samba-client dhcpv6-client, public with ssh dhcpv6-clientThe shipped zone files have the same services and masquerade and add <forward/>: intra-zone forwarding is on by default since 1.0.0
--remove-service, --remove-port=443/tcp, --remove-masquerade, --set-log-denied=all, --reload, --list-rich-rulesAll documented with the same meaning; --set-log-denied still takes all, unicast, broadcast, multicast, off and defaults to off
rule family=ipv4 source address=… destination address=… port port=3128 protocol=tcp acceptGrammar unchanged; priority= and the port protocols sctp and dccp were added. A family is still required whenever a source or destination address is used
rule family=ipv4 protocol value=icmp acceptUnchanged; needed on a DROP zone, redundant on a default zone since 1.0.0
ifconfigDeprecated in favour of ip since RHEL 7; net-tools still packaged in RHEL 9 and 10

Everything except the direct rule would be accepted as typed by a current firewalld. The direct rule is the one line to rewrite, and the sources: line left on internal is the one to remove, because its precedence over the interface binding is unchanged.

← solutionz/proxy