import UBI firewalld-1.3.4-7.el9

This commit is contained in:
eabdullin 2024-11-12 10:37:24 +00:00
parent c56d981db8
commit 536a59d2c6
19 changed files with 2478 additions and 1 deletions

View File

@ -0,0 +1,53 @@
From a27f6afa21de35aa98e5309430dbcab9e6056f9c Mon Sep 17 00:00:00 2001
From: Pat Riehecky <riehecky@fnal.gov>
Date: Wed, 1 Feb 2023 09:52:43 -0600
Subject: [PATCH 05/22] v2.0.0: feat(service): add OpenTelemetry (OTLP) service
(cherry picked from commit 77c7061cc191bec6d8a36d2666c2d3c3e0ccbb4a)
---
config/Makefile.am | 1 +
config/services/opentelemetry.xml | 7 +++++++
po/POTFILES.in | 1 +
3 files changed, 9 insertions(+)
create mode 100644 config/services/opentelemetry.xml
diff --git a/config/Makefile.am b/config/Makefile.am
index d66398563ff2..47f30c1566e0 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -247,6 +247,7 @@ CONFIG_FILES = \
services/ntp.xml \
services/nut.xml \
services/openvpn.xml \
+ services/opentelemetry.xml \
services/ovirt-imageio.xml \
services/ovirt-storageconsole.xml \
services/ovirt-vmconsole.xml \
diff --git a/config/services/opentelemetry.xml b/config/services/opentelemetry.xml
new file mode 100644
index 000000000000..46c0e5258957
--- /dev/null
+++ b/config/services/opentelemetry.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>OTLP</short>
+ <description>OpenTelemetry Protocol (OTLP) specification describes the encoding, transport, and delivery mechanism of telemetry data between telemetry sources, intermediate nodes such as collectors and telemetry backends.</description>
+ <port protocol="tcp" port="4317"/>
+ <port protocol="tcp" port="4318"/>
+</service>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f3c0595980f9..1c990542ac4d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -180,6 +180,7 @@ config/services/nrpe.xml
config/services/ntp.xml
config/services/nut.xml
config/services/openvpn.xml
+config/services/opentelemetry.xml
config/services/ovirt-imageio.xml
config/services/ovirt-storageconsole.xml
config/services/ovirt-vmconsole.xml
--
2.43.5

View File

@ -0,0 +1,131 @@
From 6f221d65193cda838e241a18dd07b6da2ae22f78 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Wed, 29 Nov 2023 17:02:07 +0100
Subject: [PATCH 06/22] v2.1.0: feat(icmp): add ICMPv6 Multicast Listener
Discovery (MLD) types
Note that ip6tables does not support these ICMPv6 types. Currently,
the name of the ICMP types in firewalld must correspond to the names
in iptables. As ip6tables doesn't support it, it does not. If ip6tables
adds support for "mld-listener-query", but calls it differently, we have
a problem. Nothing that can be done about that.
`man nft` also lists an alias "mld-listener-reduction" (for
"mld-listener-done", type 132). That alias is not supported. Use the
name as from RFC 4890.
(cherry picked from commit dd88bbf812e0a50766b69c2bf12470ecf9d2466a)
---
config/Makefile.am | 4 ++++
config/icmptypes/mld-listener-done.xml | 7 +++++++
config/icmptypes/mld-listener-query.xml | 7 +++++++
config/icmptypes/mld-listener-report.xml | 7 +++++++
config/icmptypes/mld2-listener-report.xml | 7 +++++++
po/POTFILES.in | 4 ++++
src/firewall/core/nftables.py | 4 ++++
7 files changed, 40 insertions(+)
create mode 100644 config/icmptypes/mld-listener-done.xml
create mode 100644 config/icmptypes/mld-listener-query.xml
create mode 100644 config/icmptypes/mld-listener-report.xml
create mode 100644 config/icmptypes/mld2-listener-report.xml
diff --git a/config/Makefile.am b/config/Makefile.am
index 47f30c1566e0..edae25fd9de0 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -83,6 +83,10 @@ CONFIG_FILES = \
icmptypes/host-unknown.xml \
icmptypes/host-unreachable.xml \
icmptypes/ip-header-bad.xml \
+ icmptypes/mld-listener-done.xml \
+ icmptypes/mld-listener-query.xml \
+ icmptypes/mld-listener-report.xml \
+ icmptypes/mld2-listener-report.xml \
icmptypes/neighbour-advertisement.xml \
icmptypes/neighbour-solicitation.xml \
icmptypes/network-prohibited.xml \
diff --git a/config/icmptypes/mld-listener-done.xml b/config/icmptypes/mld-listener-done.xml
new file mode 100644
index 000000000000..09b8bbba5b90
--- /dev/null
+++ b/config/icmptypes/mld-listener-done.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<icmptype>
+ <short>MLD Listener Done</short>
+ <description>ICMPv6 Link-Local Multicast Listener Discovery (MDL) of type Multicast Listener Done (type 132) (RFC 4890 section 4.4.1). Also known as mld-listener-reduction to nft.</description>
+ <destination ipv4="no"/>
+ <destination ipv6="yes"/>
+</icmptype>
diff --git a/config/icmptypes/mld-listener-query.xml b/config/icmptypes/mld-listener-query.xml
new file mode 100644
index 000000000000..418685578d1d
--- /dev/null
+++ b/config/icmptypes/mld-listener-query.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<icmptype>
+ <short>MLD Listener Query</short>
+ <description>ICMPv6 Link-Local Multicast Listener Discovery (MDL) of type Multicast Listener Query (type 130) (RFC 4890 section 4.4.1).</description>
+ <destination ipv4="no"/>
+ <destination ipv6="yes"/>
+</icmptype>
diff --git a/config/icmptypes/mld-listener-report.xml b/config/icmptypes/mld-listener-report.xml
new file mode 100644
index 000000000000..98fb4161b298
--- /dev/null
+++ b/config/icmptypes/mld-listener-report.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<icmptype>
+ <short>MLD Listener Report</short>
+ <description>ICMPv6 Link-Local Multicast Listener Discovery (MDL) of type Multicast Listener Report (type 131) (RFC 4890 section 4.4.1).</description>
+ <destination ipv4="no"/>
+ <destination ipv6="yes"/>
+</icmptype>
diff --git a/config/icmptypes/mld2-listener-report.xml b/config/icmptypes/mld2-listener-report.xml
new file mode 100644
index 000000000000..faee68c95b20
--- /dev/null
+++ b/config/icmptypes/mld2-listener-report.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<icmptype>
+ <short>MLDv2 Multicast Listener Report</short>
+ <description>ICMPv6 Link-Local Multicast Listener Discovery (MDLv2) of type Multicast Listener Report (type 143) (RFC 4890 section 4.4.1).</description>
+ <destination ipv4="no"/>
+ <destination ipv6="yes"/>
+</icmptype>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1c990542ac4d..adeebdee3f55 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -15,6 +15,10 @@ config/icmptypes/host-redirect.xml
config/icmptypes/host-unknown.xml
config/icmptypes/host-unreachable.xml
config/icmptypes/ip-header-bad.xml
+config/icmptypes/mld-listener-done.xml
+config/icmptypes/mld-listener-query.xml
+config/icmptypes/mld-listener-report.xml
+config/icmptypes/mld2-listener-report.xml
config/icmptypes/neighbour-advertisement.xml
config/icmptypes/neighbour-solicitation.xml
config/icmptypes/network-prohibited.xml
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 6ad4b9168403..3df3fa3c3742 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -140,6 +140,10 @@ ICMP_TYPES_FRAGMENTS = {
"echo-reply": _icmp_types_fragments("icmpv6", "echo-reply"),
"echo-request": _icmp_types_fragments("icmpv6", "echo-request"),
"failed-policy": _icmp_types_fragments("icmpv6", "destination-unreachable", 5),
+ "mld-listener-done": _icmp_types_fragments("icmpv6", "mld-listener-done"),
+ "mld-listener-query": _icmp_types_fragments("icmpv6", "mld-listener-query"),
+ "mld-listener-report": _icmp_types_fragments("icmpv6", "mld-listener-report"),
+ "mld2-listener-report": _icmp_types_fragments("icmpv6", "mld2-listener-report"),
"neighbour-advertisement": _icmp_types_fragments("icmpv6", "nd-neighbor-advert"),
"neighbour-solicitation": _icmp_types_fragments("icmpv6", "nd-neighbor-solicit"),
"no-route": _icmp_types_fragments("icmpv6", "destination-unreachable", 0),
--
2.43.5

View File

@ -0,0 +1,71 @@
From 22b100b8ac9aeeacae851e2b9f11e4dc1741cd85 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Tue, 12 Dec 2023 14:58:07 +0100
Subject: [PATCH 07/22] v2.1.0: fix(rich): validate service name of rich rule
Previously, validation of valid service names was not done.
That meant:
$ firewall-cmd --add-rich-rule='rule priority="-100" family="ipv4" source address="10.0.0.10" service name="listen" accept' --permanent
success
$ firewall-cmd --reload
Error: INVALID_SERVICE: listen
which left firewalld in a bad state.
Now:
$ firewall-cmd --add-rich-rule='rule priority="-100" family="ipv4" source address="10.0.0.10" service name="listen" accept' --permanent
Error: INVALID_SERVICE: Zone 'public': 'listen' not among existing services
https://issues.redhat.com/browse/RHEL-5790
(cherry picked from commit fbcdddd3e38c31a7b8325bf02764b84344c216b0)
---
src/firewall/core/io/policy.py | 8 ++++++++
src/tests/features/rich_rules.at | 7 ++++++-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/firewall/core/io/policy.py b/src/firewall/core/io/policy.py
index 7d383abb0a2d..f9a1114d7969 100644
--- a/src/firewall/core/io/policy.py
+++ b/src/firewall/core/io/policy.py
@@ -471,6 +471,14 @@ def common_check_config(obj, config, item, all_config, all_io_objects):
log.debug1("{} (unsupported)".format(ex))
else:
raise ex
+ elif isinstance(obj_rich.element, rich.Rich_Service):
+ if obj_rich.element.name not in all_io_objects["services"]:
+ raise FirewallError(
+ errors.INVALID_SERVICE,
+ "{} '{}': '{}' not among existing services".format(
+ obj_type, obj.name, obj_rich.element.name
+ ),
+ )
def common_writer(obj, handler):
# short
diff --git a/src/tests/features/rich_rules.at b/src/tests/features/rich_rules.at
index aadc76da57b4..f7d1a1d0abf4 100644
--- a/src/tests/features/rich_rules.at
+++ b/src/tests/features/rich_rules.at
@@ -46,6 +46,10 @@ FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priorit
FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priority=0 source address=10.10.10.13 drop'], 0, ignore)
FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priority=-1 source address=10.10.10.14 accept'], 0, ignore)
FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule family=ipv4 priority=1 source address=10.10.10.15 accept'], 0, ignore)
+
+dnl Invalid service name is rejected.
+FWD_CHECK([--permanent --policy foobar --add-rich-rule='rule priority="-100" family="ipv4" source address="10.0.0.10" service name="bogusservice" accept'], 101, ignore, ignore)
+
FWD_RELOAD
NFT_LIST_RULES([inet], [filter_IN_policy_foobar_pre], 0, [dnl
table inet firewalld {
@@ -319,4 +323,5 @@ IP6TABLES_LIST_RULES([filter], [IN_foobar_post], 0, [dnl
ACCEPT 0 -- ::/0 ::/0
])
-FWD_END_TEST([-e '/ERROR: INVALID_ZONE:/d'])
+FWD_END_TEST([-e '/ERROR: INVALID_ZONE:/d' dnl
+ -e "/ERROR: INVALID_SERVICE: Policy 'foobar': 'bogusservice' not among existing services/d"])
--
2.43.5

View File

@ -0,0 +1,66 @@
From 11ee9b9ed8da78bfc11edffc2c9386efa41be1cf Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 18 Dec 2023 18:22:38 -0500
Subject: [PATCH 08/22] v2.1.0: improvement(nftables): do not track rule
handles for policy table
It's not necessary. This table is transient and we simply delete the
entire table when we're done with it.
(cherry picked from commit 119dff1d86f841cd2f33ddbab278bc9257dae7b0)
---
src/firewall/core/nftables.py | 24 +++++++-----------------
1 file changed, 7 insertions(+), 17 deletions(-)
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 3df3fa3c3742..690a5dc067ab 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -386,6 +386,11 @@ class nftables(object):
if verb not in output["nftables"][index]:
continue
+ # don't bother tracking handles for the policy table as we simply
+ # delete the entire table.
+ if TABLE_NAME_POLICY == output["nftables"][index][verb]["rule"]["table"]:
+ continue
+
self.rule_to_handle[rule_key] = output["nftables"][index][verb]["rule"]["handle"]
def set_rule(self, rule, log_denied):
@@ -408,18 +413,8 @@ class nftables(object):
"name": table}}}]
def build_flush_rules(self):
- # Policy is stashed in a separate table that we're _not_ going to
- # flush. As such, we retain the policy rule handles and ref counts.
- saved_rule_to_handle = {}
- saved_rule_ref_count = {}
- for rule in self._build_set_policy_rules_ct_rules(True):
- policy_key = self._get_rule_key(rule)
- if policy_key in self.rule_to_handle:
- saved_rule_to_handle[policy_key] = self.rule_to_handle[policy_key]
- saved_rule_ref_count[policy_key] = self.rule_ref_count[policy_key]
-
- self.rule_to_handle = saved_rule_to_handle
- self.rule_ref_count = saved_rule_ref_count
+ self.rule_to_handle = {}
+ self.rule_ref_count = {}
self.rich_rule_priority_counts = {}
self.policy_priority_counts = {}
self.zone_source_index_cache = {}
@@ -475,11 +470,6 @@ class nftables(object):
rules += self._build_set_policy_rules_ct_rules(True)
elif policy == "ACCEPT":
- for rule in self._build_set_policy_rules_ct_rules(False):
- policy_key = self._get_rule_key(rule)
- if policy_key in self.rule_to_handle:
- rules.append(rule)
-
rules += self._build_delete_table_rules(TABLE_NAME_POLICY)
else:
raise FirewallError(UNKNOWN_ERROR, "not implemented")
--
2.43.5

