diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
commit | 66cec45960ce1d9c794e9399de15c138acb18aed (patch) | |
tree | 59cd19d69e9d56b7989b080da7c20ef1a3fe2a5a /ansible_collections/cisco/asa/plugins | |
parent | Initial commit. (diff) | |
download | ansible-66cec45960ce1d9c794e9399de15c138acb18aed.tar.xz ansible-66cec45960ce1d9c794e9399de15c138acb18aed.zip |
Adding upstream version 7.3.0+dfsg.upstream/7.3.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/asa/plugins')
44 files changed, 6951 insertions, 0 deletions
diff --git a/ansible_collections/cisco/asa/plugins/action/__init__.py b/ansible_collections/cisco/asa/plugins/action/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/action/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/action/asa.py b/ansible_collections/cisco/asa/plugins/action/asa.py new file mode 100644 index 00000000..32c230b0 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/action/asa.py @@ -0,0 +1,56 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.action.network import ( + ActionModule as ActionNetworkModule, +) + +from ansible.utils.display import Display + +display = Display() + + +class ActionModule(ActionNetworkModule): + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split(".")[-1] + self._config_module = ( + True if module_name in ["asa_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli"): + return { + "failed": True, + "msg": "Connection type %s is not valid for this module" + % self._play_context.connection, + } + + result = super(ActionModule, self).run(task_vars=task_vars) + if warnings: + if "warnings" in result: + result["warnings"].extend(warnings) + else: + result["warnings"] = warnings + return result diff --git a/ansible_collections/cisco/asa/plugins/cliconf/__init__.py b/ansible_collections/cisco/asa/plugins/cliconf/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/cliconf/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/cliconf/asa.py b/ansible_collections/cisco/asa/plugins/cliconf/asa.py new file mode 100644 index 00000000..f9fa955f --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/cliconf/asa.py @@ -0,0 +1,168 @@ +# +# (c) 2017 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +author: Ansible Security Team (@ansible-security) +name: asa +short_description: Use asa cliconf to run command on Cisco ASA platform +description: +- This asa plugin provides low level abstraction apis for sending and receiving CLI + commands from Cisco ASA network devices. +version_added: 1.0.0 +options: + config_commands: + description: + - Specifies a list of commands that can make configuration changes + to the target device. + - When `ansible_network_single_user_mode` is enabled, if a command sent + to the device is present in this list, the existing cache is invalidated. + version_added: 2.0.0 + type: list + elements: str + default: [] + vars: + - name: ansible_asa_config_commands +""" + +import re +import json + +from itertools import chain +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible.module_utils.common._collections_compat import Mapping +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) +from ansible_collections.ansible.netcommon.plugins.plugin_utils.cliconf_base import ( + CliconfBase, + enable_mode, +) + + +class Cliconf(CliconfBase): + def __init__(self, *args, **kwargs): + super(Cliconf, self).__init__(*args, **kwargs) + self._device_info = {} + + def get_device_info(self): + if not self._device_info: + device_info = {} + + device_info["network_os"] = "asa" + reply = self.get("show version") + data = to_text(reply, errors="surrogate_or_strict").strip() + + match = re.search(r"Version (\S+)", data) + if match: + device_info["network_os_version"] = match.group(1) + + match = re.search(r"Firepower .+ Version (\S+)", data) + if match: + device_info["network_os_firepower_version"] = match.group(1) + + match = re.search(r"Device .+ Version (\S+)", data) + if match: + device_info["network_os_device_mgr_version"] = match.group(1) + + match = re.search(r"^Model Id:\s+(.+) \(revision", data, re.M) + if match: + device_info["network_os_model"] = match.group(1) + + match = re.search(r"^(.+) up", data, re.M) + if match: + device_info["network_os_hostname"] = match.group(1) + + match = re.search(r'image file is "(.+)"', data) + if match: + device_info["network_os_image"] = match.group(1) + + self._device_info = device_info + + return self._device_info + + @enable_mode + def get_config(self, source="running", flags=None, format="text"): + if source not in ("running", "startup"): + return self.invalid_params( + "fetching configuration from %s is not supported" % source + ) + if source == "running": + cmd = "show running-config all" + else: + cmd = "show startup-config" + return self.send_command(cmd) + + @enable_mode + def edit_config(self, command): + for cmd in chain(["configure terminal"], to_list(command), ["end"]): + self.send_command(cmd) + + def get( + self, + command, + prompt=None, + answer=None, + sendonly=False, + newline=True, + check_all=False, + ): + return self.send_command( + command=command, + prompt=prompt, + answer=answer, + sendonly=sendonly, + newline=newline, + check_all=check_all, + ) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {"command": cmd} + + output = cmd.pop("output", None) + if output: + raise ValueError( + "'output' value %s is not supported for run_commands" + % output + ) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, "err", to_text(e)) + + responses.append(out) + + return responses diff --git a/ansible_collections/cisco/asa/plugins/doc_fragments/__init__.py b/ansible_collections/cisco/asa/plugins/doc_fragments/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/doc_fragments/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/doc_fragments/asa.py b/ansible_collections/cisco/asa/plugins/doc_fragments/asa.py new file mode 100644 index 00000000..70c0f97c --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/doc_fragments/asa.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +# Copyright: (c) 2016, Peter Sprygada <psprygada@ansible.com> +# Copyright: (c) 2016, Patrick Ogenstad <@ogenstad> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r"""options: + context: + description: + - Specifies which context to target if you are running in the ASA in multiple + context mode. Defaults to the current context you login to. + type: str + passwords: + description: + - Saves running-config passwords in clear-text when set to True. + Defaults to False + type: bool +notes: +- For more information on using Ansible to manage network devices see the :ref:`Ansible + Network Guide <network_guide>` +""" diff --git a/ansible_collections/cisco/asa/plugins/module_utils/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/acls.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/acls.py new file mode 100644 index 00000000..fa2dab05 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/acls.py @@ -0,0 +1,292 @@ +# -*- 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 asa_acls module +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class AclsArgs(object): + """The arg spec for the asa_acls module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "type": "dict", + "options": { + "acls": { + "elements": "dict", + "type": "list", + "options": { + "name": {"required": True, "type": "str"}, + "acl_type": { + "choices": ["extended", "standard"], + "type": "str", + }, + "rename": {"type": "str"}, + "aces": { + "elements": "dict", + "type": "list", + "options": { + "grant": { + "choices": ["permit", "deny"], + "type": "str", + }, + "line": {"type": "int"}, + "remark": {"type": "str"}, + "source": { + "type": "dict", + "options": { + "address": {"type": "str"}, + "netmask": {"type": "str"}, + "any": {"type": "bool"}, + "any4": {"type": "bool"}, + "any6": {"type": "bool"}, + "host": {"type": "str"}, + "interface": {"type": "str"}, + "object_group": {"type": "str"}, + "port_protocol": { + "type": "dict", + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": { + "type": "int" + }, + "end": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "destination": { + "type": "dict", + "options": { + "address": {"type": "str"}, + "netmask": {"type": "str"}, + "any": {"type": "bool"}, + "any4": {"type": "bool"}, + "any6": {"type": "bool"}, + "host": {"type": "str"}, + "interface": {"type": "str"}, + "object_group": {"type": "str"}, + "service_object_group": { + "type": "str" + }, + "port_protocol": { + "type": "dict", + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": { + "type": "int" + }, + "end": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "protocol": {"type": "str"}, + "protocol_options": { + "type": "dict", + "options": { + "protocol_number": {"type": "int"}, + "ahp": {"type": "bool"}, + "eigrp": {"type": "bool"}, + "esp": {"type": "bool"}, + "gre": {"type": "bool"}, + "icmp": { + "type": "dict", + "options": { + "alternate_address": { + "type": "bool" + }, + "conversion_error": { + "type": "bool" + }, + "echo": {"type": "bool"}, + "echo_reply": {"type": "bool"}, + "information_reply": { + "type": "bool" + }, + "information_request": { + "type": "bool" + }, + "mask_reply": {"type": "bool"}, + "mask_request": { + "type": "bool" + }, + "mobile_redirect": { + "type": "bool" + }, + "parameter_problem": { + "type": "bool" + }, + "redirect": {"type": "bool"}, + "router_advertisement": { + "type": "bool" + }, + "router_solicitation": { + "type": "bool" + }, + "source_quench": { + "type": "bool" + }, + "source_route_failed": { + "type": "bool" + }, + "time_exceeded": { + "type": "bool" + }, + "timestamp_reply": { + "type": "bool" + }, + "timestamp_request": { + "type": "bool" + }, + "traceroute": {"type": "bool"}, + "unreachable": { + "type": "bool" + }, + }, + }, + "icmp6": { + "type": "dict", + "options": { + "echo": {"type": "bool"}, + "echo_reply": {"type": "bool"}, + "membership_query": { + "type": "bool" + }, + "membership_reduction": { + "type": "bool" + }, + "membership_report": { + "type": "bool" + }, + "neighbor_advertisement": { + "type": "bool" + }, + "neighbor_redirect": { + "type": "bool" + }, + "neighbor_solicitation": { + "type": "bool" + }, + "packet_too_big": { + "type": "bool" + }, + "parameter_problem": { + "type": "bool" + }, + "router_advertisement": { + "type": "bool" + }, + "router_renumbering": { + "type": "bool" + }, + "router_solicitation": { + "type": "bool" + }, + "time_exceeded": { + "type": "bool" + }, + "unreachable": { + "type": "bool" + }, + }, + }, + "igmp": {"type": "bool"}, + "igrp": {"type": "bool"}, + "ip": {"type": "bool"}, + "ipinip": {"type": "bool"}, + "ipsec": {"type": "bool"}, + "nos": {"type": "bool"}, + "ospf": {"type": "bool"}, + "pcp": {"type": "bool"}, + "pim": {"type": "bool"}, + "pptp": {"type": "bool"}, + "sctp": {"type": "bool"}, + "snp": {"type": "bool"}, + "tcp": {"type": "bool"}, + "udp": {"type": "bool"}, + }, + }, + "inactive": {"type": "bool"}, + "log": { + "type": "str", + "choices": [ + "default", + "alerts", + "critical", + "debugging", + "disable", + "emergencies", + "errors", + "informational", + "interval", + "notifications", + "warnings", + ], + }, + "time_range": {"type": "str"}, + }, + }, + }, + } + }, + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/facts.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/facts.py new file mode 100644 index 00000000..e24d7165 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/facts.py @@ -0,0 +1,26 @@ +# +# -*- 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 arg spec for the asa facts module. +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class FactsArgs(object): + """The arg spec for the asa facts module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "gather_subset": dict( + default=["!config"], type="list", elements="str" + ), + "gather_network_resources": dict(type="list", elements="str"), + } diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/ogs.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/ogs.py new file mode 100644 index 00000000..30420dc9 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/ogs.py @@ -0,0 +1,260 @@ +# -*- 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 asa_ogs module +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class OGsArgs(object): + """The arg spec for the asa_ogs module""" + + argument_spec = { + "config": { + "elements": "dict", + "type": "list", + "options": { + "object_type": { + "type": "str", + "required": True, + "choices": [ + "icmp-type", + "network", + "protocol", + "security", + "service", + "user", + ], + }, + "object_groups": { + "elements": "dict", + "type": "list", + "options": { + "name": {"required": True, "type": "str"}, + "description": {"type": "str"}, + "icmp_type": { + "type": "dict", + "options": { + "icmp_object": { + "type": "list", + "elements": "str", + "choices": [ + "alternate-address", + "conversion-error", + "echo", + "echo-reply", + "information-reply", + "information-request", + "mask-reply", + "mask-request", + "mobile-redirect", + "parameter-problem", + "redirect", + "router-advertisement", + "router-solicitation", + "source-quench", + "time-exceeded", + "timestamp-reply", + "timestamp-request", + "traceroute", + "unreachable", + ], + } + }, + }, + "network_object": { + "type": "dict", + "options": { + "host": {"type": "list", "elements": "str"}, + "address": {"type": "list", "elements": "str"}, + "ipv6_address": { + "type": "list", + "elements": "str", + }, + "object": {"type": "list", "elements": "str"}, + }, + }, + "protocol_object": { + "type": "dict", + "options": { + "protocol": {"type": "list", "elements": "str"} + }, + }, + "security_group": { + "type": "dict", + "options": { + "sec_name": { + "type": "list", + "elements": "str", + }, + "tag": {"type": "list", "elements": "str"}, + }, + }, + "services_object": { + "type": "list", + "elements": "dict", + "options": { + "protocol": {"type": "str"}, + "object": {"type": "str"}, + "source_port": { + "type": "dict", + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": {"type": "str"}, + "end": {"type": "str"}, + }, + }, + }, + }, + "destination_port": { + "type": "dict", + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": {"type": "str"}, + "end": {"type": "str"}, + }, + }, + }, + }, + }, + }, + "protocol": { + "type": "str", + "choices": ["tcp", "tcp-udp", "udp"], + }, + "port_object": { + "type": "list", + "elements": "dict", + "options": { + "eq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": {"type": "str"}, + "end": {"type": "str"}, + }, + }, + }, + }, + "service_object": { + "type": "dict", + "options": { + "protocol": { + "type": "list", + "elements": "str", + "choices": [ + "ah", + "eigrp", + "esp", + "gre", + "icmp", + "icmp6", + "igmp", + "igrp", + "ip", + "ipinip", + "ipsec", + "nos", + "ospf", + "pcp", + "pim", + "pptp", + "sctp", + "snp", + "tcp", + "tcp-udp", + "udp", + ], + }, + "object": {"type": "str"}, + }, + }, + "user_object": { + "type": "dict", + "options": { + "user": { + "elements": "dict", + "type": "list", + "options": { + "name": { + "required": True, + "type": "str", + }, + "domain": { + "required": True, + "type": "str", + }, + }, + }, + "user_group": { + "elements": "dict", + "type": "list", + "options": { + "name": { + "required": True, + "type": "str", + }, + "domain": { + "required": True, + "type": "str", + }, + }, + }, + }, + }, + "group_object": {"type": "list", "elements": "str"}, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/asa.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/asa.py new file mode 100644 index 00000000..e62affb2 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/asa.py @@ -0,0 +1,155 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2016 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json + +from ansible.module_utils._text import to_text +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + EntityCollection, +) +from ansible.module_utils.connection import exec_command +from ansible.module_utils.connection import Connection, ConnectionError + +_DEVICE_CONFIGS = {} +_CONNECTION = None + +command_spec = {"command": dict(key=True), "prompt": dict(), "answer": dict()} + +asa_argument_spec = { + "context": dict(type="str"), + "passwords": dict(type="bool"), +} + + +def check_args(module): + pass + + +def get_connection(module): + if hasattr(module, "_asa_connection"): + return module._asa_connection + + # Not all modules include the 'context' key. + context = module.params.get("context") + connection_proxy = Connection(module._socket_path) + cap = json.loads(connection_proxy.get_capabilities()) + if cap["network_api"] == "cliconf": + module._asa_connection = Connection(module._socket_path) + + if context: + if context == "system": + command = "changeto system" + else: + command = "changeto context %s" % context + module._asa_connection.get(command) + + return module._asa_connection + + +def get_capabilities(module): + if hasattr(module, "_asa_capabilities"): + return module._asa_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + module._asa_capabilities = json.loads(capabilities) + + return module._asa_capabilities + + +def to_commands(module, commands): + if not isinstance(commands, list): + raise AssertionError("argument must be of type <list>") + + transform = EntityCollection(module, command_spec) + commands = transform(commands) + + for index, item in enumerate(commands): + if module.check_mode and not item["command"].startswith("show"): + module.warn( + "only show commands are supported when using check " + "mode, not executing `%s`" % item["command"] + ) + + return commands + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + return connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + # Not all modules include the 'passwords' key. + passwords = module.params.get("passwords", False) + if passwords: + cmd = "more system:running-config" + else: + cmd = "show running-config " + cmd += " ".join(flags) + cmd = cmd.strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + conn = get_connection(module) + out = conn.get(cmd) + cfg = to_text(out, errors="surrogate_then_replace").strip() + _DEVICE_CONFIGS[cmd] = cfg + return cfg + + +def load_config(module, config): + try: + conn = get_connection(module) + conn.edit_config(config) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_defaults_flag(module): + rc, out, err = exec_command(module, "show running-config ?") + out = to_text(out, errors="surrogate_then_replace") + + commands = set() + for line in out.splitlines(): + if line: + commands.add(line.strip().split()[0]) + + if "all" in commands: + return "all" + else: + return "full" diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/acls.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/acls.py new file mode 100644 index 00000000..ac704e0f --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/acls.py @@ -0,0 +1,230 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The asa_acls 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 + +import copy +from ansible.module_utils.six import iteritems +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.facts import ( + Facts, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.rm_templates.acls import ( + AclsTemplate, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.resource_module import ( + ResourceModule, +) + + +class Acls(ResourceModule): + """ + The asa_acls class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["acls"] + + def __init__(self, module): + super(Acls, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="acls", + tmplt=AclsTemplate(), + ) + + def execute_module(self): + """Execute the module + :rtype: A dictionary + :returns: The result from module execution + """ + self.gen_config() + self.run_commands() + return self.result + + def gen_config(self): + """Select the appropriate function based on the state provided + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + if self.want: + temp = {} + for entry in self.want["acls"]: + temp.update({(entry["name"]): entry}) + wantd = temp + else: + wantd = {} + if self.have: + temp = {} + for entry in self.have["acls"]: + temp.update({(entry["name"]): entry}) + haved = temp + else: + haved = {} + + for k, want in iteritems(wantd): + h_want = haved.get(k, {}) + if want.get("aces"): + for each in want["aces"]: + if h_want.get("aces"): + for e_have in h_want.get("aces"): + if e_have.get("source") == each.get( + "source" + ) and e_have.get("destination") == each.get( + "destination" + ): + if ( + "protocol" in e_have + and "protocol" not in each + and each.get("protocol_options") + == e_have.get("protocol_options") + ): + del e_have["protocol"] + break + # if state is merged, merge want onto have and then compare + if self.state == "merged": + # to append line number from have to want + # if want ace config mateches have ace config + temp_have = copy.deepcopy(haved) + for k, v in iteritems(wantd): + h_item = temp_have.pop(k, {}) + if not h_item: + continue + if v.get("aces"): + for each in v["aces"]: + if "line" in each: + continue + else: + for each_have in h_item["aces"]: + have_line = each_have.pop("line") + if each == each_have: + each.update({"line": have_line}) + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + temp = {} + for k, v in iteritems(haved): + if k in wantd or not wantd: + temp.update({k: v}) + haved = temp + wantd = {} + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self._compare(want={}, have=have) + + temp = [] + for k, want in iteritems(wantd): + if want.get("rename") and want.get("rename") not in temp: + self.commands.extend( + ["access-list {name} rename {rename}".format(**want)] + ) + elif k in haved: + temp.append(k) + self._compare(want=want, have=haved.pop(k, {})) + if self.state in ["replaced", "overridden", "deleted"]: + config_cmd = [cmd for cmd in self.commands if "no" in cmd][::-1] + config_cmd.extend( + [cmd for cmd in self.commands if "no" not in cmd] + ) + self.commands = config_cmd + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Ospf_interfaces network resource. + """ + parsers = ["aces"] + + if want.get("aces"): + for each in want["aces"]: + set_want = True + if have.get("aces"): + temp = 0 + for e_have in have.get("aces"): + if e_have.get("source") == each.get( + "source" + ) and e_have.get("destination") == each.get( + "destination" + ): + set_want = False + if each.get("protocol") == e_have.get("protocol"): + if not each.get( + "protocol_options" + ) and e_have.get("protocol_options"): + del e_have["protocol_options"] + if each == e_have: + del have.get("aces")[temp] + break + each.update( + { + "name": want.get("name"), + "acl_type": want.get("acl_type"), + } + ) + e_have.update( + { + "name": have.get("name"), + "acl_type": have.get("acl_type"), + } + ) + self.compare( + parsers=parsers, + want={"aces": each}, + have={"aces": e_have}, + ) + break + temp += 1 + else: + each.update( + { + "name": want.get("name"), + "acl_type": want.get("acl_type"), + } + ) + self.compare( + parsers=parsers, want={"aces": each}, have=dict() + ) + set_want = False + if set_want: + each.update( + { + "name": want.get("name"), + "acl_type": want.get("acl_type"), + } + ) + self.compare( + parsers=parsers, want={"aces": each}, have=dict() + ) + if self.state in ["overridden", "deleted", "replaced"]: + if have.get("aces"): + for each in have["aces"]: + each.update( + { + "name": have.get("name"), + "acl_type": have.get("acl_type"), + } + ) + self.compare( + parsers=parsers, want=dict(), have={"aces": each} + ) diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/ogs.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/ogs.py new file mode 100644 index 00000000..59e1c362 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/ogs.py @@ -0,0 +1,702 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The asa_ogs 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 + +import copy +from ansible.module_utils.six import iteritems +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.facts import ( + Facts, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.rm_templates.ogs import ( + OGsTemplate, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.resource_module import ( + ResourceModule, +) + + +class OGs(ResourceModule): + """ + The asa_ogs class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["ogs"] + + def __init__(self, module): + super(OGs, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="ogs", + tmplt=OGsTemplate(), + ) + + def execute_module(self): + """Execute the module + :rtype: A dictionary + :returns: The result from module execution + """ + self.gen_config() + self.run_commands() + return self.result + + def gen_config(self): + """Select the appropriate function based on the state provided + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + if self.want: + temp = {} + for entry in self.want: + temp.update({(entry["object_type"]): entry}) + wantd = temp + else: + wantd = {} + if self.have: + temp = {} + for entry in self.have: + temp.update({(entry["object_type"]): entry}) + haved = temp + else: + haved = {} + + obj_gp = {} + for k, v in wantd.items(): + temp = {} + for each in v.get("object_groups"): + temp[each.get("name")] = each + temp["object_type"] = k + obj_gp[k] = temp + if obj_gp: + wantd = obj_gp + obj_gp = {} + for k, v in haved.items(): + temp = {} + for each in v.get("object_groups"): + temp[each.get("name")] = each + temp["object_type"] = k + obj_gp[k] = temp + if obj_gp: + haved = obj_gp + + # if state is merged, merge want onto have + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + temp = {} + for k, v in iteritems(haved): + temp_have = {} + if k in wantd or not wantd: + for key, val in iteritems(v): + if not wantd or key in wantd[k]: + temp_have.update({key: val}) + temp.update({k: temp_have}) + haved = temp + wantd = {} + + # delete processes first so we do run into "more than one" errors + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + for each_key, each_val in iteritems(have): + if each_key != "object_type": + each_val.update( + {"object_type": have.get("object_type")} + ) + self.addcmd(each_val, "og_name", True) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + if want != have: + for k, v in iteritems(want): + if k != "object_type": + v.update({"object_type": want.get("object_type")}) + if have: + for k, v in iteritems(have): + if k != "object_type": + v.update({"object_type": want.get("object_type")}) + + object_type = want.get("object_type") + if object_type == "icmp-type": + self._icmp_object_compare(want, have) + if object_type == "network": + self._network_object_compare(want, have) + elif object_type == "protocol": + self._protocol_object_compare(want, have) + elif object_type == "security": + self._security_object_compare(want, have) + elif object_type == "service": + self._service_object_compare(want, have) + elif object_type == "user": + self._user_object_compare(want, have) + + def get_list_diff(self, want, have, object, param): + diff = [ + item + for item in want[object][param] + if item not in have[object][param] + ] + return diff + + def check_for_have_and_overidden(self, have): + if have and self.state == "overridden": + for name, entry in iteritems(have): + if name != "object_type": + self.addcmd(entry, "og_name", True) + + def _icmp_object_compare(self, want, have): + icmp_obj = "icmp_type" + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if ( + entry != h_item + and name != "object_type" + and entry[icmp_obj].get("icmp_object") + ): + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, h_item, icmp_obj, ["icmp_type"] + ) + else: + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if self.state in ("overridden", "replaced") and h_item: + self.compare(["icmp_type"], {}, h_item) + if h_item and h_item[icmp_obj].get("icmp_object"): + li_diff = self.get_list_diff( + entry, h_item, icmp_obj, "icmp_object" + ) + else: + li_diff = entry[icmp_obj].get("icmp_object") + entry[icmp_obj]["icmp_object"] = li_diff + self.addcmd(entry, "icmp_type", False) + self.check_for_have_and_overidden(have) + + def _network_object_compare(self, want, have): + network_obj = "network_object" + parsers = [ + "network_object.host", + "network_object.address", + "network_object.ipv6_address", + "network_object.object", + ] + add_obj_cmd = False + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, + h_item, + network_obj, + ["address", "host", "ipv6_address", "object"], + ) + else: + add_obj_cmd = True + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry[network_obj].get("address"): + self._compare_object_diff( + entry, + h_item, + network_obj, + "address", + parsers, + "network_object.address", + ) + elif ( + h_item + and h_item.get(network_obj) + and h_item[network_obj].get("address") + ): + h_item[network_obj] = { + "address": h_item[network_obj].get("address") + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[network_obj].get("host"): + self._compare_object_diff( + entry, + h_item, + network_obj, + "host", + parsers, + "network_object.host", + ) + elif h_item and h_item[network_obj].get("host"): + h_item[network_obj] = { + "host": h_item[network_obj].get("host") + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[network_obj].get("ipv6_address"): + self._compare_object_diff( + entry, + h_item, + network_obj, + "ipv6_address", + parsers, + "network_object.ipv6_address", + ) + elif ( + h_item + and h_item.get(network_obj) + and h_item[network_obj].get("ipv6_address") + ): + h_item[network_obj] = { + "ipv6_address": h_item[network_obj].get("ipv6_address") + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[network_obj].get("object"): + self._compare_object_diff( + entry, + h_item, + network_obj, + "object", + parsers, + "network_object.object", + ) + elif ( + h_item + and h_item.get(network_obj) + and h_item[network_obj].get("object") + ): + h_item[network_obj] = { + "object": h_item[network_obj].get("object") + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + self.check_for_have_and_overidden(have) + + def _protocol_object_compare(self, want, have): + protocol_obj = "protocol_object" + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, h_item, protocol_obj, ["protocol"] + ) + else: + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry[protocol_obj].get("protocol"): + self._compare_object_diff( + entry, + h_item, + protocol_obj, + "protocol", + [protocol_obj], + protocol_obj, + ) + self.check_for_have_and_overidden(have) + + def _security_object_compare(self, want, have): + security_obj = "security_group" + parsers = ["security_group.sec_name", "security_group.tag"] + add_obj_cmd = False + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, h_item, security_obj, ["sec_name", "tag"] + ) + else: + add_obj_cmd = True + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry[security_obj].get("sec_name"): + self._compare_object_diff( + entry, + h_item, + security_obj, + "sec_name", + parsers, + "security_group.sec_name", + ) + elif h_item and h_item[security_obj].get("sec_name"): + h_item[security_obj] = { + "sec_name": h_item[security_obj].get("sec_name") + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[security_obj].get("tag"): + self._compare_object_diff( + entry, + h_item, + security_obj, + "tag", + parsers, + "security_group.tag", + ) + elif h_item and h_item[security_obj].get("tag"): + h_item[security_obj] = { + "tag": h_item[security_obj].get("tag") + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + self.check_for_have_and_overidden(have) + + def _service_object_compare(self, want, have): + service_obj = "service_object" + services_obj = "services_object" + port_obj = "port_object" + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, h_item, service_obj, ["protocol"] + ) + else: + protocol = entry.get("protocol") + if protocol: + entry["name"] = "{0} {1}".format(name, protocol) + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry.get(service_obj): + if entry[service_obj].get("protocol"): + self._compare_object_diff( + entry, + h_item, + service_obj, + "protocol", + ["service_object"], + service_obj, + ) + elif entry.get(services_obj): + if h_item: + h_item = self.convert_list_to_dict( + val=h_item, + source="source_port", + destination="destination_port", + ) + entry = self.convert_list_to_dict( + val=entry, + source="source_port", + destination="destination_port", + ) + command_len = len(self.commands) + for k, v in iteritems(entry): + if h_item: + h_service_item = h_item.pop(k, {}) + if h_service_item != v: + self.compare( + [services_obj], + want={services_obj: v}, + have={services_obj: h_service_item}, + ) + else: + temp_want = {"name": name, services_obj: v} + self.addcmd(temp_want, "og_name", True) + + self.compare( + [services_obj], want=temp_want, have={} + ) + if h_item and self.state in ["overridden", "replaced"]: + for k, v in iteritems(h_item): + temp_have = {"name": name, services_obj: v} + self.compare( + [services_obj], want={}, have=temp_have + ) + if command_len < len(self.commands): + cmd = "object-group service {0}".format(name) + if cmd not in self.commands: + self.commands.insert(command_len, cmd) + elif entry.get(port_obj): + protocol = entry.get("protocol") + if h_item: + h_item = self.convert_list_to_dict( + val=h_item, + source="source_port", + destination="destination_port", + ) + entry = self.convert_list_to_dict( + val=entry, + source="source_port", + destination="destination_port", + ) + command_len = len(self.commands) + for k, v in iteritems(entry): + h_port_item = h_item.pop(k, {}) + if "http" in k and "_" in k: + # This condition is to TC of device behaviour, where if user tries to + # configure http it gets converted to www. + temp = k.split("_")[0] + h_port_item = {temp: "http"} + if h_port_item != v: + self.compare( + [port_obj], + want={port_obj: v}, + have={port_obj: h_port_item}, + ) + elif not h_port_item: + temp_want = {"name": name, port_obj: v} + self.compare([port_obj], want=temp_want, have={}) + if h_item and self.state in ["overridden", "replaced"]: + for k, v in iteritems(h_item): + temp_have = {"name": name, port_obj: v} + self.compare([port_obj], want={}, have=temp_have) + self.check_for_have_and_overidden(have) + + def convert_list_to_dict(self, *args, **kwargs): + temp = {} + if kwargs["val"].get("services_object"): + for every in kwargs["val"]["services_object"]: + temp_key = every["protocol"] + if "source_port" in every: + if "range" in every["source_port"]: + temp_key = ( + "range" + + "_" + + str(every["source_port"]["range"]["start"]) + + "_" + + str(every["source_port"]["range"]["end"]) + ) + else: + source_key = list(every["source_port"])[0] + temp_key = ( + temp_key + + "_" + + source_key + + "_" + + every["source_port"][source_key] + ) + if "destination_port" in every: + if "range" in every["destination_port"]: + temp_key = ( + "range" + + "_" + + str(every["destination_port"]["range"]["start"]) + + "_" + + str(every["destination_port"]["range"]["end"]) + ) + else: + destination_key = list(every["destination_port"])[0] + temp_key = ( + temp_key + + "_" + + destination_key + + "_" + + every["destination_port"][destination_key] + ) + temp.update({temp_key: every}) + return temp + elif kwargs["val"].get("port_object"): + for every in kwargs["val"]["port_object"]: + if "range" in every: + temp_key = ( + "start" + + "_" + + every["range"]["start"] + + "_" + + "end" + + "_" + + every["range"]["end"] + ) + else: + every_key = list(every)[0] + temp_key = every_key + "_" + every[every_key] + temp.update({temp_key: every}) + return temp + + def _user_object_compare(self, want, have): + user_obj = "user_object" + parsers = ["user_object.user", "user_object.user_gp"] + add_obj_cmd = False + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, h_item, user_obj, ["user", "user_group"] + ) + else: + add_obj_cmd = True + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry[user_obj].get("user"): + self._compare_object_diff( + entry, + h_item, + user_obj, + "user", + ["user_object.user"], + "user_object.user", + ) + elif h_item and h_item[user_obj].get("user"): + h_item[user_obj] = {"user": h_item[user_obj].get("user")} + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[user_obj].get("user_group"): + self._compare_object_diff( + entry, + h_item, + user_obj, + "user_group", + ["user_object.user_group"], + "user_object.user_gp", + ) + elif h_item and h_item[user_obj].get("user_group"): + h_item[user_obj] = { + "user_group": h_item[user_obj].get("user_group") + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + self.check_for_have_and_overidden(have) + + def _add_object_cmd(self, want, have, object, object_elements): + obj_cmd_added = False + for each in object_elements: + want_element = want[object].get(each) if want.get(object) else want + have_element = have[object].get(each) if have.get(object) else have + if ( + want_element + and isinstance(want_element, list) + and isinstance(want_element[0], dict) + ): + if ( + want_element + and have_element + and want_element != have_element + ): + if not obj_cmd_added: + self.addcmd(want, "og_name", False) + self.compare(["description"], want, have) + obj_cmd_added = True + else: + if ( + want_element + and have_element + and set(want_element) != set(have_element) + ): + if not obj_cmd_added: + self.addcmd(want, "og_name", False) + self.compare(["description"], want, have) + obj_cmd_added = True + + def _add_group_object_cmd(self, want, have): + if have and have.get("group_object"): + want["group_object"] = list( + set(want.get("group_object")) - set(have.get("group_object")) + ) + have["group_object"] = list( + set(have.get("group_object")) - set(want.get("group_object")) + ) + for each in want["group_object"]: + self.compare(["group_object"], {"group_object": each}, dict()) + if ( + (self.state == "replaced" or self.state == "overridden") + and have + and have.get("group_object") + ): + for each in have["group_object"]: + self.compare(["group_object"], dict(), {"group_object": each}) + + def _compare_object_diff( + self, want, have, object, object_type, parsers, val + ): + temp_have = copy.copy(have) + temp_want = copy.copy(want) + if ( + temp_have + and temp_have.get(object) + and temp_have[object].get(object_type) + ): + want_diff = self.get_list_diff( + temp_want, temp_have, object, object_type + ) + have_diff = [ + each + for each in temp_have[object][object_type] + if each not in temp_want[object][object_type] + ] + if have_diff: + temp_have[object].pop(object_type) + else: + have_diff = [] + want_diff = temp_want[object].get(object_type) + temp_want[object][object_type] = want_diff + if ( + have_diff + or temp_have.get(object) + and self.state in ("overridden", "replaced") + ): + if have_diff: + temp_have[object] = {object_type: have_diff} + self.compare(parsers, {}, temp_have) + self.addcmd(temp_want, val, False) diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/acls.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/acls.py new file mode 100644 index 00000000..4b39a7a8 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/acls.py @@ -0,0 +1,105 @@ +# +# -*- 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 asa_acls 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 copy import deepcopy +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.argspec.acls.acls import ( + AclsArgs, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.rm_templates.acls import ( + AclsTemplate, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network_template import ( + NetworkTemplate, +) + + +class AclsFacts(object): + """The asa_acls fact class""" + + def __init__(self, module, subspec="config", options="options"): + + self._module = module + self.argument_spec = AclsArgs.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_acls_config(self, connection): + return connection.get("sh access-list") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for ACLs + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_acls_config(connection) + + rmmod = NetworkTemplate(lines=data.splitlines(), tmplt=AclsTemplate()) + current = rmmod.parse() + acls = list() + if current.get("acls"): + for key, val in iteritems(current.get("acls")): + if val.get("name") == "cached": + continue + for each in val.get("aces"): + if "protocol_number" in each: + each["protocol_options"] = { + "protocol_number": each["protocol_number"] + } + del each["protocol_number"] + if "icmp_icmp6_protocol" in each and each.get("protocol"): + each["protocol_options"] = { + each.get("protocol"): { + each["icmp_icmp6_protocol"].replace( + "-", "_" + ): True + } + } + del each["icmp_icmp6_protocol"] + elif ( + each.get("protocol") + and each.get("protocol") != "icmp" + and each.get("protocol") != "icmp6" + ): + each["protocol_options"] = {each.get("protocol"): True} + acls.append(val) + facts = {} + params = {} + if acls: + params = utils.validate_config( + self.argument_spec, {"config": {"acls": acls}} + ) + params = utils.remove_empties(params) + facts["acls"] = params["config"] + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/facts.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/facts.py new file mode 100644 index 00000000..082edff6 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/facts.py @@ -0,0 +1,66 @@ +# -*- 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 facts class for asa +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import ( + FactsBase, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.acls.acls import ( + AclsFacts, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.ogs.ogs import ( + OGsFacts, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.legacy.base import ( + Default, + Hardware, + Config, +) + + +FACT_LEGACY_SUBSETS = dict(default=Default, hardware=Hardware, config=Config) + +FACT_RESOURCE_SUBSETS = dict(acls=AclsFacts, ogs=OGsFacts) + + +class Facts(FactsBase): + """The fact class for asa""" + + VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) + VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) + + def __init__(self, module): + super(Facts, self).__init__(module) + + def get_facts( + self, legacy_facts_type=None, resource_facts_type=None, data=None + ): + """Collect the facts for asa + :param legacy_facts_type: List of legacy facts types + :param resource_facts_type: List of resource fact types + :param data: previously collected conf + :rtype: dict + :return: the facts gathered + """ + if self.VALID_RESOURCE_SUBSETS: + self.get_network_resources_facts( + FACT_RESOURCE_SUBSETS, resource_facts_type, data + ) + + if self.VALID_LEGACY_GATHER_SUBSETS: + self.get_network_legacy_facts( + FACT_LEGACY_SUBSETS, legacy_facts_type + ) + + return self.ansible_facts, self._warnings diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/base.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/base.py new file mode 100644 index 00000000..3ad46007 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/base.py @@ -0,0 +1,186 @@ +# -*- 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 asa legacy 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 + + +import platform +import re + +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import ( + run_commands, + get_capabilities, +) + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.warnings = list() + self.responses = None + + def populate(self): + self.responses = run_commands( + self.module, commands=self.COMMANDS, check_rc=False + ) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = ["show version"] + + def populate(self): + super(Default, self).populate() + self.facts.update(self.platform_facts()) + data = self.responses[0] + if data: + self.facts["asatype"] = self.parse_asatype(data) + self.facts["serialnum"] = self.parse_serialnum(data) + self.parse_stacks(data) + + def parse_asatype(self, data): + match = re.search(r"Hardware:(\s+)ASA", data) + if match: + return "ASA" + + def parse_serialnum(self, data): + match = re.search(r"Serial Number: (\S+)", data) + if match: + return match.group(1) + + def parse_stacks(self, data): + match = re.findall(r"^Model [Nn]umber\s+: (\S+)", data, re.M) + if match: + self.facts["stacked_models"] = match + + match = re.findall( + r"^System [Ss]erial [Nn]umber\s+: (\S+)", data, re.M + ) + if match: + self.facts["stacked_serialnums"] = match + + def platform_facts(self): + platform_facts = {} + + resp = get_capabilities(self.module) + device_info = resp["device_info"] + platform_facts["system"] = device_info["network_os"] + + for item in ( + "model", + "image", + "version", + "platform", + "hostname", + "firepower_version", + "device_mgr_version", + ): + val = device_info.get("network_os_%s" % item) + if val: + platform_facts[item] = val + + platform_facts["api"] = resp["network_api"] + platform_facts["python_version"] = platform.python_version() + + return platform_facts + + +class Hardware(FactsBase): + + COMMANDS = ["dir", "show memory"] + + def populate(self): + warnings = list() + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts["filesystems"] = self.parse_filesystems(data) + self.facts["filesystems_info"] = self.parse_filesystems_info(data) + + data = self.responses[1] + if data: + if "Invalid input detected" in data: + warnings.append("Unable to gather memory statistics") + else: + mem_list = [l for l in data.splitlines() if "memory" in l] + for each in mem_list: + if "Free memory" in each: + match = re.search( + r"Free memory.+ (\d+) .+(\d\d)", each + ) + if match: + self.facts["memfree_mb"] = ( + int(match.group(1)) // 1024 + ) + elif "Used memory" in each: + match = re.search( + r"Used memory.+ (\d+) .+(\d\d)", each + ) + if match: + self.facts["memused_mb"] = ( + int(match.group(1)) // 1024 + ) + elif "Total memory" in each: + match = re.search( + r"Total memory.+ (\d+) .+(\d\d)", each + ) + if match: + self.facts["memtotal_mb"] = ( + int(match.group(1)) // 1024 + ) + + def parse_filesystems(self, data): + return re.findall(r"^Directory of (\S+)/", data, re.M) + + def parse_filesystems_info(self, data): + facts = dict() + fs = "" + for line in data.split("\n"): + match = re.match(r"^Directory of (\S+)/", line) + if match: + fs = match.group(1) + facts[fs] = dict() + continue + match = re.match( + r"^(\d+) bytes total \((\d+) bytes free\/(\d+)% free\)", line + ) + if match: + facts[fs]["spacetotal_kb"] = int(match.group(1)) / 1024 + facts[fs]["spacefree_kb"] = int(match.group(2)) / 1024 + + return facts + + +class Config(FactsBase): + + COMMANDS = ["show running-config"] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + data = re.sub( + r"^Building configuration...\s+Current configuration : \d+ bytes\n", + "", + data, + flags=re.MULTILINE, + ) + self.facts["config"] = data diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/ogs.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/ogs.py new file mode 100644 index 00000000..65cd7662 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/ogs.py @@ -0,0 +1,103 @@ +# -*- 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 asa_og 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 ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.argspec.ogs.ogs import ( + OGsArgs, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.rm_templates.ogs import ( + OGsTemplate, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network_template import ( + NetworkTemplate, +) + + +class OGsFacts(object): + """The asa_ogs fact class""" + + def __init__(self, module, subspec="config", options="options"): + + self._module = module + self.argument_spec = OGsArgs.argument_spec + + def get_og_data(self, connection): + return connection.get("sh running-config object-group") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for OGs + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_og_data(connection) + rmmod = NetworkTemplate(lines=data.splitlines(), tmplt=OGsTemplate()) + current = rmmod.parse() + ogs = [] + object_groups = { + "icmp-type": "icmp_type", + "network": "network_object", + "protocol": "protocol_object", + "security": "security_group", + "service": "service_object", + "user": "user_object", + } + if current.get("ogs"): + for k, v in iteritems(current.get("ogs")): + obj_gp = {} + config_dict = {} + config_dict["object_type"] = k + config_dict["object_groups"] = [] + for each in iteritems(v): + obj_gp["name"] = each[1].pop("name") + each[1].pop("object_type") + if each[1].get("description"): + obj_gp["description"] = each[1].pop("description") + if each[1].get("group_object"): + obj_gp["group_object"] = each[1].pop("group_object") + if k == "service": + if "services_object" in each[1]: + obj_gp["services_object"] = each[1][ + "services_object" + ] + elif "port_object" in each[1]: + obj_gp["port_object"] = each[1]["port_object"] + obj_gp["protocol"] = each[1]["protocol"] + else: + obj_gp[object_groups.get(k)] = each[1] + config_dict["object_groups"].append(obj_gp) + obj_gp = {} + config_dict["object_groups"] = sorted( + config_dict["object_groups"], + key=lambda k, sk="name": str(k[sk]), + ) + ogs.append(config_dict) + # sort the object group list of dict by object_type + ogs = sorted(ogs, key=lambda i: i["object_type"]) + facts = {} + params = utils.remove_empties( + utils.validate_config(self.argument_spec, {"config": ogs}) + ) + facts["ogs"] = params.get("config") + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/module.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/module.py new file mode 100644 index 00000000..1a847275 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/module.py @@ -0,0 +1,73 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.providers import ( + providers, +) +from ansible.module_utils._text import to_text + + +class NetworkModule(AnsibleModule): + + fail_on_missing_provider = True + + def __init__(self, connection=None, *args, **kwargs): + super(NetworkModule, self).__init__(*args, **kwargs) + + if connection is None: + connection = Connection(self._socket_path) + + self.connection = connection + + @property + def provider(self): + if not hasattr(self, "_provider"): + capabilities = self.from_json(self.connection.get_capabilities()) + + network_os = capabilities["device_info"]["network_os"] + network_api = capabilities["network_api"] + + if network_api == "cliconf": + connection_type = "network_cli" + + cls = providers.get( + network_os, self._name.split(".")[-1], connection_type + ) + + if not cls: + msg = ( + "unable to find suitable provider for network os %s" + % network_os + ) + if self.fail_on_missing_provider: + self.fail_json(msg=msg) + else: + self.warn(msg) + + obj = cls(self.params, self.connection, self.check_mode) + + setattr(self, "_provider", obj) + + return getattr(self, "_provider") + + def get_facts(self, subset=None): + try: + self.provider.get_facts(subset) + except Exception as exc: + self.fail_json(msg=to_text(exc)) + + def edit_config(self, config_filter=None): + current_config = self.connection.get_config(flags=config_filter) + try: + commands = self.provider.edit_config(current_config) + changed = bool(commands) + return {"commands": commands, "changed": changed} + except Exception as exc: + self.fail_json(msg=to_text(exc)) diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/providers.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/providers.py new file mode 100644 index 00000000..fdf99cb3 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/providers.py @@ -0,0 +1,129 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json + +from threading import RLock + +from ansible.module_utils.six import itervalues +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, +) + + +_registered_providers = {} +_provider_lock = RLock() + + +def register_provider(network_os, module_name): + def wrapper(cls): + _provider_lock.acquire() + try: + if network_os not in _registered_providers: + _registered_providers[network_os] = {} + for ct in cls.supported_connections: + if ct not in _registered_providers[network_os]: + _registered_providers[network_os][ct] = {} + for item in to_list(module_name): + for entry in itervalues(_registered_providers[network_os]): + entry[item] = cls + finally: + _provider_lock.release() + return cls + + return wrapper + + +def get(network_os, module_name, connection_type): + network_os_providers = _registered_providers.get(network_os) + if network_os_providers is None: + raise ValueError("unable to find a suitable provider for this module") + if connection_type not in network_os_providers: + raise ValueError("provider does not support this connection type") + elif module_name not in network_os_providers[connection_type]: + raise ValueError("could not find a suitable provider for this module") + return network_os_providers[connection_type][module_name] + + +class ProviderBase(object): + + supported_connections = () + + def __init__(self, params, connection=None, check_mode=False): + self.params = params + self.connection = connection + self.check_mode = check_mode + + @property + def capabilities(self): + if not hasattr(self, "_capabilities"): + resp = self.from_json(self.connection.get_capabilities()) + setattr(self, "_capabilities", resp) + return getattr(self, "_capabilities") + + def get_value(self, path): + params = self.params.copy() + for key in path.split("."): + params = params[key] + return params + + def get_facts(self, subset=None): + raise NotImplementedError(self.__class__.__name__) + + def edit_config(self): + raise NotImplementedError(self.__class__.__name__) + + +class CliProvider(ProviderBase): + + supported_connections = ("network_cli",) + + @property + def capabilities(self): + if not hasattr(self, "_capabilities"): + resp = self.from_json(self.connection.get_capabilities()) + setattr(self, "_capabilities", resp) + return getattr(self, "_capabilities") + + def get_config_context(self, config, path, indent=1): + if config is not None: + netcfg = NetworkConfig(indent=indent, contents=config) + try: + config = netcfg.get_block_config(to_list(path)) + except ValueError: + config = None + return config + + def render(self, config=None): + raise NotImplementedError(self.__class__.__name__) + + def cli(self, command): + try: + if not hasattr(self, "_command_output"): + setattr(self, "_command_output", {}) + return self._command_output[command] + except KeyError: + out = self.connection.get(command) + try: + out = json.loads(out) + except ValueError: + pass + self._command_output[command] = out + return out + + def get_facts(self, subset=None): + return self.populate() + + def edit_config(self, config=None): + commands = self.render(config) + if commands and self.check_mode is False: + self.connection.edit_config(commands) + return commands diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/acls.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/acls.py new file mode 100644 index 00000000..dfd7c026 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/acls.py @@ -0,0 +1,268 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import re +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network_template import ( + NetworkTemplate, +) + + +def _tmplt_access_list_name(config_data): + command = "access-list {acls_name} ".format(**config_data) + return command + + +def _tmplt_access_list_entries(config_data): + if "aces" in config_data: + command = [] + + def source_destination_common_config(config_data, cmd, type): + if config_data["aces"][type].get("any"): + cmd += " any" + elif config_data["aces"][type].get("any4"): + cmd += " any4" + elif config_data["aces"][type].get("any6"): + cmd += " any6" + elif config_data["aces"][type].get("address"): + cmd += " {address}".format(**config_data["aces"][type]) + if config_data["aces"][type].get("netmask"): + cmd += " {netmask}".format(**config_data["aces"][type]) + elif config_data["aces"][type].get("host"): + cmd += " host {host}".format(**config_data["aces"][type]) + elif config_data["aces"][type].get("interface"): + cmd += " interface {interface}".format( + **config_data["aces"][type] + ) + elif config_data["aces"][type].get("object_group"): + cmd += " object-group {object_group}".format( + **config_data["aces"][type] + ) + if type == "destination" and config_data["aces"][type].get( + "service_object_group" + ): + cmd += " object-group {service_object_group}".format( + **config_data["aces"][type] + ) + if config_data["aces"].get("protocol_options"): + protocol_option_key = list( + config_data["aces"]["protocol_options"] + )[0] + if ( + isinstance( + config_data["aces"]["protocol_options"][ + protocol_option_key + ], + dict, + ) + and type == "destination" + ): + val = list( + config_data["aces"]["protocol_options"][ + protocol_option_key + ] + )[0] + cmd += " {0}".format(val.replace("_", "-")) + if config_data["aces"][type].get("port_protocol"): + if config_data["aces"][type].get("port_protocol").get("range"): + start = config_data["aces"][type].get("port_protocol")[ + "range" + ]["start"] + end = config_data["aces"][type].get("port_protocol")[ + "range" + ]["end"] + cmd += " range {0} {1}".format(start, end) + else: + port_protocol = list( + config_data["aces"][type]["port_protocol"] + )[0] + cmd += ( + " " + + port_protocol + + " " + + config_data["aces"][type]["port_protocol"][ + port_protocol + ] + ) + return cmd + + cmd = "" + if config_data["aces"].get("remark"): + command.append( + "access-list {name} line {line} remark {remark}".format( + **config_data["aces"] + ) + ) + if len(config_data["aces"]) > 4: + try: + cmd = "access-list {name} line {line}".format( + **config_data["aces"] + ) + except KeyError: + cmd = "access-list {name}".format(**config_data["aces"]) + if ( + config_data["aces"].get("acl_type") + and config_data["aces"].get("acl_type") != "standard" + ): + cmd += " {acl_type}".format(**config_data["aces"]) + if config_data["aces"].get("grant"): + cmd += " {grant}".format(**config_data["aces"]) + if config_data["aces"].get("protocol_options"): + if ( + "protocol_number" + in config_data["aces"]["protocol_options"] + ): + cmd += " {protocol_number}".format( + **config_data["aces"]["protocol_options"] + ) + else: + cmd += " {0}".format( + list(config_data["aces"]["protocol_options"])[0] + ) + elif config_data["aces"].get("protocol"): + cmd += " {protocol}".format(**config_data["aces"]) + if config_data["aces"].get("source"): + cmd = source_destination_common_config( + config_data, cmd, "source" + ) + if config_data["aces"].get("destination"): + cmd = source_destination_common_config( + config_data, cmd, "destination" + ) + if config_data["aces"].get("log"): + cmd += " log {log}".format(**config_data["aces"]) + if config_data["aces"].get("inactive"): + cmd += " inactive" + if config_data["aces"].get("time_range"): + cmd += " time-range {time_range}".format(**config_data["aces"]) + if cmd: + command.append(cmd) + return command + + +class AclsTemplate(NetworkTemplate): + def __init__(self, lines=None): + super(AclsTemplate, self).__init__(lines=lines, tmplt=self) + + PARSERS = [ + { + "name": "acls_name", + "getval": re.compile( + r"""^access-list* + \s*(?P<acl_name>\S+); + \s*\S+\s*elements; + """, + re.VERBOSE, + ), + "setval": _tmplt_access_list_name, + "compval": "name", + "result": {"acls": {"{{ acl_name }}": {"name": "{{ acl_name }}"}}}, + "shared": True, + }, + { + "name": "aces", + "getval": re.compile( + r"""^access-list* + \s*(?P<acl_name>\S+)* + \s*(?P<line>line\s\d+)* + \s*(?P<remark>remark\s\S.*)* + \s*(?P<ethertype>ethertype)* + \s*(?P<webtype>webtype)* + \s*(?P<acl_type>extended|standard)* + \s*(?P<grant>deny|permit)* + \s*(?P<ethertype_params>(dsap\s\S+)|bpdu|eii-ipx|ipx|mpls-unicast|mpls-multicast|isis|any\s)* + \s*(?P<std_dest>(host\s\S+)|any4|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})* + \s*(?P<protocol>ah|eigrp|esp|gre|icmp|icmp6|igmp|igrp|ip|ipinip|ipsec|nos|ospf|pcp|pim|pptp|sctp|snp|tcp|udp)* + \s*(?P<protocol_num>\d+\s)* + \s*(?P<source>any4|any6|any|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\s([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|(([a-f0-9:]+:+)+[a-f0-9]+\S+|host\s(([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|(([a-f0-9:]+:+)+[a-f0-9]+)\S+)|interface\s\S+|object-group\s\S+))* + \s*(?P<source_port_protocol>(eq|gts|lt|neq)\s(\S+|\d+)|range\s\S+\s\S+)* + \s*(?P<destination>any4|any6|any|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\s([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|(([a-f0-9:]+:+)+[a-f0-9]+\S+|host\s(([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|(([a-f0-9:]+:+)+[a-f0-9]+)\S+)|interface\s\S+|object-group\s\S+))* + \s*(?P<dest_svc_object_group>object-group\s\S+)* + \s*(?P<dest_port_protocol>(eq|gts|lt|neq)\s(\S+|\d+)|range\s\S+\s\S+)* + \s*(?P<icmp_icmp6_protocol>alternate-address|conversion-error|echo|echo-reply|information-reply|information-request|mask-reply|mask-request|membership-query|membership-reduction|membership-report|mobile-redirect|neighbor-advertisement|neighbor-redirect|neighbor-solicitation|parameter-problem|packet-too-big|redirect|router-advertisement|router-renumbering|router-solicitation|source-quench|source-route-failed|time-exceeded|timestamp-reply|timestamp-request|traceroute|unreachable)* + \s*(?P<log>log\s\S+)* + \s*(?P<time_range>time-range\s\S+)* + \s*(?P<inactive>inactive)* + """, + re.VERBOSE, + ), + "setval": _tmplt_access_list_entries, + "result": { + "acls": { + "{{ acl_name }}": { + "name": "{{ acl_name }}", + "acl_type": "{{ acl_type if acl_type is defined }}", + "aces": [ + { + "grant": "{{ grant }}", + "line": "{{ line.split(' ')[1] if line is defined }}", + "remark": "{{ remark.split('remark ')[1] if remark is defined }}", + "protocol": "{{ protocol if protocol is defined else None }}", + "protocol_number": "{{ protocol_num if protocol_num is defined }}", + "icmp_icmp6_protocol": "{{ icmp_icmp6_protocol if icmp_icmp6_protocol is defined else None }}", + "source": { + "address": "{% if source is defined and '.' in source and 'host'\ + not in source and 'object-group' not in source %}{{ source.split(' ')[0] }}{% elif source is defined and\ + '::' in source and 'host' not in source %}{{ source }}{% endif %}", + "netmask": "{{ source.split(' ')[1] if source\ + is defined and '.' in source and 'host' not in source else None and 'object-group' not in source }}", + "any4": "{{ True if source is defined and source == 'any4' else None }}", + "any6": "{{ True if source is defined and source == 'any6' else None }}", + "any": "{{ True if source is defined and source == 'any' else None }}", + "host": "{{ source.split(' ')[1] if source is defined and 'host' in source else None }}", + "interface": "{{ source.split(' ')[1] if source is defined and 'interface' in source else None }}", + "object_group": "{{ source.split(' ')[1] if source is defined and 'object-group' in source else None }}", + "port_protocol": { + "{{ source_port_protocol.split(' ')[0] if source_port_protocol\ + is defined and 'range' not in source_port_protocol else None }}": "{{ source_port_protocol.split(' ')[1]\ + if source_port_protocol is defined and 'range' not in source_port_protocol else None }}", + "{{ 'range' }}": { + "start": "{{ source_port_protocol.split(' ')[1] if source_port_protocol is defined and\ + 'range' in source_port_protocol else None }}", + "end": "{{ source_port_protocol.split(' ')[2] if source_port_protocol is defined and\ + 'range' in source_port_protocol else None }}", + }, + }, + }, + "destination": { + "address": "{% if destination is defined and 'host' not in destination and\ + '.' in destination and\ + 'object-group' not in destination %}{{ destination.split(' ')[0] }}{% elif std_dest is defined and\ + '.' in std_dest and 'host' not in std_dest %}{{ std_dest.split(' ')[0] }}{% elif destination is defined and\ + '::' in destination %}{{ destination }}{% endif %}", + "netmask": "{% if destination is defined and 'host' not in destination and\ + '.' in destination and\ + 'object-group' not in destination %}{{ destination.split(' ')[1] }}{% elif std_dest is defined and\ + '.' in std_dest and 'host' not in std_dest %}{{ std_dest.split(' ')[1] }}{% endif %}", + "any4": "{% if destination is defined and\ + destination == 'any4' %}{{ True }}{% elif std_dest is defined and std_dest == 'any4' %}{{ True }}{% endif %}", + "any6": "{{ True if destination is defined and destination == 'any6' else None }}", + "any": "{{ True if destination is defined and destination == 'any' else None }}", + "host": "{% if destination is defined and\ + 'host' in destination %}{{ destination.split(' ')[1] }}{% elif std_dest is defined and\ + 'host' in std_dest %}{{ std_dest.split(' ')[1] }}{% endif %}", + "interface": "{{ destination.split(' ')[1] if destination is defined and 'interface' in destination else None }}", + "object_group": "{{ destination.split(' ')[1] if destination is defined and 'object-group' in destination else None }}", + "service_object_group": "{{ dest_svc_object_group.split('object-group ')[1] if dest_svc_object_group is defined }}", + "port_protocol": { + "{{ dest_port_protocol.split(' ')[0] if dest_port_protocol\ + is defined and 'range' not in dest_port_protocol else None }}": "{{ dest_port_protocol.split(' ')[1]\ + if dest_port_protocol is defined and 'range' not in dest_port_protocol else None }}", + "{{ 'range' }}": { + "start": "{{ dest_port_protocol.split(' ')[1] if dest_port_protocol is defined and\ + 'range' in dest_port_protocol }}", + "end": "{{ dest_port_protocol.split(' ')[2] if dest_port_protocol is defined and\ + 'range' in dest_port_protocol }}", + }, + }, + }, + "inactive": "{{ True if inactive is defined }}", + "log": "{{ log.split('log ')[1] if log is defined }}", + "time_range": "{{ time_range if time_range is defined }}", + } + ], + } + } + }, + }, + ] diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/ogs.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/ogs.py new file mode 100644 index 00000000..2163f10b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/ogs.py @@ -0,0 +1,543 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import re +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network_template import ( + NetworkTemplate, +) + + +def _tmplt_object_group(config_data): + command = "object-group {object_type} {name}".format(**config_data) + return command + + +def _tmplt_icmp_object(config_data): + commands = [] + if config_data.get("icmp_type").get("icmp_object"): + for each in config_data.get("icmp_type").get("icmp_object"): + commands.append("icmp-object {0}".format(each)) + return commands + + +def _tmplt_network_object(config_data): + commands = [] + if config_data.get("network_object").get("host"): + for each in config_data.get("network_object").get("host"): + commands.append("network-object host {0}".format(each)) + return commands + + +def _tmplt_network_object_address(config_data): + commands = [] + if config_data.get("network_object").get("address"): + for each in config_data.get("network_object").get("address"): + commands.append("network-object {0}".format(each)) + return commands + + +def _tmplt_network_object_ipv6(config_data): + commands = [] + if config_data.get("network_object").get("ipv6_address"): + for each in config_data.get("network_object").get("ipv6_address"): + commands.append("network-object {0}".format(each)) + return commands + + +def _tmplt_network_object_object(config_data): + commands = [] + if config_data.get("network_object").get("object"): + for each in config_data.get("network_object").get("object"): + commands.append("network-object object {0}".format(each)) + return commands + + +def _tmplt_protocol_object(config_data): + commands = [] + if config_data.get("protocol_object").get("protocol"): + for each in config_data.get("protocol_object").get("protocol"): + commands.append("protocol {0}".format(each)) + return commands + + +def _tmplt_sec_group_name(config_data): + commands = [] + if config_data.get("security_group").get("sec_name"): + for each in config_data.get("security_group").get("sec_name"): + commands.append("security-group name {0}".format(each)) + return commands + + +def _tmplt_sec_group_tag(config_data): + commands = [] + if config_data.get("security_group").get("tag"): + for each in config_data.get("security_group").get("tag"): + commands.append("security-group tag {0}".format(each)) + return commands + + +def _tmplt_service_object(config_data): + if config_data.get("service_object").get("protocol"): + commands = [] + for each in config_data.get("service_object").get("protocol"): + commands.append("service-object {0}".format(each)) + return commands + + +def _tmplt_services_object(config_data): + if config_data.get("services_object"): + cmd = "service-object {protocol}".format( + **config_data["services_object"] + ) + if config_data["services_object"].get("source_port"): + if config_data["services_object"]["source_port"].get("range"): + cmd += " source range {start} {end}".format( + **config_data["services_object"]["source_port"]["range"] + ) + else: + key = list(config_data["services_object"]["source_port"])[0] + cmd += " source {0} {1}".format( + key, config_data["services_object"]["source_port"][key] + ) + if config_data["services_object"].get("destination_port"): + if config_data["services_object"]["destination_port"].get("range"): + cmd += " destination range {start} {end}".format( + **config_data["services_object"]["destination_port"][ + "range" + ] + ) + else: + key = list(config_data["services_object"]["destination_port"])[ + 0 + ] + cmd += " destination {0} {1}".format( + key, + config_data["services_object"]["destination_port"][key], + ) + return cmd + + +def _tmplt_port_object(config_data): + if config_data.get("port_object"): + cmd = "port-object" + if config_data["port_object"].get("range"): + cmd += " range {start} {end}".format( + **config_data["port_object"]["range"] + ) + else: + key = list(config_data["port_object"])[0] + cmd += " {0} {1}".format(key, config_data["port_object"][key]) + return cmd + + +def _tmplt_user_object_user(config_data): + commands = [] + if config_data.get("user_object").get("user"): + for each in config_data.get("user_object").get("user"): + commands.append("user {domain}\\{name}".format(**each)) + return commands + + +def _tmplt_user_object_user_gp(config_data): + commands = [] + if config_data.get("user_object").get("user_group"): + for each in config_data.get("user_object").get("user_group"): + commands.append(r"user-group {domain}\\{name}".format(**each)) + return commands + + +def _tmplt_group_object(config_data): + command = "group-object {group_object}".format(**config_data) + return command + + +class OGsTemplate(NetworkTemplate): + def __init__(self, lines=None): + super(OGsTemplate, self).__init__(lines=lines, tmplt=self) + + PARSERS = [ + { + "name": "og_name", + "getval": re.compile( + r""" + ^object-group* + \s*(?P<obj_type>\S+)* + \s*(?P<obj_name>\S+)* + \s*(?P<protocol>\S+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_object_group, + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "object_type": "{{ obj_type }}", + "name": "{{ obj_name }}", + "protocol": "{{ protocol }}", + } + } + } + }, + "shared": True, + }, + { + "name": "description", + "getval": re.compile( + r"""\s+description:* + \s*(?P<description>.+) + *$""", + re.VERBOSE, + ), + "setval": "description {{ description }}", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"description": "{{ description }}"} + } + } + }, + }, + { + "name": "icmp_type", + "getval": re.compile( + r"""\s+icmp-object* + \s*(?P<object>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_icmp_object, + "compval": "icmp_type", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"icmp_object": ["{{ object }}"]} + } + } + }, + }, + { + "name": "network_object.address", + "getval": re.compile( + r"""\s+network-object* + \s*(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_network_object_address, + "compval": "network_object.address", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"address": ["{{ address }}"]} + } + } + }, + }, + { + "name": "network_object.ipv6_address", + "getval": re.compile( + r"""\s+network-object* + \s*(?P<ipv6>\S+::/\d+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_network_object_ipv6, + "compval": "network_object.ipv6_address", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"ipv6_address": ["{{ ipv6 }}"]} + } + } + }, + }, + { + "name": "network_object.host", + "getval": re.compile( + r"""\s+network-object* + \s*(?P<host_obj>host)* + \s*(?P<host_address>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_network_object, + "compval": "network_object.host", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"host": ["{{ host_address }}"]} + } + } + }, + }, + { + "name": "network_object.object", + "getval": re.compile( + r"""\s+network-object\s + object* + \s*(?P<object>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_network_object_object, + "compval": "network_object.object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"object": ["{{ object }}"]} + } + } + }, + }, + { + "name": "protocol_object", + "getval": re.compile( + r"""\s+protocol-object* + \s*(?P<protocol>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_protocol_object, + "compval": "protocol_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"protocol": ["{{ protocol }}"]} + } + } + }, + }, + { + "name": "security_group.sec_name", + "getval": re.compile( + r"""\s+security-group\s + name* + \s*(?P<name>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_sec_group_name, + "compval": "security_group.sec_name", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"sec_name": ["{{ name }}"]} + } + } + }, + }, + { + "name": "security_group.tag", + "getval": re.compile( + r"""\s+security-group\s + tag* + \s*(?P<tag>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_sec_group_tag, + "compval": "security_group.tag", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"tag": ["{{ tag }}"]} + } + } + }, + }, + { + "name": "port_object", + "getval": re.compile( + r"""\s+port-object* + \s*(?P<eq>eq\s\S+)* + \s*(?P<range>range\s(\S+|\d+)\s(\S+|\d+)) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_port_object, + "compval": "port_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "port_object": [ + { + "eq": "{{ eq.split(' ')[1] if eq is defined }}", + "range": { + "start": "{{ range.split('range ')[1].split(' ')[0] if range is defined else None }}", + "end": "{{ range.split('range ')[1].split(' ')[1] if range is defined else None }}", + }, + } + ] + } + } + } + }, + }, + { + "name": "services_object", + "getval": re.compile( + r"""\s+service-object* + \s*(?P<protocol>\S+)* + \s*(?P<source_port>source\s((eq|gts|lt|neq)\s(\S+|\d+)|(range\s(\S+|\S+)\s(\S+|\S+))))* + \s*(?P<destination_port>destination\s((eq|gt|lt|neq)\s(\S+|\d+)|(range\s(\S+|\S+)\s(\S+|\S+)))) + *""", + re.VERBOSE, + ), + "setval": _tmplt_services_object, + "compval": "services_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "services_object": [ + { + "protocol": "{{ protocol }}", + "source_port": { + "eq": "{{ source_port.split(' ')[2] if source_port is defined and\ + 'eq' in source_port and 'range' not in source_port }}", + "gt": "{{ source_port.split(' ')[2] if source_port is defined and\ + 'gt' in source_port and 'range' not in source_port }}", + "lt": "{{ source_port.split(' ')[2] if source_port is defined and\ + 'lt' in source_port and 'range' not in source_port }}", + "neq": "{{ source_port.split(' ')[2] if source_port is defined and\ + 'neq' in source_port and 'range' not in source_port }}", + "range": { + "start": "{{ source_port.split('range ')[1].split(' ')[0] if source_port is defined and\ + 'range' in source_port else None }}", + "end": "{{ source_port.split('range ')[1].split(' ')[1] if source_port is defined and\ + 'range' in source_port else None }}", + }, + }, + "destination_port": { + "eq": "{{ destination_port.split(' ')[2] if destination_port is defined and\ + 'eq' in destination_port and 'range' not in destination_port }}", + "gt": "{{ destination_port.split(' ')[2] if destination_port is defined and\ + 'gt' in destination_port and 'range' not in destination_port }}", + "lt": "{{ destination_port.split(' ')[2] if destination_port is defined and\ + 'lt' in destination_port and 'range' not in destination_port }}", + "neq": "{{ destination_port.split(' ')[2] if destination_port is defined and\ + 'neq' in destination_port and 'range' not in destination_port }}", + "range": { + "start": "{{ destination_port.split('range ')[1].split(' ')[0] if destination_port is defined and\ + 'range' in destination_port else None }}", + "end": "{{ destination_port.split('range ')[1].split(' ')[1] if destination_port is defined and\ + 'range' in destination_port else None }}", + }, + }, + } + ] + } + } + } + }, + }, + { + "name": "service_object.object", + "getval": re.compile( + r"""\s+service-object\s + object* + \s*(?P<object>\S+) + *$""", + re.VERBOSE, + ), + "setval": "service-object object {{ object }}", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"object": "{{ object }}"} + } + } + }, + }, + { + "name": "service_object", + "getval": re.compile( + r"""\s+service-object* + \s*(?P<protocol>\S+)*\s + *$""", + re.VERBOSE, + ), + "setval": _tmplt_service_object, + "compval": "service_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"protocol": ["{{ protocol }}"]} + } + } + }, + }, + { + "name": "user_object.user", + "getval": re.compile( + r"""\s+user* + \s*(?P<domain>\S+)\\ + (?P<user_name>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_user_object_user, + "compval": "user_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "user": [ + { + "name": "{{ user_name }}", + "domain": "{{ domain }}", + } + ] + } + } + } + }, + }, + { + "name": "user_object.user_gp", + "getval": re.compile( + r"""\s+user-group* + \s*(?P<domain>\S+\\) + (?P<user_gp>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_user_object_user_gp, + "compval": "user_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "user_group": [ + { + "name": "{{ user_gp }}", + "domain": r"{{ domain.split('\\')[0] }}", + } + ] + } + } + } + }, + }, + { + "name": "group_object", + "getval": re.compile( + r"""\s+group-object* + \s*(?P<gp_obj>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_group_object, + "compval": "group_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"group_object": ["{{ gp_obj }}"]} + } + } + }, + }, + ] diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/utils.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/utils.py new file mode 100644 index 00000000..9cb8fb03 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/utils.py @@ -0,0 +1,328 @@ +# +# -*- 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) + +# utils + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import socket +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + is_masklen, + to_netmask, +) + + +def remove_duplicate_cmd(cmd, commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if cmd in each: + if each not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + + +def remove_command_from_config_list(interface, cmd, commands): + # To delete the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append("no %s" % cmd) + return commands + + +def add_command_to_config_list(interface, cmd, commands): + # To set the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append(cmd) + + +def check_n_return_valid_ipv6_addr(module, input_list, filtered_ipv6_list): + # To verify the valid ipv6 address + try: + for each in input_list: + if "::" in each: + if "/" in each: + each = each.split("/")[0] + if socket.inet_pton(socket.AF_INET6, each): + filtered_ipv6_list.append(each) + return filtered_ipv6_list + except socket.error: + module.fail_json(msg="Incorrect IPV6 address!") + + +def new_dict_to_set(input_dict, temp_list, test_set, count=0): + # recursive function to convert input dict to set for comparision + test_dict = dict() + if isinstance(input_dict, dict): + input_dict_len = len(input_dict) + for k, v in sorted(iteritems(input_dict)): + count += 1 + if isinstance(v, list): + temp_list.append(k) + for each in v: + if isinstance(each, dict): + if [True for i in each.values() if type(i) == list]: + new_dict_to_set(each, temp_list, test_set, count) + else: + new_dict_to_set(each, temp_list, test_set, 0) + else: + if v is not None: + test_dict.update({k: v}) + try: + if ( + tuple(iteritems(test_dict)) not in test_set + and count == input_dict_len + ): + test_set.add(tuple(iteritems(test_dict))) + count = 0 + except TypeError: + temp_dict = {} + + def expand_dict(dict_to_expand): + temp = dict() + for k, v in iteritems(dict_to_expand): + if isinstance(v, dict): + expand_dict(v) + else: + if v is not None: + temp.update({k: v}) + temp_dict.update(tuple(iteritems(temp))) + + new_dict = {k: v} + expand_dict(new_dict) + if tuple(iteritems(temp_dict)) not in test_set: + test_set.add(tuple(iteritems(temp_dict))) + + +def dict_to_set(sample_dict): + # Generate a set with passed dictionary for comparison + test_dict = dict() + if isinstance(sample_dict, dict): + for k, v in iteritems(sample_dict): + if v is not None: + if isinstance(v, list): + if isinstance(v[0], dict): + li = [] + for each in v: + for key, value in iteritems(each): + if isinstance(value, list): + each[key] = tuple(value) + li.append(tuple(iteritems(each))) + v = tuple(li) + else: + v = tuple(v) + elif isinstance(v, dict): + li = [] + for key, value in iteritems(v): + if isinstance(value, list): + v[key] = tuple(value) + li.extend(tuple(iteritems(v))) + v = tuple(li) + test_dict.update({k: v}) + return_set = set(tuple(iteritems(test_dict))) + else: + return_set = set(sample_dict) + return return_set + + +def filter_dict_having_none_value(want, have): + # Generate dict with have dict value which is None in want dict + test_dict = dict() + test_key_dict = dict() + name = want.get("name") + if name: + test_dict["name"] = name + diff_ip = False + want_ip = "" + for k, v in iteritems(want): + if isinstance(v, dict): + for key, value in iteritems(v): + if value is None: + dict_val = have.get(k).get(key) + test_key_dict.update({key: dict_val}) + test_dict.update({k: test_key_dict}) + if isinstance(v, list): + for key, value in iteritems(v[0]): + if value is None: + dict_val = have.get(k).get(key) + test_key_dict.update({key: dict_val}) + test_dict.update({k: test_key_dict}) + # below conditions checks are added to check if + # secondary IP is configured, if yes then delete + # the already configured IP if want and have IP + # is different else if it's same no need to delete + for each in v: + if each.get("secondary"): + want_ip = each.get("address").split("/") + have_ip = have.get("ipv4") + if ( + len(want_ip) > 1 + and have_ip + and have_ip[0].get("secondary") + ): + have_ip = have_ip[0]["address"].split(" ")[0] + if have_ip != want_ip[0]: + diff_ip = True + if each.get("secondary") and diff_ip is True: + test_key_dict.update({"secondary": True}) + test_dict.update({"ipv4": test_key_dict}) + if v is None: + val = have.get(k) + test_dict.update({k: val}) + return test_dict + + +def remove_duplicate_interface(commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if "interface" in each: + if each not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + + +def validate_ipv4(value, module): + if value: + address = value.split("/") + if len(address) != 2: + module.fail_json( + msg="address format is <ipv4 address>/<mask>, got invalid format {0}".format( + value + ) + ) + + if not is_masklen(address[1]): + module.fail_json( + msg="invalid value for mask: {0}, mask should be in range 0-32".format( + address[1] + ) + ) + + +def validate_ipv6(value, module): + if value: + address = value.split("/") + if len(address) != 2: + module.fail_json( + msg="address format is <ipv6 address>/<mask>, got invalid format {0}".format( + value + ) + ) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json( + msg="invalid value for mask: {0}, mask should be in range 0-128".format( + address[1] + ) + ) + + +def validate_n_expand_ipv4(module, want): + # Check if input IPV4 is valid IP and expand IPV4 with its subnet mask + ip_addr_want = want.get("address") + if len(ip_addr_want.split(" ")) > 1: + return ip_addr_want + validate_ipv4(ip_addr_want, module) + ip = ip_addr_want.split("/") + if len(ip) == 2: + ip_addr_want = "{0} {1}".format(ip[0], to_netmask(ip[1])) + + return ip_addr_want + + +def normalize_interface(name): + """Return the normalized interface name""" + if not name: + return + + def _get_number(name): + digits = "" + for char in name: + if char.isdigit() or char in "/.": + digits += char + return digits + + if name.lower().startswith("gi"): + if_type = "GigabitEthernet" + elif name.lower().startswith("te"): + if_type = "TenGigabitEthernet" + elif name.lower().startswith("fa"): + if_type = "FastEthernet" + elif name.lower().startswith("fo"): + if_type = "FortyGigabitEthernet" + elif name.lower().startswith("long"): + if_type = "LongReachEthernet" + elif name.lower().startswith("et"): + if_type = "Ethernet" + elif name.lower().startswith("vl"): + if_type = "Vlan" + elif name.lower().startswith("lo"): + if_type = "loopback" + elif name.lower().startswith("po"): + if_type = "Port-channel" + elif name.lower().startswith("nv"): + if_type = "nve" + elif name.lower().startswith("twe"): + if_type = "TwentyFiveGigE" + elif name.lower().startswith("hu"): + if_type = "HundredGigE" + else: + if_type = None + + number_list = name.split(" ") + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = _get_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface + + +def get_interface_type(interface): + """Gets the type of interface""" + + if interface.upper().startswith("GI"): + return "GigabitEthernet" + elif interface.upper().startswith("TE"): + return "TenGigabitEthernet" + elif interface.upper().startswith("FA"): + return "FastEthernet" + elif interface.upper().startswith("FO"): + return "FortyGigabitEthernet" + elif interface.upper().startswith("LON"): + return "LongReachEthernet" + elif interface.upper().startswith("ET"): + return "Ethernet" + elif interface.upper().startswith("VL"): + return "Vlan" + elif interface.upper().startswith("LO"): + return "loopback" + elif interface.upper().startswith("PO"): + return "Port-channel" + elif interface.upper().startswith("NV"): + return "nve" + elif interface.upper().startswith("TWE"): + return "TwentyFiveGigE" + elif interface.upper().startswith("HU"): + return "HundredGigE" + else: + return "unknown" diff --git a/ansible_collections/cisco/asa/plugins/modules/__init__.py b/ansible_collections/cisco/asa/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/modules/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/modules/asa_acls.py b/ansible_collections/cisco/asa/plugins/modules/asa_acls.py new file mode 100644 index 00000000..78abf1b3 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/modules/asa_acls.py @@ -0,0 +1,1261 @@ +#!/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 asa_acls +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = """ +module: asa_acls +short_description: Access-Lists resource module +description: This module configures and manages the named or numbered ACLs on ASA + platforms. +version_added: 1.0.0 +author: Sumit Jaiswal (@justjais) +notes: +- Tested against Cisco ASA Version 9.10(1)11 +- This module works with connection C(network_cli). See L(ASA Platform Options,../network/user_guide/platform_asa.html). +options: + config: + description: A dictionary of ACL options. + type: dict + suboptions: + acls: + description: + - A list of Access Control Lists (ACL). + type: list + elements: dict + suboptions: + name: + description: The name or the number of the ACL. + required: true + type: str + acl_type: + description: + - ACL type + type: str + choices: + - extended + - standard + rename: + description: + - Rename an existing access-list. + - If input to rename param is given, it'll take preference over other + parameters and only rename config will be matched and computed against. + type: str + aces: + description: The entries within the ACL. + elements: dict + type: list + suboptions: + grant: + description: Specify the action. + type: str + choices: + - permit + - deny + line: + description: + - Use this to specify line number at which ACE should be entered. + - Existing ACE can be updated based on the input line number. + - It's not a required param in case of configuring the acl, but in + case of Delete operation it's required, else Delete operation won't + work as expected. + - Refer to vendor documentation for valid values. + type: int + remark: + description: + - Specify a comment (remark) for the access-list after this keyword + type: str + protocol: + description: + - Specify the protocol to match. + - Refer to vendor documentation for valid values. + type: str + protocol_options: + description: protocol type. + type: dict + suboptions: + protocol_number: + description: An IP protocol number + type: int + ahp: + description: Authentication Header Protocol. + type: bool + eigrp: + description: Cisco's EIGRP routing protocol. + type: bool + esp: + description: Encapsulation Security Payload. + type: bool + gre: + description: Cisco's GRE tunneling. + type: bool + icmp: + description: Internet Control Message Protocol. + type: dict + suboptions: + alternate_address: + description: Alternate address + type: bool + conversion_error: + description: Datagram conversion + type: bool + echo: + description: Echo (ping) + type: bool + echo_reply: + description: Echo reply + type: bool + information_reply: + description: Information replies + type: bool + information_request: + description: Information requests + type: bool + mask_reply: + description: Mask replies + type: bool + mask_request: + description: mask_request + type: bool + mobile_redirect: + description: Mobile host redirect + type: bool + parameter_problem: + description: All parameter problems + type: bool + redirect: + description: All redirects + type: bool + router_advertisement: + description: Router discovery advertisements + type: bool + router_solicitation: + description: Router discovery solicitations + type: bool + source_quench: + description: Source quenches + type: bool + source_route_failed: + description: Source route + type: bool + time_exceeded: + description: All time exceededs + type: bool + timestamp_reply: + description: Timestamp replies + type: bool + timestamp_request: + description: Timestamp requests + type: bool + traceroute: + description: Traceroute + type: bool + unreachable: + description: All unreachables + type: bool + icmp6: + description: Internet Control Message Protocol. + type: dict + suboptions: + echo: + description: Echo (ping) + type: bool + echo_reply: + description: Echo reply + type: bool + membership_query: + description: Membership query + type: bool + membership_reduction: + description: Membership reduction + type: bool + membership_report: + description: Membership report + type: bool + neighbor_advertisement: + description: Neighbor advertisement + type: bool + neighbor_redirect: + description: Neighbor redirect + type: bool + neighbor_solicitation: + description: Neighbor_solicitation + type: bool + packet_too_big: + description: Packet too big + type: bool + parameter_problem: + description: Parameter problem + type: bool + router_advertisement: + description: Router discovery advertisements + type: bool + router_renumbering: + description: Router renumbering + type: bool + router_solicitation: + description: Router solicitation + type: bool + time_exceeded: + description: Time exceeded + type: bool + unreachable: + description: All unreachables + type: bool + igmp: + description: Internet Gateway Message Protocol. + type: bool + igrp: + description: Internet Gateway Routing Protocol. + type: bool + ip: + description: Any Internet Protocol. + type: bool + ipinip: + description: IP in IP tunneling. + type: bool + ipsec: + description: IP Security. + type: bool + nos: + description: KA9Q NOS compatible IP over IP tunneling. + type: bool + ospf: + description: OSPF routing protocol. + type: bool + pcp: + description: Payload Compression Protocol. + type: bool + pim: + description: Protocol Independent Multicast. + type: bool + pptp: + description: Point-to-Point Tunneling Protocol. + type: bool + sctp: + description: Stream Control Transmission Protocol. + type: bool + snp: + description: Simple Network Protocol. + type: bool + udp: + description: User Datagram Protocol. + type: bool + tcp: + description: Match TCP packet flags + type: bool + source: + description: Specify the packet source. + type: dict + suboptions: + address: + description: Source network address. + type: str + netmask: + description: Netmask for source IP address, valid with IPV4 address. + type: str + any: + description: + - Match any source address. + type: bool + any4: + description: + - Match any ipv4 source address. + type: bool + any6: + description: + - Match any ipv6 source address. + type: bool + host: + description: A single source host + type: str + interface: + description: Use interface address as source address + type: str + object_group: + description: Network object-group for source address + type: str + port_protocol: + description: + - Specify the destination port along with protocol. + - Note, Valid with TCP/UDP protocol_options + type: dict + suboptions: + eq: + description: Match only packets on a given port number. + type: str + gt: + description: Match only packets with a greater port number. + type: str + lt: + description: Match only packets with a lower port number. + type: str + neq: + description: Match only packets not on a given port number. + type: str + range: + description: Port range operator + type: dict + suboptions: + start: + description: Specify the start of the port range. + type: int + end: + description: Specify the end of the port range. + type: int + destination: + description: Specify the packet destination. + type: dict + suboptions: + address: + description: Host address to match, or any single host address. + type: str + netmask: + description: Netmask for destination IP address, valid with IPV4 + address. + type: str + any: + description: Match any destination address. + type: bool + any4: + description: + - Match any ipv4 destination address. + type: bool + any6: + description: + - Match any ipv6 destination address. + type: bool + host: + description: A single destination host + type: str + interface: + description: Use interface address as destination address + type: str + object_group: + description: Network object-group for destination address + type: str + service_object_group: + description: Service object-group for destination port + type: str + port_protocol: + description: + - Specify the destination port along with protocol. + - Note, Valid with TCP/UDP protocol_options + type: dict + suboptions: + eq: + description: Match only packets on a given port number. + type: str + gt: + description: Match only packets with a greater port number. + type: str + lt: + description: Match only packets with a lower port number. + type: str + neq: + description: Match only packets not on a given port number. + type: str + range: + description: Port range operator + type: dict + suboptions: + start: + description: Specify the start of the port range. + type: int + end: + description: Specify the end of the port range. + type: int + inactive: + description: Keyword for disabling an ACL element. + type: bool + log: + description: Log matches against this entry. + type: str + choices: + - default + - alerts + - critical + - debugging + - disable + - emergencies + - errors + - informational + - interval + - notifications + - warnings + time_range: + description: Specify a time-range. + 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. + type: str + state: + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged + description: + - The state of the configuration after module completion + type: str + +""" + +EXAMPLES = """ +# Using merged +# Before state: +# ------------- +# +# vasa#sh access-lists +# access-list global_access; 2 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 +# access-list global_access line 2 extended deny tcp any any eq telnet (hitcnt=0) 0xae5833af +# access-list R1_traffic; 1 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 + +- name: Merge provided configuration with device configuration + cisco.asa.asa_acls: + config: + acls: + - name: temp_access + acl_type: extended + aces: + - grant: deny + line: 1 + protocol_options: + tcp: true + source: + address: 192.0.2.0 + netmask: 255.255.255.0 + destination: + address: 192.0.3.0 + netmask: 255.255.255.0 + port_protocol: + eq: www + log: default + - grant: deny + line: 2 + protocol_options: + igrp: true + source: + address: 198.51.100.0 + netmask: 255.255.255.0 + destination: + address: 198.51.110.0 + netmask: 255.255.255.0 + time_range: temp + - grant: deny + line: 3 + protocol_options: + tcp: true + source: + interface: management + destination: + interface: management + port_protocol: + eq: www + log: warnings + - grant: deny + line: 4 + protocol_options: + tcp: true + source: + object_group: test_og_network + destination: + object_group: test_network_og + port_protocol: + eq: www + log: default + - name: global_access + acl_type: extended + aces: + - line: 3 + remark: test global access + - grant: deny + line: 4 + protocol_options: + tcp: true + source: + any: true + destination: + any: true + port_protocol: + eq: www + log: errors + - name: R1_traffic + aces: + - line: 1 + remark: test_v6_acls + - grant: deny + line: 2 + protocol_options: + tcp: true + source: + address: 2001:db8:0:3::/64 + port_protocol: + eq: www + destination: + address: 2001:fc8:0:4::/64 + port_protocol: + eq: telnet + inactive: true + state: merged + +# Commands fired: +# --------------- +# access-list global_access line 3 remark test global access +# access-list global_access line 4 extended deny tcp any any eq www log errors interval 300 +# access-list R1_traffic line 1 remark test_v6_acls +# access-list R1_traffic line 2 extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet inactive +# access-list temp_access line 1 extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www log default +# access-list temp_access line 2 extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp inactive +# access-list temp_access line 2 extended deny tcp interface management interface management +# eq www log warnings +# access-list test_access line 3 extended deny tcp object-group test_og_network object-group test_network_og +# eq www log default + +# After state: +# ------------ +# +# vasa#sh access-lists +# access-list global_access; 3 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 +# access-list global_access line 2 extended deny tcp any any eq telnet (hitcnt=0) 0xae5833af +# access-list global_access line 3 remark test global access (hitcnt=0) 0xae78337e +# access-list global_access line 4 extended deny tcp any any eq www log errors interval 300 (hitcnt=0) 0x605f2421 +# access-list R1_traffic; 2 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 remark test_v6_acls +# access-list R1_traffic line 2 +# extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet +# inactive (hitcnt=0) (inactive) 0xe922b432 +# access-list temp_access; 2 elements; name hash: 0xaf1b712e +# access-list temp_access line 1 +# extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www +# log default (hitcnt=0) 0xb58abb0d +# access-list temp_access line 2 +# extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp (hitcnt=0) (inactive) 0xcd6b92ae +# access-list test_access line 3 +# extended deny tcp interface management interface management eq www log warnings +# interval 300 (hitcnt=0) 0x78aa233d +# access-list test_access line 2 extended deny tcp object-group test_og_network object-group test_network_og +# eq www log default (hitcnt=0) 0x477aec1e +# access-list test_access line 2 extended deny tcp 192.0.2.0 255.255.255.0 host 192.0.3.1 eq www +# log default (hitcnt=0) 0xdc7edff8 +# access-list test_access line 2 extended deny tcp 192.0.2.0 255.255.255.0 host 192.0.3.2 eq www +# log default (hitcnt=0) 0x7b0e9fde +# access-list test_access line 2 extended deny tcp 198.51.100.0 255.255.255.0 2001:db8:3::/64 eq www +# log default (hitcnt=0) 0x97c75adc + +# Using Merged to Rename ACLs +# Before state: +# ------------- +# +# vasa#sh access-lists +# access-list global_access; 2 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 +# access-list global_access line 2 extended deny tcp any any eq telnet (hitcnt=0) 0xae5833af +# access-list R1_traffic; 1 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 + +- name: Rename ACL with different name using Merged state + cisco.asa.asa_acls: + config: + acls: + - name: global_access + rename: global_access_renamed + - name: R1_traffic + rename: R1_traffic_renamed + state: merged + +# Commands fired: +# --------------- +# access-list global_access rename global_access_renamed +# access-list R1_traffic rename R1_traffic_renamed + +# After state: +# ------------- +# +# vasa#sh access-lists +# access-list global_access_renamed; 2 elements; name hash: 0xbd6c87a7 +# access-list global_access_renamed line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 +# access-list global_access_renamed line 2 extended deny tcp any any eq telnet (hitcnt=0) 0xae5833af +# access-list R1_traffic_renamed; 1 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic_renamed line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 + + +# Using replaced + +# Before state: +# ------------- +# +# vasa#sh access-lists +# access-list global_access; 3 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 +# access-list global_access line 2 extended deny tcp any any eq telnet (hitcnt=0) 0xae5833af +# access-list global_access line 3 extended deny tcp any any eq www log errors interval 300 (hitcnt=0) 0x605f2421 +# access-list R1_traffic; 2 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 +# access-list R1_traffic line 2 +# extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet +# inactive (hitcnt=0) (inactive) 0xe922b432 +# access-list temp_access; 2 elements; name hash: 0xaf1b712e +# access-list temp_access line 1 +# extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www +# log default (hitcnt=0) 0xb58abb0d +# access-list temp_access line 2 +# extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp (hitcnt=0) (inactive) 0xcd6b92ae + +- name: Replaces device configuration of listed acl with provided configuration + cisco.asa.asa_acls: + config: + acls: + - name: global_access + acl_type: extended + aces: + - grant: deny + line: 1 + protocol_options: + tcp: true + source: + address: 192.0.4.0 + netmask: 255.255.255.0 + port_protocol: + eq: telnet + destination: + address: 192.0.5.0 + netmask: 255.255.255.0 + port_protocol: + eq: www + state: replaced + +# Commands fired: +# --------------- +# no access-list global_access line 3 extended deny tcp any any eq www log errors interval 300 +# no access-list global_access line 2 extended deny tcp any any eq telnet +# no access-list global_access line 1 extended permit icmp any any log disable +# access-list global_access line 1 extended deny tcp 192.0.4.0 255.255.255.0 eq telnet 192.0.5.0 255.255.255.0 eq www + +# After state: +# ------------- +# +# vasa#sh access-lists +# access-list global_access; 1 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended deny tcp 192.0.4.0 255.255.255.0 eq telnet +# 192.0.5.0 255.255.255.0 eq www (hitcnt=0) 0x3e5b2757 +# access-list R1_traffic; 2 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 +# access-list R1_traffic line 2 +# extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet +# inactive (hitcnt=0) (inactive) 0xe922b432 +# access-list temp_access; 2 elements; name hash: 0xaf1b712e +# access-list temp_access line 1 +# extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www +# log default (hitcnt=0) 0xb58abb0d +# access-list temp_access line 2 +# extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp (hitcnt=0) (inactive) 0xcd6b92ae + +# Using overridden + +# Before state: +# ------------- +# +# vasa#sh access-lists +# access-list global_access; 3 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 +# access-list global_access line 2 extended deny tcp any any eq telnet (hitcnt=0) 0xae5833af +# access-list global_access line 3 extended deny tcp any any eq www log errors interval 300 (hitcnt=0) 0x605f2421 +# access-list R1_traffic; 2 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 +# access-list R1_traffic line 2 +# extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet +# inactive (hitcnt=0) (inactive) 0xe922b432 +# access-list temp_access; 2 elements; name hash: 0xaf1b712e +# access-list temp_access line 1 +# extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www +# log default (hitcnt=0) 0xb58abb0d +# access-list temp_access line 2 +# extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp (hitcnt=0) (inactive) 0xcd6b92ae + + +- name: Override device configuration of all acl with provided configuration + cisco.asa.asa_acls: + config: + acls: + - name: global_access + acl_type: extended + aces: + - grant: deny + line: 1 + protocol_options: + tcp: true + source: + address: 192.0.4.0 + netmask: 255.255.255.0 + port_protocol: + eq: telnet + destination: + address: 192.0.5.0 + netmask: 255.255.255.0 + port_protocol: + eq: www + state: overridden + +# Commands fired: +# --------------- +# access-list temp_access line 2 +# extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 time-range temp +# no access-list temp_access line 1 +# extended grant deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www log default +# no access-list R1_traffic line 2 +# extended grant deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet inactive +# no access-list R1_traffic line 1 +# extended grant deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www log errors +# no access-list global_access line 3 extended grant deny tcp any any eq www log errors +# no access-list global_access line 2 extended grant deny tcp any any eq telnet +# no access-list global_access line 1 extended grant permit icmp any any log disable +# access-list global_access line 4 extended deny tcp 192.0.4.0 255.255.255.0 eq telnet 192.0.5.0 255.255.255.0 eq www + +# After state: +# ------------- +# +# vasa#sh access-lists +# access-list global_access; 1 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 + +# Using Deleted + +# Before state: +# ------------- +# +# vasa#sh access-lists +# access-list global_access; 3 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 +# access-list global_access line 2 extended deny tcp any any eq telnet (hitcnt=0) 0xae5833af +# access-list global_access line 3 extended deny tcp any any eq www log errors interval 300 (hitcnt=0) 0x605f2421 +# access-list R1_traffic; 2 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 +# access-list R1_traffic line 2 +# extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet +# inactive (hitcnt=0) (inactive) 0xe922b432 +# access-list temp_access; 2 elements; name hash: 0xaf1b712e +# access-list temp_access line 1 +# extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www +# log default (hitcnt=0) 0xb58abb0d +# access-list temp_access line 2 +# extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp (hitcnt=0) (inactive) 0xcd6b92ae + +- name: "Delete module attributes of given acl (Note: This won't delete ALL of the ACLs configured)" + cisco.asa.asa_acls: + config: + acls: + - name: temp_access + - name: global_access + state: deleted + +# Commands fired: +# --------------- +# no access-list temp_access line 2 extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp inactive +# no access-list temp_access line 1 extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www +# log default +# no access-list global_access line 3 extended deny tcp any any eq www log errors interval 300 +# no access-list global_access line 2 extended deny tcp any any eq telnet +# no access-list global_access line 1 extended permit icmp any any log disable + +# After state: +# ------------- +# +# vasa#sh access-lists +# access-list R1_traffic; 2 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 +# access-list R1_traffic line 2 +# extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet +# inactive (hitcnt=0) (inactive) 0xe922b432 + +# Using Deleted without any config passed +#"(NOTE: This will delete all of configured resource module attributes)" + +# Before state: +# ------------- +# +# vasa#sh access-lists +# access-list global_access; 3 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 +# access-list global_access line 2 extended deny tcp any any eq telnet (hitcnt=0) 0xae5833af +# access-list global_access line 3 extended deny tcp any any eq www log errors interval 300 (hitcnt=0) 0x605f2421 +# access-list R1_traffic; 2 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 +# access-list R1_traffic line 2 +# extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet +# inactive (hitcnt=0) (inactive) 0xe922b432 +# access-list temp_access; 2 elements; name hash: 0xaf1b712e +# access-list temp_access line 1 +# extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www +# log default (hitcnt=0) 0xb58abb0d +# access-list temp_access line 2 +# extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp (hitcnt=0) (inactive) 0xcd6b92ae + +- name: 'Delete ALL ACLs in one go (Note: This WILL delete the ALL of configured ACLs)' + cisco.asa.asa_acls: + state: deleted + +# Commands fired: +# --------------- +# no access-list global_access line 1 extended permit icmp any any log disable +# no access-list global_access line 2 extended deny tcp any any eq telnet +# no access-list global_access line 3 extended deny tcp any any eq www log errors interval 300 +# no access-list R1_traffic line 1 extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 +# no access-list R1_traffic line 2 extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet inactive +# no access-list temp_access line 1 extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www log default +# no access-list temp_access line 2 extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp inactive + + +# After state: +# ------------- +# +# vasa#sh access-lists + +# Using Gathered + +# Before state: +# ------------- +# +# access-list global_access; 3 elements; name hash: 0xbd6c87a7 +# access-list global_access line 1 extended permit icmp any any log disable (hitcnt=0) 0xf1efa630 +# access-list global_access line 2 extended deny tcp any any eq telnet (hitcnt=0) 0xae5833af +# access-list R1_traffic; 2 elements; name hash: 0xaf40d3c2 +# access-list R1_traffic line 1 +# extended deny tcp 2001:db8:0:3::/64 eq telnet 2001:fc8:0:4::/64 eq www +# log errors interval 300 (hitcnt=0) 0x4a4660f3 +# access-list R1_traffic line 2 +# extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet +# inactive (hitcnt=0) (inactive) 0xe922b432 +# access-list temp_access; 2 elements; name hash: 0xaf1b712e +# access-list temp_access line 1 +# extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www +# log default (hitcnt=0) 0xb58abb0d +# access-list temp_access line 2 +# extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp (hitcnt=0) (inactive) 0xcd6b92ae + + +- name: Gather listed ACLs with provided configurations + cisco.asa.asa_acls: + config: + state: gathered + +# Module Execution Result: +# ------------------------ +# +# "gathered": [ +# { +# "acls": [ +# { +# "aces": [ +# { +# "destination": { +# "any": true +# }, +# "grant": "permit", +# "line": 1, +# "log": "disable", +# "protocol": "icmp", +# "source": { +# "any": true +# } +# }, +# { +# "destination": { +# "any": true, +# "port_protocol": { +# "eq": "telnet" +# } +# }, +# "grant": "deny", +# "line": 2, +# "protocol": "tcp", +# "protocol_options": { +# "tcp": true +# }, +# "source": { +# "any": true +# } +# } +# ], +# "acl_type": "extended", +# "name": "global_access" +# }, +# { +# "aces": [ +# { +# "destination": { +# "address": "2001:fc8:0:4::/64", +# "port_protocol": { +# "eq": "www" +# } +# }, +# "grant": "deny", +# "line": 1, +# "log": "errors", +# "protocol": "tcp", +# "protocol_options": { +# "tcp": true +# }, +# "source": { +# "address": "2001:db8:0:3::/64", +# "port_protocol": { +# "eq": "telnet" +# } +# } +# }, +# { +# "destination": { +# "address": "2001:fc8:0:4::/64", +# "port_protocol": { +# "eq": "telnet" +# } +# }, +# "grant": "deny", +# "inactive": true, +# "line": 2, +# "protocol": "tcp", +# "protocol_options": { +# "tcp": true +# }, +# "source": { +# "address": "2001:db8:0:3::/64", +# "port_protocol": { +# "eq": "www" +# } +# } +# } +# ], +# "acl_type": "extended", +# "name": "R1_traffic" +# }, +# { +# "aces": [ +# { +# "destination": { +# "address": "192.0.3.0", +# "netmask": "255.255.255.0", +# "port_protocol": { +# "eq": "www" +# } +# }, +# "grant": "deny", +# "line": 1, +# "log": "default", +# "protocol": "tcp", +# "protocol_options": { +# "tcp": true +# }, +# "source": { +# "address": "192.0.2.0", +# "netmask": "255.255.255.0" +# } +# }, +# { +# "destination": { +# "address": "198.51.110.0", +# "netmask": "255.255.255.0" +# }, +# "grant": "deny", +# "inactive": true, +# "line": 2, +# "protocol": "igrp", +# "protocol_options": { +# "igrp": true +# }, +# "source": { +# "address": "198.51.100.0", +# "netmask": "255.255.255.0" +# }, +# "time_range": "temp" +# } +# ], +# "acl_type": "extended", +# "name": "temp_access" +# } +# ] +# } +# ] + +# Using Rendered + +- name: Rendered the provided configuration with the exisiting running configuration + cisco.asa.asa_acls: + config: + acls: + - name: temp_access + acl_type: extended + aces: + - grant: deny + line: 1 + protocol_options: + tcp: true + source: + address: 192.0.2.0 + netmask: 255.255.255.0 + destination: + address: 192.0.3.0 + netmask: 255.255.255.0 + port_protocol: + eq: www + log: default + - grant: deny + line: 2 + protocol_options: + igrp: true + source: + address: 198.51.100.0 + netmask: 255.255.255.0 + destination: + address: 198.51.110.0 + netmask: 255.255.255.0 + time_range: temp + - name: R1_traffic + aces: + - grant: deny + protocol_options: + tcp: true + source: + address: 2001:db8:0:3::/64 + port_protocol: + eq: www + destination: + address: 2001:fc8:0:4::/64 + port_protocol: + eq: telnet + inactive: true + state: rendered + +# Module Execution Result: +# ------------------------ +# +# "rendered": [ +# "access-list temp_access line 1 +# extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 +# eq www log default" +# "access-list temp_access line 2 +# extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 +# time-range temp" +# "access-list R1_traffic +# deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet inactive" +# ] + +# Using Parsed + +# parsed.cfg +# +# access-list test_access; 2 elements; name hash: 0xaf1b712e +# access-list test_access line 1 extended deny tcp 192.0.2.0 255.255.255.0 192.0.3.0 255.255.255.0 eq www log default +# access-list test_access line 2 extended deny igrp 198.51.100.0 255.255.255.0 198.51.110.0 255.255.255.0 log errors +# access-list test_R1_traffic; 1 elements; name hash: 0xaf40d3c2 +# access-list test_R1_traffic line 1 extended deny tcp 2001:db8:0:3::/64 eq www 2001:fc8:0:4::/64 eq telnet inactive + +- name: Parse the commands for provided configuration + cisco.asa.asa_acls: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Module Execution Result: +# ------------------------ +# +# "parsed": [ +# { +# "acls": [ +# { +# "aces": [ +# { +# "destination": { +# "address": "192.0.3.0", +# "netmask": "255.255.255.0", +# "port_protocol": { +# "eq": "www" +# } +# }, +# "grant": "deny", +# "line": 1, +# "log": "default", +# "protocol": "tcp", +# "protocol_options": { +# "tcp": true +# }, +# "source": { +# "address": "192.0.2.0", +# "netmask": "255.255.255.0" +# } +# }, +# { +# "destination": { +# "address": "198.51.110.0", +# "netmask": "255.255.255.0" +# }, +# "grant": "deny", +# "line": 2, +# "log": "errors", +# "protocol": "igrp", +# "protocol_options": { +# "igrp": true +# }, +# "source": { +# "address": "198.51.100.0", +# "netmask": "255.255.255.0" +# } +# } +# ], +# "acl_type": "extended", +# "name": "test_access" +# }, +# { +# "aces": [ +# { +# "destination": { +# "address": "2001:fc8:0:4::/64", +# "port_protocol": { +# "eq": "telnet" +# } +# }, +# "grant": "deny", +# "inactive": true, +# "line": 1, +# "protocol": "tcp", +# "protocol_options": { +# "tcp": true +# }, +# "source": { +# "address": "2001:db8:0:3::/64", +# "port_protocol": { +# "eq": "www" +# } +# } +# } +# ], +# "acl_type": "extended", +# "name": "test_R1_TRAFFIC" +# } +# ] +# } +# ] + +""" + +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: The configuration returned will always be in the same format of the parameters above. +after: + description: The configuration as structured data after module completion. + 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: ['access-list global_access line 1 extended permit icmp any any log disable'] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.argspec.acls.acls import ( + AclsArgs, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.config.acls.acls import ( + Acls, +) + + +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", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=AclsArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + + result = Acls(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/asa/plugins/modules/asa_command.py b/ansible_collections/cisco/asa/plugins/modules/asa_command.py new file mode 100644 index 00000000..ce5f2f77 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/modules/asa_command.py @@ -0,0 +1,197 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = """ +module: asa_command +author: Peter Sprygada (@privateip), Patrick Ogenstad (@ogenstad) +short_description: Run arbitrary commands on Cisco ASA devices +description: +- Sends arbitrary commands to an ASA node and returns the results read from the device. + The C(asa_command) module includes an argument that will cause the module to wait + for a specific condition before returning or timing out if the condition is not + met. +version_added: 1.0.0 +extends_documentation_fragment: +- cisco.asa.asa +options: + commands: + description: + - List of commands to send to the remote device over the configured provider. + The resulting output from the command is returned. If the I(wait_for) argument + is provided, the module is not returned until the condition is satisfied or + the number of retires as expired. + required: true + type: list + elements: str + wait_for: + description: + - List of conditions to evaluate against the output of the command. The task will + wait for each condition to be true before moving forward. If the conditional + is not true within the configured number of retries, the task fails. See examples. + aliases: + - waitfor + type: list + elements: str + match: + description: + - The I(match) argument is used in conjunction with the I(wait_for) argument to + specify the match policy. Valid values are C(all) or C(any). If the value + is set to C(all) then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be satisfied. + default: all + choices: + - any + - all + type: str + retries: + description: + - Specifies the number of retries a command should by tried before it is considered + failed. The command is run on the target device every retry and evaluated against + the I(wait_for) conditions. + default: 10 + type: int + interval: + description: + - Configures the interval in seconds to wait between retries of the command. If + the command does not pass the specified conditions, the interval indicates how + long to wait before trying the command again. + default: 1 + type: int +notes: +- When processing wait_for, each commands' output is stored as an element of the I(result) + array. The allowed operators for conditional evaluation are I(eq), I(==), I(neq), + I(ne), I(!=), I(gt), I(>), I(ge), I(>=), I(lt), I(<), I(le), I(<=), I(contains), + I(matches). Operators can be prefaced by I(not) to negate their meaning. The I(contains) + operator searches for a substring match (like the Python I(in) operator). The I(matches) + operator searches using a regex search operation. +""" + +EXAMPLES = """ +- name: Show the ASA version + cisco.asa.asa_command: + commands: + - show version + +- name: Show ASA drops and memory + cisco.asa.asa_command: + commands: + - show asp drop + - show memory + +- name: Send repeat pings and wait for the result to pass 100% + cisco.asa.asa_command: + commands: + - ping 8.8.8.8 repeat 20 size 350 + wait_for: + - result[0] contains 100 + retries: 2 +""" + +RETURN = """ +stdout: + description: the set of responses from the commands + returned: always + type: list + sample: ['...', '...'] + +stdout_lines: + description: The value of stdout split into a list + returned: always + type: list + sample: [['...', '...'], ['...'], ['...']] + +failed_conditions: + description: the conditionals that failed + returned: failed + type: list + sample: ['...', '...'] +""" +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import ( + asa_argument_spec, + check_args, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import ( + run_commands, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import ( + Conditional, +) +from ansible.module_utils.six import string_types + + +def to_lines(stdout): + for item in stdout: + if isinstance(item, string_types): + item = str(item).split("\n") + yield item + + +def main(): + spec = dict( + # { command: <str>, prompt: <str>, response: <str> } + commands=dict(type="list", required=True, elements="str"), + wait_for=dict(type="list", aliases=["waitfor"], elements="str"), + match=dict(default="all", choices=["all", "any"], type="str"), + retries=dict(default=10, type="int"), + interval=dict(default=1, type="int"), + ) + + spec.update(asa_argument_spec) + + module = AnsibleModule(argument_spec=spec, supports_check_mode=True) + check_args(module) + + result = {"changed": False} + + wait_for = module.params["wait_for"] or list() + conditionals = [Conditional(c) for c in wait_for] + + commands = module.params["commands"] + retries = module.params["retries"] + interval = module.params["interval"] + match = module.params["match"] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == "any": + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = "One or more conditional statements have not be satisfied" + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update( + { + "changed": False, + "stdout": responses, + "stdout_lines": list(to_lines(responses)), + } + ) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/asa/plugins/modules/asa_config.py b/ansible_collections/cisco/asa/plugins/modules/asa_config.py new file mode 100644 index 00000000..305ccc0a --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/modules/asa_config.py @@ -0,0 +1,406 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = """ +module: asa_config +author: Peter Sprygada (@privateip), Patrick Ogenstad (@ogenstad) +short_description: Manage configuration sections on Cisco ASA devices +description: +- Cisco ASA configurations use a simple block indent file syntax for segmenting configuration + into sections. This module provides an implementation for working with ASA configuration + sections in a deterministic way. +version_added: 1.0.0 +extends_documentation_fragment: +- cisco.asa.asa +options: + lines: + description: + - The ordered set of commands that should be configured in the section. The commands + must be the exact same commands as found in the device running-config. Be sure + to note the configuration command syntax as some commands are automatically + modified by the device config parser. + aliases: + - commands + type: list + elements: str + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy the + commands should be checked against. If the parents argument is omitted, the + commands are checked against the set of top level or global commands. + type: list + elements: str + src: + description: + - Specifies the source path to the file that contains the configuration or configuration + template to load. The path to the source file can either be the full path on + the Ansible control host or a relative path from the playbook or role root directory. This + argument is mutually exclusive with I(lines), I(parents). + type: path + before: + description: + - The ordered set of commands to push on to the command stack if a change needs + to be made. This allows the playbook designer the opportunity to perform configuration + commands prior to pushing any changes without affecting how the set of commands + are matched against the system. + type: list + elements: str + after: + description: + - The ordered set of commands to append to the end of the command stack if a change + needs to be made. Just like with I(before) this allows the playbook designer + to append a set of commands to be executed after the command set. + type: list + elements: str + match: + description: + - Instructs the module on the way to perform the matching of the set of commands + against the current device config. If match is set to I(line), commands are + matched line by line. If match is set to I(strict), command lines are matched + with respect to position. If match is set to I(exact), command lines must be + an equal match. Finally, if match is set to I(none), the module will not attempt + to compare the source configuration with the running configuration on the remote + device. + default: line + choices: + - line + - strict + - exact + - none + type: str + replace: + description: + - Instructs the module on the way to perform the configuration on the device. If + the replace argument is set to I(line) then the modified lines are pushed to + the device in configuration mode. If the replace argument is set to I(block) + then the entire command block is pushed to the device in configuration mode + if any line is not correct + default: line + choices: + - line + - block + type: str + backup: + description: + - This argument will cause the module to create a full backup of the current C(running-config) + from the remote device before any changes are made. If the C(backup_options) + value is not given, the backup file is written to the C(backup) folder in the + playbook root directory. If the directory does not exist, it is created. + type: bool + default: no + config: + description: + - The C(config) argument allows the playbook designer to supply the base configuration + to be used to validate configuration changes necessary. If this argument is + provided, the module will not download the running-config from the remote node. + type: str + defaults: + description: + - This argument specifies whether or not to collect all defaults when getting + the remote device running config. When enabled, the module will get the current + config by issuing the command C(show running-config all). + type: bool + default: no + passwords: + description: + - This argument specifies to include passwords in the config when retrieving the + running-config from the remote device. This includes passwords related to VPN + endpoints. This argument is mutually exclusive with I(defaults). + type: bool + save: + description: + - The C(save) argument instructs the module to save the running- config to the + startup-config at the conclusion of the module running. If check mode is specified, + this argument is ignored. + type: bool + default: no + backup_options: + description: + - This is a dict object containing configurable options related to backup file + path. The value of this option is read only when C(backup) is set to I(yes), + if C(backup) is set to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and + date in format defined by <hostname>_config.<current-date>@<current-time> + type: str + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will + be first created and the filename is either the value of C(filename) or + default filename as described in C(filename) options description. If the + path value is not given in that case a I(backup) directory will be created + in the current working directory and backup configuration will be copied + in C(filename) within I(backup) directory. + type: path + type: dict + save_when: + description: + - When changes are made to the device running-configuration, the changes are not + copied to non-volatile storage by default. Using this argument will change + that before. If the argument is set to I(always), then the running-config will + always be copied to the startup-config and the I(modified) flag will always + be set to True. If the argument is set to I(modified), then the running-config + will only be copied to the startup-config if it has changed since the last save + to startup-config. If the argument is set to I(never), the running-config will + never be copied to the startup-config. If the argument is set to I(changed), + then the running-config will only be copied to the startup-config if the task + has made a change. I(changed) was added in Ansible 2.5. + default: never + version_added: 1.1.0 + choices: + - always + - never + - modified + - changed + type: str +""" + +EXAMPLES = """ +- cisco.asa.asa_config: + lines: + - network-object host 10.80.30.18 + - network-object host 10.80.30.19 + - network-object host 10.80.30.20 + parents: [object-group network OG-MONITORED-SERVERS] + +- cisco.asa.asa_config: + host: '{{ inventory_hostname }}' + lines: + - message-length maximum client auto + - message-length maximum 512 + match: line + parents: [policy-map type inspect dns PM-DNS, parameters] + authorize: yes + auth_pass: cisco + username: admin + password: cisco + context: ansible + +- cisco.asa.asa_config: + lines: + - ikev1 pre-shared-key MyS3cretVPNK3y + parents: tunnel-group 1.1.1.1 ipsec-attributes + passwords: yes + +- name: attach ASA acl on interface vlan13/nameif cloud13 + cisco.asa.asa_config: + lines: + - access-group cloud-acl_access_in in interface cloud13 + +- name: configure ASA (>=9.2) default BGP + cisco.asa.asa_config: + lines: + - bgp log-neighbor-changes + - bgp bestpath compare-routerid + parents: + - router bgp 65002 + register: bgp + when: bgp_default_config is defined +- name: configure ASA (>=9.2) BGP neighbor in default/single context mode + cisco.asa.asa_config: + lines: + - bgp router-id {{ bgp_router_id }} + - neighbor {{ bgp_neighbor_ip }} remote-as {{ bgp_neighbor_as }} + - neighbor {{ bgp_neighbor_ip }} description {{ bgp_neighbor_name }} + parents: + - router bgp 65002 + - address-family ipv4 unicast + register: bgp + when: bgp_neighbor_as is defined +- name: configure ASA interface with standby + cisco.asa.asa_config: + lines: + - description my cloud interface + - nameif cloud13 + - security-level 50 + - ip address 192.168.13.1 255.255.255.0 standby 192.168.13.2 + parents: [interface Vlan13] + register: interface +- name: Show changes to interface from task above + ansible.builtin.debug: + var: interface + +- name: configurable backup path + cisco.asa.asa_config: + lines: + - access-group cloud-acl_access_in in interface cloud13 + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user + +- name: save running to startup when modified + cisco.asa.asa_config: + save_when: modified +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['...', '...'] +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/asa_config.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import ( + asa_argument_spec, + check_args, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import ( + get_config, + load_config, + run_commands, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, + dumps, +) + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params["src"]: + candidate.load(module.params["src"]) + elif module.params["lines"]: + parents = module.params["parents"] or list() + candidate.add(module.params["lines"], parents=parents) + return candidate + + +def save_config(module, result): + result["changed"] = True + if not module.check_mode: + run_commands(module, "write mem") + + +def run(module, result): + match = module.params["match"] + replace = module.params["replace"] + path = module.params["parents"] + + candidate = get_candidate(module) + if match != "none": + contents = module.params["config"] + if not contents: + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + configobjs = candidate.difference( + config, path=path, match=match, replace=replace + ) + + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, "commands").split("\n") + + if module.params["lines"]: + if module.params["before"]: + commands[:0] = module.params["before"] + + if module.params["after"]: + commands.extend(module.params["after"]) + + result["updates"] = commands + + # send the configuration commands to the device and merge + # them with the current running config + if not module.check_mode: + load_config(module, commands) + result["changed"] = True + + if module.params["save"]: + module.warn( + "module param save is deprecated, please use newer and updated param save_when instead which is released with more functionality!" + ) + save_config(module, result) + if module.params["save_when"] == "always": + save_config(module, result) + elif module.params["save_when"] == "modified": + running_config_checksum = run_commands( + module, "show running-config | include checksum:" + ) + startup_config_checksum = run_commands( + module, "show startup-config | include checksum:" + ) + if running_config_checksum != startup_config_checksum: + save_config(module, result) + elif module.params["save_when"] == "changed" and result["changed"]: + save_config(module, result) + + +def main(): + """main entry point for module execution""" + backup_spec = dict(filename=dict(), dir_path=dict(type="path")) + argument_spec = dict( + src=dict(type="path"), + lines=dict(aliases=["commands"], type="list", elements="str"), + parents=dict(type="list", elements="str"), + before=dict(type="list", elements="str"), + after=dict(type="list", elements="str"), + match=dict( + default="line", choices=["line", "strict", "exact", "none"] + ), + replace=dict(default="line", choices=["line", "block"]), + backup_options=dict(type="dict", options=backup_spec), + config=dict(), + defaults=dict(type="bool", default=False), + passwords=dict(type="bool", default=False), + backup=dict(type="bool", default=False), + save=dict(type="bool", default=False), + save_when=dict( + choices=["always", "never", "modified", "changed"], default="never" + ), + ) + + argument_spec.update(asa_argument_spec) + + mutually_exclusive = [ + ("lines", "src"), + ("parents", "src"), + ("defaults", "passwords"), + ] + + required_if = [ + ("match", "strict", ["lines"]), + ("match", "exact", ["lines"]), + ("replace", "block", ["lines"]), + ] + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + required_if=required_if, + supports_check_mode=True, + ) + + result = {"changed": False} + + check_args(module) + + if module.params["backup"]: + result["__backup__"] = get_config(module) + + run(module, result) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/asa/plugins/modules/asa_facts.py b/ansible_collections/cisco/asa/plugins/modules/asa_facts.py new file mode 100644 index 00000000..cdc34205 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/modules/asa_facts.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = """ +module: asa_facts +author: +- Sumit Jaiswal (@justjais) +short_description: Collect facts from remote devices running Cisco ASA +description: +- Collects a base set of device facts from a remote device that is running ASA. This + module prepends all of the base network fact keys with C(ansible_net_<fact>). The + facts module will always collect a base set of facts from the device and can enable + or disable collection of additional facts. +- Note, to collects facts from ASA device properly user should elevate the privilege + to become. +version_added: 1.0.0 +extends_documentation_fragment: +- cisco.asa.asa +notes: +- Tested against asa 9.10(1)11 +options: + gather_subset: + description: + - When supplied, this argument restricts the facts collected to a given subset. + - Possible values for this argument include C(all), C(min), C(hardware), C(config). + - Specify a list of values to include a larger subset. + - Use a value with an initial C(!) to collect all facts except that subset. + required: false + type: list + elements: str + default: '!config' + gather_network_resources: + description: + - When supplied, this argument will restrict the facts collected to a given subset. + Possible values for this argument include all and the resources like interfaces, + vlans etc. Can specify a list of values to include a larger subset. Values can + also be used with an initial C(!) to specify that a specific subset should + not be collected. Values can also be used with an initial C(!) to specify + that a specific subset should not be collected. Valid subsets are 'all', 'acls', 'ogs'. + required: false + type: list + elements: str +""" + +EXAMPLES = """ +- name: Gather all legacy facts + cisco.asa.asa_facts: + gather_subset: all + +- name: Gather only the config and default facts + cisco.asa.asa_facts: + gather_subset: + - config + +- name: Do not gather hardware facts + cisco.asa.asa_facts: + gather_subset: + - '!hardware' + +- name: Gather legacy and resource facts + cisco.asa.asa_facts: + gather_subset: all +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_firepower_version: + description: The Firepower operating system version running on the remote device. + returned: always + type: str +ansible_net_device_mgr_version: + description: The Device manager version running on the remote device. + returned: always + type: str +ansible_net_asatype: + description: The operating system type (Cisco ASA) running on the remote device. + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_image: + description: The image file the device is running + returned: always + type: str +ansible_net_stacked_models: + description: The model names of each device in the stack + returned: when multiple devices are configured in a stack + type: list +ansible_net_stacked_serialnums: + description: The serial numbers of each device in the stack + returned: when multiple devices are configured in a stack + type: list +ansible_net_api: + description: The name of the transport + returned: always + type: str +ansible_net_python_version: + description: The Python version Ansible controller is using + returned: always + type: str + +# hardware +ansible_net_filesystems: + description: All file system names available on the device + returned: when hardware is configured + type: list +ansible_net_filesystems_info: + description: A hash of all file systems containing info about each file system (e.g. free and total space) + returned: when hardware is configured + type: dict +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memused_mb: + description: The used memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str +""" + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.argspec.facts.facts import ( + FactsArgs, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.facts import ( + Facts, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import ( + asa_argument_spec, +) + + +def main(): + """ + Main entry point for module execution + + :returns: ansible_facts + """ + argument_spec = FactsArgs.argument_spec + argument_spec.update(asa_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, supports_check_mode=True + ) + + warnings = [] + if module.params["gather_subset"] == "!config": + warnings.append( + "default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards" + ) + + result = Facts(module).get_facts() + + ansible_facts, additional_warnings = result + warnings.extend(additional_warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/asa/plugins/modules/asa_ogs.py b/ansible_collections/cisco/asa/plugins/modules/asa_ogs.py new file mode 100644 index 00000000..42a226af --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/modules/asa_ogs.py @@ -0,0 +1,1079 @@ +#!/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 asa_ogs +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = """ +module: asa_ogs +short_description: Object Group resource module +description: This module configures and manages Objects and Groups on ASA platforms. +version_added: 1.0.0 +author: Sumit Jaiswal (@justjais) +notes: +- Tested against Cisco ASA Version 9.10(1)11 +- This module works with connection C(network_cli). See L(ASA Platform Options,../network/user_guide/platform_asa.html). +options: + config: + description: A list of Object Group options. + type: list + elements: dict + suboptions: + object_type: + description: The object group type. + type: str + required: true + choices: + - icmp-type + - network + - protocol + - security + - service + - user + object_groups: + description: The object groups. + type: list + elements: dict + suboptions: + name: + description: Specifies object-group ID + required: true + type: str + description: + description: The description for the object-group. + type: str + icmp_type: + description: Configure an ICMP-type object + type: dict + suboptions: + icmp_object: + description: Defines the ICMP types in the group. + type: list + elements: str + choices: [alternate-address, conversion-error, echo, echo-reply, information-reply, + information-request, mask-reply, mask-request, mobile-redirect, + parameter-problem, redirect, router-advertisement, router-solicitation, + source-quench, time-exceeded, timestamp-reply, timestamp-request, + traceroute, unreachable] + network_object: + description: Configure a network object + type: dict + suboptions: + host: + description: Set this to specify a single host object. + type: list + elements: str + address: + description: Enter an IPv4 network address with space seperated netmask. + type: list + elements: str + ipv6_address: + description: Enter an IPv6 prefix. + type: list + elements: str + object: + description: Enter this keyword to specify a network object + type: list + elements: str + protocol_object: + description: Configure a protocol object + type: dict + suboptions: + protocol: + description: + - Defines the protocols in the group. + - User can either specify protocols directly/protocol numbers(0-255) + type: list + elements: str + security_group: + description: Configure a security-group + type: dict + suboptions: + sec_name: + description: Enter this keyword to specify a security-group name. + type: list + elements: str + tag: + description: Enter this keyword to specify a security-group tag. + type: list + elements: str + service_object: + description: + - Configure a service object + - NEW 'services_object' param is introduced at object_group level, please + use the newer 'services_object' param defined at object_group level instead of + 'service_object' param at object_group level, as 'service_object' option + will get deprecated and removed in a future release. + type: dict + suboptions: + protocol: + description: Defines the protocols in the group. + type: list + elements: str + choices: [ah, eigrp, esp, gre, icmp, icmp6, igmp, igrp, ip, ipinip, + ipsec, nos, ospf, pcp, pim, pptp, sctp, snp, tcp, tcp-udp, udp] + object: + description: Enter this keyword to specify a service object + type: str + services_object: + description: + - Configure list of service objects + - Newer OGs services_object param which will replace service_object param + - Relased with version 2.1.0 + type: list + elements: dict + suboptions: + protocol: + description: Defines the protocols in the group. + type: str + object: + description: Enter this keyword to specify a service object + type: str + source_port: + description: Keyword to specify source port + type: dict + suboptions: + eq: + description: Match only packets on a given port number. + type: str + gt: + description: Match only packets with a greater port number. + type: str + lt: + description: Match only packets with a lower port number. + type: str + neq: + description: Match only packets not on a given port number. + type: str + range: + description: Port range operator + type: dict + suboptions: + start: + description: Specify the start of the port range. + type: str + end: + description: Specify the end of the port range. + type: str + destination_port: + description: Keyword to specify destination port + type: dict + suboptions: + eq: + description: Match only packets on a given port number. + type: str + gt: + description: Match only packets with a greater port number. + type: str + lt: + description: Match only packets with a lower port number. + type: str + neq: + description: Match only packets not on a given port number. + type: str + range: + description: Port range operator + type: dict + suboptions: + start: + description: Specify the start of the port range. + type: str + end: + description: Specify the end of the port range. + type: str + protocol: + description: + - Specifies that object-group is for only specified protocol only. + - Required when port-object need to be configured + type: str + choices: [tcp, tcp-udp, udp] + port_object: + description: Configure a port object + type: list + elements: dict + suboptions: + eq: + description: Enter this keyword to specify a port + type: str + range: + description: Enter this keyword to specify a range of ports + type: dict + suboptions: + start: + description: Specify the start of the port range. + type: str + end: + description: Specify the end of the port range. + type: str + user_object: + description: Configures single user, local or import user group + type: dict + suboptions: + user: + description: Configure a user objectUser name to configure a user + object. + type: list + elements: dict + suboptions: + name: + description: Enter the name of the user + type: str + required: true + domain: + description: User domain + type: str + required: true + user_group: + description: Configure a user group object. + type: list + elements: dict + suboptions: + name: + description: Enter the name of the group + type: str + required: true + domain: + description: Group domain + type: str + required: true + group_object: + description: Configure an object group as an object + type: list + elements: 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. + 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 merged + +# Before state: +# ------------- +# +# ciscoasa# sh running-config object-group +# object-group network test_og_network +# description test_network_og +# network-object host 192.0.3.1 + +- name: "Merge module attributes of given object-group" + cisco.asa.asa_ogs: + config: + - object_type: network + object_groups: + - name: group_network_obj + group_object: + - test_og_network + - name: test_og_network + description: test_og_network + network_object: + host: + - 192.0.2.1 + - 192.0.2.2 + address: + - 192.0.2.0 255.255.255.0 + - 198.51.100.0 255.255.255.0 + - name: test_network_og + description: test_network_og + network_object: + host: + - 192.0.3.1 + - 192.0.3.2 + ipv6_address: + - 2001:db8:3::/64 + - object_type: security + object_groups: + - name: test_og_security + description: test_security + security_group: + sec_name: + - test_1 + - test_2 + tag: + - 10 + - 20 + - object_type: service + object_groups: + - name: O-Worker + services_object: + - protocol: tcp + destination_port: + range: + start: 100 + end: 200 + - protocol: tcp-udp + source_port: + eq: 1234 + destination_port: + gt: nfs + - name: O-UNIX-TCP + protocol: tcp + port_object: + - eq: https + - range: + start: 100 + end: 400 + - object_type: user + object_groups: + - name: test_og_user + description: test_user + user_object: + user: + - name: new_user_1 + domain: LOCAL + - name: new_user_2 + domain: LOCAL + state: merged + +# Commands fired: +# --------------- +# +# object-group security test_og_security +# description test_security +# security-group name test_1 +# security-group name test_2 +# security-group tag 10 +# security-group tag 20 +# object-group network group_network_obj +# group-object test_og_network +# object-group network test_og_network +# description test_og_network +# network-object 192.0.2.0 255.255.255.0 +# network-object 198.51.100.0 255.255.255.0 +# network-object host 192.0.2.1 +# network-object host 192.0.2.2 +# object-group network test_network_og +# network-object host 192.0.3.1 +# network-object host 192.0.3.2 +# network-object 2001:db8:3::/64 +# object-group service O-Worker +# service-object tcp destination range 100 200 +# service-object tcp source eq 1234 destination gt nfs +# object-group service O-UNIX-TCP tcp +# port-object eq https +# port-object range 100 400 +# object-group user test_og_user +# description test_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 + +# After state: +# ------------ +# +# ciscoasa# sh running-config object-group +# object-group network group_network_obj +# group-object test_og_network +# object-group network test_og_network +# description test_og_network +# network-object host 192.0.2.1 +# network-object host 192.0.2.2 +# network-object 192.0.2.0 255.255.255.0 +# network-object 198.51.100.0 255.255.255.0 +# network-object host 192.0.3.1 +# object-group network test_network_og +# description test_network_og +# network-object host 192.0.3.1 +# network-object host 192.0.3.2 +# network-object 2001:db8:0:3::/64 +# group-object test_og_network +# object-group security test_og_security +# security-group name test_1 +# security-group name test_2 +# security-group tag 10 +# security-group tag 20 +# object-group service O-Worker +# service-object tcp destination range 100 200 +# service-object tcp source eq 1234 destination gt nfs +# object-group service O-UNIX-TCP tcp +# port-object eq https +# port-object range 100 400 +# object-group user test_og_user +# description test_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 + +# Using Replaced + +# Before state: +# ------------- +# +# ciscoasa# sh running-config object-group +# object-group network test_og_network +# description test_og_network +# network-object host 192.0.2.1 +# network-object host 192.0.2.2 +# network-object 192.0.2.0 255.255.255.0 +# network-object 198.51.100.0 255.255.255.0 +# object-group network test_network_og +# description test_network_og +# network-object host 192.0.3.1 +# network-object host 192.0.3.2 +# network-object 2001:db8:0:3::/64 +# group-object test_og_network +# object-group security test_og_security +# security-group name test_1 +# security-group name test_2 +# security-group tag 10 +# security-group tag 20 +# object-group service O-Worker +# service-object tcp destination range 100 200 +# service-object tcp source eq 1234 destination gt nfs +# object-group service O-UNIX-TCP tcp +# port-object eq https +# port-object range 100 400 +# object-group user test_og_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 + +- name: "Replace module attributes of given object-group" + cisco.asa.asa_ogs: + config: + - object_type: network + object_groups: + - name: test_og_network + description: test_og_network_replace + network_object: + host: + - 192.0.3.1 + address: + - 192.0.3.0 255.255.255.0 + - object_type: protocol + object_groups: + - name: test_og_protocol + description: test_og_protocol + protocol_object: + protocol: + - tcp + - udp + state: replaced + +# Commands Fired: +# --------------- +# +# object-group protocol test_og_protocol +# description test_og_protocol +# protocol tcp +# protocol udp +# object-group network test_og_network +# description test_og_network_replace +# no network-object 192.0.2.0 255.255.255.0 +# no network-object 198.51.100.0 255.255.255.0 +# network-object 192.0.3.0 255.255.255.0 +# no network-object host 192.0.2.1 +# no network-object host 192.0.2.2 +# network-object host 192.0.3.1 + +# After state: +# ------------- +# +# ciscoasa# sh running-config object-group +# object-group network test_og_network +# description test_og_network_replace +# network-object host 192.0.3.1 +# network-object 192.0.3.0 255.255.255.0 +# object-group network test_network_og +# description test_network_og +# network-object host 192.0.3.1 +# network-object host 192.0.3.2 +# network-object 2001:db8:0:3::/64 +# group-object test_og_network +# object-group security test_og_security +# security-group name test_1 +# security-group name test_2 +# security-group tag 10 +# security-group tag 20 +# object-group service O-Worker +# service-object tcp destination range 100 200 +# service-object tcp source eq 1234 destination gt nfs +# object-group service O-UNIX-TCP tcp +# port-object eq https +# port-object range 100 400 +# object-group user test_og_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 +# object-group protocol test_og_protocol +# protocol-object tcp +# protocol-object udp + +# Using Overridden + +# Before state: +# ------------- +# +# ciscoasa# sh running-config object-group +# object-group network test_og_network +# description test_og_network +# network-object host 192.0.2.1 +# network-object host 192.0.2.2 +# network-object 192.0.2.0 255.255.255.0 +# network-object 198.51.100.0 255.255.255.0 +# object-group network test_network_og +# description test_network_og +# network-object host 192.0.3.1 +# network-object host 192.0.3.2 +# network-object 2001:db8:0:3::/64 +# group-object test_og_network +# object-group security test_og_security +# security-group name test_1 +# security-group name test_2 +# security-group tag 10 +# security-group tag 20 +# object-group service O-Worker +# service-object tcp destination range 100 200 +# service-object tcp source eq 1234 destination gt nfs +# object-group service O-UNIX-TCP tcp +# port-object eq https +# port-object range 100 400 +# object-group user test_og_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 + +- name: "Overridden module attributes of given object-group" + cisco.asa.asa_ogs: + config: + - object_type: network + object_groups: + - name: test_og_network + description: test_og_network_override + network_object: + host: + - 192.0.3.1 + address: + - 192.0.3.0 255.255.255.0 + - name: ANSIBLE_TEST + network_object: + object: + - TEST1 + - TEST2 + - object_type: protocol + object_groups: + - name: test_og_protocol + description: test_og_protocol + protocol_object: + protocol: + - tcp + - udp + state: overridden + +# Commands Fired: +# --------------- +# +# no object-group security test_og_security +# no object-group service O-Worker +# no object-group service O-UNIX-TCP +# no object-group user test_og_user +# object-group protocol test_og_protocol +# description test_og_protocol +# protocol tcp +# protocol udp +# object-group network test_og_network +# description test_og_network_override +# no network-object 192.0.2.0 255.255.255.0 +# no network-object 198.51.100.0 255.255.255.0 +# network-object 192.0.3.0 255.255.255.0 +# no network-object host 192.0.2.1 +# no network-object host 192.0.2.2 +# network-object host 192.0.3.1 +# no object-group network test_network_og +# object-group network ANSIBLE_TEST +# network-object object TEST1 +# network-object object TEST2 + +# After state: +# ------------- +# +# ciscoasa# sh running-config object-group +# object-group network test_og_network +# description test_og_network_override +# network-object host 192.0.3.1 +# network-object 192.0.3.0 255.255.255.0 +# object-group network ANSIBLE_TEST +# network-object object TEST1 +# network-object object TEST2 +# object-group protocol test_og_protocol +# protocol-object tcp +# protocol-object udp + +# Using Deleted + +# Before state: +# ------------- +# +# ciscoasa# sh running-config object-group +# object-group network test_og_network +# description test_og_network +# network-object host 192.0.2.1 +# network-object host 192.0.2.2 +# network-object 192.0.2.0 255.255.255.0 +# network-object 198.51.100.0 255.255.255.0 +# object-group network test_network_og +# description test_network_og +# network-object host 192.0.3.1 +# network-object host 192.0.3.2 +# network-object 2001:db8:0:3::/64 +# group-object test_og_network +# object-group security test_og_security +# security-group name test_1 +# security-group name test_2 +# security-group tag 10 +# security-group tag 20 +# object-group service O-Worker +# service-object tcp destination range 100 200 +# service-object tcp source eq 1234 destination gt nfs +# object-group service O-UNIX-TCP tcp +# port-object eq https +# port-object range 100 400 +# object-group user test_og_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 + +- name: "Delete given module attributes" + cisco.asa.asa_ogs: + config: + - object_type: network + object_groups: + - name: test_og_network + - name: test_network_og + - object_type: security + object_groups: + - name: test_og_security + - object_type: service + object_groups: + - name: O-UNIX-TCP + state: deleted + +# Commands Fired: +# --------------- +# +# no object-group network test_og_network +# no object-group network test_network_og +# no object-group security test_og_security +# no object-group service O-UNIX-TCP + +# After state: +# ------------- +# +# ciscoasa# sh running-config object-group +# object-group user test_og_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 +# object-group service O-Worker +# service-object tcp destination range 100 200 +# service-object tcp source eq 1234 destination gt nfs + +# Using DELETED without any config passed +#"(NOTE: This will delete all of configured resource module attributes)" + +# Before state: +# ------------- +# +# ciscoasa# sh running-config object-group +# object-group network test_og_network +# description test_og_network +# network-object host 192.0.2.1 +# network-object host 192.0.2.2 +# network-object 192.0.2.0 255.255.255.0 +# network-object 198.51.100.0 255.255.255.0 +# object-group network test_network_og +# description test_network_og +# network-object host 192.0.3.1 +# network-object host 192.0.3.2 +# network-object 2001:db8:0:3::/64 +# group-object test_og_network +# object-group security test_og_security +# security-group name test_1 +# security-group name test_2 +# security-group tag 10 +# security-group tag 20 +# object-group user test_og_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 + +- name: Delete ALL configured module attributes + cisco.asa.asa_ogs: + config: + state: deleted + +# Commands Fired: +# --------------- +# +# no object-group network test_og_network +# no object-group network test_network_og +# no object-group security test_og_security +# no object-group user test_og_user + +# After state: +# ------------- +# +# ciscoasa# sh running-config object-group + +# Using Gathered + +# Before state: +# ------------- +# +# ciscoasa# sh running-config object-group +# object-group network test_og_network +# description test_og_network +# network-object host 192.0.2.1 +# network-object host 192.0.2.2 +# network-object 192.0.2.0 255.255.255.0 +# network-object 198.51.100.0 255.255.255.0 +# object-group network test_network_og +# description test_network_og +# network-object host 192.0.3.1 +# network-object host 192.0.3.2 +# network-object 2001:db8:0:3::/64 +# group-object test_og_network +# object-group security test_og_security +# security-group name test_1 +# security-group name test_2 +# security-group tag 10 +# security-group tag 20 +# object-group user test_og_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 + +- name: Gather listed OGs with provided configurations + cisco.asa.asa_ogs: + config: + state: gathered + +# Module Execution Result: +# ------------------------ +# +# "gathered": [ +# { +# "object_groups": [ +# { +# "description": "test_security", +# "name": "test_og_security", +# "security_group": { +# "sec_name": [ +# "test_2", +# "test_1" +# ], +# "tag": [ +# 10, +# 20 +# ] +# } +# } +# ], +# "object_type": "security" +# }, +# { +# "object_groups": [ +# { +# "description": "test_network_og", +# "name": "test_network_og", +# "network_object": { +# "host": [ +# "192.0.3.1", +# "192.0.3.2" +# ], +# "ipv6_address": [ +# "2001:db8:3::/64" +# ] +# } +# }, +# { +# "description": "test_og_network", +# "name": "test_og_network", +# "network_object": { +# "address": [ +# "192.0.2.0 255.255.255.0", +# "198.51.100.0 255.255.255.0" +# ], +# "host": [ +# "192.0.2.1", +# "192.0.2.2" +# ] +# } +# } +# ], +# "object_type": "network" +# }, +# { +# "object_groups": [ +# { +# "description": "test_user", +# "name": "test_og_user", +# "user_object": { +# "user": [ +# { +# "domain": "LOCAL", +# "name": "new_user_1" +# }, +# { +# "domain": "LOCAL", +# "name": "new_user_2" +# } +# ] +# } +# } +# ], +# "object_type": "user" +# } +# ] + +# After state: +# ------------ +# +# ciscoasa# sh running-config object-group +# object-group network test_og_network +# description test_og_network +# network-object host 192.0.2.1 +# network-object host 192.0.2.2 +# network-object 192.0.2.0 255.255.255.0 +# network-object 198.51.100.0 255.255.255.0 +# object-group network test_network_og +# description test_network_og +# network-object host 192.0.3.1 +# network-object host 192.0.3.2 +# network-object 2001:db8:0:3::/64 +# group-object test_og_network +# object-group security test_og_security +# security-group name test_1 +# security-group name test_2 +# security-group tag 10 +# security-group tag 20 +# object-group user test_og_user +# user LOCAL\\new_user_1 +# user LOCAL\\new_user_2 + +# Using Rendered + +- name: Render the commands for provided configuration + cisco.asa.asa_ogs: + config: + - object_type: network + object_groups: + - name: test_og_network + description: test_og_network + network_object: + host: + - 192.0.2.1 + - 192.0.2.2 + address: + - 192.0.2.0 255.255.255.0 + - 198.51.100.0 255.255.255.0 + - name: test_network_og + description: test_network_og + network_object: + host: + - 192.0.3.1 + - 192.0.3.2 + ipv6_address: + - 2001:db8:3::/64 + - object_type: security + object_groups: + - name: test_og_security + description: test_security + security_group: + sec_name: + - test_1 + - test_2 + tag: + - 10 + - 20 + - object_type: user + object_groups: + - name: test_og_user + description: test_user + user_object: + user: + - name: new_user_1 + domain: LOCAL + - name: new_user_2 + domain: LOCAL + state: rendered + +# Module Execution Result: +# ------------------------ +# +# "rendered": [ +# "object-group security test_og_security", +# "description test_security", +# "security-group name test_1", +# "security-group name test_2", +# "security-group tag 10", +# "security-group tag 20", +# "object-group network test_og_network", +# "description test_og_network", +# "network-object 192.0.2.0 255.255.255.0", +# "network-object 198.51.100.0 255.255.255.0", +# "network-object host 192.0.2.1", +# "network-object host 192.0.2.2", +# "object-group network test_network_og", +# "description test_network_og", +# "network-object host 192.0.3.1", +# "network-object host 192.0.3.2", +# "network-object 2001:db8:3::/64", +# "object-group user test_og_user", +# "description test_user", +# "user LOCAL\\new_user_1", +# "user LOCAL\\new_user_2" +# ] + +# Using Parsed + +# parsed.cfg +# +# object-group network test_og_network +# description test_og_network +# network-object host 192.0.2.1 +# network-object 192.0.2.0 255.255.255.0 +# object-group network test_network_og +# network-object 2001:db8:3::/64 +# object-group service test_og_service +# service-object tcp-udp + +- name: Parse the commands for provided configuration + cisco.asa.asa_ogs: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + +# Module Execution Result: +# ------------------------ +# +# "parsed": [ +# { +# "object_groups": [ +# { +# "name": "test_network_og" +# }, +# { +# "description": "test_og_network", +# "name": "test_og_network", +# "network_object": { +# "host": [ +# "192.0.2.2" +# ] +# } +# } +# ], +# "object_type": "network" +# }, +# { +# "object_groups": [ +# { +# "name": "test_og_service", +# "service_object": { +# "protocol": [ +# "tcp-udp", +# "ipinip" +# ] +# } +# } +# ], +# "object_type": "service" +# } +# ] + +""" + +RETURN = """ +before: + description: The configuration as structured data prior to module invocation. + returned: always + type: list + sample: The configuration returned will always be in the same format of the parameters above. +after: + description: The configuration as structured data after module completion. + 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: ['object-group network test_network_og', 'description test_network_og', 'network-object host 192.0.2.1'] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.argspec.ogs.ogs import ( + OGsArgs, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.config.ogs.ogs import ( + OGs, +) + + +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", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=OGsArgs.argument_spec, + required_if=required_if, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True, + ) + result = OGs(module).execute_module() + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/asa/plugins/terminal/__init__.py b/ansible_collections/cisco/asa/plugins/terminal/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/terminal/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/terminal/asa.py b/ansible_collections/cisco/asa/plugins/terminal/asa.py new file mode 100644 index 00000000..1bcf1e67 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/terminal/asa.py @@ -0,0 +1,80 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import re +import json + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text, to_bytes +from ansible_collections.ansible.netcommon.plugins.plugin_utils.terminal_base import ( + TerminalBase, +) + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(rb"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(rb"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$"), + ] + + terminal_stderr_re = [ + re.compile(rb"error:", re.I), + re.compile(rb"Removing.* not allowed, it is being used"), + re.compile(rb"^Command authorization failed\r?$", re.MULTILINE), + ] + + terminal_config_prompt = re.compile(r"^.+\(config(-.*)?\)#$") + + def on_open_shell(self): + if self._get_prompt().strip().endswith(b"#"): + self.disable_pager() + + def disable_pager(self): + try: + self._exec_cli_command("no terminal pager") + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure("unable to disable terminal pager") + + def on_become(self, passwd=None): + if self._get_prompt().strip().endswith(b"#"): + return + + cmd = {"command": "enable"} + if passwd: + # Note: python-3.5 cannot combine u"" and r"" together. Thus make + # an r string and use to_text to ensure it's text on both py2 and py3. + cmd["prompt"] = to_text( + r"[\r\n]?[Pp]assword: $", errors="surrogate_or_strict" + ) + cmd["answer"] = passwd + + try: + self._exec_cli_command( + to_bytes(json.dumps(cmd), errors="surrogate_or_strict") + ) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure( + "unable to elevate privilege to enable mode" + ) + + self.disable_pager() |