Seems like firewalld is not honouring rich rules, to give docker container outside connection aside from ping

hi; I’m on Fedora 32 5.7.16-200.fc32.x86_64,
with the package firewalld: firewalld-0.8.3-1.fc32.noarch,
and my Docker containers (all of them, with every image)
don’t have internet access by default, or any outside
connection aside from ping, for that matter.
( for example, I can ping by IP, but not by domain,
because I can’t reach the DNS server with a request )

I knew absolutely nothing about firewalls and firewalld
in particular before this, but I’ve been reading about it,
trying to understand the problem, and find the solution.

Besides the official firewalld documentation, I’ve been
reading about incompatibilities between Docker and firewalld,
and a bunch of other things. I also know that,
exists podman as a Docker alternative.

But for me, this is not about: ‘make it work, and DONE’;
this is about understanding, as much as I can,
WHY works when it does, and
WHY does’t work when it doesn’t.

After learning about firewalld, for me, seems correct that,
by default, the situation be like the one described above.

Now, I want to change that: I want to be able to ping
by domain, for example. When being blocked, logs from
the ping by domain says:

FINAL_REJECT: IN="$CONTAINER_INTERFACE" OUT=wlp3s0 PHYSIN=vethb53e882 MAC=XX:....:XX SRC="$CONTAINER_IP" DST=1.1.1.1 LEN=56 TOS=0x00 PREC=0x00 TTL=63 ID=37255 DF PROTO=UDP SPT=57463 DPT=53 LEN=36

I tried different things; some of them, worked, and others don’t.
But some of them, I think should work, even when it doesn’t.

Seems like firewalld is not honouring its rich rules.
As far as I understood, you put a connection in one zone
(and only one) by its interface or source, and then,
the zone rules apply for the connection:
if there are no rich rules in the zone,
the target of the zone (ACCEPT,DROP, etc)
applies for that connection
if there are rich rules, and one matchs the connection,
then that rich rule is applied to that connection.
And, the first match ALWAYS wins.

What follows, is some output from my terminal, with
different tries, and each try is labeled, saying if it worked or not,
to give the docker container the hability to ping by domain.

In my opinion, all for the tries bellow should do the trick…
some of them, are doing things that I don’t think are necessary,
like the tries that change the target of the zone to DROP:
I did it, because I thought that maybe, the default target was buggy.
So, my question is, why doesn’t work, when it doesn’t?
What is wrong?

defining variables for the container ip and container interface, to easily refer to them from now on:

CONTAINER_IP="172.18.0.2"
CONTAINER_INTERFACE="br-71fe7cc090b3"

defining a function to easily restore the firewalld conf to default, and this way, I can completely restore the firewald conf before each try, knowing that all tries act on the same initial conf:

_restore_firewalld() {
	sudo cp -Ta /usr/lib/firewalld/ /etc/firewalld/ && \
	sudo restorecon -r /etc/firewalld/ && \
	sudo firewall-cmd --complete-reload && \
	sudo firewall-cmd --set-log-denied=unicast ##to log rejects
}

WORKS on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=docker --add-source="$CONTAINER_IP" && \
$ sudo firewall-cmd --reload && \
success
Warning: ALREADY_SET: unicast
success
success
success

$ sudo firewall-cmd --info-zone=docker 
docker (active)
  target: ACCEPT
  icmp-block-inversion: no
  interfaces: docker0
  sources: 172.18.0.2
  services: 
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

WORKS on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=trusted --add-source="$CONTAINER_IP" && \
$ sudo firewall-cmd --reload && \
success
Warning: ALREADY_SET: unicast
success
success
success

$ sudo firewall-cmd --info-zone=trusted 
trusted (active)
  target: ACCEPT
  icmp-block-inversion: no
  interfaces: 
  sources: 172.18.0.2
  services: 
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

WORKS on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=docker --add-interface="$CONTAINER_INTERFACE" && \
$ sudo firewall-cmd --reload && \
success
Warning: ALREADY_SET: unicast
success
success
success

$ sudo firewall-cmd --info-zone=docker 
docker (active)
  target: ACCEPT
  icmp-block-inversion: no
  interfaces: br-71fe7cc090b3 docker0
  sources: 
  services: 
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

WORKS on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=trusted --add-interface="$CONTAINER_INTERFACE" && \
$ sudo firewall-cmd --reload && \
success
Warning: ALREADY_SET: unicast
success
success
success