View File

@ -0,0 +1,203 @@
From c53dabcb9ca5c6d9ab2b076d961127a67afe8f8f Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Fri, 11 Aug 2023 18:16:20 +0200
Subject: [PATCH 09/22] v2.1.0: improvement(fw): make set_policy("DROP") more
flexible
We will add a reload-policy via
ReloadPolicy=OUTPUT:{ACCEPT,REJECT,DROP},INPUT:{ACCEPT,REJECT,DROP},FORWARD:{ACCEPT,REJECT,DROP}
Extend set_policy() so that the "DROP" policy can be overridden.
(cherry picked from commit e3bb468ff469373d193398b471a59f7ab7d29f27)
---
src/firewall/core/ebtables.py | 2 +-
src/firewall/core/fw.py | 27 +++++++++++++---
src/firewall/core/ipXtables.py | 11 +++++--
src/firewall/core/nftables.py | 56 ++++++++++++++++++++++++----------
4 files changed, 72 insertions(+), 24 deletions(-)
diff --git a/src/firewall/core/ebtables.py b/src/firewall/core/ebtables.py
index c1c0b8587a5c..f059975724a5 100644
--- a/src/firewall/core/ebtables.py
+++ b/src/firewall/core/ebtables.py
@@ -237,7 +237,7 @@ class ebtables(object):
rules.append(["-t", table, flag])
return rules
- def build_set_policy_rules(self, policy):
+ def build_set_policy_rules(self, policy, policy_details):
rules = []
_policy = "DROP" if policy == "PANIC" else policy
for table in BUILT_IN_CHAINS.keys():
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index f1bc124b9443..ccec875f3c3c 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -983,7 +983,18 @@ class Firewall(object):
if use_transaction is None:
transaction.execute(True)
- def set_policy(self, policy, use_transaction=None):
+ def _set_policy_build_rules(self, backend, policy, policy_details=None):
+ assert policy in ("ACCEPT", "DROP", "PANIC")
+ if policy_details is None:
+ dp = "ACCEPT" if policy == "ACCEPT" else "DROP"
+ policy_details = {
+ "INPUT": dp,
+ "OUTPUT": dp,
+ "FORWARD": dp,
+ }
+ return backend.build_set_policy_rules(policy, policy_details)
+
+ def set_policy(self, policy, policy_details=None, use_transaction=None):
if use_transaction is None:
transaction = FirewallTransaction(self)
else:
@@ -992,7 +1003,7 @@ class Firewall(object):
log.debug1("Setting policy to '%s'", policy)
for backend in self.enabled_backends():
- rules = backend.build_set_policy_rules(policy)
+ rules = self._set_policy_build_rules(backend, policy, policy_details)
transaction.add_rules(backend, rules)
if use_transaction is None:
@@ -1224,13 +1235,19 @@ class Firewall(object):
# for the old backend that was set to DROP above.
if not self._panic and old_firewall_backend != self._firewall_backend:
if old_firewall_backend == "nftables":
- for rule in self.nftables_backend.build_set_policy_rules("ACCEPT"):
+ for rule in self._set_policy_build_rules(
+ self.nftables_backend, "ACCEPT"
+ ):
self.nftables_backend.set_rule(rule, self._log_denied)
else:
- for rule in self.ip4tables_backend.build_set_policy_rules("ACCEPT"):
+ for rule in self._set_policy_build_rules(
+ self.ip4tables_backend, "ACCEPT"
+ ):
self.ip4tables_backend.set_rule(rule, self._log_denied)
if self.ip6tables_enabled:
- for rule in self.ip6tables_backend.build_set_policy_rules("ACCEPT"):
+ for rule in self._set_policy_build_rules(
+ self.ip6tables_backend, "ACCEPT"
+ ):
self.ip6tables_backend.set_rule(rule, self._log_denied)
if start_exception:
diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py
index e05a2bd4d7ed..1a0cea7a3b4e 100644
--- a/src/firewall/core/ipXtables.py
+++ b/src/firewall/core/ipXtables.py
@@ -578,7 +578,7 @@ class ip4tables(object):
rules.append(["-t", table, flag])
return rules
- def build_set_policy_rules(self, policy):
+ def build_set_policy_rules(self, policy, policy_details):
rules = []
_policy = "DROP" if policy == "PANIC" else policy
for table in BUILT_IN_CHAINS.keys():
@@ -587,7 +587,14 @@ class ip4tables(object):
if table == "nat":
continue
for chain in BUILT_IN_CHAINS[table]:
- rules.append(["-t", table, "-P", chain, _policy])
+ if table == "filter":
+ p = policy_details[chain]
+ if p == "REJECT":
+ rules.append(["-t", table, "-A", chain, "-j", "REJECT"])
+ p = "DROP"
+ else:
+ p = _policy
+ rules.append(["-t", table, "-P", chain, p])
return rules
def supported_icmp_types(self, ipv=None):
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 690a5dc067ab..e9816147ef8e 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -421,20 +421,17 @@ class nftables(object):
return self._build_delete_table_rules(TABLE_NAME)
- def _build_set_policy_rules_ct_rules(self, enable):
+ def _build_set_policy_rules_ct_rule(self, enable, hook):
add_del = { True: "add", False: "delete" }[enable]
- rules = []
- for hook in ["input", "forward", "output"]:
- rules.append({add_del: {"rule": {"family": "inet",
- "table": TABLE_NAME_POLICY,
- "chain": "%s_%s" % ("filter", hook),
- "expr": [{"match": {"left": {"ct": {"key": "state"}},
- "op": "in",
- "right": {"set": ["established", "related"]}}},
- {"accept": None}]}}})
- return rules
-
- def build_set_policy_rules(self, policy):
+ return {add_del: {"rule": {"family": "inet",
+ "table": TABLE_NAME_POLICY,
+ "chain": "%s_%s" % ("filter", hook),
+ "expr": [{"match": {"left": {"ct": {"key": "state"}},
+ "op": "in",
+ "right": {"set": ["established", "related"]}}},
+ {"accept": None}]}}}
+
+ def build_set_policy_rules(self, policy, policy_details):
# Policy is not exposed to the user. It's only to make sure we DROP
# packets while reloading and for panic mode. As such, using hooks with
# a higher priority than our base chains is sufficient.
@@ -459,16 +456,43 @@ class nftables(object):
# To drop everything except existing connections we use
# "filter" because it occurs _after_ conntrack.
- for hook in ["input", "forward", "output"]:
+ for hook in ("INPUT", "FORWARD", "OUTPUT"):
+ d_policy = policy_details[hook]
+ assert d_policy in ("ACCEPT", "REJECT", "DROP")
+ hook = hook.lower()
+ chain_name = f"filter_{hook}"
+
rules.append({"add": {"chain": {"family": "inet",
"table": TABLE_NAME_POLICY,
- "name": "%s_%s" % ("filter", hook),
+ "name": chain_name,
"type": "filter",
"hook": hook,
"prio": 0 + NFT_HOOK_OFFSET - 1,
"policy": "drop"}}})
- rules += self._build_set_policy_rules_ct_rules(True)
+ rules.append(self._build_set_policy_rules_ct_rule(True, hook))
+
+ if d_policy == "ACCEPT":
+ expr_fragment = {"accept": None}
+ elif d_policy == "DROP":
+ expr_fragment = {"drop": None}
+ else:
+ expr_fragment = {
+ "reject": {"type": "icmpx", "expr": "admin-prohibited"}
+ }
+
+ rules.append(
+ {
+ "add": {
+ "rule": {
+ "family": "inet",
+ "table": TABLE_NAME_POLICY,
+ "chain": chain_name,
+ "expr": [expr_fragment],
+ }
+ }
+ }
+ )
elif policy == "ACCEPT":
rules += self._build_delete_table_rules(TABLE_NAME_POLICY)
else:
--
2.43.5

View File

