Netfilter rule with firewalld

I want to add a rule or rules that log and block all outbound traffic (internet access) for any process running under the group no-internet as created via groupadd no-internet

I can do this with iptables using

iptables -I OUTPUT   1 -m owner --gid-owner no-internet -j LOG
iptables -I OUTPUT   1 -m owner --gid-owner no-internet -j DROP

and with nftables using

nft insert rule ip filter OUTPUT skgid no-internet counter log drop

I cannot find anything describing how to do this with firewalld / firewall-cmd.

I am running firewalld and currently I can create these rules as above at runtime and they work / take effect but I cannot find any doc on how to make them permanent without running the nftables service which the doc says I shouldn’t do unless I disable firewalld which I otherwise want to keep.

So what I’m looking for is either

A. a way to make the rule(s) permanent as created above without interfering with firewalld.

OR

B. a way to create the rule(s) using firewall-cmd so I can use firewall-cmd --runtime-to-permanent to make them permanent.

FirewallD before version 0.9 had only input filtering. The version 0.9 in Rocky 8.5 has also forward filtering (with “policy objects”).
FirewallD 1.0 does supposedly have output filtering. Whether RHEL 8 ever gets it is an open question.
That is the zones, policies, services. (Ok, zones can have simple port forwarding, i.e. DNAT & forward filter.)

Then there are “Rich Rules”. See man firewalld.richlanguage
Alas, they don’t seem to offer fancy matches.

The last resort is the “Direct Interface”. See man firewalld.direct (and “Direct Options” in man firewall-cmd )
You basically give the iptables/nft rule to firewall-cmd.
IIRC, direct interface is deprecated since future FirewallD (1.0?) will be able to do enough.

1 Like

Cheers for that !

I was just looking into setting up a systemd service to apply those ntf commands as part of the boot process when you replied so I dropped that and had a look at the --direct option: it works.

So, for anyone who’s interested, the commands equivalent to those listed above (more or less) are ;

firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 0 -m owner --gid-owner no-internet -j LOG --log-prefix 'group=no-internet'
firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 1 -m owner --gid-owner no-internet -j DROP
firewall-cmd --permanent --direct --add-rule ipv6 filter OUTPUT 0 -m owner --gid-owner no-internet -j LOG --log-prefix 'group=no-internet'
firewall-cmd --permanent --direct --add-rule ipv6 filter OUTPUT 1 -m owner --gid-owner no-internet -j DROP

those commands won’t put the rules into effect immediately but they should be post reboot (?,not tested)… anyway, for now, next do…

firewall-cmd --reload # this activates the rules

and rules definitely in effect post reboot (tested)

As executed RL 8.5 ;

[root@localhost bob]# firewall-cmd --direct --get-all-rules # to show any existing
[root@localhost bob]# firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 0 -m owner --gid-owner no-internet -j LOG --log-prefix 'group=no-internet'
success
[root@localhost bob]# firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 1 -m owner --gid-owner no-internet -j DROP
success
[root@localhost bob]# firewall-cmd --permanent --direct --add-rule ipv6 filter OUTPUT 0 -m owner --gid-owner no-internet -j LOG --log-prefix 'group=no-internet'
success
[root@localhost bob]# firewall-cmd --permanent --direct --add-rule ipv6 filter OUTPUT 1 -m owner --gid-owner no-internet -j DROP
success
[root@localhost bob]# firewall-cmd --reload

then test working by running ping as group no-internet with sg command ;

[root@localhost bob]# sg no-internet "ping 192.168.1.10"
PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.
ping: sendmsg: Operation not permitted
ping: sendmsg: Operation not permitted
ping: sendmsg: Operation not permitted
^C
--- 192.168.1.10 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2040ms

and without the group set …

[root@localhost bob]# ping 192.168.1.10
PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.
64 bytes from 192.168.1.10: icmp_seq=1 ttl=127 time=1.01 ms
64 bytes from 192.168.1.10: icmp_seq=2 ttl=127 time=0.652 ms
64 bytes from 192.168.1.10: icmp_seq=3 ttl=127 time=0.725 ms
^C
--- 192.168.1.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2017ms
rtt min/avg/max/mdev = 0.652/0.795/1.008/0.153 ms

view logs

[root@localhost bob]# cat /var/log/messages | grep 'group=no-internet'
Jan 14 21:40:10 localhost kernel: group=no-internetIN= OUT=enp1s0 SRC=192.168.122.52 DST=192.168.1.10 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=38025 DF PROTO=ICMP TYPE=8 CODE=0 ID=4397 SEQ=1 
Jan 14 21:40:11 localhost kernel: group=no-internetIN= OUT=enp1s0 SRC=192.168.122.52 DST=192.168.1.10 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=38782 DF PROTO=ICMP TYPE=8 CODE=0 ID=4397 SEQ=2 
Jan 14 21:40:12 localhost kernel: group=no-internetIN= OUT=enp1s0 SRC=192.168.122.52 DST=192.168.1.10 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=39024 DF PROTO=ICMP TYPE=8 CODE=0 ID=4397 SEQ=3 
[root@localhost bob]# 

This can all be used with a bit more jiggery-pokery to run keepassxc without network access without having to build it - which is no trivial task: one major fly in the ointment being a Ruby dependency that isn’t obviously available. There’s another topic open on the keepassxc question I’ll update/link to here later.

Thanks again !