summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoromnom62 <75066712+omnom62@users.noreply.github.com>2026-08-27 04:00:17 +1000
committerGitHub <noreply@github.com>2026-08-26 21:00:17 +0300
commit433a274ce636573953f8a4be9c68743a2ed1d0a5 (patch)
treeda9e545f9ffdf50289c1b94580450a456177d74b
parent899a6bf7955592ec40670944a860a1bee97b432c (diff)
downloadvyos.vyos-433a274ce636573953f8a4be9c68743a2ed1d0a5.tar.gz
vyos.vyos-433a274ce636573953f8a4be9c68743a2ed1d0a5.zip
T8220: Firewall Zone Policy support (#447)
Add zone base policy firewall module
-rw-r--r--changelogs/fragments/t8220-fpz.yaml3
-rw-r--r--docs/vyos.vyos.vyos_firewall_global_module.rst326
-rw-r--r--plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py75
-rw-r--r--plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py325
-rw-r--r--plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py126
-rw-r--r--plugins/modules/vyos_firewall_global.py85
-rw-r--r--tests/integration/targets/vyos_firewall_global/tests/cli/_get_version.yaml17
-rw-r--r--tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_4.cfg3
-rw-r--r--tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_5.cfg21
-rw-r--r--tests/integration/targets/vyos_firewall_global/vars/main.yaml65
-rw-r--r--tests/integration/targets/vyos_firewall_global/vars/v1_4.yaml15
-rw-r--r--tests/integration/targets/vyos_firewall_global/vars/v1_5.yaml110
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg6
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v15.cfg11
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_firewall_global14.py498
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_firewall_global15.py132
16 files changed, 1794 insertions, 24 deletions
diff --git a/changelogs/fragments/t8220-fpz.yaml b/changelogs/fragments/t8220-fpz.yaml
new file mode 100644
index 00000000..5c256046
--- /dev/null
+++ b/changelogs/fragments/t8220-fpz.yaml
@@ -0,0 +1,3 @@
+---
+minor_changes:
+ - vyos_firewall_global - Added Firewall Zone Policy support.
diff --git a/docs/vyos.vyos.vyos_firewall_global_module.rst b/docs/vyos.vyos.vyos_firewall_global_module.rst
index 8ab253c2..f7e4605d 100644
--- a/docs/vyos.vyos.vyos_firewall_global_module.rst
+++ b/docs/vyos.vyos.vyos_firewall_global_module.rst
@@ -800,6 +800,332 @@ Parameters
<div>(strict) Enable Strict Reverse Path Forwarding as defined in RFC3704.</div>
</td>
</tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>zone</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Defines a firewall zone.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>default_action</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>drop</b>&nbsp;&larr;</div></li>
+ <li>reject</li>
+ </ul>
+ </td>
+ <td>
+ <div>Specifies the default action for the zone.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>default_log</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Specifies whether or not to log packets for the firewall zone.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>description</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Allows you to specify a brief description for the firewall zone.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>interfaces</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Specifies the interfaces associated with the zone.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>intra_zone_filtering</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Specifies a policy for intra-zone filtering.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>action</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>accept</li>
+ <li>drop</li>
+ </ul>
+ </td>
+ <td>
+ <div>Action for intra-zone traffic.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>firewall</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Firewall ruleset to apply to intra-zone traffic.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ipv6_name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the IPv6 firewall ruleset to apply to intra-zone traffic.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the firewall ruleset to apply to intra-zone traffic.</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>local_zone</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Specifies whether or not the zone is local.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the firewall zone.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>sources</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Specifies the source zones for the firewall rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>firewall</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Firewall ruleset to apply to the source zone.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ipv6_name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the IPv6 firewall ruleset to apply to the source zone.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the firewall ruleset to apply to the source zone.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>zone</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the source zone.</div>
+ </td>
+ </tr>
+
+
<tr>
<td colspan="5">
diff --git a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
index aeef42bc..dd17ef85 100644
--- a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
@@ -4,23 +4,6 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-#############################################
-# WARNING #
-#############################################
-#
-# This file is auto generated by the resource
-# module builder playbook.
-#
-# Do not edit this file manually.
-#
-# Changes to this file will be over written
-# by the resource module builder.
-#
-# Changes should be made in the model used to
-# generate this file or in the resource module
-# builder template.
-#
-#############################################
"""
The arg spec for the vyos_firewall_global module
"""
@@ -185,6 +168,64 @@ class Firewall_globalArgs(object): # pylint: disable=R0903
],
"type": "str",
},
+ "zone": {
+ "elements": "dict",
+ "type": "list",
+ "options": {
+ "description": {"type": "str"},
+ "default_action": {
+ "choices": [
+ "drop",
+ "reject",
+ ],
+ "default": "drop",
+ "type": "str",
+ },
+ "default_log": {"type": "bool"},
+ "interfaces": {
+ "elements": "str",
+ "type": "list",
+ },
+ "local_zone": {"type": "bool"},
+ "name": {
+ "required": True,
+ "type": "str",
+ },
+ "intra_zone_filtering": {
+ "type": "dict",
+ "options": {
+ "action": {
+ "choices": ["accept", "drop"],
+ "type": "str",
+ },
+ "firewall": {
+ "type": "dict",
+ "options": {
+ "name": {"type": "str"},
+ "ipv6_name": {"type": "str"},
+ },
+ },
+ },
+ },
+ "sources": {
+ "elements": "dict",
+ "type": "list",
+ "options": {
+ "zone": {
+ "required": True,
+ "type": "str",
+ },
+ "firewall": {
+ "type": "dict",
+ "options": {
+ "name": {"type": "str"},
+ "ipv6_name": {"type": "str"},
+ },
+ },
+ },
+ },
+ },
+ },
},
"type": "dict",
},
diff --git a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
index fcacb913..289037ef 100644
--- a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
@@ -241,6 +241,8 @@ class Firewall_global(ConfigBase):
commands.extend(self._render_state_policy(key, w, h, opr=opr))
elif key == "route_redirects":
commands.extend(self._render_route_redirects(key, w, h, opr=opr))
+ elif key == "zone":
+ commands.extend(self._render_zone(key, w, h, opr=opr))
return commands
def _add_global_attr(self, w, h, opr=True):
@@ -692,7 +694,7 @@ class Firewall_global(ConfigBase):
cmd = "set firewall "
if (
attr
- and key != "group"
+ and key not in ["group", "zone"]
and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
):
cmd += "global-options "
@@ -810,3 +812,324 @@ class Firewall_global(ConfigBase):
elif attrib == "validation":
regex = "source-validation"
return regex
+
+ def _render_zone(self, attr, w, h, opr):
+ """
+ This function forms the commands for group list/members attributes based on the 'opr'.
+ :param attr: attribute name.
+ :param w: the desired config.
+ :param h: the target config.
+ :param opr: True/False.
+ :return: generated list of commands.
+ """
+ commands = []
+ h_grp = []
+ w_grp = []
+ l_set = ("name", "description", "default_action", "default_log", "local_zone")
+ if w:
+ w_grp = w.get(attr) or []
+ if h:
+ h_grp = h.get(attr) or []
+
+ if w_grp:
+ for want in w_grp:
+ h = self.search_attrib_in_have(h_grp, want, "name")
+
+ cmd = self._compute_command(key="zone", attr="", opr=opr)
+
+ if not opr and self._is_grp_del(h, want, "name"):
+ commands.append(cmd + " " + want["name"])
+ continue
+
+ for key, val in want.items():
+ if val:
+ if opr and key in l_set and not (h and self._is_w_same(want, h, key)):
+ if key == "name":
+ pass
+ elif isinstance(val, bool):
+ commands.append(
+ cmd + " " + want["name"] + " " + key.replace("_", "-"),
+ )
+ else:
+ commands.append(
+ cmd
+ + " "
+ + want["name"]
+ + " "
+ + key.replace("_", "-")
+ + " '"
+ + str(want[key])
+ + "'",
+ )
+ elif not opr and key in l_set:
+ if not (h and in_target_not_none(h, key)) and not self._is_grp_del(
+ h,
+ want,
+ "name",
+ ):
+ commands.append(
+ cmd + " " + want["name"] + " " + key.replace("_", "-"),
+ )
+ elif key == "interfaces":
+ commands.extend(
+ self._render_interfaces(
+ key,
+ want,
+ h,
+ opr,
+ cmd,
+ want["name"],
+ attr,
+ ),
+ )
+ elif key == "intra_zone_filtering":
+ commands.extend(
+ self._render_izf(
+ key,
+ want,
+ h,
+ opr,
+ cmd,
+ want["name"],
+ attr,
+ ),
+ )
+ elif key == "sources":
+ commands.extend(
+ self._render_sources(
+ key,
+ want,
+ h,
+ opr,
+ cmd,
+ want["name"],
+ attr,
+ ),
+ )
+ return commands
+
+ def _render_interfaces(self, attr, w, h, opr, cmd, name, type):
+ """
+ This function forms the commands for interfaces
+ based on the 'opr'.
+ :param attr: attribute name.
+ :param w: the desired config.
+ :param h: the target config.
+ :param cmd: commands to be prepend.
+ :param name: name of group.
+ :param type: group type.
+ :return: generated list of commands.
+ """
+ commands = []
+ have = []
+ if w:
+ want = w.get(attr) or []
+ if h:
+ have = h.get(attr) or []
+
+ # VyOS 1.5.0 GA moved 'interface' under a new 'member' node
+ # ("set firewall zone <name> member interface <ifname>"). 1.4.x and
+ # 1.5-rolling snapshots predating this change still use the bare
+ # 'interface' node. Known limitation: a 1.5-rolling build reporting
+ # "1.5" that predates this change will incorrectly get the new
+ # syntax -- accepted trade-off, see PR notes.
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.5"):
+ iface_kw = "member interface"
+ else:
+ iface_kw = "interface"
+
+ if want:
+ if opr:
+ interfaces = list_diff_want_only(want, have)
+
+ for interface in interfaces:
+ commands.append(
+ cmd + " " + name + " " + iface_kw + " " + interface,
+ )
+ elif not opr and have:
+ interfaces = list_diff_want_only(want, have)
+ for interface in interfaces:
+ commands.append(
+ cmd + " " + name + " " + iface_kw + " " + interface,
+ )
+ elif not opr and not have:
+ for interface in want:
+ commands.append(
+ cmd + " " + name + " " + iface_kw + " " + interface,
+ )
+ else:
+ self._module.fail_json(msg={"want": want, "have": have, "opr": opr})
+
+ return commands
+
+ def _render_izf(self, attr, w, h, opr, cmd, name, type):
+ """
+ This function forms the commands for intra zone filtering
+ based on the 'opr'.
+ :param attr: attribute name.
+ :param w: the desired config.
+ :param h: the target config.
+ :param cmd: commands to be prepend.
+ :param name: name of group.
+ :param type: group type.
+ :return: generated list of commands.
+ """
+ commands = []
+ have = []
+ if w:
+ want = w.get(attr) or []
+ if h:
+ have = h.get(attr) or []
+
+ if want:
+ if opr:
+ izfs = self._dict_diff(want, have)
+ for izf in izfs:
+ commands.append(
+ cmd
+ + " "
+ + name
+ + " intra-zone-filtering "
+ + izf[0].replace(".", " ")
+ + " "
+ + izf[1],
+ )
+ elif not opr and have:
+ izfs = self._dict_diff(want, have)
+
+ for izf in izfs:
+ commands.append(
+ cmd + " " + name + " intra-zone-filtering " + izf[0].replace(".", " "),
+ )
+ elif not opr and not have:
+ commands.append(
+ cmd + " " + name + " intra-zone-filtering",
+ )
+ return commands
+
+ def _dict_diff(self, want, have, path=""):
+ """
+ Recursively find keys/values in `want` that differ or are missing in `have`.
+ Returns list of tuples: (full_path, value_in_want)
+ """
+ diffs = []
+
+ have = have or {}
+
+ for key, want_val in want.items():
+ current_path = f"{path}.{key.replace('_', '-')}" if path else key.replace("_", "-")
+
+ if key not in have:
+ if isinstance(want_val, dict):
+ diffs.extend(self._dict_diff(want_val, {}, current_path))
+ elif isinstance(want_val, list):
+ for i, item in enumerate(want_val):
+ if isinstance(item, dict):
+ diffs.extend(self._dict_diff(item, {}, f"{current_path}[{i}]"))
+ else:
+ diffs.append((f"{current_path}[{i}]", item))
+ else:
+ diffs.append((current_path, want_val))
+
+ else:
+ have_val = have[key]
+
+ if isinstance(want_val, dict) and isinstance(have_val, dict):
+ diffs.extend(self._dict_diff(want_val, have_val, current_path))
+
+ elif isinstance(want_val, list) and isinstance(have_val, list):
+ for i, item in enumerate(want_val):
+ if i >= len(have_val):
+ diffs.append((f"{current_path}[{i}]", item))
+ elif isinstance(item, dict) and isinstance(have_val[i], dict):
+ diffs.extend(
+ self._dict_diff(item, have_val[i], f"{current_path}[{i}]"),
+ )
+ elif item != have_val[i]:
+ diffs.append((f"{current_path}[{i}]", item))
+
+ elif want_val != have_val:
+ diffs.append((current_path, want_val))
+
+ return diffs
+
+ def _render_sources(self, attr, w, h, opr, cmd, name, type):
+ """
+ This function forms the commands for sources (from)
+ based on the 'opr'.
+ :param attr: attribute name.
+ :param w: the desired config.
+ :param h: the target config.
+ :param cmd: commands to be prepend.
+ :param name: name of group.
+ :param type: group type.
+ :return: generated list of commands.
+ """
+ commands = []
+ have = []
+ if w:
+ want = w.get(attr) or []
+ if h:
+ have = h.get(attr) or []
+
+ have_index = {item["zone"]: item for item in have}
+
+ for item1 in want:
+ zone = item1["zone"]
+
+ if zone in have_index:
+ item2 = have_index[zone]
+
+ wfw = item1.get("firewall", {})
+ hfw = item2.get("firewall", {})
+ if wfw:
+ if opr:
+ sources = self._dict_diff(wfw, hfw)
+ for source in sources:
+ commands.append(
+ cmd
+ + " "
+ + name
+ + " from "
+ + zone
+ + " firewall "
+ + source[0].replace("_", "-")
+ + " "
+ + source[1],
+ )
+ elif not opr and hfw:
+ sources = self._dict_diff(wfw, hfw)
+ for source in sources:
+ commands.append(
+ cmd
+ + " "
+ + name
+ + " from "
+ + zone
+ + " firewall "
+ + source[0].replace("_", "-"),
+ )
+ elif not opr and not hfw:
+ commands.append(
+ cmd + " " + name + " from " + zone,
+ )
+ elif opr:
+ wfw = item1.get("firewall", {})
+ for key, val in wfw.items():
+ if val:
+ commands.append(
+ cmd
+ + " "
+ + name
+ + " from "
+ + zone
+ + " firewall "
+ + key.replace("_", "-")
+ + " "
+ + val,
+ )
+ elif not opr:
+ commands.append(
+ cmd + " " + name + " from " + zone,
+ )
+ return commands
diff --git a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
index c74e63d3..e13c1939 100644
--- a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
@@ -77,13 +77,17 @@ class Firewall_globalFacts(object):
:rtype: dictionary
:returns: The generated config
"""
+
conf = "\n".join(
filter(
- lambda x: ("firewall ipv6-name" and "firewall name" not in x),
+ lambda x: not (
+ x.startswith("set firewall name")
+ or x.startswith("set firewall ipv6-name")
+ or x.startswith("set firewall ipv6 name")
+ ),
conf,
),
)
-
a_lst = [
"config_trap",
"validation",
@@ -97,6 +101,7 @@ class Firewall_globalFacts(object):
"group": self.parse_group(conf),
"route_redirects": self.route_redirects(conf),
"state_policy": self.parse_state_policy(conf),
+ "zone": self.parse_zone(conf),
}
firewall.update(f_sub)
return firewall
@@ -400,3 +405,120 @@ class Firewall_globalFacts(object):
"twa_hazards_protection",
)
return True if attrib in bool_set else False
+
+ def parse_zone(self, conf):
+ """
+ This function triggers the parsing of 'zone' attributes.
+ :param conf: configuration.
+ :return: generated config dictionary.
+ """
+ cfg_dict = {}
+
+ KEY_MAP = {
+ "interface": "interfaces",
+ "intra-zone-filtering": "intra-zone-filtering",
+ "from": "sources",
+ }
+
+ LIST_ATTRS = {
+ "interfaces",
+ "intra_zone_filtering",
+ "sources",
+ }
+
+ for line in conf.splitlines():
+
+ m = search(
+ r"^set firewall zone (?P<zone>\S+)\s+(?P<attr>[a-z-]+)(?:\s+(?P<value>'[^']+'|[^\n]+))?$",
+ line,
+ )
+ if not m:
+ continue
+
+ zone_name = m.group("zone")
+ raw_attr = m.group("attr").replace("-", "_")
+ value = m.group("value")
+
+ if value is None:
+ value = True
+ else:
+ value = value.strip("'")
+
+ # VyOS 1.5.0 GA wraps 'interface' under a new 'member' node:
+ # "set firewall zone <name> member interface <ifname>". Unwrap
+ # it here so it lands in the same 'interfaces' list as the
+ # pre-1.5.0 bare "interface <ifname>" form. No version check
+ # needed -- 1.4.x/1.5-rolling configs never emit 'member'.
+ if raw_attr == "member" and isinstance(value, str) and value.startswith("interface "):
+ raw_attr = "interface"
+ value = value.split(None, 1)[1].strip("'")
+
+ zone = cfg_dict.setdefault(zone_name, {"name": zone_name})
+
+ attr = KEY_MAP.get(raw_attr, raw_attr)
+
+ if attr in LIST_ATTRS:
+ if attr == "intra_zone_filtering":
+ izf = zone.setdefault(attr, {})
+ izf_attr = self._parse_izf(value)
+ for k, v in izf_attr.items():
+ if isinstance(v, dict):
+ izf.setdefault(k, {}).update(v)
+ else:
+ izf[k] = v
+ elif attr == "sources":
+ self._parse_sources(zone, value)
+ else:
+ zone.setdefault(attr, []).append(value)
+ else:
+ zone[attr] = value
+
+ return list(cfg_dict.values())
+
+ def _parse_izf(self, value):
+
+ tokens = value.replace("'", "").split()
+
+ result = {}
+
+ key = tokens[0].replace("-", "_")
+
+ if len(tokens) == 2:
+ result[key] = tokens[1]
+
+ elif len(tokens) >= 3:
+ subkey = tokens[1].replace("-", "_")
+ result[key] = {subkey: tokens[2]}
+
+ return result
+
+ def _parse_sources(self, zone, value):
+
+ tokens = value.split()
+
+ if len(tokens) < 1:
+ return
+
+ src_zone = tokens[0]
+
+ sources = zone.setdefault("sources", [])
+
+ entry = None
+ for s in sources:
+ if s.get("zone") == src_zone:
+ entry = s
+ break
+
+ if entry is None:
+ entry = {"zone": src_zone}
+ sources.append(entry)
+
+ if len(tokens) == 1:
+ return
+
+ if tokens[1] == "firewall" and len(tokens) >= 4:
+ key = tokens[2].replace("-", "_")
+ val = tokens[3].strip("'")
+
+ firewall = entry.setdefault("firewall", {})
+ firewall[key] = val
diff --git a/plugins/modules/vyos_firewall_global.py b/plugins/modules/vyos_firewall_global.py
index 9abeb5a1..4967a564 100644
--- a/plugins/modules/vyos_firewall_global.py
+++ b/plugins/modules/vyos_firewall_global.py
@@ -280,6 +280,91 @@ options:
- notice
- info
- debug
+ zone:
+ description:
+ - Defines a firewall zone.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the firewall zone.
+ type: str
+ required: true
+ description:
+ description:
+ - Allows you to specify a brief description for the firewall zone.
+ type: str
+ default_log:
+ description:
+ - Specifies whether or not to log packets for the firewall zone.
+ type: bool
+ local_zone:
+ description:
+ - Specifies whether or not the zone is local.
+ type: bool
+ default_action:
+ description:
+ - Specifies the default action for the zone.
+ type: str
+ default: drop
+ choices:
+ - drop
+ - reject
+ interfaces:
+ description:
+ - Specifies the interfaces associated with the zone.
+ type: list
+ elements: str
+ intra_zone_filtering:
+ description:
+ - Specifies a policy for intra-zone filtering.
+ type: dict
+ suboptions:
+ action:
+ description:
+ - Action for intra-zone traffic.
+ type: str
+ choices:
+ - accept
+ - drop
+ firewall:
+ description:
+ - Firewall ruleset to apply to intra-zone traffic.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - Name of the firewall ruleset to apply to intra-zone traffic.
+ type: str
+ ipv6_name:
+ description:
+ - Name of the IPv6 firewall ruleset to apply to intra-zone traffic.
+ type: str
+ sources:
+ description:
+ - Specifies the source zones for the firewall rules.
+ type: list
+ elements: dict
+ suboptions:
+ zone:
+ description:
+ - Name of the source zone.
+ type: str
+ required: true
+ firewall:
+ description:
+ - Firewall ruleset to apply to the source zone.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - Name of the firewall ruleset to apply to the source zone.
+ type: str
+ ipv6_name:
+ description:
+ - Name of the IPv6 firewall ruleset to apply to the source zone.
+ type: str
running_config:
description:
- >
diff --git a/tests/integration/targets/vyos_firewall_global/tests/cli/_get_version.yaml b/tests/integration/targets/vyos_firewall_global/tests/cli/_get_version.yaml
index 2588b194..45bc2e18 100644
--- a/tests/integration/targets/vyos_firewall_global/tests/cli/_get_version.yaml
+++ b/tests/integration/targets/vyos_firewall_global/tests/cli/_get_version.yaml
@@ -8,6 +8,7 @@
- name: debug vyos_facts
debug:
var: vyos_facts
+ when: vyos_facts is not skipped
- name: pull version from facts
set_fact:
@@ -19,10 +20,20 @@
vyos_version: "{{ vyos_version }}.0"
when: vyos_version.count('.') == 1
-- name: include correct vars
+- name: report resolved VyOS version
+ debug:
+ msg: "Using VyOS version {{ vyos_version }} for this testcase"
+
+- name: include correct vars (pre-1.4.0)
include_vars: pre-v1_4.yaml
when: vyos_version is version('1.4.0', '<', version_type='semver')
-- name: include correct vars
+- name: include correct vars (1.4.x)
include_vars: v1_4.yaml
- when: vyos_version is version('1.4.0', '>=', version_type='semver')
+ when: >-
+ vyos_version is version('1.4.0', '>=', version_type='semver') and
+ vyos_version is version('1.5.0', '<', version_type='semver')
+
+- name: include correct vars (1.5.0+)
+ include_vars: v1_5.yaml
+ when: vyos_version is version('1.5.0', '>=', version_type='semver')
diff --git a/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_4.cfg b/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_4.cfg
index 41435780..882d9aaa 100644
--- a/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_4.cfg
+++ b/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_4.cfg
@@ -16,3 +16,6 @@ set firewall global-options state-policy established log 'enable'
set firewall global-options state-policy invalid action 'reject'
set firewall global-options syn-cookies 'enable'
set firewall global-options twa-hazards-protection 'enable'
+set firewall zone ZONE-TEST interface 'eth0.1234'
+set firewall zone ZONE-TEST description 'zone-test test description'
+set firewall zone ZONE-TEST default-action 'drop'
diff --git a/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_5.cfg b/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_5.cfg
new file mode 100644
index 00000000..88553c00
--- /dev/null
+++ b/tests/integration/targets/vyos_firewall_global/tests/cli/_parsed_config_1_5.cfg
@@ -0,0 +1,21 @@
+set firewall global-options all-ping 'enable'
+set firewall global-options broadcast-ping 'enable'
+set firewall group address-group MGMT-HOSTS address '192.0.1.1'
+set firewall group address-group MGMT-HOSTS address '192.0.1.3'
+set firewall group address-group MGMT-HOSTS address '192.0.1.5'
+set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list'
+set firewall group network-group MGMT description 'This group has the Management network addresses'
+set firewall group network-group MGMT network '192.0.1.0/24'
+set firewall global-options ip-src-route 'enable'
+set firewall global-options log-martians 'enable'
+set firewall global-options receive-redirects 'disable'
+set firewall global-options send-redirects 'enable'
+set firewall global-options source-validation 'strict'
+set firewall global-options state-policy established action 'accept'
+set firewall global-options state-policy established log 'enable'
+set firewall global-options state-policy invalid action 'reject'
+set firewall global-options syn-cookies 'enable'
+set firewall global-options twa-hazards-protection 'enable'
+set firewall zone ZONE-TEST member interface 'eth0.1234'
+set firewall zone ZONE-TEST description 'zone-test test description'
+set firewall zone ZONE-TEST default-action 'drop'
diff --git a/tests/integration/targets/vyos_firewall_global/vars/main.yaml b/tests/integration/targets/vyos_firewall_global/vars/main.yaml
index 0f041b60..94bdca87 100644
--- a/tests/integration/targets/vyos_firewall_global/vars/main.yaml
+++ b/tests/integration/targets/vyos_firewall_global/vars/main.yaml
@@ -37,6 +37,12 @@ merged:
connection_type: invalid
twa_hazards_protection: true
validation: strict
+ zone:
+ - name: ZONE-TEST
+ default_action: drop
+ description: zone-test test description
+ interfaces:
+ - eth0.1234
config:
validation: strict
log_martians: true
@@ -70,6 +76,12 @@ merged:
description: This group has the Management network addresses
members:
- address: 192.0.1.0/24
+ zone:
+ - name: ZONE-TEST
+ description: zone-test test description
+ interfaces:
+ - eth0.1234
+
diff_config:
validation: strict
log_martians: true
@@ -139,7 +151,12 @@ populate:
members:
- address: 192.0.1.0/24
afi: ipv4
-
+ zone:
+ - name: ZONE-TEST
+ description: zone-test test description
+ interfaces:
+ - eth0.1234
+ default_action: drop
replaced:
commands: "{{ replaced_commands }}"
after:
@@ -183,6 +200,16 @@ replaced:
syn_cookies: true
twa_hazards_protection: true
validation: strict
+ zone:
+ - name: FZP-2
+ default_action: reject
+ default_log: true
+ description: This is the Firewall zone fzp2
+ interfaces:
+ - eth2
+ - lo
+ intra_zone_filtering:
+ action: accept
config:
validation: strict
log_martians: true
@@ -221,6 +248,16 @@ replaced:
description: This group has the Management network addresses
members:
- address: 192.0.1.0/24
+ zone:
+ - name: FZP-2
+ default_action: reject
+ default_log: true
+ description: This is the Firewall zone fzp2
+ interfaces:
+ - eth2
+ - lo
+ intra_zone_filtering:
+ action: accept
diff_config:
validation: strict
log_martians: true
@@ -300,6 +337,12 @@ rendered:
description: This group has the Management network addresses
members:
- address: 192.0.1.0/24
+ zone:
+ - name: ZONE-TEST
+ description: zone-test test description
+ interfaces:
+ - eth0.1234
+ default_action: drop
deleted:
commands: "{{ deleted_commands }}"
@@ -341,6 +384,16 @@ round_trip:
members:
- address: 192.0.1.0/24
afi: ipv4
+ zone:
+ - name: FZP-2
+ default_action: reject
+ default_log: true
+ description: This is the Firewall zone fzp2
+ interfaces:
+ - eth2
+ - lo
+ intra_zone_filtering:
+ action: accept
forward_config:
validation: strict
log_martians: true
@@ -374,6 +427,16 @@ round_trip:
description: This group has the Management network addresses
members:
- address: 192.0.1.0/24
+ zone:
+ - name: FZP-2
+ default_action: reject
+ default_log: true
+ description: This is the Firewall zone fzp2
+ interfaces:
+ - eth2
+ - lo
+ intra_zone_filtering:
+ action: accept
revert_config:
validation: strict
log_martians: false
diff --git a/tests/integration/targets/vyos_firewall_global/vars/v1_4.yaml b/tests/integration/targets/vyos_firewall_global/vars/v1_4.yaml
index 68773b2c..fffa93d8 100644
--- a/tests/integration/targets/vyos_firewall_global/vars/v1_4.yaml
+++ b/tests/integration/targets/vyos_firewall_global/vars/v1_4.yaml
@@ -20,6 +20,9 @@ merged_commands:
- set firewall global-options twa-hazards-protection 'enable'
- set firewall global-options syn-cookies 'enable'
- set firewall global-options source-validation 'strict'
+ - set firewall zone ZONE-TEST description 'zone-test test description'
+ - set firewall zone ZONE-TEST interface eth0.1234
+ - set firewall zone ZONE-TEST default-action 'drop'
populate_commands:
- set firewall global-options all-ping 'enable'
@@ -40,9 +43,12 @@ populate_commands:
- set firewall global-options state-policy invalid action 'reject'
- set firewall global-options syn-cookies 'enable'
- set firewall global-options twa-hazards-protection 'enable'
+ - set firewall zone ZONE-TEST interface 'eth0.1234'
+ - set firewall zone ZONE-TEST description 'zone-test test description'
replaced_commands:
- delete firewall group address-group MGMT-HOSTS
+ - delete firewall zone ZONE-TEST
- set firewall group address-group SALES-HOSTS address 192.0.2.1
- set firewall group address-group SALES-HOSTS address 192.0.2.2
- set firewall group address-group SALES-HOSTS address 192.0.2.3
@@ -52,6 +58,12 @@ replaced_commands:
- set firewall group address-group ENG-HOSTS address 192.0.3.2
- set firewall group address-group ENG-HOSTS description 'Sales office hosts address list'
- set firewall group address-group ENG-HOSTS
+ - set firewall zone FZP-2 default-action 'reject'
+ - set firewall zone FZP-2 default-log
+ - set firewall zone FZP-2 description 'This is the Firewall zone fzp2'
+ - set firewall zone FZP-2 interface eth2
+ - set firewall zone FZP-2 interface lo
+ - set firewall zone FZP-2 intra-zone-filtering action accept
rendered_commands:
- set firewall group address-group SALES-HOSTS address 192.0.2.1
@@ -78,6 +90,9 @@ rendered_commands:
- set firewall global-options twa-hazards-protection 'enable'
- set firewall global-options syn-cookies 'enable'
- set firewall global-options source-validation 'strict'
+ - set firewall zone ZONE-TEST interface eth0.1234
+ - set firewall zone ZONE-TEST description 'zone-test test description'
+ - set firewall zone ZONE-TEST default-action 'drop'
deleted_commands:
- "delete firewall"
diff --git a/tests/integration/targets/vyos_firewall_global/vars/v1_5.yaml b/tests/integration/targets/vyos_firewall_global/vars/v1_5.yaml
new file mode 100644
index 00000000..d32eacc7
--- /dev/null
+++ b/tests/integration/targets/vyos_firewall_global/vars/v1_5.yaml
@@ -0,0 +1,110 @@
+---
+merged_commands:
+ - set firewall group address-group MGMT-HOSTS address 192.0.1.1
+ - set firewall group address-group MGMT-HOSTS address 192.0.1.3
+ - set firewall group address-group MGMT-HOSTS address 192.0.1.5
+ - set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list'
+ - set firewall group address-group MGMT-HOSTS
+ - set firewall group network-group MGMT network 192.0.1.0/24
+ - set firewall group network-group MGMT description 'This group has the Management network addresses'
+ - set firewall group network-group MGMT
+ - set firewall global-options ip-src-route 'enable'
+ - set firewall global-options receive-redirects 'disable'
+ - set firewall global-options send-redirects 'enable'
+ - set firewall global-options state-policy established action 'accept'
+ - set firewall global-options state-policy established log
+ - set firewall global-options state-policy invalid action 'reject'
+ - set firewall global-options broadcast-ping 'enable'
+ - set firewall global-options all-ping 'enable'
+ - set firewall global-options log-martians 'enable'
+ - set firewall global-options twa-hazards-protection 'enable'
+ - set firewall global-options syn-cookies 'enable'
+ - set firewall global-options source-validation 'strict'
+ - set firewall zone ZONE-TEST description 'zone-test test description'
+ - set firewall zone ZONE-TEST member interface eth0.1234
+ - set firewall zone ZONE-TEST default-action 'drop'
+
+populate_commands:
+ - set firewall global-options all-ping 'enable'
+ - set firewall global-options broadcast-ping 'enable'
+ - set firewall group address-group MGMT-HOSTS address '192.0.1.1'
+ - set firewall group address-group MGMT-HOSTS address '192.0.1.3'
+ - set firewall group address-group MGMT-HOSTS address '192.0.1.5'
+ - set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address list'
+ - set firewall group network-group MGMT description 'This group has the Management network addresses'
+ - set firewall group network-group MGMT network '192.0.1.0/24'
+ - set firewall global-options ip-src-route 'enable'
+ - set firewall global-options log-martians 'enable'
+ - set firewall global-options receive-redirects 'disable'
+ - set firewall global-options send-redirects 'enable'
+ - set firewall global-options source-validation 'strict'
+ - set firewall global-options state-policy established action 'accept'
+ - set firewall global-options state-policy established log
+ - set firewall global-options state-policy invalid action 'reject'
+ - set firewall global-options syn-cookies 'enable'
+ - set firewall global-options twa-hazards-protection 'enable'
+ - set firewall zone ZONE-TEST member interface 'eth0.1234'
+ - set firewall zone ZONE-TEST description 'zone-test test description'
+
+replaced_commands:
+ - delete firewall group address-group MGMT-HOSTS
+ - delete firewall zone ZONE-TEST
+ - set firewall group address-group SALES-HOSTS address 192.0.2.1
+ - set firewall group address-group SALES-HOSTS address 192.0.2.2
+ - set firewall group address-group SALES-HOSTS address 192.0.2.3
+ - set firewall group address-group SALES-HOSTS description 'Sales office hosts address list'
+ - set firewall group address-group SALES-HOSTS
+ - set firewall group address-group ENG-HOSTS address 192.0.3.1
+ - set firewall group address-group ENG-HOSTS address 192.0.3.2
+ - set firewall group address-group ENG-HOSTS description 'Sales office hosts address list'
+ - set firewall group address-group ENG-HOSTS
+ - set firewall zone FZP-2 default-action 'reject'
+ - set firewall zone FZP-2 default-log
+ - set firewall zone FZP-2 description 'This is the Firewall zone fzp2'
+ - set firewall zone FZP-2 member interface eth2
+ - set firewall zone FZP-2 member interface lo
+ - set firewall zone FZP-2 intra-zone-filtering action accept
+
+rendered_commands:
+ - set firewall group address-group SALES-HOSTS address 192.0.2.1
+ - set firewall group address-group SALES-HOSTS address 192.0.2.2
+ - set firewall group address-group SALES-HOSTS address 192.0.2.3
+ - set firewall group address-group SALES-HOSTS description 'Sales office hosts address list'
+ - set firewall group address-group SALES-HOSTS
+ - set firewall group address-group ENG-HOSTS address 192.0.3.1
+ - set firewall group address-group ENG-HOSTS address 192.0.3.2
+ - set firewall group address-group ENG-HOSTS description 'Sales office hosts address list'
+ - set firewall group address-group ENG-HOSTS
+ - set firewall group network-group MGMT network 192.0.1.0/24
+ - set firewall group network-group MGMT description 'This group has the Management network addresses'
+ - set firewall group network-group MGMT
+ - set firewall global-options ip-src-route 'enable'
+ - set firewall global-options receive-redirects 'disable'
+ - set firewall global-options send-redirects 'enable'
+ - set firewall global-options state-policy established action 'accept'
+ - set firewall global-options state-policy established log
+ - set firewall global-options state-policy invalid action 'reject'
+ - set firewall global-options broadcast-ping 'enable'
+ - set firewall global-options all-ping 'enable'
+ - set firewall global-options log-martians 'enable'
+ - set firewall global-options twa-hazards-protection 'enable'
+ - set firewall global-options syn-cookies 'enable'
+ - set firewall global-options source-validation 'strict'
+ - set firewall zone ZONE-TEST member interface eth0.1234
+ - set firewall zone ZONE-TEST description 'zone-test test description'
+ - set firewall zone ZONE-TEST default-action 'drop'
+
+deleted_commands:
+ - "delete firewall"
+
+parsed_config_file: "_parsed_config_1_5.cfg"
+
+replaced_diff:
+ - '+ network "1.1.1.1/32"'
+ - '- network "192.0.1.0/24"'
+
+merged_diff:
+ - '+ network "1.1.1.1/32"'
+
+deleted_diff:
+ - '- network "192.0.1.0/24"'
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg
index c883ca78..f58f166c 100644
--- a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg
@@ -22,3 +22,9 @@ set firewall global-options send-redirects 'enable'
set firewall zone ZONE-TEST interface 'eth0.1234'
set firewall zone ZONE-TEST description 'zone-test test description'
set firewall group address-group ZONE-TEST address '1.2.3.4'
+set firewall zone ZONE-IZF description 'zone for izf and sources delete path testing'
+set firewall zone ZONE-IZF intra-zone-filtering action 'drop'
+set firewall zone ZONE-IZF from zone-src firewall name 'existing-ruleset'
+set firewall name TESTRULESET-V4 default-action drop
+set firewall ipv6-name TESTRULESET-V6-LEGACY default-action drop
+set firewall ipv6 name TESTRULESET-V6-1_4PLUS default-action drop
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v15.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v15.cfg
new file mode 100644
index 00000000..23dd9777
--- /dev/null
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v15.cfg
@@ -0,0 +1,11 @@
+# Intentionally minimal: represents a VyOS 1.5.0 device with one existing
+# zone already configured using the 1.5.0 'member interface' syntax. Used
+# to test parse_zone's unwrap logic in isolation. TestVyosFirewallRulesModule15's
+# other tests (e.g. test_01_merged) create a differently-named zone
+# (ZONE-15), so under 'merged' state this existing zone is never touched
+# and doesn't interfere.
+set firewall zone ZONE-15-EXISTING member interface eth2
+set firewall zone ZONE-15-EXISTING description 'existing 1.5.0 zone for facts parsing test'
+set firewall name TESTRULESET-V4 default-action drop
+set firewall ipv6-name TESTRULESET-V6-LEGACY default-action drop
+set firewall ipv6 name TESTRULESET-V6-1_4PLUS default-action drop
diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py
index a7abfe8c..397ccfbd 100644
--- a/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py
+++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py
@@ -158,6 +158,31 @@ class TestVyosFirewallRulesModule14(TestVyosModule):
),
],
),
+ zone=[
+ dict(
+ name="FZP-2",
+ default_action="reject",
+ default_log=True,
+ description="This is the Firewall zone fzp2",
+ interfaces=[
+ "eth2",
+ "lo0",
+ ],
+ local_zone=True,
+ sources=[
+ dict(
+ zone="fzp5",
+ firewall=dict(
+ name="fzp1",
+ ipv6_name="fzp1-v6",
+ ),
+ ),
+ ],
+ intra_zone_filtering=dict(
+ action="accept",
+ ),
+ ),
+ ],
),
state="merged",
),
@@ -195,6 +220,15 @@ class TestVyosFirewallRulesModule14(TestVyosModule):
"set firewall global-options twa-hazards-protection 'enable'",
"set firewall global-options syn-cookies 'enable'",
"set firewall global-options source-validation 'strict'",
+ "set firewall zone FZP-2 default-action 'reject'",
+ "set firewall zone FZP-2 default-log",
+ "set firewall zone FZP-2 description 'This is the Firewall zone fzp2'",
+ "set firewall zone FZP-2 from fzp5 firewall ipv6-name fzp1-v6",
+ "set firewall zone FZP-2 from fzp5 firewall name fzp1",
+ "set firewall zone FZP-2 interface eth2",
+ "set firewall zone FZP-2 interface lo0",
+ "set firewall zone FZP-2 intra-zone-filtering action accept",
+ "set firewall zone FZP-2 local-zone",
]
self.execute_module(changed=True, commands=commands)
@@ -321,6 +355,31 @@ class TestVyosFirewallRulesModule14(TestVyosModule):
),
],
),
+ zone=[
+ dict(
+ name="FZP-2",
+ default_action="drop",
+ default_log=True,
+ description="This is the Firewall zone fzp2",
+ interfaces=[
+ "eth2",
+ "lo0",
+ ],
+ local_zone=True,
+ sources=[
+ dict(
+ zone="fzp5",
+ firewall=dict(
+ name="fzp1",
+ ipv6_name="fzp1-v6",
+ ),
+ ),
+ ],
+ intra_zone_filtering=dict(
+ action="accept",
+ ),
+ ),
+ ],
),
state="replaced",
),
@@ -334,15 +393,26 @@ class TestVyosFirewallRulesModule14(TestVyosModule):
"delete firewall global-options state-policy related",
"delete firewall global-options ipv6-src-route",
"delete firewall global-options send-redirects",
+ "delete firewall zone ZONE-IZF",
"set firewall global-options state-policy invalid action 'reject'",
"set firewall group address-group RND-HOSTS address 192.0.2.7",
"set firewall group address-group RND-HOSTS address 192.0.2.9",
"set firewall group address-group ZONE-TEST description 'This is a new description for a address group name that is also in a zone'",
+ "delete firewall zone ZONE-TEST",
"delete firewall group network-group RND description",
"delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1",
"set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2",
"delete firewall group port-group SSH port 22",
"set firewall group port-group SSH port 2222",
+ "set firewall zone FZP-2 default-action 'drop'",
+ "set firewall zone FZP-2 default-log",
+ "set firewall zone FZP-2 description 'This is the Firewall zone fzp2'",
+ "set firewall zone FZP-2 from fzp5 firewall ipv6-name fzp1-v6",
+ "set firewall zone FZP-2 from fzp5 firewall name fzp1",
+ "set firewall zone FZP-2 interface eth2",
+ "set firewall zone FZP-2 interface lo0",
+ "set firewall zone FZP-2 intra-zone-filtering action accept",
+ "set firewall zone FZP-2 local-zone",
]
self.execute_module(changed=True, commands=commands)
@@ -421,6 +491,24 @@ class TestVyosFirewallRulesModule14(TestVyosModule):
),
],
),
+ zone=[
+ dict(
+ name="ZONE-TEST",
+ description="zone-test test description",
+ interfaces=["eth0.1234"],
+ ),
+ dict(
+ name="ZONE-IZF",
+ description="zone for izf and sources delete path testing",
+ intra_zone_filtering=dict(action="drop"),
+ sources=[
+ dict(
+ zone="zone-src",
+ firewall=dict(name="existing-ruleset"),
+ ),
+ ],
+ ),
+ ],
),
state="replaced",
),
@@ -487,6 +575,15 @@ class TestVyosFirewallRulesModule14(TestVyosModule):
),
],
),
+ zone=[
+ dict(
+ name="ZONE-TEST",
+ description="zone-test test description",
+ interfaces=[
+ "lo",
+ ],
+ ),
+ ],
),
state="replaced",
),
@@ -496,6 +593,7 @@ class TestVyosFirewallRulesModule14(TestVyosModule):
"delete firewall group address-group RND-HOSTS address 192.0.2.3",
"delete firewall group address-group RND-HOSTS address 192.0.2.5",
"delete firewall group address-group ZONE-TEST address 1.2.3.4",
+ "delete firewall zone ZONE-IZF",
"delete firewall global-options all-ping",
"delete firewall global-options ipv6-src-route",
"delete firewall global-options send-redirects",
@@ -506,6 +604,8 @@ class TestVyosFirewallRulesModule14(TestVyosModule):
"set firewall group address-group RND-HOSTS address 192.0.2.7",
"set firewall group address-group RND-HOSTS address 192.0.2.9",
"set firewall group address-group ZONE-TEST address 4.3.2.1",
+ "delete firewall zone ZONE-TEST interface eth0.1234",
+ "set firewall zone ZONE-TEST interface lo",
"set firewall group address-group ZONE-TEST description 'This is a new description for a address group name that is also in a zone'",
"delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1",
"set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2",
@@ -518,3 +618,401 @@ class TestVyosFirewallRulesModule14(TestVyosModule):
set_module_args(dict(config=dict(), state="deleted"))
commands = ["delete firewall"]
self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_set_03_replaced_izf_action_change(self):
+ set_module_args(
+ dict(
+ config=dict(
+ ping=dict(all=True),
+ route_redirects=[
+ dict(ip_src_route=True, afi="ipv6"),
+ dict(icmp_redirects=dict(send=True), afi="ipv4"),
+ ],
+ state_policy=[
+ dict(connection_type="related", action="accept", log_level="alert"),
+ ],
+ group=dict(
+ address_group=[
+ dict(afi="ipv4", name="A-EMPTY"),
+ dict(
+ afi="ipv4",
+ name="RND-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.2.1"),
+ dict(address="192.0.2.3"),
+ dict(address="192.0.2.5"),
+ ],
+ ),
+ dict(
+ afi="ipv4",
+ name="DELETE-HOSTS",
+ description="The (single) last address from this group will be deleted in the tests",
+ members=[dict(address="1.2.3.4")],
+ ),
+ dict(
+ afi="ipv4",
+ name="ZONE-TEST",
+ members=[dict(address="1.2.3.4")],
+ ),
+ dict(
+ afi="ipv6",
+ name="LOCAL-v6",
+ description="This group has the hosts address lists of this machine",
+ members=[
+ dict(address="::1"),
+ dict(address="fdec:2503:89d6:59b3::1"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="RND",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.2.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="UNIQUE-LOCAL-v6",
+ description="This group encompasses the ULA address space in IPv6",
+ members=[dict(address="fc00::/7")],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="SSH",
+ description="This group has the ssh ports",
+ members=[dict(port="22")],
+ ),
+ ],
+ ),
+ zone=[
+ dict(
+ name="ZONE-TEST",
+ description="zone-test test description",
+ interfaces=["eth0.1234"],
+ ),
+ dict(
+ name="ZONE-IZF",
+ description="zone for izf and sources delete path testing",
+ intra_zone_filtering=dict(action="accept"), # was 'drop'
+ sources=[
+ dict(
+ zone="zone-src",
+ firewall=dict(name="existing-ruleset"),
+ ),
+ ],
+ ),
+ ],
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete firewall zone ZONE-IZF intra-zone-filtering action",
+ "set firewall zone ZONE-IZF intra-zone-filtering action accept",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_set_04_replaced_sources_value_change(self):
+ # ZONE-IZF.sources[0].firewall.name changes from 'existing-ruleset'
+ # (fixture) to 'new-ruleset'. Exercises _render_sources' `not opr
+ # and hfw` (delete) branch. Pre-fix this leaked the old value into
+ # the delete command.
+ set_module_args(
+ dict(
+ config=dict(
+ ping=dict(all=True),
+ route_redirects=[
+ dict(ip_src_route=True, afi="ipv6"),
+ dict(icmp_redirects=dict(send=True), afi="ipv4"),
+ ],
+ state_policy=[
+ dict(connection_type="related", action="accept", log_level="alert"),
+ ],
+ group=dict(
+ address_group=[
+ dict(afi="ipv4", name="A-EMPTY"),
+ dict(
+ afi="ipv4",
+ name="RND-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.2.1"),
+ dict(address="192.0.2.3"),
+ dict(address="192.0.2.5"),
+ ],
+ ),
+ dict(
+ afi="ipv4",
+ name="DELETE-HOSTS",
+ description="The (single) last address from this group will be deleted in the tests",
+ members=[dict(address="1.2.3.4")],
+ ),
+ dict(
+ afi="ipv4",
+ name="ZONE-TEST",
+ members=[dict(address="1.2.3.4")],
+ ),
+ dict(
+ afi="ipv6",
+ name="LOCAL-v6",
+ description="This group has the hosts address lists of this machine",
+ members=[
+ dict(address="::1"),
+ dict(address="fdec:2503:89d6:59b3::1"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="RND",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.2.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="UNIQUE-LOCAL-v6",
+ description="This group encompasses the ULA address space in IPv6",
+ members=[dict(address="fc00::/7")],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="SSH",
+ description="This group has the ssh ports",
+ members=[dict(port="22")],
+ ),
+ ],
+ ),
+ zone=[
+ dict(
+ name="ZONE-TEST",
+ description="zone-test test description",
+ interfaces=["eth0.1234"],
+ ),
+ dict(
+ name="ZONE-IZF",
+ description="zone for izf and sources delete path testing",
+ intra_zone_filtering=dict(action="drop"),
+ sources=[
+ dict(
+ zone="zone-src",
+ firewall=dict(name="new-ruleset"), # was 'existing-ruleset'
+ ),
+ ],
+ ),
+ ],
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete firewall zone ZONE-IZF from zone-src firewall name",
+ "set firewall zone ZONE-IZF from zone-src firewall name new-ruleset",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_set_05_replaced_sources_firewall_cleared(self):
+ # ZONE-IZF.sources[0] keeps the 'zone-src' key but drops the
+ # 'firewall' sub-dict entirely. Exercises _render_sources' `not opr
+ # and not hfw` branch -- unreachable before the fix, since it was
+ # guarded by `not have` instead of `not hfw`, and `have` can never
+ # be empty here (we're already inside `if zone in have_index`).
+ set_module_args(
+ dict(
+ config=dict(
+ ping=dict(all=True),
+ route_redirects=[
+ dict(ip_src_route=True, afi="ipv6"),
+ dict(icmp_redirects=dict(send=True), afi="ipv4"),
+ ],
+ state_policy=[
+ dict(connection_type="related", action="accept", log_level="alert"),
+ ],
+ group=dict(
+ address_group=[
+ dict(afi="ipv4", name="A-EMPTY"),
+ dict(
+ afi="ipv4",
+ name="RND-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.2.1"),
+ dict(address="192.0.2.3"),
+ dict(address="192.0.2.5"),
+ ],
+ ),
+ dict(
+ afi="ipv4",
+ name="DELETE-HOSTS",
+ description="The (single) last address from this group will be deleted in the tests",
+ members=[dict(address="1.2.3.4")],
+ ),
+ dict(
+ afi="ipv4",
+ name="ZONE-TEST",
+ members=[dict(address="1.2.3.4")],
+ ),
+ dict(
+ afi="ipv6",
+ name="LOCAL-v6",
+ description="This group has the hosts address lists of this machine",
+ members=[
+ dict(address="::1"),
+ dict(address="fdec:2503:89d6:59b3::1"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="RND",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.2.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="UNIQUE-LOCAL-v6",
+ description="This group encompasses the ULA address space in IPv6",
+ members=[dict(address="fc00::/7")],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="SSH",
+ description="This group has the ssh ports",
+ members=[dict(port="22")],
+ ),
+ ],
+ ),
+ zone=[
+ dict(
+ name="ZONE-TEST",
+ description="zone-test test description",
+ interfaces=["eth0.1234"],
+ ),
+ dict(
+ name="ZONE-IZF",
+ description="zone for izf and sources delete path testing",
+ intra_zone_filtering=dict(action="drop"),
+ sources=[
+ dict(zone="zone-src"), # firewall dropped entirely
+ ],
+ ),
+ ],
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete firewall zone ZONE-IZF from zone-src",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_set_06_replaced_sources_entry_removed(self):
+ # ZONE-IZF's sources list drops the 'zone-src' entry entirely (not
+ # just its firewall sub-dict). Exercises the new `elif not opr:`
+ # branch -- previously this case emitted nothing at all.
+ set_module_args(
+ dict(
+ config=dict(
+ ping=dict(all=True),
+ route_redirects=[
+ dict(ip_src_route=True, afi="ipv6"),
+ dict(icmp_redirects=dict(send=True), afi="ipv4"),
+ ],
+ state_policy=[
+ dict(connection_type="related", action="accept", log_level="alert"),
+ ],
+ group=dict(
+ address_group=[
+ dict(afi="ipv4", name="A-EMPTY"),
+ dict(
+ afi="ipv4",
+ name="RND-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.2.1"),
+ dict(address="192.0.2.3"),
+ dict(address="192.0.2.5"),
+ ],
+ ),
+ dict(
+ afi="ipv4",
+ name="DELETE-HOSTS",
+ description="The (single) last address from this group will be deleted in the tests",
+ members=[dict(address="1.2.3.4")],
+ ),
+ dict(
+ afi="ipv4",
+ name="ZONE-TEST",
+ members=[dict(address="1.2.3.4")],
+ ),
+ dict(
+ afi="ipv6",
+ name="LOCAL-v6",
+ description="This group has the hosts address lists of this machine",
+ members=[
+ dict(address="::1"),
+ dict(address="fdec:2503:89d6:59b3::1"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="RND",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.2.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="UNIQUE-LOCAL-v6",
+ description="This group encompasses the ULA address space in IPv6",
+ members=[dict(address="fc00::/7")],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="SSH",
+ description="This group has the ssh ports",
+ members=[dict(port="22")],
+ ),
+ ],
+ ),
+ zone=[
+ dict(
+ name="ZONE-TEST",
+ description="zone-test test description",
+ interfaces=["eth0.1234"],
+ ),
+ dict(
+ name="ZONE-IZF",
+ description="zone for izf and sources delete path testing",
+ intra_zone_filtering=dict(action="drop"),
+ # sources omitted entirely
+ ),
+ ],
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete firewall zone ZONE-IZF from zone-src",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_ruleset_lines_filtered_from_facts(self):
+ # Confirms render_config's pre-filter strips all three ruleset-line
+ # prefixes (IPv4 name, legacy hyphenated ipv6-name, and 1.4+
+ # space-separated "ipv6 name") before zone/global-options parsing
+ # runs, on a real 1.4.x-shaped fixture alongside existing zone data.
+ set_module_args(dict(config=dict(), state="gathered"))
+ result = self.execute_module(changed=False)
+ facts = result["gathered"]
+ self.assertNotIn("TESTRULESET-V4", str(facts))
+ self.assertNotIn("TESTRULESET-V6-LEGACY", str(facts))
+ self.assertNotIn("TESTRULESET-V6-1_4PLUS", str(facts))
diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global15.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global15.py
new file mode 100644
index 00000000..22ab5364
--- /dev/null
+++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global15.py
@@ -0,0 +1,132 @@
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from unittest.mock import patch
+
+from ansible_collections.vyos.vyos.plugins.modules import vyos_firewall_global
+from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args
+
+from .vyos_module import TestVyosModule, load_fixture
+
+
+class TestVyosFirewallRulesModule15(TestVyosModule):
+ module = vyos_firewall_global
+
+ def setUp(self):
+ super(TestVyosFirewallRulesModule15, self).setUp()
+ self.mock_get_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config",
+ )
+ self.get_config = self.mock_get_config.start()
+
+ self.mock_load_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config",
+ )
+ self.load_config = self.mock_load_config.start()
+
+ self.mock_get_resource_connection_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection",
+ )
+ self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
+
+ self.mock_get_resource_connection_facts = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection",
+ )
+ self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
+
+ self.mock_execute_show_command = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global.Firewall_globalFacts.get_device_data",
+ )
+
+ self.mock_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_global.firewall_global.get_os_version",
+ )
+ self.get_os_version = self.mock_get_os_version.start()
+ self.get_os_version.return_value = "1.5"
+
+ self.execute_show_command = self.mock_execute_show_command.start()
+ self.maxDiff = None
+
+ def tearDown(self):
+ super(TestVyosFirewallRulesModule15, self).tearDown()
+ self.mock_get_resource_connection_config.stop()
+ self.mock_get_resource_connection_facts.stop()
+ self.mock_get_config.stop()
+ self.mock_load_config.stop()
+ self.mock_execute_show_command.stop()
+ self.mock_get_os_version.stop()
+
+ def load_fixtures(self, commands=None, filename=None):
+ def load_from_file(*args, **kwargs):
+ return load_fixture("vyos_firewall_global_config_v15.cfg")
+
+ self.execute_show_command.side_effect = load_from_file
+
+ def test_vyos_firewall_global_set_01_merged_interface_uses_member_keyword(self):
+ # On a 1.5.0 device (empty fixture, no existing zone), merging a
+ # zone with an interface should render "member interface", not the
+ # bare "interface" used on 1.4.x / 1.5-rolling. This is the sole
+ # thing this class exists to prove right now -- the version-gate
+ # added to _render_interfaces.
+ set_module_args(
+ dict(
+ config=dict(
+ zone=[
+ dict(
+ name="ZONE-15",
+ interfaces=["eth1"],
+ ),
+ ],
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set firewall zone ZONE-15 default-action 'drop'",
+ "set firewall zone ZONE-15 member interface eth1",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_set_02_gathered_member_interface_parsed(self):
+ # ZONE-15-EXISTING in the fixture uses 1.5.0's "member interface"
+ # syntax. Before the parse_zone fix, this misparsed into a bogus
+ # zone['member'] = "interface eth2" string field, and 'interfaces'
+ # was missing entirely. After the fix, it should land in
+ # 'interfaces' exactly like the pre-1.5.0 bare "interface" form.
+ set_module_args(dict(config=dict(), state="gathered"))
+ result = self.execute_module(changed=False)
+ zones = result["gathered"]["zone"]
+ zone = next(z for z in zones if z["name"] == "ZONE-15-EXISTING")
+ self.assertEqual(zone["interfaces"], ["eth2"])
+ self.assertEqual(zone["description"], "existing 1.5.0 zone for facts parsing test")
+ self.assertNotIn("member", zone)
+
+ def test_vyos_firewall_global_ruleset_lines_filtered_from_facts(self):
+ # Same coverage as the 1.4 version, confirmed independently on the
+ # 1.5.0 fixture/version path.
+ set_module_args(dict(config=dict(), state="gathered"))
+ result = self.execute_module(changed=False)
+ facts = result["gathered"]
+ self.assertNotIn("TESTRULESET-V4", str(facts))
+ self.assertNotIn("TESTRULESET-V6-LEGACY", str(facts))
+ self.assertNotIn("TESTRULESET-V6-1_4PLUS", str(facts))