@ -0,0 +1,303 @@
From 67c8a0010ba6244c40e48a93560eb66d91a2ca09 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Mon, 14 Aug 2023 14:53:55 +0200
Subject: [PATCH 10/22] v2.1.0: feat(fw): add ReloadPolicy option in
firewalld.conf
One interesting aspect is that during `firewall-cmd --reload`, the code
first sets the policy to "DROP", before reloading "firewalld.conf". That
means, changing the value only takes effect after the next reload. But
that seems expected as we set the policy before starting to reload.
Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2149039
(cherry picked from commit 0019371a8f42d376ac9cce79cc5e1e7d2049f021)
---
config/firewalld.conf | 8 +++
doc/xml/firewalld.conf.xml | 16 ++++++
src/firewall/config/__init__.py.in | 1 +
src/firewall/core/fw.py | 13 ++++-
src/firewall/core/io/firewalld_conf.py | 56 ++++++++++++++++++++-
src/tests/features/features.at | 1 +
src/tests/features/reloadpolicy.at | 12 +++++
src/tests/unit/test_firewalld_conf.py | 68 ++++++++++++++++++++++++++
8 files changed, 172 insertions(+), 3 deletions(-)
create mode 100644 src/tests/features/reloadpolicy.at
create mode 100644 src/tests/unit/test_firewalld_conf.py
diff --git a/config/firewalld.conf b/config/firewalld.conf
index f8caf11c8a86..7a0be1ff1b76 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -66,6 +66,14 @@ FirewallBackend=nftables
# Default: yes
FlushAllOnReload=yes
+# ReloadPolicy
+# Policy during reload. By default all traffic except for established
+# connections is dropped while the rules are updated. Set to "DROP", "REJECT"
+# or "ACCEPT". Alternatively, specify it per table, like
+# "OUTPUT:ACCEPT,INPUT:DROP,FORWARD:REJECT".
+# Default: ReloadPolicy=INPUT:DROP,FORWARD:DROP,OUTPUT:DROP
+ReloadPolicy=INPUT:DROP,FORWARD:DROP,OUTPUT:DROP
+
# RFC3964_IPv4
# As per RFC 3964, filter IPv6 traffic with 6to4 destination addresses that
# correspond to IPv4 addresses that should not be routed over the public
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index e4312acc8e1c..022569ccf502 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -195,6 +195,22 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>ReloadPolicy</option></term>
+ <listitem>
+ <para>
+ The policy during reload. By default, all traffic except
+ established connections is dropped while reloading the
+ firewall rules. This can be overridden for INPUT, FORWARD
+ and OUTPUT. The accepted values are "DROP", "REJECT" and
+ "ACCEPT", which then applies to all tables. Alternatively,
+ the policy can be specified per table, like
+ "INPUT:REJECT,FORWARD:DROP,OUTPUT:ACCEPT".
+ Defaults to "INPUT:DROP,FORWARD:DROP,OUTPUT:DROP".
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>RFC3964_IPv4</option></term>
<listitem>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index d982384a0382..da1e31e10e58 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -133,5 +133,6 @@ FALLBACK_LOG_DENIED = "off"
FALLBACK_AUTOMATIC_HELPERS = "no"
FALLBACK_FIREWALL_BACKEND = "nftables"
FALLBACK_FLUSH_ALL_ON_RELOAD = True
+FALLBACK_RELOAD_POLICY = "INPUT:DROP,FORWARD:DROP,OUTPUT:DROP"
FALLBACK_RFC3964_IPV4 = True
FALLBACK_ALLOW_ZONE_DRIFTING = False
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index ccec875f3c3c..ac13be122b66 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -1000,7 +1000,13 @@ class Firewall(object):
else:
transaction = use_transaction
- log.debug1("Setting policy to '%s'", policy)
+ log.debug1(
+ "Setting policy to '%s'%s",
+ policy,
+ f" (ReloadPolicy={firewalld_conf._unparse_reload_policy(policy_details)})"
+ if policy == "DROP"
+ else "",
+ )
for backend in self.enabled_backends():
rules = self._set_policy_build_rules(backend, policy, policy_details)
@@ -1146,7 +1152,10 @@ class Firewall(object):
_ipset_objs.append(self.ipset.get_ipset(_name))
if not _panic:
- self.set_policy("DROP")
+ reload_policy = firewalld_conf._parse_reload_policy(
+ self._firewalld_conf.get("ReloadPolicy")
+ )
+ self.set_policy("DROP", policy_details=reload_policy)
self.flush()
self.cleanup()
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index b907c5b1e60b..d2879b319d1f 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -31,7 +31,7 @@ valid_keys = [ "DefaultZone", "MinimalMark", "CleanupOnExit",
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting" ]
+ "AllowZoneDrifting", "ReloadPolicy" ]
class firewalld_conf(object):
def __init__(self, filename):
@@ -77,6 +77,7 @@ class firewalld_conf(object):
self.set("AutomaticHelpers", config.FALLBACK_AUTOMATIC_HELPERS)
self.set("FirewallBackend", config.FALLBACK_FIREWALL_BACKEND)
self.set("FlushAllOnReload", "yes" if config.FALLBACK_FLUSH_ALL_ON_RELOAD else "no")
+ self.set("ReloadPolicy", config.FALLBACK_RELOAD_POLICY)
self.set("RFC3964_IPv4", "yes" if config.FALLBACK_RFC3964_IPV4 else "no")
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
@@ -208,6 +209,17 @@ class firewalld_conf(object):
config.FALLBACK_FLUSH_ALL_ON_RELOAD)
self.set("FlushAllOnReload", str(config.FALLBACK_FLUSH_ALL_ON_RELOAD))
+ value = self.get("ReloadPolicy")
+ try:
+ value = self._parse_reload_policy(value)
+ except ValueError:
+ log.warning(
+ "ReloadPolicy '%s' is not valid, using default value '%s'",
+ value,
+ config.FALLBACK_RELOAD_POLICY,
+ )
+ self.set("ReloadPolicy", config.FALLBACK_RELOAD_POLICY)
+
value = self.get("RFC3964_IPv4")
if not value or value.lower() not in [ "yes", "true", "no", "false" ]:
if value is not None:
@@ -330,3 +342,45 @@ class firewalld_conf(object):
raise IOError("Failed to create '%s': %s" % (self.filename, msg))
else:
os.chmod(self.filename, 0o600)
+
+ @staticmethod
+ def _parse_reload_policy(value):
+ valid = True
+ result = {
+ "INPUT": "DROP",
+ "FORWARD": "DROP",
+ "OUTPUT": "DROP",
+ }
+ if value:
+ value = value.strip()
+ v = value.upper()
+ if v in ("ACCEPT", "REJECT", "DROP"):
+ for k in result:
+ result[k] = v
+ else:
+ for a in value.replace(";", ",").split(","):
+ a = a.strip()
+ if not a:
+ continue
+ a2 = a.replace("=", ":").split(":", 2)
+ if len(a2) != 2:
+ valid = False
+ continue
+ k = a2[0].strip().upper()
+ if k not in result:
+ valid = False
+ continue
+ v = a2[1].strip().upper()
+ if v not in ("ACCEPT", "REJECT", "DROP"):
+ valid = False
+ continue
+ result[k] = v
+
+ if not valid:
+ raise ValueError("Invalid ReloadPolicy")
+
+ return result
+
+ @staticmethod
+ def _unparse_reload_policy(value):
+ return ",".join(f"{k}:{v}" for k, v in value.items())
diff --git a/src/tests/features/features.at b/src/tests/features/features.at
index f59baea1cd70..065cb2872e88 100644
--- a/src/tests/features/features.at
+++ b/src/tests/features/features.at
@@ -20,3 +20,4 @@ m4_include([features/startup_failsafe.at])
m4_include([features/ipset.at])
m4_include([features/reset_defaults.at])
m4_include([features/iptables_no_flush_on_shutdown.at])
+m4_include([features/reloadpolicy.at])
diff --git a/src/tests/features/reloadpolicy.at b/src/tests/features/reloadpolicy.at
new file mode 100644
index 000000000000..fea1aa26aab4
--- /dev/null
+++ b/src/tests/features/reloadpolicy.at
@@ -0,0 +1,12 @@
+FWD_START_TEST([check ReloadPolicy])
+AT_KEYWORDS(reloadpolicy rhbz2149039)
+
+AT_CHECK([sed -i 's/^ReloadPolicy=.*/ReloadPolicy=INPUT:REJECT,FORWARD:ACCEPT/' ./firewalld.conf])
+dnl call RELOAD twice, to see more action about the ReloadPolicy.
+FWD_RELOAD()
+FWD_RELOAD()
+
+AT_CHECK([sed -i 's/^ReloadPolicy=.*/ReloadPolicy=REJECT/' ./firewalld.conf])
+FWD_RELOAD()
+
+FWD_END_TEST()
diff --git a/src/tests/unit/test_firewalld_conf.py b/src/tests/unit/test_firewalld_conf.py
new file mode 100644
index 000000000000..0ce1fd279f91
--- /dev/null
+++ b/src/tests/unit/test_firewalld_conf.py
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import firewall.core.io.firewalld_conf
+import firewall.config
+
+
+def test_reload_policy():
+ def t(value, expected_valid=True, **kw):
+
+ expected = {
+ "INPUT": "DROP",
+ "FORWARD": "DROP",
+ "OUTPUT": "DROP",
+ }
+ for k, v in kw.items():
+ assert k in expected
+ expected[k] = v
+
+ try:
+ parsed = (
+ firewall.core.io.firewalld_conf.firewalld_conf._parse_reload_policy(
+ value
+ )
+ )
+ except ValueError:
+ assert not expected_valid
+ return
+
+ assert parsed == expected
+ assert expected_valid
+
+ unparsed = (
+ firewall.core.io.firewalld_conf.firewalld_conf._unparse_reload_policy(
+ parsed
+ )
+ )
+ parsed2 = firewall.core.io.firewalld_conf.firewalld_conf._parse_reload_policy(
+ unparsed
+ )
+ assert parsed2 == parsed
+
+ t(None)
+ t("")
+ t(" ")
+ t(" input: ACCept ", INPUT="ACCEPT")
+ t(
+ "forward:DROP, forward : REJEct; input: ACCept ",
+ INPUT="ACCEPT",
+ FORWARD="REJECT",
+ )
+ t(" accept ", INPUT="ACCEPT", FORWARD="ACCEPT", OUTPUT="ACCEPT")
+ t("REJECT", INPUT="REJECT", FORWARD="REJECT", OUTPUT="REJECT")
+ t("forward=REJECT", FORWARD="REJECT")
+ t("forward=REJECT , input=accept", FORWARD="REJECT", INPUT="ACCEPT")
+ t("forward=REJECT , xinput=accept", expected_valid=False)
+ t("forward=REJECT, ACCEPT", expected_valid=False)
+
+ def _norm(reload_policy):
+ parsed = firewall.core.io.firewalld_conf.firewalld_conf._parse_reload_policy(
+ reload_policy
+ )
+ return firewall.core.io.firewalld_conf.firewalld_conf._unparse_reload_policy(
+ parsed
+ )
+
+ assert firewall.config.FALLBACK_RELOAD_POLICY == _norm(
+ firewall.config.FALLBACK_RELOAD_POLICY
+ )
--
2.43.5

View File

@ -0,0 +1,27 @@
From 55e40954a8c596fabe03371e9a508d3518273ac1 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 14 May 2024 16:27:54 -0400
Subject: [PATCH 11/22] v2.2.0: test(functions): add macro CHECK_NFTABLES_FIB
(cherry picked from commit 0aeebef07bc57b1f56b107632cdfdd809384398c)
---
src/tests/functions.at | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/tests/functions.at b/src/tests/functions.at
index f454ca980046..65a4ce078e05 100644
--- a/src/tests/functions.at
+++ b/src/tests/functions.at
@@ -748,3 +748,9 @@ m4_define([CHECK_NM_CAPABILITY_OVS], [
m4_define([IF_BACKEND_IS_DEFAULT], [
m4_if(nftables, FIREWALL_BACKEND, [$1], [])
])
+
+m4_define([CHECK_NFTABLES_FIB], [
+ m4_if(nftables, FIREWALL_BACKEND, [
+ IF_HOST_SUPPORTS_NFT_FIB([], [AT_SKIP_IF([:])])
+ ])
+])
--
2.43.5

View File

@ -0,0 +1,31 @@
From d368d579c78652a68273897d5f8b5099d251a9b5 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 14 May 2024 16:21:06 -0400
Subject: [PATCH 12/22] v2.2.0: test(functions): add macro
CHECK_NFTABLES_FIB_IN_FORWARD
(cherry picked from commit b9cf7b75c7d94efa98545a3b7ad5020b1896b22a)
---
src/tests/functions.at | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/tests/functions.at b/src/tests/functions.at
index 65a4ce078e05..b2372dd4075b 100644
--- a/src/tests/functions.at
+++ b/src/tests/functions.at
@@ -754,3 +754,12 @@ m4_define([CHECK_NFTABLES_FIB], [
IF_HOST_SUPPORTS_NFT_FIB([], [AT_SKIP_IF([:])])
])
])
+
+m4_define([CHECK_NFTABLES_FIB_IN_FORWARD], [
+ m4_if(nftables, FIREWALL_BACKEND, [
+ NS_CHECK([nft add table inet firewalld_check])
+ NS_CHECK([nft add chain inet firewalld_check foobar { type filter hook forward priority 0 \; }])
+ AT_SKIP_IF([! NS_CMD([nft add rule inet firewalld_check foobar meta nfproto ipv6 fib saddr . mark . iif oif missing drop >/dev/null 2>&1])])
+ NS_CHECK([nft delete table inet firewalld_check])
+ ])
+])
--
2.43.5

View File

@ -0,0 +1,51 @@
From c1620d5ad4c151382373a138ab0c36dd7561a4bb Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 14 May 2024 16:29:50 -0400
Subject: [PATCH 13/22] v2.2.0: test(rpfilter): use CHECK macros
(cherry picked from commit 352f3fc7fc00b675178de1eff8f0197607741de7)
---
src/tests/features/rpfilter.at | 27 +++++++++++----------------
1 file changed, 11 insertions(+), 16 deletions(-)
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 01fb81ea75ef..ccc8a6cf5e80 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -1,22 +1,17 @@
-FWD_START_TEST([rpfilter])
+FWD_START_TEST([rpfilter - strict])
AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
-IF_HOST_SUPPORTS_NFT_FIB([
- NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
- table inet firewalld {
- chain filter_PREROUTING {
- icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept
- meta nfproto ipv6 fib saddr . mark . iif oif missing drop
- }
- }
- ])
-], [
- NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
- table inet firewalld {
- chain filter_PREROUTING {
- }
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=yes/' ./firewalld.conf])
+FWD_RELOAD()
+
+NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
+ table inet firewalld {
+ chain filter_PREROUTING {
+ icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept
+ meta nfproto ipv6 fib saddr . mark . iif oif missing drop
}
- ])
+ }
])
IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
--
2.43.5

View File

@ -0,0 +1,41 @@
From 0ba1eed533e4cd1dd77771ba7c16dc0edcea841e Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 13 May 2024 13:53:55 -0400
Subject: [PATCH 14/22] v2.2.0: test(IPv6_rpfilter): verify valid values
Including the deprecated "yes" value.
(cherry picked from commit 1e91792157d36355669b4f02a82c1ee603a9467d)
---
src/tests/features/rpfilter.at | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index ccc8a6cf5e80..755d9dfd33cc 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -22,4 +22,20 @@ IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
PREROUTING_ZONES 0 -- ::/0 ::/0
])
-FWD_END_TEST
+FWD_END_TEST()
+
+FWD_START_TEST([rpfilter - config values])
+AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
+
+dnl Verify other/deprecated configuration values are accepted.
+dnl
+m4_foreach([VALUE], [[no], [yes], [false], [true]], [
+ AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=VALUE/' ./firewalld.conf])
+ FWD_RELOAD()
+])
+dnl And a bogus one.
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=bogus/' ./firewalld.conf])
+FWD_RELOAD()
+
+FWD_END_TEST([-e "/^WARNING: IPv6_rpfilter 'bogus' is not valid/d"])
--
2.43.5

View File

