summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/firewall/nftables-zone.j256
-rw-r--r--[-rwxr-xr-x]interface-definitions/firewall.xml.in53
-rw-r--r--interface-definitions/include/version/firewall-version.xml.i2
-rw-r--r--python/vyos/ifconfig/interface.py12
-rw-r--r--python/vyos/utils/network.py4
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py96
-rwxr-xr-xsrc/conf_mode/firewall.py71
-rw-r--r--[-rwxr-xr-x]src/migration-scripts/firewall/16-to-170
-rwxr-xr-xsrc/migration-scripts/firewall/17-to-1841
-rw-r--r--src/op_mode/zone.py11
10 files changed, 268 insertions, 78 deletions
diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2
index e78725079..645a38706 100644
--- a/data/templates/firewall/nftables-zone.j2
+++ b/data/templates/firewall/nftables-zone.j2
@@ -8,7 +8,14 @@
{% endif %}
{% for zone_name, zone_conf in zone.items() %}
{% if 'local_zone' not in zone_conf %}
- oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }}
+{% if 'interface' in zone_conf.member %}
+ oifname { {{ zone_conf.member.interface | join(',') }} } counter jump VZONE_{{ zone_name }}
+{% endif %}
+{% if 'vrf' in zone_conf.member %}
+{% for vrf_name in zone_conf.member.vrf %}
+ oifname { {{ zone_conf['vrf_interfaces'][vrf_name] }} } counter jump VZONE_{{ zone_name }}
+{% endfor %}
+{% endif %}
{% endif %}
{% endfor %}
}
@@ -40,8 +47,15 @@
iifname lo counter return
{% if zone_conf.from is vyos_defined %}
{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter return
+
+{% if 'interface' in zone[from_zone].member %}
+ iifname { {{ zone[from_zone].member.interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
+ iifname { {{ zone[from_zone].member.interface | join(",") }} } counter return
+{% endif %}
+{% if 'vrf' in zone[from_zone].member %}
+ iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
+ iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter return
+{% endif %}
{% endfor %}
{% endif %}
{{ zone_conf | nft_default_rule('zone_' + zone_name, family) }}
@@ -50,23 +64,47 @@
oifname lo counter return
{% if zone_conf.from_local is vyos_defined %}
{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall[fw_name] is vyos_defined %}
- oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
- oifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% if 'interface' in zone[from_zone].member %}
+ oifname { {{ zone[from_zone].member.interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
+ oifname { {{ zone[from_zone].member.interface | 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.firewall[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 %}
chain VZONE_{{ zone_name }} {
- iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }}
+{% if 'interface' in zone_conf.member %}
+ iifname { {{ zone_conf.member.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }}
+{% endif %}
+{% if 'vrf' in zone_conf.member %}
+ iifname { {{ zone_conf.member.vrf | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }}
+{% endif %}
{% if zone_conf.intra_zone_filtering is vyos_defined %}
- iifname { {{ zone_conf.interface | join(",") }} } counter return
+{% if 'interface' in zone_conf.member %}
+ iifname { {{ zone_conf.member.interface | join(",") }} } counter return
+{% endif %}
+{% if 'vrf' in zone_conf.member %}
+ iifname { {{ zone_conf.member.vrf | join(",") }} } counter return
+{% endif %}
{% endif %}
{% if zone_conf.from is vyos_defined %}
{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
{% if zone[from_zone].local_zone is not defined %}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
- iifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% if 'interface' in zone[from_zone].member %}
+ iifname { {{ zone[from_zone].member.interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
+ iifname { {{ zone[from_zone].member.interface | join(",") }} } counter return
+{% endif %}
+{% if 'vrf' in zone[from_zone].member %}
+ iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
+ iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter return
+{% endif %}
{% endif %}
{% endfor %}
{% endif %}
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 07c88f799..e4fe9a508 100755..100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -16,15 +16,7 @@
</properties>
<children>
#include <include/generic-description.xml.i>
- <leafNode name="interface">
- <properties>
- <help>Interfaces to use this flowtable</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- <multi/>
- </properties>
- </leafNode>
+ #include <include/generic-interface-multi.xml.i>
<leafNode name="offload">
<properties>
<help>Offloading method</help>
@@ -155,15 +147,7 @@
<constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
</properties>
<children>
- <leafNode name="interface">
- <properties>
- <help>Interface-group member</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- <multi/>
- </properties>
- </leafNode>
+ #include <include/generic-interface-multi.xml.i>
<leafNode name="include">
<properties>
<help>Include another interface-group</help>
@@ -464,24 +448,27 @@
</node>
</children>
</tagNode>
- <leafNode name="interface">
+ <node name="member">
<properties>
<help>Interface associated with zone</help>
- <valueHelp>
- <format>txt</format>
- <description>Interface associated with zone</description>
- </valueHelp>
- <valueHelp>
- <format>vrf</format>
- <description>VRF associated with zone</description>
- </valueHelp>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- <path>vrf name</path>
- </completionHelp>
- <multi/>
</properties>
- </leafNode>
+ <children>
+ #include <include/generic-interface-multi.xml.i>
+ <leafNode name="vrf">
+ <properties>
+ <help>VRF associated with zone</help>
+ <valueHelp>
+ <format>vrf</format>
+ <description>VRF associated with zone</description>
+ </valueHelp>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<node name="intra-zone-filtering">
<properties>
<help>Intra-zone filtering</help>
diff --git a/interface-definitions/include/version/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i
index a15cf0eec..1a8098297 100644
--- a/interface-definitions/include/version/firewall-version.xml.i
+++ b/interface-definitions/include/version/firewall-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/firewall-version.xml.i -->
-<syntaxVersion component='firewall' version='17'></syntaxVersion>
+<syntaxVersion component='firewall' version='18'></syntaxVersion>
<!-- include end -->
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index cad1685a9..de821ab60 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -1423,11 +1423,13 @@ class Interface(Control):
tmp = get_interface_address(self.ifname)
if tmp and 'addr_info' in tmp:
for address_dict in tmp['addr_info']:
- # Only remove dynamic assigned addresses
- if 'dynamic' not in address_dict:
- continue
- address = address_dict['local']
- self.del_addr(address)
+ if address_dict['family'] == 'inet':
+ # Only remove dynamic assigned addresses
+ if 'dynamic' not in address_dict:
+ continue
+ address = address_dict['local']
+ prefixlen = address_dict['prefixlen']
+ self.del_addr(f'{address}/{prefixlen}')
# cleanup old config files
for file in [dhclient_config_file, systemd_override_file, dhclient_lease_file]:
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 8fce08de0..dc0c0a6d6 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -69,7 +69,9 @@ def get_vrf_members(vrf: str) -> list:
answer = json.loads(output)
for data in answer:
if 'ifname' in data:
- interfaces.append(data.get('ifname'))
+ # Skip PIM interfaces which appears in VRF
+ if 'pim' not in data.get('ifname'):
+ interfaces.append(data.get('ifname'))
except:
pass
return interfaces
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 6420afa38..10301831e 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -906,7 +906,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
def test_zone_basic(self):
self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop'])
self.cli_set(['firewall', 'ipv6', 'name', 'smoketestv6', 'default-action', 'drop'])
- self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0'])
+ self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'member', 'interface', 'eth0'])
self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest'])
self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'intra-zone-filtering', 'firewall', 'ipv6-name', 'smoketestv6'])
self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone'])
@@ -964,6 +964,98 @@ 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_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'])
+ self.cli_set(['firewall', 'ipv6', 'name', 'LOCAL_to_ZONE2_v6', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'zone', 'LOCAL', 'from', 'ZONE1', 'firewall', 'name', 'ZONE1-to-LOCAL'])
+ self.cli_set(['firewall', 'zone', 'LOCAL', 'local-zone'])
+ self.cli_set(['firewall', 'zone', 'ZONE1', 'from', 'ZONE2', 'firewall', 'name', 'ZONE2_to_ZONE1'])
+ self.cli_set(['firewall', 'zone', 'ZONE1', 'member', 'interface', 'eth1'])
+ self.cli_set(['firewall', 'zone', 'ZONE1', 'member', 'interface', 'eth2'])
+ self.cli_set(['firewall', 'zone', 'ZONE1', 'member', 'vrf', 'VRF-1'])
+ self.cli_set(['firewall', 'zone', 'ZONE2', 'from', 'LOCAL', 'firewall', 'ipv6-name', 'LOCAL_to_ZONE2_v6'])
+ self.cli_set(['firewall', 'zone', 'ZONE2', 'member', 'interface', 'vtun66'])
+ self.cli_set(['firewall', 'zone', 'ZONE2', 'member', 'vrf', 'VRF-2'])
+
+ self.cli_set(['vrf', 'name', 'VRF-1', 'table', '101'])
+ self.cli_set(['vrf', 'name', 'VRF-2', 'table', '102'])
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'vrf', 'VRF-1'])
+ self.cli_set(['interfaces', 'vti', 'vti1', 'vrf', 'VRF-2'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['chain NAME_ZONE1-to-LOCAL'],
+ ['counter', 'accept', 'comment "NAM-ZONE1-to-LOCAL default-action accept"'],
+ ['chain NAME_ZONE2_to_ZONE1'],
+ ['counter', 'continue', 'comment "NAM-ZONE2_to_ZONE1 default-action continue"'],
+ ['chain VYOS_ZONE_FORWARD'],
+ ['type filter hook forward priority filter + 1'],
+ ['oifname { "eth1", "eth2" }', 'counter packets', 'jump VZONE_ZONE1'],
+ ['oifname "eth0"', 'counter packets', 'jump VZONE_ZONE1'],
+ ['oifname "vtun66"', 'counter packets', 'jump VZONE_ZONE2'],
+ ['oifname "vti1"', 'counter packets', 'jump VZONE_ZONE2'],
+ ['chain VYOS_ZONE_LOCAL'],
+ ['type filter hook input priority filter + 1'],
+ ['counter packets', 'jump VZONE_LOCAL_IN'],
+ ['chain VYOS_ZONE_OUTPUT'],
+ ['type filter hook output priority filter + 1'],
+ ['counter packets', 'jump VZONE_LOCAL_OUT'],
+ ['chain VZONE_LOCAL_IN'],
+ ['iifname { "eth1", "eth2" }', 'counter packets', 'jump NAME_ZONE1-to-LOCAL'],
+ ['iifname "VRF-1"', 'counter packets', 'jump NAME_ZONE1-to-LOCAL'],
+ ['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'],
+ ['chain VZONE_LOCAL_OUT'],
+ ['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'],
+ ['chain VZONE_ZONE1'],
+ ['iifname { "eth1", "eth2" }', 'counter packets', 'return'],
+ ['iifname "VRF-1"', 'counter packets', 'return'],
+ ['iifname "vtun66"', 'counter packets', 'jump NAME_ZONE2_to_ZONE1'],
+ ['iifname "vtun66"', 'counter packets', 'return'],
+ ['iifname "VRF-2"', 'counter packets', 'jump NAME_ZONE2_to_ZONE1'],
+ ['iifname "VRF-2"', 'counter packets', 'return'],
+ ['counter packets', 'drop', 'comment "zone_ZONE1 default-action drop"'],
+ ['chain VZONE_ZONE2'],
+ ['iifname "vtun66"', 'counter packets', 'return'],
+ ['iifname "VRF-2"', 'counter packets', 'return'],
+ ['counter packets', 'drop', 'comment "zone_ZONE2 default-action drop"']
+ ]
+
+ nftables_search_v6 = [
+ ['chain NAME6_LOCAL_to_ZONE2_v6'],
+ ['counter', 'drop', 'comment "NAM-LOCAL_to_ZONE2_v6 default-action drop"'],
+ ['chain VYOS_ZONE_FORWARD'],
+ ['type filter hook forward priority filter + 1'],
+ ['oifname { "eth1", "eth2" }', 'counter packets', 'jump VZONE_ZONE1'],
+ ['oifname "eth0"', 'counter packets', 'jump VZONE_ZONE1'],
+ ['oifname "vtun66"', 'counter packets', 'jump VZONE_ZONE2'],
+ ['oifname "vti1"', 'counter packets', 'jump VZONE_ZONE2'],
+ ['chain VYOS_ZONE_LOCAL'],
+ ['type filter hook input priority filter + 1'],
+ ['counter packets', 'jump VZONE_LOCAL_IN'],
+ ['chain VYOS_ZONE_OUTPUT'],
+ ['type filter hook output priority filter + 1'],
+ ['counter packets', 'jump VZONE_LOCAL_OUT'],
+ ['chain VZONE_LOCAL_IN'],
+ ['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'],
+ ['chain VZONE_LOCAL_OUT'],
+ ['oifname "vtun66"', 'counter packets', 'jump NAME6_LOCAL_to_ZONE2_v6'],
+ ['oifname "vti1"', 'counter packets', 'jump NAME6_LOCAL_to_ZONE2_v6'],
+ ['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'],
+ ['chain VZONE_ZONE1'],
+ ['iifname { "eth1", "eth2" }', 'counter packets', 'return'],
+ ['iifname "VRF-1"', 'counter packets', 'return'],
+ ['counter packets', 'drop', 'comment "zone_ZONE1 default-action drop"'],
+ ['chain VZONE_ZONE2'],
+ ['iifname "vtun66"', 'counter packets', 'return'],
+ ['iifname "VRF-2"', 'counter packets', 'return'],
+ ['counter packets', 'drop', 'comment "zone_ZONE2 default-action drop"']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
+ self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter')
+
def test_flow_offload(self):
self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '10'])
self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0.10'])
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 10d389d73..768bb127d 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -18,7 +18,6 @@ import os
import re
from sys import exit
-
from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import is_node_changed
@@ -34,6 +33,8 @@ from vyos.utils.dict import dict_search_recursive
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import rc_cmd
+from vyos.utils.network import get_vrf_members
+from vyos.utils.network import get_interface_vrf
from vyos import ConfigError
from vyos import airbag
from pathlib import Path
@@ -133,6 +134,27 @@ def get_config(config=None):
fqdn_config_parse(firewall, 'firewall')
+ if not os.path.exists(nftables_conf):
+ firewall['first_install'] = True
+
+ if 'zone' in firewall:
+ for local_zone, local_zone_conf in firewall['zone'].items():
+ if 'local_zone' not in local_zone_conf:
+ # Get physical interfaces assigned to the zone if vrf is used:
+ if 'vrf' in local_zone_conf['member']:
+ local_zone_conf['vrf_interfaces'] = {}
+ for vrf_name in local_zone_conf['member']['vrf']:
+ local_zone_conf['vrf_interfaces'][vrf_name] = ','.join(get_vrf_members(vrf_name))
+ continue
+
+ local_zone_conf['from_local'] = {}
+
+ for zone, zone_conf in firewall['zone'].items():
+ if zone == local_zone or 'from' not in zone_conf:
+ continue
+ if local_zone in zone_conf['from']:
+ local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone]
+
set_dependents('conntrack', conf)
return firewall
@@ -441,28 +463,45 @@ def verify(firewall):
local_zone = False
zone_interfaces = []
+ zone_vrf = []
if 'zone' in firewall:
for zone, zone_conf in firewall['zone'].items():
- if 'local_zone' not in zone_conf and 'interface' not in zone_conf:
+ if 'local_zone' not in zone_conf and 'member' not in zone_conf:
raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone')
if 'local_zone' in zone_conf:
if local_zone:
raise ConfigError('There cannot be multiple local zones')
- if 'interface' in zone_conf:
+ if 'member' in zone_conf:
raise ConfigError('Local zone cannot have interfaces assigned')
if 'intra_zone_filtering' in zone_conf:
raise ConfigError('Local zone cannot use intra-zone-filtering')
local_zone = True
- if 'interface' in zone_conf:
- found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces]
+ if 'member' in zone_conf:
+ if 'interface' in zone_conf['member']:
+ for iface in zone_conf['member']['interface']:
+
+ if iface in zone_interfaces:
+ raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
- if found_duplicates:
- raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
+ iface_vrf = get_interface_vrf(iface)
+ if iface_vrf != 'default':
+ Warning(f"Interface {iface} assigned to zone {zone} is in VRF {iface_vrf}. This might not work as expected.")
+ zone_interfaces.append(iface)
- zone_interfaces += zone_conf['interface']
+ if 'vrf' in zone_conf['member']:
+ for vrf in zone_conf['member']['vrf']:
+ if vrf in zone_vrf:
+ raise ConfigError(f'VRF cannot be assigned to multiple zones')
+ zone_vrf.append(vrf)
+
+ if 'vrf_interfaces' in zone_conf:
+ for vrf_name, vrf_interfaces in zone_conf['vrf_interfaces'].items():
+ if not vrf_interfaces:
+ raise ConfigError(
+ f'VRF "{vrf_name}" cannot be a member of any zone. It does not contain any interfaces.')
if 'intra_zone_filtering' in zone_conf:
intra_zone = zone_conf['intra_zone_filtering']
@@ -498,22 +537,6 @@ def verify(firewall):
return None
def generate(firewall):
- if not os.path.exists(nftables_conf):
- firewall['first_install'] = True
-
- if 'zone' in firewall:
- for local_zone, local_zone_conf in firewall['zone'].items():
- if 'local_zone' not in local_zone_conf:
- continue
-
- local_zone_conf['from_local'] = {}
-
- for zone, zone_conf in firewall['zone'].items():
- if zone == local_zone or 'from' not in zone_conf:
- continue
- if local_zone in zone_conf['from']:
- local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone]
-
render(nftables_conf, 'firewall/nftables.j2', firewall)
render(sysctl_file, 'firewall/sysctl-firewall.conf.j2', firewall)
return None
diff --git a/src/migration-scripts/firewall/16-to-17 b/src/migration-scripts/firewall/16-to-17
index ad0706f04..ad0706f04 100755..100644
--- a/src/migration-scripts/firewall/16-to-17
+++ b/src/migration-scripts/firewall/16-to-17
diff --git a/src/migration-scripts/firewall/17-to-18 b/src/migration-scripts/firewall/17-to-18
new file mode 100755
index 000000000..34ce6aa07
--- /dev/null
+++ b/src/migration-scripts/firewall/17-to-18
@@ -0,0 +1,41 @@
+# Copyright (C) 2024-2025 VyOS maintainers and contributors
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# From
+# set firewall zone <zone> interface RED
+# set firewall zone <zone> interface eth0
+# To
+# set firewall zone <zone> member vrf RED
+# set firewall zone <zone> member interface eth0
+
+from vyos.configtree import ConfigTree
+
+base = ['firewall', 'zone']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ # Nothing to do
+ return
+
+ for zone in config.list_nodes(base):
+ zone_iface_base = base + [zone, 'interface']
+ zone_member_base = base + [zone, 'member']
+ if config.exists(zone_iface_base):
+ for iface in config.return_values(zone_iface_base):
+ if config.exists(['vrf', 'name', iface]):
+ config.set(zone_member_base + ['vrf'], value=iface, replace=False)
+ else:
+ config.set(zone_member_base + ['interface'], value=iface, replace=False)
+ config.delete(zone_iface_base)
diff --git a/src/op_mode/zone.py b/src/op_mode/zone.py
index 49fecdf28..df39549d2 100644
--- a/src/op_mode/zone.py
+++ b/src/op_mode/zone.py
@@ -56,10 +56,15 @@ def _convert_one_zone_data(zone: str, zone_config: dict) -> dict:
from_zone_dict['firewall_v6'] = dict_search(
'firewall.ipv6_name', from_zone_config)
list_of_rules.append(from_zone_dict)
+ zone_members =[]
+ interface_members = dict_search('member.interface', zone_config)
+ vrf_members = dict_search('member.vrf', zone_config)
+ zone_members += interface_members if interface_members is not None else []
+ zone_members += vrf_members if vrf_members is not None else []
zone_dict = {
'name': zone,
- 'interface': dict_search('interface', zone_config),
+ 'members': zone_members,
'type': 'LOCAL' if dict_search('local_zone',
zone_config) is not None else None,
}
@@ -126,7 +131,7 @@ def output_zone_list(zone_conf: dict) -> list:
if zone_conf['type'] == 'LOCAL':
zone_info.append('LOCAL')
else:
- zone_info.append("\n".join(zone_conf['interface']))
+ zone_info.append("\n".join(zone_conf['members']))
from_zone = []
firewall = []
@@ -175,7 +180,7 @@ def get_formatted_output(zone_policy: list) -> str:
:rtype: str
"""
headers = ["Zone",
- "Interfaces",
+ "Members",
"From Zone",
"Firewall IPv4",
"Firewall IPv6"