diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc8dec0a3..4b64e51ec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ Ansible Changes By Release #### Deprecated Modules: * ec2_facts (removed in 2.7), replaced by ec2_metadata_facts * cs_nic (removed in 2.7), replaced by cs_instance_nic_secondaryip, also see new module cs_instance_nic for managing nics +* panos_address (use M(panos_object) instead) +* panos_service (use M(panos_object) instead) #### Removed Deprecated Modules: * eos_template (use eos_config instead) @@ -297,6 +299,8 @@ Ansible Changes By Release - gunicorn - nuage * nuage_vpsk +- panos + * panos_object - purestorage * purefa_hg * purefa_host diff --git a/lib/ansible/modules/network/panos/panos_address.py b/lib/ansible/modules/network/panos/panos_address.py deleted file mode 100755 index 9accff085b..0000000000 --- a/lib/ansible/modules/network/panos/panos_address.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Ansible module to manage PaloAltoNetworks Firewall -# (c) 2016, techbizdev -# -# 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 . - -ANSIBLE_METADATA = {'metadata_version': '1.0', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: panos_address -short_description: Create address service object on PanOS devices -description: - - Create address service object of different types [IP Range, FQDN, or IP Netmask]. -author: "Luigi Mori (@jtschichold), Ken Celenza (@itdependsnetworks), Ivan Bojer (@ivanbojer)" -version_added: "2.3" -requirements: - - pan-python can be obtained from PyPi U(https://pypi.python.org/pypi/pan-python) -options: - ip_address: - description: - - IP address (or hostname) of PAN-OS device being configured. - required: true - username: - description: - - Username credentials to use for authentication. - default: "admin" - password: - description: - - Password credentials to use for authentication. - required: true - address: - description: - - IP address with or without mask, range, or FQDN. - required: true - default: None - address_name: - description: - - Human readable name of the address. - required: true - default: None - type: - description: - - This is the type of the object created. - default: ip-nemask - choices: [ 'ip-netmask', 'fqdn', 'ip-range' ] - description: - description: - - Description of the address object. - default: None - tag: - description: - - Tag of the address object. - default: None - commit: - description: - - Commit configuration to the Firewall if it is changed. - default: true -''' - -EXAMPLES = ''' -- name: create IP-Netmask Object - panos_address: - ip_address: "192.168.1.1" - password: 'admin' - address_name: 'google_dns' - address: '8.8.8.8/32' - description: 'Google DNS' - tag: 'Outbound' - commit: False - -- name: create IP-Range Object - panos_address: - ip_address: "192.168.1.1" - password: 'admin' - type: 'ip-range' - address_name: 'apple-range' - address: '17.0.0.0-17.255.255.255' - commit: False - -- name: create FQDN Object - panos_address: - ip_address: "192.168.1.1" - password: 'admin' - type: 'fqdn' - address_name: 'google.com' - address: 'www.google.com' -''' - -RETURN = ''' -# Default return values -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.basic import get_exception - -try: - import pan.xapi - from pan.xapi import PanXapiError - - HAS_LIB = True -except ImportError: - HAS_LIB = False - -_ADDRESS_XPATH = "/config/devices/entry[@name='localhost.localdomain']" + \ - "/vsys/entry[@name='vsys1']" + \ - "/address/entry[@name='%s']" - - -def address_exists(xapi, address_name): - xapi.get(_ADDRESS_XPATH % address_name) - e = xapi.element_root.find('.//entry') - if e is None: - return False - return True - - -def add_address(xapi, module, address, address_name, description, type, tag): - if address_exists(xapi, address_name): - return False - - exml = [] - exml.append('<%s>' % type) - exml.append('%s' % address) - exml.append('' % type) - - if description: - exml.append('') - exml.append('%s' % description) - exml.append('') - - if tag: - exml.append('') - exml.append('%s' % tag) - exml.append('') - - exml = ''.join(exml) - - xapi.set(xpath=_ADDRESS_XPATH % address_name, element=exml) - - return True - - -def main(): - argument_spec = dict( - ip_address=dict(required=True), - password=dict(required=True, no_log=True), - username=dict(default='admin'), - address_name=dict(required=True), - address=dict(), - description=dict(), - tag=dict(), - type=dict(default='ip-netmask', choices=['ip-netmask', 'ip-range', 'fqdn']), - commit=dict(type='bool', default=True) - ) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) - - if not HAS_LIB: - module.fail_json(msg='pan-python required for this module') - - ip_address = module.params["ip_address"] - password = module.params["password"] - username = module.params['username'] - - xapi = pan.xapi.PanXapi( - hostname=ip_address, - api_username=username, - api_password=password - ) - - address_name = module.params['address_name'] - address = module.params['address'] - commit = module.params['commit'] - - description = module.params['description'] - tag = module.params['tag'] - type = module.params['type'] - - changed = False - try: - changed = add_address(xapi, module, - address, - address_name, - description, - type, - tag) - except PanXapiError: - exc = get_exception() - module.fail_json(msg=exc.message) - - if changed and commit: - xapi.commit(cmd="", sync=True, interval=1) - - module.exit_json(changed=changed, msg="okey dokey") - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/panos/panos_object.py b/lib/ansible/modules/network/panos/panos_object.py new file mode 100644 index 0000000000..1ffadc5352 --- /dev/null +++ b/lib/ansible/modules/network/panos/panos_object.py @@ -0,0 +1,463 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage PaloAltoNetworks Firewall +# (c) 2016, techbizdev +# +# 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 . +# limitations under the License. + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.0'} + +DOCUMENTATION = ''' +--- +module: panos_object +short_description: create/read/update/delete object in PAN-OS or Panorama +description: > + - Policy objects form the match criteria for policy rules and many other functions in PAN-OS. These may include + address object, address groups, service objects, service groups, and tag. +author: "Bob Hagen (@rnh556)" +version_added: "2.4" +requirements: + - pan-python can be obtained from PyPi U(https://pypi.python.org/pypi/pan-python) + - pandevice can be obtained from PyPi U(https://pypi.python.org/pypi/pandevice) +notes: + - Checkmode is not supported. + - Panorama is supported. +options: + ip_address: + description: + - IP address (or hostname) of PAN-OS device or Panorama management console being configured. + required: true + username: + description: + - Username credentials to use for authentication. + required: false + default: "admin" + password: + description: + - Password credentials to use for authentication. + required: true + api_key: + description: + - API key that can be used instead of I(username)/I(password) credentials. + operation: + description: + - The operation to be performed. Supported values are I(add)/I(delete)/I(find). + required: true + addressobject: + description: + - The name of the address object. + address: + description: + - The IP address of the host or network in CIDR notation. + address_type: + description: + - The type of address object definition. Valid types are I(ip-netmask) and I(ip-range). + addressgroup: + description: + - A static group of address objects or dynamic address group. + static_value: + description: + - A group of address objects to be used in an addressgroup definition. + dynamic_value: + description: + - The filter match criteria to be used in a dynamic addressgroup definition. + serviceobject: + description: + - The name of the service object. + source_port: + description: + - The source port to be used in a service object definition. + destination_port: + description: + - The destination port to be used in a service object definition. + protocol: + description: + - The IP protocol to be used in a service object definition. Valid values are I(tcp) or I(udp). + servicegroup: + description: + - A group of service objects. + services: + description: + - The group of service objects used in a servicegroup definition. + description: + description: + - The description of the object. + tag_name: + description: + - The name of an object or rule tag. + color: + description: > + - The color of the tag object. Valid values are I(red, green, blue, yellow, copper, orange, purple, gray, + light green, cyan, light gray, blue gray, lime, black, gold, and brown). + devicegroup: + description: > + - The name of the Panorama device group. The group must exist on Panorama. If device group is not defined it + is assumed that we are contacting a firewall. + required: false + default: None +''' + +EXAMPLES = ''' +- name: search for shared address object + panos_object: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'find' + address: 'DevNet' + +- name: create an address group in devicegroup using API key + panos_object: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'add' + addressgroup: 'Prod_DB_Svrs' + static_value: ['prod-db1', 'prod-db2', 'prod-db3'] + description: 'Production DMZ database servers' + tag_name: 'DMZ' + devicegroup: 'DMZ Firewalls' + +- name: create a global service for TCP 3306 + panos_object: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'add' + serviceobject: 'mysql-3306' + destination_port: '3306' + protocol: 'tcp' + description: 'MySQL on tcp/3306' + +- name: create a global tag + panos_object: + ip_address: '{{ ip_address }}' + username: '{{ username }}' + password: '{{ password }}' + operation: 'add' + tag_name: 'ProjectX' + color: 'yellow' + description: 'Associated with Project X' + +- name: delete an address object from a devicegroup using API key + panos_object: + ip_address: '{{ ip_address }}' + api_key: '{{ api_key }}' + operation: 'delete' + addressobject: 'Win2K test' +''' + +RETURN = ''' +# Default return values +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import get_exception + +try: + import pan.xapi + from pan.xapi import PanXapiError + import pandevice + from pandevice import base + from pandevice import firewall + from pandevice import panorama + from pandevice import objects + import xmltodict + import json + + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def get_devicegroup(device, devicegroup): + dg_list = device.refresh_devices() + for group in dg_list: + if isinstance(group, pandevice.panorama.DeviceGroup): + if group.name == devicegroup: + return group + return False + + +def find_object(device, dev_group, obj_name, obj_type): + # Get the firewall objects + obj_type.refreshall(device) + if isinstance(device, pandevice.firewall.Firewall): + addr = device.find(obj_name, obj_type) + return addr + elif isinstance(device, pandevice.panorama.Panorama): + addr = device.find(obj_name, obj_type) + if addr is None: + if dev_group: + device.add(dev_group) + obj_type.refreshall(dev_group) + addr = dev_group.find(obj_name, obj_type) + return addr + else: + return False + + +def create_object(**kwargs): + if kwargs['addressobject']: + newobject = objects.AddressObject( + name=kwargs['addressobject'], + value=kwargs['address'], + type=kwargs['address_type'], + description=kwargs['description'], + tag=kwargs['tag_name'] + ) + if newobject.type and newobject.value: + return newobject + else: + return False + elif kwargs['addressgroup']: + newobject = objects.AddressGroup( + name=kwargs['addressgroup'], + static_value=kwargs['static_value'], + dynamic_value=kwargs['dynamic_value'], + description=kwargs['description'], + tag=kwargs['tag_name'] + ) + if newobject.static_value or newobject.dynamic_value: + return newobject + else: + return False + elif kwargs['serviceobject']: + newobject = objects.ServiceObject( + name=kwargs['serviceobject'], + protocol=kwargs['protocol'], + source_port=kwargs['source_port'], + destination_port=kwargs['destination_port'], + tag=kwargs['tag_name'] + ) + if newobject.protocol and newobject.destination_port: + return newobject + else: + return False + elif kwargs['servicegroup']: + newobject = objects.ServiceGroup( + name=kwargs['servicegroup'], + value=kwargs['services'], + tag=kwargs['tag_name'] + ) + if newobject.value: + return newobject + else: + return False + elif kwargs['tag_name']: + newobject = objects.Tag( + name=kwargs['tag_name'], + color=kwargs['color'], + comments=kwargs['description'] + ) + if newobject.name: + return newobject + else: + return False + else: + return False + + +def add_object(device, dev_group, new_object): + if dev_group: + dev_group.add(new_object) + else: + device.add(new_object) + new_object.create() + return True + + +def main(): + argument_spec = dict( + ip_address=dict(required=True), + password=dict(no_log=True), + username=dict(default='admin'), + api_key=dict(no_log=True), + operation=dict(required=True, choices=['add', 'update', 'delete', 'find']), + addressobject=dict(default=None), + addressgroup=dict(default=None), + serviceobject=dict(default=None), + servicegroup=dict(default=None), + address=dict(default=None), + address_type=dict(default='ip-netmask', choices=['ip-netmask', 'ip-range', 'fqdn']), + static_value=dict(type='list', default=None), + dynamic_value=dict(default=None), + protocol=dict(default=None, choices=['tcp', 'udp']), + source_port=dict(default=None), + destination_port=dict(default=None), + services=dict(type='list', default=None), + description=dict(default=None), + tag_name=dict(default=None), + color=dict(default=None, choices=['red', 'green', 'blue', 'yellow', 'copper', 'orange', 'purple', + 'gray', 'light green', 'cyan', 'light gray', 'blue gray', + 'lime', 'black', 'gold', 'brown']), + devicegroup=dict(default=None) + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, + required_one_of=[['api_key', 'password']], + mutually_exclusive=[['addressobject', 'addressgroup', + 'serviceobject', 'servicegroup', + 'tag_name']] + ) + if not HAS_LIB: + module.fail_json(msg='Missing required libraries.') + + ip_address = module.params["ip_address"] + password = module.params["password"] + username = module.params['username'] + api_key = module.params['api_key'] + operation = module.params['operation'] + addressobject = module.params['addressobject'] + addressgroup = module.params['addressgroup'] + serviceobject = module.params['serviceobject'] + servicegroup = module.params['servicegroup'] + address = module.params['address'] + address_type = module.params['address_type'] + static_value = module.params['static_value'] + dynamic_value = module.params['dynamic_value'] + protocol = module.params['protocol'] + source_port = module.params['source_port'] + destination_port = module.params['destination_port'] + services = module.params['services'] + description = module.params['description'] + tag_name = module.params['tag_name'] + color = module.params['color'] + devicegroup = module.params['devicegroup'] + + # Create the device with the appropriate pandevice type + device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key) + + # If Panorama, validate the devicegroup + dev_group = None + if devicegroup and isinstance(device, panorama.Panorama): + dev_group = get_devicegroup(device, devicegroup) + if dev_group: + device.add(dev_group) + else: + module.fail_json(msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup) + + # What type of object are we talking about? + if addressobject: + obj_name = addressobject + obj_type = objects.AddressObject + elif addressgroup: + obj_name = addressgroup + obj_type = objects.AddressGroup + elif serviceobject: + obj_name = serviceobject + obj_type = objects.ServiceObject + elif servicegroup: + obj_name = servicegroup + obj_type = objects.ServiceGroup + elif tag_name: + obj_name = tag_name + obj_type = objects.Tag + else: + module.fail_json(msg='No object type defined!') + + # Which operation shall we perform on the object? + if operation == "find": + # Search for the object + match = find_object(device, dev_group, obj_name, obj_type) + + # If found, format and return the result + if match: + match_dict = xmltodict.parse(match.element_str()) + module.exit_json( + stdout_lines=json.dumps(match_dict, indent=2), + msg='Object matched' + ) + else: + module.fail_json(msg='Object \'%s\' not found. Is the name correct?' % obj_name) + elif operation == "delete": + # Search for the object + match = find_object(device, dev_group, obj_name, obj_type) + + # If found, delete it + if match: + try: + match.delete() + except PanXapiError: + exc = get_exception() + module.fail_json(msg=exc.message) + + module.exit_json(changed=True, msg='Object \'%s\' successfully deleted' % obj_name) + else: + module.fail_json(msg='Object \'%s\' not found. Is the name correct?' % obj_name) + elif operation == "add": + # Search for the object. Fail if found. + match = find_object(device, dev_group, obj_name, obj_type) + if match: + module.fail_json(msg='Object \'%s\' already exists. Use operation: \'update\' to change it.' % obj_name) + else: + try: + new_object = create_object( + addressobject=addressobject, + addressgroup=addressgroup, + serviceobject=serviceobject, + servicegroup=servicegroup, + address=address, + address_type=address_type, + static_value=static_value, + dynamic_value=dynamic_value, + protocol=protocol, + source_port=source_port, + destination_port=destination_port, + services=services, + description=description, + tag_name=tag_name, + color=color + ) + changed = add_object(device, dev_group, new_object) + except PanXapiError: + exc = get_exception() + module.fail_json(msg=exc.message) + module.exit_json(changed=changed, msg='Object \'%s\' successfully added' % obj_name) + elif operation == "update": + # Search for the object. Update if found. + match = find_object(device, dev_group, obj_name, obj_type) + if match: + try: + new_object = create_object( + addressobject=addressobject, + addressgroup=addressgroup, + serviceobject=serviceobject, + servicegroup=servicegroup, + address=address, + address_type=address_type, + static_value=static_value, + dynamic_value=dynamic_value, + protocol=protocol, + source_port=source_port, + destination_port=destination_port, + services=services, + description=description, + tag_name=tag_name, + color=color + ) + changed = add_object(device, dev_group, new_object) + except PanXapiError: + exc = get_exception() + module.fail_json(msg=exc.message) + module.exit_json(changed=changed, msg='Object \'%s\' successfully updated.' % obj_name) + else: + module.fail_json(msg='Object \'%s\' does not exist. Use operation: \'add\' to add it.' % obj_name) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/network/panos/panos_service.py b/lib/ansible/modules/network/panos/panos_service.py deleted file mode 100644 index 0c22785034..0000000000 --- a/lib/ansible/modules/network/panos/panos_service.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Ansible module to manage PaloAltoNetworks Firewall -# (c) 2016, techbizdev -# -# 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 . - -DOCUMENTATION = ''' ---- -module: panos_service -short_description: create a service object -description: - - Create a service object. Service objects are fundamental representation of the applications given src/dst ports and protocol -author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)" -version_added: "2.3" -requirements: - - pan-python -options: - ip_address: - description: - - IP address (or hostname) of PAN-OS device - required: true - password: - description: - - password for authentication - required: true - username: - description: - - username for authentication - required: false - default: "admin" - service_name: - description: - - name of the service - required: true - protocol: - description: - - protocol for the service, should be tcp or udp - required: true - port: - description: - - destination port - required: true - source_port: - description: - - source port - required: false - default: None - commit: - description: - - commit if changed - required: false - default: true -''' - -EXAMPLES = ''' -# Creates service for port 22 - - name: create SSH service - panos_service: - ip_address: "192.168.1.1" - password: "admin" - service_name: "service-tcp-22" - protocol: "tcp" - port: "22" -''' - -RETURN=''' -# Default return values -''' - -ANSIBLE_METADATA = {'metadata_version': '1.0', - 'status': ['preview'], - 'supported_by': 'community'} - - - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.basic import get_exception - -try: - import pan.xapi - from pan.xapi import PanXapiError - HAS_LIB = True -except ImportError: - HAS_LIB = False - -_SERVICE_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\ - "/vsys/entry[@name='vsys1']" +\ - "/service/entry[@name='%s']" - - -def service_exists(xapi, service_name): - xapi.get(_SERVICE_XPATH % service_name) - e = xapi.element_root.find('.//entry') - if e is None: - return False - return True - - -def add_service(xapi, module, service_name, protocol, port, source_port): - if service_exists(xapi, service_name): - return False - - exml = [''] - exml.append('<%s>' % protocol) - exml.append('%s' % port) - if source_port: - exml.append('%s' % source_port) - exml.append('' % protocol) - exml.append('') - - exml = ''.join(exml) - - xapi.set(xpath=_SERVICE_XPATH % service_name, element=exml) - - return True - - -def main(): - argument_spec = dict( - ip_address=dict(required=True), - password=dict(required=True, no_log=True), - username=dict(default='admin'), - service_name=dict(required=True), - protocol=dict(required=True, choices=['tcp', 'udp']), - port=dict(required=True), - source_port=dict(), - commit=dict(type='bool', default=True) - ) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) - if not HAS_LIB: - module.fail_json(msg='pan-python is required for this module') - - ip_address = module.params["ip_address"] - password = module.params["password"] - username = module.params['username'] - service_name = module.params['service_name'] - protocol = module.params['protocol'] - port = module.params['port'] - source_port = module.params['source_port'] - commit = module.params['commit'] - - xapi = pan.xapi.PanXapi( - hostname=ip_address, - api_username=username, - api_password=password - ) - - try: - changed = add_service(xapi, module, - service_name, - protocol, - port, - source_port) - if changed and commit: - xapi.commit(cmd="", sync=True, interval=1) - except PanXapiError: - exc = get_exception() - module.fail_json(msg=exc.message) - - module.exit_json(changed=changed, msg="okey dokey") - -if __name__ == '__main__': - main() diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index 4ba7b15c65..6352547ef1 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -349,7 +349,6 @@ lib/ansible/modules/network/panos/panos_interface.py lib/ansible/modules/network/panos/panos_loadcfg.py lib/ansible/modules/network/panos/panos_mgtconfig.py lib/ansible/modules/network/panos/panos_pg.py -lib/ansible/modules/network/panos/panos_service.py lib/ansible/modules/net_tools/snmp_facts.py lib/ansible/modules/network/sros/sros_command.py lib/ansible/modules/network/sros/sros_config.py