@ -0,0 +1,336 @@
From 7973ddf8d9f972f0292c8c865da9e0ebaefd77cb Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Thu, 16 May 2024 09:26:43 -0400
Subject: [PATCH 15/22] v2.2.0: chore(IPv6_rpfilter): prepare for new config
values
This is just prep work for supporting other configuration values. This
commits supports using "strict" as a value which is synonymous with
"yes" and is the current default behavior.
(cherry picked from commit cd959f21a5ceb41057b76f817b0456c281408ae0)
---
config/firewalld.conf | 15 ++++++++++-----
doc/xml/firewalld.conf.xml | 12 +++++++++---
doc/xml/firewalld.dbus.xml | 8 ++++++--
src/firewall/config/__init__.py.in | 3 ++-
src/firewall/core/fw.py | 19 ++++++++-----------
src/firewall/core/io/firewalld_conf.py | 14 ++++++++------
src/firewall/server/config.py | 22 ++++++++++++++++++----
src/firewall/server/firewalld.py | 2 +-
src/tests/dbus/firewalld.conf.at | 4 ++++
src/tests/features/rpfilter.at | 2 +-
10 files changed, 67 insertions(+), 34 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index 7a0be1ff1b76..48e2a5a6527a 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -26,14 +26,19 @@ CleanupModulesOnExit=no
Lockdown=no
# IPv6_rpfilter
-# Performs a reverse path filter test on a packet for IPv6. If a reply to the
-# packet would be sent via the same interface that the packet arrived on, the
-# packet will match and be accepted, otherwise dropped.
+# Performs reverse path filtering (RPF) on IPv6 packets as per RFC 3704.
+# Possible values:
+# - strict: Performs "strict" filtering as per RFC 3704. This check
+# verifies that the in ingress interface is the same interface
+# that would be used to send a packet reply to the source. That
+# is, ingress == egress.
+# - no: RPF is completely disabled.
+#
# The rp_filter for IPv4 is controlled using sysctl.
# Note: This feature has a performance impact. See man page FIREWALLD.CONF(5)
# for details.
-# Default: yes
-IPv6_rpfilter=yes
+# Default: strict
+IPv6_rpfilter=strict
# IndividualCalls
# Do not use combined -restore calls, but individual calls. This increases the
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 022569ccf502..be6972aa0d8a 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -121,9 +121,15 @@
<term><option>IPv6_rpfilter</option></term>
<listitem>
<para>
- If this option is enabled (it is by default), reverse path filter test on a packet for IPv6 is performed.
- If a reply to the packet would be sent via the same interface that the packet arrived on, the packet will match and be accepted, otherwise dropped.
- For IPv4 the rp_filter is controlled using sysctl.
+ Performs reverse path filtering (RPF) on IPv6 packets as per RFC 3704.
+ Possible values:
+ - strict: Performs "strict" filtering as per RFC 3704. This check
+ verifies that the in ingress interface is the same interface
+ that would be used to send a packet reply to the source. That
+ is, ingress == egress.
+ - no: RPF is completely disabled.
+
+ The rp_filter for IPv4 is controlled using sysctl.
</para>
<para>
<emphasis role="bold">Note</emphasis>: This feature has a performance
diff --git a/doc/xml/firewalld.dbus.xml b/doc/xml/firewalld.dbus.xml
index a3196ea4af38..f04cf5ae757b 100644
--- a/doc/xml/firewalld.dbus.xml
+++ b/doc/xml/firewalld.dbus.xml
@@ -2858,8 +2858,12 @@
</listitem>
</varlistentry>
<varlistentry id="FirewallD1.config.Properties.IPv6_rpfilter">
- <term><parameter>IPv6_rpfilter</parameter> - s - (rw)</term>
- <listitem><para>Indicates whether the reverse path filter test on a packet for IPv6 is enabled. If a reply to the packet would be sent via the same interface that the packet arrived on, the packet will match and be accepted, otherwise dropped.</para></listitem>
+ <term><parameter>IPv6_rpfilter</parameter> - b - (rw)</term>
+ <listitem><para>Deprecated. See <link linkend="FirewallD1.config.Properties.IPv6_rpfilter2">org.fedoraproject.FirewallD1.config.Properties.IPv6_rpfilter2</link>.</para></listitem>
+ </varlistentry>
+ <varlistentry id="FirewallD1.config.Properties.IPv6_rpfilter2">
+ <term><parameter>IPv6_rpfilter2</parameter> - s - (rw)</term>
+ <listitem><para>Indicates whether the reverse path filter (RFE 3704) test on a packet for IPv6 is enabled.</para></listitem>
</varlistentry>
<varlistentry id="FirewallD1.config.Properties.IndividualCalls">
<term><parameter>IndividualCalls</parameter> - s - (ro)</term>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index da1e31e10e58..68e9bddce5a8 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -120,6 +120,7 @@ COMMANDS = {
LOG_DENIED_VALUES = [ "all", "unicast", "broadcast", "multicast", "off" ]
AUTOMATIC_HELPERS_VALUES = [ "yes", "no", "system" ]
FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
+IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict"]
# fallbacks: will be overloaded by firewalld.conf
FALLBACK_ZONE = "public"
@@ -127,7 +128,7 @@ FALLBACK_MINIMAL_MARK = 100
FALLBACK_CLEANUP_ON_EXIT = True
FALLBACK_CLEANUP_MODULES_ON_EXIT = False
FALLBACK_LOCKDOWN = False
-FALLBACK_IPV6_RPFILTER = True
+FALLBACK_IPV6_RPFILTER = "strict"
FALLBACK_INDIVIDUAL_CALLS = False
FALLBACK_LOG_DENIED = "off"
FALLBACK_AUTOMATIC_HELPERS = "no"
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index ac13be122b66..b2e150077958 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -98,7 +98,7 @@ class Firewall(object):
self.ebtables_enabled, self._state, self._panic,
self._default_zone, self._module_refcount, self._marks,
self.cleanup_on_exit, self.cleanup_modules_on_exit,
- self.ipv6_rpfilter_enabled, self.ipset_enabled,
+ self._ipv6_rpfilter, self.ipset_enabled,
self._individual_calls, self._log_denied)
def __init_vars(self):
@@ -112,7 +112,7 @@ class Firewall(object):
# fallback settings will be overloaded by firewalld.conf
self.cleanup_on_exit = config.FALLBACK_CLEANUP_ON_EXIT
self.cleanup_modules_on_exit = config.FALLBACK_CLEANUP_MODULES_ON_EXIT
- self.ipv6_rpfilter_enabled = config.FALLBACK_IPV6_RPFILTER
+ self._ipv6_rpfilter = config.FALLBACK_IPV6_RPFILTER
self._individual_calls = config.FALLBACK_INDIVIDUAL_CALLS
self._log_denied = config.FALLBACK_LOG_DENIED
self._firewall_backend = config.FALLBACK_FIREWALL_BACKEND
@@ -329,14 +329,11 @@ class Firewall(object):
if self._firewalld_conf.get("IPv6_rpfilter"):
value = self._firewalld_conf.get("IPv6_rpfilter")
if value is not None:
- if value.lower() in [ "no", "false" ]:
- self.ipv6_rpfilter_enabled = False
- if value.lower() in [ "yes", "true" ]:
- self.ipv6_rpfilter_enabled = True
- if self.ipv6_rpfilter_enabled:
- log.debug1("IPv6 rpfilter is enabled")
- else:
- log.debug1("IPV6 rpfilter is disabled")
+ if value.lower() in ["no", "false"]:
+ self._ipv6_rpfilter = "no"
+ elif value.lower() in ["yes", "true", "strict"]:
+ self._ipv6_rpfilter = "strict"
+ log.debug1(f"IPv6_rpfilter is set to '{self._ipv6_rpfilter}'")
if self._firewalld_conf.get("IndividualCalls"):
value = self._firewalld_conf.get("IndividualCalls")
@@ -933,7 +930,7 @@ class Firewall(object):
if self.is_ipv_enabled("ipv6"):
ipv6_backend = self.get_backend_by_ipv("ipv6")
if "raw" in ipv6_backend.get_available_tables():
- if self.ipv6_rpfilter_enabled:
+ if self._ipv6_rpfilter != "no":
rules = ipv6_backend.build_rpfilter_rules(self._log_denied)
transaction.add_rules(ipv6_backend, rules)
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index d2879b319d1f..9ad64883b656 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -71,7 +71,7 @@ class firewalld_conf(object):
self.set("CleanupOnExit", "yes" if config.FALLBACK_CLEANUP_ON_EXIT else "no")
self.set("CleanupModulesOnExit", "yes" if config.FALLBACK_CLEANUP_MODULES_ON_EXIT else "no")
self.set("Lockdown", "yes" if config.FALLBACK_LOCKDOWN else "no")
- self.set("IPv6_rpfilter","yes" if config.FALLBACK_IPV6_RPFILTER else "no")
+ self.set("IPv6_rpfilter", config.FALLBACK_IPV6_RPFILTER)
self.set("IndividualCalls", "yes" if config.FALLBACK_INDIVIDUAL_CALLS else "no")
self.set("LogDenied", config.FALLBACK_LOG_DENIED)
self.set("AutomaticHelpers", config.FALLBACK_AUTOMATIC_HELPERS)
@@ -160,12 +160,14 @@ class firewalld_conf(object):
# check ipv6_rpfilter
value = self.get("IPv6_rpfilter")
- if not value or value.lower() not in [ "yes", "true", "no", "false" ]:
+ if not value or value.lower() not in config.IPV6_RPFILTER_VALUES:
if value is not None:
- log.warning("IPv6_rpfilter '%s' is not valid, using default "
- "value %s", value if value else '',
- config.FALLBACK_IPV6_RPFILTER)
- self.set("IPv6_rpfilter","yes" if config.FALLBACK_IPV6_RPFILTER else "no")
+ log.warning(
+ "IPv6_rpfilter '%s' is not valid, using default " "value %s",
+ value if value else "",
+ config.FALLBACK_IPV6_RPFILTER,
+ )
+ self.set("IPv6_rpfilter", config.FALLBACK_IPV6_RPFILTER)
# check individual calls
value = self.get("IndividualCalls")
diff --git a/src/firewall/server/config.py b/src/firewall/server/config.py
index dfbbb889b520..b805e497bb05 100644
--- a/src/firewall/server/config.py
+++ b/src/firewall/server/config.py
@@ -100,6 +100,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupOnExit": "readwrite",
"CleanupModulesOnExit": "readwrite",
"IPv6_rpfilter": "readwrite",
+ "IPv6_rpfilter2": "readwrite",
"Lockdown": "readwrite",
"MinimalMark": "readwrite",
"IndividualCalls": "readwrite",
@@ -564,7 +565,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting" ]:
+ "AllowZoneDrifting", "IPv6_rpfilter2" ]:
raise dbus.exceptions.DBusException(
"org.freedesktop.DBus.Error.InvalidArgs: "
"Property '%s' does not exist" % prop)
@@ -594,8 +595,13 @@ class FirewallDConfig(DbusServiceObject):
value = "yes" if config.FALLBACK_LOCKDOWN else "no"
return dbus.String(value)
elif prop == "IPv6_rpfilter":
+ if value is None or value != "no":
+ return dbus.String("yes")
+ else:
+ return dbus.String("no")
+ elif prop == "IPv6_rpfilter2":
if value is None:
- value = "yes" if config.FALLBACK_IPV6_RPFILTER else "no"
+ value = config.FALLBACK_IPV6_RPFILTER
return dbus.String(value)
elif prop == "IndividualCalls":
if value is None:
@@ -640,6 +646,8 @@ class FirewallDConfig(DbusServiceObject):
return dbus.String(self._get_property(prop))
elif prop == "IPv6_rpfilter":
return dbus.String(self._get_property(prop))
+ elif prop == "IPv6_rpfilter2":
+ return dbus.String(self._get_property(prop))
elif prop == "IndividualCalls":
return dbus.String(self._get_property(prop))
elif prop == "LogDenied":
@@ -693,7 +701,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting" ]:
+ "AllowZoneDrifting", "IPv6_rpfilter2" ]:
ret[x] = self._get_property(x)
elif interface_name in [ config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
config.dbus.DBUS_INTERFACE_CONFIG_POLICIES ]:
@@ -722,7 +730,7 @@ class FirewallDConfig(DbusServiceObject):
"IPv6_rpfilter", "IndividualCalls",
"LogDenied",
"FirewallBackend", "FlushAllOnReload",
- "RFC3964_IPv4"]:
+ "RFC3964_IPv4", "IPv6_rpfilter2"]:
if property_name in [ "CleanupOnExit", "CleanupModulesOnExit",
"Lockdown", "IPv6_rpfilter",
"IndividualCalls", "FlushAllOnReload",
@@ -742,6 +750,12 @@ class FirewallDConfig(DbusServiceObject):
raise FirewallError(errors.INVALID_VALUE,
"'%s' for %s" % \
(new_value, property_name))
+ elif property_name == "IPv6_rpfilter2":
+ if new_value not in config.IPV6_RPFILTER_VALUES:
+ raise FirewallError(
+ errors.INVALID_VALUE,
+ "'%s' for %s" % (new_value, property_name),
+ )
else:
raise dbus.exceptions.DBusException(
"org.freedesktop.DBus.Error.InvalidArgs: "
diff --git a/src/firewall/server/firewalld.py b/src/firewall/server/firewalld.py
index e43ddb959f41..8b9593a22fd8 100644
--- a/src/firewall/server/firewalld.py
+++ b/src/firewall/server/firewalld.py
@@ -165,7 +165,7 @@ class FirewallD(DbusServiceObject):
return dbus.Boolean(self.fw.is_ipv_enabled("ipv6"))
elif prop == "IPv6_rpfilter":
- return dbus.Boolean(self.fw.ipv6_rpfilter_enabled)
+ return dbus.Boolean(False if self.fw._ipv6_rpfilter == "no" else True)
elif prop == "IPv6ICMPTypes":
return dbus.Array(self.fw.ipv6_supported_icmp_types, "s")
diff --git a/src/tests/dbus/firewalld.conf.at b/src/tests/dbus/firewalld.conf.at
index 10b16cd9e06f..61c220f3e59c 100644
--- a/src/tests/dbus/firewalld.conf.at
+++ b/src/tests/dbus/firewalld.conf.at
@@ -3,8 +3,10 @@ AT_KEYWORDS(dbus)
IF_HOST_SUPPORTS_NFT_FIB([
EXPECTED_IPV6_RPFILTER_VALUE=yes
+ EXPECTED_IPV6_RPFILTER2_VALUE=strict
], [
EXPECTED_IPV6_RPFILTER_VALUE=no
+ EXPECTED_IPV6_RPFILTER2_VALUE=no
])
IF_HOST_SUPPORTS_NFT_RULE_INDEX([
@@ -23,6 +25,7 @@ string "DefaultZone" : variant string "public"
string "FirewallBackend" : variant string "nftables"
string "FlushAllOnReload" : variant string "yes"
string "IPv6_rpfilter" : variant string m4_escape(["${EXPECTED_IPV6_RPFILTER_VALUE}"])
+string "IPv6_rpfilter2" : variant string m4_escape(["${EXPECTED_IPV6_RPFILTER2_VALUE}"])
string "IndividualCalls" : variant string m4_escape(["${EXPECTED_INDIVIDUAL_CALLS_VALUE}"])
string "Lockdown" : variant string "no"
string "LogDenied" : variant string "off"
@@ -43,6 +46,7 @@ _helper([AutomaticHelpers], [string:"yes"], [variant string "no"])
_helper([Lockdown], [string:"yes"], [variant string "yes"])
_helper([LogDenied], [string:"all"], [variant string "all"])
_helper([IPv6_rpfilter], [string:"yes"], [variant string "yes"])
+_helper([IPv6_rpfilter2], [string:"no"], [variant string "no"])
_helper([IndividualCalls], [string:"yes"], [variant string "yes"])
_helper([FirewallBackend], [string:"iptables"], [variant string "iptables"])
_helper([FlushAllOnReload], [string:"no"], [variant string "no"])
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 755d9dfd33cc..58a4b4500330 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -2,7 +2,7 @@ FWD_START_TEST([rpfilter - strict])
AT_KEYWORDS(rpfilter)
CHECK_NFTABLES_FIB()
-AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=yes/' ./firewalld.conf])
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=strict/' ./firewalld.conf])
FWD_RELOAD()
NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
--
2.43.5

View File

@ -0,0 +1,166 @@
From 41828e0723b1ad195a33c535918a2fb6fabaf88f Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Mon, 6 May 2024 10:22:09 -0400
Subject: [PATCH 16/22] v2.2.0: feat(IPv6_rpfilter): support loose rpfilter
Support "loose" mode as per RFC 3704. This guarantees only that there is
a patch back to the source, but does NOT guarantee that ingress ==
egress.
(cherry picked from commit 669524a10658761f614e1f199970844db7259960)
---
config/firewalld.conf | 4 ++++
doc/xml/firewalld.conf.xml | 4 ++++
src/firewall/config/__init__.py.in | 2 +-
src/firewall/core/fw.py | 2 ++
src/firewall/core/ipXtables.py | 16 ++++++++++------
src/firewall/core/nftables.py | 8 +++++++-
src/tests/features/rpfilter.at | 26 ++++++++++++++++++++++++++
7 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index 48e2a5a6527a..c35caf9f152b 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -32,6 +32,10 @@ Lockdown=no
# verifies that the in ingress interface is the same interface
# that would be used to send a packet reply to the source. That
# is, ingress == egress.
+# - loose: Performs "loose" filtering as per RFC 3704. This check only
+# verifies that there is a route back to the source through any
+# interface; even if it's not the same one on which the packet
+# arrived.
# - no: RPF is completely disabled.
#
# The rp_filter for IPv4 is controlled using sysctl.
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index be6972aa0d8a..279a149ab2a1 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -127,6 +127,10 @@
verifies that the in ingress interface is the same interface
that would be used to send a packet reply to the source. That
is, ingress == egress.
+ - loose: Performs "loose" filtering as per RFC 3704. This check only
+ verifies that there is a route back to the source through any
+ interface; even if it's not the same one on which the packet
+ arrived.
- no: RPF is completely disabled.
The rp_filter for IPv4 is controlled using sysctl.
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index 68e9bddce5a8..6bfe96be35a6 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -120,7 +120,7 @@ COMMANDS = {
LOG_DENIED_VALUES = [ "all", "unicast", "broadcast", "multicast", "off" ]
AUTOMATIC_HELPERS_VALUES = [ "yes", "no", "system" ]
FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
-IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict"]
+IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict", "loose"]
# fallbacks: will be overloaded by firewalld.conf
FALLBACK_ZONE = "public"
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index b2e150077958..d9724e9c8534 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -333,6 +333,8 @@ class Firewall(object):
self._ipv6_rpfilter = "no"
elif value.lower() in ["yes", "true", "strict"]:
self._ipv6_rpfilter = "strict"
+ elif value.lower() in ["loose"]:
+ self._ipv6_rpfilter = "loose"
log.debug1(f"IPv6_rpfilter is set to '{self._ipv6_rpfilter}'")
if self._firewalld_conf.get("IndividualCalls"):
diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py
index 1a0cea7a3b4e..339840c37ba4 100644
--- a/src/firewall/core/ipXtables.py
+++ b/src/firewall/core/ipXtables.py
@@ -1456,13 +1456,17 @@ class ip6tables(ip4tables):
def build_rpfilter_rules(self, log_denied=False):
rules = []
- rules.append([ "-I", "PREROUTING", "-t", "mangle",
- "-m", "rpfilter", "--invert", "--validmark",
- "-j", "DROP" ])
+ rpfilter_fragment = ["-m", "rpfilter", "--invert", "--validmark"]
+ if self._fw._ipv6_rpfilter == "loose":
+ rpfilter_fragment += ["--loose"]
+
+ rules.append([ "-I", "PREROUTING", "-t", "mangle" ]
+ + rpfilter_fragment +
+ [ "-j", "DROP" ])
if log_denied != "off":
- rules.append([ "-I", "PREROUTING", "-t", "mangle",
- "-m", "rpfilter", "--invert", "--validmark",
- "-j", "LOG",
+ rules.append([ "-I", "PREROUTING", "-t", "mangle" ]
+ + rpfilter_fragment +
+ [ "-j", "LOG",
"--log-prefix", "rpfilter_DROP: " ])
rules.append([ "-I", "PREROUTING", "-t", "mangle",
"-p", "ipv6-icmp",
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index e9816147ef8e..9827e84042ef 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1614,10 +1614,16 @@ class nftables(object):
def build_rpfilter_rules(self, log_denied=False):
rules = []
+
+ if self._fw._ipv6_rpfilter == "loose":
+ fib_flags = ["saddr", "mark"]
+ else:
+ fib_flags = ["saddr", "mark", "iif"]
+
expr_fragments = [{"match": {"left": {"meta": {"key": "nfproto"}},
"op": "==",
"right": "ipv6"}},
- {"match": {"left": {"fib": {"flags": ["saddr", "iif", "mark"],
+ {"match": {"left": {"fib": {"flags": fib_flags,
"result": "oif"}},
"op": "==",
"right": False}}]
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 58a4b4500330..23cd9e0e8d7f 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -24,6 +24,32 @@ IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
FWD_END_TEST()
+FWD_START_TEST([rpfilter - loose])
+AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
+
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=loose/' ./firewalld.conf])
+FWD_RELOAD()
+
+NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
+ table inet firewalld {
+ chain filter_PREROUTING {
+ icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept
+ meta nfproto ipv6 fib saddr . mark oif missing drop
+ }
+ }
+])
+
+IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
+ ACCEPT 58 -- ::/0 ::/0 ipv6-icmptype 134
+ ACCEPT 58 -- ::/0 ::/0 ipv6-icmptype 135
+ DROP 0 -- ::/0 ::/0 rpfilter loose validmark invert
+ PREROUTING_direct 0 -- ::/0 ::/0
+ PREROUTING_ZONES 0 -- ::/0 ::/0
+])
+
+FWD_END_TEST()
+
FWD_START_TEST([rpfilter - config values])
AT_KEYWORDS(rpfilter)
CHECK_NFTABLES_FIB()
--
2.43.5

View File

@ -0,0 +1,229 @@
From a965defecfad03530f3374271fb4889ff895ab1c Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 14 May 2024 12:16:44 -0400
Subject: [PATCH 17/22] v2.2.0: feat(IPv6_rpfilter): support loose-forward
rpfilter
This adds a new config value for IPv6_rpfilter, loose-forward. In this
mode the rpfilter occurs only for forwarded packets using the "loose"
algorithm from RFC 3704. This is a significant performance improvement
for end-stations that have a single default route because the RPF check
is completely absent on INPUT.
This new value is NOT compatible with the iptables backend. This is
enforced by configuration checks.
(cherry picked from commit fb692d2e560253c97c7fdd1e9fdbfcc8c61d0eba)
---
config/firewalld.conf | 2 ++
doc/xml/firewalld.conf.xml | 2 ++
src/firewall/config/__init__.py.in | 3 ++-
src/firewall/core/fw.py | 2 ++
src/firewall/core/io/firewalld_conf.py | 15 +++++++++++
src/firewall/core/nftables.py | 30 ++++++++++++++-------
src/tests/features/rpfilter.at | 36 ++++++++++++++++++++++++++
7 files changed, 79 insertions(+), 11 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index c35caf9f152b..5d0777d54b15 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -36,6 +36,8 @@ Lockdown=no
# verifies that there is a route back to the source through any
# interface; even if it's not the same one on which the packet
# arrived.
+# - loose-forward: This is almost identical to "loose", but does not perform
+# RPF for packets targeted to the host (INPUT).
# - no: RPF is completely disabled.
#
# The rp_filter for IPv4 is controlled using sysctl.
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 279a149ab2a1..1303758347e2 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -131,6 +131,8 @@
verifies that there is a route back to the source through any
interface; even if it's not the same one on which the packet
arrived.
+ - loose-forward: This is almost identical to "loose", but does not perform
+ RPF for packets targeted to the host (INPUT).
- no: RPF is completely disabled.
The rp_filter for IPv4 is controlled using sysctl.
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index 6bfe96be35a6..f2c4c9f5afa1 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -120,7 +120,8 @@ COMMANDS = {
LOG_DENIED_VALUES = [ "all", "unicast", "broadcast", "multicast", "off" ]
AUTOMATIC_HELPERS_VALUES = [ "yes", "no", "system" ]
FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
-IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict", "loose"]
+IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict", "loose",
+ "loose-forward"]
# fallbacks: will be overloaded by firewalld.conf
FALLBACK_ZONE = "public"
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index d9724e9c8534..8c20a4a606e2 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -335,6 +335,8 @@ class Firewall(object):
self._ipv6_rpfilter = "strict"
elif value.lower() in ["loose"]:
self._ipv6_rpfilter = "loose"
+ elif value.lower() in ["loose-forward"]:
+ self._ipv6_rpfilter = "loose-forward"
log.debug1(f"IPv6_rpfilter is set to '{self._ipv6_rpfilter}'")
if self._firewalld_conf.get("IndividualCalls"):
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index 9ad64883b656..159715df7ede 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -26,6 +26,7 @@ import shutil
from firewall import config
from firewall.core.logger import log
+from firewall import errors
valid_keys = [ "DefaultZone", "MinimalMark", "CleanupOnExit",
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
@@ -81,6 +82,18 @@ class firewalld_conf(object):
self.set("RFC3964_IPv4", "yes" if config.FALLBACK_RFC3964_IPV4 else "no")
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
+ def sanity_check(self):
+ if (
+ self.get("FirewallBackend") == "iptables"
+ and self.get("IPv6_rpfilter") == "loose-forward"
+ ):
+ raise errors.FirewallError(
+ errors.INVALID_VALUE,
+ "IPv6_rpfilter=loose-forward is incompatible "
+ "with FirewallBackend=iptables. This is a limitation "
+ "of the iptables backend.",
+ )
+
# load self.filename
def read(self):
self.clear()
@@ -238,6 +251,8 @@ class firewalld_conf(object):
config.FALLBACK_ALLOW_ZONE_DRIFTING)
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
+ self.sanity_check()
+
# save to self.filename if there are key/value changes
def write(self):
if len(self._config) < 1:
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 9827e84042ef..da2d8eb8ec29 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1614,9 +1614,13 @@ class nftables(object):
def build_rpfilter_rules(self, log_denied=False):
rules = []
+ rpfilter_chain = "filter_PREROUTING"
if self._fw._ipv6_rpfilter == "loose":
fib_flags = ["saddr", "mark"]
+ elif self._fw._ipv6_rpfilter == "loose-forward":
+ fib_flags = ["saddr", "mark"]
+ rpfilter_chain = "filter_FORWARD"
else:
fib_flags = ["saddr", "mark", "iif"]
@@ -1633,17 +1637,19 @@ class nftables(object):
rules.append({"insert": {"rule": {"family": "inet",
"table": TABLE_NAME,
- "chain": "filter_PREROUTING",
+ "chain": rpfilter_chain,
"expr": expr_fragments}}})
# RHBZ#1058505, RHBZ#1575431 (bug in kernel 4.16-4.17)
- rules.append({"insert": {"rule": {"family": "inet",
- "table": TABLE_NAME,
- "chain": "filter_PREROUTING",
- "expr": [{"match": {"left": {"payload": {"protocol": "icmpv6",
- "field": "type"}},
- "op": "==",
- "right": {"set": ["nd-router-advert", "nd-neighbor-solicit"]}}},
- {"accept": None}]}}})
+ if self._fw._ipv6_rpfilter != "loose-forward":
+ # this rule doesn't make sense for forwarded packets
+ rules.append({"insert": {"rule": {"family": "inet",
+ "table": TABLE_NAME,
+ "chain": rpfilter_chain,
+ "expr": [{"match": {"left": {"payload": {"protocol": "icmpv6",
+ "field": "type"}},
+ "op": "==",
+ "right": {"set": ["nd-router-advert", "nd-neighbor-solicit"]}}},
+ {"accept": None}]}}})
return rules
def build_rfc3964_ipv4_rules(self):
@@ -1674,7 +1680,11 @@ class nftables(object):
"chain": "filter_OUTPUT",
"index": 1,
"expr": expr_fragments}}})
- forward_index = 4 if self._fw.get_log_denied() != "off" else 3
+ forward_index = 3
+ if self._fw.get_log_denied() != "off":
+ forward_index += 1
+ if self._fw._ipv6_rpfilter == "loose-forward":
+ forward_index += 1
rules.append({"add": {"rule": {"family": "inet",
"table": TABLE_NAME,
"chain": "filter_FORWARD",
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 23cd9e0e8d7f..5c25ed7e16f0 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -50,6 +50,42 @@ IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
FWD_END_TEST()
+FWD_START_TEST([rpfilter - loose-forward])
+AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
+CHECK_NFTABLES_FIB_IN_FORWARD()
+
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=loose-forward/' ./firewalld.conf])
+m4_if(iptables, FIREWALL_BACKEND, [
+FWD_RELOAD(114, [ignore], [ignore])
+], [
+FWD_RELOAD()
+])
+
+NFT_LIST_RULES([inet], [filter_FORWARD], 0, [dnl
+ table inet firewalld {
+ chain filter_FORWARD {
+ meta nfproto ipv6 fib saddr . mark oif missing drop
+ ct state established,related accept
+ ct status dnat accept
+ iifname "lo" accept
+ ct state invalid drop
+ ip6 daddr { ::/96, ::ffff:0.0.0.0/96, 2002::/24, 2002:a00::/24, 2002:7f00::/24, 2002:a9fe::/32, 2002:ac10::/28, 2002:c0a8::/32, 2002:e000::/19 } reject with icmpv6 addr-unreachable
+ jump filter_FORWARD_ZONES
+ reject with icmpx admin-prohibited
+ }
+ }
+])
+
+NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
+ table inet firewalld {
+ chain filter_PREROUTING {
+ }
+ }
+])
+
+FWD_END_TEST([-e "/^ERROR: INVALID_VALUE:/d"])
+
FWD_START_TEST([rpfilter - config values])
AT_KEYWORDS(rpfilter)
CHECK_NFTABLES_FIB()
--
2.43.5

View File

@ -0,0 +1,183 @@
From 7629e1c24b73d8c1e56d275c07076bc3bf99caad Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Fri, 17 May 2024 10:05:04 -0400
Subject: [PATCH 18/22] v2.2.0: feat(IPv6_rpfilter): support strict-forward
rpfilter
(cherry picked from commit 98e5cbb862885e00981611a79f7a3186da5d1050)
---
config/firewalld.conf | 2 ++
doc/xml/firewalld.conf.xml | 5 +++-
src/firewall/config/__init__.py.in | 2 +-
src/firewall/core/fw.py | 2 ++
src/firewall/core/io/firewalld_conf.py | 8 +++---
src/firewall/core/nftables.py | 7 +++--
src/tests/features/rpfilter.at | 36 ++++++++++++++++++++++++++
7 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index 5d0777d54b15..cf95af3eea8e 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -36,6 +36,8 @@ Lockdown=no
# verifies that there is a route back to the source through any
# interface; even if it's not the same one on which the packet
# arrived.
+# - strict-forward: This is almost identical to "strict", but does not perform
+# RPF for packets targeted to the host (INPUT).
# - loose-forward: This is almost identical to "loose", but does not perform
# RPF for packets targeted to the host (INPUT).
# - no: RPF is completely disabled.
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 1303758347e2..4b9bfdad15be 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -131,6 +131,8 @@
verifies that there is a route back to the source through any
interface; even if it's not the same one on which the packet
arrived.
+ - strict-forward: This is almost identical to "loose", but does not perform
+ RPF for packets targeted to the host (INPUT).
- loose-forward: This is almost identical to "loose", but does not perform
RPF for packets targeted to the host (INPUT).
- no: RPF is completely disabled.
@@ -144,7 +146,8 @@
the established connections fast path. As such it can have a
significant performance impact if there is a lot of traffic. It's
enabled by default for security, but can be disabled if performance is
- a concern.
+ a concern. Alternatively one of the variants that only does RPF on
+ forwarded packets may be used.
</para>
</listitem>
</varlistentry>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index f2c4c9f5afa1..2eda337e8180 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -121,7 +121,7 @@ LOG_DENIED_VALUES = [ "all", "unicast", "broadcast", "multicast", "off" ]
AUTOMATIC_HELPERS_VALUES = [ "yes", "no", "system" ]
FIREWALL_BACKEND_VALUES = [ "nftables", "iptables" ]
IPV6_RPFILTER_VALUES = ["yes", "true", "no", "false", "strict", "loose",
- "loose-forward"]
+ "loose-forward", "strict-forward"]
# fallbacks: will be overloaded by firewalld.conf
FALLBACK_ZONE = "public"
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index 8c20a4a606e2..f91ff53fc37a 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -337,6 +337,8 @@ class Firewall(object):
self._ipv6_rpfilter = "loose"
elif value.lower() in ["loose-forward"]:
self._ipv6_rpfilter = "loose-forward"
+ elif value.lower() in ["strict-forward"]:
+ self._ipv6_rpfilter = "strict-forward"
log.debug1(f"IPv6_rpfilter is set to '{self._ipv6_rpfilter}'")
if self._firewalld_conf.get("IndividualCalls"):
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index 159715df7ede..20072e26cfcd 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -83,13 +83,13 @@ class firewalld_conf(object):
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
def sanity_check(self):
- if (
- self.get("FirewallBackend") == "iptables"
- and self.get("IPv6_rpfilter") == "loose-forward"
+ if self.get("FirewallBackend") == "iptables" and self.get("IPv6_rpfilter") in (
+ "loose-forward",
+ "strict-forward",
):
raise errors.FirewallError(
errors.INVALID_VALUE,
- "IPv6_rpfilter=loose-forward is incompatible "
+ f"IPv6_rpfilter={self.get('IPv6_rpfilter')} is incompatible "
"with FirewallBackend=iptables. This is a limitation "
"of the iptables backend.",
)
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index da2d8eb8ec29..5a49b34e3a4f 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -1621,6 +1621,9 @@ class nftables(object):
elif self._fw._ipv6_rpfilter == "loose-forward":
fib_flags = ["saddr", "mark"]
rpfilter_chain = "filter_FORWARD"
+ elif self._fw._ipv6_rpfilter == "strict-forward":
+ fib_flags = ["saddr", "mark", "iif"]
+ rpfilter_chain = "filter_FORWARD"
else:
fib_flags = ["saddr", "mark", "iif"]
@@ -1640,7 +1643,7 @@ class nftables(object):
"chain": rpfilter_chain,
"expr": expr_fragments}}})
# RHBZ#1058505, RHBZ#1575431 (bug in kernel 4.16-4.17)
- if self._fw._ipv6_rpfilter != "loose-forward":
+ if self._fw._ipv6_rpfilter not in ("loose-forward", "strict-forward"):
# this rule doesn't make sense for forwarded packets
rules.append({"insert": {"rule": {"family": "inet",
"table": TABLE_NAME,
@@ -1683,7 +1686,7 @@ class nftables(object):
forward_index = 3
if self._fw.get_log_denied() != "off":
forward_index += 1
- if self._fw._ipv6_rpfilter == "loose-forward":
+ if self._fw._ipv6_rpfilter in ("loose-forward", "strict-forward"):
forward_index += 1
rules.append({"add": {"rule": {"family": "inet",
"table": TABLE_NAME,
diff --git a/src/tests/features/rpfilter.at b/src/tests/features/rpfilter.at
index 5c25ed7e16f0..9ad50c993ba0 100644
--- a/src/tests/features/rpfilter.at
+++ b/src/tests/features/rpfilter.at
@@ -50,6 +50,42 @@ IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
FWD_END_TEST()
+FWD_START_TEST([rpfilter - strict-forward])
+AT_KEYWORDS(rpfilter)
+CHECK_NFTABLES_FIB()
+CHECK_NFTABLES_FIB_IN_FORWARD()
+
+AT_CHECK([sed -i 's/^IPv6_rpfilter.*/IPv6_rpfilter=strict-forward/' ./firewalld.conf])
+m4_if(iptables, FIREWALL_BACKEND, [
+FWD_RELOAD(114, [ignore], [ignore])
+], [
+FWD_RELOAD()
+])
+
+NFT_LIST_RULES([inet], [filter_FORWARD], 0, [dnl
+ table inet firewalld {
+ chain filter_FORWARD {
+ meta nfproto ipv6 fib saddr . mark . iif oif missing drop
+ ct state established,related accept
+ ct status dnat accept
+ iifname "lo" accept
+ ct state invalid drop
+ ip6 daddr { ::/96, ::ffff:0.0.0.0/96, 2002::/24, 2002:a00::/24, 2002:7f00::/24, 2002:a9fe::/32, 2002:ac10::/28, 2002:c0a8::/32, 2002:e000::/19 } reject with icmpv6 addr-unreachable
+ jump filter_FORWARD_ZONES
+ reject with icmpx admin-prohibited
+ }
+ }
+])
+
+NFT_LIST_RULES([inet], [filter_PREROUTING], 0, [dnl
+ table inet firewalld {
+ chain filter_PREROUTING {
+ }
+ }
+])
+
+FWD_END_TEST([-e "/^ERROR: INVALID_VALUE:/d"])
+
FWD_START_TEST([rpfilter - loose-forward])
AT_KEYWORDS(rpfilter)
CHECK_NFTABLES_FIB()
--
2.43.5

View File

@ -0,0 +1,31 @@
From e031ce8e41d2fc23735be91f7070127f8812b490 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Fri, 21 Jun 2024 16:17:44 -0400
Subject: [PATCH 19/22] v2.2.0: test(functions): start firewalld with file
logging
This is a test environment. Trying to log to syslog does not make sense.
The default, mixed, sends to both file and syslog, but with different
log level settings.
(cherry picked from commit 5e0c28d5f7f71a216485169610d8c72a3a3518d1)
---
src/tests/functions.at | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/tests/functions.at b/src/tests/functions.at
index b2372dd4075b..d1c89ed5b982 100644
--- a/src/tests/functions.at
+++ b/src/tests/functions.at
@@ -9,7 +9,7 @@ m4_define([FWD_STOP_FIREWALLD], [
])
m4_define([FWD_START_FIREWALLD], [
- FIREWALLD_ARGS="--nofork --nopid --log-file ./firewalld.log --system-config ./"
+ FIREWALLD_ARGS="--nofork --nopid --log-file ./firewalld.log --log-target file --system-config ./"
dnl if testsuite ran with debug flag, add debug output
${at_debug_p} && FIREWALLD_ARGS="--debug=9 ${FIREWALLD_ARGS}"
if test "x${FIREWALLD_DEFAULT_CONFIG}" != x ; then
--
2.43.5

View File

@ -0,0 +1,417 @@
From ce75ad2920f457d5b2ce578ead5d8c64f0daa490 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Fri, 21 Jun 2024 17:38:29 -0400
Subject: [PATCH 20/22] v2.2.0: feat(nftables): table ownership
This allows using the nftables table flags "owner" and "persist". When
these are in use ONLY firewalld will be able to change the firewalld
table/rules. All other processes will be blocked. This enhances
firewalld robustness by guaranteeing that firewalld's rules can not be
modified without it's knowledge, e.g. by other entities.
This also allows firewalld and nftables services to coexist. An "nft
flush ruleset" will NOT flush the "firewalld" table.
Fixes: RHEL-17002
(cherry picked from commit becd083fc2905921651af73cb15ce8c9aba9203b)
---
config/firewalld.conf | 8 +++
doc/xml/firewalld.conf.xml | 13 ++++
doc/xml/firewalld.dbus.xml | 11 ++++
src/firewall/config/__init__.py.in | 1 +
src/firewall/core/fw.py | 22 +++++++
src/firewall/core/io/firewalld_conf.py | 6 +-
src/firewall/core/nftables.py | 83 ++++++++++++++++++++++----
src/firewall/server/config.py | 16 +++--
src/tests/dbus/firewalld.conf.at | 2 +
src/tests/regression/rhbz2222044.at | 5 ++
10 files changed, 150 insertions(+), 17 deletions(-)
diff --git a/config/firewalld.conf b/config/firewalld.conf
index cf95af3eea8e..28345a13d54e 100644
--- a/config/firewalld.conf
+++ b/config/firewalld.conf
@@ -93,3 +93,11 @@ ReloadPolicy=INPUT:DROP,FORWARD:DROP,OUTPUT:DROP
# internet.
# Defaults to "yes".
RFC3964_IPv4=yes
+
+# NftablesTableOwner
+# If set to yes, the generated nftables rule set will be owned exclusively by
+# firewalld. This prevents other entities from mistakenly (or maliciously)
+# modifying firewalld's rule set. If you intentionally modify firewalld's
+# rules, then you will have to set this to "no".
+# Defaults to "yes".
+NftablesTableOwner=yes
diff --git a/doc/xml/firewalld.conf.xml b/doc/xml/firewalld.conf.xml
index 4b9bfdad15be..cf7134c04012 100644
--- a/doc/xml/firewalld.conf.xml
+++ b/doc/xml/firewalld.conf.xml
@@ -247,6 +247,19 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>NftablesTableOwner</option></term>
+ <listitem>
+ <para>
+ If set to yes, the generated nftables rule set will be owned exclusively by
+ firewalld. This prevents other entities from mistakenly (or maliciously)
+ modifying firewalld's rule set. If you intentionally modify firewalld's
+ rules, then you will have to set this to "no".
+ Defaults to "yes".
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/xml/firewalld.dbus.xml b/doc/xml/firewalld.dbus.xml
index f04cf5ae757b..00d7e8fbe5b1 100644
--- a/doc/xml/firewalld.dbus.xml
+++ b/doc/xml/firewalld.dbus.xml
@@ -2905,6 +2905,17 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry id="FirewallD1.config.Properties.NftablesTableOwner">
+ <term>NftablesTableOwner - s - (rw)</term>
+ <listitem>
+ <para>
+ If set to yes, the generated nftables rule set will be owned exclusively by
+ firewalld. This prevents other entities from mistakenly (or maliciously)
+ modifying firewalld's rule set. If you intentionally modify firewalld's
+ rules, then you will have to set this to "no".
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect3>
</refsect2>
diff --git a/src/firewall/config/__init__.py.in b/src/firewall/config/__init__.py.in
index 2eda337e8180..16291fcf795a 100644
--- a/src/firewall/config/__init__.py.in
+++ b/src/firewall/config/__init__.py.in
@@ -138,3 +138,4 @@ FALLBACK_FLUSH_ALL_ON_RELOAD = True
FALLBACK_RELOAD_POLICY = "INPUT:DROP,FORWARD:DROP,OUTPUT:DROP"
FALLBACK_RFC3964_IPV4 = True
FALLBACK_ALLOW_ZONE_DRIFTING = False
+FALLBACK_NFTABLES_TABLE_OWNER = True
diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py
index f91ff53fc37a..557b6e527dbd 100644
--- a/src/firewall/core/fw.py
+++ b/src/firewall/core/fw.py
@@ -119,6 +119,7 @@ class Firewall(object):
self._flush_all_on_reload = config.FALLBACK_FLUSH_ALL_ON_RELOAD
self._rfc3964_ipv4 = config.FALLBACK_RFC3964_IPV4
self._allow_zone_drifting = config.FALLBACK_ALLOW_ZONE_DRIFTING
+ self._nftables_table_owner = config.FALLBACK_NFTABLES_TABLE_OWNER
if self._offline:
self.ip4tables_enabled = False
@@ -290,6 +291,17 @@ class Firewall(object):
log.debug1("ebtables-restore is not supporting the --noflush "
"option, will therefore not be used")
+ self.nftables_backend.probe_support()
+
+ if (
+ self._nftables_table_owner
+ and not self.nftables_backend.supports_table_owner
+ ):
+ log.info1(
+ "Configuration has NftablesTableOwner=True, but it's "
+ "not supported by nftables. Table ownership will be disabled."
+ )
+
def _start_load_firewalld_conf(self):
# load firewalld config
log.debug1("Loading firewalld config file '%s'", config.FIREWALLD_CONF)
@@ -378,6 +390,16 @@ class Firewall(object):
log.debug1("RFC3964_IPv4 is set to '%s'",
self._rfc3964_ipv4)
+ if self._firewalld_conf.get("NftablesTableOwner"):
+ value = self._firewalld_conf.get("NftablesTableOwner")
+ if value.lower() in ["no", "false"]:
+ self._nftables_table_owner = False
+ else:
+ self._nftables_table_owner = True
+ log.debug1(
+ "NftablesTableOwner is set to '%s'", self._nftables_table_owner
+ )
+
self.config.set_firewalld_conf(copy.deepcopy(self._firewalld_conf))
def _start_load_lockdown_whitelist(self):
diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py
index 20072e26cfcd..d5b47666c0f0 100644
--- a/src/firewall/core/io/firewalld_conf.py
+++ b/src/firewall/core/io/firewalld_conf.py
@@ -32,7 +32,7 @@ valid_keys = [ "DefaultZone", "MinimalMark", "CleanupOnExit",
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting", "ReloadPolicy" ]
+ "AllowZoneDrifting", "ReloadPolicy", "NftablesTableOwner" ]
class firewalld_conf(object):
def __init__(self, filename):
@@ -81,6 +81,10 @@ class firewalld_conf(object):
self.set("ReloadPolicy", config.FALLBACK_RELOAD_POLICY)
self.set("RFC3964_IPv4", "yes" if config.FALLBACK_RFC3964_IPV4 else "no")
self.set("AllowZoneDrifting", "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no")
+ self.set(
+ "NftablesTableOwner",
+ "yes" if config.FALLBACK_NFTABLES_TABLE_OWNER else "no",
+ )
def sanity_check(self):
if self.get("FirewallBackend") == "iptables" and self.get("IPv6_rpfilter") in (
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 5a49b34e3a4f..8115bcb9d7f4 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -36,6 +36,7 @@ from nftables.nftables import Nftables
TABLE_NAME = "firewalld"
TABLE_NAME_POLICY = TABLE_NAME + "_" + "policy_drop"
+TABLE_NAME_PROBE = TABLE_NAME + "_" + "probe"
POLICY_CHAIN_PREFIX = "policy_"
# Map iptables (table, chain) to hooks and priorities.
@@ -169,6 +170,7 @@ class nftables(object):
def __init__(self, fw):
self._fw = fw
self.restore_command_exists = True
+ self.supports_table_owner = False
self.available_tables = []
self.rule_to_handle = {}
self.rule_ref_count = {}
@@ -180,6 +182,57 @@ class nftables(object):
self.nftables.set_echo_output(True)
self.nftables.set_handle_output(True)
+ def _probe_support_table_owner(self):
+ try:
+ rules = {
+ "nftables": [
+ {"metainfo": {"json_schema_version": 1}},
+ {
+ "add": {
+ "table": {
+ "family": "inet",
+ "name": TABLE_NAME_PROBE,
+ "flags": ["owner", "persist"],
+ }
+ }
+ },
+ ]
+ }
+
+ rc, output, _ = self.nftables.json_cmd(rules)
+ if rc:
+ raise ValueError("nftables probe table owner failed")
+
+ # old nftables versions would ignore table flags in JSON, so we
+ # must parse back and verify the flags are set.
+ rules = {
+ "nftables": [
+ {"metainfo": {"json_schema_version": 1}},
+ {"list": {"table": {"family": "inet", "name": TABLE_NAME_PROBE}}},
+ ]
+ }
+ self.nftables.set_echo_output(False)
+ rc, output, _ = self.nftables.json_cmd(rules)
+ self.nftables.set_echo_output(True)
+ flags = output["nftables"][1]["table"]["flags"]
+
+ self.set_rule(
+ {"delete": {"table": {"family": "inet", "name": TABLE_NAME_PROBE}}},
+ self._fw.get_log_denied(),
+ )
+
+ if "owner" not in flags or "persist" not in flags:
+ raise ValueError("nftables probe table owner failed")
+
+ log.debug2("nftables: probe_support(): owner flag is supported.")
+ self.supports_table_owner = True
+ except:
+ log.debug2("nftables: probe_support(): owner flag is NOT supported.")
+ self.supports_table_owner = False
+
+ def probe_support(self):
+ self._probe_support_table_owner()
+
def _run_replace_zone_source(self, rule, zone_source_index_cache):
for verb in ["add", "insert", "delete"]:
if verb in rule:
@@ -401,16 +454,27 @@ class nftables(object):
# Tables always exist in nftables
return [table] if table else IPTABLES_TO_NFT_HOOK.keys()
+ def _build_add_table_rules(self, table):
+ rule = {"add": {"table": {"family": "inet", "name": table}}}
+
+ if (
+ table == TABLE_NAME
+ and self._fw._nftables_table_owner
+ and self.supports_table_owner
+ ):
+ rule["add"]["table"]["flags"] = ["owner", "persist"]
+
+ return [rule]
+
def _build_delete_table_rules(self, table):
# To avoid nftables returning ENOENT we always add the table before
# deleting to guarantee it will exist.
#
# In the future, this add+delete should be replaced with "destroy", but
# that verb is too new to rely upon.
- return [{"add": {"table": {"family": "inet",
- "name": table}}},
- {"delete": {"table": {"family": "inet",
- "name": table}}}]
+ return self._build_add_table_rules(table) + [
+ {"delete": {"table": {"family": "inet", "name": table}}},
+ ]
def build_flush_rules(self):
self.rule_to_handle = {}
@@ -437,8 +501,7 @@ class nftables(object):
# a higher priority than our base chains is sufficient.
rules = []
if policy == "PANIC":
- rules.append({"add": {"table": {"family": "inet",
- "name": TABLE_NAME_POLICY}}})
+ rules.extend(self._build_add_table_rules(TABLE_NAME_POLICY))
# Use "raw" priority for panic mode. This occurs before
# conntrack, mangle, nat, etc
@@ -451,8 +514,7 @@ class nftables(object):
"prio": -300 + NFT_HOOK_OFFSET - 1,
"policy": "drop"}}})
elif policy == "DROP":
- rules.append({"add": {"table": {"family": "inet",
- "name": TABLE_NAME_POLICY}}})
+ rules.extend(self._build_add_table_rules(TABLE_NAME_POLICY))
# To drop everything except existing connections we use
# "filter" because it occurs _after_ conntrack.
@@ -511,10 +573,7 @@ class nftables(object):
return list(supported)
def build_default_tables(self):
- default_tables = []
- default_tables.append({"add": {"table": {"family": "inet",
- "name": TABLE_NAME}}})
- return default_tables
+ return self._build_add_table_rules(TABLE_NAME)
def build_default_rules(self, log_denied="off"):
default_rules = []
diff --git a/src/firewall/server/config.py b/src/firewall/server/config.py
index b805e497bb05..c07a1e6a2503 100644
--- a/src/firewall/server/config.py
+++ b/src/firewall/server/config.py
@@ -110,6 +110,7 @@ class FirewallDConfig(DbusServiceObject):
"FlushAllOnReload": "readwrite",
"RFC3964_IPv4": "readwrite",
"AllowZoneDrifting": "readwrite",
+ "NftablesTableOwner": "readwrite",
}
)
@@ -565,7 +566,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting", "IPv6_rpfilter2" ]:
+ "AllowZoneDrifting", "IPv6_rpfilter2", "NftablesTableOwner" ]:
raise dbus.exceptions.DBusException(
"org.freedesktop.DBus.Error.InvalidArgs: "
"Property '%s' does not exist" % prop)
@@ -631,6 +632,10 @@ class FirewallDConfig(DbusServiceObject):
if value is None:
value = "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no"
return dbus.String(value)
+ elif prop == "NftablesTableOwner":
+ if value is None:
+ value = "yes" if config.FALLBACK_NFTABLES_TABLE_OWNER else "no"
+ return dbus.String(value)
@dbus_handle_exceptions
def _get_dbus_property(self, prop):
@@ -662,6 +667,8 @@ class FirewallDConfig(DbusServiceObject):
return dbus.String(self._get_property(prop))
elif prop == "AllowZoneDrifting":
return dbus.String(self._get_property(prop))
+ elif prop == "NftablesTableOwner":
+ return dbus.String(self._get_property(prop))
else:
raise dbus.exceptions.DBusException(
"org.freedesktop.DBus.Error.InvalidArgs: "
@@ -701,7 +708,7 @@ class FirewallDConfig(DbusServiceObject):
"CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
"IndividualCalls", "LogDenied", "AutomaticHelpers",
"FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
- "AllowZoneDrifting", "IPv6_rpfilter2" ]:
+ "AllowZoneDrifting", "IPv6_rpfilter2", "NftablesTableOwner" ]:
ret[x] = self._get_property(x)
elif interface_name in [ config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
config.dbus.DBUS_INTERFACE_CONFIG_POLICIES ]:
@@ -730,11 +737,12 @@ class FirewallDConfig(DbusServiceObject):
"IPv6_rpfilter", "IndividualCalls",
"LogDenied",
"FirewallBackend", "FlushAllOnReload",
- "RFC3964_IPv4", "IPv6_rpfilter2"]:
+ "RFC3964_IPv4", "IPv6_rpfilter2",
+ "NftablesTableOwner" ]:
if property_name in [ "CleanupOnExit", "CleanupModulesOnExit",
"Lockdown", "IPv6_rpfilter",
"IndividualCalls", "FlushAllOnReload",
- "RFC3964_IPv4"]:
+ "RFC3964_IPv4", "NftablesTableOwner" ]:
if new_value.lower() not in [ "yes", "no",
"true", "false" ]:
raise FirewallError(errors.INVALID_VALUE,
diff --git a/src/tests/dbus/firewalld.conf.at b/src/tests/dbus/firewalld.conf.at
index 61c220f3e59c..2ead41baa00a 100644
--- a/src/tests/dbus/firewalld.conf.at
+++ b/src/tests/dbus/firewalld.conf.at
@@ -30,6 +30,7 @@ string "IndividualCalls" : variant string m4_escape(["${EXPECTED_INDIVIDUAL_CALL
string "Lockdown" : variant string "no"
string "LogDenied" : variant string "off"
string "MinimalMark" : variant int32 100
+string "NftablesTableOwner" : variant string "yes"
string "RFC3964_IPv4" : variant string "yes"
])
@@ -54,6 +55,7 @@ _helper([CleanupModulesOnExit], [string:"yes"], [variant string "yes"])
_helper([CleanupOnExit], [string:"no"], [variant string "no"])
_helper([RFC3964_IPv4], [string:"no"], [variant string "no"])
_helper([AllowZoneDrifting], [string:"yes"], [variant string "no"])
+_helper([NftablesTableOwner], [string:"no"], [variant string "no"])
dnl Note: DefaultZone is RO
m4_undefine([_helper])
diff --git a/src/tests/regression/rhbz2222044.at b/src/tests/regression/rhbz2222044.at
index 7e3454509188..2d0333865076 100644
--- a/src/tests/regression/rhbz2222044.at
+++ b/src/tests/regression/rhbz2222044.at
@@ -2,6 +2,11 @@ FWD_START_TEST([duplicate rules after restart])
AT_KEYWORDS(rhbz2222044)
AT_SKIP_IF([! NS_CMD([command -v wc >/dev/null 2>&1])])
+dnl Disable for this test because CI do not support table owner. It's very new
+dnl in nftables.
+AT_CHECK([sed -i 's/^NftablesTableOwner=.*/NftablesTableOwner=no/' ./firewalld.conf])
+FWD_RELOAD()
+
dnl rules have not changed so rule count should not change
m4_define([check_rule_count], [
m4_if(nftables, FIREWALL_BACKEND, [
--
2.43.5

View File

@ -0,0 +1,69 @@
From bf91ea35e7faf66484bdae7d0b3260c4717ee39a Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Tue, 18 Jun 2024 16:20:06 -0400
Subject: [PATCH 21/22] v2.2.0: test(nftables): table ownership
Coverage: RHEL-17002
(cherry picked from commit e7728b843c2ec3a61dbe436575c977e2ad9c8674)
---
src/tests/features/features.at | 1 +
src/tests/features/nftables_table_owner.at | 38 ++++++++++++++++++++++
2 files changed, 39 insertions(+)
create mode 100644 src/tests/features/nftables_table_owner.at
diff --git a/src/tests/features/features.at b/src/tests/features/features.at
index 065cb2872e88..83ad9d122189 100644
--- a/src/tests/features/features.at
+++ b/src/tests/features/features.at
@@ -21,3 +21,4 @@ m4_include([features/ipset.at])
m4_include([features/reset_defaults.at])
m4_include([features/iptables_no_flush_on_shutdown.at])
m4_include([features/reloadpolicy.at])
+m4_include([features/nftables_table_owner.at])
diff --git a/src/tests/features/nftables_table_owner.at b/src/tests/features/nftables_table_owner.at
new file mode 100644
index 000000000000..abc946da0ad7
--- /dev/null
+++ b/src/tests/features/nftables_table_owner.at
@@ -0,0 +1,38 @@
+m4_if(nftables, FIREWALL_BACKEND, [
+FWD_START_TEST([nftables table owner])
+AT_KEYWORDS(RHEL-17002)
+
+AT_CHECK([sed -i 's/^NftablesTableOwner=.*/NftablesTableOwner=yes/' ./firewalld.conf])
+FWD_RELOAD()
+
+AT_SKIP_IF([grep "Configuration has NftablesTableOwner=True, but it's not supported by nftables." ./firewalld.log])
+
+NS_CHECK([nft list table inet firewalld | TRIM_WHITESPACE | head -n 2], 0, [m4_strip([dnl
+ table inet firewalld { # progname firewalld
+ flags owner,persist
+])])
+
+dnl Test the transitions from On to Off
+dnl
+
+AT_CHECK([sed -i 's/^NftablesTableOwner=.*/NftablesTableOwner=no/' ./firewalld.conf])
+FWD_RELOAD()
+
+NS_CHECK([nft list table inet firewalld | TRIM_WHITESPACE | head -n 2], 0, [m4_strip([dnl
+ table inet firewalld {
+ chain mangle_PREROUTING {
+])])
+
+dnl Test the transitions from Off to On
+dnl
+
+AT_CHECK([sed -i 's/^NftablesTableOwner=.*/NftablesTableOwner=yes/' ./firewalld.conf])
+FWD_RELOAD()
+
+NS_CHECK([nft list table inet firewalld | TRIM_WHITESPACE | head -n 2], 0, [m4_strip([dnl
+ table inet firewalld { # progname firewalld
+ flags owner,persist
+])])
+
+FWD_END_TEST()
+])
--
2.43.5

View File

@ -0,0 +1,29 @@
From 8d87974a34c055e0daee3a83f11c539fc98fd6bf Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Wed, 26 Jun 2024 15:31:38 -0400
Subject: [PATCH 22/22] v2.2.0: chore(service): remove Conflicts with nftables
Now that firewalld uses the table owner flag it can coexist with the
nftables service.
(cherry picked from commit 9c6cb982981ad0aee5b85773823614ca7fd69073)
---
config/firewalld.service.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/firewalld.service.in b/config/firewalld.service.in
index afbe0ac5def7..b757a08f28dc 100644
--- a/config/firewalld.service.in
+++ b/config/firewalld.service.in
@@ -4,7 +4,7 @@ Before=network-pre.target
Wants=network-pre.target
After=dbus.service
After=polkit.service
-Conflicts=iptables.service ip6tables.service ebtables.service ipset.service nftables.service
+Conflicts=iptables.service ip6tables.service ebtables.service ipset.service
Documentation=man:firewalld(1)
[Service]
--
2.43.5

View File

@ -1,7 +1,7 @@
Summary: A firewall daemon with D-Bus interface providing a dynamic firewall
Name: firewalld
Version: 1.3.4
Release: 1%{?dist}
Release: 7%{?dist}
URL: http://www.firewalld.org
License: GPLv2+
Source0: https://github.com/firewalld/firewalld/releases/download/v%{version}/firewalld-%{version}.tar.bz2
@ -9,6 +9,24 @@ Patch1: 0001-RHEL-only-Add-cockpit-by-default-to-some-zones.patch
Patch2: 0002-v1.4.0-test-atlocal-pass-EBTABLES-to-testsuite.patch
Patch3: 0003-v1.4.0-feat-direct-avoid-iptables-flush-if-using-nft.patch
Patch4: 0004-v1.4.0-test-direct-avoid-iptables-flush-if-using-nft.patch
Patch5: 0005-v2.0.0-feat-service-add-OpenTelemetry-OTLP-service.patch
Patch6: 0006-v2.1.0-feat-icmp-add-ICMPv6-Multicast-Listener-Disco.patch
Patch7: 0007-v2.1.0-fix-rich-validate-service-name-of-rich-rule.patch
Patch8: 0008-v2.1.0-improvement-nftables-do-not-track-rule-handle.patch
Patch9: 0009-v2.1.0-improvement-fw-make-set_policy-DROP-more-flex.patch
Patch10: 0010-v2.1.0-feat-fw-add-ReloadPolicy-option-in-firewalld..patch
Patch11: 0011-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB.patch
Patch12: 0012-v2.2.0-test-functions-add-macro-CHECK_NFTABLES_FIB_I.patch
Patch13: 0013-v2.2.0-test-rpfilter-use-CHECK-macros.patch
Patch14: 0014-v2.2.0-test-IPv6_rpfilter-verify-valid-values.patch
Patch15: 0015-v2.2.0-chore-IPv6_rpfilter-prepare-for-new-config-va.patch
Patch16: 0016-v2.2.0-feat-IPv6_rpfilter-support-loose-rpfilter.patch
Patch17: 0017-v2.2.0-feat-IPv6_rpfilter-support-loose-forward-rpfi.patch
Patch18: 0018-v2.2.0-feat-IPv6_rpfilter-support-strict-forward-rpf.patch
Patch19: 0019-v2.2.0-test-functions-start-firewalld-with-file-logg.patch
Patch20: 0020-v2.2.0-feat-nftables-table-ownership.patch
Patch21: 0021-v2.2.0-test-nftables-table-ownership.patch
Patch22: 0022-v2.2.0-chore-service-remove-Conflicts-with-nftables.patch
BuildArch: noarch
BuildRequires: autoconf
BuildRequires: automake
@ -111,6 +129,8 @@ end
%autosetup -p1
%build
# must run automake since patches touch .am files
./autogen.sh
%configure --enable-sysconfig --enable-rpmmacros PYTHON="%{__python3} %{py3_shbang_opts}"
make %{?_smp_mflags}
@ -230,6 +250,26 @@ rm -rf %{buildroot}%{_datadir}/firewalld/testsuite
%{_mandir}/man1/firewall-config*.1*
%changelog
* Mon Jul 01 2024 Eric Garver <egarver@redhat.com> - 1.3.4-7
- feat(nftables): table ownership
* Mon Jul 01 2024 Eric Garver <egarver@redhat.com> - 1.3.4-6
- feat(IPv6_rpfilter): support loose rpfilter
- feat(IPv6_rpfilter): support loose-forward rpfilter
- feat(IPv6_rpfilter): support strict-forward rpfilter
* Mon Jul 01 2024 Eric Garver <egarver@redhat.com> - 1.3.4-5
- feat(fw): add ReloadPolicy option in firewalld.conf
* Mon Jul 01 2024 Eric Garver <egarver@redhat.com> - 1.3.4-4
- fix(rich): validate service name of rich rule
* Mon Jul 01 2024 Eric Garver <egarver@redhat.com> - 1.3.4-3
- feat(icmp): add ICMPv6 Multicast Listener Discovery (MLD) types
* Mon Jul 01 2024 Eric Garver <egarver@redhat.com> - 1.3.4-2
- feat(service): add OpenTelemetry (OTLP) service
* Thu Oct 26 2023 Eric Garver <egarver@redhat.com> - 1.3.4-1
- package rebase to v1.3.4