diff options
Diffstat (limited to 'plugins')
18 files changed, 1592 insertions, 213 deletions
diff --git a/plugins/cliconf/vyos.py b/plugins/cliconf/vyos.py index 7e6b0b17..5beffaa1 100644 --- a/plugins/cliconf/vyos.py +++ b/plugins/cliconf/vyos.py @@ -80,6 +80,11 @@ class Cliconf(CliconfBase): if match: device_info["network_os_version"] = match.group(1) + if device_info["network_os_version"]: + match = re.search(r"VyOS\s*(\d+\.\d+)", device_info["network_os_version"]) + if match: + device_info["network_os_major_version"] = match.group(1) + match = re.search(r"(?:HW|Hardware) model:\s*(\S+)", data) if match: device_info["network_os_model"] = match.group(1) 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 2326bea1..f79454ed 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 @@ -134,6 +134,9 @@ class Firewall_globalArgs(object): # pylint: disable=R0903 "type": "str", }, "log": {"type": "bool"}, + "log_level": { + "choices": ["emerg", "alert", "crit", "err", "warn", "notice", "info", "debug"] + } }, "type": "list", }, diff --git a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py index eb285cfd..4d0973e3 100644 --- a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py +++ b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py @@ -50,11 +50,16 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903 "elements": "dict", "options": { "default_action": { - "choices": ["drop", "reject", "accept"], + "choices": ["drop", "reject", "accept", "jump"], "type": "str", }, + "default_jump_target": {"type": "str"}, "description": {"type": "str"}, "enable_default_log": {"type": "bool"}, + "filter": { + "choices": ["input", "output", "forward"], + "type": "str" + }, "name": {"type": "str"}, "rules": { "elements": "dict", @@ -65,6 +70,11 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903 "reject", "accept", "inspect", + "continue", + "return", + "jump", + "queue", + "synproxy", ], "type": "str", }, @@ -147,9 +157,23 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "inbound_interface": { + "options": { + "group": { + "type": "str", + }, + "name": { + "type": "str", + }, + }, + "type": "dict", + }, "ipsec": { - "choices": ["match-ipsec", "match-none"], - "type": "str", + "choices": ["match-ipsec", "match-none", "match-ipsec-in", "match-ipsec-out", "match-none-in", "match-none-out"], + "type": "str" + }, + "jump_target": { + "type": "str" }, "limit": { "options": { @@ -169,6 +193,17 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903 "choices": ["enable", "disable"], }, "number": {"required": True, "type": "int"}, + "outbound_interface": { + "options": { + "group": { + "type": "str", + }, + "name": { + "type": "str", + }, + }, + "type": "dict", + }, "p2p": { "elements": "dict", "options": { @@ -185,19 +220,52 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903 "type": "str", }, }, + "type": "list" + }, + "packet_length": { + "elements": "dict", + "options": { + "length": { + "type": "str", + }, + }, + "type": "list" + }, + "packet_length_exclude": { + "elements": "dict", + "options": { + "length": { + "type": "str", + } + }, "type": "list", }, + "packet_type": { + "choices": [ + "broadcast", + "multicast", + "host", + "other" + ], + "type": "str" + }, "protocol": {"type": "str"}, + "queue": {"type": "str"}, + "queue_options": { + "choices": ["bypass", "fanout"], + "type": "str" + }, "recent": { "options": { "count": {"type": "int"}, - "time": {"type": "int"}, + "time": {"type": "str"}, }, "type": "dict", }, "source": { "options": { "address": {"type": "str"}, + "fqdn": {"type": "str"}, "group": { "options": { "address_group": {"type": "str"}, @@ -220,8 +288,37 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "synproxy": { + "options": { + "mss": {"type": "int"}, + "window_scale": {"type": "int"}, + }, + "type": "dict", + }, "tcp": { - "options": {"flags": {"type": "str"}}, + "options": { + "flags": { + "elements": "dict", + "options": { + "flag": { + "choices": [ + "ack", + "cwr", + "ecn", + "fin", + "psh", + "rst", + "syn", + "urg", + "all", + ], + "type": "str" + }, + "invert": {"type": "bool"} + }, + "type": "list" + } + }, "type": "dict", }, "time": { 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 8694f11b..7e978ff9 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 @@ -31,6 +31,10 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils list_diff_want_only, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion + class Firewall_global(ConfigBase): """ @@ -255,7 +259,7 @@ class Firewall_global(ConfigBase): continue if ( key in l_set - and not (h and self._in_target(h, key)) + and not self._in_target(h, key) and not self._is_del(l_set, h) ): commands.append( @@ -455,7 +459,10 @@ class Firewall_global(ConfigBase): """ commands = [] have = [] - l_set = ("log", "action", "connection_type") + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + l_set = ("log", "action", "connection_type", "log_level") + else: + l_set = ("log", "action", "connection_type") if not opr and self._is_root_del(h, w, attr): commands.append(self._form_attr_cmd(attr=attr, opr=opr)) else: @@ -478,25 +485,23 @@ class Firewall_global(ConfigBase): ), ) elif not opr and key in l_set: - if not (h and self._in_target(h, key)) and not self._is_del( - l_set, - h, - ): - if key == "action": - commands.append( - self._form_attr_cmd( - attr=attr + " " + w["connection_type"], - opr=opr, - ), - ) - else: - commands.append( - self._form_attr_cmd( - attr=attr + " " + w["connection_type"], - val=self._bool_to_str(val), - opr=opr, - ), - ) + if not h: + commands.append( + self._form_attr_cmd( + attr=attr + " " + w["connection_type"], + opr=opr, + ), + ) + break # delete the whole thing and move on + if (not self._in_target(h, key) or h[key] is None) and (self._in_target(w, key) and w[key]): + # delete if not being replaced and value currently exists + commands.append( + self._form_attr_cmd( + attr=attr + " " + w["connection_type"] + " " + key, + val=self._bool_to_str(val), + opr=opr, + ), + ) return commands def _render_route_redirects(self, attr, w, h, opr): @@ -520,6 +525,14 @@ class Firewall_global(ConfigBase): if want: for w in want: h = self.search_attrib_in_have(have, w, "afi") + if 'afi' in w: + afi = w['afi'] + else: + if h and 'afi' in h: + afi = h['afi'] + else: + afi = None + afi = None for key, val in iteritems(w): if val and key != "afi": if opr and key in l_set and not (h and self._is_w_same(w, h, key)): @@ -528,6 +541,7 @@ class Firewall_global(ConfigBase): attr=key, val=self._bool_to_str(val), opr=opr, + type=afi ), ) elif not opr and key in l_set: @@ -537,6 +551,7 @@ class Firewall_global(ConfigBase): attr=key, val=self._bool_to_str(val), opr=opr, + type=afi ), ) continue @@ -546,6 +561,7 @@ class Firewall_global(ConfigBase): attr=key, val=self._bool_to_str(val), opr=opr, + type=afi ), ) elif key == "icmp_redirects": @@ -565,20 +581,27 @@ class Firewall_global(ConfigBase): commands = [] h_red = {} l_set = ("send", "receive") + if w and 'afi' in w: + afi = w['afi'] + else: + if h and 'afi' in h: + afi = h['afi'] + else: + afi = None if w[attr]: if h and attr in h.keys(): h_red = h.get(attr) or {} for item, value in iteritems(w[attr]): if opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)): commands.append( - self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr), + self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr, type=afi) ) elif ( not opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)) ): - commands.append(self._form_attr_cmd(attr=item, opr=opr)) + commands.append(self._form_attr_cmd(attr=item, opr=opr, type=afi)) return commands def search_attrib_in_have(self, have, want, attr): @@ -595,16 +618,17 @@ class Firewall_global(ConfigBase): return h return None - def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): + def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True, type=None): """ This function forms the command for leaf attribute. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. + :param type: AF type of attribute. :return: generated command. """ - command = self._compute_command(key=key, attr=self._map_attrib(attr), val=val, opr=opr) + command = self._compute_command(key=key, attr=self._map_attrib(attr, type=type), val=val, opr=opr) return command def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True): @@ -621,13 +645,15 @@ class Firewall_global(ConfigBase): cmd = "delete firewall " else: cmd = "set firewall " + if key != "group" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + cmd += "global-options " if key: cmd += key.replace("_", "-") + " " if attr: cmd += attr.replace("_", "-") if val and opr: cmd += " '" + str(val) + "'" - return cmd + return cmd.strip() def _bool_to_str(self, val): """ @@ -698,7 +724,7 @@ class Firewall_global(ConfigBase): :param key: number. :return: True/False. """ - return key in b_set and not (h and self._in_target(h, key)) + return key in b_set and not self._in_target(h, key) def _map_attrib(self, attrib, type=None): """ diff --git a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py index 09e19d70..106b2b8b 100644 --- a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py +++ b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py @@ -15,8 +15,6 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -import re - from copy import deepcopy from ansible.module_utils.six import iteritems @@ -33,6 +31,10 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils list_diff_want_only, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion + class Firewall_rules(ConfigBase): """ @@ -171,12 +173,8 @@ class Firewall_rules(ConfigBase): # In the desired configuration, search for the rule set we # already have (to be replaced by our desired # configuration's rule set). - wanted_rule_set = self.search_r_sets_in_have( - want, - rs["name"], - "r_list", - h["afi"], - ) + rs_id = self._rs_id(rs, h["afi"]) + wanted_rule_set = self.search_r_sets_in_have(want, rs_id, "r_list") if wanted_rule_set is not None: # Remove the rules that we already have if the wanted # rules exist under the same name. @@ -202,11 +200,12 @@ class Firewall_rules(ConfigBase): commands = [] if have: for h in have: - r_sets = self._get_r_sets(h) - for rs in r_sets: - w = self.search_r_sets_in_have(want, rs["name"], "r_list", h["afi"]) + have_r_sets = self._get_r_sets(h) + for rs in have_r_sets: + rs_id = self._rs_id(rs, h["afi"]) + w = self.search_r_sets_in_have(want, rs_id, "r_list") if not w: - commands.append(self._compute_command(h["afi"], rs["name"], remove=True)) + commands.append(self._compute_command(rs_id, remove=True)) else: commands.extend(self._add_r_sets(h["afi"], rs, w, opr=False)) commands.extend(self._state_merged(want, have)) @@ -223,7 +222,8 @@ class Firewall_rules(ConfigBase): for w in want: r_sets = self._get_r_sets(w) for rs in r_sets: - h = self.search_r_sets_in_have(have, rs["name"], "r_list", w["afi"]) + rs_id = self._rs_id(rs, w["afi"]) + h = self.search_r_sets_in_have(have, rs_id, "r_list") commands.extend(self._add_r_sets(w["afi"], rs, h)) return commands @@ -240,18 +240,21 @@ class Firewall_rules(ConfigBase): r_sets = self._get_r_sets(w) if r_sets: for rs in r_sets: - h = self.search_r_sets_in_have(have, rs["name"], "r_list", w["afi"]) + rs_id = self._rs_id(rs, w["afi"]) + h = self.search_r_sets_in_have(have, rs_id, "r_list") if h: - commands.append(self._compute_command(w["afi"], h["name"], remove=True)) + commands.append(self._compute_command(rs_id, remove=True)) elif have: for h in have: if h["afi"] == w["afi"]: - commands.append(self._compute_command(w["afi"], remove=True)) + commands.append( + self._compute_command(self._rs_id(None, w["afi"]), remove=True) + ) elif have: for h in have: r_sets = self._get_r_sets(h) if r_sets: - commands.append(self._compute_command(afi=h["afi"], remove=True)) + commands.append(self._compute_command(self._rs_id(None, h["afi"]), remove=True)) return commands def _add_r_sets(self, afi, want, have, opr=True): @@ -265,11 +268,12 @@ class Firewall_rules(ConfigBase): :return: generated commands list. """ commands = [] - l_set = ("description", "default_action", "enable_default_log") + l_set = ("description", "default_action", "default_jump_target", "enable_default_log") h_rs = {} h_rules = {} w_rs = deepcopy(remove_empties(want)) w_rules = w_rs.pop("rules", None) + rs_id = self._rs_id(want, afi=afi) if have: h_rs = deepcopy(remove_empties(have)) h_rules = h_rs.pop("rules", None) @@ -278,9 +282,9 @@ class Firewall_rules(ConfigBase): if opr and key in l_set and not (h_rs and self._is_w_same(w_rs, h_rs, key)): if key == "enable_default_log": if val and (not h_rs or key not in h_rs or not h_rs[key]): - commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs)) + commands.append(self._add_rs_base_attrib(rs_id, key, w_rs)) else: - commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs)) + commands.append(self._add_rs_base_attrib(rs_id, key, w_rs)) elif not opr and key in l_set: if ( key == "enable_default_log" @@ -288,22 +292,24 @@ class Firewall_rules(ConfigBase): and h_rs and (key not in h_rs or not h_rs[key]) ): - commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs, opr)) + commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr)) elif not (h_rs and self._in_target(h_rs, key)): - commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs, opr)) - commands.extend(self._add_rules(afi, want["name"], w_rules, h_rules, opr)) + commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr)) + commands.extend(self._add_rules(rs_id, w_rules, h_rules, opr)) if h_rules: have["rules"] = h_rules if w_rules: want["rules"] = w_rules return commands - def _add_rules(self, afi, name, w_rules, h_rules, opr=True): + def _add_rules(self, rs_id, w_rules, h_rules, opr=True): """ This function forms the set/delete commands based on the 'opr' type for rules attributes. - :param want: desired config. - :param have: target config. + :param rs_id: rule-set identifier. + :param w_rules: desired config. + :param h_rules: target config. + :param opr: True/False. :return: generated commands list. """ commands = [] @@ -316,31 +322,70 @@ class Firewall_rules(ConfigBase): "disable", "description", "log", + "jump_target", ) if w_rules: for w in w_rules: - cmd = self._compute_command(afi, name, w["number"], opr=opr) - h = self.search_r_sets_in_have(h_rules, w["number"], type="rules") + cmd = self._compute_command(rs_id, w["number"], opr=opr) + h = self.search_rules_in_have_rs(h_rules, w["number"]) for key, val in iteritems(w): if val: if opr and key in l_set and not (h and self._is_w_same(w, h, key)): if key == "disable": if not (not val and (not h or key not in h or not h[key])): - commands.append(self._add_r_base_attrib(afi, name, key, w)) + commands.append(self._add_r_base_attrib(rs_id, key, w)) else: - commands.append(self._add_r_base_attrib(afi, name, key, w)) + commands.append(self._add_r_base_attrib(rs_id, key, w)) elif not opr: + # Note: if you are experiencing sticky configuration on replace + # you may need to add an explicit check for the key here. Anything that + # doesn't have a custom operation is taken care of by the `l_set` check + # below, but I'm not sure how any of the others work. + # It's possible that historically the delete was forced (but now it's + # checked). if key == "number" and self._is_del(l_set, h): - commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr)) + commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr)) continue - if key == "disable" and val and h and (key not in h or not h[key]): - commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr)) + if ( + key == "tcp" + and val + and h + and (key not in h or not h[key] or h[key] != w[key]) + ): + commands.extend(self._add_tcp(key, w, h, cmd, opr)) + if ( + key == "state" + and val + and h + and (key not in h or not h[key] or h[key] != w[key]) + ): + commands.extend(self._add_state(key, w, h, cmd, opr)) + if ( + key == "icmp" + and val + and h + and (key not in h or not h[key] or h[key] != w[key]) + ): + commands.extend(self._add_icmp(key, w, h, cmd, opr)) + if ( + key in ("packet_length", "packet_length_exclude") + and val + and h + and (key not in h or not h[key] or h[key] != w[key]) + ): + commands.extend(self._add_packet_length(key, w, h, cmd, opr)) + elif key == "disable" and val and h and (key not in h or not h[key]): + commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr)) + elif key in ("inbound_interface", "outbound_interface") and not ( + h and self._is_w_same(w, h, key) + ): + commands.extend(self._add_interface(key, w, h, cmd, opr)) elif ( key in l_set and not (h and self._in_target(h, key)) and not self._is_del(l_set, h) ): - commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr)) + commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr)) elif key == "p2p": commands.extend(self._add_p2p(key, w, h, cmd, opr)) elif key == "tcp": @@ -357,6 +402,10 @@ class Firewall_rules(ConfigBase): commands.extend(self._add_recent(key, w, h, cmd, opr)) elif key == "destination" or key == "source": commands.extend(self._add_src_or_dest(key, w, h, cmd, opr)) + elif key in ("packet_length", "packet_length_exclude"): + commands.extend(self._add_packet_length(key, w, h, cmd, opr)) + elif key in ("inbound_interface", "outbound_interface"): + commands.extend(self._add_interface(key, w, h, cmd, opr)) return commands def _add_p2p(self, attr, w, h, cmd, opr): @@ -405,8 +454,11 @@ class Firewall_rules(ConfigBase): and item in l_set and not (h_state and self._is_w_same(w[attr], h_state, item)) ): - commands.append(cmd + (" " + attr + " " + item + " " + self._bool_to_str(val))) - elif not opr and item in l_set and not (h_state and self._in_target(h_state, item)): + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + commands.append(cmd + (" " + attr + " " + item)) + else: + commands.append(cmd + (" " + attr + " " + item + " " + self._bool_to_str(val))) + elif not opr and item in l_set and not self._in_target(h_state, item): commands.append(cmd + (" " + attr + " " + item)) return commands @@ -460,26 +512,50 @@ class Firewall_rules(ConfigBase): and not (h_icmp and self._is_w_same(w[attr], h_icmp, item)) ): if item == "type_name": - os_version = self._get_os_version() - ver = re.search( - "vyos ([\\d\\.]+)-?.*", # noqa: W605 - os_version, - re.IGNORECASE, - ) - if ver.group(1) >= "1.4": + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.3"): param_name = "type-name" else: param_name = "type" - if "ipv6-name" in cmd: + if "ipv6" in cmd: # ipv6-name or ipv6 commands.append(cmd + (" " + "icmpv6" + " " + param_name + " " + val)) else: commands.append( cmd + (" " + attr + " " + item.replace("_", "-") + " " + val), ) else: - commands.append(cmd + (" " + attr + " " + item + " " + str(val))) - elif not opr and item in l_set and not (h_icmp and self._in_target(h_icmp, item)): - commands.append(cmd + (" " + attr + " " + item)) + if "ipv6" in cmd: # ipv6-name or ipv6 + commands.append(cmd + (" " + "icmpv6" + " " + item + " " + str(val))) + else: + commands.append(cmd + (" " + attr + " " + item + " " + str(val))) + elif not opr and item in l_set and not self._in_target(h_icmp, item): + commands.append(cmd + (" " + attr + " " + item.replace("_", "-") + " " + str(val))) + return commands + + def _add_interface(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'interface' attributes based on the 'opr'. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + h_if = {} + l_set = ("name", "group") + if w[attr]: + if h and attr in h.keys(): + h_if = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if opr and item in l_set and not (h_if and self._is_w_same(w[attr], h_if, item)): + commands.append( + cmd + + (" " + attr.replace("_", "-") + " " + item.replace("_", "-") + " " + val) + ) + elif not opr and item in l_set and not (h_if and self._in_target(h_if, item)): + commands.append( + cmd + (" " + attr.replace("_", "-") + " " + item.replace("_", "-")) + ) return commands def _add_time(self, attr, w, h, cmd, opr): @@ -524,15 +600,107 @@ class Firewall_rules(ConfigBase): commands.append(cmd + (" " + attr + " " + item)) return commands + def _add_tcp_1_4(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'tcp' attributes based on the 'opr'. + Version 1.4+ + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + have = [] + key = "flags" + want = [] + + if w: + if w.get(attr): + want = w.get(attr).get(key) or [] + if h: + if h.get(attr): + have = h.get(attr).get(key) or [] + if want: + if opr: + flags = list_diff_want_only(want, have) + for flag in flags: + invert = flag.get("invert", False) + commands.append( + cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"]) + ) + elif not opr: + flags = list_diff_want_only(want, have) + for flag in flags: + invert = flag.get("invert", False) + commands.append( + cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"]) + ) + return commands + + def _add_packet_length(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'packet_length[_exclude]' attributes based on the 'opr'. + If < 1.4, handle tcp attributes. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + have = [] + want = [] + + if w: + if w.get(attr): + want = w.get(attr) or [] + if h: + if h.get(attr): + have = h.get(attr) or [] + attr = attr.replace("_", "-") + if want: + if opr: + lengths = list_diff_want_only(want, have) + for l_rec in lengths: + commands.append(cmd + " " + attr + " " + str(l_rec["length"])) + elif not opr: + lengths = list_diff_want_only(want, have) + for l_rec in lengths: + commands.append(cmd + " " + attr + " " + str(l_rec["length"])) + return commands + + def _tcp_flags_string(self, flags): + """ + This function forms the tcp flags string. + :param flags: flags list. + :return: flags string or None. + """ + if not flags: + return "" + flag_str = "" + for flag in flags: + this_flag = flag["flag"].upper() + if flag.get("invert", False): + this_flag = "!" + this_flag + if len(flag_str) > 0: + flag_str = ",".join([flag_str, this_flag]) + else: + flag_str = this_flag + return flag_str + def _add_tcp(self, attr, w, h, cmd, opr): """ This function forms the commands for 'tcp' attributes based on the 'opr'. + If < 1.4, handle tcp attributes. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + return self._add_tcp_1_4(attr, w, h, cmd, opr) h_tcp = {} commands = [] if w[attr]: @@ -542,10 +710,11 @@ class Firewall_rules(ConfigBase): if h and key in h[attr].keys(): h_tcp = h[attr].get(key) or {} if flags: - if opr and not (h_tcp and self._is_w_same(w[attr], h[attr], key)): - commands.append(cmd + (" " + attr + " " + key + " " + flags)) - if not opr and not (h_tcp and self._is_w_same(w[attr], h[attr], key)): - commands.append(cmd + (" " + attr + " " + key + " " + flags)) + flag_str = self._tcp_flags_string(flags) + if opr and not (h_tcp and flags == h_tcp): + commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str)) + if not opr and not (h_tcp and flags == h_tcp): + commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str)) return commands def _add_limit(self, attr, w, h, cmd, opr): @@ -671,43 +840,68 @@ class Firewall_rules(ConfigBase): ) return commands - def search_r_sets_in_have(self, have, w_name, type="rule_sets", afi=None): + def search_rules_in_have_rs(self, have_rules, r_number): + """ + This function returns the rule if it is present in target config. + :param have: target config. + :param rs_id: rule-set identifier. + :param r_number: rule-number. + :return: rule. + """ + if have_rules: + for h in have_rules: + key = "number" + for r in have_rules: + if key in r and r[key] == r_number: + return r + return None + + def search_r_sets_in_have(self, have, rs_id, type="rule_sets"): """ This function returns the rule-set/rule if it is present in target config. :param have: target config. - :param w_name: rule-set name. - :param type: rule_sets/rule/r_list. - :param afi: address family (when type is r_list). + :param rs_id: rule-identifier. + :param type: rule_sets if searching a rule_set and r_list if searching from a rule_list. :return: rule-set/rule. """ - if have: + if "afi" in rs_id: + afi = rs_id["afi"] + else: + afi = None + if rs_id["filter"]: + key = "filter" + w_value = rs_id["filter"] + elif rs_id["name"]: key = "name" - if type == "rules": - key = "number" - for r in have: - if r[key] == w_name: - return r - elif type == "r_list": + w_value = rs_id["name"] + else: + raise ValueError("id must be specific to name or filter") + + if type not in ("r_list", "rule_sets"): + raise ValueError("type must be rule_sets or r_list") + if have: + if type == "r_list": for h in have: if h["afi"] == afi: r_sets = self._get_r_sets(h) for rs in r_sets: - if rs[key] == w_name: + if key in rs and rs[key] == w_value: return rs else: + # searching a ruleset for rs in have: - if rs[key] == w_name: + if key in rs and rs[key] == w_value: return rs return None - def _get_r_sets(self, item, type="rule_sets"): + def _get_r_sets(self, item): """ - This function returns the list of rule-sets/rules. + This function returns the list of rule-sets. :param item: config dictionary. - :param type: rule_sets/rule/r_list. :return: list of rule-sets/rules. """ rs_list = [] + type = "rule_sets" r_sets = item[type] if r_sets: for rs in r_sets: @@ -716,8 +910,7 @@ class Firewall_rules(ConfigBase): def _compute_command( self, - afi, - name=None, + rs_id, number=None, attrib=None, value=None, @@ -726,46 +919,53 @@ class Firewall_rules(ConfigBase): ): """ This function construct the add/delete command based on passed attributes. - :param afi: address type. - :param name: rule-set name. + :param rs_id: rule-set identifier. :param number: rule-number. :param attrib: attribute name. :param value: value. :param remove: True if delete command needed to be construct. - :param opr: opeeration flag. + :param opr: operation flag. :return: generated command. """ + if rs_id["name"] and rs_id["filter"]: + raise ValueError("name and filter cannot be used together") if remove or not opr: - cmd = "delete firewall " + self._get_fw_type(afi) + cmd = "delete firewall " + self._get_fw_type(rs_id["afi"]) else: - cmd = "set firewall " + self._get_fw_type(afi) - if name: - cmd += " " + name + cmd = "set firewall " + self._get_fw_type(rs_id["afi"]) + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + if rs_id["name"]: + cmd += " name " + rs_id["name"] + elif rs_id["filter"]: + cmd += " " + rs_id["filter"] + " filter" + elif rs_id["name"]: + cmd += " " + rs_id["name"] if number: cmd += " rule " + str(number) if attrib: - cmd += " " + attrib.replace("_", "-") + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4") and attrib == "enable_default_log": + cmd += " " + "default-log" + else: + cmd += " " + attrib.replace("_", "-") if value and opr and attrib != "enable_default_log" and attrib != "disable": cmd += " '" + str(value) + "'" return cmd - def _add_r_base_attrib(self, afi, name, attr, rule, opr=True): + def _add_r_base_attrib(self, rs_id, attr, rule, opr=True): """ This function forms the command for 'rules' attributes which doesn't have further sub attributes. - :param afi: address type. - :param name: rule-set name + :param rs_id: rule-set identifier. :param attrib: attribute name :param rule: rule config dictionary. :param opr: True/False. :return: generated command. """ if attr == "number": - command = self._compute_command(afi=afi, name=name, number=rule["number"], opr=opr) + command = self._compute_command(rs_id, number=rule["number"], opr=opr) else: command = self._compute_command( - afi=afi, - name=name, + rs_id=rs_id, number=rule["number"], attrib=attr, value=rule[attr], @@ -773,21 +973,54 @@ class Firewall_rules(ConfigBase): ) return command - def _add_rs_base_attrib(self, afi, name, attrib, rule, opr=True): + def _rs_id(self, have, afi, name=None, filter=None): """ + This function returns the rule-set identifier based on + the example rule, overriding the components as specified. - This function forms the command for 'rule-sets' attributes which doesn't - have further sub attributes. + :param have: example rule. :param afi: address type. - :param name: rule-set name + :param name: rule-set name. + :param filter: filter name. + :return: rule-set identifier. + """ + identifier = {"name": None, "filter": None} + if afi: + identifier["afi"] = afi + else: + raise ValueError("afi must be provided") + + if name: + identifier["name"] = name + return identifier + elif filter: + identifier["filter"] = filter + return identifier + if have: + if "name" in have and have["name"]: + identifier["name"] = have["name"] + return identifier + if "filter" in have and have["filter"]: + identifier["filter"] = have["filter"] + return identifier + # raise ValueError("name or filter must be provided or present in have") + # unless we want a wildcard + return identifier + + def _add_rs_base_attrib(self, rs_id, attrib, rule, opr=True): + """ + + This function forms the command for 'rule-sets' attributes which don't + have further sub attributes. + + :param rs_id: rule-set identifier. :param attrib: attribute name :param rule: rule config dictionary. :param opr: True/False. :return: generated command. """ command = self._compute_command( - afi=afi, - name=name, + rs_id=rs_id, attrib=attrib, value=rule[attrib], opr=opr, @@ -808,6 +1041,8 @@ class Firewall_rules(ConfigBase): :param afi: address type :return: rule-set type. """ + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + return "ipv6" if afi == "ipv6" else "ipv4" return "ipv6-name" if afi == "ipv6" else "name" def _is_del(self, l_set, h, key="number"): @@ -834,37 +1069,9 @@ class Firewall_rules(ConfigBase): def _in_target(self, h, key): """ - This function checks whether the target nexist and key present in target config. + This function checks whether the target exists and key present in target config. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h else False - - def _is_base_attrib(self, key): - """ - This function checks whether key is present in predefined - based attribute set. - :param key: - :return: True/False. - """ - r_set = ( - "p2p", - "ipsec", - "log", - "action", - "fragment", - "protocol", - "disable", - "description", - "mac_address", - "default_action", - "enable_default_log", - ) - return True if key in r_set else False - - def _get_os_version(self): - os_version = "1.2" - if self._connection: - os_version = self._connection.get_device_info()["network_os_version"] - return os_version diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py index 731014cd..62e4f922 100644 --- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py @@ -275,7 +275,7 @@ class Interfaces(ConfigBase): commands.append( self._compute_commands(key=key, interface=want_copy["name"], remove=True), ) - if have_copy["enabled"] is False: + if have_copy["enabled"] is False and not ('enabled' in want_copy and want_copy["enabled"] is False): commands.append( self._compute_commands(key="enabled", value=True, interface=want_copy["name"]), ) diff --git a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py index a7652a6b..51b47494 100644 --- a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py +++ b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py @@ -27,9 +27,17 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.u from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import ( - Ospf_interfacesTemplate, + Ospf_interfacesTemplate ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import ( + Ospf_interfacesTemplate14 +) + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion + class Ospf_interfaces(ResourceModule): """ @@ -61,12 +69,30 @@ class Ospf_interfaces(ResourceModule): "passive", ] + def _validate_template(self): + version = get_os_version(self._module) + if LooseVersion(version) >= LooseVersion("1.4"): + self._tmplt = Ospf_interfacesTemplate14() + else: + self._tmplt = Ospf_interfacesTemplate() + + def parse(self): + """ override parse to check template """ + self._validate_template() + return super().parse() + + def get_parser(self, name): + """get_parsers""" + self._validate_template() + return super().get_parser(name) + def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ + self._validate_template() if self.state not in ["parsed", "gathered"]: self.generate_commands() self.run_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 5b472222..3f4da3ea 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 @@ -174,9 +174,8 @@ class Firewall_globalFacts(object): :return: generated rule list configuration. """ sp_lst = [] - attrib = "state-policy" - policies = findall(r"^set firewall " + attrib + " (\\S+)", conf, M) - + policies = findall(r"^set firewall (?:global-options )state-policy (\S+)", conf, M) + policies = list(set(policies)) # remove redundancies if policies: rules_lst = [] for sp in set(policies): @@ -197,7 +196,7 @@ class Firewall_globalFacts(object): :param attrib: connection type. :return: generated rule configuration dictionary. """ - a_lst = ["action", "log"] + a_lst = ["action", "log", "log_level"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict @@ -304,16 +303,15 @@ class Firewall_globalFacts(object): regex = match + " " + regex if conf: if self.is_bool(attrib): - attr = self.map_regex(attrib, type) - out = conf.find(attr.replace("_", "-")) - dis = conf.find(attr.replace("_", "-") + " 'disable'") - if out >= 1: - if dis >= 1: + # fancy regex to make sure we don't get a substring + out = search(r"^.*" + regex + r"( 'disable')?(?=\s|$)", conf, M) + if out: + if out.group(1): config[attrib] = False else: config[attrib] = True else: - out = search(r"^.*" + regex + " (.+)", conf, M) + out = search(r"^.*" + regex + r" (.+)", conf, M) if out: val = out.group(1).strip("'") if self.is_num(attrib): diff --git a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py index ead038a5..1fc70255 100644 --- a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py +++ b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py @@ -14,8 +14,6 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -import re - from copy import deepcopy from re import M, findall, search @@ -61,15 +59,31 @@ class Firewall_rulesFacts(object): data = self.get_device_data(connection) # split the config into instances of the resource objs = [] - v6_rules = findall(r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M) - v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M) + # check 1.4+ first + new_rules = True + v6_rules = findall(r"^set firewall ipv6 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)", data, M) + if not v6_rules: + v6_rules = findall(r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M) + if v6_rules: + new_rules = False + v4_rules = findall(r"^set firewall ipv4 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)", data, M) + if not v4_rules: + v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M) + if v4_rules: + new_rules = False if v6_rules: - config = self.get_rules(data, v6_rules, type="ipv6") + if new_rules: + config = self.get_rules_post_1_4(data, v6_rules, type="ipv6") + else: + config = self.get_rules(data, v6_rules, type="ipv6") if config: config = utils.remove_empties(config) objs.append(config) if v4_rules: - config = self.get_rules(data, v4_rules, type="ipv4") + if new_rules: + config = self.get_rules_post_1_4(data, v4_rules, type="ipv4") + else: + config = self.get_rules(data, v4_rules, type="ipv4") if config: config = utils.remove_empties(config) objs.append(config) @@ -113,6 +127,39 @@ class Firewall_rulesFacts(object): config = {"afi": "ipv6", "rule_sets": r_v6} return config + def get_rules_post_1_4(self, data, rules, type): + """ + This function performs following: + - Form regex to fetch 'rule-sets' specific config from data. + - Form the rule-set list based on ip address. + Specifically for v1.4+ version. + :param data: configuration. + :param rules: list of rule-sets. + :param type: ip address type. + :return: generated rule-sets configuration. + """ + r_v4 = [] + r_v6 = [] + for kind, name in set(rules): + rule_regex = r" %s %s %s .+$" % (type, kind, name.strip("'")) + cfg = findall(rule_regex, data, M) + fr = self.render_config(cfg, name.strip("'")) + if kind == "name": + fr["name"] = name.strip("'") + elif kind in ("forward", "input", "output"): + fr["filter"] = kind + else: + raise ValueError("Unknown rule kind: %s %s" % kind, name) + if type == "ipv6": + r_v6.append(fr) + else: + r_v4.append(fr) + if r_v4: + config = {"afi": "ipv4", "rule_sets": r_v4} + if r_v6: + config = {"afi": "ipv6", "rule_sets": r_v6} + return config + def render_config(self, conf, match): """ Render config as dictionary structure and delete keys @@ -124,10 +171,12 @@ class Firewall_rulesFacts(object): :returns: The generated config """ conf = "\n".join(filter(lambda x: x, conf)) - a_lst = ["description", "default_action", "enable_default_log"] + a_lst = ["description", "default_action", "default_jump_target", "enable_default_log", "default_log"] config = self.parse_attr(conf, a_lst, match) if not config: config = {} + if 'default_log' in config: + config['enable_default_log'] = config.pop('default_log') config["rules"] = self.parse_rules_lst(conf) return config @@ -169,11 +218,14 @@ class Firewall_rulesFacts(object): "disable", "description", "icmp", + "jump_target", + "queue", + "queue_options", ] rule = self.parse_attr(conf, a_lst) r_sub = { "p2p": self.parse_p2p(conf), - "tcp": self.parse_tcp(conf, "tcp"), + "tcp": self.parse_tcp(conf), "icmp": self.parse_icmp(conf, "icmp"), "time": self.parse_time(conf, "time"), "limit": self.parse_limit(conf, "limit"), @@ -181,10 +233,42 @@ class Firewall_rulesFacts(object): "recent": self.parse_recent(conf, "recent"), "source": self.parse_src_or_dest(conf, "source"), "destination": self.parse_src_or_dest(conf, "destination"), + "inbound_interface": self.parse_interface(conf, "inbound-interface"), + "outbound_interface": self.parse_interface(conf, "outbound-interface"), + "packet_length": self.parse_packet_length(conf, "packet-length"), + "packet_length_exclude": self.parse_packet_length(conf, "packet-length-exclude"), } rule.update(r_sub) return rule + def parse_interface(self, conf, attrib): + """ + This function triggers the parsing of 'interface' attributes. + :param conf: configuration. + :param attrib: 'interface'. + :return: generated config dictionary. + """ + a_lst = ["name", "group"] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_packet_length(self, conf, attrib=None): + """ + This function triggers the parsing of 'packet-length' attributes. + :param conf: configuration. + :param attrib: 'packet-length'. + :return: generated config dictionary. + """ + lengths = [] + rule_regex = r"%s (\d+)" % attrib + found_lengths = findall(rule_regex, conf, M) + if found_lengths: + lengths = [] + for l in set(found_lengths): + obj = {"length": l.strip("'")} + lengths.append(obj) + return lengths + def parse_p2p(self, conf): """ This function forms the regex to fetch the 'p2p' with in @@ -226,15 +310,41 @@ class Firewall_rulesFacts(object): cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict - def parse_tcp(self, conf, attrib=None): + def parse_tcp(self, conf): """ This function triggers the parsing of 'tcp' attributes. :param conf: configuration. :param attrib: 'tcp'. :return: generated config dictionary. """ - cfg_dict = self.parse_attr(conf, ["flags"], match=attrib) - return cfg_dict + f_lst = [] + flags = findall(r"tcp flags (not )?(?:\'*)([\w!,]+)(?:\'*)", conf, M) + # for pre 1.4, this is a string including possible commas + # and ! as an inverter. For 1.4+ this is a single flag per + # command and 'not' as the inverter + if flags: + flag_lst = [] + for n, f in set(flags): + f = f.strip("'").lower() + if "," in f: + # pre 1.4 version with multiple flags + fs = f.split(",") + for f in fs: + if "!" in f: + obj = {"flag": f.strip("'!"), "invert": True} + else: + obj = {"flag": f.strip("'")} + flag_lst.append(obj) + elif "!" in f: + obj = {"flag": f.strip("'!"), "invert": True} + flag_lst.append(obj) + else: + obj = {"flag": f.strip("'")} + if n: + obj["invert"] = True + flag_lst.append(obj) + f_lst = sorted(flag_lst, key=lambda i: i["flag"]) + return {"flags": f_lst} def parse_time(self, conf, attrib=None): """ @@ -276,6 +386,44 @@ class Firewall_rulesFacts(object): cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict + def parse_icmp_attr(self, conf, match): + """ + This function peforms the following: + - parse ICMP arguemnts for firewall rules + - consider that older versions may need numbers or letters + in type, newer ones are more specific + :param conf: configuration. + :param match: parent node/attribute name. + :return: generated config dictionary. + """ + config = {} + if not conf: + return config + + for attrib in ("code", "type", "type-name"): + regex = self.map_regex(attrib) + if match: + regex = match + " " + regex + out = search(r"^.*" + regex + " (.+)", conf, M) + if out: + val = out.group(1).strip("'") + if attrib == 'type-name': + config['type_name'] = val + if attrib == 'code': + config['code'] = int(val) + if attrib == 'type': + # <1.3 could be # (type), #/# (type/code) or 'type' (type_name) + # recent this is only for strings + if "/" in val: # type/code + (type_no, code) = val.split(".") + config['type'] = type_no + config['code'] = code + elif val.isnumeric(): + config['type'] = type_no + else: + config['type_name'] = val + return config + def parse_icmp(self, conf, attrib=None): """ This function triggers the parsing of 'icmp' attributes. @@ -283,11 +431,9 @@ class Firewall_rulesFacts(object): :param attrib: 'icmp'. :return: generated config dictionary. """ - a_lst = ["code", "type", "type_name"] - if attrib == "icmp": - attrib = "icmpv6" - conf = re.sub("icmpv6 type", "icmpv6 type-name", conf) - cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + cfg_dict = self.parse_icmp_attr(conf, "icmp") + if (len(cfg_dict) == 0): + cfg_dict = self.parse_icmp_attr(conf, "icmpv6") return cfg_dict def parse_limit(self, conf, attrib=None): @@ -330,7 +476,6 @@ class Firewall_rulesFacts(object): if conf: if self.is_bool(attrib): out = conf.find(attrib.replace("_", "-")) - dis = conf.find(attrib.replace("_", "-") + " 'disable'") if out >= 1: if dis >= 1: @@ -375,6 +520,7 @@ class Firewall_rulesFacts(object): "disabled", "established", "enable_default_log", + "default_log", ) return True if attrib in bool_set else False diff --git a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py index 995be911..cd8008c6 100644 --- a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py @@ -17,7 +17,7 @@ __metaclass__ = type from copy import deepcopy -from re import M, findall +from re import M, findall, search from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils @@ -66,7 +66,7 @@ class InterfacesFacts(object): ) if interface_names: for interface in set(interface_names): - intf_regex = r" %s .+$" % interface.strip("'") + intf_regex = r" %s (.+$)" % interface.strip("'") cfg = findall(intf_regex, data, M) obj = self.render_config(cfg) obj["name"] = interface.strip("'") @@ -106,7 +106,7 @@ class InterfacesFacts(object): if vif_names: vifs_list = [] for vif in set(vif_names): - vif_regex = r" %s .+$" % vif + vif_regex = r"%s (.+$)" % vif cfg = "\n".join(findall(vif_regex, conf, M)) obj = self.parse_attribs(["description", "mtu"], cfg) obj["vlan_id"] = int(vif) @@ -117,6 +117,14 @@ class InterfacesFacts(object): return vifs_list def parse_attribs(self, attribs, conf): + """ + Parse the attributes of a network interface. + + :param attribs: List of attribute names. + :param conf: Configuration string. + :rtype: dict + :returns: Parsed configuration dictionary. + """ config = {} for item in attribs: value = utils.parse_conf_arg(conf, item) @@ -126,7 +134,11 @@ class InterfacesFacts(object): config[item] = value.strip("'") else: config[item] = None - if "disable" in conf: + + # match only on disable next to the interface name + # there are other sub-commands that can be disabled + match = search(r"^ *disable", conf, M) + if match: config["enabled"] = False else: config["enabled"] = True diff --git a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py index be467a0a..7d4d1a08 100644 --- a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py @@ -62,7 +62,7 @@ class L3_interfacesFacts(object): # operate on a collection of resource x objs = [] interface_names = re.findall( - r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|vxlan) (?:\'*)(\S+)(?:\'*)", + r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|loopback|vxlan) (?:\'*)(\S+)(?:\'*)", data, re.M, ) diff --git a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py index af6c5770..c0e74895 100644 --- a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py @@ -23,9 +23,17 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.osp Ospf_interfacesArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import ( - Ospf_interfacesTemplate, + Ospf_interfacesTemplate ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import ( + Ospf_interfacesTemplate14 +) + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version + +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion + class Ospf_interfacesFacts(object): """The vyos ospf_interfaces facts class""" @@ -35,9 +43,30 @@ class Ospf_interfacesFacts(object): self.argument_spec = Ospf_interfacesArgs.argument_spec def get_device_data(self, connection): + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + # use set protocols ospf in order to get both ospf and ospfv3 + return connection.get("show configuration commands | match 'set protocols ospf'") return connection.get('show configuration commands | match "set interfaces"') - def get_config_set(self, data): + def get_config_set_1_4(self, data): + """To classify the configurations beased on interface""" + interface_list = [] + config_set = [] + int_string = "" + for config_line in data.splitlines(): + ospf_int = re.search(r"set protocols (?:ospf|ospfv3) interface (\S+) .*", config_line) + if ospf_int: + if ospf_int.group(1) not in interface_list: + if int_string: + config_set.append(int_string) + interface_list.append(ospf_int.group(1)) + int_string = "" + int_string = int_string + config_line + "\n" + if int_string: + config_set.append(int_string) + return config_set + + def get_config_set_1_2(self, data): """To classify the configurations beased on interface""" interface_list = [] config_set = [] @@ -55,6 +84,12 @@ class Ospf_interfacesFacts(object): config_set.append(int_string) return config_set + def get_config_set(self, data, connection): + """To classify the configurations beased on interface""" + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + return self.get_config_set_1_4(data) + return self.get_config_set_1_2(data) + def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for Ospf_interfaces network resource @@ -67,16 +102,20 @@ class Ospf_interfacesFacts(object): """ facts = {} objs = [] - ospf_interfaces_parser = Ospf_interfacesTemplate(lines=[], module=self._module) + if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"): + ospf_interface_class = Ospf_interfacesTemplate14 + else: + ospf_interface_class = Ospf_interfacesTemplate + ospf_interfaces_parser = ospf_interface_class(lines=[], module=self._module) if not data: data = self.get_device_data(connection) # parse native config using the Ospf_interfaces template ospf_interfaces_facts = [] - resources = self.get_config_set(data) + resources = self.get_config_set(data, connection) for resource in resources: - ospf_interfaces_parser = Ospf_interfacesTemplate( + ospf_interfaces_parser = ospf_interface_class( lines=resource.split("\n"), module=self._module, ) @@ -93,7 +132,7 @@ class Ospf_interfacesFacts(object): self.argument_spec, {"config": ospf_interfaces_facts}, redact=True, - ), + ) ) if params.get("config"): for cfg in params["config"]: diff --git a/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py new file mode 100644 index 00000000..7f49d47a --- /dev/null +++ b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py @@ -0,0 +1,650 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +""" +The Ospf_interfaces parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _get_parameters(data): + if data["afi"] == "ipv6": + val = ["ospfv3", "ipv6"] + else: + val = ["ospf", "ip"] + return val + + +def _tmplt_ospf_int_delete(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + params[0] + " interface {name}".format(**config_data) + ) + + return command + + +def _tmplt_ospf_int_cost(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " cost {cost}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_auth_password(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " authentication plaintext-password {plaintext_password}".format( + **config_data["address_family"]["authentication"] + ) + ) + return command + + +def _tmplt_ospf_int_auth_md5(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " authentication md5 key-id {key_id} ".format( + **config_data["address_family"]["authentication"]["md5_key"] + ) + + "md5-key {key}".format(**config_data["address_family"]["authentication"]["md5_key"]) + ) + + return command + + +def _tmplt_ospf_int_auth_md5_delete(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " authentication" + ) + + return command + + +def _tmplt_ospf_int_bw(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " bandwidth {bandwidth}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_hello_interval(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " hello-interval {hello_interval}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_dead_interval(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " dead-interval {dead_interval}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_mtu_ignore(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " mtu-ignore" + ) + + return command + + +def _tmplt_ospf_int_network(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " network {network}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_priority(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " priority {priority}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_retransmit_interval(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " retransmit-interval {retransmit_interval}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_transmit_delay(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " transmit-delay {transmit_delay}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_ifmtu(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " ifmtu {ifmtu}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_instance(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " instance-id {instance}".format(**config_data["address_family"]) + ) + + return command + + +def _tmplt_ospf_int_passive(config_data): + params = _get_parameters(config_data["address_family"]) + command = ( + "protocols " + + params[0] + + " interface {name}".format(**config_data) + + " passive" + ) + + return command + + +class Ospf_interfacesTemplate14(NetworkTemplate): + def __init__(self, lines=None, module=None): + prefix = {"set": "set", "remove": "delete"} + super(Ospf_interfacesTemplate14, self).__init__( + lines=lines, tmplt=self, prefix=prefix, module=module + ) + + # fmt: off + PARSERS = [ + { + "name": "ip_ospf", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + *$""", + re.VERBOSE, + ), + "remval": _tmplt_ospf_int_delete, + "compval": "address_family", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + } + } + } + }, + { + "name": "authentication_password", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+authentication + \s+plaintext-password + \s+(?P<text>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_auth_password, + "compval": "address_family.authentication", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "authentication": { + "plaintext_password": "{{ text }}" + } + } + } + } + }, + { + "name": "authentication_md5", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+authentication + \s+md5 + \s+key-id + \s+(?P<id>\d+) + \s+md5-key + \s+(?P<text>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_auth_md5, + "remval": _tmplt_ospf_int_auth_md5_delete, + "compval": "address_family.authentication", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "authentication": { + "md5_key": { + "key_id": "{{ id }}", + "key": "{{ text }}" + } + } + } + } + } + }, + { + "name": "bandwidth", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+bandwidth + \s+(?P<bw>\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_bw, + "compval": "address_family.bandwidth", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "bandwidth": "{{ bw }}" + } + } + } + }, + { + "name": "cost", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+cost + \s+(?P<val>\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_cost, + "compval": "address_family.cost", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "cost": "{{ val }}" + } + } + } + }, + { + "name": "hello_interval", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+hello-interval + \s+(?P<val>\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_hello_interval, + "compval": "address_family.hello_interval", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "hello_interval": "{{ val }}" + } + } + } + }, + { + "name": "dead_interval", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+dead-interval + \s+(?P<val>\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_dead_interval, + "compval": "address_family.dead_interval", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "dead_interval": "{{ val }}" + } + } + } + }, + { + "name": "mtu_ignore", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+(?P<mtu>\'mtu-ignore\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_mtu_ignore, + "compval": "address_family.mtu_ignore", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "mtu_ignore": "{{ True if mtu is defined }}" + } + } + } + }, + { + "name": "network", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+network + \s+(?P<val>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_network, + "compval": "address_family.network", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "network": "{{ val }}" + } + } + } + }, + { + "name": "priority", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+priority + \s+(?P<val>\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_priority, + "compval": "address_family.priority", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "priority": "{{ val }}" + } + } + } + }, + { + "name": "retransmit_interval", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+retransmit-interval + \s+(?P<val>\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_retransmit_interval, + "compval": "address_family.retransmit_interval", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "retransmit_interval": "{{ val }}" + } + } + } + }, + { + "name": "transmit_delay", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+transmit-delay + \s+(?P<val>\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_transmit_delay, + "compval": "address_family.transmit_delay", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "transmit_delay": "{{ val }}" + } + } + } + }, + { + "name": "ifmtu", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+ifmtu + \s+(?P<val>\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_ifmtu, + "compval": "address_family.ifmtu", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "ifmtu": "{{ val }}" + } + } + } + }, + { + "name": "instance", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+instance-id + \s+(?P<val>\'\d+\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_instance, + "compval": "address_family.instance", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "instance": "{{ val }}" + } + } + } + }, + { + "name": "passive", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + \s+(?P<pass>\'passive\') + *$""", + re.VERBOSE, + ), + "setval": _tmplt_ospf_int_passive, + "compval": "address_family.passive", + "result": { + "name": "{{ name }}", + "address_family": { + '{{ "ipv4" if proto == "ospf" else "ipv6" }}': { + "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}', + "passive": "{{ True if pass is defined }}" + } + } + } + }, + { + "name": "interface_name", + "getval": re.compile( + r""" + ^set + \s+protocols + \s+(?P<proto>ospf|ospfv3) + \s+interface + \s+(?P<name>\S+) + .*$""", + re.VERBOSE, + ), + "setval": "set protocols {{ proto }} interface {{ name }}", + "result": { + "name": "{{ name }}", + } + }, + ] + # fmt: on diff --git a/plugins/module_utils/network/vyos/utils/version.py b/plugins/module_utils/network/vyos/utils/version.py new file mode 100644 index 00000000..cc3028c3 --- /dev/null +++ b/plugins/module_utils/network/vyos/utils/version.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +"""Provide version object to compare version numbers.""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.compat.version import LooseVersion # pylint: disable=unused-import diff --git a/plugins/module_utils/network/vyos/vyos.py b/plugins/module_utils/network/vyos/vyos.py index 4fcb3317..1430b1b1 100644 --- a/plugins/module_utils/network/vyos/vyos.py +++ b/plugins/module_utils/network/vyos/vyos.py @@ -34,7 +34,6 @@ import json from ansible.module_utils._text import to_text from ansible.module_utils.connection import Connection, ConnectionError - _DEVICE_CONFIGS = {} @@ -100,3 +99,10 @@ def load_config(module, commands, commit=False, comment=None): module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) return response.get("diff") + + +def get_os_version(module): + connection = get_connection(module) + if connection.get_device_info(): + os_version = connection.get_device_info()["network_os_major_version"] + return os_version diff --git a/plugins/modules/vyos_firewall_global.py b/plugins/modules/vyos_firewall_global.py index 205ef136..befe5e73 100644 --- a/plugins/modules/vyos_firewall_global.py +++ b/plugins/modules/vyos_firewall_global.py @@ -253,6 +253,19 @@ options: description: - Enable logging of packets part of an established connection. type: bool + log_level: + description: + - Only available in 1.4+ + type: str + choices: + - emerg + - alert + - crit + - err + - warn + - notice + - info + - debug running_config: description: - The module, by default, will connect to the remote device and retrieve the current diff --git a/plugins/modules/vyos_firewall_rules.py b/plugins/modules/vyos_firewall_rules.py index 06a300f5..fd2e7d55 100644 --- a/plugins/modules/vyos_firewall_rules.py +++ b/plugins/modules/vyos_firewall_rules.py @@ -31,6 +31,11 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network' +} DOCUMENTATION = """ module: vyos_firewall_rules @@ -62,9 +67,16 @@ options: type: list elements: dict suboptions: + filter: + description: + - Filter type (exclusive to "name"). + - Supported in 1.4 and later. + type: str + choices: ['input', 'output', 'forward'] name: description: - Firewall rule set name. + - Required for 1.3- and optional for 1.4+. type: str default_action: description: @@ -72,11 +84,15 @@ options: - drop (Drop if no prior rules are hit (default)) - reject (Drop and notify source if no prior rules are hit) - accept (Accept if no prior rules are hit) + - jump (Jump to another rule-set, 1.4+) + type: str + choices: ['drop', 'reject', 'accept', 'jump'] + default_jump_target: + description: + - Default jump target if the default action is jump. + - Only valid in 1.4 and later. + - Only valid when default_action = jump. type: str - choices: - - drop - - reject - - accept description: description: - Rule set description. @@ -103,12 +119,19 @@ options: action: description: - Specifying the action. + - inspect is available < 1.4 + - continue, return, jump, queue, synproxy are available >= 1.4 type: str choices: - drop - reject - accept - inspect + - continue + - return + - jump + - queue + - synproxy destination: description: - Specifying the destination parameters. @@ -148,6 +171,7 @@ options: disable: description: - Option to disable firewall rule. + - aliased to disabled type: bool aliases: ["disabled"] fragment: @@ -215,6 +239,21 @@ options: description: - ICMP type. type: int + inbound_interface: + description: + - Inbound interface. + - Only valid in 1.4 and later. + type: dict + suboptions: + name: + description: + - Interface name. + - Can have wildcards + type: str + group: + description: + - Interface group. + type: str ipsec: description: - Inbound ip sec packets. @@ -222,13 +261,16 @@ options: choices: - match-ipsec - match-none - log: + - match-ipsec-in + - match-ipsec-out + - match-none-in + - match-none-out + jump_target: description: - - Option to log packets matching rule + - Jump target if the action is jump. + - Only valid in 1.4 and later. + - Only valid when action = jump. type: str - choices: - - disable - - enable limit: description: - Rate limit using a token bucket filter. @@ -255,6 +297,55 @@ options: description: - This is the time unit. type: str + log: + description: + - Log matching packets. + type: str + choices: ['disable', 'enable'] + outbound_interface: + description: + - Match outbound interface. + - Only valid in 1.4 and later. + type: dict + suboptions: + name: + description: + - Interface name. + - Can have wildcards + type: str + group: + description: + - Interface group. + type: str + packet_length: + description: + - Packet length match. + - Only valid in 1.4 and later. + - Multiple values from 1 to 65535 and ranges are supported + type: list + elements: dict + suboptions: + length: + description: + - Packet length or range. + type: str + packet_length_exclude: + description: + - Packet length match. + - Only valid in 1.4 and later. + - Multiple values from 1 to 65535 and ranges are supported + type: list + elements: dict + suboptions: + length: + description: + - Packet length or range. + type: str + packet_type: + description: + - Packet type match. + type: str + choices: ['broadcast', 'multicast', 'host', 'other'] p2p: description: - P2P application packets. @@ -283,6 +374,20 @@ options: - all All IP protocols. - (!)All IP protocols except for the specified name or number. type: str + queue: + description: + - Queue options. + - Only valid in 1.4 and later. + - Only valid when action = queue. + - Can be a queue number or range. + type: str + queue_options: + description: + - Queue options. + - Only valid in 1.4 and later. + - Only valid when action = queue. + type: str + choices: ['bypass', 'fanout'] recent: description: - Parameters for matching recently seen sources. @@ -295,7 +400,8 @@ options: time: description: - Source addresses seen in the last N seconds. - type: int + - Since 1.4, this is a string of second/minute/hour + type: str source: description: - Source parameters. @@ -337,6 +443,12 @@ options: - <MAC address> MAC address to match. - <!MAC address> Match everything except the specified MAC address. type: str + fqdn: + description: + - Fully qualified domain name. + - Available in 1.4 and later. + type: str + state: description: - Session state. @@ -358,6 +470,21 @@ options: description: - Related state. type: bool + synproxy: + description: + - SYN proxy options. + - Only valid in 1.4 and later. + - Only valid when action = synproxy. + type: dict + suboptions: + mss: + description: + - Adjust MSS (501-65535) + type: int + window_scale: + description: + - Window scale (1-14). + type: int tcp: description: - TCP flags to match. @@ -365,8 +492,22 @@ options: suboptions: flags: description: - - TCP flags to be matched. - type: str + - list of tcp flags to be matched + - 5.0 breaking change to support 1.4+ and 1.3- + type: list + elements: dict + suboptions: + flag: + description: + - TCP flag to be matched. + - syn, ack, fin, rst, urg, psh, all (1.3-) + - syn, ack, fin, rst, urg, psh, cwr, ecn (1.4+) + type: str + choices: ['ack', 'cwr', 'ecn', 'fin', 'psh', 'rst', 'syn', 'urg', 'all'] + invert: + description: + - Invert the match. + type: bool time: description: - Time to match rule. @@ -1460,14 +1601,14 @@ RETURN = """ before: description: The configuration prior to the model invocation. returned: always - type: list + type: dict sample: > The configuration returned will always be in the same format of the parameters above. after: description: The resulting configuration model invocation. returned: when changed - type: list + type: dict sample: > The configuration returned will always be in the same format of the parameters above. @@ -1486,17 +1627,14 @@ commands: from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import ( - Firewall_rulesArgs, -) -from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules import ( - Firewall_rules, -) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import Firewall_rulesArgs +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules import Firewall_rules def main(): """ Main entry point for module execution + :returns: the result form module invocation """ required_if = [ @@ -1518,5 +1656,5 @@ def main(): module.exit_json(**result) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/plugins/modules/vyos_ospf_interfaces.py b/plugins/modules/vyos_ospf_interfaces.py index c2326895..33290581 100644 --- a/plugins/modules/vyos_ospf_interfaces.py +++ b/plugins/modules/vyos_ospf_interfaces.py @@ -901,7 +901,7 @@ def main(): argument_spec=Ospf_interfacesArgs.argument_spec, mutually_exclusive=[], required_if=[], - supports_check_mode=False, + supports_check_mode=True, ) result = Ospf_interfaces(module).execute_module() |