summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon <965089+sarthurdev@users.noreply.github.com>2025-11-24 20:48:07 +0100
committerGitHub <noreply@github.com>2025-11-24 20:48:07 +0100
commitfdda826b48688e253c30d3578ca5edd45b034ab0 (patch)
treee23d2dce4f302de5157604501188894a2abd31c5
parent1aec97d70ceaba9f89db8c592478e10ed1c0aefc (diff)
parentcc5895fe61f938189e229dffb7746fc93aac0f3b (diff)
downloadvyos-1x-fdda826b48688e253c30d3578ca5edd45b034ab0.tar.gz
vyos-1x-fdda826b48688e253c30d3578ca5edd45b034ab0.zip
Merge pull request #4672 from apschultz/zone_default_firewall_ruleset
firewall: T7739: Default ruleset for firewall zones
-rw-r--r--data/templates/firewall/nftables-zone.j222
-rw-r--r--interface-definitions/firewall.xml.in23
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py44
-rwxr-xr-xsrc/conf_mode/firewall.py19
4 files changed, 106 insertions, 2 deletions
diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2
index 5a2b8912e..7b4128082 100644
--- a/data/templates/firewall/nftables-zone.j2
+++ b/data/templates/firewall/nftables-zone.j2
@@ -56,6 +56,10 @@
{% endif %}
{% endfor %}
{% endif %}
+{% if zone_conf.default_firewall is vyos_defined and zone_conf.default_firewall[fw_name] is vyos_defined %}
+ counter jump NAME{{ suffix }}_{{ zone_conf.default_firewall[fw_name] }}
+ counter return
+{% endif %}
{{ zone_conf | nft_default_rule('zone_' + zone_name, family) }}
}
chain VZONE_{{ zone_name }}_OUT {
@@ -72,6 +76,20 @@
{% endif %}
{% endfor %}
{% endif %}
+{% if zone_conf.default_local is vyos_defined %}
+{% for from_zone, from_conf in zone_conf.default_local.items() if from_conf[fw_name] is vyos_defined %}
+{% if 'interface' in zone[from_zone].member %}
+ oifname { {{ zone[from_zone].member.interface | quoted_join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf[fw_name] }}
+ oifname { {{ zone[from_zone].member.interface | quoted_join(",") }} } counter return
+{% endif %}
+{% if 'vrf' in zone[from_zone].member %}
+{% for vrf_name in zone[from_zone].member.vrf %}
+ oifname { "{{ zone[from_zone]['vrf_interfaces'][vrf_name] }}" } counter jump NAME{{ suffix }}_{{ from_conf[fw_name] }}
+ oifname { "{{ zone[from_zone]['vrf_interfaces'][vrf_name] }}" } counter return
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
{{ zone_conf | nft_default_rule('zone_' + zone_name, family) }}
}
{% else %}
@@ -104,6 +122,10 @@
{% endif %}
{% endfor %}
{% endif %}
+{% if zone_conf.default_firewall is vyos_defined and zone_conf.default_firewall[fw_name] is vyos_defined %}
+ counter jump NAME{{ suffix }}_{{ zone_conf.default_firewall[fw_name] }}
+ counter return
+{% endif %}
{{ zone_conf | nft_default_rule('zone_' + zone_name, family) }}
}
{% endif %}
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index e51cb68a6..f5986164a 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -428,6 +428,29 @@
</properties>
<defaultValue>drop</defaultValue>
</leafNode>
+ <node name="default-firewall">
+ <properties>
+ <help>Default firewall rules for traffic coming into this zone</help>
+ </properties>
+ <children>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>IPv6 firewall ruleset</help>
+ <completionHelp>
+ <path>firewall ipv6 name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="name">
+ <properties>
+ <help>IPv4 firewall ruleset</help>
+ <completionHelp>
+ <path>firewall ipv4 name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<tagNode name="from">
<properties>
<help>Zone from which to filter traffic</help>
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index c29471b63..ca08cc389 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -994,6 +994,50 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip vyos_filter')
self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter')
+ def test_zone_with_default_firewall(self):
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-default', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'member', 'interface', 'eth0'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-eth1', 'firewall', 'name', 'smoketest'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'default-firewall', 'name', 'smoketest-default'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth1', 'member', 'interface', 'eth1'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth1', 'default-firewall', 'name', 'smoketest-default'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth2', 'member', 'interface', 'eth2'])
+ self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone'])
+ self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest'])
+ self.cli_set(['firewall', 'zone', 'smoketest-local', 'default-firewall', 'name', 'smoketest-default'])
+ self.cli_commit()
+
+ smoketest_eth0_search = [
+ ['iifname "eth1"', 'jump NAME_smoketest'],
+ ['jump NAME_smoketest-default']
+ ]
+ self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-eth0')
+ self.verify_nftables_chain(smoketest_eth0_search, 'ip vyos_filter', 'VZONE_smoketest-eth0')
+
+ smoketest_eth1_search = [
+ ['jump NAME_smoketest-default']
+ ]
+ self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-eth1')
+ self.verify_nftables_chain(smoketest_eth1_search, 'ip vyos_filter', 'VZONE_smoketest-eth1')
+
+ self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-eth2')
+
+ smoketest_local_in_search = [
+ ['iifname "eth0"', 'jump NAME_smoketest'],
+ ['jump NAME_smoketest-default'],
+ ]
+ self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-local_IN')
+ self.verify_nftables_chain(smoketest_local_in_search, 'ip vyos_filter', 'VZONE_smoketest-local_IN')
+
+ smoketest_local_out_search = [
+ ['oifname "eth0"', 'jump NAME_smoketest'],
+ ['oifname "eth1"', 'jump NAME_smoketest-default']
+ ]
+ self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-local_OUT')
+ self.verify_nftables_chain(smoketest_local_out_search, 'ip vyos_filter', 'VZONE_smoketest-local_OUT')
+
def test_zone_with_vrf(self):
self.cli_set(['firewall', 'ipv4', 'name', 'ZONE1-to-LOCAL', 'default-action', 'accept'])
self.cli_set(['firewall', 'ipv4', 'name', 'ZONE2_to_ZONE1', 'default-action', 'continue'])
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 4a4212a0f..a609e305c 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -154,12 +154,15 @@ def get_config(config=None):
continue
local_zone_conf['from_local'] = {}
+ local_zone_conf['default_local'] = {}
for zone, zone_conf in firewall['zone'].items():
- if zone == local_zone or 'from' not in zone_conf:
+ if zone == local_zone:
continue
- if local_zone in zone_conf['from']:
+ if 'from' in zone_conf and local_zone in zone_conf['from']:
local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone]
+ elif 'default_firewall' in zone_conf:
+ local_zone_conf['default_local'][zone] = zone_conf['default_firewall']
set_dependents('conntrack', conf)
@@ -627,6 +630,18 @@ def verify(firewall):
if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name):
raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
+ if 'default_firewall' in zone_conf:
+ v4_name = dict_search_args(zone_conf, 'default_firewall', 'name')
+ if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name):
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
+
+ v6_name = dict_search_args(zone_conf, 'default_firewall', 'ipv6_name')
+ if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name):
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
+
+ if not v4_name and not v6_name:
+ raise ConfigError('No firewall names specified for default-firewall')
+
return None
def generate(firewall):