From e76630c4e4039ccf4b1ff1d0846962038bd25a41 Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 17 Feb 2020 08:08:08 -0500 Subject: [PATCH] VyOS: firewall_rules module added (#65680) * firewall_rules module added Signed-off-by: rohitthakur2590 * state fact gathering code Signed-off-by: rohitthakur2590 * facts and operation code added Signed-off-by: rohitthakur2590 * unit tests added Signed-off-by: rohitthakur2590 * sanity fixes Signed-off-by: rohitthakur2590 * sanity fixes Signed-off-by: rohitthakur2590 * sanity fixes Signed-off-by: rohitthakur2590 * delete opr updated Signed-off-by: rohitthakur2590 --- .../vyos/argspec/firewall_rules/__init__.py | 0 .../argspec/firewall_rules/firewall_rules.py | 318 ++++ .../vyos/config/firewall_rules/__init__.py | 0 .../config/firewall_rules/firewall_rules.py | 706 ++++++++ .../module_utils/network/vyos/facts/facts.py | 4 +- .../vyos/facts/firewall_rules/__init__.py | 0 .../facts/firewall_rules/firewall_rules.py | 348 ++++ .../modules/network/vyos/vyos_facts.py | 2 +- .../network/vyos/vyos_firewall_rules.py | 1505 +++++++++++++++++ .../vyos_firewall_rules/defaults/main.yaml | 3 + .../vyos_firewall_rules/meta/main.yaml | 3 + .../vyos_firewall_rules/tasks/cli.yaml | 19 + .../vyos_firewall_rules/tasks/main.yaml | 2 + .../tests/cli/_parsed_config.cfg | 25 + .../tests/cli/_populate.yaml | 31 + .../tests/cli/_remove_config.yaml | 8 + .../tests/cli/deleted.yaml | 50 + .../tests/cli/deleted_afi.yaml | 46 + .../tests/cli/deleted_all.yaml | 44 + .../tests/cli/deleted_rule.yaml | 49 + .../tests/cli/empty_config.yaml | 58 + .../tests/cli/gathered.yaml | 31 + .../vyos_firewall_rules/tests/cli/merged.yaml | 87 + .../tests/cli/overridden.yaml | 60 + .../vyos_firewall_rules/tests/cli/parsed.yaml | 39 + .../tests/cli/rendered.yaml | 63 + .../tests/cli/replaced.yaml | 65 + .../vyos_firewall_rules/tests/cli/rtt.yaml | 87 + .../vyos_firewall_rules/vars/main.yaml | 327 ++++ .../fixtures/vyos_firewall_rules_config.cfg | 13 + .../network/vyos/test_vyos_firewall_rules.py | 827 +++++++++ 31 files changed, 4818 insertions(+), 2 deletions(-) create mode 100644 lib/ansible/module_utils/network/vyos/argspec/firewall_rules/__init__.py create mode 100644 lib/ansible/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py create mode 100644 lib/ansible/module_utils/network/vyos/config/firewall_rules/__init__.py create mode 100644 lib/ansible/module_utils/network/vyos/config/firewall_rules/firewall_rules.py create mode 100644 lib/ansible/module_utils/network/vyos/facts/firewall_rules/__init__.py create mode 100644 lib/ansible/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py create mode 100644 lib/ansible/modules/network/vyos/vyos_firewall_rules.py create mode 100644 test/integration/targets/vyos_firewall_rules/defaults/main.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/meta/main.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tasks/cli.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tasks/main.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/_parsed_config.cfg create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/_populate.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/_remove_config.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/deleted.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/deleted_afi.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/deleted_all.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/deleted_rule.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/empty_config.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/gathered.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/merged.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/overridden.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/parsed.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/rendered.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/replaced.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/tests/cli/rtt.yaml create mode 100644 test/integration/targets/vyos_firewall_rules/vars/main.yaml create mode 100644 test/units/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg create mode 100644 test/units/modules/network/vyos/test_vyos_firewall_rules.py diff --git a/lib/ansible/module_utils/network/vyos/argspec/firewall_rules/__init__.py b/lib/ansible/module_utils/network/vyos/argspec/firewall_rules/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py b/lib/ansible/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py new file mode 100644 index 00000000000..fed2eede07f --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py @@ -0,0 +1,318 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# 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_rules module +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class Firewall_rulesArgs(object): # pylint: disable=R0903 + """The arg spec for the vyos_firewall_rules module + """ + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'afi': { + 'choices': ['ipv4', 'ipv6'], + 'required': True, + 'type': 'str' + }, + 'rule_sets': { + 'elements': 'dict', + 'options': { + 'default_action': { + 'choices': ['drop', 'reject', 'accept'], + 'type': 'str' + }, + 'description': { + 'type': 'str' + }, + 'enable_default_log': { + 'type': 'bool' + }, + 'name': { + 'type': 'str' + }, + 'rules': { + 'elements': 'dict', + 'options': { + 'action': { + 'choices': + ['drop', 'reject', 'accept', 'inspect'], + 'type': + 'str' + }, + 'description': { + 'type': 'str' + }, + 'destination': { + 'options': { + 'address': { + 'type': 'str' + }, + 'group': { + 'options': { + 'address_group': { + 'type': 'str' + }, + 'network_group': { + 'type': 'str' + }, + 'port_group': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'port': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'disabled': { + 'type': 'bool' + }, + 'fragment': { + 'choices': + ['match-frag', 'match-non-frag'], + 'type': 'str' + }, + 'icmp': { + 'options': { + 'code': { + 'type': 'int' + }, + 'type': { + 'type': 'int' + }, + 'type_name': { + 'choices': [ + 'any', 'echo-reply', + 'destination-unreachable', + 'network-unreachable', + 'host-unreachable', + 'protocol-unreachable', + 'port-unreachable', + 'fragmentation-needed', + 'source-route-failed', + 'network-unknown', + 'host-unknown', + 'network-prohibited', + 'host-prohibited', + 'TOS-network-unreachable', + 'TOS-host-unreachable', + 'communication-prohibited', + 'host-precedence-violation', + 'precedence-cutoff', + 'source-quench', 'redirect', + 'network-redirect', + 'host-redirect', + 'TOS-network-redirect', + 'TOS-host-redirect', + 'echo-request', + 'router-advertisement', + 'router-solicitation', + 'time-exceeded', + 'ttl-zero-during-transit', + 'ttl-zero-during-reassembly', + 'parameter-problem', + 'ip-header-bad', + 'required-option-missing', + 'timestamp-request', + 'timestamp-reply', + 'address-mask-request', + 'address-mask-reply', 'ping', + 'pong', 'ttl-exceeded' + ], + 'type': + 'str' + } + }, + 'type': 'dict' + }, + 'ipsec': { + 'choices': ['match-ipsec', 'match-none'], + 'type': 'str' + }, + 'limit': { + 'options': { + 'burst': { + 'type': 'int' + }, + 'rate': { + 'options': { + 'number': { + 'type': 'int' + }, + 'unit': { + 'type': 'str' + } + }, + 'type': 'dict' + } + }, + 'type': 'dict' + }, + 'number': { + 'required': True, + 'type': 'int' + }, + 'p2p': { + 'elements': 'dict', + 'options': { + 'application': { + 'choices': [ + 'all', 'applejuice', + 'bittorrent', 'directconnect', + 'edonkey', 'gnutella', 'kazaa' + ], + 'type': + 'str' + } + }, + 'type': 'list' + }, + 'protocol': { + 'type': 'str' + }, + 'recent': { + 'options': { + 'count': { + 'type': 'int' + }, + 'time': { + 'type': 'int' + } + }, + 'type': 'dict' + }, + 'source': { + 'options': { + 'address': { + 'type': 'str' + }, + 'group': { + 'options': { + 'address_group': { + 'type': 'str' + }, + 'network_group': { + 'type': 'str' + }, + 'port_group': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'mac_address': { + 'type': 'str' + }, + 'port': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'state': { + 'options': { + 'established': { + 'type': 'bool' + }, + 'invalid': { + 'type': 'bool' + }, + 'new': { + 'type': 'bool' + }, + 'related': { + 'type': 'bool' + } + }, + 'type': 'dict' + }, + 'tcp': { + 'options': { + 'flags': { + 'type': 'str' + } + }, + 'type': 'dict' + }, + 'time': { + 'options': { + 'monthdays': { + 'type': 'str' + }, + 'startdate': { + 'type': 'str' + }, + 'starttime': { + 'type': 'str' + }, + 'stopdate': { + 'type': 'str' + }, + 'stoptime': { + 'type': 'str' + }, + 'utc': { + 'type': 'bool' + }, + 'weekdays': { + 'type': 'str' + } + }, + 'type': 'dict' + } + }, + 'type': 'list' + } + }, + 'type': 'list' + } + }, + 'type': 'list' + }, + 'running_config': { + 'type': 'str' + }, + 'state': { + 'choices': [ + 'merged', 'replaced', 'overridden', 'deleted', 'gathered', + 'rendered', 'parsed' + ], + 'default': + 'merged', + 'type': + 'str' + } + } # pylint: disable=C0301 diff --git a/lib/ansible/module_utils/network/vyos/config/firewall_rules/__init__.py b/lib/ansible/module_utils/network/vyos/config/firewall_rules/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/config/firewall_rules/firewall_rules.py b/lib/ansible/module_utils/network/vyos/config/firewall_rules/firewall_rules.py new file mode 100644 index 00000000000..973846cfbc5 --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/config/firewall_rules/firewall_rules.py @@ -0,0 +1,706 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos_firewall_rules class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from copy import deepcopy +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list, dict_diff, remove_empties +from ansible.module_utils.network.vyos.facts.facts import Facts +from ansible.module_utils.six import iteritems +from ansible.module_utils.network.vyos.utils.utils import list_diff_want_only + + +class Firewall_rules(ConfigBase): + """ + The vyos_firewall_rules class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'firewall_rules', + ] + + def __init__(self, module): + super(Firewall_rules, self).__init__(module) + + def get_firewall_rules_facts(self, data=None): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources, data=data) + firewall_rules_facts = facts['ansible_network_resources'].get('firewall_rules') + if not firewall_rules_facts: + return [] + return firewall_rules_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + commands = list() + + if self.state in self.ACTION_STATES: + existing_firewall_rules_facts = self.get_firewall_rules_facts() + else: + existing_firewall_rules_facts = [] + + if self.state in self.ACTION_STATES or self.state == 'rendered': + commands.extend(self.set_config(existing_firewall_rules_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result['changed'] = True + + if self.state in self.ACTION_STATES: + result['commands'] = commands + + if self.state in self.ACTION_STATES or self.state == 'gathered': + changed_firewall_rules_facts = self.get_firewall_rules_facts() + elif self.state == 'rendered': + result['rendered'] = commands + elif self.state == 'parsed': + running_config = self._module.params['running_config'] + if not running_config: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed" + ) + result['parsed'] = self.get_firewall_rules_facts(data=running_config) + else: + changed_firewall_rules_facts = [] + + if self.state in self.ACTION_STATES: + result['before'] = existing_firewall_rules_facts + if result['changed']: + result['after'] = changed_firewall_rules_facts + elif self.state == 'gathered': + result['gathered'] = changed_firewall_rules_facts + + result['warnings'] = warnings + return result + + def set_config(self, existing_firewall_rules_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + want = self._module.params['config'] + have = existing_firewall_rules_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, w, h): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + if self.state in ('merged', 'replaced', 'overridden', 'rendered') and not w: + self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(self.state)) + if self.state == 'overridden': + commands.extend(self._state_overridden(w, h)) + elif self.state == 'deleted': + commands.extend(self._state_deleted(w, h)) + elif w: + if self.state == 'merged' or self.state == 'rendered': + commands.extend(self._state_merged(w, h)) + elif self.state == 'replaced': + commands.extend(self._state_replaced(w, h)) + return commands + + def _state_replaced(self, want, have): + """ The command generator when state is replaced + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + 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') + commands.extend(self._add_r_sets(h['afi'], rs, w, opr=False)) + commands.extend(self._state_merged(want, have)) + return commands + + def _state_overridden(self, want, have): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + 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') + if not w: + commands.append(self._compute_command(h['afi'], rs['name'], remove=True)) + else: + commands.extend(self._add_r_sets(h['afi'], rs, w, opr=False)) + commands.extend(self._state_merged(want, have)) + return commands + + def _state_merged(self, want, have): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + 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') + commands.extend(self._add_r_sets(w['afi'], rs, h)) + return commands + + def _state_deleted(self, want, have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + if want: + for w in want: + 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') + if h: + w_rules = rs.get('rules') or [] + h_rules = h.get('rules') or [] + if w_rules and h_rules: + for rule in w_rules: + if self.search_r_sets_in_have(h_rules, rule['number'], 'rules'): + commands.append(self._add_r_base_attrib(w['afi'], rs['name'], 'number', rule, opr=False)) + else: + commands.append(self._compute_command(w['afi'], h['name'], remove=True)) + elif have: + for h in have: + if h['afi'] == w['afi']: + commands.append(self._compute_command(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)) + return commands + + def _add_r_sets(self, afi, want, have, opr=True): + """ + This function forms the set/delete commands based on the 'opr' type + for rule-sets attributes. + :param afi: address type. + :param want: desired config. + :param have: target config. + :param opr: True/False. + :return: generated commands list. + """ + commands = [] + l_set = ('description', + 'default_action', + 'enable_default_log') + h_rs = {} + h_rules = {} + w_rs = deepcopy(remove_empties(want)) + w_rules = w_rs.pop('rules', None) + if have: + h_rs = deepcopy(remove_empties(have)) + h_rules = h_rs.pop('rules', None) + if w_rs: + for key, val in iteritems(w_rs): + 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)) + else: + commands.append(self._add_rs_base_attrib(afi, want['name'], key, w_rs)) + elif not opr and key in l_set: + if key == 'enable_default_log' and val 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)) + 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)) + 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): + """ + This function forms the set/delete commands based on the 'opr' type + for rules attributes. + :param want: desired config. + :param have: target config. + :return: generated commands list. + """ + commands = [] + l_set = ('ipsec', + 'action', + 'number', + 'protocol', + 'fragment', + 'disabled', + 'description') + 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') + 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 == 'disabled': + 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)) + else: + commands.append(self._add_r_base_attrib(afi, name, key, w)) + elif not opr: + if key == 'number' and self._is_del(l_set, h): + commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr)) + continue + elif key == 'disabled' 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)) + 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)) + elif key == 'p2p': + commands.extend(self._add_p2p(key, w, h, cmd, opr)) + elif key == 'tcp': + commands.extend(self._add_tcp(key, w, h, cmd, opr)) + elif key == 'time': + commands.extend(self._add_time(key, w, h, cmd, opr)) + elif key == 'icmp': + commands.extend(self._add_icmp(key, w, h, cmd, opr)) + elif key == 'state': + commands.extend(self._add_state(key, w, h, cmd, opr)) + elif key == 'limit': + commands.extend(self._add_limit(key, w, h, cmd, opr)) + elif key == 'recent': + 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)) + return commands + + def _add_p2p(self, attr, w, h, cmd, opr): + """ + This function forms the set/delete commands based on the 'opr' type + for p2p applications attributes. + :param want: desired config. + :param have: target config. + :return: generated commands list. + """ + commands = [] + have = [] + if w: + want = w.get(attr) or [] + if h: + have = h.get(attr) or [] + if want: + if opr: + applications = list_diff_want_only(want, have) + for app in applications: + commands.append(cmd + (' ' + attr + ' ' + app['application'])) + elif not opr and have: + applications = list_diff_want_only(want, have) + for app in applications: + commands.append(cmd + (' ' + attr + ' ' + app['application'])) + return commands + + def _add_state(self, attr, w, h, cmd, opr): + """ + This function forms the command for 'state' 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. + """ + h_state = {} + commands = [] + l_set = ('new', + 'invalid', + 'related', + 'established') + if w[attr]: + if h and attr in h.keys(): + h_state = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if opr 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)): + commands.append(cmd + (' ' + attr + ' ' + item)) + return commands + + def _add_recent(self, attr, w, h, cmd, opr): + """ + This function forms the command for 'recent' 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_recent = {} + l_set = ('count', 'time') + if w[attr]: + if h and attr in h.keys(): + h_recent = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if opr and item in l_set and not (h_recent and self._is_w_same(w[attr], h_recent, item)): + commands.append(cmd + (' ' + attr + ' ' + item + ' ' + str(val))) + elif not opr and item in l_set and not (h_recent and self._in_target(h_recent, item)): + commands.append(cmd + (' ' + attr + ' ' + item)) + return commands + + def _add_icmp(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'icmp' 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_icmp = {} + l_set = ('code', 'type', 'type_name') + if w[attr]: + if h and attr in h.keys(): + h_icmp = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if opr and item in l_set and not (h_icmp and self._is_w_same(w[attr], h_icmp, item)): + if item == 'type_name': + if 'ipv6-name' in cmd: + commands.append(cmd + (' ' + 'icmpv6' + ' ' + 'type' + ' ' + 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)) + return commands + + def _add_time(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'time' 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_time = {} + l_set = ('utc', + 'stopdate', + 'stoptime', + 'weekdays', + 'monthdays', + 'startdate', + 'starttime') + if w[attr]: + if h and attr in h.keys(): + h_time = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if opr and item in l_set and not (h_time and self._is_w_same(w[attr], h_time, item)): + if item == 'utc': + if not (not val and (not h_time or item not in h_time)): + commands.append(cmd + (' ' + attr + ' ' + item)) + else: + commands.append(cmd + (' ' + attr + ' ' + item + ' ' + val)) + elif not opr and item in l_set and not (h_time and self._is_w_same(w[attr], h_time, item)): + commands.append(cmd + (' ' + attr + ' ' + item)) + return commands + + def _add_tcp(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'tcp' 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. + """ + h_tcp = {} + commands = [] + if w[attr]: + key = 'flags' + flags = w[attr].get(key) or {} + if flags: + 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)) + return commands + + def _add_limit(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'limit' 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. + """ + h_limit = {} + commands = [] + if w[attr]: + key = 'burst' + if opr and key in w[attr].keys() and not (h and attr in h.keys() and self._is_w_same(w[attr], h[attr], key)): + commands.append(cmd + (' ' + attr + ' ' + key + ' ' + str(w[attr].get(key)))) + elif not opr and key in w[attr].keys() and not (h and attr in h.keys() and self._in_target(h[attr], key)): + commands.append(cmd + (' ' + attr + ' ' + key + ' ' + str(w[attr].get(key)))) + key = 'rate' + rate = w[attr].get(key) or {} + if rate: + if h and key in h[attr].keys(): + h_limit = h[attr].get(key) or {} + if 'unit' in rate and 'number' in rate: + if opr and not (h_limit and self._is_w_same(rate, h_limit, 'unit') and self.is_w_same(rate, h_limit, 'number')): + commands.append(cmd + (' ' + attr + ' ' + key + ' ' + str(rate['number']) + '/' + rate['unit'])) + if not opr and not (h_limit and self._is_w_same(rate, h_limit, 'unit') and self._is_w_same(rate, h_limit, 'number')): + commands.append(cmd + (' ' + attr + ' ' + key)) + return commands + + def _add_src_or_dest(self, attr, w, h, cmd, opr=True): + """ + This function forms the commands for 'src/dest' 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_group = {} + g_set = ('port_group', + 'address_group', + 'network_group') + if w[attr]: + keys = ('address', 'mac_address', 'port') + for key in keys: + if opr and key in w[attr].keys() and not (h and attr in h.keys() and self._is_w_same(w[attr], h[attr], key)): + commands.append(cmd + (' ' + attr + ' ' + key.replace("_", "-") + ' ' + w[attr].get(key))) + elif not opr and key in w[attr].keys() and not (h and attr in h.keys() and self._in_target(h[attr], key)): + commands.append(cmd + (' ' + attr + ' ' + key)) + + key = 'group' + group = w[attr].get(key) or {} + if group: + if h and key in h[attr].keys(): + h_group = h[attr].get(key) or {} + for item, val in iteritems(group): + if val: + if opr and item in g_set and not (h_group and self._is_w_same(group, h_group, item)): + commands.append(cmd + (' ' + attr + ' ' + key + ' ' + item.replace("_", "-") + ' ' + val)) + elif not opr and item in g_set and not (h_group and self._in_target(h_group, item)): + commands.append(cmd + (' ' + attr + ' ' + key + ' ' + item.replace("_", "-"))) + return commands + + def search_r_sets_in_have(self, have, w_name, 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. + :return: rule-set/rule. + """ + if have: + key = 'name' + if type == 'rules': + key = 'number' + for r in have: + if r[key] == w_name: + return r + elif type == 'r_list': + for h in have: + r_sets = self._get_r_sets(h) + for rs in r_sets: + if rs[key] == w_name: + return rs + else: + for rs in have: + if rs[key] == w_name: + return rs + return None + + def _get_r_sets(self, item, type='rule_sets'): + """ + This function returns the list of rule-sets/rules. + :param item: config dictionary. + :param type: rule_sets/rule/r_list. + :return: list of rule-sets/rules. + """ + rs_list = [] + r_sets = item[type] + if r_sets: + for rs in r_sets: + rs_list.append(rs) + return rs_list + + def _compute_command(self, afi, name=None, number=None, attrib=None, value=None, remove=False, opr=True): + """ + This function construct the add/delete command based on passed attributes. + :param afi: address type. + :param name: rule-set name. + :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. + :return: generated command. + """ + if remove or not opr: + cmd = 'delete firewall ' + self._get_fw_type(afi) + else: + cmd = 'set firewall ' + self._get_fw_type(afi) + if name: + cmd += (' ' + name) + if number: + cmd += (' rule ' + str(number)) + if attrib: + cmd += (' ' + attrib.replace("_", "-")) + if value and opr and attrib != 'enable_default_log' and attrib != 'disabled': + cmd += (" '" + str(value) + "'") + return cmd + + def _add_r_base_attrib(self, afi, name, 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 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 + ) + else: + command = self._compute_command( + afi=afi, name=name, number=rule['number'], attrib=attr, value=rule[attr], opr=opr + ) + return command + + def _add_rs_base_attrib(self, afi, name, attrib, rule, opr=True): + """ + + This function forms the command for 'rule-sets' attributes which doesn't + have further sub attributes. + :param afi: address type. + :param name: rule-set name + :param attrib: attribute name + :param rule: rule config dictionary. + :param opr: True/False. + :return: generated command. + """ + command = self._compute_command(afi=afi, name=name, attrib=attrib, value=rule[attrib], opr=opr) + return command + + def _bool_to_str(self, val): + """ + This function converts the bool value into string. + :param val: bool value. + :return: enable/disable. + """ + return 'enable' if val else 'disable' + + def _get_fw_type(self, afi): + """ + This function returns the firewall rule-set type based on IP address. + :param afi: address type + :return: rule-set type. + """ + return 'ipv6-name' if afi == 'ipv6' else 'name' + + def _is_del(self, l_set, h, key='number'): + """ + This function checks whether rule needs to be deleted based on + the rule number. + :param l_set: attribute set. + :param h: target config. + :param key: number. + :return: True/False. + """ + return key in l_set and not (h and self._in_target(h, key)) + + def _is_w_same(self, w, h, key): + """ + This function checks whether the key value is same in base and + target config dictionary. + :param w: base config. + :param h: target config. + :param key:attribute name. + :return: True/False. + """ + return True if h and key in h and h[key] == w[key] else False + + def _in_target(self, h, key): + """ + This function checks whether the target nexist 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', + 'action', + 'fragment', + 'protocol', + 'disabled', + 'description', + 'mac_address', + 'default_action', + 'enable_default_log') + return True if key in r_set else False diff --git a/lib/ansible/module_utils/network/vyos/facts/facts.py b/lib/ansible/module_utils/network/vyos/facts/facts.py index c215b31733c..7c12166ee09 100644 --- a/lib/ansible/module_utils/network/vyos/facts/facts.py +++ b/lib/ansible/module_utils/network/vyos/facts/facts.py @@ -14,6 +14,7 @@ from ansible.module_utils.network.vyos.facts.l3_interfaces.l3_interfaces import from ansible.module_utils.network.vyos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts from ansible.module_utils.network.vyos.facts.lldp_global.lldp_global import Lldp_globalFacts from ansible.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts +from ansible.module_utils.network.vyos.facts.firewall_rules.firewall_rules import Firewall_rulesFacts from ansible.module_utils.network.vyos.facts.legacy.base import Default, Neighbors, Config @@ -27,7 +28,8 @@ FACT_RESOURCE_SUBSETS = dict( l3_interfaces=L3_interfacesFacts, lag_interfaces=Lag_interfacesFacts, lldp_global=Lldp_globalFacts, - lldp_interfaces=Lldp_interfacesFacts + lldp_interfaces=Lldp_interfacesFacts, + firewall_rules=Firewall_rulesFacts ) diff --git a/lib/ansible/module_utils/network/vyos/facts/firewall_rules/__init__.py b/lib/ansible/module_utils/network/vyos/facts/firewall_rules/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py b/lib/ansible/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py new file mode 100644 index 00000000000..e1af714cf06 --- /dev/null +++ b/lib/ansible/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py @@ -0,0 +1,348 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos firewall_rules fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from re import findall, search, M +from copy import deepcopy +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import Firewall_rulesArgs + + +class Firewall_rulesFacts(object): + """ The vyos firewall_rules fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Firewall_rulesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def get_device_data(self, connection): + return connection.get_config() + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for firewall_rules + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + # typically data is populated from the current device configuration + # data = connection.get('show running-config | section ^interface') + # using mock data instead + 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) + if v6_rules: + 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 config: + config = utils.remove_empties(config) + objs.append(config) + + ansible_facts['ansible_network_resources'].pop('firewall_rules', None) + facts = {} + if objs: + facts['firewall_rules'] = [] + params = utils.validate_config(self.argument_spec, {'config': objs}) + for cfg in params['config']: + facts['firewall_rules'].append(utils.remove_empties(cfg)) + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def get_rules(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. + :param data: configuration. + :param rules: list of rule-sets. + :param type: ip address type. + :return: generated rule-sets configuration. + """ + r_v4 = [] + r_v6 = [] + for r in set(rules): + rule_regex = r' %s .+$' % r.strip("'") + cfg = findall(rule_regex, data, M) + fr = self.render_config(cfg, r.strip("'")) + fr['name'] = r.strip("'") + 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 + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + conf = '\n'.join(filter(lambda x: x, conf)) + a_lst = ['description', 'default_action', 'enable_default_log'] + config = self.parse_attr(conf, a_lst, match) + if not config: + config = {} + config['rules'] = self.parse_rules_lst(conf) + return config + + def parse_rules_lst(self, conf): + """ + This function forms the regex to fetch the 'rules' with in + 'rule-sets' + :param conf: configuration data. + :return: generated rule list configuration. + """ + r_lst = [] + rules = findall(r'rule (?:\'*)(\d+)(?:\'*)', conf, M) + if rules: + rules_lst = [] + for r in set(rules): + r_regex = r' %s .+$' % r + cfg = '\n'.join(findall(r_regex, conf, M)) + obj = self.parse_rules(cfg) + obj['number'] = int(r) + if obj: + rules_lst.append(obj) + r_lst = sorted(rules_lst, key=lambda i: i['number']) + return r_lst + + def parse_rules(self, conf): + """ + This function triggers the parsing of 'rule' attributes. + a_lst is a list having rule attributes which doesn't + have further sub attributes. + :param conf: configuration + :return: generated rule configuration dictionary. + """ + a_lst = ['ipsec', 'action', 'protocol', 'fragment', 'disabled', 'description'] + rule = self.parse_attr(conf, a_lst) + r_sub = {'p2p': self.parse_p2p(conf), + 'tcp': self.parse_tcp(conf, 'tcp'), + 'icmp': self.parse_icmp(conf, 'icmp'), + 'time': self.parse_time(conf, 'time'), + 'limit': self.parse_limit(conf, 'limit'), + 'state': self.parse_state(conf, 'state'), + 'recent': self.parse_recent(conf, 'recent'), + 'source': self.parse_src_or_dest(conf, 'source'), + 'destination': self.parse_src_or_dest(conf, 'destination')} + rule.update(r_sub) + return rule + + def parse_p2p(self, conf): + """ + This function forms the regex to fetch the 'p2p' with in + 'rules' + :param conf: configuration data. + :return: generated rule list configuration. + """ + a_lst = [] + applications = findall(r'p2p (?:\'*)(\d+)(?:\'*)', conf, M) + if applications: + app_lst = [] + for r in set(applications): + obj = {'application': r.strip("'")} + app_lst.append(obj) + a_lst = sorted(app_lst, key=lambda i: i['application']) + return a_lst + + def parse_src_or_dest(self, conf, attrib=None): + """ + This function triggers the parsing of 'source or + destination' attributes. + :param conf: configuration. + :param attrib:'source/destination'. + :return:generated source/destination configuration dictionary. + """ + a_lst = ['port', 'address', 'mac_address'] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + cfg_dict['group'] = self.parse_group(conf, attrib + ' group') + return cfg_dict + + def parse_recent(self, conf, attrib=None): + """ + This function triggers the parsing of 'recent' attributes + :param conf: configuration. + :param attrib: 'recent'. + :return: generated config dictionary. + """ + a_lst = ['time', 'count'] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_tcp(self, conf, attrib=None): + """ + 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 + + def parse_time(self, conf, attrib=None): + """ + This function triggers the parsing of 'time' attributes. + :param conf: configuration. + :param attrib: 'time'. + :return: generated config dictionary. + """ + a_lst = ['stopdate', 'stoptime', 'weekdays', 'monthdays', 'startdate', 'starttime'] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_state(self, conf, attrib=None): + """ + This function triggers the parsing of 'state' attributes. + :param conf: configuration + :param attrib: 'state'. + :return: generated config dictionary. + """ + a_lst = ['new', 'invalid', 'related', 'established'] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_group(self, conf, attrib=None): + """ + This function triggers the parsing of 'group' attributes. + :param conf: configuration. + :param attrib: 'group'. + :return: generated config dictionary. + """ + a_lst = ['port_group', 'address_group', 'network_group'] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_icmp(self, conf, attrib=None): + """ + This function triggers the parsing of 'icmp' attributes. + :param conf: configuration to be parsed. + :param attrib: 'icmp'. + :return: generated config dictionary. + """ + a_lst = ['code', 'type', 'type_name'] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_limit(self, conf, attrib=None): + """ + This function triggers the parsing of 'limit' attributes. + :param conf: configuration to be parsed. + :param attrib: 'limit' + :return: generated config dictionary. + """ + cfg_dict = self.parse_attr(conf, ['burst'], match=attrib) + cfg_dict['rate'] = self.parse_rate(conf, 'rate') + return cfg_dict + + def parse_rate(self, conf, attrib=None): + """ + This function triggers the parsing of 'rate' attributes. + :param conf: configuration. + :param attrib: 'rate' + :return: generated config dictionary. + """ + a_lst = ['unit', 'number'] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_attr(self, conf, attr_list, match=None): + """ + This function peforms the following: + - Form the regex to fetch the required attribute config. + - Type cast the output in desired format. + :param conf: configuration. + :param attr_list: list of attributes. + :param match: parent node/attribute name. + :return: generated config dictionary. + """ + config = {} + for attrib in attr_list: + regex = self.map_regex(attrib) + if match: + regex = match + ' ' + regex + if conf: + if self.is_bool(attrib): + out = conf.find(attrib.replace("_", "-")) + + dis = conf.find(attrib.replace("_", "-") + " 'disable'") + if out >= 1: + if dis >= 1: + config[attrib] = False + else: + config[attrib] = True + else: + out = search(r'^.*' + regex + ' (.+)', conf, M) + if out: + val = out.group(1).strip("'") + if self.is_num(attrib): + val = int(val) + config[attrib] = val + return config + + def map_regex(self, attrib): + """ + - This function construct the regex string. + - replace the underscore with hyphen. + :param attrib: attribute + :return: regex string + """ + regex = attrib.replace("_", "-") + if attrib == 'disabled': + regex = 'disable' + return regex + + def is_bool(self, attrib): + """ + This function looks for the attribute in predefined bool type set. + :param attrib: attribute. + :return: True/False + """ + bool_set = ('new', 'invalid', 'related', 'disabled', 'established', 'enable_default_log') + return True if attrib in bool_set else False + + def is_num(self, attrib): + """ + This function looks for the attribute in predefined integer type set. + :param attrib: attribute. + :return: True/false. + """ + num_set = ('time', 'code', 'type', 'count', 'burst', 'number') + return True if attrib in num_set else False diff --git a/lib/ansible/modules/network/vyos/vyos_facts.py b/lib/ansible/modules/network/vyos/vyos_facts.py index 5f5dde8b9b2..cdd71781eb1 100644 --- a/lib/ansible/modules/network/vyos/vyos_facts.py +++ b/lib/ansible/modules/network/vyos/vyos_facts.py @@ -52,7 +52,7 @@ options: can also be used with an initial C(M(!)) to specify that a specific subset should not be collected. Valid subsets are 'all', 'interfaces', 'l3_interfaces', 'lag_interfaces', - 'lldp_global', 'lldp_interfaces'. + 'lldp_global', 'lldp_interfaces', 'firewall_rules'. required: false version_added: "2.9" """ diff --git a/lib/ansible/modules/network/vyos/vyos_firewall_rules.py b/lib/ansible/modules/network/vyos/vyos_firewall_rules.py new file mode 100644 index 00000000000..59ab876aad7 --- /dev/null +++ b/lib/ansible/modules/network/vyos/vyos_firewall_rules.py @@ -0,0 +1,1505 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# 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 module file for vyos_firewall_rules +""" + +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 +version_added: '2.10' +short_description: Manage firewall rule-set attributes on VyOS devices +description: This module manages firewall rule-set attributes on VyOS devices +notes: + - Tested against VyOS 1.1.8 (helium). + - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). +author: + - Rohit Thakur (@rohitthakur2590) +options: + config: + description: A dictionary of Firewall rule-set options. + type: list + elements: dict + suboptions: + afi: + description: + - Specifies the type of rule-set. + type: str + choices: ['ipv4', 'ipv6'] + required: True + rule_sets: + description: + - The Firewall rule-set list. + type: list + elements: dict + suboptions: + name: + description: + - Firewall rule set name. + type: str + default_action: + description: + - Default action for rule-set. + - 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) + type: str + choices: ['drop', 'reject', 'accept'] + description: + description: + - Rule set description. + type: str + enable_default_log: + description: + - Option to log packets hitting default-action. + type: bool + rules: + description: + - A ditionary that specifies the rule-set configurations. + type: list + elements: dict + suboptions: + number: + description: + - Rule number. + type: int + required: True + description: + description: + - Description of this rule. + type: str + action: + description: + - Specifying the action. + type: str + choices: ['drop', 'reject', 'accept', 'inspect'] + destination: + description: + - Specifying the destination parameters. + type: dict + suboptions: + address: + description: + - Destination ip address subnet or range. + - IPv4/6 address, subnet or range to match. + - Match everything except the specified address, subnet or range. + - Destination ip address subnet or range. + type: str + group: + description: + - Destination group. + type: dict + suboptions: + address_group: + description: + - Group of addresses. + type: str + network_group: + description: + - Group of networks. + type: str + port_group: + description: + - Group of ports. + type: str + port: + description: + - Multiple destination ports can be specified as a comma-separated list. + - The whole list can also be "negated" using '!'. + - For example:'!22,telnet,http,123,1001-1005'. + type: str + disabled: + description: + - Option to disable firewall rule. + type: bool + fragment: + description: + - IP fragment match. + type: str + choices: ['match-frag', 'match-non-frag'] + icmp: + description: + - ICMP type and code information. + type: dict + suboptions: + type_name: + description: + - ICMP type-name. + type: str + choices: ['any', 'echo-reply', 'destination-unreachable', 'network-unreachable', + 'host-unreachable', 'protocol-unreachable', 'port-unreachable', 'fragmentation-needed', + 'source-route-failed', 'network-unknown', 'host-unknown', 'network-prohibited', + 'host-prohibited', 'TOS-network-unreachable', 'TOS-host-unreachable', + 'communication-prohibited', 'host-precedence-violation', + 'precedence-cutoff', 'source-quench', 'redirect', 'network-redirect', 'host-redirect', + 'TOS-network-redirect', 'TOS-host-redirect', 'echo-request', 'router-advertisement', + 'router-solicitation', 'time-exceeded', 'ttl-zero-during-transit', + 'ttl-zero-during-reassembly', 'parameter-problem', 'ip-header-bad', + 'required-option-missing', 'timestamp-request', 'timestamp-reply', 'address-mask-request', + 'address-mask-reply', 'ping', 'pong', 'ttl-exceeded'] + code: + description: + - ICMP code. + type: int + type: + description: + - ICMP type. + type: int + ipsec: + description: + - Inboud ip sec packets. + type: str + choices: ['match-ipsec', 'match-none'] + limit: + description: + - Rate limit using a token bucket filter. + type: dict + suboptions: + burst: + description: + - Maximum number of packets to allow in excess of rate. + type: int + rate: + description: + - format for rate (integer/time unit). + - any one of second, minute, hour or day may be used to specify time unit. + - eg. 1/second implies rule to be matched at an average of once per second. + type: dict + suboptions: + number: + description: + - This is the integer value. + type: int + unit: + description: + - This is the time unit. + type: str + p2p: + description: + - P2P application packets. + type: list + elements: dict + suboptions: + application: + description: + - Name of the application. + type: str + choices: ['all', 'applejuice', 'bittorrent', 'directconnect', 'edonkey', 'gnutella', 'kazaa'] + protocol: + description: + - Protocol to match (protocol name in /etc/protocols or protocol number or all). + - IP protocol name from /etc/protocols (e.g. "tcp" or "udp"). + - <0-255> IP protocol number. + - tcp_udp Both TCP and UDP. + - all All IP protocols. + - (!)All IP protocols except for the specified name or number. + type: str + recent: + description: + - Parameters for matching recently seen sources. + type: dict + suboptions: + count: + description: + - Source addresses seen more than N times. + type: int + time: + description: + - Source addresses seen in the last N seconds. + type: int + source: + description: + - Source parameters. + type: dict + suboptions: + address: + description: + - Source ip address subnet or range. + - IPv4/6 address, subnet or range to match. + - Match everything except the specified address, subnet or range. + - Source ip address subnet or range. + type: str + group: + description: + - Source group. + type: dict + suboptions: + address_group: + description: + - Group of addresses. + type: str + network_group: + description: + - Group of networks. + type: str + port_group: + description: + - Group of ports. + type: str + port: + description: + - Multiple source ports can be specified as a comma-separated list. + - The whole list can also be "negated" using '!'. + - For example:'!22,telnet,http,123,1001-1005'. + type: str + mac_address: + description: + - MAC address to match. + - Match everything except the specified MAC address. + type: str + state: + description: + - Session state. + type: dict + suboptions: + established: + description: + - Established state. + type: bool + invalid: + description: + - Invalid state. + type: bool + new: + description: + - New state. + type: bool + related: + description: + - Related state. + type: bool + tcp: + description: + - TCP flags to match. + type: dict + suboptions: + flags: + description: + - TCP flags to be matched. + type: str + time: + description: + - Time to match rule. + type: dict + suboptions: + utc: + description: + - Interpret times for startdate, stopdate, starttime and stoptime to be UTC. + type: bool + monthdays: + description: + - Monthdays to match rule on. + type: str + startdate: + description: + - Date to start matching rule. + type: str + starttime: + description: + - Time of day to start matching rule. + type: str + stopdate: + description: + - Date to stop matching rule. + type: str + stoptime: + description: + - Time of day to stop matching rule. + type: str + weekdays: + description: + - Weekdays to match rule on. + type: str + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(running_config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. This value of this option should be the + output received from device by executing command + C(show configuration commands | grep 'firewall' + type: str + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged +""" +EXAMPLES = """ +# Using deleted to delete firewall rules based on rule-set name +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' +# +- name: Delete attributes of given firewall rules. + vyos_firewall_rules: + config: + - afi: ipv4 + rule_sets: + - name: 'Downlink' + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete firewall name Downlink" +# ] +# +# "after": [] +# After state +# ------------ +# vyos@vyos# run show configuration commands | grep firewall +# set firewall group address-group 'inbound' + + +# Using deleted to delete all the the firewall rules when provided config is empty +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' +# +- name: Delete attributes of given firewall rules. + vyos_firewall_rules: + config: + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete firewall name" +# ] +# +# "after": [] +# After state +# ------------ +# vyos@vyos# run show configuration commands | grep firewall +# set firewall group address-group 'inbound' + + +# Using deleted to delete the the firewall rules based on afi +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' +# +- name: Delete attributes of given firewall rules. + vyos_firewall_rules: + config: + - afi: ipv4 + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete firewall name", +# ] +# +# "after": [] +# After state +# ------------ +# vyos@vyos# run show configuration commands | grep firewall +# set firewall group address-group 'inbound' + + + +# Using deleted to delete the the firewall rules based on rule number/id +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' +# +- name: Delete attributes of given firewall rules. + vyos_firewall_rules: + config: + - afi: ipv4 + rule_sets: + - name: 'Downlink' + rules: + - number: 501 + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete firewall ipv6-name Downlink rule 501" +# ] +# +# "after": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# After state +# ------------ +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' + + +# Using merged +# +# Before state: +# ------------- +# +# vyos@vyos# run show configuration commands | grep firewall +# set firewall group address-group 'inbound' +# +- name: Merge the provided configuration with the exisiting running configuration + vyos_firewall_rules: + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + rules: + - number: 1 + action: 'accept' + description: 'Fwipv6-Rule 1 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 2 + action: 'accept' + description: 'Fwipv6-Rule 2 is configured by Ansible' + ipsec: 'match-ipsec' + + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + destination: + group: + address_group: 'inbound' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + state: merged +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": [] +# +# "commands": [ +# "set firewall ipv6-name UPLINK default-action 'accept'", +# "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'", +# "set firewall ipv6-name UPLINK rule 1 action 'accept'", +# "set firewall ipv6-name UPLINK rule 1", +# "set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible'", +# "set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec'", +# "set firewall ipv6-name UPLINK rule 2 action 'accept'", +# "set firewall ipv6-name UPLINK rule 2", +# "set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible'", +# "set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec'", +# "set firewall name INBOUND default-action 'accept'", +# "set firewall name INBOUND description 'IPv4 INBOUND rule set'", +# "set firewall name INBOUND rule 101 action 'accept'", +# "set firewall name INBOUND rule 101", +# "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", +# "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", +# "set firewall name INBOUND rule 102 action 'reject'", +# "set firewall name INBOUND rule 102", +# "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", +# "set firewall name INBOUND rule 102 ipsec 'match-ipsec'", +# "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'", +# "set firewall name INBOUND rule 103 destination group address-group inbound", +# "set firewall name INBOUND rule 103", +# "set firewall name INBOUND rule 103 source address 192.0.2.0", +# "set firewall name INBOUND rule 103 state established enable", +# "set firewall name INBOUND rule 103 state related enable", +# "set firewall name INBOUND rule 103 state invalid disable", +# "set firewall name INBOUND rule 103 state new disable", +# "set firewall name INBOUND rule 103 action 'accept'" +# ] +# +# "after": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK", +# "rules": [ +# { +# "action": "accept", +# "description": "Fwipv6-Rule 1 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 1 +# }, +# { +# "action": "accept", +# "description": "Fwipv6-Rule 2 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 2 +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 102 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 102 +# }, +# { +# "action": "accept", +# "description": "Rule 103 is configured by Ansible", +# "destination": { +# "group": { +# "address_group": "inbound" +# } +# }, +# "number": 103, +# "source": { +# "address": "192.0.2.0" +# }, +# "state": { +# "established": true, +# "invalid": false, +# "new": false, +# "related": true +# } +# } +# ] +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall ipv6-name UPLINK rule 1 action 'accept' +# set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +# set firewall ipv6-name UPLINK rule 2 action 'accept' +# set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 102 action 'reject' +# set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +# set firewall name INBOUND rule 102 ipsec 'match-ipsec' +# set firewall name INBOUND rule 103 action 'accept' +# set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +# set firewall name INBOUND rule 103 destination group address-group 'inbound' +# set firewall name INBOUND rule 103 source address '192.0.2.0' +# set firewall name INBOUND rule 103 state established 'enable' +# set firewall name INBOUND rule 103 state invalid 'disable' +# set firewall name INBOUND rule 103 state new 'disable' +# set firewall name INBOUND rule 103 state related 'enable' + + +# Using replaced +# +# Before state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall ipv6-name UPLINK rule 1 action 'accept' +# set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +# set firewall ipv6-name UPLINK rule 2 action 'accept' +# set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 102 action 'reject' +# set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +# set firewall name INBOUND rule 102 ipsec 'match-ipsec' +# set firewall name INBOUND rule 103 action 'accept' +# set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +# set firewall name INBOUND rule 103 destination group address-group 'inbound' +# set firewall name INBOUND rule 103 source address '192.0.2.0' +# set firewall name INBOUND rule 103 state established 'enable' +# set firewall name INBOUND rule 103 state invalid 'disable' +# set firewall name INBOUND rule 103 state new 'disable' +# set firewall name INBOUND rule 103 state related 'enable' +# +- name: Replace device configurations of listed firewall rules with provided configurations + vyos_firewall_rules: + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 104 + action: 'reject' + description: 'Rule 104 is configured by Ansible' + ipsec: 'match-none' + state: replaced +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "before": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK", +# "rules": [ +# { +# "action": "accept", +# "description": "Fwipv6-Rule 1 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 1 +# }, +# { +# "action": "accept", +# "description": "Fwipv6-Rule 2 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 2 +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 102 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 102 +# }, +# { +# "action": "accept", +# "description": "Rule 103 is configured by Ansible", +# "destination": { +# "group": { +# "address_group": "inbound" +# } +# }, +# "number": 103, +# "source": { +# "address": "192.0.2.0" +# }, +# "state": { +# "established": true, +# "invalid": false, +# "new": false, +# "related": true +# } +# } +# ] +# } +# ] +# } +# ] +# +# "commands": [ +# "delete firewall ipv6-name UPLINK rule 1", +# "delete firewall ipv6-name UPLINK rule 2", +# "delete firewall name INBOUND rule 102", +# "delete firewall name INBOUND rule 103", +# "set firewall name INBOUND rule 104 action 'reject'", +# "set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible'", +# "set firewall name INBOUND rule 104", +# "set firewall name INBOUND rule 104 ipsec 'match-none'" +# ] +# +# "after": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK" +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 104 is configured by Ansible", +# "ipsec": "match-none", +# "number": 104 +# } +# ] +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 104 action 'reject' +# set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible' +# set firewall name INBOUND rule 104 ipsec 'match-none' + + +# Using overridden +# +# Before state +# -------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 104 action 'reject' +# set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible' +# set firewall name INBOUND rule 104 ipsec 'match-none' +# +- name: Overrides all device configuration with provided configuration + vyos_firewall_rules: + config: + - afi: 'ipv4' + rule_sets: + - name: 'Downlink' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 501 + action: 'accept' + description: 'Rule 501 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 502 + action: 'reject' + description: 'Rule 502 is configured by Ansible' + ipsec: 'match-ipsec' + state: overridden +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "before": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK" +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 104 is configured by Ansible", +# "ipsec": "match-none", +# "number": 104 +# } +# ] +# } +# ] +# } +# ] +# +# "commands": [ +# "delete firewall ipv6-name UPLINK", +# "delete firewall name INBOUND", +# "set firewall name Downlink default-action 'accept'", +# "set firewall name Downlink description 'IPv4 INBOUND rule set'", +# "set firewall name Downlink rule 501 action 'accept'", +# "set firewall name Downlink rule 501", +# "set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible'", +# "set firewall name Downlink rule 501 ipsec 'match-ipsec'", +# "set firewall name Downlink rule 502 action 'reject'", +# "set firewall name Downlink rule 502", +# "set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible'", +# "set firewall name Downlink rule 502 ipsec 'match-ipsec'" +# +# +# "after": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# +# +# After state +# ------------ +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' + + +# Using gathered +# +# Before state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall ipv6-name UPLINK rule 1 action 'accept' +# set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +# set firewall ipv6-name UPLINK rule 2 action 'accept' +# set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 102 action 'reject' +# set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +# set firewall name INBOUND rule 102 ipsec 'match-ipsec' +# set firewall name INBOUND rule 103 action 'accept' +# set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +# set firewall name INBOUND rule 103 destination group address-group 'inbound' +# set firewall name INBOUND rule 103 source address '192.0.2.0' +# set firewall name INBOUND rule 103 state established 'enable' +# set firewall name INBOUND rule 103 state invalid 'disable' +# set firewall name INBOUND rule 103 state new 'disable' +# set firewall name INBOUND rule 103 state related 'enable' +# +- name: Gather listed firewall rules with provided configurations + vyos_firewall_rules: + config: + state: gathered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "gathered": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK", +# "rules": [ +# { +# "action": "accept", +# "description": "Fwipv6-Rule 1 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 1 +# }, +# { +# "action": "accept", +# "description": "Fwipv6-Rule 2 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 2 +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 102 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 102 +# }, +# { +# "action": "accept", +# "description": "Rule 103 is configured by Ansible", +# "destination": { +# "group": { +# "address_group": "inbound" +# } +# }, +# "number": 103, +# "source": { +# "address": "192.0.2.0" +# }, +# "state": { +# "established": true, +# "invalid": false, +# "new": false, +# "related": true +# } +# } +# ] +# } +# ] +# } +# ] +# +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall ipv6-name UPLINK rule 1 action 'accept' +# set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +# set firewall ipv6-name UPLINK rule 2 action 'accept' +# set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 102 action 'reject' +# set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +# set firewall name INBOUND rule 102 ipsec 'match-ipsec' +# set firewall name INBOUND rule 103 action 'accept' +# set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +# set firewall name INBOUND rule 103 destination group address-group 'inbound' +# set firewall name INBOUND rule 103 source address '192.0.2.0' +# set firewall name INBOUND rule 103 state established 'enable' +# set firewall name INBOUND rule 103 state invalid 'disable' +# set firewall name INBOUND rule 103 state new 'disable' +# set firewall name INBOUND rule 103 state related 'enable' + + +# Using rendered +# +# +- name: Render the commands for provided configuration + vyos_firewall_rules: + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + destination: + group: + address_group: 'inbound' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + state: rendered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "rendered": [ +# "set firewall ipv6-name UPLINK default-action 'accept'", +# "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'", +# "set firewall name INBOUND default-action 'accept'", +# "set firewall name INBOUND description 'IPv4 INBOUND rule set'", +# "set firewall name INBOUND rule 101 action 'accept'", +# "set firewall name INBOUND rule 101", +# "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", +# "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", +# "set firewall name INBOUND rule 102 action 'reject'", +# "set firewall name INBOUND rule 102", +# "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", +# "set firewall name INBOUND rule 102 ipsec 'match-ipsec'", +# "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'", +# "set firewall name INBOUND rule 103 destination group address-group inbound", +# "set firewall name INBOUND rule 103", +# "set firewall name INBOUND rule 103 source address 192.0.2.0", +# "set firewall name INBOUND rule 103 state established enable", +# "set firewall name INBOUND rule 103 state related enable", +# "set firewall name INBOUND rule 103 state invalid disable", +# "set firewall name INBOUND rule 103 state new disable", +# "set firewall name INBOUND rule 103 action 'accept'" +# ] + + +# Using parsed +# +# +- name: Render the commands for provided configuration + vyos_firewall_rules: + running_config: + "set firewall group address-group 'inbound' + set firewall name Downlink default-action 'accept' + set firewall name Downlink description 'IPv4 INBOUND rule set' + set firewall name Downlink rule 501 action 'accept' + set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' + set firewall name Downlink rule 501 ipsec 'match-ipsec' + set firewall name Downlink rule 502 action 'reject' + set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' + set firewall name Downlink rule 502 ipsec 'match-ipsec'" + state: parsed +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "parsed": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: list + 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 + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: + - "set firewall name Downlink default-action 'accept'" + - "set firewall name Downlink description 'IPv4 INBOUND rule set'" + - "set firewall name Downlink rule 501 action 'accept'" + - "set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible'" + - "set firewall name Downlink rule 502 ipsec 'match-ipsec'" +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import Firewall_rulesArgs +from ansible.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 = [('state', 'merged', ('config',)), + ('state', 'replaced', ('config',)), + ('state', 'overridden', ('config',)), + ('state', 'parsed', ('running_config',))] + mutually_exclusive = [('config', 'running_config')] + + module = AnsibleModule(argument_spec=Firewall_rulesArgs.argument_spec, + required_if=required_if, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive) + result = Firewall_rules(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/vyos_firewall_rules/defaults/main.yaml b/test/integration/targets/vyos_firewall_rules/defaults/main.yaml new file mode 100644 index 00000000000..164afead284 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/test/integration/targets/vyos_firewall_rules/meta/main.yaml b/test/integration/targets/vyos_firewall_rules/meta/main.yaml new file mode 100644 index 00000000000..7413320e481 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/meta/main.yaml @@ -0,0 +1,3 @@ +--- +dependencies: + - prepare_vyos_tests diff --git a/test/integration/targets/vyos_firewall_rules/tasks/cli.yaml b/test/integration/targets/vyos_firewall_rules/tasks/cli.yaml new file mode 100644 index 00000000000..655e51ee63d --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tasks/cli.yaml @@ -0,0 +1,19 @@ +--- +- name: Collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test case (connection=network_cli) + include: "{{ test_case_to_run }}" + vars: + ansible_connection: network_cli + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/vyos_firewall_rules/tasks/main.yaml b/test/integration/targets/vyos_firewall_rules/tasks/main.yaml new file mode 100644 index 00000000000..d4cf26fc4a1 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- {include: cli.yaml, tags: ['cli']} diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/_parsed_config.cfg b/test/integration/targets/vyos_firewall_rules/tests/cli/_parsed_config.cfg new file mode 100644 index 00000000000..b54c10948c5 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/_parsed_config.cfg @@ -0,0 +1,25 @@ +set firewall group address-group 'inbound' +set firewall ipv6-name UPLINK default-action 'accept' +set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +set firewall ipv6-name UPLINK rule 1 action 'accept' +set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +set firewall ipv6-name UPLINK rule 2 action 'accept' +set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +set firewall name INBOUND default-action 'accept' +set firewall name INBOUND description 'IPv4 INBOUND rule set' +set firewall name INBOUND rule 101 action 'accept' +set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +set firewall name INBOUND rule 101 ipsec 'match-ipsec' +set firewall name INBOUND rule 102 action 'reject' +set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +set firewall name INBOUND rule 102 ipsec 'match-ipsec' +set firewall name INBOUND rule 103 action 'accept' +set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +set firewall name INBOUND rule 103 destination group address-group 'inbound' +set firewall name INBOUND rule 103 source address '192.0.2.0' +set firewall name INBOUND rule 103 state established 'enable' +set firewall name INBOUND rule 103 state invalid 'disable' +set firewall name INBOUND rule 103 state new 'disable' +set firewall name INBOUND rule 103 state related 'enable' diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/_populate.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/_populate.yaml new file mode 100644 index 00000000000..ae6e17a1887 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/_populate.yaml @@ -0,0 +1,31 @@ +--- +- name: Setup + cli_config: + config: "{{ lines }}" + vars: + lines: | + set firewall group address-group 'inbound' + set firewall ipv6-name UPLINK default-action 'accept' + set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' + set firewall ipv6-name UPLINK rule 1 action 'accept' + set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' + set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' + set firewall ipv6-name UPLINK rule 2 action 'accept' + set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' + set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' + set firewall name INBOUND default-action 'accept' + set firewall name INBOUND description 'IPv4 INBOUND rule set' + set firewall name INBOUND rule 101 action 'accept' + set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' + set firewall name INBOUND rule 101 ipsec 'match-ipsec' + set firewall name INBOUND rule 102 action 'reject' + set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' + set firewall name INBOUND rule 102 ipsec 'match-ipsec' + set firewall name INBOUND rule 103 action 'accept' + set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' + set firewall name INBOUND rule 103 destination group address-group 'inbound' + set firewall name INBOUND rule 103 source address '192.0.2.0' + set firewall name INBOUND rule 103 state established 'enable' + set firewall name INBOUND rule 103 state invalid 'disable' + set firewall name INBOUND rule 103 state new 'disable' + set firewall name INBOUND rule 103 state related 'enable' diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/_remove_config.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/_remove_config.yaml new file mode 100644 index 00000000000..9a9aff91034 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/_remove_config.yaml @@ -0,0 +1,8 @@ +--- +- name: Remove Config + cli_config: + config: "{{ lines }}" + vars: + lines: | + delete firewall ipv6-name + delete firewall name diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/deleted.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/deleted.yaml new file mode 100644 index 00000000000..881c19349b6 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/deleted.yaml @@ -0,0 +1,50 @@ +--- +- debug: + msg: "Start vyos_firewall_rules deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _populate.yaml + +- block: + - name: Delete firewall rule set. + vyos_firewall_rules: &deleted_rs + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + state: deleted + register: result + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_rs['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_rs['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + vyos_firewall_rules: *deleted_rs + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result.changed == false" + - "result.commands|length == 0" + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_rs['after'] | symmetric_difference(result['before']) |length == 0 }}" + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/deleted_afi.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/deleted_afi.yaml new file mode 100644 index 00000000000..1502e86bf40 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/deleted_afi.yaml @@ -0,0 +1,46 @@ +--- +- debug: + msg: "Start vyos_firewall_rules deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _populate.yaml + +- block: + - name: Delete firewall rule. + vyos_firewall_rules: &deleted_afi + config: + - afi: 'ipv6' + - afi: 'ipv4' + state: deleted + register: result + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_afi_all['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + vyos_firewall_rules: *deleted_afi + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result.changed == false" + - "result.commands|length == 0" + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['before']) |length == 0 }}" + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/deleted_all.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/deleted_all.yaml new file mode 100644 index 00000000000..60c7a672841 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/deleted_all.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: "Start vyos_firewall_rules deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _populate.yaml + +- block: + - name: Delete all the firewall rules. + vyos_firewall_rules: &deleted_all + config: + state: deleted + register: result + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_afi_all['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + vyos_firewall_rules: *deleted_all + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result.changed == false" + - "result.commands|length == 0" + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['before']) |length == 0 }}" + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/deleted_rule.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/deleted_rule.yaml new file mode 100644 index 00000000000..7ecdcb380ce --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/deleted_rule.yaml @@ -0,0 +1,49 @@ +--- +- debug: + msg: "Start vyos_firewall_rules deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _populate.yaml + +- block: + - name: Delete firewall rule. + vyos_firewall_rules: &deleted_r + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + rules: + - number: 1 + state: deleted + register: result + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_r['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_r['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + vyos_firewall_rules: *deleted_r + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result.changed == false" + - "result.commands|length == 0" + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_r['after'] | symmetric_difference(result['before']) |length == 0 }}" + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/empty_config.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/empty_config.yaml new file mode 100644 index 00000000000..262b3059e5a --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/empty_config.yaml @@ -0,0 +1,58 @@ +--- +- debug: + msg: "START vyos_firewall_rules empty_config integration tests on connection={{ ansible_connection }}" + +- name: Merged with empty config should give appropriate error message + vyos_firewall_rules: + config: + state: merged + register: result + ignore_errors: true + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty config should give appropriate error message + vyos_firewall_rules: + config: + state: replaced + register: result + ignore_errors: true + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Overridden with empty config should give appropriate error message + vyos_firewall_rules: + config: + state: overridden + register: result + ignore_errors: true + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Parsed with empty running_config should give appropriate error message + vyos_firewall_rules: + running_config: + state: parsed + register: result + ignore_errors: true + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state parsed' + +- name: Rendered with empty config should give appropriate error message + vyos_firewall_rules: + config: + state: rendered + register: result + ignore_errors: true + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/gathered.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/gathered.yaml new file mode 100644 index 00000000000..5a0bac753bf --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/gathered.yaml @@ -0,0 +1,31 @@ +--- +- debug: + msg: "START vyos_firewall_rules gathered integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Merge the provided configuration with the exisiting running configuration + vyos_firewall_rules: &gathered + config: + state: gathered + register: result + + - name: Assert that gathered dicts was correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['gathered']) |length == 0 }}" + + - name: Gather the existing running configuration (IDEMPOTENT) + vyos_firewall_rules: *gathered + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/merged.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/merged.yaml new file mode 100644 index 00000000000..596f4a143d3 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/merged.yaml @@ -0,0 +1,87 @@ +--- +- debug: + msg: "START vyos_firewall_rules merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: _populate.yaml + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge the provided configuration with the exisiting running configuration + vyos_firewall_rules: &merged + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + rules: + - number: 1 + action: 'accept' + description: 'Fwipv6-Rule 1 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 2 + action: 'accept' + description: 'Fwipv6-Rule 2 is configured by Ansible' + ipsec: 'match-ipsec' + + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + destination: + group: + address_group: 'inbound' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + state: merged + register: result + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that after dicts was correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + vyos_firewall_rules: *merged + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/overridden.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/overridden.yaml new file mode 100644 index 00000000000..d902f4791ea --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/overridden.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: "START vyos_firewall_rules overridden integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Overrides all device configuration with provided configuration + vyos_firewall_rules: &overridden + config: + - afi: 'ipv4' + rule_sets: + - name: 'Downlink' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 501 + action: 'accept' + description: 'Rule 501 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 502 + action: 'reject' + description: 'Rule 502 is configured by Ansible' + ipsec: 'match-ipsec' + state: overridden + register: result + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that correct commands were generated + assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that after dicts were correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Overrides all device configuration with provided configurations (IDEMPOTENT) + vyos_firewall_rules: *overridden + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/parsed.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/parsed.yaml new file mode 100644 index 00000000000..7fb066da00c --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/parsed.yaml @@ -0,0 +1,39 @@ +--- +- debug: + msg: "START vyos_firewall_rules parsed integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Gather firewall_rules facts + vyos_facts: + gather_subset: + - default + gather_network_resources: + - firewall_rules + register: firewall_rules_facts + + - name: Provide the running configuration for parsing (config to be parsed) + vyos_firewall_rules: &parsed + running_config: + "{{ lookup('file', '_parsed_config.cfg') }}" + state: parsed + register: result + + - name: Assert that correct parsing done + assert: + that: "{{ ansible_facts['network_resources']['firewall_rules'] | symmetric_difference(result['parsed']) |length == 0 }}" + + - name: Gather the existing running configuration (IDEMPOTENT) + vyos_firewall_rules: *parsed + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/rendered.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/rendered.yaml new file mode 100644 index 00000000000..958550e2111 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/rendered.yaml @@ -0,0 +1,63 @@ +--- +- debug: + msg: "START vyos_firewall_rules rendered integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Structure provided configuration into device specific commands + vyos_firewall_rules: &rendered + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + destination: + group: + address_group: 'inbound' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + state: rendered + register: result + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ rendered['commands'] | symmetric_difference(result['rendered']) |length == 0 }}" + + - name: Structure provided configuration into device specific commands (IDEMPOTENT) + vyos_firewall_rules: *rendered + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/replaced.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/replaced.yaml new file mode 100644 index 00000000000..164ff18707c --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/replaced.yaml @@ -0,0 +1,65 @@ +--- +- debug: + msg: "START vyos_firewall_rules replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Replace device configurations of listed firewall rules with provided configurations + vyos_firewall_rules: &replaced + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 104 + action: 'reject' + description: 'Rule 104 is configured by Ansible' + ipsec: 'match-none' + state: replaced + register: result + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that before dicts are correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Replace device configurations of listed firewall rules with provided configurarions (IDEMPOTENT) + vyos_firewall_rules: *replaced + register: result + + - name: Assert that task was idempotent + assert: + that: + - "result['changed'] == false" + + - name: Assert that before dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/tests/cli/rtt.yaml b/test/integration/targets/vyos_firewall_rules/tests/cli/rtt.yaml new file mode 100644 index 00000000000..921defb9d6c --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/tests/cli/rtt.yaml @@ -0,0 +1,87 @@ +--- +- debug: + msg: "START vyos_firewall_rules round trip integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- block: + - name: Apply the provided configuration (base config) + vyos_firewall_rules: + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + rules: + - number: 1 + action: 'accept' + description: 'Fwipv6-Rule 1 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 2 + action: 'accept' + description: 'Fwipv6-Rule 2 is configured by Ansible' + ipsec: 'match-ipsec' + + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + state: merged + register: base_config + + - name: Gather firewall_rules facts + vyos_facts: + gather_subset: + - default + gather_network_resources: + - firewall_rules + + - name: Apply the provided configuration (config to be reverted) + vyos_firewall_rules: + config: + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + state: merged + register: result + + - name: Assert that changes were applied + assert: + that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Revert back to base config using facts round trip + vyos_firewall_rules: + config: "{{ ansible_facts['network_resources']['firewall_rules'] }}" + state: overridden + register: revert + + - name: Assert that config was reverted + assert: + that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/vyos_firewall_rules/vars/main.yaml b/test/integration/targets/vyos_firewall_rules/vars/main.yaml new file mode 100644 index 00000000000..64032cd3078 --- /dev/null +++ b/test/integration/targets/vyos_firewall_rules/vars/main.yaml @@ -0,0 +1,327 @@ +--- +merged: + before: [] + + + commands: + - "set firewall ipv6-name UPLINK default-action 'accept'" + - "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'" + - "set firewall ipv6-name UPLINK rule 1 action 'accept'" + - "set firewall ipv6-name UPLINK rule 1" + - "set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible'" + - "set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec'" + - "set firewall ipv6-name UPLINK rule 2 action 'accept'" + - "set firewall ipv6-name UPLINK rule 2" + - "set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible'" + - "set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec'" + - "set firewall name INBOUND default-action 'accept'" + - "set firewall name INBOUND description 'IPv4 INBOUND rule set'" + - "set firewall name INBOUND rule 101 action 'accept'" + - "set firewall name INBOUND rule 101" + - "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'" + - "set firewall name INBOUND rule 101 ipsec 'match-ipsec'" + - "set firewall name INBOUND rule 102 action 'reject'" + - "set firewall name INBOUND rule 102" + - "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'" + - "set firewall name INBOUND rule 102 ipsec 'match-ipsec'" + - "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'" + - "set firewall name INBOUND rule 103 destination group address-group inbound" + - "set firewall name INBOUND rule 103" + - "set firewall name INBOUND rule 103 source address 192.0.2.0" + - "set firewall name INBOUND rule 103 state established enable" + - "set firewall name INBOUND rule 103 state related enable" + - "set firewall name INBOUND rule 103 state invalid disable" + - "set firewall name INBOUND rule 103 state new disable" + - "set firewall name INBOUND rule 103 action 'accept'" + + after: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + rules: + - number: 1 + action: 'accept' + description: 'Fwipv6-Rule 1 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 2 + action: 'accept' + description: 'Fwipv6-Rule 2 is configured by Ansible' + ipsec: 'match-ipsec' + + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + destination: + group: + address_group: 'inbound' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + +populate: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + rules: + - number: 1 + action: 'accept' + description: 'Fwipv6-Rule 1 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 2 + action: 'accept' + description: 'Fwipv6-Rule 2 is configured by Ansible' + ipsec: 'match-ipsec' + + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + destination: + group: + address_group: 'inbound' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + +replaced: + commands: + - "delete firewall ipv6-name UPLINK rule 1" + - "delete firewall ipv6-name UPLINK rule 2" + - "delete firewall name INBOUND rule 102" + - "delete firewall name INBOUND rule 103" + - "set firewall name INBOUND rule 104 action 'reject'" + - "set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible'" + - "set firewall name INBOUND rule 104" + - "set firewall name INBOUND rule 104 ipsec 'match-none'" + + after: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 104 + action: 'reject' + description: 'Rule 104 is configured by Ansible' + ipsec: 'match-none' +overridden: + before: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 104 + action: 'reject' + description: 'Rule 104 is configured by Ansible' + ipsec: 'match-none' + commands: + - "delete firewall ipv6-name UPLINK" + - "delete firewall name INBOUND" + - "set firewall name Downlink default-action 'accept'" + - "set firewall name Downlink description 'IPv4 INBOUND rule set'" + - "set firewall name Downlink rule 501 action 'accept'" + - "set firewall name Downlink rule 501" + - "set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible'" + - "set firewall name Downlink rule 501 ipsec 'match-ipsec'" + - "set firewall name Downlink rule 502 action 'reject'" + - "set firewall name Downlink rule 502" + - "set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible'" + - "set firewall name Downlink rule 502 ipsec 'match-ipsec'" + + after: + - afi: 'ipv4' + rule_sets: + - name: 'Downlink' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 501 + action: 'accept' + description: 'Rule 501 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 502 + action: 'reject' + description: 'Rule 502 is configured by Ansible' + ipsec: 'match-ipsec' +rendered: + commands: + - "set firewall ipv6-name UPLINK default-action 'accept'" + - "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'" + - "set firewall name INBOUND default-action 'accept'" + - "set firewall name INBOUND description 'IPv4 INBOUND rule set'" + - "set firewall name INBOUND rule 101 action 'accept'" + - "set firewall name INBOUND rule 101" + - "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'" + - "set firewall name INBOUND rule 101 ipsec 'match-ipsec'" + - "set firewall name INBOUND rule 102 action 'reject'" + - "set firewall name INBOUND rule 102" + - "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'" + - "set firewall name INBOUND rule 102 ipsec 'match-ipsec'" + - "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'" + - "set firewall name INBOUND rule 103 destination group address-group inbound" + - "set firewall name INBOUND rule 103" + - "set firewall name INBOUND rule 103 source address 192.0.2.0" + - "set firewall name INBOUND rule 103 state established enable" + - "set firewall name INBOUND rule 103 state related enable" + - "set firewall name INBOUND rule 103 state invalid disable" + - "set firewall name INBOUND rule 103 state new disable" + - "set firewall name INBOUND rule 103 action 'accept'" + +deleted_rs: + commands: + - "delete firewall ipv6-name UPLINK" + - "delete firewall name INBOUND" + + after: [] + +deleted_afi_all: + commands: + - "delete firewall ipv6-name" + - "delete firewall name" + + after: [] + +deleted_r: + commands: + - "delete firewall ipv6-name UPLINK rule 1" + + after: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + rules: + - number: 2 + action: 'accept' + description: 'Fwipv6-Rule 2 is configured by Ansible' + ipsec: 'match-ipsec' + + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + destination: + group: + address_group: 'inbound' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + +round_trip: + after: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + rules: + - number: 1 + action: 'accept' + description: 'Fwipv6-Rule 1 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 2 + action: 'accept' + description: 'Fwipv6-Rule 2 is configured by Ansible' + ipsec: 'match-ipsec' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true diff --git a/test/units/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg b/test/units/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg new file mode 100644 index 00000000000..f65b386963c --- /dev/null +++ b/test/units/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg @@ -0,0 +1,13 @@ +set firewall name V4-INGRESS default-action 'accept' +set firewall ipv6-name V6-INGRESS default-action 'accept' +set firewall name V4-INGRESS description 'This is IPv4 V4-INGRESS rule set' +set firewall name V4-INGRESS enable-default-log +set firewall name V4-INGRESS rule 101 protocol 'icmp' +set firewall name V4-INGRESS rule 101 description 'Rule 101 is configured by Ansible' +set firewall name V4-INGRESS rule 101 fragment 'match-frag' +set firewall name V4-INGRESS rule 101 +set firewall name V4-INGRESS rule 101 disabled +set firewall name V4-INGRESS rule 101 action 'accept' +set firewall name V4-INGRESS rule 101 ipsec 'match-ipsec' +set firewall name V4-EGRESS default-action 'reject' +set firewall ipv6-name V6-EGRESS default-action 'reject' diff --git a/test/units/modules/network/vyos/test_vyos_firewall_rules.py b/test/units/modules/network/vyos/test_vyos_firewall_rules.py new file mode 100644 index 00000000000..ebf8d44a8e8 --- /dev/null +++ b/test/units/modules/network/vyos/test_vyos_firewall_rules.py @@ -0,0 +1,827 @@ +# (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 . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from units.compat.mock import patch, MagicMock +from ansible.modules.network.vyos import vyos_firewall_rules +from units.modules.utils import set_module_args +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosFirewallRulesModule(TestVyosModule): + + module = vyos_firewall_rules + + def setUp(self): + super(TestVyosFirewallRulesModule, self).setUp() + self.mock_get_config = patch( + 'ansible.module_utils.network.common.network.Config.get_config') + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + 'ansible.module_utils.network.common.network.Config.load_config') + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + 'ansible.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.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.module_utils.network.vyos.facts.static_routes.static_routes.Static_routesFacts.get_device_data' + ) + + self.mock_execute_show_command = patch( + 'ansible.module_utils.network.vyos.facts.firewall_rules.firewall_rules.Firewall_rulesFacts.get_device_data' + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestVyosFirewallRulesModule, 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() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + return load_fixture('vyos_firewall_rules_config.cfg') + + self.execute_show_command.side_effect = load_from_file + + def test_vyos_firewall_rule_set_01_merged(self): + set_module_args( + dict(config=[ + dict(afi='ipv6', + rule_sets=[ + dict(name='V6-INBOUND', + description='This is IPv6 INBOUND rule set', + default_action='reject', + enable_default_log=True, + rules=[]), + dict(name='V6-OUTBOUND', + description='This is IPv6 OUTBOUND rule set', + default_action='accept', + enable_default_log=False, + rules=[]) + ]), + dict(afi='ipv4', + rule_sets=[ + dict(name='V4-INBOUND', + description='This is IPv4 INBOUND rule set', + default_action='reject', + enable_default_log=True, + rules=[]), + dict(name='V4-OUTBOUND', + description='This is IPv4 OUTBOUND rule set', + default_action='accept', + enable_default_log=False, + rules=[]) + ]) + ], + state="merged")) + commands = [ + "set firewall ipv6-name V6-INBOUND default-action 'reject'", + "set firewall ipv6-name V6-INBOUND description 'This is IPv6 INBOUND rule set'", + 'set firewall ipv6-name V6-INBOUND enable-default-log', + "set firewall ipv6-name V6-OUTBOUND default-action 'accept'", + "set firewall ipv6-name V6-OUTBOUND description 'This is IPv6 OUTBOUND rule set'", + "set firewall name V4-INBOUND default-action 'reject'", + "set firewall name V4-INBOUND description 'This is IPv4 INBOUND rule set'", + 'set firewall name V4-INBOUND enable-default-log', + "set firewall name V4-OUTBOUND default-action 'accept'", + "set firewall name V4-OUTBOUND description 'This is IPv4 OUTBOUND rule set'" + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_rule_set_02_merged(self): + set_module_args( + dict(config=[ + dict(afi='ipv6', + rule_sets=[ + dict(name='V6-INBOUND', + description='This is IPv6 INBOUND rule set', + default_action='reject', + enable_default_log=True, + rules=[]), + dict(name='V6-OUTBOUND', + description='This is IPv6 OUTBOUND rule set', + default_action='accept', + enable_default_log=False, + rules=[]) + ]), + dict(afi='ipv4', + rule_sets=[ + dict(name='V4-INBOUND', + description='This is IPv4 INBOUND rule set', + default_action='reject', + enable_default_log=True, + rules=[]), + dict(name='V4-OUTBOUND', + description='This is IPv4 OUTBOUND rule set', + default_action='accept', + enable_default_log=False, + rules=[]) + ]) + ], + state="merged")) + commands = [ + "set firewall ipv6-name V6-INBOUND default-action 'reject'", + "set firewall ipv6-name V6-INBOUND description 'This is IPv6 INBOUND rule set'", + 'set firewall ipv6-name V6-INBOUND enable-default-log', + "set firewall ipv6-name V6-OUTBOUND default-action 'accept'", + "set firewall ipv6-name V6-OUTBOUND description 'This is IPv6 OUTBOUND rule set'", + "set firewall name V4-INBOUND default-action 'reject'", + "set firewall name V4-INBOUND description 'This is IPv4 INBOUND rule set'", + 'set firewall name V4-INBOUND enable-default-log', + "set firewall name V4-OUTBOUND default-action 'accept'", + "set firewall name V4-OUTBOUND description 'This is IPv4 OUTBOUND rule set'" + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='INBOUND', + description='This is IPv4 INBOUND rule set', + default_action='accept', + enable_default_log=True, + rules=[ + dict(number='101', + action='accept', + description='Rule 101 is configured by Ansible', + ipsec='match-ipsec', + protocol='icmp', + fragment='match-frag', + disabled=True) + ]), + ]) + ], + state="merged")) + commands = [ + "set firewall name INBOUND default-action 'accept'", + "set firewall name INBOUND description 'This is IPv4 INBOUND rule set'", + 'set firewall name INBOUND enable-default-log', + "set firewall name INBOUND rule 101 protocol 'icmp'", + "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", + "set firewall name INBOUND rule 101 fragment 'match-frag'", + 'set firewall name INBOUND rule 101', + 'set firewall name INBOUND rule 101 disabled', + "set firewall name INBOUND rule 101 action 'accept'", + "set firewall name INBOUND rule 101 ipsec 'match-ipsec'" + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_02(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='INBOUND', + rules=[ + dict(number='101', + protocol='tcp', + source=dict( + address='192.0.2.0', + mac_address='38:00:25:19:76:0c', + port=2127), + destination=dict(address='192.0.1.0', + port=2124), + limit=dict(burst=10, + rate=dict(number=20, + unit='second')), + recent=dict(count=10, time=20), + state=dict(established=True, + related=True, + invalid=True, + new=True)) + ]), + ]) + ], + state="merged")) + commands = [ + "set firewall name INBOUND rule 101 protocol 'tcp'", + 'set firewall name INBOUND rule 101 destination address 192.0.1.0', + 'set firewall name INBOUND rule 101 destination port 2124', + 'set firewall name INBOUND rule 101', + 'set firewall name INBOUND rule 101 source address 192.0.2.0', + 'set firewall name INBOUND rule 101 source mac-address 38:00:25:19:76:0c', + 'set firewall name INBOUND rule 101 source port 2127', + 'set firewall name INBOUND rule 101 state new enable', + 'set firewall name INBOUND rule 101 state invalid enable', + 'set firewall name INBOUND rule 101 state related enable', + 'set firewall name INBOUND rule 101 state established enable', + 'set firewall name INBOUND rule 101 limit burst 10', + 'set firewall name INBOUND rule 101 limit rate 20/second', + 'set firewall name INBOUND rule 101 recent count 10', + 'set firewall name INBOUND rule 101 recent time 20', + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_03(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='INBOUND', + rules=[ + dict(number='101', + destination=dict(group=dict( + address_group='OUT-ADDR-GROUP', + network_group='OUT-NET-GROUP', + port_group='OUT-PORT-GROUP')), + source=dict(group=dict( + address_group='IN-ADDR-GROUP', + network_group='IN-NET-GROUP', + port_group='IN-PORT-GROUP'))) + ]), + ]) + ], + state="merged")) + commands = [ + 'set firewall name INBOUND rule 101 source group address-group IN-ADDR-GROUP', + 'set firewall name INBOUND rule 101 source group network-group IN-NET-GROUP', + 'set firewall name INBOUND rule 101 source group port-group IN-PORT-GROUP', + 'set firewall name INBOUND rule 101 destination group address-group OUT-ADDR-GROUP', + 'set firewall name INBOUND rule 101 destination group network-group OUT-NET-GROUP', + 'set firewall name INBOUND rule 101 destination group port-group OUT-PORT-GROUP', + 'set firewall name INBOUND rule 101' + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_04(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='INBOUND', + rules=[ + dict(number='101', + time=dict(monthdays='2', + startdate='2020-01-24', + starttime='13:20:00', + stopdate='2020-01-28', + stoptime='13:30:00', + weekdays='!Sat,Sun', + utc=True), + tcp=dict(flags='ALL')) + ]), + ]) + ], + state="merged")) + commands = [ + 'set firewall name INBOUND rule 101', + 'set firewall name INBOUND rule 101 tcp flags ALL', + 'set firewall name INBOUND rule 101 time utc', + 'set firewall name INBOUND rule 101 time monthdays 2', + 'set firewall name INBOUND rule 101 time startdate 2020-01-24', + 'set firewall name INBOUND rule 101 time stopdate 2020-01-28', + 'set firewall name INBOUND rule 101 time weekdays !Sat,Sun', + 'set firewall name INBOUND rule 101 time stoptime 13:30:00', + 'set firewall name INBOUND rule 101 time starttime 13:20:00', + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv6', + rule_sets=[ + dict(name='INBOUND', + description='This is IPv6 INBOUND rule set', + default_action='accept', + enable_default_log=True, + rules=[ + dict(number='101', + action='accept', + description='Rule 101 is configured by Ansible', + ipsec='match-ipsec', + protocol='icmp', + disabled=True) + ]), + ]) + ], + state="merged")) + commands = [ + "set firewall ipv6-name INBOUND default-action 'accept'", + "set firewall ipv6-name INBOUND description 'This is IPv6 INBOUND rule set'", + 'set firewall ipv6-name INBOUND enable-default-log', + "set firewall ipv6-name INBOUND rule 101 protocol 'icmp'", + "set firewall ipv6-name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", + 'set firewall ipv6-name INBOUND rule 101', + 'set firewall ipv6-name INBOUND rule 101 disabled', + "set firewall ipv6-name INBOUND rule 101 action 'accept'", + "set firewall ipv6-name INBOUND rule 101 ipsec 'match-ipsec'" + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_02(self): + set_module_args( + dict(config=[ + dict(afi='ipv6', + rule_sets=[ + dict(name='INBOUND', + rules=[ + dict(number='101', + protocol='tcp', + source=dict( + address='2001:db8::12', + mac_address='38:00:25:19:76:0c', + port=2127), + destination=dict(address='2001:db8::11', + port=2124), + limit=dict(burst=10, + rate=dict(number=20, + unit='second')), + recent=dict(count=10, time=20), + state=dict(established=True, + related=True, + invalid=True, + new=True)) + ]), + ]) + ], + state="merged")) + commands = [ + "set firewall ipv6-name INBOUND rule 101 protocol 'tcp'", + 'set firewall ipv6-name INBOUND rule 101 destination address 2001:db8::11', + 'set firewall ipv6-name INBOUND rule 101 destination port 2124', + 'set firewall ipv6-name INBOUND rule 101', + 'set firewall ipv6-name INBOUND rule 101 source address 2001:db8::12', + 'set firewall ipv6-name INBOUND rule 101 source mac-address 38:00:25:19:76:0c', + 'set firewall ipv6-name INBOUND rule 101 source port 2127', + 'set firewall ipv6-name INBOUND rule 101 state new enable', + 'set firewall ipv6-name INBOUND rule 101 state invalid enable', + 'set firewall ipv6-name INBOUND rule 101 state related enable', + 'set firewall ipv6-name INBOUND rule 101 state established enable', + 'set firewall ipv6-name INBOUND rule 101 limit burst 10', + 'set firewall ipv6-name INBOUND rule 101 recent count 10', + 'set firewall ipv6-name INBOUND rule 101 recent time 20', + 'set firewall ipv6-name INBOUND rule 101 limit rate 20/second' + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_03(self): + set_module_args( + dict(config=[ + dict(afi='ipv6', + rule_sets=[ + dict(name='INBOUND', + rules=[ + dict(number='101', + destination=dict(group=dict( + address_group='OUT-ADDR-GROUP', + network_group='OUT-NET-GROUP', + port_group='OUT-PORT-GROUP')), + source=dict(group=dict( + address_group='IN-ADDR-GROUP', + network_group='IN-NET-GROUP', + port_group='IN-PORT-GROUP'))) + ]), + ]) + ], + state="merged")) + commands = [ + 'set firewall ipv6-name INBOUND rule 101 source group address-group IN-ADDR-GROUP', + 'set firewall ipv6-name INBOUND rule 101 source group network-group IN-NET-GROUP', + 'set firewall ipv6-name INBOUND rule 101 source group port-group IN-PORT-GROUP', + 'set firewall ipv6-name INBOUND rule 101 destination group address-group OUT-ADDR-GROUP', + 'set firewall ipv6-name INBOUND rule 101 destination group network-group OUT-NET-GROUP', + 'set firewall ipv6-name INBOUND rule 101 destination group port-group OUT-PORT-GROUP', + 'set firewall ipv6-name INBOUND rule 101' + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_04(self): + set_module_args( + dict(config=[ + dict(afi='ipv6', + rule_sets=[ + dict(name='INBOUND', + rules=[ + dict(number='101', + time=dict(monthdays='2', + startdate='2020-01-24', + starttime='13:20:00', + stopdate='2020-01-28', + stoptime='13:30:00', + weekdays='!Sat,Sun', + utc=True), + tcp=dict(flags='ALL')) + ]), + ]) + ], + state="merged")) + commands = [ + 'set firewall ipv6-name INBOUND rule 101', + 'set firewall ipv6-name INBOUND rule 101 tcp flags ALL', + 'set firewall ipv6-name INBOUND rule 101 time utc', + 'set firewall ipv6-name INBOUND rule 101 time monthdays 2', + 'set firewall ipv6-name INBOUND rule 101 time startdate 2020-01-24', + 'set firewall ipv6-name INBOUND rule 101 time stopdate 2020-01-28', + 'set firewall ipv6-name INBOUND rule 101 time weekdays !Sat,Sun', + 'set firewall ipv6-name INBOUND rule 101 time stoptime 13:30:00', + 'set firewall ipv6-name INBOUND rule 101 time starttime 13:20:00' + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_icmp_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv6', + rule_sets=[ + dict(name='INBOUND', + rules=[ + dict(number='101', + protocol='icmp', + icmp=dict(type_name='port-unreachable')) + ]), + ]) + ], + state="merged")) + commands = [ + 'set firewall ipv6-name INBOUND rule 101 icmpv6 type port-unreachable', + "set firewall ipv6-name INBOUND rule 101 protocol 'icmp'", + 'set firewall ipv6-name INBOUND rule 101' + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='INBOUND', + rules=[ + dict(number='101', + protocol='icmp', + icmp=dict(type=1, code=1)) + ]), + ]) + ], + state="merged")) + commands = [ + 'set firewall name INBOUND rule 101 icmp type 1', + 'set firewall name INBOUND rule 101 icmp code 1', + "set firewall name INBOUND rule 101 protocol 'icmp'", + 'set firewall name INBOUND rule 101' + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_02(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='INBOUND', + rules=[ + dict(number='101', + protocol='icmp', + icmp=dict(type_name='echo-request')) + ]), + ]) + ], + state="merged")) + commands = [ + 'set firewall name INBOUND rule 101 icmp type-name echo-request', + "set firewall name INBOUND rule 101 protocol 'icmp'", + 'set firewall name INBOUND rule 101' + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_del_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', rule_sets=[ + dict(name='V4-INGRESS'), + ]) + ], + state="deleted")) + commands = ['delete firewall name V4-INGRESS'] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_del_02(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', rule_sets=[ + dict(name='V4-INGRESS'), + ]), + dict(afi='ipv6', rule_sets=[ + dict(name='V6-INGRESS'), + ]) + ], + state="deleted")) + commands = [ + 'delete firewall name V4-INGRESS', + 'delete firewall ipv6-name V6-INGRESS' + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_del_03(self): + set_module_args(dict(config=[], state="deleted")) + commands = ['delete firewall name', 'delete firewall ipv6-name'] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_del_04(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', rule_sets=[ + dict(name='V4-ING'), + ]), + dict(afi='ipv6', rule_sets=[ + dict(name='V6-ING'), + ]) + ], + state="deleted")) + self.execute_module(changed=False, commands=[]) + + def test_vyos_firewall_v4v6_rule_sets_rule_rep_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='V4-INGRESS', + description='This is IPv4 INGRESS rule set', + default_action='accept', + enable_default_log=True, + rules=[ + dict(number='101', + action='reject', + description='Rule 101 is configured by Ansible RM', + ipsec='match-ipsec', + protocol='tcp', + fragment='match-frag', + disabled=False), + dict(number='102', + action='accept', + description='Rule 102 is configured by Ansible RM', + protocol='icmp', + disabled=True) + ]), + ]), + dict(afi='ipv6', + rule_sets=[ + dict(name='V6-INGRESS', + default_action='accept', + description='This rule-set is configured by Ansible RM'), + dict(name='V6-EGRESS', + default_action='reject', + description='This rule-set is configured by Ansible RM') + ]) + ], + state="replaced")) + commands = [ + 'delete firewall name V4-INGRESS rule 101 disabled', + 'delete firewall name V4-EGRESS default-action', + "set firewall name V4-INGRESS description 'This is IPv4 INGRESS rule set'", + "set firewall name V4-INGRESS rule 101 protocol 'tcp'", + "set firewall name V4-INGRESS rule 101 description 'Rule 101 is configured by Ansible RM'", + "set firewall name V4-INGRESS rule 101 action 'reject'", + 'set firewall name V4-INGRESS rule 102 disabled', + "set firewall name V4-INGRESS rule 102 action 'accept'", + "set firewall name V4-INGRESS rule 102 protocol 'icmp'", + "set firewall name V4-INGRESS rule 102 description 'Rule 102 is configured by Ansible RM'", + 'set firewall name V4-INGRESS rule 102', + "set firewall ipv6-name V6-INGRESS description 'This rule-set is configured by Ansible RM'", + "set firewall ipv6-name V6-EGRESS description 'This rule-set is configured by Ansible RM'" + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_rule_rep_02(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='V4-INGRESS', + description='This is IPv4 V4-INGRESS rule set', + default_action='accept', + enable_default_log=False, + rules=[ + dict(number='101', + action='accept', + description='Rule 101 is configured by Ansible', + ipsec='match-ipsec', + protocol='icmp', + fragment='match-frag', + disabled=True), + ]), + ]), + dict(afi='ipv6', + rule_sets=[ + dict( + name='V6-INGRESS', + default_action='accept', + ), + dict( + name='V6-EGRESS', + default_action='reject', + ) + ]) + ], + state="replaced")) + commands = [ + 'delete firewall name V4-INGRESS enable-default-log', + 'delete firewall name V4-EGRESS default-action' + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='V4-INGRESS', + description='This is IPv4 V4-INGRESS rule set', + default_action='accept', + enable_default_log=True, + rules=[ + dict(number='101', + action='accept', + description='Rule 101 is configured by Ansible', + ipsec='match-ipsec', + protocol='icmp', + fragment='match-frag', + disabled=True) + ]), + dict( + name='V4-EGRESS', + default_action='reject', + ), + ]), + dict(afi='ipv6', + rule_sets=[ + dict( + name='V6-INGRESS', + default_action='accept', + ), + dict( + name='V6-EGRESS', + default_action='reject', + ) + ]) + ], + state="replaced")) + self.execute_module(changed=False, commands=[]) + + def test_vyos_firewall_v4v6_rule_sets_rule_mer_idem_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='V4-INGRESS', + description='This is IPv4 V4-INGRESS rule set', + default_action='accept', + enable_default_log=True, + rules=[ + dict(number='101', + action='accept', + description='Rule 101 is configured by Ansible', + ipsec='match-ipsec', + protocol='icmp', + fragment='match-frag', + disabled=True) + ]), + dict( + name='V4-EGRESS', + default_action='reject', + ), + ]), + dict(afi='ipv6', + rule_sets=[ + dict( + name='V6-INGRESS', + default_action='accept', + ), + dict( + name='V6-EGRESS', + default_action='reject', + ) + ]) + ], + state="merged")) + self.execute_module(changed=False, commands=[]) + + def test_vyos_firewall_v4v6_rule_sets_rule_ovr_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='V4-IN', + description='This is IPv4 INGRESS rule set', + default_action='accept', + enable_default_log=True, + rules=[ + dict(number='1', + action='reject', + description='Rule 1 is configured by Ansible RM', + ipsec='match-ipsec', + protocol='tcp', + fragment='match-frag', + disabled=False), + dict(number='2', + action='accept', + description='Rule 102 is configured by Ansible RM', + protocol='icmp', + disabled=True) + ]), + ]), + dict(afi='ipv6', + rule_sets=[ + dict(name='V6-IN', + default_action='accept', + description='This rule-set is configured by Ansible RM'), + dict(name='V6-EG', + default_action='reject', + description='This rule-set is configured by Ansible RM') + ]) + ], + state="overridden")) + commands = [ + 'delete firewall ipv6-name V6-INGRESS', + 'delete firewall ipv6-name V6-EGRESS', + 'delete firewall name V4-INGRESS', + 'delete firewall name V4-EGRESS', + "set firewall name V4-IN default-action 'accept'", + "set firewall name V4-IN description 'This is IPv4 INGRESS rule set'", + 'set firewall name V4-IN enable-default-log', + "set firewall name V4-IN rule 1 protocol 'tcp'", + "set firewall name V4-IN rule 1 description 'Rule 1 is configured by Ansible RM'", + "set firewall name V4-IN rule 1 fragment 'match-frag'", + 'set firewall name V4-IN rule 1', + "set firewall name V4-IN rule 1 action 'reject'", + "set firewall name V4-IN rule 1 ipsec 'match-ipsec'", + 'set firewall name V4-IN rule 2 disabled', + "set firewall name V4-IN rule 2 action 'accept'", + "set firewall name V4-IN rule 2 protocol 'icmp'", + "set firewall name V4-IN rule 2 description 'Rule 102 is configured by Ansible RM'", + 'set firewall name V4-IN rule 2', + "set firewall ipv6-name V6-IN default-action 'accept'", + "set firewall ipv6-name V6-IN description 'This rule-set is configured by Ansible RM'", + "set firewall ipv6-name V6-EG default-action 'reject'", + "set firewall ipv6-name V6-EG description 'This rule-set is configured by Ansible RM'" + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_rule_ovr_idem_01(self): + set_module_args( + dict(config=[ + dict(afi='ipv4', + rule_sets=[ + dict(name='V4-INGRESS', + description='This is IPv4 V4-INGRESS rule set', + default_action='accept', + enable_default_log=True, + rules=[ + dict(number='101', + action='accept', + description='Rule 101 is configured by Ansible', + ipsec='match-ipsec', + protocol='icmp', + fragment='match-frag', + disabled=True) + ]), + dict( + name='V4-EGRESS', + default_action='reject', + ), + ]), + dict(afi='ipv6', + rule_sets=[ + dict( + name='V6-INGRESS', + default_action='accept', + ), + dict( + name='V6-EGRESS', + default_action='reject', + ) + ]) + ], + state="overridden")) + self.execute_module(changed=False, commands=[])