Policy Objects: Filtering Container and Virtual Machine Traffic
Introduction
A handful of container and virtual machine runtimes have some level of integration with firewalld. The reality is the integration is minimal partly due to limitations in older firewalld versions. With firewalld’s new Policy Objects feature we can improve the situation and allow users to filter their container and virtual machine traffic.
Podman, for example, adds the container’s block of address to the trusted
zone. This effectively means firewalld does no filtering on the container
traffic. All the traffic is immediately accepted. Podman will also install its
own iptables rules to do other things: forward ports, masquerading. With
policy objects this duality of iptables and firewalld is unnecessary as it can
all be done in firewalld.
This post is both an illustration of what container and virtual machine runtimes can do to better integrate with firewalld and an example of what is possible with policy objects.
Podman as an Example
In this post we’ll use Podman as our example. First it’s useful to know how Podman currently interacts with firewalld and iptables.
Podman adds each container to firewalld’s trusted
zone. This is what allows
the container traffic to pass through firewalld.
# firewall-cmd --zone trusted --list-all
trusted (active)
target: ACCEPT
icmp-block-inversion: no
interfaces:
sources: 10.88.0.14/32
services:
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
Podman also uses iptables to setup masquerading for the container. This is the part that could not be done via firewalld before policy objects.
# iptables -t nat -L
[..]
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
CNI-a4307a257bab71a40e0f6b64 all -- 10.88.0.14 anywhere /* name: "podman" id: "7b9e4aef72c4cf188df65589f43bf4256488959700557163f57e96920d18ec2a" */
[..]
Chain CNI-a4307a257bab71a40e0f6b64 (1 references)
target prot opt source destination
ACCEPT all -- anywhere 10.88.0.0/16 /* name: "podman" id: "7b9e4aef72c4cf188df65589f43bf4256488959700557163f57e96920d18ec2a" */
MASQUERADE all -- anywhere !224.0.0.0/4 /* name: "podman" id: "7b9e4aef72c4cf188df65589f43bf4256488959700557163f57e96920d18ec2a" */
What’s Missing?
What’s missing from all the above is filtering. Podman has no method of filtering incoming or outgoing traffic on behalf of the container. Later in this post we’ll see that’s now possible in firewalld.
Creating a Podman Zone
To make things easier to reason about we’ll create a new zone called podman
.
This will make it clear that the zone is intended for containers.
# firewall-cmd --permanent --new-zone podman
Filtering to the World
Above we saw that Podman is creating iptables rules to perform masquerading. Let’s see how this could be done natively in firewalld instead.
First we need a new policy for our outbound container traffic.
# firewall-cmd --permanent --new-policy podmanToWorld
# firewall-cmd --permanent --policy podmanToWorld --add-ingress-zone podman
# firewall-cmd --permanent --policy podmanToWorld --add-egress-zone ANY
Note: ANY means any egress zone, but it does not include traffic destined to the host running firewalld.
At this point you can add anything you want to the new policy and it will filter traffic originating from the container and destined to any other host.
Let’s use this new policy to enable masquerading for the containers.
# firewall-cmd --permanent --policy podmanToWorld --add-masquerade
But we can also filter anything we want. Let’s say I don’t want my containers connecting to remote ftp servers.
# firewall-cmd --permanent --policy podmanToWorld --add-rich-rule='rule service name="ftp" reject'
As another example, maybe I don’t want my containers communicating with the
192.168.0.0/16
network. We can reject that traffic as well.
# firewall-cmd --permanent --policy podmanToWorld --add-rich-rule='rule family=ipv4 destination address=192.168.0.0/16 reject'
Forwarding Ports to a Container
We can also use a policy to forward ports from the host to the container.
# firewall-cmd --permanent --new-policy fwdPortsPodman
# firewall-cmd --permanent --policy fwdPortsPodman --add-ingress-zone ANY
# firewall-cmd --permanent --policy fwdPortsPodman --add-egress-zone HOST
Now add the forward ports.
# firewall-cmd --permanent --policy fwdPortsPodman --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=10.88.0.14
Note: In the past it was possible to forward ports to a container, but it
required adding the forward-port
to the ingress zone, e.g. public
. This
has the major disadvantage that you must know the expected ingress zone.
That’s not necessary with policies.
Filtering to the Host
You can also use a policy to filter traffic from the containers to the host.
# firewall-cmd --permanent --new-policy podmanToHost
# firewall-cmd --permanent --policy podmanToHost --add-ingress-zone podman
# firewall-cmd --permanent --policy podmanToHost --add-egress-zone HOST
A good setting would be to block all traffic to the host by default then selectively open the specific things you need.
# firewall-cmd --permanent --policy podmanToHost --set-target REJECT
# firewall-cmd --permanent --policy podmanToHost --add-service dhcp
# firewall-cmd --permanent --policy podmanToHost --add-service dns
Note: Alternatively you can get the same effect by adding the services to
the podman
zone and setting --set-target=reject
. This is because most, but
not all (e.g. masquerade), features added to a zone filter on the INPUT chain.
Move the Container to the Podman Zone
The last step is to reload firewalld and move the container to the new podman zone. This will cause all the policies we created to become active.
# firewall-cmd --reload
# firewall-cmd --zone podman --change-source 10.88.0.14/32
Note that this is a runtime configuration change. That’s because podman adds the container’s IP address to the runtime only.
Removing Podman’s iptables Rules
Now that firewalld is handling the masquerading we can flush the iptables rules created by Podman. After this there will be no Podman related rules in iptables. Everything is being handled by firewalld.
# iptables -t nat -F
Disclaimer: This command is a flush of all NAT rules. If there are other rules not installed by Podman they’ll also be deleted.
Conclusion
Hopefully this post illustrated the flexibility of policy objects and how you might use them with Podman. The same approach can be taken for docker and libvirt as well. In the future it would be great to see these projects expand their integration with firewalld and avoid the direct usage of iptables.
firewalld 0.9.0 release
A new release of firewalld, version 0.9.0, is available.
This is a feature release. It also includes all bug fixes since v0.8.0.
New features:
Statistics since v0.8.0:
- 178 commits
- 113 files changed, 15642 insertions(+), 3217 deletions(-)
Source available here:
- Tarball: firewalld-0.9.0.tar.gz
- SHA256: 7cfbf8a33f726151e60c07486af0921fa05cbbab097adf90ec1caef37b49d9a0
- Complete changelog on github: 0.8.0 to 0.9.0
Policy Objects: Introduction
A new feature, policy objects, will be part of the next firewalld feature release (v0.9.0). This is a major feature that has been in the works for almost a full year. It is significant because it closes one of the long standing gaps in firewalld’s functionality: forward and output filtering.
Motivation
With some exceptions (e.g. masquerade, forward-ports) firewalld was
previously limited to being an end-station firewall. This meant you could not
use it to filter traffic flowing between virtual machines, containers, and
zones. A subset of that functionality was available by using the direct
interface and writing your own iptables
rules, but it wasn’t a great user
experience.
What is needed is a way to apply a policy for traffic flowing between zones. Then the user can attach firewalld’s primitives: services, ports, rich rules, etc. to the policy. The end result is something that provides a very similar user interface to zones, but is much more powerful.
What does it look like?
Manipulating policies is very similar to zones. This was a deliberate design decision to make them approachable for existing firewalld users.
Here are some examples for adding features to a policy:
# firewall-cmd --policy mypolicy --add-service ssh
# firewall-cmd --policy mypolicy --add-port 1234/tcp
# firewall-cmd --policy mypolicy --add-masquerade
This example shows the settings of the built-in policy allow-host-ipv6
.
# firewall-cmd --info-policy allow-host-ipv6
allow-host-ipv6 (active)
priority: -15000
target: CONTINUE
ingress-zones: ANY
egress-zones: HOST
services:
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
rule family="ipv6" icmp-type name="neighbour-advertisement" accept
rule family="ipv6" icmp-type name="neighbour-solicitation" accept
rule family="ipv6" icmp-type name="router-advertisement" accept
rule family="ipv6" icmp-type name="redirect" accept
Relationship to Zones
Policies are applied to traffic flowing between zones in a stateful unidirectional manner. This allows different policies depending on the direction of traffic.
+----------+ policyA +----------+
| | <------------ | |
| libvirt | | public |
| | ------------> | |
+----------+ policyB +----------+
This diagram shows policyA
that applies to traffic flowing from the public
zone to the libvirt
zone. policyB
applies to traffic flowing from the
internal
zone to the public
zone.
The configuration changes necessary to result in the diagram above are:
# firewall-cmd --permanent --new-policy policyA
# firewall-cmd --permanent --policy policyA --add-ingress-zone public
# firewall-cmd --permanent --policy policyA --add-egress-zone libvirt
# firewall-cmd --permanent --new-policy policyB
# firewall-cmd --permanent --policy policyB --add-ingress-zone libvirt
# firewall-cmd --permanent --policy policyB --add-egress-zone public
Manipulating Policies
Policies must be created by the user.
# firewall-cmd --permanent --new-policy mypolicy
Ingress and Egress Zones
As shown in the diagram above policies exists between zones. This means a set of ingress and egress zones must be defined before the policy becomes active. In this context an ingress zone is the zone from which the packet originated. The egress zone is the zone to which the packet is destined.
This example makes mypolicy
apply to traffic flowing from the public
zone
to the internal
zone:
# firewall-cmd --permanent --policy mypolicy --add-ingress-zone public
# firewall-cmd --permanent --policy mypolicy --add-egress-zone internal
The ingress and egress zone list can also be manipulated in the runtime configuration. It is also valid to use multiple zones in the ingress and egress zone lists.
# firewall-cmd --policy mypolicy --add-ingress-zone public
# firewall-cmd --policy mypolicy --add-ingress-zone external
Making a policy go inactive is done by emptying the ingress and/or the egress zone list.
# firewall-cmd --policy mypolicy --remove-ingress-zone internal
Priority
Multiple policies can be applied to the same set of traffic. Therefore it’s useful to have a priority to sort the policies by precedence.
# firewall-cmd --permanent --policy mypolicy --set-priority -500
The following rules apply to policy priorities:
- policies with negative priorities apply before rules in zones
- policies with positive priorities apply after rules in zones
- This is especially important to understand because many zones have catch
all accept/drop/reject. This applies to zone with a
--set-target
that is notdefault
.
- This is especially important to understand because many zones have catch
all accept/drop/reject. This applies to zone with a
- priority 0 is reserved and not usable
Default Target
Policies have a --set-target
option just like zones. This is a catch-all at
the end of the policy’s rules. The default is CONTINUE
which means packets
will be subject to rules in following policies and zones. Other values are:
ACCEPT
, DROP
, and REJECT
.
# firewall-cmd --permanent --policy mypolicy --set-target CONTINUE
Symbolic Zones
There are a couple of symbolic zones for use in the ingress and egress zone
lists: HOST
, and ANY
. HOST
is used to allow policies for traffic
originating from or destined to the host running firewalld. ANY
is used to
apply a policy to all current and future zones. ANY
is effectively a wildcard
for all zones.
This example, creates a policy that applies to traffic originating from the
host running firewalld and is destined to any zone. Or said differently
traffic in the OUTPUT
chain.
# firewall-cmd --permanent --new-policy myOutputPolicy
# firewall-cmd --permanent --policy myOutputPolicy --add-ingress-zone HOST
# firewall-cmd --permanent --policy myOutputPolicy --add-egress-zone ANY
Man Pages
There are new man pages for policies.
- Concepts: firewalld.policies
- XML configuration: firewalld.policy
Default Policies
At present firewalld ships with a single built-in policy: allow-host-ipv6
. This
policy is active by default and allows a subset of ICMPv6 necessary for
bootstrapping IPv6 connectivity.
firewalld 0.7.5 release
A new release of firewalld, version 0.7.5, is available.
This is a bug fix only release.
Alexander Bokovoy (1):
- fix: update dynamic DCE RPC ports in freeipa-trust service
Eric Garver (30):
- fix(systemd): Conflict with nftables.service
- fix(direct): rule in a zone chain
- test(direct): rule in a zone chain
- fix(client): addService needs to reduce tuple size
- test(dbus): zone: fix false failure due to list order
- test(dbus): zone: fix zone runtime functional test title
- fix(doc): dbus: signatures for zone tuple based APIs
- fix(config): bool values in dict based import/export
- fix(dbus): service: don’t cleanup config for old set APIs
- fix(ipset): flush the set if IndividiualCalls=yes
- fix(firewall-offline-cmd): remove instances of “[P]” in help text
- docs: replace occurrences of the term blacklist with denylist
Phil Sutter (1):
- fix: core: rich: Catch ValueError on non-numeric priority values
Vrinda Punj (3):
- docs(README): add libxslt for doc generation
- fix(cli): add –zone is an invalid option with –direct
- fix(cli): add ipset type hash:mac is incompatible with the family parameter
Source available here:
- Tarball: firewalld-0.7.5.tar.gz
- SHA256: 45a8a7dbc084ef56ce306154d3834922e7f1fc2bf11b6c821f579cad51313226
- Complete changelog on github: 0.7.4 to 0.7.5
firewalld 0.8.3 release
A new release of firewalld, version 0.8.3, is available.
This is a bug fix only release.
Alexander Bokovoy (1):
- fix: update dynamic DCE RPC ports in freeipa-trust service
Eric Garver (33):
- fix: nftables: ipset: port ranges for non-default protocols
- fix(systemd): Conflict with nftables.service
- fix(direct): rule in a zone chain
- fix(client): addService needs to reduce tuple size
- fix(doc): dbus: signatures for zone tuple based APIs
- fix(config): bool values in dict based import/export
- fix(dbus): service: don’t cleanup config for old set APIs
- fix(ipset): flush the set if IndividiualCalls=yes
- fix(firewall-offline-cmd): remove instances of “[P]” in help text
- fix(rich): source mac with nftables backend
- docs: replace occurrences of the term blacklist with denylist
Phil Sutter (1):
- fix: core: rich: Catch ValueError on non-numeric priority values
Vrinda Punj (3):
- docs(README): add libxslt for doc generation
- fix(cli): add –zone is an invalid option with –direct
- fix(cli): add ipset type hash:mac is incompatible with the family parameter
Source available here:
- Tarball: firewalld-0.8.3.tar.gz
- SHA256: 4ecb16d82c2825ccfb8f109e543c0492cf6ea8c43e2d0f59901bddcead037dc6
- Complete changelog on github: 0.8.2 to 0.8.3