$ sudo firewall-cmd --info-zone=trusted 
trusted (active)
  target: ACCEPT
  icmp-block-inversion: no
  interfaces: br-71fe7cc090b3
  sources: 
  services: 
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

WORKS on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=public --set-target=ACCEPT && \
$ sudo firewall-cmd --permanent --zone=public --add-source="$CONTAINER_IP" && \
$ sudo firewall-cmd --reload && \
success
Warning: ALREADY_SET: unicast
success
success
success

$ sudo firewall-cmd --info-zone=public 
success
public (active)
  target: ACCEPT
  icmp-block-inversion: no
  interfaces: 
  sources: 172.18.0.2
  services: dhcpv6-client mdns ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

DOES NOT WORK on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=docker --add-rich-rule="rule family=ipv4 source address="$CONTAINER_IP" accept" && \
$ sudo firewall-cmd --reload
success
Warning: ALREADY_SET: unicast
success
success
success

$ sudo firewall-cmd --info-zone=docker 
docker (active)
  target: ACCEPT
  icmp-block-inversion: no
  interfaces: docker0
  sources: 
  services: 
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
	rule family="ipv4" source address="172.18.0.2" accept

DOES NOT WORK on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=public --add-rich-rule="rule family=ipv4 source address="$CONTAINER_IP" accept" && \
$ sudo firewall-cmd --reload
success
Warning: ALREADY_SET: unicast
success
success
success

$ sudo firewall-cmd --info-zone=public 
public
  target: default
  icmp-block-inversion: no
  interfaces: 
  sources: 
  services: dhcpv6-client mdns ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
	rule family="ipv4" source address="172.18.0.2" accept

DOES NOT WORK on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=public --add-source="$CONTAINER_IP" && \
$ sudo firewall-cmd --permanent --zone=public --add-rich-rule="rule family=ipv4 source address="$CONTAINER_IP" accept" && \
$ sudo firewall-cmd --reload
success
Warning: ALREADY_SET: unicast
success
success
success
success

$ sudo firewall-cmd --info-zone=public 
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: 
  sources: 172.18.0.2
  services: dhcpv6-client mdns ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
	rule family="ipv4" source address="172.18.0.2" accept

DOES NOT WORK on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=public --set-target=DROP && \
$ sudo firewall-cmd --permanent --zone=public --add-rich-rule="rule family=ipv4 source address="$CONTAINER_IP" accept" && \
$ sudo firewall-cmd --reload
success
Warning: ALREADY_SET: unicast
success
success
success
success

$ sudo firewall-cmd --info-zone=public 
public
  target: DROP
  icmp-block-inversion: no
  interfaces: 
  sources: 
  services: dhcpv6-client mdns ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
	rule family="ipv4" source address="172.18.0.2" accept

DOES NOT WORK on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=public --set-target=DROP && \
$ sudo firewall-cmd --permanent --zone=public --add-source="$CONTAINER_IP" && \
$ sudo firewall-cmd --permanent --zone=public --add-rich-rule="rule family=ipv4 source address="$CONTAINER_IP" accept" && \
$ sudo firewall-cmd --reload
success
Warning: ALREADY_SET: unicast
success
success
success
success
success

$ sudo firewall-cmd --info-zone=public 
public (active)
  target: DROP
  icmp-block-inversion: no
  interfaces: 
  sources: 172.18.0.2
  services: dhcpv6-client mdns ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
	rule family="ipv4" source address="172.18.0.2" accept

DOES NOT WORK on its own, from default restored firewalld conf:

$ _restore_firewalld && /
$ sudo firewall-cmd --permanent --zone=public --add-interface="$CONTAINER_INTERFACE" && \
$ sudo firewall-cmd --permanent --zone=public --add-source="$CONTAINER_IP" && \
$ sudo firewall-cmd --permanent --zone=public --add-rich-rule="rule family=ipv4 source address="$CONTAINER_IP" accept" && \
$ sudo firewall-cmd --reload
success
Warning: ALREADY_SET: unicast
success
success
success
success
success

$ sudo firewall-cmd --info-zone=public 
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: br-71fe7cc090b3
  sources: 172.18.0.2
  services: dhcpv6-client mdns ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
	rule family="ipv4" source address="172.18.0.2" accept
1 Like

Hello. I’ve just ran into the very same issue on CentOS Stream release 8. I cannot find any good solution to this issue except some very ugly hacks one wouldn’t like to maintain in the system. Does any native and clean solution exist yet, please? I tried both IPtables and NFtables backends for Firewalld. Thank you.