diff --git a/lib/ansible/module_utils/network/checkpoint/checkpoint.py b/lib/ansible/module_utils/network/checkpoint/checkpoint.py index 7545ebfe34..2448b73e22 100644 --- a/lib/ansible/module_utils/network/checkpoint/checkpoint.py +++ b/lib/ansible/module_utils/network/checkpoint/checkpoint.py @@ -26,6 +26,188 @@ # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +from __future__ import (absolute_import, division, print_function) + +import time + +from ansible.module_utils.connection import Connection + + +checkpoint_argument_spec_for_objects = dict( + auto_publish_session=dict(type='bool'), + wait_for_task=dict(type='bool', default=True), + state=dict(type='str', required=True, choices=['present', 'absent']), + version=dict(type='str') +) + +checkpoint_argument_spec_for_facts = dict( + version=dict(type='str') +) + +checkpoint_argument_spec_for_commands = dict( + wait_for_task=dict(type='bool', default=True), + version=dict(type='str') +) + + +# send the request to checkpoint +def send_request(connection, version, url, payload=None): + code, response = connection.send_request('/web_api/' + version + url, payload) + + return code, response + + +# get the payload from the user parameters +def is_checkpoint_param(parameter): + if parameter == 'auto_publish_session' or\ + parameter == 'state' or\ + parameter == 'wait_for_task' or\ + parameter == 'version': + return False + return True + + +# build the payload from the parameters which has value (not None), and they are parameter of checkpoint API as well +def get_payload_from_parameters(module): + payload = {} + for parameter in module.params: + if module.params[parameter] and is_checkpoint_param(parameter): + payload[parameter.replace("_", "-")] = module.params[parameter] + return payload + + +# wait for task +def wait_for_task(module, version, connection, task_id): + task_id_payload = {'task-id': task_id} + task_complete = False + current_iteration = 0 + max_num_iterations = 300 + + # As long as there is a task in progress + while not task_complete and current_iteration < max_num_iterations: + current_iteration += 1 + # Check the status of the task + code, response = send_request(connection, version, 'show-task', task_id_payload) + + attempts_counter = 0 + while code != 200: + if attempts_counter < 5: + attempts_counter += 1 + time.sleep(2) + code, response = send_request(connection, version, 'show-task', task_id_payload) + else: + response['message'] = "ERROR: Failed to handle asynchronous tasks as synchronous, tasks result is" \ + " undefined.\n" + response['message'] + module.fail_json(msg=response) + + # Count the number of tasks that are not in-progress + completed_tasks = 0 + for task in response['tasks']: + if task['status'] == 'failed': + module.fail_json(msg='Task {0} with task id {1} failed. Look at the logs for more details' + .format(task['task-name'], task['task-id'])) + if task['status'] == 'in progress': + break + completed_tasks += 1 + + # Are we done? check if all tasks are completed + if completed_tasks == len(response["tasks"]): + task_complete = True + else: + time.sleep(2) # Wait for two seconds + if not task_complete: + module.fail_json(msg="ERROR: Timeout.\nTask-id: {0}.".format(task_id_payload['task-id'])) + + +# handle publish command, and wait for it to end if the user asked so +def handle_publish(module, connection, version): + if module.params['auto_publish_session']: + publish_code, publish_response = send_request(connection, version, 'publish') + if publish_code != 200: + module.fail_json(msg=publish_response) + if module.params['wait_for_task']: + wait_for_task(module, version, connection, publish_response['task-id']) + + +# handle a command +def api_command(module, command): + payload = get_payload_from_parameters(module) + connection = Connection(module._socket_path) + # if user insert a specific version, we add it to the url + version = ('v' + module.params['version'] + '/') if module.params['version'] else '' + + code, response = send_request(connection, version, command, payload) + result = {'changed': True} + + if code == 200: + if module.params['wait_for_task']: + if 'task-id' in response: + wait_for_task(module, version, connection, response['task-id']) + elif 'tasks' in response: + for task_id in response['tasks']: + wait_for_task(module, version, connection, task_id) + + result[command] = response + else: + module.fail_json(msg='Checkpoint device returned error {0} with message {1}'.format(code, response)) + + return result + + +# handle api call +def api_call(module, api_call_object): + payload = get_payload_from_parameters(module) + connection = Connection(module._socket_path) + # if user insert a specific version, we add it to the url + version = ('v' + module.params['version'] + '/') if module.params['version'] else '' + + payload_for_equals = {'type': api_call_object, 'params': payload} + equals_code, equals_response = send_request(connection, version, 'equals', payload_for_equals) + # if code is 400 (bad request) or 500 (internal error) - fail + if equals_code == 400 or equals_code == 500: + module.fail_json(msg=equals_response) + result = {'changed': False} + + if module.params['state'] == 'present': + if equals_code == 200: + if not equals_response['equals']: + code, response = send_request(connection, version, 'set-' + api_call_object, payload) + if code != 200: + module.fail_json(msg=response) + + handle_publish(module, connection, version) + + result['changed'] = True + result[api_call_object] = response + else: + # objects are equals and there is no need for set request + pass + elif equals_code == 404: + code, response = send_request(connection, version, 'add-' + api_call_object, payload) + if code != 200: + module.fail_json(msg=response) + + handle_publish(module, connection, version) + + result['changed'] = True + result[api_call_object] = response + else: + # state == absent + if equals_code == 200: + code, response = send_request(connection, version, 'delete-' + api_call_object, payload) + if code != 200: + module.fail_json(msg=response) + + handle_publish(module, connection, version) + + result['changed'] = True + elif equals_code == 404: + # no need to delete because object dose not exist + pass + + result['checkpoint_session_uid'] = connection.get_session_uid() + return result + checkpoint_argument_spec = dict(auto_publish_session=dict(type='bool', default=True), policy_package=dict(type='str', default='standard'), diff --git a/lib/ansible/modules/network/checkpoint/cp_network.py b/lib/ansible/modules/network/checkpoint/cp_network.py new file mode 100644 index 0000000000..de892a26a3 --- /dev/null +++ b/lib/ansible/modules/network/checkpoint/cp_network.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage CheckPoint Firewall (c) 2019 +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +--- +module: cp_network +short_description: Manages network objects on Checkpoint over Web Services API +description: + - Manages network objects on Checkpoint devices including creating, updating and removing objects. + All operations are performed over Web Services API. +version_added: "2.9" +author: "Or Soffer (@chkp-orso)" +options: + name: + description: + - Object name. + type: str + subnet: + description: + - IPv4 or IPv6 network address. If both addresses are required use subnet4 and subnet6 fields explicitly. + type: str + subnet4: + description: + - IPv4 network address. + type: str + subnet6: + description: + - IPv6 network address. + type: str + mask_length: + description: + - IPv4 or IPv6 network mask length. If both masks are required use mask-length4 and mask-length6 fields + explicitly. Instead of IPv4 mask length it is possible to specify IPv4 mask itself in subnet-mask field. + type: int + mask_length4: + description: + - IPv4 network mask length. + type: int + mask_length6: + description: + - IPv6 network mask length. + type: int + subnet_mask: + description: + - IPv4 network mask. + type: str + nat_settings: + description: + - NAT settings. + type: dict + tags: + description: + - Collection of tag identifiers. + type: list + broadcast: + description: + - Allow broadcast address inclusion. + type: str + choices: + - disallow + - allow + color: + description: + - Color of the object. Should be one of existing colors. + choices: ['aquamarine', 'black', 'blue', 'crete blue', 'burlywood', 'cyan', 'dark green', 'khaki', 'orchid', + 'dark orange', 'dark sea green', 'pink', 'turquoise', 'dark blue', 'firebrick', 'brown', 'forest green', + 'gold', 'dark gold', 'gray', 'dark gray', 'light green', 'lemon chiffon', 'coral', 'sea green', + 'sky blue', 'magenta', 'purple', 'slate blue', 'violet red', 'navy blue', 'olive', 'orange', 'red', + 'sienna', 'yellow'] + comments: + description: + - Comments string. + type: str + details_level: + description: + - The level of detail for some of the fields in the response can vary from showing only the UID value of the + object to a fully detailed representation of the object. + type: str + choices: [uid, standard, full] + groups: + description: + - Collection of group identifiers. + type: list + ignore_warnings: + description: + - Apply changes ignoring warnings. + type: bool + ignore_errors: + description: + - Apply changes ignoring errors. You won't be able to publish such a changes. If ignore-warnings flag was omitted + - warnings will also be ignored. + type: bool + uid: + description: + - Object unique identifier. + type: str + new_name: + description: + - New name of the object. + type: str +extends_documentation_fragment: checkpoint_objects +""" + +EXAMPLES = """ +- name: add-network + cp_network: + name: New Network 3 + nat_settings: + auto-rule: true + hide-behind: ip-address + install-on: All + ip-address: 192.0.2.1 + method: static + state: present + subnet: 192.0.2.1 + subnet_mask: 255.255.255.0 + +- name: set-network + cp_network: + name : New Network 1 + new-name : New Network 2 + color : green + subnet : 192.0.0.0 + mask-length : 16 + groups : New Group 1 + state: present + +- name: delete-network + cp_network: + name : New Network 2 + state: absent +""" + +RETURN = """ +cp_network: + description: The checkpoint object created or updated. + returned: always, except when deleting the object. + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.checkpoint.checkpoint import checkpoint_argument_spec_for_objects, api_call + + +def main(): + argument_spec = dict( + name=dict(type='str'), + subnet=dict(type='str'), + subnet4=dict(type='str'), + subnet6=dict(type='str'), + mask_length=dict(type='int'), + mask_length4=dict(type='int'), + mask_length6=dict(type='int'), + subnet_mask=dict(type='str'), + nat_settings=dict(type='dict'), + tags=dict(type='list'), + broadcast=dict(type='str', choices=['disallow', 'allow']), + color=dict(type='str', choices=['aquamarine', 'black', 'blue', 'crete blue', 'burlywood', 'cyan', 'dark green', + 'khaki', 'orchid', 'dark orange', 'dark sea green', 'pink', 'turquoise', + 'dark blue', 'firebrick', 'brown', 'forest green', 'gold', 'dark gold', 'gray', + 'dark gray', 'light green', 'lemon chiffon', 'coral', 'sea green', 'sky blue', + 'magenta', 'purple', 'slate blue', 'violet red', 'navy blue', 'olive', 'orange', + 'red', 'sienna', 'yellow']), + comments=dict(type='str'), + details_level=dict(type='str', choices=['uid', 'standard', 'full']), + groups=dict(type='list'), + ignore_warnings=dict(type='bool'), + ignore_errors=dict(type='bool'), + uid=dict(type='str'), + new_name=dict(type='str') + ) + argument_spec.update(checkpoint_argument_spec_for_objects) + + module = AnsibleModule(argument_spec=argument_spec, required_one_of=[['name', 'uid']], + mutually_exclusive=[['name', 'uid']]) + api_call_object = 'network' + + result = api_call(module, api_call_object) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/network/checkpoint/cp_publish.py b/lib/ansible/modules/network/checkpoint/cp_publish.py new file mode 100644 index 0000000000..0ddade67b0 --- /dev/null +++ b/lib/ansible/modules/network/checkpoint/cp_publish.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage CheckPoint Firewall (c) 2019 +# +# 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 . +# + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +--- +module: cp_publish +short_description: All the changes done by this user will be seen by all users only after publish is called. +description: + - All the changes done by this user will be seen by all users only after publish is called. + All operations are performed over Web Services API. +version_added: "2.9" +author: "Or Soffer (@chkp-orso)" +options: + uid: + description: + - Session unique identifier. Specify it to publish a different session than the one you currently use. + type: str +extends_documentation_fragment: checkpoint_commands +""" + +EXAMPLES = """ +- name: publish + cp_publish: +""" + +RETURN = """ +cp_publish: + description: The checkpoint publish output. + returned: always. + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.checkpoint.checkpoint import checkpoint_argument_spec_for_commands, api_command + + +def main(): + argument_spec = dict( + uid=dict(type='str') + ) + argument_spec.update(checkpoint_argument_spec_for_commands) + + module = AnsibleModule(argument_spec=argument_spec) + + command = "publish" + + result = api_command(module, command) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/plugins/doc_fragments/checkpoint_commands.py b/lib/ansible/plugins/doc_fragments/checkpoint_commands.py new file mode 100644 index 0000000000..9f17df52f2 --- /dev/null +++ b/lib/ansible/plugins/doc_fragments/checkpoint_commands.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Or Soffer +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + wait_for_task: + description: + - Wait for the task to end. Such as publish task. + type: bool + default: True + version: + description: + - Version of checkpoint. If not given one, the latest version taken. + type: str +''' diff --git a/lib/ansible/plugins/doc_fragments/checkpoint_objects.py b/lib/ansible/plugins/doc_fragments/checkpoint_objects.py new file mode 100644 index 0000000000..a5dc858e13 --- /dev/null +++ b/lib/ansible/plugins/doc_fragments/checkpoint_objects.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Or Soffer +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + state: + description: + - State of the access rule (present or absent). Defaults to present. + type: str + required: True + choices: + - 'present' + - 'absent' + auto_publish_session: + description: + - Publish the current session if changes have been performed + after task completes. + type: bool + wait_for_task: + description: + - Wait for the task to end. Such as publish task. + type: bool + default: True + version: + description: + - Version of checkpoint. If not given one, the latest version taken. + type: str +''' diff --git a/lib/ansible/plugins/httpapi/checkpoint.py b/lib/ansible/plugins/httpapi/checkpoint.py index ae304117f9..1e0a6164be 100644 --- a/lib/ansible/plugins/httpapi/checkpoint.py +++ b/lib/ansible/plugins/httpapi/checkpoint.py @@ -63,7 +63,7 @@ class HttpApi(HttpApiBase): return response.getcode(), self._response_to_json(value) except AnsibleConnectionFailure as e: - return 404, 'Object not found' + return 404, e.message except HTTPError as e: error = json.loads(e.read()) return e.code, error