mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-20 18:59:08 +00:00
Initial commit
This commit is contained in:
commit
aebc1b03fd
4861 changed files with 812621 additions and 0 deletions
196
plugins/modules/network/panos/panos_admin.py
Normal file
196
plugins/modules/network/panos/panos_admin.py
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_admin
|
||||
short_description: Add or modify PAN-OS user accounts password.
|
||||
description:
|
||||
- PanOS module that allows changes to the user account passwords by doing
|
||||
API calls to the Firewall using pan-api as the protocol.
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
admin_username:
|
||||
description:
|
||||
- username for admin user
|
||||
default: "admin"
|
||||
admin_password:
|
||||
description:
|
||||
- password for admin user
|
||||
required: true
|
||||
role:
|
||||
description:
|
||||
- role for admin user
|
||||
commit:
|
||||
description:
|
||||
- commit if changed
|
||||
type: bool
|
||||
default: 'yes'
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Set the password of user admin to "badpassword"
|
||||
# Doesn't commit the candidate config
|
||||
- name: set admin password
|
||||
panos_admin:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "admin"
|
||||
admin_username: admin
|
||||
admin_password: "badpassword"
|
||||
commit: False
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
status:
|
||||
description: success status
|
||||
returned: success
|
||||
type: str
|
||||
sample: "okey dokey"
|
||||
'''
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
_ADMIN_XPATH = "/config/mgt-config/users/entry[@name='%s']"
|
||||
|
||||
|
||||
def admin_exists(xapi, admin_username):
|
||||
xapi.get(_ADMIN_XPATH % admin_username)
|
||||
e = xapi.element_root.find('.//entry')
|
||||
return e
|
||||
|
||||
|
||||
def admin_set(xapi, module, admin_username, admin_password, role):
|
||||
if admin_password is not None:
|
||||
xapi.op(cmd='request password-hash password "%s"' % admin_password,
|
||||
cmd_xml=True)
|
||||
r = xapi.element_root
|
||||
phash = r.find('.//phash').text
|
||||
if role is not None:
|
||||
rbval = "yes"
|
||||
if role != "superuser" and role != 'superreader':
|
||||
rbval = ""
|
||||
|
||||
ea = admin_exists(xapi, admin_username)
|
||||
if ea is not None:
|
||||
# user exists
|
||||
changed = False
|
||||
|
||||
if role is not None:
|
||||
rb = ea.find('.//role-based')
|
||||
if rb is not None:
|
||||
if rb[0].tag != role:
|
||||
changed = True
|
||||
xpath = _ADMIN_XPATH % admin_username
|
||||
xpath += '/permissions/role-based/%s' % rb[0].tag
|
||||
xapi.delete(xpath=xpath)
|
||||
|
||||
xpath = _ADMIN_XPATH % admin_username
|
||||
xpath += '/permissions/role-based'
|
||||
xapi.set(xpath=xpath,
|
||||
element='<%s>%s</%s>' % (role, rbval, role))
|
||||
|
||||
if admin_password is not None:
|
||||
xapi.edit(xpath=_ADMIN_XPATH % admin_username + '/phash',
|
||||
element='<phash>%s</phash>' % phash)
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
# setup the non encrypted part of the monitor
|
||||
exml = []
|
||||
|
||||
exml.append('<phash>%s</phash>' % phash)
|
||||
exml.append('<permissions><role-based><%s>%s</%s>'
|
||||
'</role-based></permissions>' % (role, rbval, role))
|
||||
|
||||
exml = ''.join(exml)
|
||||
# module.fail_json(msg=exml)
|
||||
|
||||
xapi.set(xpath=_ADMIN_XPATH % admin_username, element=exml)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(),
|
||||
password=dict(no_log=True),
|
||||
username=dict(default='admin'),
|
||||
admin_username=dict(default='admin'),
|
||||
admin_password=dict(no_log=True),
|
||||
role=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 required for this module')
|
||||
|
||||
ip_address = module.params["ip_address"]
|
||||
if not ip_address:
|
||||
module.fail_json(msg="ip_address should be specified")
|
||||
password = module.params["password"]
|
||||
if not password:
|
||||
module.fail_json(msg="password is required")
|
||||
username = module.params['username']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password
|
||||
)
|
||||
|
||||
admin_username = module.params['admin_username']
|
||||
if admin_username is None:
|
||||
module.fail_json(msg="admin_username is required")
|
||||
admin_password = module.params['admin_password']
|
||||
role = module.params['role']
|
||||
commit = module.params['commit']
|
||||
|
||||
changed = admin_set(xapi, module, admin_username, admin_password, role)
|
||||
|
||||
if changed and commit:
|
||||
xapi.commit(cmd="<commit></commit>", sync=True, interval=1)
|
||||
|
||||
module.exit_json(changed=changed, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
205
plugins/modules/network/panos/panos_admpwd.py
Normal file
205
plugins/modules/network/panos/panos_admpwd.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_admpwd
|
||||
short_description: change admin password of PAN-OS device using SSH with SSH key
|
||||
description:
|
||||
- Change the admin password of PAN-OS via SSH using a SSH key for authentication.
|
||||
- Useful for AWS instances where the first login should be done via SSH.
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- paramiko
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- IP address (or hostname) of PAN-OS device
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- username for initial authentication
|
||||
required: false
|
||||
default: "admin"
|
||||
key_filename:
|
||||
description:
|
||||
- filename of the SSH Key to use for authentication
|
||||
required: true
|
||||
newpassword:
|
||||
description:
|
||||
- password to configure for admin on the PAN-OS device
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Tries for 10 times to set the admin password of 192.168.1.1 to "badpassword"
|
||||
# via SSH, authenticating using key /tmp/ssh.key
|
||||
- name: set admin password
|
||||
panos_admpwd:
|
||||
ip_address: "192.168.1.1"
|
||||
username: "admin"
|
||||
key_filename: "/tmp/ssh.key"
|
||||
newpassword: "badpassword"
|
||||
register: result
|
||||
until: result is not failed
|
||||
retries: 10
|
||||
delay: 30
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
status:
|
||||
description: success status
|
||||
returned: success
|
||||
type: str
|
||||
sample: "Last login: Fri Sep 16 11:09:20 2016 from 10.35.34.56.....Configuration committed successfully"
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.compat.paramiko import paramiko
|
||||
import time
|
||||
import sys
|
||||
|
||||
_PROMPTBUFF = 4096
|
||||
|
||||
|
||||
def wait_with_timeout(module, shell, prompt, timeout=60):
|
||||
now = time.time()
|
||||
result = ""
|
||||
while True:
|
||||
if shell.recv_ready():
|
||||
result += shell.recv(_PROMPTBUFF)
|
||||
endresult = result.strip()
|
||||
if len(endresult) != 0 and endresult[-1] == prompt:
|
||||
break
|
||||
|
||||
if time.time() - now > timeout:
|
||||
module.fail_json(msg="Timeout waiting for prompt")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def set_panwfw_password(module, ip_address, key_filename, newpassword, username):
|
||||
stdout = ""
|
||||
|
||||
ssh = paramiko.SSHClient()
|
||||
|
||||
# add policy to accept all host keys, I haven't found
|
||||
# a way to retrieve the instance SSH key fingerprint from AWS
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
ssh.connect(ip_address, username=username, key_filename=key_filename)
|
||||
shell = ssh.invoke_shell()
|
||||
|
||||
# wait for the shell to start
|
||||
buff = wait_with_timeout(module, shell, ">")
|
||||
stdout += buff
|
||||
|
||||
# step into config mode
|
||||
shell.send('configure\n')
|
||||
# wait for the config prompt
|
||||
buff = wait_with_timeout(module, shell, "#")
|
||||
stdout += buff
|
||||
|
||||
if module.check_mode:
|
||||
# exit and close connection
|
||||
shell.send('exit\n')
|
||||
ssh.close()
|
||||
return False, 'Connection test successful. Password left intact.'
|
||||
|
||||
# set admin password
|
||||
shell.send('set mgt-config users ' + username + ' password\n')
|
||||
|
||||
# wait for the password prompt
|
||||
buff = wait_with_timeout(module, shell, ":")
|
||||
stdout += buff
|
||||
|
||||
# enter password for the first time
|
||||
shell.send(newpassword + '\n')
|
||||
|
||||
# wait for the password prompt
|
||||
buff = wait_with_timeout(module, shell, ":")
|
||||
stdout += buff
|
||||
|
||||
# enter password for the second time
|
||||
shell.send(newpassword + '\n')
|
||||
|
||||
# wait for the config mode prompt
|
||||
buff = wait_with_timeout(module, shell, "#")
|
||||
stdout += buff
|
||||
|
||||
# commit !
|
||||
shell.send('commit\n')
|
||||
|
||||
# wait for the prompt
|
||||
buff = wait_with_timeout(module, shell, "#", 120)
|
||||
stdout += buff
|
||||
|
||||
if 'success' not in buff:
|
||||
module.fail_json(msg="Error setting " + username + " password: " + stdout)
|
||||
|
||||
# exit
|
||||
shell.send('exit\n')
|
||||
|
||||
ssh.close()
|
||||
|
||||
return True, stdout
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
username=dict(default='admin'),
|
||||
key_filename=dict(required=True),
|
||||
newpassword=dict(no_log=True, required=True)
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
|
||||
if paramiko is None:
|
||||
module.fail_json(msg='paramiko is required for this module')
|
||||
|
||||
ip_address = module.params["ip_address"]
|
||||
if not ip_address:
|
||||
module.fail_json(msg="ip_address should be specified")
|
||||
key_filename = module.params["key_filename"]
|
||||
if not key_filename:
|
||||
module.fail_json(msg="key_filename should be specified")
|
||||
newpassword = module.params["newpassword"]
|
||||
if not newpassword:
|
||||
module.fail_json(msg="newpassword is required")
|
||||
username = module.params['username']
|
||||
|
||||
try:
|
||||
changed, stdout = set_panwfw_password(module, ip_address, key_filename, newpassword, username)
|
||||
module.exit_json(changed=changed, stdout=stdout)
|
||||
except Exception:
|
||||
x = sys.exc_info()[1]
|
||||
module.fail_json(msg=x)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
194
plugins/modules/network/panos/panos_cert_gen_ssh.py
Normal file
194
plugins/modules/network/panos/panos_cert_gen_ssh.py
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_cert_gen_ssh
|
||||
short_description: generates a self-signed certificate using SSH protocol with SSH key
|
||||
description:
|
||||
- This module generates a self-signed certificate that can be used by GlobalProtect client, SSL connector, or
|
||||
- otherwise. Root certificate must be preset on the system first. This module depends on paramiko for ssh.
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- paramiko
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
notes:
|
||||
- Checkmode is not supported.
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- IP address (or hostname) of PAN-OS device being configured.
|
||||
required: true
|
||||
key_filename:
|
||||
description:
|
||||
- Location of the filename that is used for the auth. Either I(key_filename) or I(password) is required.
|
||||
required: true
|
||||
password:
|
||||
description:
|
||||
- Password credentials to use for auth. Either I(key_filename) or I(password) is required.
|
||||
required: true
|
||||
cert_friendly_name:
|
||||
description:
|
||||
- Human friendly certificate name (not CN but just a friendly name).
|
||||
required: true
|
||||
cert_cn:
|
||||
description:
|
||||
- Certificate CN (common name) embedded in the certificate signature.
|
||||
required: true
|
||||
signed_by:
|
||||
description:
|
||||
- Undersigning authority (CA) that MUST already be presents on the device.
|
||||
required: true
|
||||
rsa_nbits:
|
||||
description:
|
||||
- Number of bits used by the RSA algorithm for the certificate generation.
|
||||
default: "2048"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Generates a new self-signed certificate using ssh
|
||||
- name: generate self signed certificate
|
||||
panos_cert_gen_ssh:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "paloalto"
|
||||
cert_cn: "1.1.1.1"
|
||||
cert_friendly_name: "test123"
|
||||
signed_by: "root-ca"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.compat.paramiko import paramiko
|
||||
import time
|
||||
|
||||
_PROMPTBUFF = 4096
|
||||
|
||||
|
||||
def wait_with_timeout(module, shell, prompt, timeout=60):
|
||||
now = time.time()
|
||||
result = ""
|
||||
while True:
|
||||
if shell.recv_ready():
|
||||
result += shell.recv(_PROMPTBUFF)
|
||||
endresult = result.strip()
|
||||
if len(endresult) != 0 and endresult[-1] == prompt:
|
||||
break
|
||||
|
||||
if time.time() - now > timeout:
|
||||
module.fail_json(msg="Timeout waiting for prompt")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_cert(module, ip_address, key_filename, password,
|
||||
cert_cn, cert_friendly_name, signed_by, rsa_nbits):
|
||||
stdout = ""
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
|
||||
# add policy to accept all host keys, I haven't found
|
||||
# a way to retrieve the instance SSH key fingerprint from AWS
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
if not key_filename:
|
||||
client.connect(ip_address, username="admin", password=password)
|
||||
else:
|
||||
client.connect(ip_address, username="admin", key_filename=key_filename)
|
||||
|
||||
shell = client.invoke_shell()
|
||||
# wait for the shell to start
|
||||
buff = wait_with_timeout(module, shell, ">")
|
||||
stdout += buff
|
||||
|
||||
# generate self-signed certificate
|
||||
if isinstance(cert_cn, list):
|
||||
cert_cn = cert_cn[0]
|
||||
cmd = 'request certificate generate signed-by {0} certificate-name {1} name {2} algorithm RSA rsa-nbits {3}\n'.format(
|
||||
signed_by, cert_friendly_name, cert_cn, rsa_nbits)
|
||||
shell.send(cmd)
|
||||
|
||||
# wait for the shell to complete
|
||||
buff = wait_with_timeout(module, shell, ">")
|
||||
stdout += buff
|
||||
|
||||
# exit
|
||||
shell.send('exit\n')
|
||||
|
||||
if 'Success' not in buff:
|
||||
module.fail_json(msg="Error generating self signed certificate: " + stdout)
|
||||
|
||||
client.close()
|
||||
return stdout
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
key_filename=dict(),
|
||||
password=dict(no_log=True),
|
||||
cert_cn=dict(required=True),
|
||||
cert_friendly_name=dict(required=True),
|
||||
rsa_nbits=dict(default='2048'),
|
||||
signed_by=dict(required=True)
|
||||
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False,
|
||||
required_one_of=[['key_filename', 'password']])
|
||||
if paramiko is None:
|
||||
module.fail_json(msg='paramiko is required for this module')
|
||||
|
||||
ip_address = module.params["ip_address"]
|
||||
key_filename = module.params["key_filename"]
|
||||
password = module.params["password"]
|
||||
cert_cn = module.params["cert_cn"]
|
||||
cert_friendly_name = module.params["cert_friendly_name"]
|
||||
signed_by = module.params["signed_by"]
|
||||
rsa_nbits = module.params["rsa_nbits"]
|
||||
|
||||
try:
|
||||
stdout = generate_cert(module,
|
||||
ip_address,
|
||||
key_filename,
|
||||
password,
|
||||
cert_cn,
|
||||
cert_friendly_name,
|
||||
signed_by,
|
||||
rsa_nbits)
|
||||
except Exception as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
module.exit_json(changed=True, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
147
plugins/modules/network/panos/panos_check.py
Normal file
147
plugins/modules/network/panos/panos_check.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_check
|
||||
short_description: check if PAN-OS device is ready for configuration
|
||||
description:
|
||||
- Check if PAN-OS device is ready for being configured (no pending jobs).
|
||||
- The check could be done once or multiple times until the device is ready.
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
timeout:
|
||||
description:
|
||||
- timeout of API calls
|
||||
required: false
|
||||
default: 0
|
||||
interval:
|
||||
description:
|
||||
- time waited between checks
|
||||
required: false
|
||||
default: 0
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# single check on 192.168.1.1 with credentials admin/admin
|
||||
- name: check if ready
|
||||
panos_check:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "admin"
|
||||
|
||||
# check for 10 times, every 30 seconds, if device 192.168.1.1
|
||||
# is ready, using credentials admin/admin
|
||||
- name: wait for reboot
|
||||
panos_check:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "admin"
|
||||
register: result
|
||||
until: result is not failed
|
||||
retries: 10
|
||||
delay: 30
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import time
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
def check_jobs(jobs, module):
|
||||
job_check = False
|
||||
for j in jobs:
|
||||
status = j.find('.//status')
|
||||
if status is None:
|
||||
return False
|
||||
if status.text != 'FIN':
|
||||
return False
|
||||
job_check = True
|
||||
return job_check
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
password=dict(required=True, no_log=True),
|
||||
username=dict(default='admin'),
|
||||
timeout=dict(default=0, type='int'),
|
||||
interval=dict(default=0, type='int')
|
||||
)
|
||||
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']
|
||||
timeout = module.params['timeout']
|
||||
interval = module.params['interval']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
checkpnt = time.time() + timeout
|
||||
while True:
|
||||
try:
|
||||
xapi.op(cmd="show jobs all", cmd_xml=True)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
jobs = xapi.element_root.findall('.//job')
|
||||
if check_jobs(jobs, module):
|
||||
module.exit_json(changed=True, msg="okey dokey")
|
||||
|
||||
if time.time() > checkpnt:
|
||||
break
|
||||
|
||||
time.sleep(interval)
|
||||
|
||||
module.fail_json(msg="Timeout")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
235
plugins/modules/network/panos/panos_commit.py
Normal file
235
plugins/modules/network/panos/panos_commit.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2019, Tomi Raittinen <tomi.raittinen@gmail.com>
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_commit
|
||||
short_description: commit firewall's candidate configuration
|
||||
description:
|
||||
- PanOS module that will commit firewall's candidate configuration on
|
||||
- the device. The new configuration will become active immediately.
|
||||
author:
|
||||
- Luigi Mori (@jtschichold)
|
||||
- Ivan Bojer (@ivanbojer)
|
||||
- Tomi Raittinen (@traittinen)
|
||||
requirements:
|
||||
- pan-python
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- IP address (or hostname) of PAN-OS device.
|
||||
required: true
|
||||
password:
|
||||
description:
|
||||
- Password for authentication. If the value is not specified in the
|
||||
task, the value of environment variable C(ANSIBLE_NET_PASSWORD)
|
||||
will be used instead.
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- Username for authentication. If the value is not specified in the
|
||||
task, the value of environment variable C(ANSIBLE_NET_USERNAME)
|
||||
will be used instead if defined. C(admin) will be used if nothing
|
||||
above is defined.
|
||||
default: admin
|
||||
interval:
|
||||
description:
|
||||
- interval for checking commit job
|
||||
default: 0.5
|
||||
timeout:
|
||||
description:
|
||||
- timeout for commit job
|
||||
sync:
|
||||
description:
|
||||
- if commit should be synchronous
|
||||
type: bool
|
||||
default: 'yes'
|
||||
description:
|
||||
description:
|
||||
- Commit description/comment
|
||||
type: str
|
||||
commit_changes_by:
|
||||
description:
|
||||
- Commit changes made by specified admin
|
||||
type: list
|
||||
commit_vsys:
|
||||
description:
|
||||
- Commit changes for specified VSYS
|
||||
type: list
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Commit candidate config on 192.168.1.1 in sync mode
|
||||
- panos_commit:
|
||||
ip_address: "192.168.1.1"
|
||||
username: "admin"
|
||||
password: "admin"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
panos_commit:
|
||||
description: Information about commit job.
|
||||
returned: always
|
||||
type: complex
|
||||
version_added: 2.8
|
||||
contains:
|
||||
job_id:
|
||||
description: Palo Alto job ID for the commit operation. Only returned if commit job is launched on device.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "139"
|
||||
status_code:
|
||||
description: Palo Alto API status code. Null if commit is successful.
|
||||
returned: always
|
||||
type: str
|
||||
sample: 19
|
||||
status_detail:
|
||||
description: Palo Alto API detailed status message.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Configuration committed successfully
|
||||
status_text:
|
||||
description: Palo Alto API status text.
|
||||
returned: always
|
||||
type: str
|
||||
sample: success
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True, type='str'),
|
||||
password=dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
|
||||
username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']), default="admin"),
|
||||
interval=dict(default=0.5),
|
||||
timeout=dict(),
|
||||
sync=dict(type='bool', default=True),
|
||||
description=dict(type='str'),
|
||||
commit_changes_by=dict(type='list'),
|
||||
commit_vsys=dict(type='list')
|
||||
)
|
||||
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"]
|
||||
if not ip_address:
|
||||
module.fail_json(msg="ip_address should be specified")
|
||||
|
||||
password = module.params["password"]
|
||||
if not password:
|
||||
module.fail_json(msg="password is required")
|
||||
|
||||
username = module.params['username']
|
||||
if not username:
|
||||
module.fail_json(msg="username is required")
|
||||
|
||||
interval = module.params['interval']
|
||||
timeout = module.params['timeout']
|
||||
sync = module.params['sync']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password
|
||||
)
|
||||
|
||||
cmd = "<commit>"
|
||||
|
||||
description = module.params["description"]
|
||||
if description:
|
||||
cmd += "<description>" + description + "</description>"
|
||||
|
||||
commit_changes_by = module.params["commit_changes_by"]
|
||||
commit_vsys = module.params["commit_vsys"]
|
||||
|
||||
if commit_changes_by or commit_vsys:
|
||||
|
||||
cmd += "<partial>"
|
||||
|
||||
if commit_changes_by:
|
||||
cmd += "<admin>"
|
||||
for admin in commit_changes_by:
|
||||
cmd += "<member>" + admin + "</member>"
|
||||
cmd += "</admin>"
|
||||
|
||||
if commit_vsys:
|
||||
cmd += "<vsys>"
|
||||
for vsys in commit_vsys:
|
||||
cmd += "<member>" + vsys + "</member>"
|
||||
cmd += "</vsys>"
|
||||
|
||||
cmd += "</partial><force></force>"
|
||||
|
||||
cmd += "</commit>"
|
||||
|
||||
xapi.commit(
|
||||
cmd=cmd,
|
||||
sync=sync,
|
||||
interval=interval,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
try:
|
||||
result = xapi.xml_root().encode('utf-8')
|
||||
root = etree.fromstring(result)
|
||||
job_id = root.find('./result/job/id').text
|
||||
except AttributeError:
|
||||
job_id = None
|
||||
|
||||
panos_commit_details = dict(
|
||||
status_text=xapi.status,
|
||||
status_code=xapi.status_code,
|
||||
status_detail=xapi.status_detail,
|
||||
job_id=job_id
|
||||
)
|
||||
|
||||
if "Commit failed" in xapi.status_detail:
|
||||
module.fail_json(msg=xapi.status_detail, panos_commit=panos_commit_details)
|
||||
|
||||
if job_id:
|
||||
module.exit_json(changed=True, msg="Commit successful.", panos_commit=panos_commit_details)
|
||||
else:
|
||||
module.exit_json(changed=False, msg="No changes to commit.", panos_commit=panos_commit_details)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
145
plugins/modules/network/panos/panos_dag.py
Normal file
145
plugins/modules/network/panos/panos_dag.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_dag
|
||||
short_description: create a dynamic address group
|
||||
description:
|
||||
- Create a dynamic address group object in the firewall used for policy rules
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
dag_name:
|
||||
description:
|
||||
- name of the dynamic address group
|
||||
required: true
|
||||
dag_filter:
|
||||
description:
|
||||
- dynamic filter user by the dynamic address group
|
||||
required: true
|
||||
commit:
|
||||
description:
|
||||
- commit if changed
|
||||
type: bool
|
||||
default: 'yes'
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: dag
|
||||
panos_dag:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "admin"
|
||||
dag_name: "dag-1"
|
||||
dag_filter: "'aws-tag.aws:cloudformation:logical-id.ServerInstance' and 'instanceState.running'"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
_ADDRGROUP_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\
|
||||
"/vsys/entry[@name='vsys1']/address-group/entry[@name='%s']"
|
||||
|
||||
|
||||
def addressgroup_exists(xapi, group_name):
|
||||
xapi.get(_ADDRGROUP_XPATH % group_name)
|
||||
e = xapi.element_root.find('.//entry')
|
||||
if e is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def add_dag(xapi, dag_name, dag_filter):
|
||||
if addressgroup_exists(xapi, dag_name):
|
||||
return False
|
||||
|
||||
# setup the non encrypted part of the monitor
|
||||
exml = []
|
||||
|
||||
exml.append('<dynamic>')
|
||||
exml.append('<filter>%s</filter>' % dag_filter)
|
||||
exml.append('</dynamic>')
|
||||
|
||||
exml = ''.join(exml)
|
||||
xapi.set(xpath=_ADDRGROUP_XPATH % dag_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'),
|
||||
dag_name=dict(required=True),
|
||||
dag_filter=dict(required=True),
|
||||
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']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password
|
||||
)
|
||||
|
||||
dag_name = module.params['dag_name']
|
||||
dag_filter = module.params['dag_filter']
|
||||
commit = module.params['commit']
|
||||
|
||||
changed = add_dag(xapi, dag_name, dag_filter)
|
||||
|
||||
if changed and commit:
|
||||
xapi.commit(cmd="<commit></commit>", sync=True, interval=1)
|
||||
|
||||
module.exit_json(changed=changed, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
232
plugins/modules/network/panos/panos_dag_tags.py
Normal file
232
plugins/modules/network/panos/panos_dag_tags.py
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
# limitations under the License.
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_dag_tags
|
||||
short_description: Create tags for DAG's on PAN-OS devices.
|
||||
description:
|
||||
- Create the ip address to tag associations. Tags will in turn be used to create DAG's
|
||||
author: "Vinay Venkataraghavan (@vinayvenkat)"
|
||||
requirements:
|
||||
- pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/)
|
||||
- pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/)
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
notes:
|
||||
- Checkmode is not supported.
|
||||
- Panorama is not supported.
|
||||
options:
|
||||
api_key:
|
||||
description:
|
||||
- API key that can be used instead of I(username)/I(password) credentials.
|
||||
description:
|
||||
description:
|
||||
- The purpose / objective of the static Address Group
|
||||
commit:
|
||||
description:
|
||||
- commit if changed
|
||||
default: true
|
||||
type: bool
|
||||
devicegroup:
|
||||
description: >
|
||||
- Device groups are used for the Panorama interaction with Firewall(s). The group must exists on Panorama.
|
||||
If device group is not define we assume that we are contacting Firewall.
|
||||
operation:
|
||||
description:
|
||||
- The action to be taken. Supported values are I(add)/I(update)/I(find)/I(delete).
|
||||
tag_names:
|
||||
description:
|
||||
- The list of the tags that will be added or removed from the IP address.
|
||||
ip_to_register:
|
||||
description:
|
||||
- IP that will be registered with the given tag names.
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create the tags to map IP addresses
|
||||
panos_dag_tags:
|
||||
ip_address: "{{ ip_address }}"
|
||||
password: "{{ password }}"
|
||||
ip_to_register: "{{ ip_to_register }}"
|
||||
tag_names: "{{ tag_names }}"
|
||||
description: "Tags to allow certain IP's to access various SaaS Applications"
|
||||
operation: 'add'
|
||||
tags: "adddagip"
|
||||
|
||||
- name: List the IP address to tag mapping
|
||||
panos_dag_tags:
|
||||
ip_address: "{{ ip_address }}"
|
||||
password: "{{ password }}"
|
||||
tag_names: "{{ tag_names }}"
|
||||
description: "List the IP address to tag mapping"
|
||||
operation: 'list'
|
||||
tags: "listdagip"
|
||||
|
||||
- name: Unregister an IP address from a tag mapping
|
||||
panos_dag_tags:
|
||||
ip_address: "{{ ip_address }}"
|
||||
password: "{{ password }}"
|
||||
ip_to_register: "{{ ip_to_register }}"
|
||||
tag_names: "{{ tag_names }}"
|
||||
description: "Unregister IP address from tag mappings"
|
||||
operation: 'delete'
|
||||
tags: "deletedagip"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
try:
|
||||
from pandevice import base
|
||||
from pandevice import firewall
|
||||
from pandevice import panorama
|
||||
from pandevice import objects
|
||||
|
||||
from pan.xapi import PanXapiError
|
||||
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
def get_devicegroup(device, devicegroup):
|
||||
dg_list = device.refresh_devices()
|
||||
for group in dg_list:
|
||||
if isinstance(group, panorama.DeviceGroup):
|
||||
if group.name == devicegroup:
|
||||
return group
|
||||
return False
|
||||
|
||||
|
||||
def register_ip_to_tag_map(device, ip_addresses, tag):
|
||||
exc = None
|
||||
try:
|
||||
device.userid.register(ip_addresses, tag)
|
||||
except PanXapiError as exc:
|
||||
return False, exc
|
||||
|
||||
return True, exc
|
||||
|
||||
|
||||
def get_all_address_group_mapping(device):
|
||||
exc = None
|
||||
ret = None
|
||||
try:
|
||||
ret = device.userid.get_registered_ip()
|
||||
except PanXapiError as exc:
|
||||
return False, exc
|
||||
|
||||
return ret, exc
|
||||
|
||||
|
||||
def delete_address_from_mapping(device, ip_address, tags):
|
||||
exc = None
|
||||
try:
|
||||
ret = device.userid.unregister(ip_address, tags)
|
||||
except PanXapiError as exc:
|
||||
return False, exc
|
||||
|
||||
return True, exc
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
password=dict(required=True, no_log=True),
|
||||
username=dict(default='admin'),
|
||||
api_key=dict(no_log=True),
|
||||
devicegroup=dict(default=None),
|
||||
description=dict(default=None),
|
||||
ip_to_register=dict(type='str', required=False),
|
||||
tag_names=dict(type='list', required=True),
|
||||
commit=dict(type='bool', default=True),
|
||||
operation=dict(type='str', required=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']
|
||||
api_key = module.params['api_key']
|
||||
commit = module.params['commit']
|
||||
devicegroup = module.params['devicegroup']
|
||||
operation = module.params['operation']
|
||||
|
||||
# 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)
|
||||
|
||||
result = None
|
||||
if operation == 'add':
|
||||
result, exc = register_ip_to_tag_map(device,
|
||||
ip_addresses=module.params.get('ip_to_register', None),
|
||||
tag=module.params.get('tag_names', None)
|
||||
)
|
||||
elif operation == 'list':
|
||||
result, exc = get_all_address_group_mapping(device)
|
||||
elif operation == 'delete':
|
||||
result, exc = delete_address_from_mapping(device,
|
||||
ip_address=module.params.get('ip_to_register', None),
|
||||
tags=module.params.get('tag_names', [])
|
||||
)
|
||||
else:
|
||||
module.fail_json(msg="Unsupported option")
|
||||
|
||||
if not result:
|
||||
module.fail_json(msg=exc.message)
|
||||
|
||||
if commit:
|
||||
try:
|
||||
device.commit(sync=True)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
module.exit_json(changed=True, msg=result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
195
plugins/modules/network/panos/panos_import.py
Normal file
195
plugins/modules/network/panos/panos_import.py
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_import
|
||||
short_description: import file on PAN-OS devices
|
||||
description:
|
||||
- Import file on PAN-OS device
|
||||
notes:
|
||||
- API reference documentation can be read from the C(/api/) directory of your appliance
|
||||
- Certificate validation is enabled by default as of Ansible 2.6. This may break existing playbooks but should be disabled with caution.
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python
|
||||
- requests
|
||||
- requests_toolbelt
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
category:
|
||||
description:
|
||||
- Category of file uploaded. The default is software.
|
||||
- See API > Import section of the API reference for category options.
|
||||
default: software
|
||||
file:
|
||||
description:
|
||||
- Location of the file to import into device.
|
||||
url:
|
||||
description:
|
||||
- URL of the file that will be imported to device.
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated. Disabling certificate validation is not recommended.
|
||||
default: yes
|
||||
type: bool
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# import software image PanOS_vm-6.1.1 on 192.168.1.1
|
||||
- name: import software image into PAN-OS
|
||||
panos_import:
|
||||
ip_address: 192.168.1.1
|
||||
username: admin
|
||||
password: admin
|
||||
file: /tmp/PanOS_vm-6.1.1
|
||||
category: software
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
import os.path
|
||||
import xml.etree
|
||||
import tempfile
|
||||
import shutil
|
||||
import os
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
import requests
|
||||
import requests_toolbelt
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
def import_file(xapi, module, ip_address, file_, category):
|
||||
xapi.keygen()
|
||||
|
||||
params = {
|
||||
'type': 'import',
|
||||
'category': category,
|
||||
'key': xapi.api_key
|
||||
}
|
||||
|
||||
filename = os.path.basename(file_)
|
||||
|
||||
mef = requests_toolbelt.MultipartEncoder(
|
||||
fields={
|
||||
'file': (filename, open(file_, 'rb'), 'application/octet-stream')
|
||||
}
|
||||
)
|
||||
|
||||
r = requests.post(
|
||||
'https://' + ip_address + '/api/',
|
||||
verify=module.params['validate_certs'],
|
||||
params=params,
|
||||
headers={'Content-Type': mef.content_type},
|
||||
data=mef
|
||||
)
|
||||
|
||||
# if something goes wrong just raise an exception
|
||||
r.raise_for_status()
|
||||
|
||||
resp = xml.etree.ElementTree.fromstring(r.content)
|
||||
|
||||
if resp.attrib['status'] == 'error':
|
||||
module.fail_json(msg=r.content)
|
||||
|
||||
return True, filename
|
||||
|
||||
|
||||
def download_file(url):
|
||||
r = requests.get(url, stream=True)
|
||||
fo = tempfile.NamedTemporaryFile(prefix='ai', delete=False)
|
||||
shutil.copyfileobj(r.raw, fo)
|
||||
fo.close()
|
||||
|
||||
return fo.name
|
||||
|
||||
|
||||
def delete_file(path):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
password=dict(required=True, no_log=True),
|
||||
username=dict(default='admin'),
|
||||
category=dict(default='software'),
|
||||
file=dict(),
|
||||
url=dict(),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, required_one_of=[['file', 'url']])
|
||||
if not HAS_LIB:
|
||||
module.fail_json(msg='pan-python, requests, and requests_toolbelt are 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
|
||||
)
|
||||
|
||||
file_ = module.params['file']
|
||||
url = module.params['url']
|
||||
|
||||
category = module.params['category']
|
||||
|
||||
# we can get file from URL or local storage
|
||||
if url is not None:
|
||||
file_ = download_file(url)
|
||||
|
||||
try:
|
||||
changed, filename = import_file(xapi, module, ip_address, file_, category)
|
||||
except Exception as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
# cleanup and delete file if local
|
||||
if url is not None:
|
||||
delete_file(file_)
|
||||
|
||||
module.exit_json(changed=changed, filename=filename, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
179
plugins/modules/network/panos/panos_interface.py
Normal file
179
plugins/modules/network/panos/panos_interface.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_interface
|
||||
short_description: configure data-port network interface for DHCP
|
||||
description:
|
||||
- Configure data-port (DP) network interface for DHCP. By default DP interfaces are static.
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/)
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
notes:
|
||||
- Checkmode is not supported.
|
||||
options:
|
||||
if_name:
|
||||
description:
|
||||
- Name of the interface to configure.
|
||||
required: true
|
||||
zone_name:
|
||||
description: >
|
||||
Name of the zone for the interface. If the zone does not exist it is created but if the zone exists and
|
||||
it is not of the layer3 type the operation will fail.
|
||||
required: true
|
||||
create_default_route:
|
||||
description:
|
||||
- Whether or not to add default route with router learned via DHCP.
|
||||
default: "false"
|
||||
type: bool
|
||||
commit:
|
||||
description:
|
||||
- Commit if changed
|
||||
default: true
|
||||
type: bool
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: enable DHCP client on ethernet1/1 in zone public
|
||||
interface:
|
||||
password: "admin"
|
||||
ip_address: "192.168.1.1"
|
||||
if_name: "ethernet1/1"
|
||||
zone_name: "public"
|
||||
create_default_route: "yes"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
from pan.xapi import PanXapiError
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
_IF_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\
|
||||
"/network/interface/ethernet/entry[@name='%s']"
|
||||
|
||||
_ZONE_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\
|
||||
"/vsys/entry/zone/entry"
|
||||
_ZONE_XPATH_QUERY = _ZONE_XPATH + "[network/layer3/member/text()='%s']"
|
||||
_ZONE_XPATH_IF = _ZONE_XPATH + "[@name='%s']/network/layer3/member[text()='%s']"
|
||||
_VR_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\
|
||||
"/network/virtual-router/entry"
|
||||
|
||||
|
||||
def add_dhcp_if(xapi, if_name, zone_name, create_default_route):
|
||||
if_xml = [
|
||||
'<entry name="%s">',
|
||||
'<layer3>',
|
||||
'<dhcp-client>',
|
||||
'<create-default-route>%s</create-default-route>',
|
||||
'</dhcp-client>'
|
||||
'</layer3>'
|
||||
'</entry>'
|
||||
]
|
||||
cdr = 'yes'
|
||||
if not create_default_route:
|
||||
cdr = 'no'
|
||||
if_xml = (''.join(if_xml)) % (if_name, cdr)
|
||||
xapi.edit(xpath=_IF_XPATH % if_name, element=if_xml)
|
||||
|
||||
xapi.set(xpath=_ZONE_XPATH + "[@name='%s']/network/layer3" % zone_name,
|
||||
element='<member>%s</member>' % if_name)
|
||||
xapi.set(xpath=_VR_XPATH + "[@name='default']/interface",
|
||||
element='<member>%s</member>' % if_name)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def if_exists(xapi, if_name):
|
||||
xpath = _IF_XPATH % if_name
|
||||
xapi.get(xpath=xpath)
|
||||
network = xapi.element_root.find('.//layer3')
|
||||
return (network is not None)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
password=dict(required=True, no_log=True),
|
||||
username=dict(default='admin'),
|
||||
if_name=dict(required=True),
|
||||
zone_name=dict(required=True),
|
||||
create_default_route=dict(type='bool', default=False),
|
||||
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']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password
|
||||
)
|
||||
|
||||
if_name = module.params['if_name']
|
||||
zone_name = module.params['zone_name']
|
||||
create_default_route = module.params['create_default_route']
|
||||
commit = module.params['commit']
|
||||
|
||||
ifexists = if_exists(xapi, if_name)
|
||||
|
||||
if ifexists:
|
||||
module.exit_json(changed=False, msg="interface exists, not changed")
|
||||
|
||||
try:
|
||||
changed = add_dhcp_if(xapi, if_name, zone_name, create_default_route)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
if changed and commit:
|
||||
xapi.commit(cmd="<commit></commit>", sync=True, interval=1)
|
||||
|
||||
module.exit_json(changed=changed, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
173
plugins/modules/network/panos/panos_lic.py
Normal file
173
plugins/modules/network/panos/panos_lic.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_lic
|
||||
short_description: apply authcode to a device/instance
|
||||
description:
|
||||
- Apply an authcode to a device.
|
||||
- The authcode should have been previously registered on the Palo Alto Networks support portal.
|
||||
- The device should have Internet access.
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
auth_code:
|
||||
description:
|
||||
- authcode to be applied
|
||||
required: true
|
||||
force:
|
||||
description:
|
||||
- whether to apply authcode even if device is already licensed
|
||||
required: false
|
||||
default: "false"
|
||||
type: bool
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: fetch license
|
||||
panos_lic:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "paloalto"
|
||||
auth_code: "IBADCODE"
|
||||
register: result
|
||||
- name: Display serialnumber (if already registered)
|
||||
debug:
|
||||
var: "{{result.serialnumber}}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
serialnumber:
|
||||
description: serialnumber of the device in case that it has been already registered
|
||||
returned: success
|
||||
type: str
|
||||
sample: 007200004214
|
||||
'''
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
def get_serial(xapi, module):
|
||||
xapi.op(cmd="show system info", cmd_xml=True)
|
||||
r = xapi.element_root
|
||||
serial = r.find('.//serial')
|
||||
if serial is None:
|
||||
module.fail_json(msg="No <serial> tag in show system info")
|
||||
|
||||
serial = serial.text
|
||||
|
||||
return serial
|
||||
|
||||
|
||||
def apply_authcode(xapi, module, auth_code):
|
||||
try:
|
||||
xapi.op(cmd='request license fetch auth-code "%s"' % auth_code,
|
||||
cmd_xml=True)
|
||||
except pan.xapi.PanXapiError:
|
||||
if hasattr(xapi, 'xml_document'):
|
||||
if 'Successfully' in xapi.xml_document:
|
||||
return
|
||||
|
||||
if 'Invalid Auth Code' in xapi.xml_document:
|
||||
module.fail_json(msg="Invalid Auth Code")
|
||||
|
||||
raise
|
||||
|
||||
return
|
||||
|
||||
|
||||
def fetch_authcode(xapi, module):
|
||||
try:
|
||||
xapi.op(cmd='request license fetch', cmd_xml=True)
|
||||
except pan.xapi.PanXapiError:
|
||||
if hasattr(xapi, 'xml_document'):
|
||||
if 'Successfully' in xapi.xml_document:
|
||||
return
|
||||
|
||||
if 'Invalid Auth Code' in xapi.xml_document:
|
||||
module.fail_json(msg="Invalid Auth Code")
|
||||
|
||||
raise
|
||||
|
||||
return
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
password=dict(required=True, no_log=True),
|
||||
auth_code=dict(),
|
||||
username=dict(default='admin'),
|
||||
force=dict(type='bool', default=False)
|
||||
)
|
||||
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"]
|
||||
auth_code = module.params["auth_code"]
|
||||
force = module.params['force']
|
||||
username = module.params['username']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password
|
||||
)
|
||||
|
||||
if not force:
|
||||
serialnumber = get_serial(xapi, module)
|
||||
if serialnumber != 'unknown':
|
||||
return module.exit_json(changed=False, serialnumber=serialnumber)
|
||||
if auth_code:
|
||||
apply_authcode(xapi, module, auth_code)
|
||||
else:
|
||||
fetch_authcode(xapi, module)
|
||||
|
||||
module.exit_json(changed=True, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
125
plugins/modules/network/panos/panos_loadcfg.py
Normal file
125
plugins/modules/network/panos/panos_loadcfg.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_loadcfg
|
||||
short_description: load configuration on PAN-OS device
|
||||
description:
|
||||
- Load configuration on PAN-OS device
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
file:
|
||||
description:
|
||||
- configuration file to load
|
||||
commit:
|
||||
description:
|
||||
- commit if changed
|
||||
type: bool
|
||||
default: 'yes'
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Import and load config file from URL
|
||||
- name: import configuration
|
||||
panos_import:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "admin"
|
||||
url: "{{ConfigURL}}"
|
||||
category: "configuration"
|
||||
register: result
|
||||
- name: load configuration
|
||||
panos_loadcfg:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "admin"
|
||||
file: "{{result.filename}}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
def load_cfgfile(xapi, module, ip_address, file_):
|
||||
# load configuration file
|
||||
cmd = '<load><config><from>%s</from></config></load>' %\
|
||||
file_
|
||||
|
||||
xapi.op(cmd=cmd)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
password=dict(required=True, no_log=True),
|
||||
username=dict(default='admin'),
|
||||
file=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']
|
||||
file_ = module.params['file']
|
||||
commit = module.params['commit']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password
|
||||
)
|
||||
|
||||
changed = load_cfgfile(xapi, module, ip_address, file_)
|
||||
if changed and commit:
|
||||
xapi.commit(cmd="<commit></commit>", sync=True, interval=1)
|
||||
|
||||
module.exit_json(changed=changed, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
389
plugins/modules/network/panos/panos_match_rule.py
Normal file
389
plugins/modules/network/panos/panos_match_rule.py
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
# limitations under the License.
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_match_rule
|
||||
short_description: Test for match against a security rule on PAN-OS devices or Panorama management console.
|
||||
description:
|
||||
- Security policies allow you to enforce rules and take action, and can be as general or specific as needed.
|
||||
author: "Robert Hagen (@rnh556)"
|
||||
requirements:
|
||||
- pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/)
|
||||
- pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/)
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
notes:
|
||||
- Checkmode is not supported.
|
||||
- Panorama NOT is supported.
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- IP address (or hostname) of PAN-OS device being configured.
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- Username credentials to use for auth unless I(api_key) is set.
|
||||
default: "admin"
|
||||
password:
|
||||
description:
|
||||
- Password credentials to use for auth unless I(api_key) is set.
|
||||
required: true
|
||||
api_key:
|
||||
description:
|
||||
- API key that can be used instead of I(username)/I(password) credentials.
|
||||
rule_type:
|
||||
description:
|
||||
- Type of rule. Valid types are I(security) or I(nat).
|
||||
required: true
|
||||
choices:
|
||||
- security
|
||||
- nat
|
||||
source_zone:
|
||||
description:
|
||||
- The source zone.
|
||||
source_ip:
|
||||
description:
|
||||
- The source IP address.
|
||||
required: true
|
||||
source_port:
|
||||
description:
|
||||
- The source port.
|
||||
source_user:
|
||||
description:
|
||||
- The source user or group.
|
||||
to_interface:
|
||||
description:
|
||||
- The inbound interface in a NAT rule.
|
||||
destination_zone:
|
||||
description:
|
||||
- The destination zone.
|
||||
destination_ip:
|
||||
description:
|
||||
- The destination IP address.
|
||||
destination_port:
|
||||
description:
|
||||
- The destination port.
|
||||
application:
|
||||
description:
|
||||
- The application.
|
||||
protocol:
|
||||
description:
|
||||
- The IP protocol number from 1 to 255.
|
||||
category:
|
||||
description:
|
||||
- URL category
|
||||
vsys_id:
|
||||
description:
|
||||
- ID of the VSYS object.
|
||||
default: "vsys1"
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: check security rules for Google DNS
|
||||
panos_match_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
rule_type: 'security'
|
||||
source_ip: '10.0.0.0'
|
||||
destination_ip: '8.8.8.8'
|
||||
application: 'dns'
|
||||
destination_port: '53'
|
||||
protocol: '17'
|
||||
register: result
|
||||
- debug: msg='{{result.stdout_lines}}'
|
||||
|
||||
- name: check security rules inbound SSH with user match
|
||||
panos_match_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
rule_type: 'security'
|
||||
source_ip: '0.0.0.0'
|
||||
source_user: 'mydomain\\jsmith'
|
||||
destination_ip: '192.168.100.115'
|
||||
destination_port: '22'
|
||||
protocol: '6'
|
||||
register: result
|
||||
- debug: msg='{{result.stdout_lines}}'
|
||||
|
||||
- name: check NAT rules for source NAT
|
||||
panos_match_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
rule_type: 'nat'
|
||||
source_zone: 'Prod-DMZ'
|
||||
source_ip: '10.10.118.50'
|
||||
to_interface: 'ethernet1/2'
|
||||
destination_zone: 'Internet'
|
||||
destination_ip: '0.0.0.0'
|
||||
protocol: '6'
|
||||
register: result
|
||||
- debug: msg='{{result.stdout_lines}}'
|
||||
|
||||
- name: check NAT rules for inbound web
|
||||
panos_match_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
rule_type: 'nat'
|
||||
source_zone: 'Internet'
|
||||
source_ip: '0.0.0.0'
|
||||
to_interface: 'ethernet1/1'
|
||||
destination_zone: 'Prod DMZ'
|
||||
destination_ip: '192.168.118.50'
|
||||
destination_port: '80'
|
||||
protocol: '6'
|
||||
register: result
|
||||
- debug: msg='{{result.stdout_lines}}'
|
||||
|
||||
- name: check security rules for outbound POP3 in vsys4
|
||||
panos_match_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
vsys_id: 'vsys4'
|
||||
rule_type: 'security'
|
||||
source_ip: '10.0.0.0'
|
||||
destination_ip: '4.3.2.1'
|
||||
application: 'pop3'
|
||||
destination_port: '110'
|
||||
protocol: '6'
|
||||
register: result
|
||||
- debug: msg='{{result.stdout_lines}}'
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
from pan.xapi import PanXapiError
|
||||
from pan.xapi import PanXapiError
|
||||
from pandevice import base
|
||||
from pandevice import policies
|
||||
from pandevice import panorama
|
||||
import xmltodict
|
||||
import json
|
||||
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
def create_security_test(**kwargs):
|
||||
security_test = 'test security-policy-match'
|
||||
|
||||
# Add the source IP (required)
|
||||
if kwargs['source_ip']:
|
||||
security_test += ' source \"%s\"' % kwargs['source_ip']
|
||||
|
||||
# Add the source user (optional)
|
||||
if kwargs['source_user']:
|
||||
security_test += ' source-user \"%s\"' % kwargs['source_user']
|
||||
|
||||
# Add the destination IP (required)
|
||||
if kwargs['destination_ip']:
|
||||
security_test += ' destination \"%s\"' % kwargs['destination_ip']
|
||||
|
||||
# Add the application (optional)
|
||||
if kwargs['application']:
|
||||
security_test += ' application \"%s\"' % kwargs['application']
|
||||
|
||||
# Add the destination port (required)
|
||||
if kwargs['destination_port']:
|
||||
security_test += ' destination-port \"%s\"' % kwargs['destination_port']
|
||||
|
||||
# Add the IP protocol number (required)
|
||||
if kwargs['protocol']:
|
||||
security_test += ' protocol \"%s\"' % kwargs['protocol']
|
||||
|
||||
# Add the URL category (optional)
|
||||
if kwargs['category']:
|
||||
security_test += ' category \"%s\"' % kwargs['category']
|
||||
|
||||
# Return the resulting string
|
||||
return security_test
|
||||
|
||||
|
||||
def create_nat_test(**kwargs):
|
||||
nat_test = 'test nat-policy-match'
|
||||
|
||||
# Add the source zone (optional)
|
||||
if kwargs['source_zone']:
|
||||
nat_test += ' from \"%s\"' % kwargs['source_zone']
|
||||
|
||||
# Add the source IP (required)
|
||||
if kwargs['source_ip']:
|
||||
nat_test += ' source \"%s\"' % kwargs['source_ip']
|
||||
|
||||
# Add the source user (optional)
|
||||
if kwargs['source_port']:
|
||||
nat_test += ' source-port \"%s\"' % kwargs['source_port']
|
||||
|
||||
# Add inbound interface (optional)
|
||||
if kwargs['to_interface']:
|
||||
nat_test += ' to-interface \"%s\"' % kwargs['to_interface']
|
||||
|
||||
# Add the destination zone (optional)
|
||||
if kwargs['destination_zone']:
|
||||
nat_test += ' to \"%s\"' % kwargs['destination_zone']
|
||||
|
||||
# Add the destination IP (required)
|
||||
if kwargs['destination_ip']:
|
||||
nat_test += ' destination \"%s\"' % kwargs['destination_ip']
|
||||
|
||||
# Add the destination port (optional)
|
||||
if kwargs['destination_port']:
|
||||
nat_test += ' destination-port \"%s\"' % kwargs['destination_port']
|
||||
|
||||
# Add the IP protocol number (required)
|
||||
if kwargs['protocol']:
|
||||
nat_test += ' protocol \"%s\"' % kwargs['protocol']
|
||||
|
||||
# Return the resulting string
|
||||
return nat_test
|
||||
|
||||
|
||||
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),
|
||||
vsys_id=dict(default='vsys1'),
|
||||
rule_type=dict(required=True, choices=['security', 'nat']),
|
||||
source_zone=dict(default=None),
|
||||
source_ip=dict(default=None),
|
||||
source_user=dict(default=None),
|
||||
source_port=dict(default=None, type=int),
|
||||
to_interface=dict(default=None),
|
||||
destination_zone=dict(default=None),
|
||||
category=dict(default=None),
|
||||
application=dict(default=None),
|
||||
protocol=dict(default=None, type=int),
|
||||
destination_ip=dict(default=None),
|
||||
destination_port=dict(default=None, type=int)
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False,
|
||||
required_one_of=[['api_key', 'password']])
|
||||
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']
|
||||
vsys_id = module.params['vsys_id']
|
||||
rule_type = module.params['rule_type']
|
||||
source_zone = module.params['source_zone']
|
||||
source_ip = module.params['source_ip']
|
||||
source_user = module.params['source_user']
|
||||
source_port = module.params['source_port']
|
||||
to_interface = module.params['to_interface']
|
||||
destination_zone = module.params['destination_zone']
|
||||
destination_ip = module.params['destination_ip']
|
||||
destination_port = module.params['destination_port']
|
||||
category = module.params['category']
|
||||
application = module.params['application']
|
||||
protocol = module.params['protocol']
|
||||
|
||||
# Create the device with the appropriate pandevice type
|
||||
device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key)
|
||||
|
||||
# Fail the module if this is a Panorama instance
|
||||
if isinstance(device, panorama.Panorama):
|
||||
module.fail_json(
|
||||
failed=1,
|
||||
msg='Panorama is not supported.'
|
||||
)
|
||||
|
||||
# Create and attach security and NAT rulebases. Then populate them.
|
||||
sec_rule_base = nat_rule_base = policies.Rulebase()
|
||||
device.add(sec_rule_base)
|
||||
device.add(nat_rule_base)
|
||||
policies.SecurityRule.refreshall(sec_rule_base)
|
||||
policies.NatRule.refreshall(nat_rule_base)
|
||||
|
||||
# Which action shall we take on the object?
|
||||
if rule_type == 'security':
|
||||
# Search for the object
|
||||
test_string = create_security_test(
|
||||
source_ip=source_ip,
|
||||
source_user=source_user,
|
||||
destination_ip=destination_ip,
|
||||
destination_port=destination_port,
|
||||
application=application,
|
||||
protocol=protocol,
|
||||
category=category
|
||||
)
|
||||
elif rule_type == 'nat':
|
||||
test_string = create_nat_test(
|
||||
source_zone=source_zone,
|
||||
source_ip=source_ip,
|
||||
source_port=source_port,
|
||||
to_interface=to_interface,
|
||||
destination_zone=destination_zone,
|
||||
destination_ip=destination_ip,
|
||||
destination_port=destination_port,
|
||||
protocol=protocol
|
||||
)
|
||||
|
||||
# Submit the op command with the appropriate test string
|
||||
try:
|
||||
response = device.op(cmd=test_string, vsys=vsys_id)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=exc.message)
|
||||
|
||||
if response.find('result/rules').__len__() == 1:
|
||||
rule_name = response.find('result/rules/entry').text.split(';')[0]
|
||||
elif rule_type == 'nat':
|
||||
module.exit_json(msg='No matching NAT rule.')
|
||||
else:
|
||||
module.fail_json(msg='Rule match failed. Please check playbook syntax.')
|
||||
|
||||
if rule_type == 'security':
|
||||
rule_match = sec_rule_base.find(rule_name, policies.SecurityRule)
|
||||
elif rule_type == 'nat':
|
||||
rule_match = nat_rule_base.find(rule_name, policies.NatRule)
|
||||
|
||||
# Print out the rule
|
||||
module.exit_json(
|
||||
stdout_lines=json.dumps(xmltodict.parse(rule_match.element_str()), indent=2),
|
||||
msg='Rule matched'
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
191
plugins/modules/network/panos/panos_mgtconfig.py
Normal file
191
plugins/modules/network/panos/panos_mgtconfig.py
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_mgtconfig
|
||||
short_description: configure management settings of device
|
||||
description:
|
||||
- Configure management settings of device
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
dns_server_primary:
|
||||
description:
|
||||
- address of primary DNS server
|
||||
dns_server_secondary:
|
||||
description:
|
||||
- address of secondary DNS server
|
||||
panorama_primary:
|
||||
description:
|
||||
- address of primary Panorama server
|
||||
panorama_secondary:
|
||||
description:
|
||||
- address of secondary Panorama server
|
||||
commit:
|
||||
description:
|
||||
- commit if changed
|
||||
type: bool
|
||||
default: 'yes'
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: set dns and panorama
|
||||
panos_mgtconfig:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "admin"
|
||||
dns_server_primary: "1.1.1.1"
|
||||
dns_server_secondary: "1.1.1.2"
|
||||
panorama_primary: "1.1.1.3"
|
||||
panorama_secondary: "1.1.1.4"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
from pan.xapi import PanXapiError
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
_XPATH_DNS_SERVERS = "/config/devices/entry[@name='localhost.localdomain']" +\
|
||||
"/deviceconfig/system/dns-setting/servers"
|
||||
_XPATH_PANORAMA_SERVERS = "/config" +\
|
||||
"/devices/entry[@name='localhost.localdomain']" +\
|
||||
"/deviceconfig/system"
|
||||
|
||||
|
||||
def set_dns_server(xapi, new_dns_server, primary=True):
|
||||
if primary:
|
||||
tag = "primary"
|
||||
else:
|
||||
tag = "secondary"
|
||||
xpath = _XPATH_DNS_SERVERS + "/" + tag
|
||||
|
||||
# check the current element value
|
||||
xapi.get(xpath)
|
||||
val = xapi.element_root.find(".//" + tag)
|
||||
if val is not None:
|
||||
# element exists
|
||||
val = val.text
|
||||
if val == new_dns_server:
|
||||
return False
|
||||
|
||||
element = "<%(tag)s>%(value)s</%(tag)s>" %\
|
||||
dict(tag=tag, value=new_dns_server)
|
||||
xapi.edit(xpath, element)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def set_panorama_server(xapi, new_panorama_server, primary=True):
|
||||
if primary:
|
||||
tag = "panorama-server"
|
||||
else:
|
||||
tag = "panorama-server-2"
|
||||
xpath = _XPATH_PANORAMA_SERVERS + "/" + tag
|
||||
|
||||
# check the current element value
|
||||
xapi.get(xpath)
|
||||
val = xapi.element_root.find(".//" + tag)
|
||||
if val is not None:
|
||||
# element exists
|
||||
val = val.text
|
||||
if val == new_panorama_server:
|
||||
return False
|
||||
|
||||
element = "<%(tag)s>%(value)s</%(tag)s>" %\
|
||||
dict(tag=tag, value=new_panorama_server)
|
||||
xapi.edit(xpath, element)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
password=dict(required=True, no_log=True),
|
||||
username=dict(default='admin'),
|
||||
dns_server_primary=dict(),
|
||||
dns_server_secondary=dict(),
|
||||
panorama_primary=dict(),
|
||||
panorama_secondary=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']
|
||||
dns_server_primary = module.params['dns_server_primary']
|
||||
dns_server_secondary = module.params['dns_server_secondary']
|
||||
panorama_primary = module.params['panorama_primary']
|
||||
panorama_secondary = module.params['panorama_secondary']
|
||||
commit = module.params['commit']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password
|
||||
)
|
||||
|
||||
changed = False
|
||||
try:
|
||||
if dns_server_primary is not None:
|
||||
changed |= set_dns_server(xapi, dns_server_primary, primary=True)
|
||||
if dns_server_secondary is not None:
|
||||
changed |= set_dns_server(xapi, dns_server_secondary, primary=False)
|
||||
if panorama_primary is not None:
|
||||
changed |= set_panorama_server(xapi, panorama_primary, primary=True)
|
||||
if panorama_secondary is not None:
|
||||
changed |= set_panorama_server(xapi, panorama_secondary, primary=False)
|
||||
|
||||
if changed and commit:
|
||||
xapi.commit(cmd="<commit></commit>", sync=True, interval=1)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
module.exit_json(changed=changed, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
471
plugins/modules/network/panos/panos_nat_rule.py
Normal file
471
plugins/modules/network/panos/panos_nat_rule.py
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
# 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: panos_nat_rule
|
||||
short_description: create a policy NAT rule
|
||||
description: >
|
||||
- Create a policy nat rule. Keep in mind that we can either end up configuring source NAT, destination NAT, or
|
||||
both. Instead of splitting it into two we will make a fair attempt to determine which one the user wants.
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer), Robert Hagen (@rnh556)"
|
||||
requirements:
|
||||
- pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/)
|
||||
- pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/)
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
notes:
|
||||
- Checkmode is not supported.
|
||||
- Panorama is supported.
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- IP address (or hostname) of PAN-OS device being configured.
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- Username credentials to use for auth unless I(api_key) is set.
|
||||
default: "admin"
|
||||
password:
|
||||
description:
|
||||
- Password credentials to use for auth unless I(api_key) is set.
|
||||
required: true
|
||||
api_key:
|
||||
description:
|
||||
- API key that can be used instead of I(username)/I(password) credentials.
|
||||
operation:
|
||||
description:
|
||||
- The action to be taken. Supported values are I(add)/I(update)/I(find)/I(delete).
|
||||
required: true
|
||||
choices:
|
||||
- add
|
||||
- update
|
||||
- delete
|
||||
- find
|
||||
devicegroup:
|
||||
description:
|
||||
- If Panorama, the device group to put this rule in.
|
||||
rule_name:
|
||||
description:
|
||||
- name of the SNAT rule
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- The description
|
||||
source_zone:
|
||||
description:
|
||||
- list of source zones
|
||||
required: true
|
||||
destination_zone:
|
||||
description:
|
||||
- destination zone
|
||||
required: true
|
||||
source_ip:
|
||||
description:
|
||||
- list of source addresses
|
||||
default: ["any"]
|
||||
destination_ip:
|
||||
description:
|
||||
- list of destination addresses
|
||||
default: ["any"]
|
||||
service:
|
||||
description:
|
||||
- service
|
||||
default: "any"
|
||||
snat_type:
|
||||
description:
|
||||
- type of source translation
|
||||
choices:
|
||||
- static-ip
|
||||
- dynamic-ip-and-port
|
||||
- dynamic-ip
|
||||
snat_address_type:
|
||||
description:
|
||||
- type of source translation. Supported values are I(translated-address)/I(translated-address).
|
||||
default: 'interface-address'
|
||||
choices:
|
||||
- interface-address
|
||||
- translated-address
|
||||
snat_static_address:
|
||||
description:
|
||||
- Source NAT translated address. Used with Static-IP translation.
|
||||
snat_dynamic_address:
|
||||
description:
|
||||
- Source NAT translated address. Used with Dynamic-IP and Dynamic-IP-and-Port.
|
||||
snat_interface:
|
||||
description:
|
||||
- snat interface
|
||||
snat_interface_address:
|
||||
description:
|
||||
- snat interface address
|
||||
snat_bidirectional:
|
||||
description:
|
||||
- bidirectional flag
|
||||
type: bool
|
||||
default: 'no'
|
||||
dnat_address:
|
||||
description:
|
||||
- dnat translated address
|
||||
dnat_port:
|
||||
description:
|
||||
- dnat translated port
|
||||
tag_name:
|
||||
description:
|
||||
- Tag for the NAT rule.
|
||||
to_interface:
|
||||
description:
|
||||
- Destination interface.
|
||||
default: 'any'
|
||||
commit:
|
||||
description:
|
||||
- Commit configuration if changed.
|
||||
type: bool
|
||||
default: 'yes'
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a source and destination nat rule
|
||||
- name: Create NAT SSH rule for 10.0.1.101
|
||||
panos_nat_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
rule_name: "Web SSH"
|
||||
source_zone: ["external"]
|
||||
destination_zone: "external"
|
||||
source: ["any"]
|
||||
destination: ["10.0.0.100"]
|
||||
service: "service-tcp-221"
|
||||
snat_type: "dynamic-ip-and-port"
|
||||
snat_interface: "ethernet1/2"
|
||||
dnat_address: "10.0.1.101"
|
||||
dnat_port: "22"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
# import pydevd
|
||||
# pydevd.settrace('localhost', port=60374, stdoutToServer=True, stderrToServer=True)
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
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
|
||||
from pandevice import policies
|
||||
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 get_rulebase(device, devicegroup):
|
||||
# Build the rulebase
|
||||
if isinstance(device, pandevice.firewall.Firewall):
|
||||
rulebase = pandevice.policies.Rulebase()
|
||||
device.add(rulebase)
|
||||
elif isinstance(device, pandevice.panorama.Panorama):
|
||||
dg = panorama.DeviceGroup(devicegroup)
|
||||
device.add(dg)
|
||||
rulebase = policies.PreRulebase()
|
||||
dg.add(rulebase)
|
||||
else:
|
||||
return False
|
||||
policies.NatRule.refreshall(rulebase)
|
||||
return rulebase
|
||||
|
||||
|
||||
def find_rule(rulebase, rule_name):
|
||||
# Search for the rule name
|
||||
rule = rulebase.find(rule_name)
|
||||
if rule:
|
||||
return rule
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def create_nat_rule(**kwargs):
|
||||
nat_rule = policies.NatRule(
|
||||
name=kwargs['rule_name'],
|
||||
description=kwargs['description'],
|
||||
fromzone=kwargs['source_zone'],
|
||||
source=kwargs['source_ip'],
|
||||
tozone=kwargs['destination_zone'],
|
||||
destination=kwargs['destination_ip'],
|
||||
service=kwargs['service'],
|
||||
to_interface=kwargs['to_interface'],
|
||||
nat_type=kwargs['nat_type']
|
||||
)
|
||||
|
||||
# Source translation: Static IP
|
||||
if kwargs['snat_type'] in ['static-ip'] and kwargs['snat_static_address']:
|
||||
nat_rule.source_translation_type = kwargs['snat_type']
|
||||
nat_rule.source_translation_static_translated_address = kwargs['snat_static_address']
|
||||
# Bi-directional flag set?
|
||||
if kwargs['snat_bidirectional']:
|
||||
nat_rule.source_translation_static_bi_directional = kwargs['snat_bidirectional']
|
||||
|
||||
# Source translation: Dynamic IP and port
|
||||
elif kwargs['snat_type'] in ['dynamic-ip-and-port']:
|
||||
nat_rule.source_translation_type = kwargs['snat_type']
|
||||
nat_rule.source_translation_address_type = kwargs['snat_address_type']
|
||||
# Interface address?
|
||||
if kwargs['snat_interface']:
|
||||
nat_rule.source_translation_interface = kwargs['snat_interface']
|
||||
# Interface IP?
|
||||
if kwargs['snat_interface_address']:
|
||||
nat_rule.source_translation_ip_address = kwargs['snat_interface_address']
|
||||
else:
|
||||
nat_rule.source_translation_translated_addresses = kwargs['snat_dynamic_address']
|
||||
|
||||
# Source translation: Dynamic IP
|
||||
elif kwargs['snat_type'] in ['dynamic-ip']:
|
||||
if kwargs['snat_dynamic_address']:
|
||||
nat_rule.source_translation_type = kwargs['snat_type']
|
||||
nat_rule.source_translation_translated_addresses = kwargs['snat_dynamic_address']
|
||||
else:
|
||||
return False
|
||||
|
||||
# Destination translation
|
||||
if kwargs['dnat_address']:
|
||||
nat_rule.destination_translated_address = kwargs['dnat_address']
|
||||
if kwargs['dnat_port']:
|
||||
nat_rule.destination_translated_port = kwargs['dnat_port']
|
||||
|
||||
# Any tags?
|
||||
if 'tag_name' in kwargs:
|
||||
nat_rule.tag = kwargs['tag_name']
|
||||
|
||||
return nat_rule
|
||||
|
||||
|
||||
def add_rule(rulebase, nat_rule):
|
||||
if rulebase:
|
||||
rulebase.add(nat_rule)
|
||||
nat_rule.create()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def update_rule(rulebase, nat_rule):
|
||||
if rulebase:
|
||||
rulebase.add(nat_rule)
|
||||
nat_rule.apply()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
username=dict(default='admin'),
|
||||
password=dict(required=True, no_log=True),
|
||||
api_key=dict(no_log=True),
|
||||
operation=dict(required=True, choices=['add', 'update', 'delete', 'find']),
|
||||
rule_name=dict(required=True),
|
||||
description=dict(),
|
||||
tag_name=dict(),
|
||||
source_zone=dict(type='list'),
|
||||
source_ip=dict(type='list', default=['any']),
|
||||
destination_zone=dict(),
|
||||
destination_ip=dict(type='list', default=['any']),
|
||||
service=dict(default='any'),
|
||||
to_interface=dict(default='any'),
|
||||
snat_type=dict(choices=['static-ip', 'dynamic-ip-and-port', 'dynamic-ip']),
|
||||
snat_address_type=dict(choices=['interface-address', 'translated-address'], default='interface-address'),
|
||||
snat_static_address=dict(),
|
||||
snat_dynamic_address=dict(type='list'),
|
||||
snat_interface=dict(),
|
||||
snat_interface_address=dict(),
|
||||
snat_bidirectional=dict(type='bool', default=False),
|
||||
dnat_address=dict(),
|
||||
dnat_port=dict(),
|
||||
devicegroup=dict(),
|
||||
commit=dict(type='bool', default=True)
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False,
|
||||
required_one_of=[['api_key', 'password']])
|
||||
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']
|
||||
rule_name = module.params['rule_name']
|
||||
description = module.params['description']
|
||||
tag_name = module.params['tag_name']
|
||||
source_zone = module.params['source_zone']
|
||||
source_ip = module.params['source_ip']
|
||||
destination_zone = module.params['destination_zone']
|
||||
destination_ip = module.params['destination_ip']
|
||||
service = module.params['service']
|
||||
to_interface = module.params['to_interface']
|
||||
nat_type = 'ipv4'
|
||||
snat_type = module.params['snat_type']
|
||||
snat_address_type = module.params['snat_address_type']
|
||||
snat_static_address = module.params['snat_static_address']
|
||||
snat_dynamic_address = module.params['snat_dynamic_address']
|
||||
snat_interface = module.params['snat_interface']
|
||||
snat_interface_address = module.params['snat_interface_address']
|
||||
snat_bidirectional = module.params['snat_bidirectional']
|
||||
dnat_address = module.params['dnat_address']
|
||||
dnat_port = module.params['dnat_port']
|
||||
devicegroup = module.params['devicegroup']
|
||||
|
||||
commit = module.params['commit']
|
||||
|
||||
# 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)
|
||||
|
||||
# Get the rulebase
|
||||
rulebase = get_rulebase(device, dev_group)
|
||||
|
||||
# Which action shall we take on the object?
|
||||
if operation == "find":
|
||||
# Search for the rule
|
||||
match = find_rule(rulebase, rule_name)
|
||||
# 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='Rule matched'
|
||||
)
|
||||
else:
|
||||
module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name)
|
||||
elif operation == "delete":
|
||||
# Search for the object
|
||||
match = find_rule(rulebase, rule_name)
|
||||
# If found, delete it
|
||||
if match:
|
||||
try:
|
||||
match.delete()
|
||||
if commit:
|
||||
device.commit(sync=True)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
module.exit_json(changed=True, msg='Rule \'%s\' successfully deleted.' % rule_name)
|
||||
else:
|
||||
module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name)
|
||||
elif operation == "add":
|
||||
# Look for required parameters
|
||||
if source_zone and destination_zone and nat_type:
|
||||
pass
|
||||
else:
|
||||
module.fail_json(msg='Missing parameter. Required: source_zone, destination_zone, nat_type')
|
||||
# Search for the rule. Fail if found.
|
||||
match = find_rule(rulebase, rule_name)
|
||||
if match:
|
||||
module.fail_json(msg='Rule \'%s\' already exists. Use operation: \'update\' to change it.' % rule_name)
|
||||
else:
|
||||
try:
|
||||
new_rule = create_nat_rule(
|
||||
rule_name=rule_name,
|
||||
description=description,
|
||||
tag_name=tag_name,
|
||||
source_zone=source_zone,
|
||||
destination_zone=destination_zone,
|
||||
source_ip=source_ip,
|
||||
destination_ip=destination_ip,
|
||||
service=service,
|
||||
to_interface=to_interface,
|
||||
nat_type=nat_type,
|
||||
snat_type=snat_type,
|
||||
snat_address_type=snat_address_type,
|
||||
snat_static_address=snat_static_address,
|
||||
snat_dynamic_address=snat_dynamic_address,
|
||||
snat_interface=snat_interface,
|
||||
snat_interface_address=snat_interface_address,
|
||||
snat_bidirectional=snat_bidirectional,
|
||||
dnat_address=dnat_address,
|
||||
dnat_port=dnat_port
|
||||
)
|
||||
changed = add_rule(rulebase, new_rule)
|
||||
if changed and commit:
|
||||
device.commit(sync=True)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
module.exit_json(changed=changed, msg='Rule \'%s\' successfully added.' % rule_name)
|
||||
elif operation == 'update':
|
||||
# Search for the rule. Update if found.
|
||||
match = find_rule(rulebase, rule_name)
|
||||
if match:
|
||||
try:
|
||||
new_rule = create_nat_rule(
|
||||
rule_name=rule_name,
|
||||
description=description,
|
||||
tag_name=tag_name,
|
||||
source_zone=source_zone,
|
||||
destination_zone=destination_zone,
|
||||
source_ip=source_ip,
|
||||
destination_ip=destination_ip,
|
||||
service=service,
|
||||
to_interface=to_interface,
|
||||
nat_type=nat_type,
|
||||
snat_type=snat_type,
|
||||
snat_address_type=snat_address_type,
|
||||
snat_static_address=snat_static_address,
|
||||
snat_dynamic_address=snat_dynamic_address,
|
||||
snat_interface=snat_interface,
|
||||
snat_interface_address=snat_interface_address,
|
||||
snat_bidirectional=snat_bidirectional,
|
||||
dnat_address=dnat_address,
|
||||
dnat_port=dnat_port
|
||||
)
|
||||
changed = update_rule(rulebase, new_rule)
|
||||
if changed and commit:
|
||||
device.commit(sync=True)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
module.exit_json(changed=changed, msg='Rule \'%s\' successfully updated.' % rule_name)
|
||||
else:
|
||||
module.fail_json(msg='Rule \'%s\' does not exist. Use operation: \'add\' to add it.' % rule_name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
490
plugins/modules/network/panos/panos_object.py
Normal file
490
plugins/modules/network/panos/panos_object.py
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
# limitations under the License.
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
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)"
|
||||
requirements:
|
||||
- pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/)
|
||||
- pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/)
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
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.
|
||||
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
|
||||
choices:
|
||||
- add
|
||||
- update
|
||||
- delete
|
||||
- find
|
||||
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).
|
||||
default: 'ip-netmask'
|
||||
choices:
|
||||
- ip-netmask
|
||||
- ip-range
|
||||
- fqdn
|
||||
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).
|
||||
choices:
|
||||
- tcp
|
||||
- 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).
|
||||
choices:
|
||||
- red
|
||||
- green
|
||||
- blue
|
||||
- yellow
|
||||
- copper
|
||||
- orange
|
||||
- purple
|
||||
- gray
|
||||
- light green
|
||||
- cyan
|
||||
- light gray
|
||||
- blue gray
|
||||
- lime
|
||||
- black
|
||||
- gold
|
||||
- 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.
|
||||
'''
|
||||
|
||||
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._text import to_native
|
||||
|
||||
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 as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
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 as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
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 as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
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()
|
||||
158
plugins/modules/network/panos/panos_op.py
Normal file
158
plugins/modules/network/panos/panos_op.py
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_op
|
||||
short_description: execute arbitrary OP commands on PANW devices (e.g. show interface all)
|
||||
description: This module will allow user to pass and execute any supported OP command on the PANW device.
|
||||
author: "Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/)
|
||||
- pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/)
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
notes:
|
||||
- Checkmode is NOT supported.
|
||||
- Panorama is NOT 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.
|
||||
cmd:
|
||||
description:
|
||||
- The OP command to be performed.
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: show list of all interfaces
|
||||
panos_op:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
cmd: 'show interfaces all'
|
||||
|
||||
- name: show system info
|
||||
panos_op:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
cmd: 'show system info'
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
stdout:
|
||||
description: output of the given OP command as JSON formatted string
|
||||
returned: success
|
||||
type: str
|
||||
sample: "{system: {app-release-date: 2017/05/01 15:09:12}}"
|
||||
|
||||
stdout_xml:
|
||||
description: output of the given OP command as JSON formatted string
|
||||
returned: success
|
||||
type: str
|
||||
sample: "<response status=success><result><system><hostname>fw2</hostname>"
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
from pan.xapi import PanXapiError
|
||||
import pandevice
|
||||
from pandevice import base
|
||||
from pandevice import firewall
|
||||
from pandevice import panorama
|
||||
import xmltodict
|
||||
import json
|
||||
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
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),
|
||||
cmd=dict(required=True)
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False,
|
||||
required_one_of=[['api_key', 'password']])
|
||||
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']
|
||||
cmd = module.params['cmd']
|
||||
|
||||
# Create the device with the appropriate pandevice type
|
||||
device = base.PanDevice.create_from_device(ip_address, username, password, api_key=api_key)
|
||||
|
||||
changed = False
|
||||
try:
|
||||
xml_output = device.op(cmd, xml=True)
|
||||
changed = True
|
||||
except PanXapiError as exc:
|
||||
if 'non NULL value' in exc.message:
|
||||
# rewrap and call again
|
||||
cmd_array = cmd.split()
|
||||
cmd_array_len = len(cmd_array)
|
||||
cmd_array[cmd_array_len - 1] = '\"' + cmd_array[cmd_array_len - 1] + '\"'
|
||||
cmd2 = ' '.join(cmd_array)
|
||||
try:
|
||||
xml_output = device.op(cmd2, xml=True)
|
||||
changed = True
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=exc.message)
|
||||
|
||||
obj_dict = xmltodict.parse(xml_output)
|
||||
json_output = json.dumps(obj_dict)
|
||||
|
||||
module.exit_json(changed=changed, msg="Done", stdout=json_output, stdout_xml=xml_output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
203
plugins/modules/network/panos/panos_pg.py
Normal file
203
plugins/modules/network/panos/panos_pg.py
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_pg
|
||||
short_description: create a security profiles group
|
||||
description:
|
||||
- Create a security profile group
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
pg_name:
|
||||
description:
|
||||
- name of the security profile group
|
||||
required: true
|
||||
data_filtering:
|
||||
description:
|
||||
- name of the data filtering profile
|
||||
file_blocking:
|
||||
description:
|
||||
- name of the file blocking profile
|
||||
spyware:
|
||||
description:
|
||||
- name of the spyware profile
|
||||
url_filtering:
|
||||
description:
|
||||
- name of the url filtering profile
|
||||
virus:
|
||||
description:
|
||||
- name of the anti-virus profile
|
||||
vulnerability:
|
||||
description:
|
||||
- name of the vulnerability profile
|
||||
wildfire:
|
||||
description:
|
||||
- name of the wildfire analysis profile
|
||||
commit:
|
||||
description:
|
||||
- commit if changed
|
||||
type: bool
|
||||
default: 'yes'
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: setup security profile group
|
||||
panos_pg:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "admin"
|
||||
username: "admin"
|
||||
pg_name: "pg-default"
|
||||
virus: "default"
|
||||
spyware: "default"
|
||||
vulnerability: "default"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
from pan.xapi import PanXapiError
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
_PG_XPATH = "/config/devices/entry[@name='localhost.localdomain']" +\
|
||||
"/vsys/entry[@name='vsys1']" +\
|
||||
"/profile-group/entry[@name='%s']"
|
||||
|
||||
|
||||
def pg_exists(xapi, pg_name):
|
||||
xapi.get(_PG_XPATH % pg_name)
|
||||
e = xapi.element_root.find('.//entry')
|
||||
if e is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def add_pg(xapi, pg_name, data_filtering, file_blocking, spyware,
|
||||
url_filtering, virus, vulnerability, wildfire):
|
||||
if pg_exists(xapi, pg_name):
|
||||
return False
|
||||
|
||||
exml = []
|
||||
|
||||
if data_filtering is not None:
|
||||
exml.append('<data-filtering><member>%s</member></data-filtering>' %
|
||||
data_filtering)
|
||||
if file_blocking is not None:
|
||||
exml.append('<file-blocking><member>%s</member></file-blocking>' %
|
||||
file_blocking)
|
||||
if spyware is not None:
|
||||
exml.append('<spyware><member>%s</member></spyware>' %
|
||||
spyware)
|
||||
if url_filtering is not None:
|
||||
exml.append('<url-filtering><member>%s</member></url-filtering>' %
|
||||
url_filtering)
|
||||
if virus is not None:
|
||||
exml.append('<virus><member>%s</member></virus>' %
|
||||
virus)
|
||||
if vulnerability is not None:
|
||||
exml.append('<vulnerability><member>%s</member></vulnerability>' %
|
||||
vulnerability)
|
||||
if wildfire is not None:
|
||||
exml.append('<wildfire-analysis><member>%s</member></wildfire-analysis>' %
|
||||
wildfire)
|
||||
|
||||
exml = ''.join(exml)
|
||||
xapi.set(xpath=_PG_XPATH % pg_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'),
|
||||
pg_name=dict(required=True),
|
||||
data_filtering=dict(),
|
||||
file_blocking=dict(),
|
||||
spyware=dict(),
|
||||
url_filtering=dict(),
|
||||
virus=dict(),
|
||||
vulnerability=dict(),
|
||||
wildfire=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']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password
|
||||
)
|
||||
|
||||
pg_name = module.params['pg_name']
|
||||
data_filtering = module.params['data_filtering']
|
||||
file_blocking = module.params['file_blocking']
|
||||
spyware = module.params['spyware']
|
||||
url_filtering = module.params['url_filtering']
|
||||
virus = module.params['virus']
|
||||
vulnerability = module.params['vulnerability']
|
||||
wildfire = module.params['wildfire']
|
||||
commit = module.params['commit']
|
||||
|
||||
try:
|
||||
changed = add_pg(xapi, pg_name, data_filtering, file_blocking,
|
||||
spyware, url_filtering, virus, vulnerability, wildfire)
|
||||
|
||||
if changed and commit:
|
||||
xapi.commit(cmd="<commit></commit>", sync=True, interval=1)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
module.exit_json(changed=changed, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
495
plugins/modules/network/panos/panos_query_rules.py
Normal file
495
plugins/modules/network/panos/panos_query_rules.py
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
# limitations under the License.
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_query_rules
|
||||
short_description: PANOS module that allows search for security rules in PANW NGFW devices.
|
||||
description: >
|
||||
- Security policies allow you to enforce rules and take action, and can be as general or specific as needed. The
|
||||
policy rules are compared against the incoming traffic in sequence, and because the first rule that matches the
|
||||
traffic is applied, the more specific rules must precede the more general ones.
|
||||
author: "Bob Hagen (@rnh556)"
|
||||
requirements:
|
||||
- pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/)
|
||||
- pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/)
|
||||
- xmltodict can be obtains from PyPI U(https://pypi.org/project/xmltodict/)
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
notes:
|
||||
- Checkmode is not supported.
|
||||
- Panorama is supported.
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- IP address (or hostname) of PAN-OS firewall or Panorama management console being queried.
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- Username credentials to use for authentication.
|
||||
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.
|
||||
application:
|
||||
description:
|
||||
- Name of the application or application group to be queried.
|
||||
source_zone:
|
||||
description:
|
||||
- Name of the source security zone to be queried.
|
||||
source_ip:
|
||||
description:
|
||||
- The source IP address to be queried.
|
||||
source_port:
|
||||
description:
|
||||
- The source port to be queried.
|
||||
destination_zone:
|
||||
description:
|
||||
- Name of the destination security zone to be queried.
|
||||
destination_ip:
|
||||
description:
|
||||
- The destination IP address to be queried.
|
||||
destination_port:
|
||||
description:
|
||||
- The destination port to be queried.
|
||||
protocol:
|
||||
description:
|
||||
- The protocol used to be queried. Must be either I(tcp) or I(udp).
|
||||
choices:
|
||||
- tcp
|
||||
- udp
|
||||
tag_name:
|
||||
description:
|
||||
- Name of the rule tag to be queried.
|
||||
devicegroup:
|
||||
description:
|
||||
- The Panorama device group in which to conduct the query.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: search for rules with tcp/3306
|
||||
panos_query_rules:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
source_zone: 'DevNet'
|
||||
destination_zone: 'DevVPC'
|
||||
destination_port: '3306'
|
||||
protocol: 'tcp'
|
||||
|
||||
- name: search devicegroup for inbound rules to dmz host
|
||||
panos_query_rules:
|
||||
ip_address: '{{ ip_address }}'
|
||||
api_key: '{{ api_key }}'
|
||||
destination_zone: 'DMZ'
|
||||
destination_ip: '10.100.42.18'
|
||||
address: 'DeviceGroupA'
|
||||
|
||||
- name: search for rules containing a specified rule tag
|
||||
panos_query_rules:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
tag_name: 'ProjectX'
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
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
|
||||
from pandevice import policies
|
||||
import ipaddress
|
||||
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 get_rulebase(device, devicegroup):
|
||||
# Build the rulebase
|
||||
if isinstance(device, firewall.Firewall):
|
||||
rulebase = policies.Rulebase()
|
||||
device.add(rulebase)
|
||||
elif isinstance(device, panorama.Panorama):
|
||||
dg = panorama.DeviceGroup(devicegroup)
|
||||
device.add(dg)
|
||||
rulebase = policies.PreRulebase()
|
||||
dg.add(rulebase)
|
||||
else:
|
||||
return False
|
||||
policies.SecurityRule.refreshall(rulebase)
|
||||
return rulebase
|
||||
|
||||
|
||||
def get_object(device, dev_group, obj_name):
|
||||
# Search global address objects
|
||||
match = device.find(obj_name, objects.AddressObject)
|
||||
if match:
|
||||
return match
|
||||
|
||||
# Search global address groups
|
||||
match = device.find(obj_name, objects.AddressGroup)
|
||||
if match:
|
||||
return match
|
||||
|
||||
# Search Panorama device group
|
||||
if isinstance(device, pandevice.panorama.Panorama):
|
||||
# Search device group address objects
|
||||
match = dev_group.find(obj_name, objects.AddressObject)
|
||||
if match:
|
||||
return match
|
||||
|
||||
# Search device group address groups
|
||||
match = dev_group.find(obj_name, objects.AddressGroup)
|
||||
if match:
|
||||
return match
|
||||
return False
|
||||
|
||||
|
||||
def addr_in_obj(addr, obj):
|
||||
ip = ipaddress.ip_address(addr)
|
||||
# Process address objects
|
||||
if isinstance(obj, objects.AddressObject):
|
||||
if obj.type == 'ip-netmask':
|
||||
net = ipaddress.ip_network(obj.value)
|
||||
if ip in net:
|
||||
return True
|
||||
if obj.type == 'ip-range':
|
||||
ip_range = obj.value.split('-')
|
||||
lower = ipaddress.ip_address(ip_range[0])
|
||||
upper = ipaddress.ip_address(ip_range[1])
|
||||
if lower < ip < upper:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_services(device, dev_group, svc_list, obj_list):
|
||||
for svc in svc_list:
|
||||
|
||||
# Search global address objects
|
||||
global_obj_match = device.find(svc, objects.ServiceObject)
|
||||
if global_obj_match:
|
||||
obj_list.append(global_obj_match)
|
||||
|
||||
# Search global address groups
|
||||
global_grp_match = device.find(svc, objects.ServiceGroup)
|
||||
if global_grp_match:
|
||||
get_services(device, dev_group, global_grp_match.value, obj_list)
|
||||
|
||||
# Search Panorama device group
|
||||
if isinstance(device, pandevice.panorama.Panorama):
|
||||
|
||||
# Search device group address objects
|
||||
dg_obj_match = dev_group.find(svc, objects.ServiceObject)
|
||||
if dg_obj_match:
|
||||
obj_list.append(dg_obj_match)
|
||||
|
||||
# Search device group address groups
|
||||
dg_grp_match = dev_group.find(svc, objects.ServiceGroup)
|
||||
if dg_grp_match:
|
||||
get_services(device, dev_group, dg_grp_match.value, obj_list)
|
||||
|
||||
return obj_list
|
||||
|
||||
|
||||
def port_in_svc(orientation, port, protocol, obj):
|
||||
# Process address objects
|
||||
if orientation == 'source':
|
||||
for x in obj.source_port.split(','):
|
||||
if '-' in x:
|
||||
port_range = x.split('-')
|
||||
lower = int(port_range[0])
|
||||
upper = int(port_range[1])
|
||||
if (lower <= int(port) <= upper) and (obj.protocol == protocol):
|
||||
return True
|
||||
else:
|
||||
if port == x and obj.protocol == protocol:
|
||||
return True
|
||||
elif orientation == 'destination':
|
||||
for x in obj.destination_port.split(','):
|
||||
if '-' in x:
|
||||
port_range = x.split('-')
|
||||
lower = int(port_range[0])
|
||||
upper = int(port_range[1])
|
||||
if (lower <= int(port) <= upper) and (obj.protocol == protocol):
|
||||
return True
|
||||
else:
|
||||
if port == x and obj.protocol == protocol:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_tag(device, dev_group, tag_name):
|
||||
# Search global address objects
|
||||
match = device.find(tag_name, objects.Tag)
|
||||
if match:
|
||||
return match
|
||||
# Search Panorama device group
|
||||
if isinstance(device, panorama.Panorama):
|
||||
# Search device group address objects
|
||||
match = dev_group.find(tag_name, objects.Tag)
|
||||
if match:
|
||||
return match
|
||||
return False
|
||||
|
||||
|
||||
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),
|
||||
application=dict(default=None),
|
||||
source_zone=dict(default=None),
|
||||
destination_zone=dict(default=None),
|
||||
source_ip=dict(default=None),
|
||||
destination_ip=dict(default=None),
|
||||
source_port=dict(default=None),
|
||||
destination_port=dict(default=None),
|
||||
protocol=dict(default=None, choices=['tcp', 'udp']),
|
||||
tag_name=dict(default=None),
|
||||
devicegroup=dict(default=None)
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False,
|
||||
required_one_of=[['api_key', 'password']]
|
||||
)
|
||||
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']
|
||||
application = module.params['application']
|
||||
source_zone = module.params['source_zone']
|
||||
source_ip = module.params['source_ip']
|
||||
source_port = module.params['source_port']
|
||||
destination_zone = module.params['destination_zone']
|
||||
destination_ip = module.params['destination_ip']
|
||||
destination_port = module.params['destination_port']
|
||||
protocol = module.params['protocol']
|
||||
tag_name = module.params['tag_name']
|
||||
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)
|
||||
|
||||
# Grab the global objects
|
||||
objects.AddressObject.refreshall(device)
|
||||
objects.AddressGroup.refreshall(device)
|
||||
objects.ServiceObject.refreshall(device)
|
||||
objects.ServiceGroup.refreshall(device)
|
||||
objects.Tag.refreshall(device)
|
||||
|
||||
# If Panorama, validate the devicegroup and grab the devicegroup objects
|
||||
dev_group = None
|
||||
if devicegroup and isinstance(device, panorama.Panorama):
|
||||
dev_group = get_devicegroup(device, devicegroup)
|
||||
if dev_group:
|
||||
device.add(dev_group)
|
||||
objects.AddressObject.refreshall(dev_group)
|
||||
objects.AddressGroup.refreshall(dev_group)
|
||||
objects.ServiceObject.refreshall(dev_group)
|
||||
objects.ServiceGroup.refreshall(dev_group)
|
||||
objects.Tag.refreshall(dev_group)
|
||||
else:
|
||||
module.fail_json(
|
||||
failed=1,
|
||||
msg='\'%s\' device group not found in Panorama. Is the name correct?' % devicegroup
|
||||
)
|
||||
|
||||
# Build the rulebase and produce list
|
||||
rulebase = get_rulebase(device, dev_group)
|
||||
rulelist = rulebase.children
|
||||
hitbase = policies.Rulebase()
|
||||
loose_match = True
|
||||
|
||||
# Process each rule
|
||||
for rule in rulelist:
|
||||
hitlist = []
|
||||
|
||||
if source_zone:
|
||||
source_zone_match = False
|
||||
if loose_match and 'any' in rule.fromzone:
|
||||
source_zone_match = True
|
||||
else:
|
||||
for object_string in rule.fromzone:
|
||||
if object_string == source_zone:
|
||||
source_zone_match = True
|
||||
hitlist.append(source_zone_match)
|
||||
|
||||
if destination_zone:
|
||||
destination_zone_match = False
|
||||
if loose_match and 'any' in rule.tozone:
|
||||
destination_zone_match = True
|
||||
else:
|
||||
for object_string in rule.tozone:
|
||||
if object_string == destination_zone:
|
||||
destination_zone_match = True
|
||||
hitlist.append(destination_zone_match)
|
||||
|
||||
if source_ip:
|
||||
source_ip_match = False
|
||||
if loose_match and 'any' in rule.source:
|
||||
source_ip_match = True
|
||||
else:
|
||||
for object_string in rule.source:
|
||||
# Get a valid AddressObject or AddressGroup
|
||||
obj = get_object(device, dev_group, object_string)
|
||||
# Otherwise the object_string is not an object and should be handled differently
|
||||
if obj is False:
|
||||
if '-' in object_string:
|
||||
obj = ipaddress.ip_address(source_ip)
|
||||
source_range = object_string.split('-')
|
||||
source_lower = ipaddress.ip_address(source_range[0])
|
||||
source_upper = ipaddress.ip_address(source_range[1])
|
||||
if source_lower <= obj <= source_upper:
|
||||
source_ip_match = True
|
||||
else:
|
||||
if source_ip == object_string:
|
||||
source_ip_match = True
|
||||
if isinstance(obj, objects.AddressObject) and addr_in_obj(source_ip, obj):
|
||||
source_ip_match = True
|
||||
elif isinstance(obj, objects.AddressGroup) and obj.static_value:
|
||||
for member_string in obj.static_value:
|
||||
member = get_object(device, dev_group, member_string)
|
||||
if addr_in_obj(source_ip, member):
|
||||
source_ip_match = True
|
||||
hitlist.append(source_ip_match)
|
||||
|
||||
if destination_ip:
|
||||
destination_ip_match = False
|
||||
if loose_match and 'any' in rule.destination:
|
||||
destination_ip_match = True
|
||||
else:
|
||||
for object_string in rule.destination:
|
||||
# Get a valid AddressObject or AddressGroup
|
||||
obj = get_object(device, dev_group, object_string)
|
||||
# Otherwise the object_string is not an object and should be handled differently
|
||||
if obj is False:
|
||||
if '-' in object_string:
|
||||
obj = ipaddress.ip_address(destination_ip)
|
||||
destination_range = object_string.split('-')
|
||||
destination_lower = ipaddress.ip_address(destination_range[0])
|
||||
destination_upper = ipaddress.ip_address(destination_range[1])
|
||||
if destination_lower <= obj <= destination_upper:
|
||||
destination_ip_match = True
|
||||
else:
|
||||
if destination_ip == object_string:
|
||||
destination_ip_match = True
|
||||
if isinstance(obj, objects.AddressObject) and addr_in_obj(destination_ip, obj):
|
||||
destination_ip_match = True
|
||||
elif isinstance(obj, objects.AddressGroup) and obj.static_value:
|
||||
for member_string in obj.static_value:
|
||||
member = get_object(device, dev_group, member_string)
|
||||
if addr_in_obj(destination_ip, member):
|
||||
destination_ip_match = True
|
||||
hitlist.append(destination_ip_match)
|
||||
|
||||
if source_port:
|
||||
source_port_match = False
|
||||
orientation = 'source'
|
||||
if loose_match and (rule.service[0] == 'any'):
|
||||
source_port_match = True
|
||||
elif rule.service[0] == 'application-default':
|
||||
source_port_match = False # Fix this once apps are supported
|
||||
else:
|
||||
service_list = []
|
||||
service_list = get_services(device, dev_group, rule.service, service_list)
|
||||
for obj in service_list:
|
||||
if port_in_svc(orientation, source_port, protocol, obj):
|
||||
source_port_match = True
|
||||
break
|
||||
hitlist.append(source_port_match)
|
||||
|
||||
if destination_port:
|
||||
destination_port_match = False
|
||||
orientation = 'destination'
|
||||
if loose_match and (rule.service[0] == 'any'):
|
||||
destination_port_match = True
|
||||
elif rule.service[0] == 'application-default':
|
||||
destination_port_match = False # Fix this once apps are supported
|
||||
else:
|
||||
service_list = []
|
||||
service_list = get_services(device, dev_group, rule.service, service_list)
|
||||
for obj in service_list:
|
||||
if port_in_svc(orientation, destination_port, protocol, obj):
|
||||
destination_port_match = True
|
||||
break
|
||||
hitlist.append(destination_port_match)
|
||||
|
||||
if tag_name:
|
||||
tag_match = False
|
||||
if rule.tag:
|
||||
for object_string in rule.tag:
|
||||
obj = get_tag(device, dev_group, object_string)
|
||||
if obj and (obj.name == tag_name):
|
||||
tag_match = True
|
||||
hitlist.append(tag_match)
|
||||
|
||||
# Add to hit rulebase
|
||||
if False not in hitlist:
|
||||
hitbase.add(rule)
|
||||
|
||||
# Dump the hit rulebase
|
||||
if hitbase.children:
|
||||
output_string = xmltodict.parse(hitbase.element_str())
|
||||
module.exit_json(
|
||||
stdout_lines=json.dumps(output_string, indent=2),
|
||||
msg='%s of %s rules matched' % (hitbase.children.__len__(), rulebase.children.__len__())
|
||||
)
|
||||
else:
|
||||
module.fail_json(msg='No matching rules found.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
110
plugins/modules/network/panos/panos_restart.py
Normal file
110
plugins/modules/network/panos/panos_restart.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_restart
|
||||
short_description: restart a device
|
||||
description:
|
||||
- Restart a device
|
||||
author: "Luigi Mori (@jtschichold), Ivan Bojer (@ivanbojer)"
|
||||
requirements:
|
||||
- pan-python
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- panos_restart:
|
||||
ip_address: "192.168.1.1"
|
||||
username: "admin"
|
||||
password: "admin"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
status:
|
||||
description: success status
|
||||
returned: success
|
||||
type: str
|
||||
sample: "okey dokey"
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(),
|
||||
password=dict(no_log=True),
|
||||
username=dict(default='admin')
|
||||
)
|
||||
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"]
|
||||
if not ip_address:
|
||||
module.fail_json(msg="ip_address should be specified")
|
||||
password = module.params["password"]
|
||||
if not password:
|
||||
module.fail_json(msg="password is required")
|
||||
username = module.params['username']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password
|
||||
)
|
||||
|
||||
try:
|
||||
xapi.op(cmd="<request><restart><system></system></restart></request>")
|
||||
except Exception as e:
|
||||
if 'succeeded' in to_native(e):
|
||||
module.exit_json(changed=True, msg=to_native(e))
|
||||
else:
|
||||
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
|
||||
|
||||
module.exit_json(changed=True, msg="okey dokey")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
268
plugins/modules/network/panos/panos_sag.py
Normal file
268
plugins/modules/network/panos/panos_sag.py
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_sag
|
||||
short_description: Create a static address group.
|
||||
description:
|
||||
- Create a static address group object in the firewall used for policy rules.
|
||||
author: "Vinay Venkataraghavan (@vinayvenkat)"
|
||||
requirements:
|
||||
- pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/)
|
||||
- pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/)
|
||||
- xmltodict can be obtained from PyPI U(https://pypi.org/project/xmltodict/)
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
options:
|
||||
api_key:
|
||||
description:
|
||||
- API key that can be used instead of I(username)/I(password) credentials.
|
||||
sag_name:
|
||||
description:
|
||||
- name of the dynamic address group
|
||||
required: true
|
||||
sag_match_filter:
|
||||
description:
|
||||
- Static filter user by the address group
|
||||
type: list
|
||||
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.
|
||||
description:
|
||||
description:
|
||||
- The purpose / objective of the static Address Group
|
||||
tags:
|
||||
description:
|
||||
- Tags to be associated with the address group
|
||||
commit:
|
||||
description:
|
||||
- commit if changed
|
||||
type: bool
|
||||
default: 'yes'
|
||||
operation:
|
||||
description:
|
||||
- The operation to perform Supported values are I(add)/I(list)/I(delete).
|
||||
required: true
|
||||
choices:
|
||||
- add
|
||||
- list
|
||||
- delete
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: sag
|
||||
panos_sag:
|
||||
ip_address: "192.168.1.1"
|
||||
password: "admin"
|
||||
sag_name: "sag-1"
|
||||
static_value: ['test-addresses', ]
|
||||
description: "A description for the static address group"
|
||||
tags: ["tags to be associated with the group", ]
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
try:
|
||||
from pandevice import base
|
||||
from pandevice import firewall
|
||||
from pandevice import panorama
|
||||
from pandevice import objects
|
||||
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, 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, firewall.Firewall):
|
||||
addr = device.find(obj_name, obj_type)
|
||||
return addr
|
||||
elif isinstance(device, 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_address_group_object(**kwargs):
|
||||
"""
|
||||
Create an Address object
|
||||
|
||||
:param kwargs: key word arguments to instantiate AddressGroup object
|
||||
@return False or ```objects.AddressObject```
|
||||
"""
|
||||
ad_object = objects.AddressGroup(
|
||||
name=kwargs['address_gp_name'],
|
||||
static_value=kwargs['sag_match_filter'],
|
||||
description=kwargs['description'],
|
||||
tag=kwargs['tag_name']
|
||||
)
|
||||
if ad_object.static_value or ad_object.dynamic_value:
|
||||
return ad_object
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def add_address_group(device, dev_group, ag_object):
|
||||
"""
|
||||
Create a new dynamic address group object on the
|
||||
PAN FW.
|
||||
|
||||
:param device: Firewall Handle
|
||||
:param dev_group: Panorama device group
|
||||
:param ag_object: Address group object
|
||||
"""
|
||||
|
||||
if dev_group:
|
||||
dev_group.add(ag_object)
|
||||
else:
|
||||
device.add(ag_object)
|
||||
|
||||
exc = None
|
||||
try:
|
||||
ag_object.create()
|
||||
except Exception as exc:
|
||||
return False, exc
|
||||
|
||||
return True, exc
|
||||
|
||||
|
||||
def delete_address_group(device, dev_group, obj_name):
|
||||
"""
|
||||
|
||||
:param device:
|
||||
:param dev_group:
|
||||
:param obj_name:
|
||||
:return:
|
||||
"""
|
||||
static_obj = find_object(device, dev_group, obj_name, objects.AddressGroup)
|
||||
# If found, delete it
|
||||
|
||||
if static_obj:
|
||||
try:
|
||||
static_obj.delete()
|
||||
except Exception as exc:
|
||||
return False, exc
|
||||
return True, None
|
||||
else:
|
||||
return False, None
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
password=dict(required=True),
|
||||
username=dict(default='admin'),
|
||||
api_key=dict(no_log=True),
|
||||
sag_match_filter=dict(type='list', required=False),
|
||||
sag_name=dict(required=True),
|
||||
commit=dict(type='bool', default=True),
|
||||
devicegroup=dict(default=None),
|
||||
description=dict(default=None),
|
||||
tags=dict(type='list', default=[]),
|
||||
operation=dict(type='str', required=True, choices=['add', 'list', 'delete'])
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False,
|
||||
required_one_of=[['api_key', 'password']])
|
||||
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']
|
||||
api_key = module.params['api_key']
|
||||
operation = module.params['operation']
|
||||
|
||||
ag_object = create_address_group_object(address_gp_name=module.params.get('sag_name', None),
|
||||
sag_match_filter=module.params.get('sag_match_filter', None),
|
||||
description=module.params.get('description', None),
|
||||
tag_name=module.params.get('tags', None)
|
||||
)
|
||||
commit = module.params['commit']
|
||||
|
||||
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)
|
||||
|
||||
if operation == 'add':
|
||||
result, exc = add_address_group(device, dev_group, ag_object)
|
||||
|
||||
if result and commit:
|
||||
try:
|
||||
device.commit(sync=True)
|
||||
except Exception as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
elif operation == 'delete':
|
||||
obj_name = module.params.get('sag_name', None)
|
||||
result, exc = delete_address_group(device, dev_group, obj_name)
|
||||
if not result and exc:
|
||||
module.fail_json(msg=exc.message)
|
||||
elif not result:
|
||||
module.fail_json(msg="Specified object not found.")
|
||||
else:
|
||||
module.fail_json(changed=False, msg="Unsupported option.")
|
||||
|
||||
module.exit_json(changed=True, msg="Address Group Operation Completed.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
576
plugins/modules/network/panos/panos_security_rule.py
Normal file
576
plugins/modules/network/panos/panos_security_rule.py
Normal file
|
|
@ -0,0 +1,576 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
|
||||
# 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
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_security_rule
|
||||
short_description: Create security rule policy on PAN-OS devices or Panorama management console.
|
||||
description:
|
||||
- Security policies allow you to enforce rules and take action, and can be as general or specific as needed.
|
||||
The policy rules are compared against the incoming traffic in sequence, and because the first rule that matches the traffic is applied,
|
||||
the more specific rules must precede the more general ones.
|
||||
author: "Ivan Bojer (@ivanbojer), Robert Hagen (@rnh556)"
|
||||
requirements:
|
||||
- pan-python can be obtained from PyPI U(https://pypi.org/project/pan-python/)
|
||||
- pandevice can be obtained from PyPI U(https://pypi.org/project/pandevice/)
|
||||
- xmltodict can be obtained from PyPI U(https://pypi.org/project/xmltodict/)
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
notes:
|
||||
- Checkmode is not supported.
|
||||
- Panorama is supported.
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- IP address (or hostname) of PAN-OS device being configured.
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- Username credentials to use for auth unless I(api_key) is set.
|
||||
default: "admin"
|
||||
password:
|
||||
description:
|
||||
- Password credentials to use for auth unless I(api_key) is set.
|
||||
required: true
|
||||
api_key:
|
||||
description:
|
||||
- API key that can be used instead of I(username)/I(password) credentials.
|
||||
operation:
|
||||
description:
|
||||
- The action to be taken. Supported values are I(add)/I(update)/I(find)/I(delete).
|
||||
default: 'add'
|
||||
choices:
|
||||
- add
|
||||
- update
|
||||
- delete
|
||||
- find
|
||||
category:
|
||||
description:
|
||||
- The category.
|
||||
type: list
|
||||
default: ['any']
|
||||
rule_name:
|
||||
description:
|
||||
- Name of the security rule.
|
||||
required: true
|
||||
rule_type:
|
||||
description:
|
||||
- Type of security rule (version 6.1 of PanOS and above).
|
||||
default: "universal"
|
||||
description:
|
||||
description:
|
||||
- Description for the security rule.
|
||||
tag_name:
|
||||
description:
|
||||
- Administrative tags that can be added to the rule. Note, tags must be already defined.
|
||||
source_zone:
|
||||
description:
|
||||
- List of source zones.
|
||||
default: "any"
|
||||
destination_zone:
|
||||
description:
|
||||
- List of destination zones.
|
||||
default: "any"
|
||||
source_ip:
|
||||
description:
|
||||
- List of source addresses.
|
||||
default: "any"
|
||||
source_user:
|
||||
description:
|
||||
- Use users to enforce policy for individual users or a group of users.
|
||||
default: "any"
|
||||
hip_profiles:
|
||||
description: >
|
||||
- If you are using GlobalProtect with host information profile (HIP) enabled, you can also base the policy
|
||||
on information collected by GlobalProtect. For example, the user access level can be determined HIP that
|
||||
notifies the firewall about the user's local configuration.
|
||||
default: "any"
|
||||
destination_ip:
|
||||
description:
|
||||
- List of destination addresses.
|
||||
default: "any"
|
||||
application:
|
||||
description:
|
||||
- List of applications.
|
||||
default: "any"
|
||||
service:
|
||||
description:
|
||||
- List of services.
|
||||
default: "application-default"
|
||||
log_start:
|
||||
description:
|
||||
- Whether to log at session start.
|
||||
type: bool
|
||||
log_end:
|
||||
description:
|
||||
- Whether to log at session end.
|
||||
default: true
|
||||
type: bool
|
||||
action:
|
||||
description:
|
||||
- Action to apply once rules maches.
|
||||
default: "allow"
|
||||
group_profile:
|
||||
description: >
|
||||
- Security profile group that is already defined in the system. This property supersedes antivirus,
|
||||
vulnerability, spyware, url_filtering, file_blocking, data_filtering, and wildfire_analysis properties.
|
||||
antivirus:
|
||||
description:
|
||||
- Name of the already defined antivirus profile.
|
||||
vulnerability:
|
||||
description:
|
||||
- Name of the already defined vulnerability profile.
|
||||
spyware:
|
||||
description:
|
||||
- Name of the already defined spyware profile.
|
||||
url_filtering:
|
||||
description:
|
||||
- Name of the already defined url_filtering profile.
|
||||
file_blocking:
|
||||
description:
|
||||
- Name of the already defined file_blocking profile.
|
||||
data_filtering:
|
||||
description:
|
||||
- Name of the already defined data_filtering profile.
|
||||
wildfire_analysis:
|
||||
description:
|
||||
- Name of the already defined wildfire_analysis profile.
|
||||
devicegroup:
|
||||
description: >
|
||||
- Device groups are used for the Panorama interaction with Firewall(s). The group must exists on Panorama.
|
||||
If device group is not define we assume that we are contacting Firewall.
|
||||
commit:
|
||||
description:
|
||||
- Commit configuration if changed.
|
||||
type: bool
|
||||
default: 'yes'
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: add an SSH inbound rule to devicegroup
|
||||
panos_security_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
operation: 'add'
|
||||
rule_name: 'SSH permit'
|
||||
description: 'SSH rule test'
|
||||
tag_name: ['ProjectX']
|
||||
source_zone: ['public']
|
||||
destination_zone: ['private']
|
||||
source_ip: ['any']
|
||||
source_user: ['any']
|
||||
destination_ip: ['1.1.1.1']
|
||||
category: ['any']
|
||||
application: ['ssh']
|
||||
service: ['application-default']
|
||||
hip_profiles: ['any']
|
||||
action: 'allow'
|
||||
devicegroup: 'Cloud Edge'
|
||||
|
||||
- name: add a rule to allow HTTP multimedia only from CDNs
|
||||
panos_security_rule:
|
||||
ip_address: '10.5.172.91'
|
||||
username: 'admin'
|
||||
password: 'paloalto'
|
||||
operation: 'add'
|
||||
rule_name: 'HTTP Multimedia'
|
||||
description: 'Allow HTTP multimedia only to host at 1.1.1.1'
|
||||
source_zone: ['public']
|
||||
destination_zone: ['private']
|
||||
source_ip: ['any']
|
||||
source_user: ['any']
|
||||
destination_ip: ['1.1.1.1']
|
||||
category: ['content-delivery-networks']
|
||||
application: ['http-video', 'http-audio']
|
||||
service: ['service-http', 'service-https']
|
||||
hip_profiles: ['any']
|
||||
action: 'allow'
|
||||
|
||||
- name: add a more complex rule that uses security profiles
|
||||
panos_security_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
username: '{{ username }}'
|
||||
password: '{{ password }}'
|
||||
operation: 'add'
|
||||
rule_name: 'Allow HTTP w profile'
|
||||
log_start: false
|
||||
log_end: true
|
||||
action: 'allow'
|
||||
antivirus: 'default'
|
||||
vulnerability: 'default'
|
||||
spyware: 'default'
|
||||
url_filtering: 'default'
|
||||
wildfire_analysis: 'default'
|
||||
|
||||
- name: delete a devicegroup security rule
|
||||
panos_security_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
api_key: '{{ api_key }}'
|
||||
operation: 'delete'
|
||||
rule_name: 'Allow telnet'
|
||||
devicegroup: 'DC Firewalls'
|
||||
|
||||
- name: find a specific security rule
|
||||
panos_security_rule:
|
||||
ip_address: '{{ ip_address }}'
|
||||
password: '{{ password }}'
|
||||
operation: 'find'
|
||||
rule_name: 'Allow RDP to DCs'
|
||||
register: result
|
||||
- debug: msg='{{result.stdout_lines}}'
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
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
|
||||
from pandevice import policies
|
||||
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 get_rulebase(device, devicegroup):
|
||||
# Build the rulebase
|
||||
if isinstance(device, pandevice.firewall.Firewall):
|
||||
rulebase = pandevice.policies.Rulebase()
|
||||
device.add(rulebase)
|
||||
elif isinstance(device, pandevice.panorama.Panorama):
|
||||
dg = panorama.DeviceGroup(devicegroup)
|
||||
device.add(dg)
|
||||
rulebase = policies.PreRulebase()
|
||||
dg.add(rulebase)
|
||||
else:
|
||||
return False
|
||||
policies.SecurityRule.refreshall(rulebase)
|
||||
return rulebase
|
||||
|
||||
|
||||
def find_rule(rulebase, rule_name):
|
||||
# Search for the rule name
|
||||
rule = rulebase.find(rule_name)
|
||||
if rule:
|
||||
return rule
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def rule_is_match(propose_rule, current_rule):
|
||||
|
||||
match_check = ['name', 'description', 'group_profile', 'antivirus', 'vulnerability',
|
||||
'spyware', 'url_filtering', 'file_blocking', 'data_filtering',
|
||||
'wildfire_analysis', 'type', 'action', 'tag', 'log_start', 'log_end']
|
||||
list_check = ['tozone', 'fromzone', 'source', 'source_user', 'destination', 'category',
|
||||
'application', 'service', 'hip_profiles']
|
||||
|
||||
for check in match_check:
|
||||
propose_check = getattr(propose_rule, check, None)
|
||||
current_check = getattr(current_rule, check, None)
|
||||
if propose_check != current_check:
|
||||
return False
|
||||
for check in list_check:
|
||||
propose_check = getattr(propose_rule, check, [])
|
||||
current_check = getattr(current_rule, check, [])
|
||||
if set(propose_check) != set(current_check):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def create_security_rule(**kwargs):
|
||||
security_rule = policies.SecurityRule(
|
||||
name=kwargs['rule_name'],
|
||||
description=kwargs['description'],
|
||||
fromzone=kwargs['source_zone'],
|
||||
source=kwargs['source_ip'],
|
||||
source_user=kwargs['source_user'],
|
||||
hip_profiles=kwargs['hip_profiles'],
|
||||
tozone=kwargs['destination_zone'],
|
||||
destination=kwargs['destination_ip'],
|
||||
application=kwargs['application'],
|
||||
service=kwargs['service'],
|
||||
category=kwargs['category'],
|
||||
log_start=kwargs['log_start'],
|
||||
log_end=kwargs['log_end'],
|
||||
action=kwargs['action'],
|
||||
type=kwargs['rule_type']
|
||||
)
|
||||
|
||||
if 'tag_name' in kwargs:
|
||||
security_rule.tag = kwargs['tag_name']
|
||||
|
||||
# profile settings
|
||||
if 'group_profile' in kwargs:
|
||||
security_rule.group = kwargs['group_profile']
|
||||
else:
|
||||
if 'antivirus' in kwargs:
|
||||
security_rule.virus = kwargs['antivirus']
|
||||
if 'vulnerability' in kwargs:
|
||||
security_rule.vulnerability = kwargs['vulnerability']
|
||||
if 'spyware' in kwargs:
|
||||
security_rule.spyware = kwargs['spyware']
|
||||
if 'url_filtering' in kwargs:
|
||||
security_rule.url_filtering = kwargs['url_filtering']
|
||||
if 'file_blocking' in kwargs:
|
||||
security_rule.file_blocking = kwargs['file_blocking']
|
||||
if 'data_filtering' in kwargs:
|
||||
security_rule.data_filtering = kwargs['data_filtering']
|
||||
if 'wildfire_analysis' in kwargs:
|
||||
security_rule.wildfire_analysis = kwargs['wildfire_analysis']
|
||||
return security_rule
|
||||
|
||||
|
||||
def add_rule(rulebase, sec_rule):
|
||||
if rulebase:
|
||||
rulebase.add(sec_rule)
|
||||
sec_rule.create()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def update_rule(rulebase, nat_rule):
|
||||
if rulebase:
|
||||
rulebase.add(nat_rule)
|
||||
nat_rule.apply()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
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(default='add', choices=['add', 'update', 'delete', 'find']),
|
||||
rule_name=dict(required=True),
|
||||
description=dict(default=''),
|
||||
tag_name=dict(type='list'),
|
||||
destination_zone=dict(type='list', default=['any']),
|
||||
source_zone=dict(type='list', default=['any']),
|
||||
source_ip=dict(type='list', default=["any"]),
|
||||
source_user=dict(type='list', default=['any']),
|
||||
destination_ip=dict(type='list', default=["any"]),
|
||||
category=dict(type='list', default=['any']),
|
||||
application=dict(type='list', default=['any']),
|
||||
service=dict(type='list', default=['application-default']),
|
||||
hip_profiles=dict(type='list', default=['any']),
|
||||
group_profile=dict(),
|
||||
antivirus=dict(),
|
||||
vulnerability=dict(),
|
||||
spyware=dict(),
|
||||
url_filtering=dict(),
|
||||
file_blocking=dict(),
|
||||
data_filtering=dict(),
|
||||
wildfire_analysis=dict(),
|
||||
log_start=dict(type='bool', default=False),
|
||||
log_end=dict(type='bool', default=True),
|
||||
rule_type=dict(default='universal'),
|
||||
action=dict(default='allow'),
|
||||
devicegroup=dict(),
|
||||
commit=dict(type='bool', default=True)
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False,
|
||||
required_one_of=[['api_key', 'password']])
|
||||
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']
|
||||
rule_name = module.params['rule_name']
|
||||
description = module.params['description']
|
||||
tag_name = module.params['tag_name']
|
||||
source_zone = module.params['source_zone']
|
||||
source_ip = module.params['source_ip']
|
||||
source_user = module.params['source_user']
|
||||
hip_profiles = module.params['hip_profiles']
|
||||
destination_zone = module.params['destination_zone']
|
||||
destination_ip = module.params['destination_ip']
|
||||
application = module.params['application']
|
||||
service = module.params['service']
|
||||
category = module.params['category']
|
||||
log_start = module.params['log_start']
|
||||
log_end = module.params['log_end']
|
||||
action = module.params['action']
|
||||
group_profile = module.params['group_profile']
|
||||
antivirus = module.params['antivirus']
|
||||
vulnerability = module.params['vulnerability']
|
||||
spyware = module.params['spyware']
|
||||
url_filtering = module.params['url_filtering']
|
||||
file_blocking = module.params['file_blocking']
|
||||
data_filtering = module.params['data_filtering']
|
||||
wildfire_analysis = module.params['wildfire_analysis']
|
||||
rule_type = module.params['rule_type']
|
||||
devicegroup = module.params['devicegroup']
|
||||
|
||||
commit = module.params['commit']
|
||||
|
||||
# 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)
|
||||
|
||||
# Get the rulebase
|
||||
rulebase = get_rulebase(device, dev_group)
|
||||
|
||||
# Which action shall we take on the object?
|
||||
if operation == "find":
|
||||
# Search for the object
|
||||
match = find_rule(rulebase, rule_name)
|
||||
# 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='Rule matched'
|
||||
)
|
||||
else:
|
||||
module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name)
|
||||
elif operation == "delete":
|
||||
# Search for the object
|
||||
match = find_rule(rulebase, rule_name)
|
||||
# If found, delete it
|
||||
if match:
|
||||
try:
|
||||
if commit:
|
||||
match.delete()
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
module.exit_json(changed=True, msg='Rule \'%s\' successfully deleted' % rule_name)
|
||||
else:
|
||||
module.fail_json(msg='Rule \'%s\' not found. Is the name correct?' % rule_name)
|
||||
elif operation == "add":
|
||||
new_rule = create_security_rule(
|
||||
rule_name=rule_name,
|
||||
description=description,
|
||||
tag_name=tag_name,
|
||||
source_zone=source_zone,
|
||||
destination_zone=destination_zone,
|
||||
source_ip=source_ip,
|
||||
source_user=source_user,
|
||||
destination_ip=destination_ip,
|
||||
category=category,
|
||||
application=application,
|
||||
service=service,
|
||||
hip_profiles=hip_profiles,
|
||||
group_profile=group_profile,
|
||||
antivirus=antivirus,
|
||||
vulnerability=vulnerability,
|
||||
spyware=spyware,
|
||||
url_filtering=url_filtering,
|
||||
file_blocking=file_blocking,
|
||||
data_filtering=data_filtering,
|
||||
wildfire_analysis=wildfire_analysis,
|
||||
log_start=log_start,
|
||||
log_end=log_end,
|
||||
rule_type=rule_type,
|
||||
action=action
|
||||
)
|
||||
# Search for the rule. Fail if found.
|
||||
match = find_rule(rulebase, rule_name)
|
||||
if match:
|
||||
if rule_is_match(match, new_rule):
|
||||
module.exit_json(changed=False, msg='Rule \'%s\' is already in place' % rule_name)
|
||||
else:
|
||||
module.fail_json(msg='Rule \'%s\' already exists. Use operation: \'update\' to change it.' % rule_name)
|
||||
else:
|
||||
try:
|
||||
changed = add_rule(rulebase, new_rule)
|
||||
if changed and commit:
|
||||
device.commit(sync=True)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
module.exit_json(changed=changed, msg='Rule \'%s\' successfully added' % rule_name)
|
||||
elif operation == 'update':
|
||||
# Search for the rule. Update if found.
|
||||
match = find_rule(rulebase, rule_name)
|
||||
if match:
|
||||
try:
|
||||
new_rule = create_security_rule(
|
||||
rule_name=rule_name,
|
||||
description=description,
|
||||
tag_name=tag_name,
|
||||
source_zone=source_zone,
|
||||
destination_zone=destination_zone,
|
||||
source_ip=source_ip,
|
||||
source_user=source_user,
|
||||
destination_ip=destination_ip,
|
||||
category=category,
|
||||
application=application,
|
||||
service=service,
|
||||
hip_profiles=hip_profiles,
|
||||
group_profile=group_profile,
|
||||
antivirus=antivirus,
|
||||
vulnerability=vulnerability,
|
||||
spyware=spyware,
|
||||
url_filtering=url_filtering,
|
||||
file_blocking=file_blocking,
|
||||
data_filtering=data_filtering,
|
||||
wildfire_analysis=wildfire_analysis,
|
||||
log_start=log_start,
|
||||
log_end=log_end,
|
||||
rule_type=rule_type,
|
||||
action=action
|
||||
)
|
||||
changed = update_rule(rulebase, new_rule)
|
||||
if changed and commit:
|
||||
device.commit(sync=True)
|
||||
except PanXapiError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
module.exit_json(changed=changed, msg='Rule \'%s\' successfully updated' % rule_name)
|
||||
else:
|
||||
module.fail_json(msg='Rule \'%s\' does not exist. Use operation: \'add\' to add it.' % rule_name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
167
plugins/modules/network/panos/panos_set.py
Normal file
167
plugins/modules/network/panos/panos_set.py
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible module to manage PaloAltoNetworks Firewall
|
||||
# (c) 2018, Jasper Mackenzie <jasper.mackenzie@gmail.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: panos_set
|
||||
short_description: Execute arbitrary commands on a PAN-OS device using XPath and element
|
||||
description:
|
||||
- Run an arbitrary 'xapi' command taking an XPath (i.e get) or XPath and element (i.e set).
|
||||
- See https://github.com/kevinsteves/pan-python/blob/master/doc/pan.xapi.rst for details
|
||||
- Runs a 'set' command by default
|
||||
- This should support _all_ commands that your PAN-OS device accepts vi it's cli
|
||||
- cli commands are found as
|
||||
- Once logged in issue 'debug cli on'
|
||||
- Enter configuration mode by issuing 'configure'
|
||||
- Enter your set (or other) command, for example 'set deviceconfig system timezone Australia/Melbourne'
|
||||
- returns
|
||||
- >
|
||||
"<request cmd="set"
|
||||
obj="/config/devices/entry[@name='localhost.localdomain']/deviceconfig/system"
|
||||
cookie=XXXX><timezone>Australia/Melbourne</timezone></request>
|
||||
- The 'xpath' is "/config/devices/entry[@name='localhost.localdomain']/deviceconfig/system"
|
||||
- The 'element' is "<timezone>Australia/Melbourne</timezone>"
|
||||
author: "Jasper Mackenzie (@spmp)"
|
||||
deprecated:
|
||||
alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
|
||||
removed_in: "2.12"
|
||||
why: Consolidating code base.
|
||||
requirements:
|
||||
- pan-python
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- IP address or host FQDN of the target PAN-OS NVA
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- User name for a user with admin rights on the PAN-OS NVA
|
||||
default: admin
|
||||
password:
|
||||
description:
|
||||
- Password for the given 'username'
|
||||
required: true
|
||||
command:
|
||||
description:
|
||||
- Xapi method name which supports 'xpath' or 'xpath' and 'element'
|
||||
choices:
|
||||
- set
|
||||
- edit
|
||||
- delete
|
||||
- get
|
||||
- show
|
||||
- override
|
||||
default: set
|
||||
xpath:
|
||||
description:
|
||||
- The 'xpath' for the commands configurable
|
||||
required: true
|
||||
element:
|
||||
description:
|
||||
- The 'element' for the 'xpath' if required
|
||||
extends_documentation_fragment:
|
||||
- community.general.panos
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
|
||||
- name: Set timezone on PA NVA
|
||||
panos_set:
|
||||
ip_address: "192.168.1.1"
|
||||
username: "my-random-admin"
|
||||
password: "admin1234"
|
||||
xpath: "/config/devices/entry/deviceconfig/system"
|
||||
element: "<timezone>Australia/Melbourne</timezone>"
|
||||
|
||||
- name: Commit configuration
|
||||
panos_commit:
|
||||
ip_address: "192.168.1.1"
|
||||
username: "my-random-admin"
|
||||
password: "admin1234"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
# Default return values
|
||||
'''
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['deprecated'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
import pan.xapi
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
ip_address=dict(required=True),
|
||||
password=dict(required=True, no_log=True),
|
||||
username=dict(default='admin'),
|
||||
command=dict(default='set', choices=['set', 'edit', 'delete', 'get', 'show', 'override']),
|
||||
xpath=dict(required=True),
|
||||
element=dict(default=None)
|
||||
)
|
||||
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']
|
||||
xpath = module.params['xpath']
|
||||
element = module.params['element']
|
||||
xcommand = module.params['command']
|
||||
|
||||
xapi = pan.xapi.PanXapi(
|
||||
hostname=ip_address,
|
||||
api_username=username,
|
||||
api_password=password,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
if element is None:
|
||||
# Issue command with no `element`
|
||||
try:
|
||||
getattr(xapi, xcommand)(xpath=xpath)
|
||||
except Exception as e:
|
||||
raise Exception("Failed to run '%s' with xpath: '%s' with the following error: %s" %
|
||||
(xcommand, xpath, e))
|
||||
else:
|
||||
# Issue command with `element`
|
||||
try:
|
||||
getattr(xapi, xcommand)(xpath=xpath, element=element)
|
||||
except Exception as e:
|
||||
raise Exception("Failed to run '%s' with xpath: '%s' and element '%s' with the following error: %s" %
|
||||
(xcommand, xpath, element, e))
|
||||
|
||||
module.exit_json(
|
||||
status="success"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue