From 5e4e32b45eb39062ba6a23b68b0f60f35be6b6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Angel=20Mu=C3=B1oz=20Gonz=C3=A1lez?= Date: Tue, 27 Aug 2019 17:37:39 +0200 Subject: [PATCH] FortiOS modules for 2.9 - 10 (#61367) * FortiOS modules for 2.9 - 10 * Fortios User Device was wrongly set to 2.8 instead of 2.9 * Retriggering shippable due to random failure --- .../network/fortios/fortios_user_adgrp.py | 133 +- .../network/fortios/fortios_user_device.py | 47 +- .../network/fortios/fortios_user_radius.py | 417 +++-- .../fortios/fortios_user_tacacsplus.py | 198 ++- .../network/fortios/fortios_voip_profile.py | 825 ++++++---- .../fortios/fortios_vpn_ipsec_concentrator.py | 152 +- .../fortios/fortios_vpn_ipsec_forticlient.py | 144 +- .../fortios/fortios_vpn_ipsec_manualkey.py | 177 +- .../fortios_vpn_ipsec_manualkey_interface.py | 245 +-- .../fortios/fortios_vpn_ipsec_phase1.py | 755 +++++---- .../fortios_vpn_ipsec_phase1_interface.py | 953 ++++++----- .../fortios/fortios_vpn_ipsec_phase2.py | 371 +++-- .../fortios_vpn_ipsec_phase2_interface.py | 373 +++-- .../fortios/fortios_vpn_ssl_settings.py | 551 ++++--- .../fortios/fortios_vpn_ssl_web_portal.py | 748 +++++---- .../network/fortios/fortios_waf_profile.py | 507 ++++-- .../network/fortios/fortios_wanopt_profile.py | 348 ++-- .../fortios/fortios_wanopt_settings.py | 120 +- .../fortios/fortios_webfilter_content.py | 168 +- .../fortios_webfilter_content_header.py | 168 +- test/sanity/ignore.txt | 37 - .../fortios/test_fortios_user_adgrp.py | 209 +++ .../fortios/test_fortios_user_device.py | 164 +- .../fortios/test_fortios_user_radius.py | 539 +++++++ .../fortios/test_fortios_user_tacacsplus.py | 299 ++++ .../fortios/test_fortios_voip_profile.py | 219 +++ .../test_fortios_vpn_ipsec_concentrator.py | 199 +++ .../test_fortios_vpn_ipsec_forticlient.py | 229 +++ .../test_fortios_vpn_ipsec_manualkey.py | 289 ++++ ...t_fortios_vpn_ipsec_manualkey_interface.py | 329 ++++ .../fortios/test_fortios_vpn_ipsec_phase1.py | 1149 +++++++++++++ ...test_fortios_vpn_ipsec_phase1_interface.py | 1419 +++++++++++++++++ .../fortios/test_fortios_vpn_ipsec_phase2.py | 599 +++++++ ...test_fortios_vpn_ipsec_phase2_interface.py | 599 +++++++ .../fortios/test_fortios_vpn_ssl_settings.py | 495 ++++++ .../test_fortios_vpn_ssl_web_portal.py | 689 ++++++++ .../fortios/test_fortios_waf_profile.py | 229 +++ .../fortios/test_fortios_wanopt_profile.py | 229 +++ .../fortios/test_fortios_wanopt_settings.py | 167 ++ .../fortios/test_fortios_webfilter_content.py | 219 +++ .../test_fortios_webfilter_content_header.py | 219 +++ 41 files changed, 12932 insertions(+), 2994 deletions(-) create mode 100644 test/units/modules/network/fortios/test_fortios_user_adgrp.py create mode 100644 test/units/modules/network/fortios/test_fortios_user_radius.py create mode 100644 test/units/modules/network/fortios/test_fortios_user_tacacsplus.py create mode 100644 test/units/modules/network/fortios/test_fortios_voip_profile.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ipsec_concentrator.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ipsec_forticlient.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ipsec_manualkey.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ipsec_manualkey_interface.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase1.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase1_interface.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase2.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase2_interface.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ssl_settings.py create mode 100644 test/units/modules/network/fortios/test_fortios_vpn_ssl_web_portal.py create mode 100644 test/units/modules/network/fortios/test_fortios_waf_profile.py create mode 100644 test/units/modules/network/fortios/test_fortios_wanopt_profile.py create mode 100644 test/units/modules/network/fortios/test_fortios_wanopt_settings.py create mode 100644 test/units/modules/network/fortios/test_fortios_webfilter_content.py create mode 100644 test/units/modules/network/fortios/test_fortios_webfilter_content_header.py diff --git a/lib/ansible/modules/network/fortios/fortios_user_adgrp.py b/lib/ansible/modules/network/fortios/fortios_user_adgrp.py index 7cc8a1c8378..4dbc1727ee6 100644 --- a/lib/ansible/modules/network/fortios/fortios_user_adgrp.py +++ b/lib/ansible/modules/network/fortios/fortios_user_adgrp.py @@ -26,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_user_adgrp short_description: Configure FSSO groups in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify user feature and adgrp category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -41,47 +41,62 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 user_adgrp: description: - Configure FSSO groups. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent name: description: - Name. required: true - server-name: + type: str + server_name: description: - FSSO agent name. Source user.fsso.name. + type: str ''' EXAMPLES = ''' @@ -91,6 +106,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure FSSO groups. fortios_user_adgrp: @@ -99,10 +115,10 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" user_adgrp: - state: "present" name: "default_name_3" - server-name: " (source user.fsso.name)" + server_name: " (source user.fsso.name)" ''' RETURN = ''' @@ -165,12 +181,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -178,11 +198,11 @@ def login(data, fos): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_user_adgrp_data(json): - option_list = ['name', 'server-name'] + option_list = ['name', 'server_name'] dictionary = {} for attribute in option_list: @@ -192,48 +212,68 @@ def filter_user_adgrp_data(json): return dictionary +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data + + return data + + def user_adgrp(data, fos): vdom = data['vdom'] + state = data['state'] user_adgrp_data = data['user_adgrp'] - filtered_data = filter_user_adgrp_data(user_adgrp_data) + filtered_data = underscore_to_hyphen(filter_user_adgrp_data(user_adgrp_data)) - if user_adgrp_data['state'] == "present": + if state == "present": return fos.set('user', 'adgrp', data=filtered_data, vdom=vdom) - elif user_adgrp_data['state'] == "absent": + elif state == "absent": return fos.delete('user', 'adgrp', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_user(data, fos): - login(data, fos) if data['user_adgrp']: resp = user_adgrp(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "user_adgrp": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, "name": {"required": True, "type": "str"}, - "server-name": {"required": False, "type": "str"} + "server_name": {"required": False, "type": "str"} } } @@ -241,14 +281,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_user(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_user(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_user(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_user_device.py b/lib/ansible/modules/network/fortios/fortios_user_device.py index 5ca873312c0..adfdffd3bac 100644 --- a/lib/ansible/modules/network/fortios/fortios_user_device.py +++ b/lib/ansible/modules/network/fortios/fortios_user_device.py @@ -13,7 +13,7 @@ from __future__ import (absolute_import, division, print_function) # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program. If not, see __metaclass__ = type @@ -24,12 +24,12 @@ ANSIBLE_METADATA = {'status': ['preview'], DOCUMENTATION = ''' --- module: fortios_user_device -short_description: Configure devices in Fortinet's FortiOS and FortiGate +short_description: Configure devices in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS device by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify user feature and device category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.9" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,12 +44,12 @@ options: description: - FortiOS or FortiGate IP address. type: str - required: true + required: false username: description: - FortiOS or FortiGate username. type: str - required: true + required: false password: description: - FortiOS or FortiGate password. @@ -64,14 +64,19 @@ options: default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol. + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true state: description: - Indicates whether to create or remove the object. type: str + required: true choices: - present - absent @@ -85,12 +90,15 @@ options: description: - Device alias. required: true + type: str avatar: description: - Image file for avatar (maximum 4K base64 encoded). + type: str category: description: - Device category. + type: str choices: - none - amazon-device @@ -102,34 +110,43 @@ options: comment: description: - Comment. + type: str mac: description: - - Device MAC address(es). + - Device MAC address. + type: str master_device: description: - Master device (optional). Source user.device.alias. + type: str tagging: description: - Config object tagging. + type: list suboptions: category: description: - Tag category. Source system.object-tagging.category. + type: str name: description: - Tagging entry name. required: true + type: str tags: description: - Tags. + type: list suboptions: name: description: - Tag name. Source system.object-tagging.tags.name. required: true + type: str type: description: - Device type. + type: str choices: - unknown - android-phone @@ -155,6 +172,7 @@ options: user: description: - User name. + type: str ''' EXAMPLES = ''' @@ -164,6 +182,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure devices. fortios_user_device: @@ -260,6 +279,7 @@ def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -267,7 +287,7 @@ def login(data, fos): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_user_device_data(json): @@ -334,13 +354,14 @@ def main(): fields = { "host": {"required": False, "type": "str"}, "username": {"required": False, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, "state": {"required": True, "type": "str", "choices": ["present", "absent"]}, "user_device": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { "alias": {"required": True, "type": "str"}, "avatar": {"required": False, "type": "str"}, @@ -369,6 +390,7 @@ def main(): "printer", "router-nat-device", "windows-pc", "windows-phone", "windows-tablet", "other-network-device"]}, "user": {"required": False, "type": "str"} + } } } @@ -376,6 +398,7 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) + # legacy_mode refers to using fortiosapi instead of HTTPAPI legacy_mode = 'host' in module.params and module.params['host'] is not None and \ 'username' in module.params and module.params['username'] is not None and \ 'password' in module.params and module.params['password'] is not None diff --git a/lib/ansible/modules/network/fortios/fortios_user_radius.py b/lib/ansible/modules/network/fortios/fortios_user_radius.py index 5194f8da180..a6913f146b8 100644 --- a/lib/ansible/modules/network/fortios/fortios_user_radius.py +++ b/lib/ansible/modules/network/fortios/fortios_user_radius.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_user_radius short_description: Configure RADIUS server entries in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify user feature and radius category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,84 +41,108 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 user_radius: description: - Configure RADIUS server entries. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - accounting-server: + accounting_server: description: - Additional accounting servers. + type: list suboptions: id: description: - ID (0 - 4294967295). required: true + type: int port: description: - RADIUS accounting port number. + type: int secret: description: - Secret key. + type: str server: description: - - Server CN domain name or IP. - source-ip: + - name_str or ip_str Server CN domain name or IP. + type: str + source_ip: description: - Source IP address for communications to the RADIUS server. + type: str status: description: - Status. + type: str choices: - enable - disable - acct-all-servers: + acct_all_servers: description: - - Enable/disable sending of accounting messages to all configured servers (default = disable). + - Enable/disable sending of accounting messages to all configured servers. + type: str choices: - enable - disable - acct-interim-interval: + acct_interim_interval: description: - Time in seconds between each accounting interim update message. - all-usergroup: + type: int + all_usergroup: description: - Enable/disable automatically including this RADIUS server in all user groups. + type: str choices: - disable - enable - auth-type: + auth_type: description: - Authentication methods/protocols permitted for this RADIUS server. + type: str choices: - auto - ms_chap_v2 @@ -131,14 +152,17 @@ options: class: description: - Class attribute name(s). + type: list suboptions: name: description: - Class name. required: true - h3c-compatibility: + type: str + h3c_compatibility: description: - Enable/disable compatibility with the H3C, a mechanism that performs security checking for authentication. + type: str choices: - enable - disable @@ -146,43 +170,52 @@ options: description: - RADIUS server entry name. required: true - nas-ip: + type: str + nas_ip: description: - IP address used to communicate with the RADIUS server and used as NAS-IP-Address and Called-Station-ID attributes. - password-encoding: + type: str + password_encoding: description: - Password encoding. + type: str choices: - auto - ISO-8859-1 - password-renewal: + password_renewal: description: - Enable/disable password renewal. + type: str choices: - enable - disable - radius-coa: + radius_coa: description: - Enable to allow a mechanism to change the attributes of an authentication, authorization, and accounting session after it is authenticated. + type: str choices: - enable - disable - radius-port: + radius_port: description: - RADIUS service port number. + type: int rsso: description: - Enable/disable RADIUS based single sign on feature. + type: str choices: - enable - disable - rsso-context-timeout: + rsso_context_timeout: description: - Time in seconds before the logged out user is removed from the "user context list" of logged on users. - rsso-endpoint-attribute: + type: int + rsso_endpoint_attribute: description: - RADIUS attributes used to extract the user end point identifer from the RADIUS Start record. + type: str choices: - User-Name - NAS-IP-Address @@ -206,9 +239,10 @@ options: - Framed-AppleTalk-Zone - Acct-Session-Id - Acct-Multi-Session-Id - rsso-endpoint-block-attribute: + rsso_endpoint_block_attribute: description: - RADIUS attributes used to block a user. + type: str choices: - User-Name - NAS-IP-Address @@ -232,21 +266,24 @@ options: - Framed-AppleTalk-Zone - Acct-Session-Id - Acct-Multi-Session-Id - rsso-ep-one-ip-only: + rsso_ep_one_ip_only: description: - Enable/disable the replacement of old IP addresses with new ones for the same endpoint on RADIUS accounting Start messages. + type: str choices: - enable - disable - rsso-flush-ip-session: + rsso_flush_ip_session: description: - Enable/disable flushing user IP sessions on RADIUS accounting Stop messages. + type: str choices: - enable - disable - rsso-log-flags: + rsso_log_flags: description: - Events to log. + type: str choices: - protocol-error - profile-missing @@ -255,45 +292,56 @@ options: - endpoint-block - radiusd-other - none - rsso-log-period: + rsso_log_period: description: - Time interval in seconds that group event log messages will be generated for dynamic profile events. - rsso-radius-response: + type: int + rsso_radius_response: description: - Enable/disable sending RADIUS response packets after receiving Start and Stop records. + type: str choices: - enable - disable - rsso-radius-server-port: + rsso_radius_server_port: description: - UDP port to listen on for RADIUS Start and Stop records. - rsso-secret: + type: int + rsso_secret: description: - RADIUS secret used by the RADIUS accounting server. - rsso-validate-request-secret: + type: str + rsso_validate_request_secret: description: - Enable/disable validating the RADIUS request shared secret in the Start or End record. + type: str choices: - enable - disable - secondary-secret: + secondary_secret: description: - Secret key to access the secondary server. - secondary-server: + type: str + secondary_server: description: - - Secondary RADIUS CN domain name or IP. + - name_str or ip_str secondary RADIUS CN domain name or IP. + type: str secret: description: - Pre-shared secret key used to access the primary RADIUS server. + type: str server: description: - Primary RADIUS server CN domain name or IP address. - source-ip: + type: str + source_ip: description: - Source IP address for communications to the RADIUS server. - sso-attribute: + type: str + sso_attribute: description: - RADIUS attribute that contains the profile group name to be extracted from the RADIUS Start record. + type: str choices: - User-Name - NAS-IP-Address @@ -317,33 +365,40 @@ options: - Framed-AppleTalk-Zone - Acct-Session-Id - Acct-Multi-Session-Id - sso-attribute-key: + sso_attribute_key: description: - Key prefix for SSO group value in the SSO attribute. - sso-attribute-value-override: + type: str + sso_attribute_value_override: description: - Enable/disable override old attribute value with new value for the same endpoint. + type: str choices: - enable - disable - tertiary-secret: + tertiary_secret: description: - Secret key to access the tertiary server. - tertiary-server: + type: str + tertiary_server: description: - - Tertiary RADIUS CN domain name or IP. + - name_str or ip_str tertiary RADIUS CN domain name or IP. + type: str timeout: description: - Time in seconds between re-sending authentication requests. - use-management-vdom: + type: int + use_management_vdom: description: - Enable/disable using management VDOM to send requests. + type: str choices: - enable - disable - username-case-sensitive: + username_case_sensitive: description: - Enable/disable case sensitive user names. + type: str choices: - enable - disable @@ -356,6 +411,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure RADIUS server entries. fortios_user_radius: @@ -364,55 +420,55 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" user_radius: - state: "present" - accounting-server: + accounting_server: - id: "4" port: "5" secret: "" server: "192.168.100.40" - source-ip: "84.230.14.43" + source_ip: "84.230.14.43" status: "enable" - acct-all-servers: "enable" - acct-interim-interval: "11" - all-usergroup: "disable" - auth-type: "auto" + acct_all_servers: "enable" + acct_interim_interval: "11" + all_usergroup: "disable" + auth_type: "auto" class: - name: "default_name_15" - h3c-compatibility: "enable" + h3c_compatibility: "enable" name: "default_name_17" - nas-ip: "" - password-encoding: "auto" - password-renewal: "enable" - radius-coa: "enable" - radius-port: "22" + nas_ip: "" + password_encoding: "auto" + password_renewal: "enable" + radius_coa: "enable" + radius_port: "22" rsso: "enable" - rsso-context-timeout: "24" - rsso-endpoint-attribute: "User-Name" - rsso-endpoint-block-attribute: "User-Name" - rsso-ep-one-ip-only: "enable" - rsso-flush-ip-session: "enable" - rsso-log-flags: "protocol-error" - rsso-log-period: "30" - rsso-radius-response: "enable" - rsso-radius-server-port: "32" - rsso-secret: "" - rsso-validate-request-secret: "enable" - secondary-secret: "" - secondary-server: "" + rsso_context_timeout: "24" + rsso_endpoint_attribute: "User-Name" + rsso_endpoint_block_attribute: "User-Name" + rsso_ep_one_ip_only: "enable" + rsso_flush_ip_session: "enable" + rsso_log_flags: "protocol-error" + rsso_log_period: "30" + rsso_radius_response: "enable" + rsso_radius_server_port: "32" + rsso_secret: "" + rsso_validate_request_secret: "enable" + secondary_secret: "" + secondary_server: "" secret: "" server: "192.168.100.40" - source-ip: "84.230.14.43" - sso-attribute: "User-Name" - sso-attribute-key: "" - sso-attribute-value-override: "enable" - tertiary-secret: "" - tertiary-server: "" + source_ip: "84.230.14.43" + sso_attribute: "User-Name" + sso_attribute_key: "" + sso_attribute_value_override: "enable" + tertiary_secret: "" + tertiary_server: "" timeout: "45" - use-management-vdom: "enable" - username-case-sensitive: "enable" + use_management_vdom: "enable" + username_case_sensitive: "enable" ''' RETURN = ''' @@ -475,14 +531,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule - -fos = None +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -490,23 +548,23 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_user_radius_data(json): - option_list = ['accounting-server', 'acct-all-servers', 'acct-interim-interval', - 'all-usergroup', 'auth-type', 'class', - 'h3c-compatibility', 'name', 'nas-ip', - 'password-encoding', 'password-renewal', 'radius-coa', - 'radius-port', 'rsso', 'rsso-context-timeout', - 'rsso-endpoint-attribute', 'rsso-endpoint-block-attribute', 'rsso-ep-one-ip-only', - 'rsso-flush-ip-session', 'rsso-log-flags', 'rsso-log-period', - 'rsso-radius-response', 'rsso-radius-server-port', 'rsso-secret', - 'rsso-validate-request-secret', 'secondary-secret', 'secondary-server', - 'secret', 'server', 'source-ip', - 'sso-attribute', 'sso-attribute-key', 'sso-attribute-value-override', - 'tertiary-secret', 'tertiary-server', 'timeout', - 'use-management-vdom', 'username-case-sensitive'] + option_list = ['accounting_server', 'acct_all_servers', 'acct_interim_interval', + 'all_usergroup', 'auth_type', 'class', + 'h3c_compatibility', 'name', 'nas_ip', + 'password_encoding', 'password_renewal', 'radius_coa', + 'radius_port', 'rsso', 'rsso_context_timeout', + 'rsso_endpoint_attribute', 'rsso_endpoint_block_attribute', 'rsso_ep_one_ip_only', + 'rsso_flush_ip_session', 'rsso_log_flags', 'rsso_log_period', + 'rsso_radius_response', 'rsso_radius_server_port', 'rsso_secret', + 'rsso_validate_request_secret', 'secondary_secret', 'secondary_server', + 'secret', 'server', 'source_ip', + 'sso_attribute', 'sso_attribute_key', 'sso_attribute_value_override', + 'tertiary_secret', 'tertiary_server', 'timeout', + 'use_management_vdom', 'username_case_sensitive'] dictionary = {} for attribute in option_list: @@ -516,98 +574,103 @@ def filter_user_radius_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def user_radius(data, fos): vdom = data['vdom'] + state = data['state'] user_radius_data = data['user_radius'] - flattened_data = flatten_multilists_attributes(user_radius_data) - filtered_data = filter_user_radius_data(flattened_data) - if user_radius_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_user_radius_data(user_radius_data)) + + if state == "present": return fos.set('user', 'radius', data=filtered_data, vdom=vdom) - elif user_radius_data['state'] == "absent": + elif state == "absent": return fos.delete('user', 'radius', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_user(data, fos): - login(data) if data['user_radius']: resp = user_radius(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "user_radius": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "accounting-server": {"required": False, "type": "list", + "accounting_server": {"required": False, "type": "list", "options": { "id": {"required": True, "type": "int"}, "port": {"required": False, "type": "int"}, "secret": {"required": False, "type": "str"}, "server": {"required": False, "type": "str"}, - "source-ip": {"required": False, "type": "str"}, + "source_ip": {"required": False, "type": "str"}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]} }}, - "acct-all-servers": {"required": False, "type": "str", + "acct_all_servers": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "acct-interim-interval": {"required": False, "type": "int"}, - "all-usergroup": {"required": False, "type": "str", + "acct_interim_interval": {"required": False, "type": "int"}, + "all_usergroup": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "auth-type": {"required": False, "type": "str", + "auth_type": {"required": False, "type": "str", "choices": ["auto", "ms_chap_v2", "ms_chap", "chap", "pap"]}, "class": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "h3c-compatibility": {"required": False, "type": "str", + "h3c_compatibility": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "name": {"required": True, "type": "str"}, - "nas-ip": {"required": False, "type": "str"}, - "password-encoding": {"required": False, "type": "str", + "nas_ip": {"required": False, "type": "str"}, + "password_encoding": {"required": False, "type": "str", "choices": ["auto", "ISO-8859-1"]}, - "password-renewal": {"required": False, "type": "str", + "password_renewal": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "radius-coa": {"required": False, "type": "str", + "radius_coa": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "radius-port": {"required": False, "type": "int"}, + "radius_port": {"required": False, "type": "int"}, "rsso": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "rsso-context-timeout": {"required": False, "type": "int"}, - "rsso-endpoint-attribute": {"required": False, "type": "str", + "rsso_context_timeout": {"required": False, "type": "int"}, + "rsso_endpoint_attribute": {"required": False, "type": "str", "choices": ["User-Name", "NAS-IP-Address", "Framed-IP-Address", "Framed-IP-Netmask", "Filter-Id", "Login-IP-Host", "Reply-Message", "Callback-Number", "Callback-Id", @@ -616,7 +679,7 @@ def main(): "Proxy-State", "Login-LAT-Service", "Login-LAT-Node", "Login-LAT-Group", "Framed-AppleTalk-Zone", "Acct-Session-Id", "Acct-Multi-Session-Id"]}, - "rsso-endpoint-block-attribute": {"required": False, "type": "str", + "rsso_endpoint_block_attribute": {"required": False, "type": "str", "choices": ["User-Name", "NAS-IP-Address", "Framed-IP-Address", "Framed-IP-Netmask", "Filter-Id", "Login-IP-Host", "Reply-Message", "Callback-Number", "Callback-Id", @@ -625,27 +688,27 @@ def main(): "Proxy-State", "Login-LAT-Service", "Login-LAT-Node", "Login-LAT-Group", "Framed-AppleTalk-Zone", "Acct-Session-Id", "Acct-Multi-Session-Id"]}, - "rsso-ep-one-ip-only": {"required": False, "type": "str", + "rsso_ep_one_ip_only": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "rsso-flush-ip-session": {"required": False, "type": "str", + "rsso_flush_ip_session": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "rsso-log-flags": {"required": False, "type": "str", + "rsso_log_flags": {"required": False, "type": "str", "choices": ["protocol-error", "profile-missing", "accounting-stop-missed", "accounting-event", "endpoint-block", "radiusd-other", "none"]}, - "rsso-log-period": {"required": False, "type": "int"}, - "rsso-radius-response": {"required": False, "type": "str", + "rsso_log_period": {"required": False, "type": "int"}, + "rsso_radius_response": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "rsso-radius-server-port": {"required": False, "type": "int"}, - "rsso-secret": {"required": False, "type": "str"}, - "rsso-validate-request-secret": {"required": False, "type": "str", + "rsso_radius_server_port": {"required": False, "type": "int"}, + "rsso_secret": {"required": False, "type": "str"}, + "rsso_validate_request_secret": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "secondary-secret": {"required": False, "type": "str"}, - "secondary-server": {"required": False, "type": "str"}, + "secondary_secret": {"required": False, "type": "str"}, + "secondary_server": {"required": False, "type": "str"}, "secret": {"required": False, "type": "str"}, "server": {"required": False, "type": "str"}, - "source-ip": {"required": False, "type": "str"}, - "sso-attribute": {"required": False, "type": "str", + "source_ip": {"required": False, "type": "str"}, + "sso_attribute": {"required": False, "type": "str", "choices": ["User-Name", "NAS-IP-Address", "Framed-IP-Address", "Framed-IP-Netmask", "Filter-Id", "Login-IP-Host", "Reply-Message", "Callback-Number", "Callback-Id", @@ -654,15 +717,15 @@ def main(): "Proxy-State", "Login-LAT-Service", "Login-LAT-Node", "Login-LAT-Group", "Framed-AppleTalk-Zone", "Acct-Session-Id", "Acct-Multi-Session-Id"]}, - "sso-attribute-key": {"required": False, "type": "str"}, - "sso-attribute-value-override": {"required": False, "type": "str", + "sso_attribute_key": {"required": False, "type": "str"}, + "sso_attribute_value_override": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tertiary-secret": {"required": False, "type": "str"}, - "tertiary-server": {"required": False, "type": "str"}, + "tertiary_secret": {"required": False, "type": "str"}, + "tertiary_server": {"required": False, "type": "str"}, "timeout": {"required": False, "type": "int"}, - "use-management-vdom": {"required": False, "type": "str", + "use_management_vdom": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "username-case-sensitive": {"required": False, "type": "str", + "username_case_sensitive": {"required": False, "type": "str", "choices": ["enable", "disable"]} } @@ -671,15 +734,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_user(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_user(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_user(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_user_tacacsplus.py b/lib/ansible/modules/network/fortios/fortios_user_tacacsplus.py index c24019b449b..085e1867379 100644 --- a/lib/ansible/modules/network/fortios/fortios_user_tacacsplus.py +++ b/lib/ansible/modules/network/fortios/fortios_user_tacacsplus.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_user_tacacsplus short_description: Configure TACACS+ server entries in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by - allowing the user to configure user feature and tacacsplus category. - Examples includes all options and need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the + user to set and modify user feature and tacacsplus category. + Examples include all parameters and values need to be adjusted to datasources before usage. + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,43 +41,57 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip adress. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 user_tacacsplus: description: - Configure TACACS+ server entries. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - authen-type: + authen_type: description: - Allowed authentication protocols/methods. + type: str choices: - mschap - chap @@ -90,37 +101,47 @@ options: authorization: description: - Enable/disable TACACS+ authorization. + type: str choices: - enable - disable key: description: - Key to access the primary server. + type: str name: description: - TACACS+ server entry name. required: true + type: str port: description: - Port number of the TACACS+ server. - secondary-key: + type: int + secondary_key: description: - Key to access the secondary server. - secondary-server: + type: str + secondary_server: description: - Secondary TACACS+ server CN domain name or IP address. + type: str server: description: - Primary TACACS+ server CN domain name or IP address. - source-ip: + type: str + source_ip: description: - source IP for communications to TACACS+ server. - tertiary-key: + type: str + tertiary_key: description: - Key to access the tertiary server. - tertiary-server: + type: str + tertiary_server: description: - Tertiary TACACS+ server CN domain name or IP address. + type: str ''' EXAMPLES = ''' @@ -130,6 +151,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure TACACS+ server entries. fortios_user_tacacsplus: @@ -138,19 +160,19 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" user_tacacsplus: - state: "present" - authen-type: "mschap" + authen_type: "mschap" authorization: "enable" key: "" name: "default_name_6" port: "7" - secondary-key: "" - secondary-server: "" + secondary_key: "" + secondary_server: "" server: "192.168.100.40" - source-ip: "84.230.14.43" - tertiary-key: "" - tertiary-server: "" + source_ip: "84.230.14.43" + tertiary_key: "" + tertiary_server: "" ''' RETURN = ''' @@ -213,14 +235,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule - -fos = None +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -228,14 +252,14 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_user_tacacsplus_data(json): - option_list = ['authen-type', 'authorization', 'key', - 'name', 'port', 'secondary-key', - 'secondary-server', 'server', 'source-ip', - 'tertiary-key', 'tertiary-server'] + option_list = ['authen_type', 'authorization', 'key', + 'name', 'port', 'secondary_key', + 'secondary_server', 'server', 'source_ip', + 'tertiary_key', 'tertiary_server'] dictionary = {} for attribute in option_list: @@ -245,49 +269,67 @@ def filter_user_tacacsplus_data(json): return dictionary +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data + + return data + + def user_tacacsplus(data, fos): vdom = data['vdom'] + state = data['state'] user_tacacsplus_data = data['user_tacacsplus'] - filtered_data = filter_user_tacacsplus_data(user_tacacsplus_data) - if user_tacacsplus_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_user_tacacsplus_data(user_tacacsplus_data)) + + if state == "present": return fos.set('user', 'tacacs+', data=filtered_data, vdom=vdom) - elif user_tacacsplus_data['state'] == "absent": + elif state == "absent": return fos.delete('user', 'tacacs+', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_user(data, fos): - login(data) - methodlist = ['user_tacacsplus'] - for method in methodlist: - if data[method]: - resp = eval(method)(data, fos) - break + if data['user_tacacsplus']: + resp = user_tacacsplus(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "user_tacacsplus": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "authen-type": {"required": False, "type": "str", + "authen_type": {"required": False, "type": "str", "choices": ["mschap", "chap", "pap", "ascii", "auto"]}, "authorization": {"required": False, "type": "str", @@ -295,12 +337,12 @@ def main(): "key": {"required": False, "type": "str"}, "name": {"required": True, "type": "str"}, "port": {"required": False, "type": "int"}, - "secondary-key": {"required": False, "type": "str"}, - "secondary-server": {"required": False, "type": "str"}, + "secondary_key": {"required": False, "type": "str"}, + "secondary_server": {"required": False, "type": "str"}, "server": {"required": False, "type": "str"}, - "source-ip": {"required": False, "type": "str"}, - "tertiary-key": {"required": False, "type": "str"}, - "tertiary-server": {"required": False, "type": "str"} + "source_ip": {"required": False, "type": "str"}, + "tertiary_key": {"required": False, "type": "str"}, + "tertiary_server": {"required": False, "type": "str"} } } @@ -308,15 +350,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_user(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_user(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_user(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_voip_profile.py b/lib/ansible/modules/network/fortios/fortios_voip_profile.py index bcb85c15d0b..1a54492474e 100644 --- a/lib/ansible/modules/network/fortios/fortios_voip_profile.py +++ b/lib/ansible/modules/network/fortios/fortios_voip_profile.py @@ -26,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_voip_profile short_description: Configure VoIP profiles in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify voip feature and profile category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -41,629 +41,748 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 voip_profile: description: - Configure VoIP profiles. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent comment: description: - Comment. + type: str name: description: - Profile name. required: true + type: str sccp: description: - SCCP. + type: dict suboptions: - block-mcast: + block_mcast: description: - Enable/disable block multicast RTP connections. + type: str choices: - disable - enable - log-call-summary: + log_call_summary: description: - Enable/disable log summary of SCCP calls. + type: str choices: - disable - enable - log-violations: + log_violations: description: - Enable/disable logging of SCCP violations. + type: str choices: - disable - enable - max-calls: + max_calls: description: - Maximum calls per minute per SCCP client (max 65535). + type: int status: description: - Enable/disable SCCP. + type: str choices: - disable - enable - verify-header: + verify_header: description: - Enable/disable verify SCCP header content. + type: str choices: - disable - enable sip: description: - SIP. + type: dict suboptions: - ack-rate: + ack_rate: description: - ACK request rate limit (per second, per policy). - block-ack: + type: int + block_ack: description: - Enable/disable block ACK requests. + type: str choices: - disable - enable - block-bye: + block_bye: description: - Enable/disable block BYE requests. + type: str choices: - disable - enable - block-cancel: + block_cancel: description: - Enable/disable block CANCEL requests. + type: str choices: - disable - enable - block-geo-red-options: + block_geo_red_options: description: - Enable/disable block OPTIONS requests, but OPTIONS requests still notify for redundancy. + type: str choices: - disable - enable - block-info: + block_info: description: - Enable/disable block INFO requests. + type: str choices: - disable - enable - block-invite: + block_invite: description: - Enable/disable block INVITE requests. + type: str choices: - disable - enable - block-long-lines: + block_long_lines: description: - Enable/disable block requests with headers exceeding max-line-length. + type: str choices: - disable - enable - block-message: + block_message: description: - Enable/disable block MESSAGE requests. + type: str choices: - disable - enable - block-notify: + block_notify: description: - Enable/disable block NOTIFY requests. + type: str choices: - disable - enable - block-options: + block_options: description: - Enable/disable block OPTIONS requests and no OPTIONS as notifying message for redundancy either. + type: str choices: - disable - enable - block-prack: + block_prack: description: - Enable/disable block prack requests. + type: str choices: - disable - enable - block-publish: + block_publish: description: - Enable/disable block PUBLISH requests. + type: str choices: - disable - enable - block-refer: + block_refer: description: - Enable/disable block REFER requests. + type: str choices: - disable - enable - block-register: + block_register: description: - Enable/disable block REGISTER requests. + type: str choices: - disable - enable - block-subscribe: + block_subscribe: description: - Enable/disable block SUBSCRIBE requests. + type: str choices: - disable - enable - block-unknown: + block_unknown: description: - Block unrecognized SIP requests (enabled by default). + type: str choices: - disable - enable - block-update: + block_update: description: - Enable/disable block UPDATE requests. + type: str choices: - disable - enable - bye-rate: + bye_rate: description: - BYE request rate limit (per second, per policy). - call-keepalive: + type: int + call_keepalive: description: - Continue tracking calls with no RTP for this many minutes. - cancel-rate: + type: int + cancel_rate: description: - CANCEL request rate limit (per second, per policy). - contact-fixup: + type: int + contact_fixup: description: - "Fixup contact anyway even if contact's IP:port doesn't match session's IP:port." + type: str choices: - disable - enable - hnt-restrict-source-ip: + hnt_restrict_source_ip: description: - Enable/disable restrict RTP source IP to be the same as SIP source IP when HNT is enabled. + type: str choices: - disable - enable - hosted-nat-traversal: + hosted_nat_traversal: description: - Hosted NAT Traversal (HNT). + type: str choices: - disable - enable - info-rate: + info_rate: description: - INFO request rate limit (per second, per policy). - invite-rate: + type: int + invite_rate: description: - INVITE request rate limit (per second, per policy). - ips-rtp: + type: int + ips_rtp: description: - Enable/disable allow IPS on RTP. + type: str choices: - disable - enable - log-call-summary: + log_call_summary: description: - Enable/disable logging of SIP call summary. + type: str choices: - disable - enable - log-violations: + log_violations: description: - Enable/disable logging of SIP violations. + type: str choices: - disable - enable - malformed-header-allow: + malformed_header_allow: description: - Action for malformed Allow header. + type: str choices: - discard - pass - respond - malformed-header-call-id: + malformed_header_call_id: description: - Action for malformed Call-ID header. + type: str choices: - discard - pass - respond - malformed-header-contact: + malformed_header_contact: description: - Action for malformed Contact header. + type: str choices: - discard - pass - respond - malformed-header-content-length: + malformed_header_content_length: description: - Action for malformed Content-Length header. + type: str choices: - discard - pass - respond - malformed-header-content-type: + malformed_header_content_type: description: - Action for malformed Content-Type header. + type: str choices: - discard - pass - respond - malformed-header-cseq: + malformed_header_cseq: description: - Action for malformed CSeq header. + type: str choices: - discard - pass - respond - malformed-header-expires: + malformed_header_expires: description: - Action for malformed Expires header. + type: str choices: - discard - pass - respond - malformed-header-from: + malformed_header_from: description: - Action for malformed From header. + type: str choices: - discard - pass - respond - malformed-header-max-forwards: + malformed_header_max_forwards: description: - Action for malformed Max-Forwards header. + type: str choices: - discard - pass - respond - malformed-header-p-asserted-identity: + malformed_header_p_asserted_identity: description: - Action for malformed P-Asserted-Identity header. + type: str choices: - discard - pass - respond - malformed-header-rack: + malformed_header_rack: description: - Action for malformed RAck header. + type: str choices: - discard - pass - respond - malformed-header-record-route: + malformed_header_record_route: description: - Action for malformed Record-Route header. + type: str choices: - discard - pass - respond - malformed-header-route: + malformed_header_route: description: - Action for malformed Route header. + type: str choices: - discard - pass - respond - malformed-header-rseq: + malformed_header_rseq: description: - Action for malformed RSeq header. + type: str choices: - discard - pass - respond - malformed-header-sdp-a: + malformed_header_sdp_a: description: - Action for malformed SDP a line. + type: str choices: - discard - pass - respond - malformed-header-sdp-b: + malformed_header_sdp_b: description: - Action for malformed SDP b line. + type: str choices: - discard - pass - respond - malformed-header-sdp-c: + malformed_header_sdp_c: description: - Action for malformed SDP c line. + type: str choices: - discard - pass - respond - malformed-header-sdp-i: + malformed_header_sdp_i: description: - Action for malformed SDP i line. + type: str choices: - discard - pass - respond - malformed-header-sdp-k: + malformed_header_sdp_k: description: - Action for malformed SDP k line. + type: str choices: - discard - pass - respond - malformed-header-sdp-m: + malformed_header_sdp_m: description: - Action for malformed SDP m line. + type: str choices: - discard - pass - respond - malformed-header-sdp-o: + malformed_header_sdp_o: description: - Action for malformed SDP o line. + type: str choices: - discard - pass - respond - malformed-header-sdp-r: + malformed_header_sdp_r: description: - Action for malformed SDP r line. + type: str choices: - discard - pass - respond - malformed-header-sdp-s: + malformed_header_sdp_s: description: - Action for malformed SDP s line. + type: str choices: - discard - pass - respond - malformed-header-sdp-t: + malformed_header_sdp_t: description: - Action for malformed SDP t line. + type: str choices: - discard - pass - respond - malformed-header-sdp-v: + malformed_header_sdp_v: description: - Action for malformed SDP v line. + type: str choices: - discard - pass - respond - malformed-header-sdp-z: + malformed_header_sdp_z: description: - Action for malformed SDP z line. + type: str choices: - discard - pass - respond - malformed-header-to: + malformed_header_to: description: - Action for malformed To header. + type: str choices: - discard - pass - respond - malformed-header-via: + malformed_header_via: description: - Action for malformed VIA header. + type: str choices: - discard - pass - respond - malformed-request-line: + malformed_request_line: description: - Action for malformed request line. + type: str choices: - discard - pass - respond - max-body-length: + max_body_length: description: - Maximum SIP message body length (0 meaning no limit). - max-dialogs: + type: int + max_dialogs: description: - Maximum number of concurrent calls/dialogs (per policy). - max-idle-dialogs: + type: int + max_idle_dialogs: description: - Maximum number established but idle dialogs to retain (per policy). - max-line-length: + type: int + max_line_length: description: - Maximum SIP header line length (78-4096). - message-rate: + type: int + message_rate: description: - MESSAGE request rate limit (per second, per policy). - nat-trace: + type: int + nat_trace: description: - Enable/disable preservation of original IP in SDP i line. + type: str choices: - disable - enable - no-sdp-fixup: + no_sdp_fixup: description: - Enable/disable no SDP fix-up. + type: str choices: - disable - enable - notify-rate: + notify_rate: description: - NOTIFY request rate limit (per second, per policy). - open-contact-pinhole: + type: int + open_contact_pinhole: description: - Enable/disable open pinhole for non-REGISTER Contact port. + type: str choices: - disable - enable - open-record-route-pinhole: + open_record_route_pinhole: description: - Enable/disable open pinhole for Record-Route port. + type: str choices: - disable - enable - open-register-pinhole: + open_register_pinhole: description: - Enable/disable open pinhole for REGISTER Contact port. + type: str choices: - disable - enable - open-via-pinhole: + open_via_pinhole: description: - Enable/disable open pinhole for Via port. + type: str choices: - disable - enable - options-rate: + options_rate: description: - OPTIONS request rate limit (per second, per policy). - prack-rate: + type: int + prack_rate: description: - PRACK request rate limit (per second, per policy). - preserve-override: + type: int + preserve_override: description: - - "Override i line to preserve original IPS (default: append)." + - "Override i line to preserve original IPS ." + type: str choices: - disable - enable - provisional-invite-expiry-time: + provisional_invite_expiry_time: description: - Expiry time for provisional INVITE (10 - 3600 sec). - publish-rate: + type: int + publish_rate: description: - PUBLISH request rate limit (per second, per policy). - refer-rate: + type: int + refer_rate: description: - REFER request rate limit (per second, per policy). - register-contact-trace: + type: int + register_contact_trace: description: - Enable/disable trace original IP/port within the contact header of REGISTER requests. + type: str choices: - disable - enable - register-rate: + register_rate: description: - REGISTER request rate limit (per second, per policy). - rfc2543-branch: + type: int + rfc2543_branch: description: - Enable/disable support via branch compliant with RFC 2543. + type: str choices: - disable - enable rtp: description: - Enable/disable create pinholes for RTP traffic to traverse firewall. + type: str choices: - disable - enable - ssl-algorithm: + ssl_algorithm: description: - Relative strength of encryption algorithms accepted in negotiation. + type: str choices: - high - medium - low - ssl-auth-client: + ssl_auth_client: description: - Require a client certificate and authenticate it with the peer/peergrp. Source user.peer.name user.peergrp.name. - ssl-auth-server: + type: str + ssl_auth_server: description: - Authenticate the server's certificate with the peer/peergrp. Source user.peer.name user.peergrp.name. - ssl-client-certificate: + type: str + ssl_client_certificate: description: - Name of Certificate to offer to server if requested. Source vpn.certificate.local.name. - ssl-client-renegotiation: + type: str + ssl_client_renegotiation: description: - Allow/block client renegotiation by server. + type: str choices: - allow - deny - secure - ssl-max-version: + ssl_max_version: description: - Highest SSL/TLS version to negotiate. + type: str choices: - ssl-3.0 - tls-1.0 - tls-1.1 - tls-1.2 - ssl-min-version: + ssl_min_version: description: - Lowest SSL/TLS version to negotiate. + type: str choices: - ssl-3.0 - tls-1.0 - tls-1.1 - tls-1.2 - ssl-mode: + ssl_mode: description: - SSL/TLS mode for encryption & decryption of traffic. + type: str choices: - off - full - ssl-pfs: + ssl_pfs: description: - SSL Perfect Forward Secrecy. + type: str choices: - require - deny - allow - ssl-send-empty-frags: + ssl_send_empty_frags: description: - Send empty fragments to avoid attack on CBC IV (SSL 3.0 & TLS 1.0 only). + type: str choices: - enable - disable - ssl-server-certificate: + ssl_server_certificate: description: - Name of Certificate return to the client in every SSL connection. Source vpn.certificate.local.name. + type: str status: description: - Enable/disable SIP. + type: str choices: - disable - enable - strict-register: + strict_register: description: - Enable/disable only allow the registrar to connect. + type: str choices: - disable - enable - subscribe-rate: + subscribe_rate: description: - SUBSCRIBE request rate limit (per second, per policy). - unknown-header: + type: int + unknown_header: description: - Action for unknown SIP header. + type: str choices: - discard - pass - respond - update-rate: + update_rate: description: - UPDATE request rate limit (per second, per policy). + type: int ''' EXAMPLES = ''' @@ -673,6 +792,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure VoIP profiles. fortios_voip_profile: @@ -681,114 +801,114 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" voip_profile: - state: "present" comment: "Comment." name: "default_name_4" sccp: - block-mcast: "disable" - log-call-summary: "disable" - log-violations: "disable" - max-calls: "9" + block_mcast: "disable" + log_call_summary: "disable" + log_violations: "disable" + max_calls: "9" status: "disable" - verify-header: "disable" + verify_header: "disable" sip: - ack-rate: "13" - block-ack: "disable" - block-bye: "disable" - block-cancel: "disable" - block-geo-red-options: "disable" - block-info: "disable" - block-invite: "disable" - block-long-lines: "disable" - block-message: "disable" - block-notify: "disable" - block-options: "disable" - block-prack: "disable" - block-publish: "disable" - block-refer: "disable" - block-register: "disable" - block-subscribe: "disable" - block-unknown: "disable" - block-update: "disable" - bye-rate: "31" - call-keepalive: "32" - cancel-rate: "33" - contact-fixup: "disable" - hnt-restrict-source-ip: "disable" - hosted-nat-traversal: "disable" - info-rate: "37" - invite-rate: "38" - ips-rtp: "disable" - log-call-summary: "disable" - log-violations: "disable" - malformed-header-allow: "discard" - malformed-header-call-id: "discard" - malformed-header-contact: "discard" - malformed-header-content-length: "discard" - malformed-header-content-type: "discard" - malformed-header-cseq: "discard" - malformed-header-expires: "discard" - malformed-header-from: "discard" - malformed-header-max-forwards: "discard" - malformed-header-p-asserted-identity: "discard" - malformed-header-rack: "discard" - malformed-header-record-route: "discard" - malformed-header-route: "discard" - malformed-header-rseq: "discard" - malformed-header-sdp-a: "discard" - malformed-header-sdp-b: "discard" - malformed-header-sdp-c: "discard" - malformed-header-sdp-i: "discard" - malformed-header-sdp-k: "discard" - malformed-header-sdp-m: "discard" - malformed-header-sdp-o: "discard" - malformed-header-sdp-r: "discard" - malformed-header-sdp-s: "discard" - malformed-header-sdp-t: "discard" - malformed-header-sdp-v: "discard" - malformed-header-sdp-z: "discard" - malformed-header-to: "discard" - malformed-header-via: "discard" - malformed-request-line: "discard" - max-body-length: "71" - max-dialogs: "72" - max-idle-dialogs: "73" - max-line-length: "74" - message-rate: "75" - nat-trace: "disable" - no-sdp-fixup: "disable" - notify-rate: "78" - open-contact-pinhole: "disable" - open-record-route-pinhole: "disable" - open-register-pinhole: "disable" - open-via-pinhole: "disable" - options-rate: "83" - prack-rate: "84" - preserve-override: "disable" - provisional-invite-expiry-time: "86" - publish-rate: "87" - refer-rate: "88" - register-contact-trace: "disable" - register-rate: "90" - rfc2543-branch: "disable" + ack_rate: "13" + block_ack: "disable" + block_bye: "disable" + block_cancel: "disable" + block_geo_red_options: "disable" + block_info: "disable" + block_invite: "disable" + block_long_lines: "disable" + block_message: "disable" + block_notify: "disable" + block_options: "disable" + block_prack: "disable" + block_publish: "disable" + block_refer: "disable" + block_register: "disable" + block_subscribe: "disable" + block_unknown: "disable" + block_update: "disable" + bye_rate: "31" + call_keepalive: "32" + cancel_rate: "33" + contact_fixup: "disable" + hnt_restrict_source_ip: "disable" + hosted_nat_traversal: "disable" + info_rate: "37" + invite_rate: "38" + ips_rtp: "disable" + log_call_summary: "disable" + log_violations: "disable" + malformed_header_allow: "discard" + malformed_header_call_id: "discard" + malformed_header_contact: "discard" + malformed_header_content_length: "discard" + malformed_header_content_type: "discard" + malformed_header_cseq: "discard" + malformed_header_expires: "discard" + malformed_header_from: "discard" + malformed_header_max_forwards: "discard" + malformed_header_p_asserted_identity: "discard" + malformed_header_rack: "discard" + malformed_header_record_route: "discard" + malformed_header_route: "discard" + malformed_header_rseq: "discard" + malformed_header_sdp_a: "discard" + malformed_header_sdp_b: "discard" + malformed_header_sdp_c: "discard" + malformed_header_sdp_i: "discard" + malformed_header_sdp_k: "discard" + malformed_header_sdp_m: "discard" + malformed_header_sdp_o: "discard" + malformed_header_sdp_r: "discard" + malformed_header_sdp_s: "discard" + malformed_header_sdp_t: "discard" + malformed_header_sdp_v: "discard" + malformed_header_sdp_z: "discard" + malformed_header_to: "discard" + malformed_header_via: "discard" + malformed_request_line: "discard" + max_body_length: "71" + max_dialogs: "72" + max_idle_dialogs: "73" + max_line_length: "74" + message_rate: "75" + nat_trace: "disable" + no_sdp_fixup: "disable" + notify_rate: "78" + open_contact_pinhole: "disable" + open_record_route_pinhole: "disable" + open_register_pinhole: "disable" + open_via_pinhole: "disable" + options_rate: "83" + prack_rate: "84" + preserve_override: "disable" + provisional_invite_expiry_time: "86" + publish_rate: "87" + refer_rate: "88" + register_contact_trace: "disable" + register_rate: "90" + rfc2543_branch: "disable" rtp: "disable" - ssl-algorithm: "high" - ssl-auth-client: " (source user.peer.name user.peergrp.name)" - ssl-auth-server: " (source user.peer.name user.peergrp.name)" - ssl-client-certificate: " (source vpn.certificate.local.name)" - ssl-client-renegotiation: "allow" - ssl-max-version: "ssl-3.0" - ssl-min-version: "ssl-3.0" - ssl-mode: "off" - ssl-pfs: "require" - ssl-send-empty-frags: "enable" - ssl-server-certificate: " (source vpn.certificate.local.name)" + ssl_algorithm: "high" + ssl_auth_client: " (source user.peer.name user.peergrp.name)" + ssl_auth_server: " (source user.peer.name user.peergrp.name)" + ssl_client_certificate: " (source vpn.certificate.local.name)" + ssl_client_renegotiation: "allow" + ssl_max_version: "ssl-3.0" + ssl_min_version: "ssl-3.0" + ssl_mode: "off" + ssl_pfs: "require" + ssl_send_empty_frags: "enable" + ssl_server_certificate: " (source vpn.certificate.local.name)" status: "disable" - strict-register: "disable" - subscribe-rate: "106" - unknown-header: "discard" - update-rate: "108" + strict_register: "disable" + subscribe_rate: "106" + unknown_header: "discard" + update_rate: "108" ''' RETURN = ''' @@ -851,12 +971,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -864,7 +988,7 @@ def login(data, fos): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_voip_profile_data(json): @@ -879,234 +1003,254 @@ def filter_voip_profile_data(json): return dictionary +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data + + return data + + def voip_profile(data, fos): vdom = data['vdom'] + state = data['state'] voip_profile_data = data['voip_profile'] - filtered_data = filter_voip_profile_data(voip_profile_data) + filtered_data = underscore_to_hyphen(filter_voip_profile_data(voip_profile_data)) - if voip_profile_data['state'] == "present": + if state == "present": return fos.set('voip', 'profile', data=filtered_data, vdom=vdom) - elif voip_profile_data['state'] == "absent": + elif state == "absent": return fos.delete('voip', 'profile', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_voip(data, fos): - login(data, fos) if data['voip_profile']: resp = voip_profile(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "voip_profile": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, "comment": {"required": False, "type": "str"}, "name": {"required": True, "type": "str"}, "sccp": {"required": False, "type": "dict", "options": { - "block-mcast": {"required": False, "type": "str", + "block_mcast": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "log-call-summary": {"required": False, "type": "str", + "log_call_summary": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "log-violations": {"required": False, "type": "str", + "log_violations": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "max-calls": {"required": False, "type": "int"}, + "max_calls": {"required": False, "type": "int"}, "status": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "verify-header": {"required": False, "type": "str", + "verify_header": {"required": False, "type": "str", "choices": ["disable", "enable"]} }}, "sip": {"required": False, "type": "dict", "options": { - "ack-rate": {"required": False, "type": "int"}, - "block-ack": {"required": False, "type": "str", + "ack_rate": {"required": False, "type": "int"}, + "block_ack": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-bye": {"required": False, "type": "str", + "block_bye": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-cancel": {"required": False, "type": "str", + "block_cancel": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-geo-red-options": {"required": False, "type": "str", + "block_geo_red_options": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-info": {"required": False, "type": "str", + "block_info": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-invite": {"required": False, "type": "str", + "block_invite": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-long-lines": {"required": False, "type": "str", + "block_long_lines": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-message": {"required": False, "type": "str", + "block_message": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-notify": {"required": False, "type": "str", + "block_notify": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-options": {"required": False, "type": "str", + "block_options": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-prack": {"required": False, "type": "str", + "block_prack": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-publish": {"required": False, "type": "str", + "block_publish": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-refer": {"required": False, "type": "str", + "block_refer": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-register": {"required": False, "type": "str", + "block_register": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-subscribe": {"required": False, "type": "str", + "block_subscribe": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-unknown": {"required": False, "type": "str", + "block_unknown": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "block-update": {"required": False, "type": "str", + "block_update": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "bye-rate": {"required": False, "type": "int"}, - "call-keepalive": {"required": False, "type": "int"}, - "cancel-rate": {"required": False, "type": "int"}, - "contact-fixup": {"required": False, "type": "str", + "bye_rate": {"required": False, "type": "int"}, + "call_keepalive": {"required": False, "type": "int"}, + "cancel_rate": {"required": False, "type": "int"}, + "contact_fixup": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "hnt-restrict-source-ip": {"required": False, "type": "str", + "hnt_restrict_source_ip": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "hosted-nat-traversal": {"required": False, "type": "str", + "hosted_nat_traversal": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "info-rate": {"required": False, "type": "int"}, - "invite-rate": {"required": False, "type": "int"}, - "ips-rtp": {"required": False, "type": "str", + "info_rate": {"required": False, "type": "int"}, + "invite_rate": {"required": False, "type": "int"}, + "ips_rtp": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "log-call-summary": {"required": False, "type": "str", + "log_call_summary": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "log-violations": {"required": False, "type": "str", + "log_violations": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "malformed-header-allow": {"required": False, "type": "str", + "malformed_header_allow": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-call-id": {"required": False, "type": "str", + "malformed_header_call_id": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-contact": {"required": False, "type": "str", + "malformed_header_contact": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-content-length": {"required": False, "type": "str", + "malformed_header_content_length": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-content-type": {"required": False, "type": "str", + "malformed_header_content_type": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-cseq": {"required": False, "type": "str", + "malformed_header_cseq": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-expires": {"required": False, "type": "str", + "malformed_header_expires": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-from": {"required": False, "type": "str", + "malformed_header_from": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-max-forwards": {"required": False, "type": "str", + "malformed_header_max_forwards": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-p-asserted-identity": {"required": False, "type": "str", + "malformed_header_p_asserted_identity": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-rack": {"required": False, "type": "str", + "malformed_header_rack": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-record-route": {"required": False, "type": "str", + "malformed_header_record_route": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-route": {"required": False, "type": "str", + "malformed_header_route": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-rseq": {"required": False, "type": "str", + "malformed_header_rseq": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-a": {"required": False, "type": "str", + "malformed_header_sdp_a": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-b": {"required": False, "type": "str", + "malformed_header_sdp_b": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-c": {"required": False, "type": "str", + "malformed_header_sdp_c": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-i": {"required": False, "type": "str", + "malformed_header_sdp_i": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-k": {"required": False, "type": "str", + "malformed_header_sdp_k": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-m": {"required": False, "type": "str", + "malformed_header_sdp_m": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-o": {"required": False, "type": "str", + "malformed_header_sdp_o": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-r": {"required": False, "type": "str", + "malformed_header_sdp_r": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-s": {"required": False, "type": "str", + "malformed_header_sdp_s": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-t": {"required": False, "type": "str", + "malformed_header_sdp_t": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-v": {"required": False, "type": "str", + "malformed_header_sdp_v": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-sdp-z": {"required": False, "type": "str", + "malformed_header_sdp_z": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-to": {"required": False, "type": "str", + "malformed_header_to": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-header-via": {"required": False, "type": "str", + "malformed_header_via": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "malformed-request-line": {"required": False, "type": "str", + "malformed_request_line": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "max-body-length": {"required": False, "type": "int"}, - "max-dialogs": {"required": False, "type": "int"}, - "max-idle-dialogs": {"required": False, "type": "int"}, - "max-line-length": {"required": False, "type": "int"}, - "message-rate": {"required": False, "type": "int"}, - "nat-trace": {"required": False, "type": "str", + "max_body_length": {"required": False, "type": "int"}, + "max_dialogs": {"required": False, "type": "int"}, + "max_idle_dialogs": {"required": False, "type": "int"}, + "max_line_length": {"required": False, "type": "int"}, + "message_rate": {"required": False, "type": "int"}, + "nat_trace": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "no-sdp-fixup": {"required": False, "type": "str", + "no_sdp_fixup": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "notify-rate": {"required": False, "type": "int"}, - "open-contact-pinhole": {"required": False, "type": "str", + "notify_rate": {"required": False, "type": "int"}, + "open_contact_pinhole": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "open-record-route-pinhole": {"required": False, "type": "str", + "open_record_route_pinhole": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "open-register-pinhole": {"required": False, "type": "str", + "open_register_pinhole": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "open-via-pinhole": {"required": False, "type": "str", + "open_via_pinhole": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "options-rate": {"required": False, "type": "int"}, - "prack-rate": {"required": False, "type": "int"}, - "preserve-override": {"required": False, "type": "str", + "options_rate": {"required": False, "type": "int"}, + "prack_rate": {"required": False, "type": "int"}, + "preserve_override": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "provisional-invite-expiry-time": {"required": False, "type": "int"}, - "publish-rate": {"required": False, "type": "int"}, - "refer-rate": {"required": False, "type": "int"}, - "register-contact-trace": {"required": False, "type": "str", + "provisional_invite_expiry_time": {"required": False, "type": "int"}, + "publish_rate": {"required": False, "type": "int"}, + "refer_rate": {"required": False, "type": "int"}, + "register_contact_trace": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "register-rate": {"required": False, "type": "int"}, - "rfc2543-branch": {"required": False, "type": "str", + "register_rate": {"required": False, "type": "int"}, + "rfc2543_branch": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "rtp": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "ssl-algorithm": {"required": False, "type": "str", + "ssl_algorithm": {"required": False, "type": "str", "choices": ["high", "medium", "low"]}, - "ssl-auth-client": {"required": False, "type": "str"}, - "ssl-auth-server": {"required": False, "type": "str"}, - "ssl-client-certificate": {"required": False, "type": "str"}, - "ssl-client-renegotiation": {"required": False, "type": "str", + "ssl_auth_client": {"required": False, "type": "str"}, + "ssl_auth_server": {"required": False, "type": "str"}, + "ssl_client_certificate": {"required": False, "type": "str"}, + "ssl_client_renegotiation": {"required": False, "type": "str", "choices": ["allow", "deny", "secure"]}, - "ssl-max-version": {"required": False, "type": "str", + "ssl_max_version": {"required": False, "type": "str", "choices": ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]}, - "ssl-min-version": {"required": False, "type": "str", + "ssl_min_version": {"required": False, "type": "str", "choices": ["ssl-3.0", "tls-1.0", "tls-1.1", "tls-1.2"]}, - "ssl-mode": {"required": False, "type": "str", + "ssl_mode": {"required": False, "type": "str", "choices": ["off", "full"]}, - "ssl-pfs": {"required": False, "type": "str", + "ssl_pfs": {"required": False, "type": "str", "choices": ["require", "deny", "allow"]}, - "ssl-send-empty-frags": {"required": False, "type": "str", + "ssl_send_empty_frags": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "ssl-server-certificate": {"required": False, "type": "str"}, + "ssl_server_certificate": {"required": False, "type": "str"}, "status": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "strict-register": {"required": False, "type": "str", + "strict_register": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "subscribe-rate": {"required": False, "type": "int"}, - "unknown-header": {"required": False, "type": "str", + "subscribe_rate": {"required": False, "type": "int"}, + "unknown_header": {"required": False, "type": "str", "choices": ["discard", "pass", "respond"]}, - "update-rate": {"required": False, "type": "int"} + "update_rate": {"required": False, "type": "int"} }} } @@ -1115,14 +1259,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_voip(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_voip(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_voip(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_concentrator.py b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_concentrator.py index a56c7e3ac90..856f689829b 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_concentrator.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_concentrator.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ipsec_concentrator short_description: Concentrator configuration in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ipsec feature and concentrator category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,55 +41,72 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 vpn_ipsec_concentrator: description: - Concentrator configuration. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent member: description: - Names of up to 3 VPN tunnels to add to the concentrator. + type: list suboptions: name: description: - Member name. Source vpn.ipsec.manualkey.name vpn.ipsec.phase1.name. required: true + type: str name: description: - Concentrator name. required: true - src-check: + type: str + src_check: description: - Enable to check source address of phase 2 selector. Disable to check only the destination selector. + type: str choices: - disable - enable @@ -105,6 +119,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Concentrator configuration. fortios_vpn_ipsec_concentrator: @@ -113,13 +128,13 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" vpn_ipsec_concentrator: - state: "present" member: - name: "default_name_4 (source vpn.ipsec.manualkey.name vpn.ipsec.phase1.name)" name: "default_name_5" - src-check: "disable" + src_check: "disable" ''' RETURN = ''' @@ -182,14 +197,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule - -fos = None +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -197,11 +214,11 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ipsec_concentrator_data(json): - option_list = ['member', 'name', 'src-check'] + option_list = ['member', 'name', 'src_check'] dictionary = {} for attribute in option_list: @@ -211,67 +228,72 @@ def filter_vpn_ipsec_concentrator_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def vpn_ipsec_concentrator(data, fos): vdom = data['vdom'] + state = data['state'] vpn_ipsec_concentrator_data = data['vpn_ipsec_concentrator'] - flattened_data = flatten_multilists_attributes(vpn_ipsec_concentrator_data) - filtered_data = filter_vpn_ipsec_concentrator_data(flattened_data) - if vpn_ipsec_concentrator_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_vpn_ipsec_concentrator_data(vpn_ipsec_concentrator_data)) + + if state == "present": return fos.set('vpn.ipsec', 'concentrator', data=filtered_data, vdom=vdom) - elif vpn_ipsec_concentrator_data['state'] == "absent": + elif state == "absent": return fos.delete('vpn.ipsec', 'concentrator', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ipsec(data, fos): - login(data) if data['vpn_ipsec_concentrator']: resp = vpn_ipsec_concentrator(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "vpn_ipsec_concentrator": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, "member": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, "name": {"required": True, "type": "str"}, - "src-check": {"required": False, "type": "str", + "src_check": {"required": False, "type": "str", "choices": ["disable", "enable"]} } @@ -280,15 +302,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_forticlient.py b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_forticlient.py index c8350afa283..5461557dceb 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_forticlient.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_forticlient.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ipsec_forticlient short_description: Configure FortiClient policy realm in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ipsec feature and forticlient category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,57 +41,74 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 vpn_ipsec_forticlient: description: - Configure FortiClient policy realm. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent phase2name: description: - Phase 2 tunnel name that you defined in the FortiClient dialup configuration. Source vpn.ipsec.phase2.name vpn.ipsec.phase2-interface .name. + type: str realm: description: - FortiClient realm name. required: true + type: str status: description: - Enable/disable this FortiClient configuration. + type: str choices: - enable - disable usergroupname: description: - User group name for FortiClient users. Source user.group.name. + type: str ''' EXAMPLES = ''' @@ -104,6 +118,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure FortiClient policy realm. fortios_vpn_ipsec_forticlient: @@ -112,8 +127,8 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" vpn_ipsec_forticlient: - state: "present" phase2name: " (source vpn.ipsec.phase2.name vpn.ipsec.phase2-interface.name)" realm: "" status: "enable" @@ -180,14 +195,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule - -fos = None +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -195,7 +212,7 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ipsec_forticlient_data(json): @@ -210,61 +227,66 @@ def filter_vpn_ipsec_forticlient_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def vpn_ipsec_forticlient(data, fos): vdom = data['vdom'] + state = data['state'] vpn_ipsec_forticlient_data = data['vpn_ipsec_forticlient'] - flattened_data = flatten_multilists_attributes(vpn_ipsec_forticlient_data) - filtered_data = filter_vpn_ipsec_forticlient_data(flattened_data) - if vpn_ipsec_forticlient_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_vpn_ipsec_forticlient_data(vpn_ipsec_forticlient_data)) + + if state == "present": return fos.set('vpn.ipsec', 'forticlient', data=filtered_data, vdom=vdom) - elif vpn_ipsec_forticlient_data['state'] == "absent": + elif state == "absent": return fos.delete('vpn.ipsec', 'forticlient', mkey=filtered_data['realm'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ipsec(data, fos): - login(data) if data['vpn_ipsec_forticlient']: resp = vpn_ipsec_forticlient(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "vpn_ipsec_forticlient": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, "phase2name": {"required": False, "type": "str"}, "realm": {"required": True, "type": "str"}, "status": {"required": False, "type": "str", @@ -277,15 +299,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey.py b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey.py index 615de35145a..5e0612fa61b 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ipsec_manualkey short_description: Configure IPsec manual keys in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ipsec feature and manualkey category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,43 +41,57 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 vpn_ipsec_manualkey: description: - Configure IPsec manual keys. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent authentication: description: - Authentication algorithm. Must be the same for both ends of the tunnel. + type: str choices: - null - md5 @@ -91,40 +102,43 @@ options: authkey: description: - Hexadecimal authentication key in 16-digit (8-byte) segments separated by hyphens. + type: str enckey: description: - Hexadecimal encryption key in 16-digit (8-byte) segments separated by hyphens. + type: str encryption: description: - Encryption algorithm. Must be the same for both ends of the tunnel. + type: str choices: - null - des interface: description: - Name of the physical, aggregate, or VLAN interface. Source system.interface.name. - local-gw: + type: str + local_gw: description: - Local gateway. + type: str localspi: description: - Local SPI, a hexadecimal 8-digit (4-byte) tag. Discerns between two traffic streams with different encryption rules. + type: str name: description: - IPsec tunnel name. required: true - npu-offload: - description: - - Enable/disable NPU offloading. - choices: - - enable - - disable - remote-gw: + type: str + remote_gw: description: - Peer gateway. + type: str remotespi: description: - Remote SPI, a hexadecimal 8-digit (4-byte) tag. Discerns between two traffic streams with different encryption rules. + type: str ''' EXAMPLES = ''' @@ -134,6 +148,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure IPsec manual keys. fortios_vpn_ipsec_manualkey: @@ -142,18 +157,17 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" vpn_ipsec_manualkey: - state: "present" authentication: "null" authkey: "" enckey: "" encryption: "null" interface: " (source system.interface.name)" - local-gw: "" + local_gw: "" localspi: "" name: "default_name_10" - npu-offload: "enable" - remote-gw: "" + remote_gw: "" remotespi: "" ''' @@ -217,14 +231,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -fos = None - -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -232,14 +248,14 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ipsec_manualkey_data(json): option_list = ['authentication', 'authkey', 'enckey', - 'encryption', 'interface', 'local-gw', - 'localspi', 'name', 'npu-offload', - 'remote-gw', 'remotespi'] + 'encryption', 'interface', 'local_gw', + 'localspi', 'name', 'remote_gw', + 'remotespi'] dictionary = {} for attribute in option_list: @@ -249,61 +265,66 @@ def filter_vpn_ipsec_manualkey_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def vpn_ipsec_manualkey(data, fos): vdom = data['vdom'] + state = data['state'] vpn_ipsec_manualkey_data = data['vpn_ipsec_manualkey'] - flattened_data = flatten_multilists_attributes(vpn_ipsec_manualkey_data) - filtered_data = filter_vpn_ipsec_manualkey_data(flattened_data) - if vpn_ipsec_manualkey_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_vpn_ipsec_manualkey_data(vpn_ipsec_manualkey_data)) + + if state == "present": return fos.set('vpn.ipsec', 'manualkey', data=filtered_data, vdom=vdom) - elif vpn_ipsec_manualkey_data['state'] == "absent": + elif state == "absent": return fos.delete('vpn.ipsec', 'manualkey', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ipsec(data, fos): - login(data) if data['vpn_ipsec_manualkey']: resp = vpn_ipsec_manualkey(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "vpn_ipsec_manualkey": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, "authentication": {"required": False, "type": "str", "choices": ["null", "md5", "sha1", "sha256", "sha384", "sha512"]}, @@ -312,12 +333,10 @@ def main(): "encryption": {"required": False, "type": "str", "choices": ["null", "des"]}, "interface": {"required": False, "type": "str"}, - "local-gw": {"required": False, "type": "str"}, + "local_gw": {"required": False, "type": "str"}, "localspi": {"required": False, "type": "str"}, "name": {"required": True, "type": "str"}, - "npu-offload": {"required": False, "type": "str", - "choices": ["enable", "disable"]}, - "remote-gw": {"required": False, "type": "str"}, + "remote_gw": {"required": False, "type": "str"}, "remotespi": {"required": False, "type": "str"} } @@ -326,15 +345,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey_interface.py b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey_interface.py index 1a996070c15..c8dad491c12 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey_interface.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey_interface.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ipsec_manualkey_interface short_description: Configure IPsec manual keys in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ipsec feature and manualkey_interface category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,49 +41,64 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 vpn_ipsec_manualkey_interface: description: - Configure IPsec manual keys. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - addr-type: + addr_type: description: - IP version to use for IP packets. + type: str choices: - 4 - 6 - auth-alg: + auth_alg: description: - Authentication algorithm. Must be the same for both ends of the tunnel. + type: str choices: - null - md5 @@ -94,55 +106,61 @@ options: - sha256 - sha384 - sha512 - auth-key: + auth_key: description: - Hexadecimal authentication key in 16-digit (8-byte) segments separated by hyphens. - enc-alg: + type: str + enc_alg: description: - Encryption algorithm. Must be the same for both ends of the tunnel. + type: str choices: - null - des - enc-key: + enc_key: description: - Hexadecimal encryption key in 16-digit (8-byte) segments separated by hyphens. + type: str interface: description: - Name of the physical, aggregate, or VLAN interface. Source system.interface.name. - ip-version: + type: str + ip_version: description: - IP version to use for VPN interface. + type: str choices: - 4 - 6 - local-gw: + local_gw: description: - IPv4 address of the local gateway's external interface. - local-gw6: + type: str + local_gw6: description: - Local IPv6 address of VPN gateway. - local-spi: + type: str + local_spi: description: - Local SPI, a hexadecimal 8-digit (4-byte) tag. Discerns between two traffic streams with different encryption rules. + type: str name: description: - IPsec tunnel name. required: true - npu-offload: - description: - - Enable/disable offloading IPsec VPN manual key sessions to NPUs. - choices: - - enable - - disable - remote-gw: + type: str + remote_gw: description: - IPv4 address of the remote gateway's external interface. - remote-gw6: + type: str + remote_gw6: description: - Remote IPv6 address of VPN gateway. - remote-spi: + type: str + remote_spi: description: - Remote SPI, a hexadecimal 8-digit (4-byte) tag. Discerns between two traffic streams with different encryption rules. + type: str ''' EXAMPLES = ''' @@ -152,6 +170,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure IPsec manual keys. fortios_vpn_ipsec_manualkey_interface: @@ -160,23 +179,22 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" vpn_ipsec_manualkey_interface: - state: "present" - addr-type: "4" - auth-alg: "null" - auth-key: "" - enc-alg: "null" - enc-key: "" + addr_type: "4" + auth_alg: "null" + auth_key: "" + enc_alg: "null" + enc_key: "" interface: " (source system.interface.name)" - ip-version: "4" - local-gw: "" - local-gw6: "" - local-spi: "" + ip_version: "4" + local_gw: "" + local_gw6: "" + local_spi: "" name: "default_name_13" - npu-offload: "enable" - remote-gw: "" - remote-gw6: "" - remote-spi: "" + remote_gw: "" + remote_gw6: "" + remote_spi: "" ''' RETURN = ''' @@ -239,14 +257,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -fos = None - -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -254,15 +274,15 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ipsec_manualkey_interface_data(json): - option_list = ['addr-type', 'auth-alg', 'auth-key', - 'enc-alg', 'enc-key', 'interface', - 'ip-version', 'local-gw', 'local-gw6', - 'local-spi', 'name', 'npu-offload', - 'remote-gw', 'remote-gw6', 'remote-spi'] + option_list = ['addr_type', 'auth_alg', 'auth_key', + 'enc_alg', 'enc_key', 'interface', + 'ip_version', 'local_gw', 'local_gw6', + 'local_spi', 'name', 'remote_gw', + 'remote_gw6', 'remote_spi'] dictionary = {} for attribute in option_list: @@ -272,82 +292,85 @@ def filter_vpn_ipsec_manualkey_interface_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def vpn_ipsec_manualkey_interface(data, fos): vdom = data['vdom'] + state = data['state'] vpn_ipsec_manualkey_interface_data = data['vpn_ipsec_manualkey_interface'] - flattened_data = flatten_multilists_attributes(vpn_ipsec_manualkey_interface_data) - filtered_data = filter_vpn_ipsec_manualkey_interface_data(flattened_data) - if vpn_ipsec_manualkey_interface_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_vpn_ipsec_manualkey_interface_data(vpn_ipsec_manualkey_interface_data)) + + if state == "present": return fos.set('vpn.ipsec', 'manualkey-interface', data=filtered_data, vdom=vdom) - elif vpn_ipsec_manualkey_interface_data['state'] == "absent": + elif state == "absent": return fos.delete('vpn.ipsec', 'manualkey-interface', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ipsec(data, fos): - login(data) if data['vpn_ipsec_manualkey_interface']: resp = vpn_ipsec_manualkey_interface(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "vpn_ipsec_manualkey_interface": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "addr-type": {"required": False, "type": "str", + "addr_type": {"required": False, "type": "str", "choices": ["4", "6"]}, - "auth-alg": {"required": False, "type": "str", + "auth_alg": {"required": False, "type": "str", "choices": ["null", "md5", "sha1", "sha256", "sha384", "sha512"]}, - "auth-key": {"required": False, "type": "str"}, - "enc-alg": {"required": False, "type": "str", + "auth_key": {"required": False, "type": "str"}, + "enc_alg": {"required": False, "type": "str", "choices": ["null", "des"]}, - "enc-key": {"required": False, "type": "str"}, + "enc_key": {"required": False, "type": "str"}, "interface": {"required": False, "type": "str"}, - "ip-version": {"required": False, "type": "str", + "ip_version": {"required": False, "type": "str", "choices": ["4", "6"]}, - "local-gw": {"required": False, "type": "str"}, - "local-gw6": {"required": False, "type": "str"}, - "local-spi": {"required": False, "type": "str"}, + "local_gw": {"required": False, "type": "str"}, + "local_gw6": {"required": False, "type": "str"}, + "local_spi": {"required": False, "type": "str"}, "name": {"required": True, "type": "str"}, - "npu-offload": {"required": False, "type": "str", - "choices": ["enable", "disable"]}, - "remote-gw": {"required": False, "type": "str"}, - "remote-gw6": {"required": False, "type": "str"}, - "remote-spi": {"required": False, "type": "str"} + "remote_gw": {"required": False, "type": "str"}, + "remote_gw6": {"required": False, "type": "str"}, + "remote_spi": {"required": False, "type": "str"} } } @@ -355,15 +378,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1.py b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1.py index f0db4c6f97e..de060c3a090 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ipsec_phase1 short_description: Configure VPN remote gateway in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ipsec feature and phase1 category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,67 +41,85 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. type: bool default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. + type: bool + default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 vpn_ipsec_phase1: description: - Configure VPN remote gateway. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - acct-verify: + acct_verify: description: - Enable/disable verification of RADIUS accounting record. + type: str choices: - enable - disable - add-gw-route: + add_gw_route: description: - Enable/disable automatically add a route to the remote gateway. + type: str choices: - enable - disable - add-route: + add_route: description: - Enable/disable control addition of a route to peer destination selector. + type: str choices: - disable - enable - assign-ip: + assign_ip: description: - Enable/disable assignment of IP to IPsec interface via configuration method. + type: str choices: - disable - enable - assign-ip-from: + assign_ip_from: description: - Method by which the IP address will be assigned. + type: str choices: - range - usrgrp @@ -113,79 +128,96 @@ options: authmethod: description: - Authentication method. + type: str choices: - psk - signature - authmethod-remote: + authmethod_remote: description: - Authentication method (remote side). + type: str choices: - psk - signature authpasswd: description: - XAuth password (max 35 characters). + type: str authusr: description: - XAuth user name. + type: str authusrgrp: description: - Authentication user group. Source user.group.name. - auto-negotiate: + type: str + auto_negotiate: description: - Enable/disable automatic initiation of IKE SA negotiation. + type: str choices: - enable - disable - backup-gateway: + backup_gateway: description: - Instruct unity clients about the backup gateway address(es). + type: list suboptions: address: description: - Address of backup gateway. required: true + type: str banner: description: - Message that unity client should display after connecting. - cert-id-validation: + type: str + cert_id_validation: description: - Enable/disable cross validation of peer ID and the identity in the peer's certificate as specified in RFC 4945. + type: str choices: - enable - disable certificate: description: - Names of up to 4 signed personal certificates. + type: list suboptions: name: description: - Certificate name. Source vpn.certificate.local.name. required: true - childless-ike: + type: str + childless_ike: description: - Enable/disable childless IKEv2 initiation (RFC 6023). + type: str choices: - enable - disable - client-auto-negotiate: + client_auto_negotiate: description: - Enable/disable allowing the VPN client to bring up the tunnel when there is no traffic. + type: str choices: - disable - enable - client-keep-alive: + client_keep_alive: description: - Enable/disable allowing the VPN client to keep the tunnel up when there is no traffic. + type: str choices: - disable - enable comments: description: - Comment. + type: str dhgrp: description: - DH group. + type: str choices: - 1 - 2 @@ -203,213 +235,267 @@ options: - 29 - 30 - 31 - digital-signature-auth: + digital_signature_auth: description: - Enable/disable IKEv2 Digital Signature Authentication (RFC 7427). + type: str choices: - enable - disable distance: description: - Distance for routes added by IKE (1 - 255). - dns-mode: + type: int + dns_mode: description: - DNS server mode. + type: str choices: - manual - auto domain: description: - Instruct unity clients about the default DNS domain. + type: str dpd: description: - Dead Peer Detection mode. + type: str choices: - disable - on-idle - on-demand - dpd-retrycount: + dpd_retrycount: description: - Number of DPD retry attempts. - dpd-retryinterval: + type: int + dpd_retryinterval: description: - DPD retry interval. + type: str eap: description: - Enable/disable IKEv2 EAP authentication. + type: str choices: - enable - disable - eap-identity: + eap_identity: description: - IKEv2 EAP peer identity type. + type: str choices: - use-id-payload - send-request - enforce-unique-id: + enforce_unique_id: description: - Enable/disable peer ID uniqueness check. + type: str choices: - disable - keep-new - keep-old - forticlient-enforcement: + forticlient_enforcement: description: - Enable/disable FortiClient enforcement. + type: str choices: - enable - disable fragmentation: description: - Enable/disable fragment IKE message on re-transmission. + type: str choices: - enable - disable - fragmentation-mtu: + fragmentation_mtu: description: - IKE fragmentation MTU (500 - 16000). - group-authentication: + type: int + group_authentication: description: - Enable/disable IKEv2 IDi group authentication. + type: str choices: - enable - disable - group-authentication-secret: + group_authentication_secret: description: - Password for IKEv2 IDi group authentication. (ASCII string or hexadecimal indicated by a leading 0x.) - ha-sync-esp-seqno: + type: str + ha_sync_esp_seqno: description: - Enable/disable sequence number jump ahead for IPsec HA. + type: str choices: - enable - disable - idle-timeout: + idle_timeout: description: - Enable/disable IPsec tunnel idle timeout. + type: str choices: - enable - disable - idle-timeoutinterval: + idle_timeoutinterval: description: - IPsec tunnel idle timeout in minutes (5 - 43200). - ike-version: + type: int + ike_version: description: - IKE protocol version. + type: str choices: - 1 - 2 - include-local-lan: + include_local_lan: description: - Enable/disable allow local LAN access on unity clients. + type: str choices: - disable - enable interface: description: - Local physical, aggregate, or VLAN outgoing interface. Source system.interface.name. - ipv4-dns-server1: + type: str + ipv4_dns_server1: description: - IPv4 DNS server 1. - ipv4-dns-server2: + type: str + ipv4_dns_server2: description: - IPv4 DNS server 2. - ipv4-dns-server3: + type: str + ipv4_dns_server3: description: - IPv4 DNS server 3. - ipv4-end-ip: + type: str + ipv4_end_ip: description: - End of IPv4 range. - ipv4-exclude-range: + type: str + ipv4_exclude_range: description: - Configuration Method IPv4 exclude ranges. + type: list suboptions: - end-ip: + end_ip: description: - End of IPv4 exclusive range. + type: str id: description: - ID. required: true - start-ip: + type: int + start_ip: description: - Start of IPv4 exclusive range. - ipv4-name: + type: str + ipv4_name: description: - IPv4 address name. Source firewall.address.name firewall.addrgrp.name. - ipv4-netmask: + type: str + ipv4_netmask: description: - IPv4 Netmask. - ipv4-split-exclude: + type: str + ipv4_split_exclude: description: - IPv4 subnets that should not be sent over the IPsec tunnel. Source firewall.address.name firewall.addrgrp.name. - ipv4-split-include: + type: str + ipv4_split_include: description: - IPv4 split-include subnets. Source firewall.address.name firewall.addrgrp.name. - ipv4-start-ip: + type: str + ipv4_start_ip: description: - Start of IPv4 range. - ipv4-wins-server1: + type: str + ipv4_wins_server1: description: - WINS server 1. - ipv4-wins-server2: + type: str + ipv4_wins_server2: description: - WINS server 2. - ipv6-dns-server1: + type: str + ipv6_dns_server1: description: - IPv6 DNS server 1. - ipv6-dns-server2: + type: str + ipv6_dns_server2: description: - IPv6 DNS server 2. - ipv6-dns-server3: + type: str + ipv6_dns_server3: description: - IPv6 DNS server 3. - ipv6-end-ip: + type: str + ipv6_end_ip: description: - End of IPv6 range. - ipv6-exclude-range: + type: str + ipv6_exclude_range: description: - Configuration method IPv6 exclude ranges. + type: list suboptions: - end-ip: + end_ip: description: - End of IPv6 exclusive range. + type: str id: description: - ID. required: true - start-ip: + type: int + start_ip: description: - Start of IPv6 exclusive range. - ipv6-name: + type: str + ipv6_name: description: - IPv6 address name. Source firewall.address6.name firewall.addrgrp6.name. - ipv6-prefix: + type: str + ipv6_prefix: description: - IPv6 prefix. - ipv6-split-exclude: + type: int + ipv6_split_exclude: description: - IPv6 subnets that should not be sent over the IPsec tunnel. Source firewall.address6.name firewall.addrgrp6.name. - ipv6-split-include: + type: str + ipv6_split_include: description: - IPv6 split-include subnets. Source firewall.address6.name firewall.addrgrp6.name. - ipv6-start-ip: + type: str + ipv6_start_ip: description: - Start of IPv6 range. + type: str keepalive: description: - NAT-T keep alive interval. + type: int keylife: description: - Time to wait in seconds before phase 1 encryption key expires. - local-gw: + type: int + local_gw: description: - Local VPN gateway. + type: str localid: description: - Local ID. - localid-type: + type: str + localid_type: description: - Local ID type. + type: str choices: - auto - fqdn @@ -417,9 +503,10 @@ options: - keyid - address - asn1dn - mesh-selector-type: + mesh_selector_type: description: - Add selectors containing subsets of the configuration depending on traffic. + type: str choices: - disable - subnet @@ -427,12 +514,14 @@ options: mode: description: - ID protection mode used to establish a secure channel. + type: str choices: - aggressive - main - mode-cfg: + mode_cfg: description: - Enable/disable configuration method. + type: str choices: - disable - enable @@ -440,34 +529,35 @@ options: description: - IPsec remote gateway name. required: true + type: str nattraversal: description: - Enable/disable NAT traversal. + type: str choices: - enable - disable - forced - negotiate-timeout: + negotiate_timeout: description: - IKE SA negotiation timeout in seconds (1 - 300). - npu-offload: - description: - - Enable/disable offloading NPU. - choices: - - enable - - disable + type: int peer: description: - Accept this peer certificate. Source user.peer.name. + type: str peergrp: description: - Accept this peer certificate group. Source user.peergrp.name. + type: str peerid: description: - Accept this peer identity. + type: str peertype: description: - Accept this peer type. + type: str choices: - any - one @@ -477,22 +567,27 @@ options: ppk: description: - Enable/disable IKEv2 Postquantum Preshared Key (PPK). + type: str choices: - disable - allow - require - ppk-identity: + ppk_identity: description: - IKEv2 Postquantum Preshared Key Identity. - ppk-secret: + type: str + ppk_secret: description: - IKEv2 Postquantum Preshared Key (ASCII string or hexadecimal encoded with a leading 0x). + type: str priority: description: - Priority for routes added by IKE (0 - 4294967295). + type: int proposal: description: - Phase1 proposal. + type: str choices: - des-md5 - des-sha1 @@ -502,59 +597,71 @@ options: psksecret: description: - Pre-shared secret for PSK authentication (ASCII string or hexadecimal encoded with a leading 0x). - psksecret-remote: + type: str + psksecret_remote: description: - Pre-shared secret for remote side PSK authentication (ASCII string or hexadecimal encoded with a leading 0x). + type: str reauth: description: - Enable/disable re-authentication upon IKE SA lifetime expiration. + type: str choices: - disable - enable rekey: description: - Enable/disable phase1 rekey. + type: str choices: - enable - disable - remote-gw: + remote_gw: description: - Remote VPN gateway. - remotegw-ddns: + type: str + remotegw_ddns: description: - Domain name of remote gateway (eg. name.DDNS.com). - rsa-signature-format: + type: str + rsa_signature_format: description: - Digital Signature Authentication RSA signature format. + type: str choices: - pkcs1 - pss - save-password: + save_password: description: - Enable/disable saving XAuth username and password on VPN clients. + type: str choices: - disable - enable - send-cert-chain: + send_cert_chain: description: - Enable/disable sending certificate chain. + type: str choices: - enable - disable - signature-hash-alg: + signature_hash_alg: description: - Digital Signature Authentication hash algorithms. + type: str choices: - sha1 - sha2-256 - sha2-384 - sha2-512 - split-include-service: + split_include_service: description: - Split-include services. Source firewall.service.group.name firewall.service.custom.name. - suite-b: + type: str + suite_b: description: - Use Suite-B. + type: str choices: - disable - suite-b-gcm-128 @@ -562,22 +669,26 @@ options: type: description: - Remote gateway type. + type: str choices: - static - dynamic - ddns - unity-support: + unity_support: description: - Enable/disable support for Cisco UNITY Configuration Method extensions. + type: str choices: - disable - enable usrgrp: description: - User group name for dialup peers. Source user.group.name. - wizard-type: + type: str + wizard_type: description: - GUI VPN Wizard Type. + type: str choices: - custom - dialup-forticlient @@ -592,6 +703,7 @@ options: xauthtype: description: - XAuth type. + type: str choices: - disable - client @@ -607,6 +719,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure VPN remote gateway. fortios_vpn_ipsec_phase1: @@ -615,120 +728,119 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" vpn_ipsec_phase1: - state: "present" - acct-verify: "enable" - add-gw-route: "enable" - add-route: "disable" - assign-ip: "disable" - assign-ip-from: "range" + acct_verify: "enable" + add_gw_route: "enable" + add_route: "disable" + assign_ip: "disable" + assign_ip_from: "range" authmethod: "psk" - authmethod-remote: "psk" + authmethod_remote: "psk" authpasswd: "" authusr: "" authusrgrp: " (source user.group.name)" - auto-negotiate: "enable" - backup-gateway: + auto_negotiate: "enable" + backup_gateway: - address: "" banner: "" - cert-id-validation: "enable" + cert_id_validation: "enable" certificate: - name: "default_name_19 (source vpn.certificate.local.name)" - childless-ike: "enable" - client-auto-negotiate: "disable" - client-keep-alive: "disable" + childless_ike: "enable" + client_auto_negotiate: "disable" + client_keep_alive: "disable" comments: "" dhgrp: "1" - digital-signature-auth: "enable" + digital_signature_auth: "enable" distance: "26" - dns-mode: "manual" + dns_mode: "manual" domain: "" dpd: "disable" - dpd-retrycount: "30" - dpd-retryinterval: "" + dpd_retrycount: "30" + dpd_retryinterval: "" eap: "enable" - eap-identity: "use-id-payload" - enforce-unique-id: "disable" - forticlient-enforcement: "enable" + eap_identity: "use-id-payload" + enforce_unique_id: "disable" + forticlient_enforcement: "enable" fragmentation: "enable" - fragmentation-mtu: "37" - group-authentication: "enable" - group-authentication-secret: "" - ha-sync-esp-seqno: "enable" - idle-timeout: "enable" - idle-timeoutinterval: "42" - ike-version: "1" - include-local-lan: "disable" + fragmentation_mtu: "37" + group_authentication: "enable" + group_authentication_secret: "" + ha_sync_esp_seqno: "enable" + idle_timeout: "enable" + idle_timeoutinterval: "42" + ike_version: "1" + include_local_lan: "disable" interface: " (source system.interface.name)" - ipv4-dns-server1: "" - ipv4-dns-server2: "" - ipv4-dns-server3: "" - ipv4-end-ip: "" - ipv4-exclude-range: + ipv4_dns_server1: "" + ipv4_dns_server2: "" + ipv4_dns_server3: "" + ipv4_end_ip: "" + ipv4_exclude_range: - - end-ip: "" + end_ip: "" id: "52" - start-ip: "" - ipv4-name: " (source firewall.address.name firewall.addrgrp.name)" - ipv4-netmask: "" - ipv4-split-exclude: " (source firewall.address.name firewall.addrgrp.name)" - ipv4-split-include: " (source firewall.address.name firewall.addrgrp.name)" - ipv4-start-ip: "" - ipv4-wins-server1: "" - ipv4-wins-server2: "" - ipv6-dns-server1: "" - ipv6-dns-server2: "" - ipv6-dns-server3: "" - ipv6-end-ip: "" - ipv6-exclude-range: + start_ip: "" + ipv4_name: " (source firewall.address.name firewall.addrgrp.name)" + ipv4_netmask: "" + ipv4_split_exclude: " (source firewall.address.name firewall.addrgrp.name)" + ipv4_split_include: " (source firewall.address.name firewall.addrgrp.name)" + ipv4_start_ip: "" + ipv4_wins_server1: "" + ipv4_wins_server2: "" + ipv6_dns_server1: "" + ipv6_dns_server2: "" + ipv6_dns_server3: "" + ipv6_end_ip: "" + ipv6_exclude_range: - - end-ip: "" + end_ip: "" id: "67" - start-ip: "" - ipv6-name: " (source firewall.address6.name firewall.addrgrp6.name)" - ipv6-prefix: "70" - ipv6-split-exclude: " (source firewall.address6.name firewall.addrgrp6.name)" - ipv6-split-include: " (source firewall.address6.name firewall.addrgrp6.name)" - ipv6-start-ip: "" + start_ip: "" + ipv6_name: " (source firewall.address6.name firewall.addrgrp6.name)" + ipv6_prefix: "70" + ipv6_split_exclude: " (source firewall.address6.name firewall.addrgrp6.name)" + ipv6_split_include: " (source firewall.address6.name firewall.addrgrp6.name)" + ipv6_start_ip: "" keepalive: "74" keylife: "75" - local-gw: "" + local_gw: "" localid: "" - localid-type: "auto" - mesh-selector-type: "disable" + localid_type: "auto" + mesh_selector_type: "disable" mode: "aggressive" - mode-cfg: "disable" + mode_cfg: "disable" name: "default_name_82" nattraversal: "enable" - negotiate-timeout: "84" - npu-offload: "enable" + negotiate_timeout: "84" peer: " (source user.peer.name)" peergrp: " (source user.peergrp.name)" peerid: "" peertype: "any" ppk: "disable" - ppk-identity: "" - ppk-secret: "" - priority: "93" + ppk_identity: "" + ppk_secret: "" + priority: "92" proposal: "des-md5" psksecret: "" - psksecret-remote: "" + psksecret_remote: "" reauth: "disable" rekey: "enable" - remote-gw: "" - remotegw-ddns: "" - rsa-signature-format: "pkcs1" - save-password: "disable" - send-cert-chain: "enable" - signature-hash-alg: "sha1" - split-include-service: " (source firewall.service.group.name firewall.service.custom.name)" - suite-b: "disable" + remote_gw: "" + remotegw_ddns: "" + rsa_signature_format: "pkcs1" + save_password: "disable" + send_cert_chain: "enable" + signature_hash_alg: "sha1" + split_include_service: " (source firewall.service.group.name firewall.service.custom.name)" + suite_b: "disable" type: "static" - unity-support: "disable" + unity_support: "disable" usrgrp: " (source user.group.name)" - wizard-type: "custom" + wizard_type: "custom" xauthtype: "disable" ''' @@ -792,14 +904,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule - -fos = None +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -807,44 +921,44 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ipsec_phase1_data(json): - option_list = ['acct-verify', 'add-gw-route', 'add-route', - 'assign-ip', 'assign-ip-from', 'authmethod', - 'authmethod-remote', 'authpasswd', 'authusr', - 'authusrgrp', 'auto-negotiate', 'backup-gateway', - 'banner', 'cert-id-validation', 'certificate', - 'childless-ike', 'client-auto-negotiate', 'client-keep-alive', - 'comments', 'dhgrp', 'digital-signature-auth', - 'distance', 'dns-mode', 'domain', - 'dpd', 'dpd-retrycount', 'dpd-retryinterval', - 'eap', 'eap-identity', 'enforce-unique-id', - 'forticlient-enforcement', 'fragmentation', 'fragmentation-mtu', - 'group-authentication', 'group-authentication-secret', 'ha-sync-esp-seqno', - 'idle-timeout', 'idle-timeoutinterval', 'ike-version', - 'include-local-lan', 'interface', 'ipv4-dns-server1', - 'ipv4-dns-server2', 'ipv4-dns-server3', 'ipv4-end-ip', - 'ipv4-exclude-range', 'ipv4-name', 'ipv4-netmask', - 'ipv4-split-exclude', 'ipv4-split-include', 'ipv4-start-ip', - 'ipv4-wins-server1', 'ipv4-wins-server2', 'ipv6-dns-server1', - 'ipv6-dns-server2', 'ipv6-dns-server3', 'ipv6-end-ip', - 'ipv6-exclude-range', 'ipv6-name', 'ipv6-prefix', - 'ipv6-split-exclude', 'ipv6-split-include', 'ipv6-start-ip', - 'keepalive', 'keylife', 'local-gw', - 'localid', 'localid-type', 'mesh-selector-type', - 'mode', 'mode-cfg', 'name', - 'nattraversal', 'negotiate-timeout', 'npu-offload', - 'peer', 'peergrp', 'peerid', - 'peertype', 'ppk', 'ppk-identity', - 'ppk-secret', 'priority', 'proposal', - 'psksecret', 'psksecret-remote', 'reauth', - 'rekey', 'remote-gw', 'remotegw-ddns', - 'rsa-signature-format', 'save-password', 'send-cert-chain', - 'signature-hash-alg', 'split-include-service', 'suite-b', - 'type', 'unity-support', 'usrgrp', - 'wizard-type', 'xauthtype'] + option_list = ['acct_verify', 'add_gw_route', 'add_route', + 'assign_ip', 'assign_ip_from', 'authmethod', + 'authmethod_remote', 'authpasswd', 'authusr', + 'authusrgrp', 'auto_negotiate', 'backup_gateway', + 'banner', 'cert_id_validation', 'certificate', + 'childless_ike', 'client_auto_negotiate', 'client_keep_alive', + 'comments', 'dhgrp', 'digital_signature_auth', + 'distance', 'dns_mode', 'domain', + 'dpd', 'dpd_retrycount', 'dpd_retryinterval', + 'eap', 'eap_identity', 'enforce_unique_id', + 'forticlient_enforcement', 'fragmentation', 'fragmentation_mtu', + 'group_authentication', 'group_authentication_secret', 'ha_sync_esp_seqno', + 'idle_timeout', 'idle_timeoutinterval', 'ike_version', + 'include_local_lan', 'interface', 'ipv4_dns_server1', + 'ipv4_dns_server2', 'ipv4_dns_server3', 'ipv4_end_ip', + 'ipv4_exclude_range', 'ipv4_name', 'ipv4_netmask', + 'ipv4_split_exclude', 'ipv4_split_include', 'ipv4_start_ip', + 'ipv4_wins_server1', 'ipv4_wins_server2', 'ipv6_dns_server1', + 'ipv6_dns_server2', 'ipv6_dns_server3', 'ipv6_end_ip', + 'ipv6_exclude_range', 'ipv6_name', 'ipv6_prefix', + 'ipv6_split_exclude', 'ipv6_split_include', 'ipv6_start_ip', + 'keepalive', 'keylife', 'local_gw', + 'localid', 'localid_type', 'mesh_selector_type', + 'mode', 'mode_cfg', 'name', + 'nattraversal', 'negotiate_timeout', 'peer', + 'peergrp', 'peerid', 'peertype', + 'ppk', 'ppk_identity', 'ppk_secret', + 'priority', 'proposal', 'psksecret', + 'psksecret_remote', 'reauth', 'rekey', + 'remote_gw', 'remotegw_ddns', 'rsa_signature_format', + 'save_password', 'send_cert_chain', 'signature_hash_alg', + 'split_include_service', 'suite_b', 'type', + 'unity_support', 'usrgrp', 'wizard_type', + 'xauthtype'] dictionary = {} for attribute in option_list: @@ -854,97 +968,102 @@ def filter_vpn_ipsec_phase1_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def vpn_ipsec_phase1(data, fos): vdom = data['vdom'] + state = data['state'] vpn_ipsec_phase1_data = data['vpn_ipsec_phase1'] - flattened_data = flatten_multilists_attributes(vpn_ipsec_phase1_data) - filtered_data = filter_vpn_ipsec_phase1_data(flattened_data) - if vpn_ipsec_phase1_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_vpn_ipsec_phase1_data(vpn_ipsec_phase1_data)) + + if state == "present": return fos.set('vpn.ipsec', 'phase1', data=filtered_data, vdom=vdom) - elif vpn_ipsec_phase1_data['state'] == "absent": + elif state == "absent": return fos.delete('vpn.ipsec', 'phase1', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ipsec(data, fos): - login(data) if data['vpn_ipsec_phase1']: resp = vpn_ipsec_phase1(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "vpn_ipsec_phase1": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "acct-verify": {"required": False, "type": "str", + "acct_verify": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "add-gw-route": {"required": False, "type": "str", + "add_gw_route": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "add-route": {"required": False, "type": "str", + "add_route": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "assign-ip": {"required": False, "type": "str", + "assign_ip": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "assign-ip-from": {"required": False, "type": "str", + "assign_ip_from": {"required": False, "type": "str", "choices": ["range", "usrgrp", "dhcp", "name"]}, "authmethod": {"required": False, "type": "str", "choices": ["psk", "signature"]}, - "authmethod-remote": {"required": False, "type": "str", + "authmethod_remote": {"required": False, "type": "str", "choices": ["psk", "signature"]}, "authpasswd": {"required": False, "type": "str"}, "authusr": {"required": False, "type": "str"}, "authusrgrp": {"required": False, "type": "str"}, - "auto-negotiate": {"required": False, "type": "str", + "auto_negotiate": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "backup-gateway": {"required": False, "type": "list", + "backup_gateway": {"required": False, "type": "list", "options": { "address": {"required": True, "type": "str"} }}, "banner": {"required": False, "type": "str"}, - "cert-id-validation": {"required": False, "type": "str", + "cert_id_validation": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "certificate": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "childless-ike": {"required": False, "type": "str", + "childless_ike": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "client-auto-negotiate": {"required": False, "type": "str", + "client_auto_negotiate": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "client-keep-alive": {"required": False, "type": "str", + "client_keep_alive": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "comments": {"required": False, "type": "str"}, "dhgrp": {"required": False, "type": "str", @@ -954,91 +1073,89 @@ def main(): "20", "21", "27", "28", "29", "30", "31"]}, - "digital-signature-auth": {"required": False, "type": "str", + "digital_signature_auth": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "distance": {"required": False, "type": "int"}, - "dns-mode": {"required": False, "type": "str", + "dns_mode": {"required": False, "type": "str", "choices": ["manual", "auto"]}, "domain": {"required": False, "type": "str"}, "dpd": {"required": False, "type": "str", "choices": ["disable", "on-idle", "on-demand"]}, - "dpd-retrycount": {"required": False, "type": "int"}, - "dpd-retryinterval": {"required": False, "type": "str"}, + "dpd_retrycount": {"required": False, "type": "int"}, + "dpd_retryinterval": {"required": False, "type": "str"}, "eap": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "eap-identity": {"required": False, "type": "str", + "eap_identity": {"required": False, "type": "str", "choices": ["use-id-payload", "send-request"]}, - "enforce-unique-id": {"required": False, "type": "str", + "enforce_unique_id": {"required": False, "type": "str", "choices": ["disable", "keep-new", "keep-old"]}, - "forticlient-enforcement": {"required": False, "type": "str", + "forticlient_enforcement": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "fragmentation": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "fragmentation-mtu": {"required": False, "type": "int"}, - "group-authentication": {"required": False, "type": "str", + "fragmentation_mtu": {"required": False, "type": "int"}, + "group_authentication": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "group-authentication-secret": {"required": False, "type": "password-3"}, - "ha-sync-esp-seqno": {"required": False, "type": "str", + "group_authentication_secret": {"required": False, "type": "str"}, + "ha_sync_esp_seqno": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "idle-timeout": {"required": False, "type": "str", + "idle_timeout": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "idle-timeoutinterval": {"required": False, "type": "int"}, - "ike-version": {"required": False, "type": "str", + "idle_timeoutinterval": {"required": False, "type": "int"}, + "ike_version": {"required": False, "type": "str", "choices": ["1", "2"]}, - "include-local-lan": {"required": False, "type": "str", + "include_local_lan": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "interface": {"required": False, "type": "str"}, - "ipv4-dns-server1": {"required": False, "type": "str"}, - "ipv4-dns-server2": {"required": False, "type": "str"}, - "ipv4-dns-server3": {"required": False, "type": "str"}, - "ipv4-end-ip": {"required": False, "type": "str"}, - "ipv4-exclude-range": {"required": False, "type": "list", + "ipv4_dns_server1": {"required": False, "type": "str"}, + "ipv4_dns_server2": {"required": False, "type": "str"}, + "ipv4_dns_server3": {"required": False, "type": "str"}, + "ipv4_end_ip": {"required": False, "type": "str"}, + "ipv4_exclude_range": {"required": False, "type": "list", "options": { - "end-ip": {"required": False, "type": "str"}, + "end_ip": {"required": False, "type": "str"}, "id": {"required": True, "type": "int"}, - "start-ip": {"required": False, "type": "str"} + "start_ip": {"required": False, "type": "str"} }}, - "ipv4-name": {"required": False, "type": "str"}, - "ipv4-netmask": {"required": False, "type": "str"}, - "ipv4-split-exclude": {"required": False, "type": "str"}, - "ipv4-split-include": {"required": False, "type": "str"}, - "ipv4-start-ip": {"required": False, "type": "str"}, - "ipv4-wins-server1": {"required": False, "type": "str"}, - "ipv4-wins-server2": {"required": False, "type": "str"}, - "ipv6-dns-server1": {"required": False, "type": "str"}, - "ipv6-dns-server2": {"required": False, "type": "str"}, - "ipv6-dns-server3": {"required": False, "type": "str"}, - "ipv6-end-ip": {"required": False, "type": "str"}, - "ipv6-exclude-range": {"required": False, "type": "list", + "ipv4_name": {"required": False, "type": "str"}, + "ipv4_netmask": {"required": False, "type": "str"}, + "ipv4_split_exclude": {"required": False, "type": "str"}, + "ipv4_split_include": {"required": False, "type": "str"}, + "ipv4_start_ip": {"required": False, "type": "str"}, + "ipv4_wins_server1": {"required": False, "type": "str"}, + "ipv4_wins_server2": {"required": False, "type": "str"}, + "ipv6_dns_server1": {"required": False, "type": "str"}, + "ipv6_dns_server2": {"required": False, "type": "str"}, + "ipv6_dns_server3": {"required": False, "type": "str"}, + "ipv6_end_ip": {"required": False, "type": "str"}, + "ipv6_exclude_range": {"required": False, "type": "list", "options": { - "end-ip": {"required": False, "type": "str"}, + "end_ip": {"required": False, "type": "str"}, "id": {"required": True, "type": "int"}, - "start-ip": {"required": False, "type": "str"} + "start_ip": {"required": False, "type": "str"} }}, - "ipv6-name": {"required": False, "type": "str"}, - "ipv6-prefix": {"required": False, "type": "int"}, - "ipv6-split-exclude": {"required": False, "type": "str"}, - "ipv6-split-include": {"required": False, "type": "str"}, - "ipv6-start-ip": {"required": False, "type": "str"}, + "ipv6_name": {"required": False, "type": "str"}, + "ipv6_prefix": {"required": False, "type": "int"}, + "ipv6_split_exclude": {"required": False, "type": "str"}, + "ipv6_split_include": {"required": False, "type": "str"}, + "ipv6_start_ip": {"required": False, "type": "str"}, "keepalive": {"required": False, "type": "int"}, "keylife": {"required": False, "type": "int"}, - "local-gw": {"required": False, "type": "str"}, + "local_gw": {"required": False, "type": "str"}, "localid": {"required": False, "type": "str"}, - "localid-type": {"required": False, "type": "str", + "localid_type": {"required": False, "type": "str", "choices": ["auto", "fqdn", "user-fqdn", "keyid", "address", "asn1dn"]}, - "mesh-selector-type": {"required": False, "type": "str", + "mesh_selector_type": {"required": False, "type": "str", "choices": ["disable", "subnet", "host"]}, "mode": {"required": False, "type": "str", "choices": ["aggressive", "main"]}, - "mode-cfg": {"required": False, "type": "str", + "mode_cfg": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "name": {"required": True, "type": "str"}, "nattraversal": {"required": False, "type": "str", "choices": ["enable", "disable", "forced"]}, - "negotiate-timeout": {"required": False, "type": "int"}, - "npu-offload": {"required": False, "type": "str", - "choices": ["enable", "disable"]}, + "negotiate_timeout": {"required": False, "type": "int"}, "peer": {"required": False, "type": "str"}, "peergrp": {"required": False, "type": "str"}, "peerid": {"required": False, "type": "str"}, @@ -1047,38 +1164,38 @@ def main(): "peer", "peergrp"]}, "ppk": {"required": False, "type": "str", "choices": ["disable", "allow", "require"]}, - "ppk-identity": {"required": False, "type": "str"}, - "ppk-secret": {"required": False, "type": "password-3"}, + "ppk_identity": {"required": False, "type": "str"}, + "ppk_secret": {"required": False, "type": "str"}, "priority": {"required": False, "type": "int"}, "proposal": {"required": False, "type": "str", "choices": ["des-md5", "des-sha1", "des-sha256", "des-sha384", "des-sha512"]}, - "psksecret": {"required": False, "type": "password-3"}, - "psksecret-remote": {"required": False, "type": "password-3"}, + "psksecret": {"required": False, "type": "str"}, + "psksecret_remote": {"required": False, "type": "str"}, "reauth": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "rekey": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "remote-gw": {"required": False, "type": "str"}, - "remotegw-ddns": {"required": False, "type": "str"}, - "rsa-signature-format": {"required": False, "type": "str", + "remote_gw": {"required": False, "type": "str"}, + "remotegw_ddns": {"required": False, "type": "str"}, + "rsa_signature_format": {"required": False, "type": "str", "choices": ["pkcs1", "pss"]}, - "save-password": {"required": False, "type": "str", + "save_password": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "send-cert-chain": {"required": False, "type": "str", + "send_cert_chain": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "signature-hash-alg": {"required": False, "type": "str", + "signature_hash_alg": {"required": False, "type": "str", "choices": ["sha1", "sha2-256", "sha2-384", "sha2-512"]}, - "split-include-service": {"required": False, "type": "str"}, - "suite-b": {"required": False, "type": "str", + "split_include_service": {"required": False, "type": "str"}, + "suite_b": {"required": False, "type": "str", "choices": ["disable", "suite-b-gcm-128", "suite-b-gcm-256"]}, "type": {"required": False, "type": "str", "choices": ["static", "dynamic", "ddns"]}, - "unity-support": {"required": False, "type": "str", + "unity_support": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "usrgrp": {"required": False, "type": "str"}, - "wizard-type": {"required": False, "type": "str", + "wizard_type": {"required": False, "type": "str", "choices": ["custom", "dialup-forticlient", "dialup-ios", "dialup-android", "dialup-windows", "dialup-cisco", "static-fortigate", "dialup-fortigate", "static-cisco", @@ -1093,15 +1210,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1_interface.py b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1_interface.py index 546006b3291..c63002ceea7 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1_interface.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1_interface.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ipsec_phase1_interface short_description: Configure VPN remote gateway in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ipsec feature and phase1_interface category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,67 +41,85 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. type: bool default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. + type: bool + default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 vpn_ipsec_phase1_interface: description: - Configure VPN remote gateway. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - acct-verify: + acct_verify: description: - Enable/disable verification of RADIUS accounting record. + type: str choices: - enable - disable - add-gw-route: + add_gw_route: description: - Enable/disable automatically add a route to the remote gateway. + type: str choices: - enable - disable - add-route: + add_route: description: - Enable/disable control addition of a route to peer destination selector. + type: str choices: - disable - enable - assign-ip: + assign_ip: description: - Enable/disable assignment of IP to IPsec interface via configuration method. + type: str choices: - disable - enable - assign-ip-from: + assign_ip_from: description: - Method by which the IP address will be assigned. + type: str choices: - range - usrgrp @@ -113,109 +128,132 @@ options: authmethod: description: - Authentication method. + type: str choices: - psk - signature - authmethod-remote: + authmethod_remote: description: - Authentication method (remote side). + type: str choices: - psk - signature authpasswd: description: - XAuth password (max 35 characters). + type: str authusr: description: - XAuth user name. + type: str authusrgrp: description: - Authentication user group. Source user.group.name. - auto-discovery-forwarder: + type: str + auto_discovery_forwarder: description: - Enable/disable forwarding auto-discovery short-cut messages. + type: str choices: - enable - disable - auto-discovery-psk: + auto_discovery_psk: description: - Enable/disable use of pre-shared secrets for authentication of auto-discovery tunnels. + type: str choices: - enable - disable - auto-discovery-receiver: + auto_discovery_receiver: description: - Enable/disable accepting auto-discovery short-cut messages. + type: str choices: - enable - disable - auto-discovery-sender: + auto_discovery_sender: description: - Enable/disable sending auto-discovery short-cut messages. + type: str choices: - enable - disable - auto-negotiate: + auto_negotiate: description: - Enable/disable automatic initiation of IKE SA negotiation. + type: str choices: - enable - disable - backup-gateway: + backup_gateway: description: - Instruct unity clients about the backup gateway address(es). + type: list suboptions: address: description: - Address of backup gateway. required: true + type: str banner: description: - Message that unity client should display after connecting. - cert-id-validation: + type: str + cert_id_validation: description: - Enable/disable cross validation of peer ID and the identity in the peer's certificate as specified in RFC 4945. + type: str choices: - enable - disable certificate: description: - The names of up to 4 signed personal certificates. + type: list suboptions: name: description: - Certificate name. Source vpn.certificate.local.name. required: true - childless-ike: + type: str + childless_ike: description: - Enable/disable childless IKEv2 initiation (RFC 6023). + type: str choices: - enable - disable - client-auto-negotiate: + client_auto_negotiate: description: - Enable/disable allowing the VPN client to bring up the tunnel when there is no traffic. + type: str choices: - disable - enable - client-keep-alive: + client_keep_alive: description: - Enable/disable allowing the VPN client to keep the tunnel up when there is no traffic. + type: str choices: - disable - enable comments: description: - Comment. - default-gw: + type: str + default_gw: description: - IPv4 address of default route gateway to use for traffic exiting the interface. - default-gw-priority: + type: str + default_gw_priority: description: - Priority for default gateway route. A higher priority number signifies a less preferred route. + type: int dhgrp: description: - DH group. + type: str choices: - 1 - 2 @@ -233,254 +271,325 @@ options: - 29 - 30 - 31 - digital-signature-auth: + digital_signature_auth: description: - Enable/disable IKEv2 Digital Signature Authentication (RFC 7427). + type: str choices: - enable - disable distance: description: - Distance for routes added by IKE (1 - 255). - dns-mode: + type: int + dns_mode: description: - DNS server mode. + type: str choices: - manual - auto domain: description: - Instruct unity clients about the default DNS domain. + type: str dpd: description: - Dead Peer Detection mode. + type: str choices: - disable - on-idle - on-demand - dpd-retrycount: + dpd_retrycount: description: - Number of DPD retry attempts. - dpd-retryinterval: + type: int + dpd_retryinterval: description: - DPD retry interval. + type: str eap: description: - Enable/disable IKEv2 EAP authentication. + type: str choices: - enable - disable - eap-identity: + eap_identity: description: - IKEv2 EAP peer identity type. + type: str choices: - use-id-payload - send-request - encap-local-gw4: + encap_local_gw4: description: - Local IPv4 address of GRE/VXLAN tunnel. - encap-local-gw6: + type: str + encap_local_gw6: description: - Local IPv6 address of GRE/VXLAN tunnel. - encap-remote-gw4: + type: str + encap_remote_gw4: description: - Remote IPv4 address of GRE/VXLAN tunnel. - encap-remote-gw6: + type: str + encap_remote_gw6: description: - Remote IPv6 address of GRE/VXLAN tunnel. + type: str encapsulation: description: - Enable/disable GRE/VXLAN encapsulation. + type: str choices: - none - gre - vxlan - encapsulation-address: + encapsulation_address: description: - Source for GRE/VXLAN tunnel address. + type: str choices: - ike - ipv4 - ipv6 - enforce-unique-id: + enforce_unique_id: description: - Enable/disable peer ID uniqueness check. + type: str choices: - disable - keep-new - keep-old - exchange-interface-ip: + exchange_interface_ip: description: - Enable/disable exchange of IPsec interface IP address. + type: str choices: - enable - disable - forticlient-enforcement: + exchange_ip_addr4: + description: + - IPv4 address to exchange with peers. + type: str + exchange_ip_addr6: + description: + - IPv6 address to exchange with peers + type: str + forticlient_enforcement: description: - Enable/disable FortiClient enforcement. + type: str choices: - enable - disable fragmentation: description: - Enable/disable fragment IKE message on re-transmission. + type: str choices: - enable - disable - fragmentation-mtu: + fragmentation_mtu: description: - IKE fragmentation MTU (500 - 16000). - group-authentication: + type: int + group_authentication: description: - Enable/disable IKEv2 IDi group authentication. + type: str choices: - enable - disable - group-authentication-secret: + group_authentication_secret: description: - Password for IKEv2 IDi group authentication. (ASCII string or hexadecimal indicated by a leading 0x.) - ha-sync-esp-seqno: + type: str + ha_sync_esp_seqno: description: - Enable/disable sequence number jump ahead for IPsec HA. + type: str choices: - enable - disable - idle-timeout: + idle_timeout: description: - Enable/disable IPsec tunnel idle timeout. + type: str choices: - enable - disable - idle-timeoutinterval: + idle_timeoutinterval: description: - IPsec tunnel idle timeout in minutes (5 - 43200). - ike-version: + type: int + ike_version: description: - IKE protocol version. + type: str choices: - 1 - 2 - include-local-lan: + include_local_lan: description: - Enable/disable allow local LAN access on unity clients. + type: str choices: - disable - enable interface: description: - Local physical, aggregate, or VLAN outgoing interface. Source system.interface.name. - ip-version: + type: str + ip_version: description: - IP version to use for VPN interface. + type: str choices: - 4 - 6 - ipv4-dns-server1: + ipv4_dns_server1: description: - IPv4 DNS server 1. - ipv4-dns-server2: + type: str + ipv4_dns_server2: description: - IPv4 DNS server 2. - ipv4-dns-server3: + type: str + ipv4_dns_server3: description: - IPv4 DNS server 3. - ipv4-end-ip: + type: str + ipv4_end_ip: description: - End of IPv4 range. - ipv4-exclude-range: + type: str + ipv4_exclude_range: description: - Configuration Method IPv4 exclude ranges. + type: list suboptions: - end-ip: + end_ip: description: - End of IPv4 exclusive range. + type: str id: description: - ID. required: true - start-ip: + type: int + start_ip: description: - Start of IPv4 exclusive range. - ipv4-name: + type: str + ipv4_name: description: - IPv4 address name. Source firewall.address.name firewall.addrgrp.name. - ipv4-netmask: + type: str + ipv4_netmask: description: - IPv4 Netmask. - ipv4-split-exclude: + type: str + ipv4_split_exclude: description: - IPv4 subnets that should not be sent over the IPsec tunnel. Source firewall.address.name firewall.addrgrp.name. - ipv4-split-include: + type: str + ipv4_split_include: description: - IPv4 split-include subnets. Source firewall.address.name firewall.addrgrp.name. - ipv4-start-ip: + type: str + ipv4_start_ip: description: - Start of IPv4 range. - ipv4-wins-server1: + type: str + ipv4_wins_server1: description: - WINS server 1. - ipv4-wins-server2: + type: str + ipv4_wins_server2: description: - WINS server 2. - ipv6-dns-server1: + type: str + ipv6_dns_server1: description: - IPv6 DNS server 1. - ipv6-dns-server2: + type: str + ipv6_dns_server2: description: - IPv6 DNS server 2. - ipv6-dns-server3: + type: str + ipv6_dns_server3: description: - IPv6 DNS server 3. - ipv6-end-ip: + type: str + ipv6_end_ip: description: - End of IPv6 range. - ipv6-exclude-range: + type: str + ipv6_exclude_range: description: - Configuration method IPv6 exclude ranges. + type: list suboptions: - end-ip: + end_ip: description: - End of IPv6 exclusive range. + type: str id: description: - ID. required: true - start-ip: + type: int + start_ip: description: - Start of IPv6 exclusive range. - ipv6-name: + type: str + ipv6_name: description: - IPv6 address name. Source firewall.address6.name firewall.addrgrp6.name. - ipv6-prefix: + type: str + ipv6_prefix: description: - IPv6 prefix. - ipv6-split-exclude: + type: int + ipv6_split_exclude: description: - IPv6 subnets that should not be sent over the IPsec tunnel. Source firewall.address6.name firewall.addrgrp6.name. - ipv6-split-include: + type: str + ipv6_split_include: description: - IPv6 split-include subnets. Source firewall.address6.name firewall.addrgrp6.name. - ipv6-start-ip: + type: str + ipv6_start_ip: description: - Start of IPv6 range. + type: str keepalive: description: - NAT-T keep alive interval. + type: int keylife: description: - Time to wait in seconds before phase 1 encryption key expires. - local-gw: + type: int + local_gw: description: - IPv4 address of the local gateway's external interface. - local-gw6: + type: str + local_gw6: description: - IPv6 address of the local gateway's external interface. + type: str localid: description: - Local ID. - localid-type: + type: str + localid_type: description: - Local ID type. + type: str choices: - auto - fqdn @@ -488,9 +597,10 @@ options: - keyid - address - asn1dn - mesh-selector-type: + mesh_selector_type: description: - Add selectors containing subsets of the configuration depending on traffic. + type: str choices: - disable - subnet @@ -498,34 +608,41 @@ options: mode: description: - The ID protection mode used to establish a secure channel. + type: str choices: - aggressive - main - mode-cfg: + mode_cfg: description: - Enable/disable configuration method. + type: str choices: - disable - enable monitor: description: - IPsec interface as backup for primary interface. Source vpn.ipsec.phase1-interface.name. - monitor-hold-down-delay: + type: str + monitor_hold_down_delay: description: - Time to wait in seconds before recovery once primary re-establishes. - monitor-hold-down-time: + type: int + monitor_hold_down_time: description: - Time of day at which to fail back to primary after it re-establishes. - monitor-hold-down-type: + type: str + monitor_hold_down_type: description: - Recovery time method when primary interface re-establishes. + type: str choices: - immediate - delay - time - monitor-hold-down-weekday: + monitor_hold_down_weekday: description: - Day of the week to recover once primary re-establishes. + type: str choices: - everyday - sunday @@ -539,46 +656,49 @@ options: description: - IPsec remote gateway name. required: true + type: str nattraversal: description: - Enable/disable NAT traversal. + type: str choices: - enable - disable - forced - negotiate-timeout: + negotiate_timeout: description: - IKE SA negotiation timeout in seconds (1 - 300). - net-device: + type: int + net_device: description: - Enable/disable kernel device creation for dialup instances. + type: str choices: - enable - disable - npu-offload: - description: - - Enable/disable offloading NPU. - choices: - - enable - - disable - passive-mode: + passive_mode: description: - Enable/disable IPsec passive mode for static tunnels. + type: str choices: - enable - disable peer: description: - Accept this peer certificate. Source user.peer.name. + type: str peergrp: description: - Accept this peer certificate group. Source user.peergrp.name. + type: str peerid: description: - Accept this peer identity. + type: str peertype: description: - Accept this peer type. + type: str choices: - any - one @@ -588,22 +708,27 @@ options: ppk: description: - Enable/disable IKEv2 Postquantum Preshared Key (PPK). + type: str choices: - disable - allow - require - ppk-identity: + ppk_identity: description: - IKEv2 Postquantum Preshared Key Identity. - ppk-secret: + type: str + ppk_secret: description: - IKEv2 Postquantum Preshared Key (ASCII string or hexadecimal encoded with a leading 0x). + type: str priority: description: - Priority for routes added by IKE (0 - 4294967295). + type: int proposal: description: - Phase1 proposal. + type: str choices: - des-md5 - des-sha1 @@ -613,94 +738,113 @@ options: psksecret: description: - Pre-shared secret for PSK authentication (ASCII string or hexadecimal encoded with a leading 0x). - psksecret-remote: + type: str + psksecret_remote: description: - Pre-shared secret for remote side PSK authentication (ASCII string or hexadecimal encoded with a leading 0x). + type: str reauth: description: - Enable/disable re-authentication upon IKE SA lifetime expiration. + type: str choices: - disable - enable rekey: description: - Enable/disable phase1 rekey. + type: str choices: - enable - disable - remote-gw: + remote_gw: description: - IPv4 address of the remote gateway's external interface. - remote-gw6: + type: str + remote_gw6: description: - IPv6 address of the remote gateway's external interface. - remotegw-ddns: + type: str + remotegw_ddns: description: - Domain name of remote gateway (eg. name.DDNS.com). - rsa-signature-format: + type: str + rsa_signature_format: description: - Digital Signature Authentication RSA signature format. + type: str choices: - pkcs1 - pss - save-password: + save_password: description: - Enable/disable saving XAuth username and password on VPN clients. + type: str choices: - disable - enable - send-cert-chain: + send_cert_chain: description: - Enable/disable sending certificate chain. + type: str choices: - enable - disable - signature-hash-alg: + signature_hash_alg: description: - Digital Signature Authentication hash algorithms. + type: str choices: - sha1 - sha2-256 - sha2-384 - sha2-512 - split-include-service: + split_include_service: description: - Split-include services. Source firewall.service.group.name firewall.service.custom.name. - suite-b: + type: str + suite_b: description: - Use Suite-B. + type: str choices: - disable - suite-b-gcm-128 - suite-b-gcm-256 - tunnel-search: + tunnel_search: description: - Tunnel search method for when the interface is shared. + type: str choices: - selectors - nexthop type: description: - Remote gateway type. + type: str choices: - static - dynamic - ddns - unity-support: + unity_support: description: - Enable/disable support for Cisco UNITY Configuration Method extensions. + type: str choices: - disable - enable usrgrp: description: - User group name for dialup peers. Source user.group.name. + type: str vni: description: - VNI of VXLAN tunnel. - wizard-type: + type: int + wizard_type: description: - GUI VPN Wizard Type. + type: str choices: - custom - dialup-forticlient @@ -715,6 +859,7 @@ options: xauthtype: description: - XAuth type. + type: str choices: - disable - client @@ -730,6 +875,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure VPN remote gateway. fortios_vpn_ipsec_phase1_interface: @@ -738,145 +884,146 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" vpn_ipsec_phase1_interface: - state: "present" - acct-verify: "enable" - add-gw-route: "enable" - add-route: "disable" - assign-ip: "disable" - assign-ip-from: "range" + acct_verify: "enable" + add_gw_route: "enable" + add_route: "disable" + assign_ip: "disable" + assign_ip_from: "range" authmethod: "psk" - authmethod-remote: "psk" + authmethod_remote: "psk" authpasswd: "" authusr: "" authusrgrp: " (source user.group.name)" - auto-discovery-forwarder: "enable" - auto-discovery-psk: "enable" - auto-discovery-receiver: "enable" - auto-discovery-sender: "enable" - auto-negotiate: "enable" - backup-gateway: + auto_discovery_forwarder: "enable" + auto_discovery_psk: "enable" + auto_discovery_receiver: "enable" + auto_discovery_sender: "enable" + auto_negotiate: "enable" + backup_gateway: - address: "" banner: "" - cert-id-validation: "enable" + cert_id_validation: "enable" certificate: - name: "default_name_23 (source vpn.certificate.local.name)" - childless-ike: "enable" - client-auto-negotiate: "disable" - client-keep-alive: "disable" + childless_ike: "enable" + client_auto_negotiate: "disable" + client_keep_alive: "disable" comments: "" - default-gw: "" - default-gw-priority: "29" + default_gw: "" + default_gw_priority: "29" dhgrp: "1" - digital-signature-auth: "enable" + digital_signature_auth: "enable" distance: "32" - dns-mode: "manual" + dns_mode: "manual" domain: "" dpd: "disable" - dpd-retrycount: "36" - dpd-retryinterval: "" + dpd_retrycount: "36" + dpd_retryinterval: "" eap: "enable" - eap-identity: "use-id-payload" - encap-local-gw4: "" - encap-local-gw6: "" - encap-remote-gw4: "" - encap-remote-gw6: "" + eap_identity: "use-id-payload" + encap_local_gw4: "" + encap_local_gw6: "" + encap_remote_gw4: "" + encap_remote_gw6: "" encapsulation: "none" - encapsulation-address: "ike" - enforce-unique-id: "disable" - exchange-interface-ip: "enable" - forticlient-enforcement: "enable" + encapsulation_address: "ike" + enforce_unique_id: "disable" + exchange_interface_ip: "enable" + exchange_ip_addr4: "" + exchange_ip_addr6: "" + forticlient_enforcement: "enable" fragmentation: "enable" - fragmentation-mtu: "50" - group-authentication: "enable" - group-authentication-secret: "" - ha-sync-esp-seqno: "enable" - idle-timeout: "enable" - idle-timeoutinterval: "55" - ike-version: "1" - include-local-lan: "disable" + fragmentation_mtu: "52" + group_authentication: "enable" + group_authentication_secret: "" + ha_sync_esp_seqno: "enable" + idle_timeout: "enable" + idle_timeoutinterval: "57" + ike_version: "1" + include_local_lan: "disable" interface: " (source system.interface.name)" - ip-version: "4" - ipv4-dns-server1: "" - ipv4-dns-server2: "" - ipv4-dns-server3: "" - ipv4-end-ip: "" - ipv4-exclude-range: + ip_version: "4" + ipv4_dns_server1: "" + ipv4_dns_server2: "" + ipv4_dns_server3: "" + ipv4_end_ip: "" + ipv4_exclude_range: - - end-ip: "" - id: "66" - start-ip: "" - ipv4-name: " (source firewall.address.name firewall.addrgrp.name)" - ipv4-netmask: "" - ipv4-split-exclude: " (source firewall.address.name firewall.addrgrp.name)" - ipv4-split-include: " (source firewall.address.name firewall.addrgrp.name)" - ipv4-start-ip: "" - ipv4-wins-server1: "" - ipv4-wins-server2: "" - ipv6-dns-server1: "" - ipv6-dns-server2: "" - ipv6-dns-server3: "" - ipv6-end-ip: "" - ipv6-exclude-range: + end_ip: "" + id: "68" + start_ip: "" + ipv4_name: " (source firewall.address.name firewall.addrgrp.name)" + ipv4_netmask: "" + ipv4_split_exclude: " (source firewall.address.name firewall.addrgrp.name)" + ipv4_split_include: " (source firewall.address.name firewall.addrgrp.name)" + ipv4_start_ip: "" + ipv4_wins_server1: "" + ipv4_wins_server2: "" + ipv6_dns_server1: "" + ipv6_dns_server2: "" + ipv6_dns_server3: "" + ipv6_end_ip: "" + ipv6_exclude_range: - - end-ip: "" - id: "81" - start-ip: "" - ipv6-name: " (source firewall.address6.name firewall.addrgrp6.name)" - ipv6-prefix: "84" - ipv6-split-exclude: " (source firewall.address6.name firewall.addrgrp6.name)" - ipv6-split-include: " (source firewall.address6.name firewall.addrgrp6.name)" - ipv6-start-ip: "" - keepalive: "88" - keylife: "89" - local-gw: "" - local-gw6: "" + end_ip: "" + id: "83" + start_ip: "" + ipv6_name: " (source firewall.address6.name firewall.addrgrp6.name)" + ipv6_prefix: "86" + ipv6_split_exclude: " (source firewall.address6.name firewall.addrgrp6.name)" + ipv6_split_include: " (source firewall.address6.name firewall.addrgrp6.name)" + ipv6_start_ip: "" + keepalive: "90" + keylife: "91" + local_gw: "" + local_gw6: "" localid: "" - localid-type: "auto" - mesh-selector-type: "disable" + localid_type: "auto" + mesh_selector_type: "disable" mode: "aggressive" - mode-cfg: "disable" + mode_cfg: "disable" monitor: " (source vpn.ipsec.phase1-interface.name)" - monitor-hold-down-delay: "98" - monitor-hold-down-time: "" - monitor-hold-down-type: "immediate" - monitor-hold-down-weekday: "everyday" - name: "default_name_102" + monitor_hold_down_delay: "100" + monitor_hold_down_time: "" + monitor_hold_down_type: "immediate" + monitor_hold_down_weekday: "everyday" + name: "default_name_104" nattraversal: "enable" - negotiate-timeout: "104" - net-device: "enable" - npu-offload: "enable" - passive-mode: "enable" + negotiate_timeout: "106" + net_device: "enable" + passive_mode: "enable" peer: " (source user.peer.name)" peergrp: " (source user.peergrp.name)" peerid: "" peertype: "any" ppk: "disable" - ppk-identity: "" - ppk-secret: "" - priority: "115" + ppk_identity: "" + ppk_secret: "" + priority: "116" proposal: "des-md5" psksecret: "" - psksecret-remote: "" + psksecret_remote: "" reauth: "disable" rekey: "enable" - remote-gw: "" - remote-gw6: "" - remotegw-ddns: "" - rsa-signature-format: "pkcs1" - save-password: "disable" - send-cert-chain: "enable" - signature-hash-alg: "sha1" - split-include-service: " (source firewall.service.group.name firewall.service.custom.name)" - suite-b: "disable" - tunnel-search: "selectors" + remote_gw: "" + remote_gw6: "" + remotegw_ddns: "" + rsa_signature_format: "pkcs1" + save_password: "disable" + send_cert_chain: "enable" + signature_hash_alg: "sha1" + split_include_service: " (source firewall.service.group.name firewall.service.custom.name)" + suite_b: "disable" + tunnel_search: "selectors" type: "static" - unity-support: "disable" + unity_support: "disable" usrgrp: " (source user.group.name)" - vni: "134" - wizard-type: "custom" + vni: "135" + wizard_type: "custom" xauthtype: "disable" ''' @@ -940,14 +1087,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -fos = None - -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -955,52 +1104,53 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ipsec_phase1_interface_data(json): - option_list = ['acct-verify', 'add-gw-route', 'add-route', - 'assign-ip', 'assign-ip-from', 'authmethod', - 'authmethod-remote', 'authpasswd', 'authusr', - 'authusrgrp', 'auto-discovery-forwarder', 'auto-discovery-psk', - 'auto-discovery-receiver', 'auto-discovery-sender', 'auto-negotiate', - 'backup-gateway', 'banner', 'cert-id-validation', - 'certificate', 'childless-ike', 'client-auto-negotiate', - 'client-keep-alive', 'comments', 'default-gw', - 'default-gw-priority', 'dhgrp', 'digital-signature-auth', - 'distance', 'dns-mode', 'domain', - 'dpd', 'dpd-retrycount', 'dpd-retryinterval', - 'eap', 'eap-identity', 'encap-local-gw4', - 'encap-local-gw6', 'encap-remote-gw4', 'encap-remote-gw6', - 'encapsulation', 'encapsulation-address', 'enforce-unique-id', - 'exchange-interface-ip', 'forticlient-enforcement', 'fragmentation', - 'fragmentation-mtu', 'group-authentication', 'group-authentication-secret', - 'ha-sync-esp-seqno', 'idle-timeout', 'idle-timeoutinterval', - 'ike-version', 'include-local-lan', 'interface', - 'ip-version', 'ipv4-dns-server1', 'ipv4-dns-server2', - 'ipv4-dns-server3', 'ipv4-end-ip', 'ipv4-exclude-range', - 'ipv4-name', 'ipv4-netmask', 'ipv4-split-exclude', - 'ipv4-split-include', 'ipv4-start-ip', 'ipv4-wins-server1', - 'ipv4-wins-server2', 'ipv6-dns-server1', 'ipv6-dns-server2', - 'ipv6-dns-server3', 'ipv6-end-ip', 'ipv6-exclude-range', - 'ipv6-name', 'ipv6-prefix', 'ipv6-split-exclude', - 'ipv6-split-include', 'ipv6-start-ip', 'keepalive', - 'keylife', 'local-gw', 'local-gw6', - 'localid', 'localid-type', 'mesh-selector-type', - 'mode', 'mode-cfg', 'monitor', - 'monitor-hold-down-delay', 'monitor-hold-down-time', 'monitor-hold-down-type', - 'monitor-hold-down-weekday', 'name', 'nattraversal', - 'negotiate-timeout', 'net-device', 'npu-offload', - 'passive-mode', 'peer', 'peergrp', - 'peerid', 'peertype', 'ppk', - 'ppk-identity', 'ppk-secret', 'priority', - 'proposal', 'psksecret', 'psksecret-remote', - 'reauth', 'rekey', 'remote-gw', - 'remote-gw6', 'remotegw-ddns', 'rsa-signature-format', - 'save-password', 'send-cert-chain', 'signature-hash-alg', - 'split-include-service', 'suite-b', 'tunnel-search', - 'type', 'unity-support', 'usrgrp', - 'vni', 'wizard-type', 'xauthtype'] + option_list = ['acct_verify', 'add_gw_route', 'add_route', + 'assign_ip', 'assign_ip_from', 'authmethod', + 'authmethod_remote', 'authpasswd', 'authusr', + 'authusrgrp', 'auto_discovery_forwarder', 'auto_discovery_psk', + 'auto_discovery_receiver', 'auto_discovery_sender', 'auto_negotiate', + 'backup_gateway', 'banner', 'cert_id_validation', + 'certificate', 'childless_ike', 'client_auto_negotiate', + 'client_keep_alive', 'comments', 'default_gw', + 'default_gw_priority', 'dhgrp', 'digital_signature_auth', + 'distance', 'dns_mode', 'domain', + 'dpd', 'dpd_retrycount', 'dpd_retryinterval', + 'eap', 'eap_identity', 'encap_local_gw4', + 'encap_local_gw6', 'encap_remote_gw4', 'encap_remote_gw6', + 'encapsulation', 'encapsulation_address', 'enforce_unique_id', + 'exchange_interface_ip', 'exchange_ip_addr4', 'exchange_ip_addr6', + 'forticlient_enforcement', 'fragmentation', 'fragmentation_mtu', + 'group_authentication', 'group_authentication_secret', 'ha_sync_esp_seqno', + 'idle_timeout', 'idle_timeoutinterval', 'ike_version', + 'include_local_lan', 'interface', 'ip_version', + 'ipv4_dns_server1', 'ipv4_dns_server2', 'ipv4_dns_server3', + 'ipv4_end_ip', 'ipv4_exclude_range', 'ipv4_name', + 'ipv4_netmask', 'ipv4_split_exclude', 'ipv4_split_include', + 'ipv4_start_ip', 'ipv4_wins_server1', 'ipv4_wins_server2', + 'ipv6_dns_server1', 'ipv6_dns_server2', 'ipv6_dns_server3', + 'ipv6_end_ip', 'ipv6_exclude_range', 'ipv6_name', + 'ipv6_prefix', 'ipv6_split_exclude', 'ipv6_split_include', + 'ipv6_start_ip', 'keepalive', 'keylife', + 'local_gw', 'local_gw6', 'localid', + 'localid_type', 'mesh_selector_type', 'mode', + 'mode_cfg', 'monitor', 'monitor_hold_down_delay', + 'monitor_hold_down_time', 'monitor_hold_down_type', 'monitor_hold_down_weekday', + 'name', 'nattraversal', 'negotiate_timeout', + 'net_device', 'passive_mode', 'peer', + 'peergrp', 'peerid', 'peertype', + 'ppk', 'ppk_identity', 'ppk_secret', + 'priority', 'proposal', 'psksecret', + 'psksecret_remote', 'reauth', 'rekey', + 'remote_gw', 'remote_gw6', 'remotegw_ddns', + 'rsa_signature_format', 'save_password', 'send_cert_chain', + 'signature_hash_alg', 'split_include_service', 'suite_b', + 'tunnel_search', 'type', 'unity_support', + 'usrgrp', 'vni', 'wizard_type', + 'xauthtype'] dictionary = {} for attribute in option_list: @@ -1010,109 +1160,114 @@ def filter_vpn_ipsec_phase1_interface_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def vpn_ipsec_phase1_interface(data, fos): vdom = data['vdom'] + state = data['state'] vpn_ipsec_phase1_interface_data = data['vpn_ipsec_phase1_interface'] - flattened_data = flatten_multilists_attributes(vpn_ipsec_phase1_interface_data) - filtered_data = filter_vpn_ipsec_phase1_interface_data(flattened_data) - if vpn_ipsec_phase1_interface_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_vpn_ipsec_phase1_interface_data(vpn_ipsec_phase1_interface_data)) + + if state == "present": return fos.set('vpn.ipsec', 'phase1-interface', data=filtered_data, vdom=vdom) - elif vpn_ipsec_phase1_interface_data['state'] == "absent": + elif state == "absent": return fos.delete('vpn.ipsec', 'phase1-interface', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ipsec(data, fos): - login(data) if data['vpn_ipsec_phase1_interface']: resp = vpn_ipsec_phase1_interface(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "vpn_ipsec_phase1_interface": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "acct-verify": {"required": False, "type": "str", + "acct_verify": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "add-gw-route": {"required": False, "type": "str", + "add_gw_route": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "add-route": {"required": False, "type": "str", + "add_route": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "assign-ip": {"required": False, "type": "str", + "assign_ip": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "assign-ip-from": {"required": False, "type": "str", + "assign_ip_from": {"required": False, "type": "str", "choices": ["range", "usrgrp", "dhcp", "name"]}, "authmethod": {"required": False, "type": "str", "choices": ["psk", "signature"]}, - "authmethod-remote": {"required": False, "type": "str", + "authmethod_remote": {"required": False, "type": "str", "choices": ["psk", "signature"]}, "authpasswd": {"required": False, "type": "str"}, "authusr": {"required": False, "type": "str"}, "authusrgrp": {"required": False, "type": "str"}, - "auto-discovery-forwarder": {"required": False, "type": "str", + "auto_discovery_forwarder": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "auto-discovery-psk": {"required": False, "type": "str", + "auto_discovery_psk": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "auto-discovery-receiver": {"required": False, "type": "str", + "auto_discovery_receiver": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "auto-discovery-sender": {"required": False, "type": "str", + "auto_discovery_sender": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "auto-negotiate": {"required": False, "type": "str", + "auto_negotiate": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "backup-gateway": {"required": False, "type": "list", + "backup_gateway": {"required": False, "type": "list", "options": { "address": {"required": True, "type": "str"} }}, "banner": {"required": False, "type": "str"}, - "cert-id-validation": {"required": False, "type": "str", + "cert_id_validation": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "certificate": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "childless-ike": {"required": False, "type": "str", + "childless_ike": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "client-auto-negotiate": {"required": False, "type": "str", + "client_auto_negotiate": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "client-keep-alive": {"required": False, "type": "str", + "client_keep_alive": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "comments": {"required": False, "type": "str"}, - "default-gw": {"required": False, "type": "str"}, - "default-gw-priority": {"required": False, "type": "int"}, + "default_gw": {"required": False, "type": "str"}, + "default_gw_priority": {"required": False, "type": "int"}, "dhgrp": {"required": False, "type": "str", "choices": ["1", "2", "5", "14", "15", "16", @@ -1120,116 +1275,116 @@ def main(): "20", "21", "27", "28", "29", "30", "31"]}, - "digital-signature-auth": {"required": False, "type": "str", + "digital_signature_auth": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "distance": {"required": False, "type": "int"}, - "dns-mode": {"required": False, "type": "str", + "dns_mode": {"required": False, "type": "str", "choices": ["manual", "auto"]}, "domain": {"required": False, "type": "str"}, "dpd": {"required": False, "type": "str", "choices": ["disable", "on-idle", "on-demand"]}, - "dpd-retrycount": {"required": False, "type": "int"}, - "dpd-retryinterval": {"required": False, "type": "str"}, + "dpd_retrycount": {"required": False, "type": "int"}, + "dpd_retryinterval": {"required": False, "type": "str"}, "eap": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "eap-identity": {"required": False, "type": "str", + "eap_identity": {"required": False, "type": "str", "choices": ["use-id-payload", "send-request"]}, - "encap-local-gw4": {"required": False, "type": "str"}, - "encap-local-gw6": {"required": False, "type": "str"}, - "encap-remote-gw4": {"required": False, "type": "str"}, - "encap-remote-gw6": {"required": False, "type": "str"}, + "encap_local_gw4": {"required": False, "type": "str"}, + "encap_local_gw6": {"required": False, "type": "str"}, + "encap_remote_gw4": {"required": False, "type": "str"}, + "encap_remote_gw6": {"required": False, "type": "str"}, "encapsulation": {"required": False, "type": "str", "choices": ["none", "gre", "vxlan"]}, - "encapsulation-address": {"required": False, "type": "str", + "encapsulation_address": {"required": False, "type": "str", "choices": ["ike", "ipv4", "ipv6"]}, - "enforce-unique-id": {"required": False, "type": "str", + "enforce_unique_id": {"required": False, "type": "str", "choices": ["disable", "keep-new", "keep-old"]}, - "exchange-interface-ip": {"required": False, "type": "str", + "exchange_interface_ip": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "forticlient-enforcement": {"required": False, "type": "str", + "exchange_ip_addr4": {"required": False, "type": "str"}, + "exchange_ip_addr6": {"required": False, "type": "str"}, + "forticlient_enforcement": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "fragmentation": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "fragmentation-mtu": {"required": False, "type": "int"}, - "group-authentication": {"required": False, "type": "str", + "fragmentation_mtu": {"required": False, "type": "int"}, + "group_authentication": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "group-authentication-secret": {"required": False, "type": "password-3"}, - "ha-sync-esp-seqno": {"required": False, "type": "str", + "group_authentication_secret": {"required": False, "type": "str"}, + "ha_sync_esp_seqno": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "idle-timeout": {"required": False, "type": "str", + "idle_timeout": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "idle-timeoutinterval": {"required": False, "type": "int"}, - "ike-version": {"required": False, "type": "str", + "idle_timeoutinterval": {"required": False, "type": "int"}, + "ike_version": {"required": False, "type": "str", "choices": ["1", "2"]}, - "include-local-lan": {"required": False, "type": "str", + "include_local_lan": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "interface": {"required": False, "type": "str"}, - "ip-version": {"required": False, "type": "str", + "ip_version": {"required": False, "type": "str", "choices": ["4", "6"]}, - "ipv4-dns-server1": {"required": False, "type": "str"}, - "ipv4-dns-server2": {"required": False, "type": "str"}, - "ipv4-dns-server3": {"required": False, "type": "str"}, - "ipv4-end-ip": {"required": False, "type": "str"}, - "ipv4-exclude-range": {"required": False, "type": "list", + "ipv4_dns_server1": {"required": False, "type": "str"}, + "ipv4_dns_server2": {"required": False, "type": "str"}, + "ipv4_dns_server3": {"required": False, "type": "str"}, + "ipv4_end_ip": {"required": False, "type": "str"}, + "ipv4_exclude_range": {"required": False, "type": "list", "options": { - "end-ip": {"required": False, "type": "str"}, + "end_ip": {"required": False, "type": "str"}, "id": {"required": True, "type": "int"}, - "start-ip": {"required": False, "type": "str"} + "start_ip": {"required": False, "type": "str"} }}, - "ipv4-name": {"required": False, "type": "str"}, - "ipv4-netmask": {"required": False, "type": "str"}, - "ipv4-split-exclude": {"required": False, "type": "str"}, - "ipv4-split-include": {"required": False, "type": "str"}, - "ipv4-start-ip": {"required": False, "type": "str"}, - "ipv4-wins-server1": {"required": False, "type": "str"}, - "ipv4-wins-server2": {"required": False, "type": "str"}, - "ipv6-dns-server1": {"required": False, "type": "str"}, - "ipv6-dns-server2": {"required": False, "type": "str"}, - "ipv6-dns-server3": {"required": False, "type": "str"}, - "ipv6-end-ip": {"required": False, "type": "str"}, - "ipv6-exclude-range": {"required": False, "type": "list", + "ipv4_name": {"required": False, "type": "str"}, + "ipv4_netmask": {"required": False, "type": "str"}, + "ipv4_split_exclude": {"required": False, "type": "str"}, + "ipv4_split_include": {"required": False, "type": "str"}, + "ipv4_start_ip": {"required": False, "type": "str"}, + "ipv4_wins_server1": {"required": False, "type": "str"}, + "ipv4_wins_server2": {"required": False, "type": "str"}, + "ipv6_dns_server1": {"required": False, "type": "str"}, + "ipv6_dns_server2": {"required": False, "type": "str"}, + "ipv6_dns_server3": {"required": False, "type": "str"}, + "ipv6_end_ip": {"required": False, "type": "str"}, + "ipv6_exclude_range": {"required": False, "type": "list", "options": { - "end-ip": {"required": False, "type": "str"}, + "end_ip": {"required": False, "type": "str"}, "id": {"required": True, "type": "int"}, - "start-ip": {"required": False, "type": "str"} + "start_ip": {"required": False, "type": "str"} }}, - "ipv6-name": {"required": False, "type": "str"}, - "ipv6-prefix": {"required": False, "type": "int"}, - "ipv6-split-exclude": {"required": False, "type": "str"}, - "ipv6-split-include": {"required": False, "type": "str"}, - "ipv6-start-ip": {"required": False, "type": "str"}, + "ipv6_name": {"required": False, "type": "str"}, + "ipv6_prefix": {"required": False, "type": "int"}, + "ipv6_split_exclude": {"required": False, "type": "str"}, + "ipv6_split_include": {"required": False, "type": "str"}, + "ipv6_start_ip": {"required": False, "type": "str"}, "keepalive": {"required": False, "type": "int"}, "keylife": {"required": False, "type": "int"}, - "local-gw": {"required": False, "type": "str"}, - "local-gw6": {"required": False, "type": "str"}, + "local_gw": {"required": False, "type": "str"}, + "local_gw6": {"required": False, "type": "str"}, "localid": {"required": False, "type": "str"}, - "localid-type": {"required": False, "type": "str", + "localid_type": {"required": False, "type": "str", "choices": ["auto", "fqdn", "user-fqdn", "keyid", "address", "asn1dn"]}, - "mesh-selector-type": {"required": False, "type": "str", + "mesh_selector_type": {"required": False, "type": "str", "choices": ["disable", "subnet", "host"]}, "mode": {"required": False, "type": "str", "choices": ["aggressive", "main"]}, - "mode-cfg": {"required": False, "type": "str", + "mode_cfg": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "monitor": {"required": False, "type": "str"}, - "monitor-hold-down-delay": {"required": False, "type": "int"}, - "monitor-hold-down-time": {"required": False, "type": "str"}, - "monitor-hold-down-type": {"required": False, "type": "str", + "monitor_hold_down_delay": {"required": False, "type": "int"}, + "monitor_hold_down_time": {"required": False, "type": "str"}, + "monitor_hold_down_type": {"required": False, "type": "str", "choices": ["immediate", "delay", "time"]}, - "monitor-hold-down-weekday": {"required": False, "type": "str", + "monitor_hold_down_weekday": {"required": False, "type": "str", "choices": ["everyday", "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]}, "name": {"required": True, "type": "str"}, "nattraversal": {"required": False, "type": "str", "choices": ["enable", "disable", "forced"]}, - "negotiate-timeout": {"required": False, "type": "int"}, - "net-device": {"required": False, "type": "str", + "negotiate_timeout": {"required": False, "type": "int"}, + "net_device": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "npu-offload": {"required": False, "type": "str", - "choices": ["enable", "disable"]}, - "passive-mode": {"required": False, "type": "str", + "passive_mode": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "peer": {"required": False, "type": "str"}, "peergrp": {"required": False, "type": "str"}, @@ -1239,42 +1394,42 @@ def main(): "peer", "peergrp"]}, "ppk": {"required": False, "type": "str", "choices": ["disable", "allow", "require"]}, - "ppk-identity": {"required": False, "type": "str"}, - "ppk-secret": {"required": False, "type": "password-3"}, + "ppk_identity": {"required": False, "type": "str"}, + "ppk_secret": {"required": False, "type": "str"}, "priority": {"required": False, "type": "int"}, "proposal": {"required": False, "type": "str", "choices": ["des-md5", "des-sha1", "des-sha256", "des-sha384", "des-sha512"]}, - "psksecret": {"required": False, "type": "password-3"}, - "psksecret-remote": {"required": False, "type": "password-3"}, + "psksecret": {"required": False, "type": "str"}, + "psksecret_remote": {"required": False, "type": "str"}, "reauth": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "rekey": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "remote-gw": {"required": False, "type": "str"}, - "remote-gw6": {"required": False, "type": "str"}, - "remotegw-ddns": {"required": False, "type": "str"}, - "rsa-signature-format": {"required": False, "type": "str", + "remote_gw": {"required": False, "type": "str"}, + "remote_gw6": {"required": False, "type": "str"}, + "remotegw_ddns": {"required": False, "type": "str"}, + "rsa_signature_format": {"required": False, "type": "str", "choices": ["pkcs1", "pss"]}, - "save-password": {"required": False, "type": "str", + "save_password": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "send-cert-chain": {"required": False, "type": "str", + "send_cert_chain": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "signature-hash-alg": {"required": False, "type": "str", + "signature_hash_alg": {"required": False, "type": "str", "choices": ["sha1", "sha2-256", "sha2-384", "sha2-512"]}, - "split-include-service": {"required": False, "type": "str"}, - "suite-b": {"required": False, "type": "str", + "split_include_service": {"required": False, "type": "str"}, + "suite_b": {"required": False, "type": "str", "choices": ["disable", "suite-b-gcm-128", "suite-b-gcm-256"]}, - "tunnel-search": {"required": False, "type": "str", + "tunnel_search": {"required": False, "type": "str", "choices": ["selectors", "nexthop"]}, "type": {"required": False, "type": "str", "choices": ["static", "dynamic", "ddns"]}, - "unity-support": {"required": False, "type": "str", + "unity_support": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "usrgrp": {"required": False, "type": "str"}, "vni": {"required": False, "type": "int"}, - "wizard-type": {"required": False, "type": "str", + "wizard_type": {"required": False, "type": "str", "choices": ["custom", "dialup-forticlient", "dialup-ios", "dialup-android", "dialup-windows", "dialup-cisco", "static-fortigate", "dialup-fortigate", "static-cisco", @@ -1289,15 +1444,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2.py b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2.py index 09ecef14bc3..10ffad9be13 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ipsec_phase2 short_description: Configure VPN autokey tunnel in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ipsec feature and phase2 category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,65 +41,83 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 vpn_ipsec_phase2: description: - Configure VPN autokey tunnel. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - add-route: + add_route: description: - Enable/disable automatic route addition. + type: str choices: - phase1 - enable - disable - auto-negotiate: + auto_negotiate: description: - Enable/disable IPsec SA auto-negotiation. + type: str choices: - enable - disable comments: description: - Comment. - dhcp-ipsec: + type: str + dhcp_ipsec: description: - Enable/disable DHCP-IPsec. + type: str choices: - enable - disable dhgrp: description: - Phase2 DH group. + type: str choices: - 1 - 2 @@ -120,56 +135,69 @@ options: - 29 - 30 - 31 - dst-addr-type: + dst_addr_type: description: - Remote proxy ID type. + type: str choices: - subnet - range - ip - name - dst-end-ip: + dst_end_ip: description: - Remote proxy ID IPv4 end. - dst-end-ip6: + type: str + dst_end_ip6: description: - Remote proxy ID IPv6 end. - dst-name: + type: str + dst_name: description: - Remote proxy ID name. Source firewall.address.name firewall.addrgrp.name. - dst-name6: + type: str + dst_name6: description: - Remote proxy ID name. Source firewall.address6.name firewall.addrgrp6.name. - dst-port: + type: str + dst_port: description: - Quick mode destination port (1 - 65535 or 0 for all). - dst-start-ip: + type: int + dst_start_ip: description: - Remote proxy ID IPv4 start. - dst-start-ip6: + type: str + dst_start_ip6: description: - Remote proxy ID IPv6 start. - dst-subnet: + type: str + dst_subnet: description: - Remote proxy ID IPv4 subnet. - dst-subnet6: + type: str + dst_subnet6: description: - Remote proxy ID IPv6 subnet. + type: str encapsulation: description: - ESP encapsulation mode. + type: str choices: - tunnel-mode - transport-mode keepalive: description: - Enable/disable keep alive. + type: str choices: - enable - disable - keylife-type: + keylife_type: description: - Keylife type. + type: str choices: - seconds - kbs @@ -177,12 +205,15 @@ options: keylifekbs: description: - Phase2 key life in number of bytes of traffic (5120 - 4294967295). + type: int keylifeseconds: description: - Phase2 key life in time in seconds (120 - 172800). + type: int l2tp: description: - Enable/disable L2TP over IPsec. + type: str choices: - enable - disable @@ -190,18 +221,22 @@ options: description: - IPsec tunnel name. required: true + type: str pfs: description: - Enable/disable PFS feature. + type: str choices: - enable - disable phase1name: description: - Phase 1 determines the options required for phase 2. Source vpn.ipsec.phase1.name. + type: str proposal: description: - Phase2 proposal. + type: str choices: - null-md5 - null-sha1 @@ -217,70 +252,86 @@ options: protocol: description: - Quick mode protocol selector (1 - 255 or 0 for all). + type: int replay: description: - Enable/disable replay detection. + type: str choices: - enable - disable - route-overlap: + route_overlap: description: - Action for overlapping routes. + type: str choices: - use-old - use-new - allow - selector-match: + selector_match: description: - Match type to use when comparing selectors. + type: str choices: - exact - subset - auto - single-source: + single_source: description: - Enable/disable single source IP restriction. + type: str choices: - enable - disable - src-addr-type: + src_addr_type: description: - Local proxy ID type. + type: str choices: - subnet - range - ip - name - src-end-ip: + src_end_ip: description: - Local proxy ID end. - src-end-ip6: + type: str + src_end_ip6: description: - Local proxy ID IPv6 end. - src-name: + type: str + src_name: description: - Local proxy ID name. Source firewall.address.name firewall.addrgrp.name. - src-name6: + type: str + src_name6: description: - Local proxy ID name. Source firewall.address6.name firewall.addrgrp6.name. - src-port: + type: str + src_port: description: - Quick mode source port (1 - 65535 or 0 for all). - src-start-ip: + type: int + src_start_ip: description: - Local proxy ID start. - src-start-ip6: + type: str + src_start_ip6: description: - Local proxy ID IPv6 start. - src-subnet: + type: str + src_subnet: description: - Local proxy ID subnet. - src-subnet6: + type: str + src_subnet6: description: - Local proxy ID IPv6 subnet. - use-natip: + type: str + use_natip: description: - Enable to use the FortiGate public IP as the source selector when outbound NAT is used. + type: str choices: - enable - disable @@ -293,6 +344,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure VPN autokey tunnel. fortios_vpn_ipsec_phase2: @@ -301,26 +353,26 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" vpn_ipsec_phase2: - state: "present" - add-route: "phase1" - auto-negotiate: "enable" + add_route: "phase1" + auto_negotiate: "enable" comments: "" - dhcp-ipsec: "enable" + dhcp_ipsec: "enable" dhgrp: "1" - dst-addr-type: "subnet" - dst-end-ip: "" - dst-end-ip6: "" - dst-name: " (source firewall.address.name firewall.addrgrp.name)" - dst-name6: " (source firewall.address6.name firewall.addrgrp6.name)" - dst-port: "13" - dst-start-ip: "" - dst-start-ip6: "" - dst-subnet: "" - dst-subnet6: "" + dst_addr_type: "subnet" + dst_end_ip: "" + dst_end_ip6: "" + dst_name: " (source firewall.address.name firewall.addrgrp.name)" + dst_name6: " (source firewall.address6.name firewall.addrgrp6.name)" + dst_port: "13" + dst_start_ip: "" + dst_start_ip6: "" + dst_subnet: "" + dst_subnet6: "" encapsulation: "tunnel-mode" keepalive: "enable" - keylife-type: "seconds" + keylife_type: "seconds" keylifekbs: "21" keylifeseconds: "22" l2tp: "enable" @@ -330,20 +382,20 @@ EXAMPLES = ''' proposal: "null-md5" protocol: "28" replay: "enable" - route-overlap: "use-old" - selector-match: "exact" - single-source: "enable" - src-addr-type: "subnet" - src-end-ip: "" - src-end-ip6: "" - src-name: " (source firewall.address.name firewall.addrgrp.name)" - src-name6: " (source firewall.address6.name firewall.addrgrp6.name)" - src-port: "38" - src-start-ip: "" - src-start-ip6: "" - src-subnet: "" - src-subnet6: "" - use-natip: "enable" + route_overlap: "use-old" + selector_match: "exact" + single_source: "enable" + src_addr_type: "subnet" + src_end_ip: "" + src_end_ip6: "" + src_name: " (source firewall.address.name firewall.addrgrp.name)" + src_name6: " (source firewall.address6.name firewall.addrgrp6.name)" + src_port: "38" + src_start_ip: "" + src_start_ip6: "" + src_subnet: "" + src_subnet6: "" + use_natip: "enable" ''' RETURN = ''' @@ -406,14 +458,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule - -fos = None +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -421,24 +475,24 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ipsec_phase2_data(json): - option_list = ['add-route', 'auto-negotiate', 'comments', - 'dhcp-ipsec', 'dhgrp', 'dst-addr-type', - 'dst-end-ip', 'dst-end-ip6', 'dst-name', - 'dst-name6', 'dst-port', 'dst-start-ip', - 'dst-start-ip6', 'dst-subnet', 'dst-subnet6', - 'encapsulation', 'keepalive', 'keylife-type', + option_list = ['add_route', 'auto_negotiate', 'comments', + 'dhcp_ipsec', 'dhgrp', 'dst_addr_type', + 'dst_end_ip', 'dst_end_ip6', 'dst_name', + 'dst_name6', 'dst_port', 'dst_start_ip', + 'dst_start_ip6', 'dst_subnet', 'dst_subnet6', + 'encapsulation', 'keepalive', 'keylife_type', 'keylifekbs', 'keylifeseconds', 'l2tp', 'name', 'pfs', 'phase1name', 'proposal', 'protocol', 'replay', - 'route-overlap', 'selector-match', 'single-source', - 'src-addr-type', 'src-end-ip', 'src-end-ip6', - 'src-name', 'src-name6', 'src-port', - 'src-start-ip', 'src-start-ip6', 'src-subnet', - 'src-subnet6', 'use-natip'] + 'route_overlap', 'selector_match', 'single_source', + 'src_addr_type', 'src_end_ip', 'src_end_ip6', + 'src_name', 'src_name6', 'src_port', + 'src_start_ip', 'src_start_ip6', 'src_subnet', + 'src_subnet6', 'use_natip'] dictionary = {} for attribute in option_list: @@ -448,67 +502,72 @@ def filter_vpn_ipsec_phase2_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def vpn_ipsec_phase2(data, fos): vdom = data['vdom'] + state = data['state'] vpn_ipsec_phase2_data = data['vpn_ipsec_phase2'] - flattened_data = flatten_multilists_attributes(vpn_ipsec_phase2_data) - filtered_data = filter_vpn_ipsec_phase2_data(flattened_data) - if vpn_ipsec_phase2_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_vpn_ipsec_phase2_data(vpn_ipsec_phase2_data)) + + if state == "present": return fos.set('vpn.ipsec', 'phase2', data=filtered_data, vdom=vdom) - elif vpn_ipsec_phase2_data['state'] == "absent": + elif state == "absent": return fos.delete('vpn.ipsec', 'phase2', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ipsec(data, fos): - login(data) if data['vpn_ipsec_phase2']: resp = vpn_ipsec_phase2(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "vpn_ipsec_phase2": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "add-route": {"required": False, "type": "str", + "add_route": {"required": False, "type": "str", "choices": ["phase1", "enable", "disable"]}, - "auto-negotiate": {"required": False, "type": "str", + "auto_negotiate": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "comments": {"required": False, "type": "str"}, - "dhcp-ipsec": {"required": False, "type": "str", + "dhcp_ipsec": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "dhgrp": {"required": False, "type": "str", "choices": ["1", "2", "5", @@ -517,23 +576,23 @@ def main(): "20", "21", "27", "28", "29", "30", "31"]}, - "dst-addr-type": {"required": False, "type": "str", + "dst_addr_type": {"required": False, "type": "str", "choices": ["subnet", "range", "ip", "name"]}, - "dst-end-ip": {"required": False, "type": "str"}, - "dst-end-ip6": {"required": False, "type": "str"}, - "dst-name": {"required": False, "type": "str"}, - "dst-name6": {"required": False, "type": "str"}, - "dst-port": {"required": False, "type": "int"}, - "dst-start-ip": {"required": False, "type": "str"}, - "dst-start-ip6": {"required": False, "type": "str"}, - "dst-subnet": {"required": False, "type": "str"}, - "dst-subnet6": {"required": False, "type": "str"}, + "dst_end_ip": {"required": False, "type": "str"}, + "dst_end_ip6": {"required": False, "type": "str"}, + "dst_name": {"required": False, "type": "str"}, + "dst_name6": {"required": False, "type": "str"}, + "dst_port": {"required": False, "type": "int"}, + "dst_start_ip": {"required": False, "type": "str"}, + "dst_start_ip6": {"required": False, "type": "str"}, + "dst_subnet": {"required": False, "type": "str"}, + "dst_subnet6": {"required": False, "type": "str"}, "encapsulation": {"required": False, "type": "str", "choices": ["tunnel-mode", "transport-mode"]}, "keepalive": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "keylife-type": {"required": False, "type": "str", + "keylife_type": {"required": False, "type": "str", "choices": ["seconds", "kbs", "both"]}, "keylifekbs": {"required": False, "type": "int"}, "keylifeseconds": {"required": False, "type": "int"}, @@ -551,25 +610,25 @@ def main(): "protocol": {"required": False, "type": "int"}, "replay": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "route-overlap": {"required": False, "type": "str", + "route_overlap": {"required": False, "type": "str", "choices": ["use-old", "use-new", "allow"]}, - "selector-match": {"required": False, "type": "str", + "selector_match": {"required": False, "type": "str", "choices": ["exact", "subset", "auto"]}, - "single-source": {"required": False, "type": "str", + "single_source": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "src-addr-type": {"required": False, "type": "str", + "src_addr_type": {"required": False, "type": "str", "choices": ["subnet", "range", "ip", "name"]}, - "src-end-ip": {"required": False, "type": "str"}, - "src-end-ip6": {"required": False, "type": "str"}, - "src-name": {"required": False, "type": "str"}, - "src-name6": {"required": False, "type": "str"}, - "src-port": {"required": False, "type": "int"}, - "src-start-ip": {"required": False, "type": "str"}, - "src-start-ip6": {"required": False, "type": "str"}, - "src-subnet": {"required": False, "type": "str"}, - "src-subnet6": {"required": False, "type": "str"}, - "use-natip": {"required": False, "type": "str", + "src_end_ip": {"required": False, "type": "str"}, + "src_end_ip6": {"required": False, "type": "str"}, + "src_name": {"required": False, "type": "str"}, + "src_name6": {"required": False, "type": "str"}, + "src_port": {"required": False, "type": "int"}, + "src_start_ip": {"required": False, "type": "str"}, + "src_start_ip6": {"required": False, "type": "str"}, + "src_subnet": {"required": False, "type": "str"}, + "src_subnet6": {"required": False, "type": "str"}, + "use_natip": {"required": False, "type": "str", "choices": ["enable", "disable"]} } @@ -578,15 +637,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2_interface.py b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2_interface.py index 1c55117855e..8f4158b5dc2 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2_interface.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2_interface.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ipsec_phase2_interface short_description: Configure VPN autokey tunnel in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ipsec feature and phase2_interface category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,79 +41,99 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 vpn_ipsec_phase2_interface: description: - Configure VPN autokey tunnel. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - add-route: + add_route: description: - Enable/disable automatic route addition. + type: str choices: - phase1 - enable - disable - auto-discovery-forwarder: + auto_discovery_forwarder: description: - Enable/disable forwarding short-cut messages. + type: str choices: - phase1 - enable - disable - auto-discovery-sender: + auto_discovery_sender: description: - Enable/disable sending short-cut messages. + type: str choices: - phase1 - enable - disable - auto-negotiate: + auto_negotiate: description: - Enable/disable IPsec SA auto-negotiation. + type: str choices: - enable - disable comments: description: - Comment. - dhcp-ipsec: + type: str + dhcp_ipsec: description: - Enable/disable DHCP-IPsec. + type: str choices: - enable - disable dhgrp: description: - Phase2 DH group. + type: str choices: - 1 - 2 @@ -134,9 +151,10 @@ options: - 29 - 30 - 31 - dst-addr-type: + dst_addr_type: description: - Remote proxy ID type. + type: str choices: - subnet - range @@ -146,48 +164,60 @@ options: - range6 - ip6 - name6 - dst-end-ip: + dst_end_ip: description: - Remote proxy ID IPv4 end. - dst-end-ip6: + type: str + dst_end_ip6: description: - Remote proxy ID IPv6 end. - dst-name: + type: str + dst_name: description: - Remote proxy ID name. Source firewall.address.name firewall.addrgrp.name. - dst-name6: + type: str + dst_name6: description: - Remote proxy ID name. Source firewall.address6.name firewall.addrgrp6.name. - dst-port: + type: str + dst_port: description: - Quick mode destination port (1 - 65535 or 0 for all). - dst-start-ip: + type: int + dst_start_ip: description: - Remote proxy ID IPv4 start. - dst-start-ip6: + type: str + dst_start_ip6: description: - Remote proxy ID IPv6 start. - dst-subnet: + type: str + dst_subnet: description: - Remote proxy ID IPv4 subnet. - dst-subnet6: + type: str + dst_subnet6: description: - Remote proxy ID IPv6 subnet. + type: str encapsulation: description: - ESP encapsulation mode. + type: str choices: - tunnel-mode - transport-mode keepalive: description: - Enable/disable keep alive. + type: str choices: - enable - disable - keylife-type: + keylife_type: description: - Keylife type. + type: str choices: - seconds - kbs @@ -195,12 +225,15 @@ options: keylifekbs: description: - Phase2 key life in number of bytes of traffic (5120 - 4294967295). + type: int keylifeseconds: description: - Phase2 key life in time in seconds (120 - 172800). + type: int l2tp: description: - Enable/disable L2TP over IPsec. + type: str choices: - enable - disable @@ -208,18 +241,22 @@ options: description: - IPsec tunnel name. required: true + type: str pfs: description: - Enable/disable PFS feature. + type: str choices: - enable - disable phase1name: description: - Phase 1 determines the options required for phase 2. Source vpn.ipsec.phase1-interface.name. + type: str proposal: description: - Phase2 proposal. + type: str choices: - null-md5 - null-sha1 @@ -235,28 +272,33 @@ options: protocol: description: - Quick mode protocol selector (1 - 255 or 0 for all). + type: int replay: description: - Enable/disable replay detection. + type: str choices: - enable - disable - route-overlap: + route_overlap: description: - Action for overlapping routes. + type: str choices: - use-old - use-new - allow - single-source: + single_source: description: - Enable/disable single source IP restriction. + type: str choices: - enable - disable - src-addr-type: + src_addr_type: description: - Local proxy ID type. + type: str choices: - subnet - range @@ -266,33 +308,42 @@ options: - range6 - ip6 - name6 - src-end-ip: + src_end_ip: description: - Local proxy ID end. - src-end-ip6: + type: str + src_end_ip6: description: - Local proxy ID IPv6 end. - src-name: + type: str + src_name: description: - Local proxy ID name. Source firewall.address.name firewall.addrgrp.name. - src-name6: + type: str + src_name6: description: - Local proxy ID name. Source firewall.address6.name firewall.addrgrp6.name. - src-port: + type: str + src_port: description: - Quick mode source port (1 - 65535 or 0 for all). - src-start-ip: + type: int + src_start_ip: description: - Local proxy ID start. - src-start-ip6: + type: str + src_start_ip6: description: - Local proxy ID IPv6 start. - src-subnet: + type: str + src_subnet: description: - Local proxy ID subnet. - src-subnet6: + type: str + src_subnet6: description: - Local proxy ID IPv6 subnet. + type: str ''' EXAMPLES = ''' @@ -302,6 +353,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure VPN autokey tunnel. fortios_vpn_ipsec_phase2_interface: @@ -310,28 +362,28 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" vpn_ipsec_phase2_interface: - state: "present" - add-route: "phase1" - auto-discovery-forwarder: "phase1" - auto-discovery-sender: "phase1" - auto-negotiate: "enable" + add_route: "phase1" + auto_discovery_forwarder: "phase1" + auto_discovery_sender: "phase1" + auto_negotiate: "enable" comments: "" - dhcp-ipsec: "enable" + dhcp_ipsec: "enable" dhgrp: "1" - dst-addr-type: "subnet" - dst-end-ip: "" - dst-end-ip6: "" - dst-name: " (source firewall.address.name firewall.addrgrp.name)" - dst-name6: " (source firewall.address6.name firewall.addrgrp6.name)" - dst-port: "15" - dst-start-ip: "" - dst-start-ip6: "" - dst-subnet: "" - dst-subnet6: "" + dst_addr_type: "subnet" + dst_end_ip: "" + dst_end_ip6: "" + dst_name: " (source firewall.address.name firewall.addrgrp.name)" + dst_name6: " (source firewall.address6.name firewall.addrgrp6.name)" + dst_port: "15" + dst_start_ip: "" + dst_start_ip6: "" + dst_subnet: "" + dst_subnet6: "" encapsulation: "tunnel-mode" keepalive: "enable" - keylife-type: "seconds" + keylife_type: "seconds" keylifekbs: "23" keylifeseconds: "24" l2tp: "enable" @@ -341,18 +393,18 @@ EXAMPLES = ''' proposal: "null-md5" protocol: "30" replay: "enable" - route-overlap: "use-old" - single-source: "enable" - src-addr-type: "subnet" - src-end-ip: "" - src-end-ip6: "" - src-name: " (source firewall.address.name firewall.addrgrp.name)" - src-name6: " (source firewall.address6.name firewall.addrgrp6.name)" - src-port: "39" - src-start-ip: "" - src-start-ip6: "" - src-subnet: "" - src-subnet6: "" + route_overlap: "use-old" + single_source: "enable" + src_addr_type: "subnet" + src_end_ip: "" + src_end_ip6: "" + src_name: " (source firewall.address.name firewall.addrgrp.name)" + src_name6: " (source firewall.address6.name firewall.addrgrp6.name)" + src_port: "39" + src_start_ip: "" + src_start_ip6: "" + src_subnet: "" + src_subnet6: "" ''' RETURN = ''' @@ -415,14 +467,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule - -fos = None +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -430,24 +484,24 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ipsec_phase2_interface_data(json): - option_list = ['add-route', 'auto-discovery-forwarder', 'auto-discovery-sender', - 'auto-negotiate', 'comments', 'dhcp-ipsec', - 'dhgrp', 'dst-addr-type', 'dst-end-ip', - 'dst-end-ip6', 'dst-name', 'dst-name6', - 'dst-port', 'dst-start-ip', 'dst-start-ip6', - 'dst-subnet', 'dst-subnet6', 'encapsulation', - 'keepalive', 'keylife-type', 'keylifekbs', + option_list = ['add_route', 'auto_discovery_forwarder', 'auto_discovery_sender', + 'auto_negotiate', 'comments', 'dhcp_ipsec', + 'dhgrp', 'dst_addr_type', 'dst_end_ip', + 'dst_end_ip6', 'dst_name', 'dst_name6', + 'dst_port', 'dst_start_ip', 'dst_start_ip6', + 'dst_subnet', 'dst_subnet6', 'encapsulation', + 'keepalive', 'keylife_type', 'keylifekbs', 'keylifeseconds', 'l2tp', 'name', 'pfs', 'phase1name', 'proposal', - 'protocol', 'replay', 'route-overlap', - 'single-source', 'src-addr-type', 'src-end-ip', - 'src-end-ip6', 'src-name', 'src-name6', - 'src-port', 'src-start-ip', 'src-start-ip6', - 'src-subnet', 'src-subnet6'] + 'protocol', 'replay', 'route_overlap', + 'single_source', 'src_addr_type', 'src_end_ip', + 'src_end_ip6', 'src_name', 'src_name6', + 'src_port', 'src_start_ip', 'src_start_ip6', + 'src_subnet', 'src_subnet6'] dictionary = {} for attribute in option_list: @@ -457,71 +511,76 @@ def filter_vpn_ipsec_phase2_interface_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def vpn_ipsec_phase2_interface(data, fos): vdom = data['vdom'] + state = data['state'] vpn_ipsec_phase2_interface_data = data['vpn_ipsec_phase2_interface'] - flattened_data = flatten_multilists_attributes(vpn_ipsec_phase2_interface_data) - filtered_data = filter_vpn_ipsec_phase2_interface_data(flattened_data) - if vpn_ipsec_phase2_interface_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_vpn_ipsec_phase2_interface_data(vpn_ipsec_phase2_interface_data)) + + if state == "present": return fos.set('vpn.ipsec', 'phase2-interface', data=filtered_data, vdom=vdom) - elif vpn_ipsec_phase2_interface_data['state'] == "absent": + elif state == "absent": return fos.delete('vpn.ipsec', 'phase2-interface', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ipsec(data, fos): - login(data) if data['vpn_ipsec_phase2_interface']: resp = vpn_ipsec_phase2_interface(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "vpn_ipsec_phase2_interface": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "add-route": {"required": False, "type": "str", + "add_route": {"required": False, "type": "str", "choices": ["phase1", "enable", "disable"]}, - "auto-discovery-forwarder": {"required": False, "type": "str", + "auto_discovery_forwarder": {"required": False, "type": "str", "choices": ["phase1", "enable", "disable"]}, - "auto-discovery-sender": {"required": False, "type": "str", + "auto_discovery_sender": {"required": False, "type": "str", "choices": ["phase1", "enable", "disable"]}, - "auto-negotiate": {"required": False, "type": "str", + "auto_negotiate": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "comments": {"required": False, "type": "str"}, - "dhcp-ipsec": {"required": False, "type": "str", + "dhcp_ipsec": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "dhgrp": {"required": False, "type": "str", "choices": ["1", "2", "5", @@ -530,24 +589,24 @@ def main(): "20", "21", "27", "28", "29", "30", "31"]}, - "dst-addr-type": {"required": False, "type": "str", + "dst_addr_type": {"required": False, "type": "str", "choices": ["subnet", "range", "ip", "name", "subnet6", "range6", "ip6", "name6"]}, - "dst-end-ip": {"required": False, "type": "str"}, - "dst-end-ip6": {"required": False, "type": "str"}, - "dst-name": {"required": False, "type": "str"}, - "dst-name6": {"required": False, "type": "str"}, - "dst-port": {"required": False, "type": "int"}, - "dst-start-ip": {"required": False, "type": "str"}, - "dst-start-ip6": {"required": False, "type": "str"}, - "dst-subnet": {"required": False, "type": "str"}, - "dst-subnet6": {"required": False, "type": "str"}, + "dst_end_ip": {"required": False, "type": "str"}, + "dst_end_ip6": {"required": False, "type": "str"}, + "dst_name": {"required": False, "type": "str"}, + "dst_name6": {"required": False, "type": "str"}, + "dst_port": {"required": False, "type": "int"}, + "dst_start_ip": {"required": False, "type": "str"}, + "dst_start_ip6": {"required": False, "type": "str"}, + "dst_subnet": {"required": False, "type": "str"}, + "dst_subnet6": {"required": False, "type": "str"}, "encapsulation": {"required": False, "type": "str", "choices": ["tunnel-mode", "transport-mode"]}, "keepalive": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "keylife-type": {"required": False, "type": "str", + "keylife_type": {"required": False, "type": "str", "choices": ["seconds", "kbs", "both"]}, "keylifekbs": {"required": False, "type": "int"}, "keylifeseconds": {"required": False, "type": "int"}, @@ -565,23 +624,23 @@ def main(): "protocol": {"required": False, "type": "int"}, "replay": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "route-overlap": {"required": False, "type": "str", + "route_overlap": {"required": False, "type": "str", "choices": ["use-old", "use-new", "allow"]}, - "single-source": {"required": False, "type": "str", + "single_source": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "src-addr-type": {"required": False, "type": "str", + "src_addr_type": {"required": False, "type": "str", "choices": ["subnet", "range", "ip", "name", "subnet6", "range6", "ip6", "name6"]}, - "src-end-ip": {"required": False, "type": "str"}, - "src-end-ip6": {"required": False, "type": "str"}, - "src-name": {"required": False, "type": "str"}, - "src-name6": {"required": False, "type": "str"}, - "src-port": {"required": False, "type": "int"}, - "src-start-ip": {"required": False, "type": "str"}, - "src-start-ip6": {"required": False, "type": "str"}, - "src-subnet": {"required": False, "type": "str"}, - "src-subnet6": {"required": False, "type": "str"} + "src_end_ip": {"required": False, "type": "str"}, + "src_end_ip6": {"required": False, "type": "str"}, + "src_name": {"required": False, "type": "str"}, + "src_name6": {"required": False, "type": "str"}, + "src_port": {"required": False, "type": "int"}, + "src_start_ip": {"required": False, "type": "str"}, + "src_start_ip6": {"required": False, "type": "str"}, + "src_subnet": {"required": False, "type": "str"}, + "src_subnet6": {"required": False, "type": "str"} } } @@ -589,15 +648,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ipsec(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ssl_settings.py b/lib/ansible/modules/network/fortios/fortios_vpn_ssl_settings.py index 9441c97b12e..9975d038948 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ssl_settings.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ssl_settings.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ssl_settings short_description: Configure SSL VPN in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ssl feature and settings category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,44 +41,57 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 vpn_ssl_settings: description: - Configure SSL VPN. default: null + type: dict suboptions: - auth-timeout: + auth_timeout: description: - SSL-VPN authentication timeout (1 - 259200 sec (3 days), 0 for no timeout). - authentication-rule: + type: int + authentication_rule: description: - Authentication rule for SSL VPN. + type: list suboptions: auth: description: - SSL VPN authentication method restriction. + type: str choices: - any - local @@ -91,87 +101,106 @@ options: cipher: description: - SSL VPN cipher strength. + type: str choices: - any - high - medium - client-cert: + client_cert: description: - Enable/disable SSL VPN client certificate restrictive. + type: str choices: - enable - disable groups: description: - User groups. + type: list suboptions: name: description: - Group name. Source user.group.name. required: true + type: str id: description: - ID (0 - 4294967295). required: true + type: int portal: description: - SSL VPN portal. Source vpn.ssl.web.portal.name. + type: str realm: description: - SSL VPN realm. Source vpn.ssl.web.realm.url-path. - source-address: + type: str + source_address: description: - Source address of incoming traffic. + type: list suboptions: name: description: - Address name. Source firewall.address.name firewall.addrgrp.name. required: true - source-address-negate: + type: str + source_address_negate: description: - Enable/disable negated source address match. + type: str choices: - enable - disable - source-address6: + source_address6: description: - IPv6 source address of incoming traffic. + type: list suboptions: name: description: - IPv6 address name. Source firewall.address6.name firewall.addrgrp6.name. required: true - source-address6-negate: + type: str + source_address6_negate: description: - Enable/disable negated source IPv6 address match. + type: str choices: - enable - disable - source-interface: + source_interface: description: - SSL VPN source interface of incoming traffic. + type: list suboptions: name: description: - Interface name. Source system.interface.name system.zone.name. required: true + type: str users: description: - User name. + type: list suboptions: name: description: - User name. Source user.local.name. required: true - auto-tunnel-static-route: + type: str + auto_tunnel_static_route: description: - Enable to auto-create static routes for the SSL-VPN tunnel IP addresses. + type: str choices: - enable - disable - banned-cipher: + banned_cipher: description: - Select one or more cipher technologies that cannot be used in SSL-VPN negotiations. + type: str choices: - RSA - DH @@ -188,227 +217,278 @@ options: - SHA256 - SHA384 - STATIC - check-referer: + check_referer: description: - Enable/disable verification of referer field in HTTP request header. + type: str choices: - enable - disable - default-portal: + default_portal: description: - Default SSL VPN portal. Source vpn.ssl.web.portal.name. - deflate-compression-level: + type: str + deflate_compression_level: description: - Compression level (0~9). - deflate-min-data-size: + type: int + deflate_min_data_size: description: - Minimum amount of data that triggers compression (200 - 65535 bytes). - dns-server1: + type: int + dns_server1: description: - DNS server 1. - dns-server2: + type: str + dns_server2: description: - DNS server 2. - dns-suffix: + type: str + dns_suffix: description: - DNS suffix used for SSL-VPN clients. - dtls-hello-timeout: + type: str + dtls_hello_timeout: description: - - SSLVPN maximum DTLS hello timeout (10 - 60 sec, default = 10). - dtls-tunnel: + - SSLVPN maximum DTLS hello timeout (10 - 60 sec). + type: int + dtls_tunnel: description: - Enable DTLS to prevent eavesdropping, tampering, or message forgery. + type: str choices: - enable - disable - force-two-factor-auth: + force_two_factor_auth: description: - Enable to force two-factor authentication for all SSL-VPNs. + type: str choices: - enable - disable - header-x-forwarded-for: + header_x_forwarded_for: description: - Forward the same, add, or remove HTTP header. + type: str choices: - pass - add - remove - http-compression: + http_compression: description: - Enable to allow HTTP compression over SSL-VPN tunnels. + type: str choices: - enable - disable - http-only-cookie: + http_only_cookie: description: - Enable/disable SSL-VPN support for HttpOnly cookies. + type: str choices: - enable - disable - http-request-body-timeout: + http_request_body_timeout: description: - - SSL-VPN session is disconnected if an HTTP request body is not received within this time (1 - 60 sec, default = 20). - http-request-header-timeout: + - SSL-VPN session is disconnected if an HTTP request body is not received within this time (1 - 60 sec). + type: int + http_request_header_timeout: description: - - SSL-VPN session is disconnected if an HTTP request header is not received within this time (1 - 60 sec, default = 20). - https-redirect: + - SSL-VPN session is disconnected if an HTTP request header is not received within this time (1 - 60 sec). + type: int + https_redirect: description: - Enable/disable redirect of port 80 to SSL-VPN port. + type: str choices: - enable - disable - idle-timeout: + idle_timeout: description: - SSL VPN disconnects if idle for specified time in seconds. - ipv6-dns-server1: + type: int + ipv6_dns_server1: description: - IPv6 DNS server 1. - ipv6-dns-server2: + type: str + ipv6_dns_server2: description: - IPv6 DNS server 2. - ipv6-wins-server1: + type: str + ipv6_wins_server1: description: - IPv6 WINS server 1. - ipv6-wins-server2: + type: str + ipv6_wins_server2: description: - IPv6 WINS server 2. - login-attempt-limit: + type: str + login_attempt_limit: description: - - SSL VPN maximum login attempt times before block (0 - 10, default = 2, 0 = no limit). - login-block-time: + - SSL VPN maximum login attempt times before block (0 - 10). + type: int + login_block_time: description: - - Time for which a user is blocked from logging in after too many failed login attempts (0 - 86400 sec, default = 60). - login-timeout: + - Time for which a user is blocked from logging in after too many failed login attempts (0 - 86400 sec). + type: int + login_timeout: description: - - SSLVPN maximum login timeout (10 - 180 sec, default = 30). + - SSLVPN maximum login timeout (10 - 180 sec). + type: int port: description: - SSL-VPN access port (1 - 65535). - port-precedence: + type: int + port_precedence: description: - Enable means that if SSL-VPN connections are allowed on an interface admin GUI connections are blocked on that interface. + type: str choices: - enable - disable reqclientcert: description: - Enable to require client certificates for all SSL-VPN users. + type: str choices: - enable - disable - route-source-interface: + route_source_interface: description: - Enable to allow SSL-VPN sessions to bypass routing and bind to the incoming interface. + type: str choices: - enable - disable servercert: description: - Name of the server certificate to be used for SSL-VPNs. Source vpn.certificate.local.name. - source-address: + type: str + source_address: description: - Source address of incoming traffic. + type: list suboptions: name: description: - Address name. Source firewall.address.name firewall.addrgrp.name. required: true - source-address-negate: + type: str + source_address_negate: description: - Enable/disable negated source address match. + type: str choices: - enable - disable - source-address6: + source_address6: description: - IPv6 source address of incoming traffic. + type: list suboptions: name: description: - IPv6 address name. Source firewall.address6.name firewall.addrgrp6.name. required: true - source-address6-negate: + type: str + source_address6_negate: description: - Enable/disable negated source IPv6 address match. + type: str choices: - enable - disable - source-interface: + source_interface: description: - SSL VPN source interface of incoming traffic. + type: list suboptions: name: description: - Interface name. Source system.interface.name system.zone.name. required: true - ssl-client-renegotiation: + type: str + ssl_client_renegotiation: description: - Enable to allow client renegotiation by the server if the tunnel goes down. + type: str choices: - disable - enable - ssl-insert-empty-fragment: + ssl_insert_empty_fragment: description: - Enable/disable insertion of empty fragment. + type: str choices: - enable - disable - tlsv1-0: + tlsv1_0: description: - Enable/disable TLSv1.0. + type: str choices: - enable - disable - tlsv1-1: + tlsv1_1: description: - Enable/disable TLSv1.1. + type: str choices: - enable - disable - tlsv1-2: + tlsv1_2: description: - Enable/disable TLSv1.2. + type: str choices: - enable - disable - tunnel-ip-pools: + tunnel_ip_pools: description: - Names of the IPv4 IP Pool firewall objects that define the IP addresses reserved for remote clients. + type: list suboptions: name: description: - Address name. Source firewall.address.name firewall.addrgrp.name. required: true - tunnel-ipv6-pools: + type: str + tunnel_ipv6_pools: description: - Names of the IPv6 IP Pool firewall objects that define the IP addresses reserved for remote clients. + type: list suboptions: name: description: - Address name. Source firewall.address6.name firewall.addrgrp6.name. required: true - unsafe-legacy-renegotiation: + type: str + unsafe_legacy_renegotiation: description: - Enable/disable unsafe legacy re-negotiation. + type: str choices: - enable - disable - url-obscuration: + url_obscuration: description: - Enable to obscure the host name of the URL of the web browser display. + type: str choices: - enable - disable - wins-server1: + wins_server1: description: - WINS server 1. - wins-server2: + type: str + wins_server2: description: - WINS server 2. - x-content-type-options: + type: str + x_content_type_options: description: - Add HTTP X-Content-Type-Options header. + type: str choices: - enable - disable @@ -421,6 +501,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure SSL VPN. fortios_vpn_ssl_settings: @@ -430,90 +511,90 @@ EXAMPLES = ''' vdom: "{{ vdom }}" https: "False" vpn_ssl_settings: - auth-timeout: "3" - authentication-rule: + auth_timeout: "3" + authentication_rule: - auth: "any" cipher: "any" - client-cert: "enable" + client_cert: "enable" groups: - name: "default_name_9 (source user.group.name)" id: "10" portal: " (source vpn.ssl.web.portal.name)" realm: " (source vpn.ssl.web.realm.url-path)" - source-address: + source_address: - name: "default_name_14 (source firewall.address.name firewall.addrgrp.name)" - source-address-negate: "enable" - source-address6: + source_address_negate: "enable" + source_address6: - name: "default_name_17 (source firewall.address6.name firewall.addrgrp6.name)" - source-address6-negate: "enable" - source-interface: + source_address6_negate: "enable" + source_interface: - name: "default_name_20 (source system.interface.name system.zone.name)" users: - name: "default_name_22 (source user.local.name)" - auto-tunnel-static-route: "enable" - banned-cipher: "RSA" - check-referer: "enable" - default-portal: " (source vpn.ssl.web.portal.name)" - deflate-compression-level: "27" - deflate-min-data-size: "28" - dns-server1: "" - dns-server2: "" - dns-suffix: "" - dtls-hello-timeout: "32" - dtls-tunnel: "enable" - force-two-factor-auth: "enable" - header-x-forwarded-for: "pass" - http-compression: "enable" - http-only-cookie: "enable" - http-request-body-timeout: "38" - http-request-header-timeout: "39" - https-redirect: "enable" - idle-timeout: "41" - ipv6-dns-server1: "" - ipv6-dns-server2: "" - ipv6-wins-server1: "" - ipv6-wins-server2: "" - login-attempt-limit: "46" - login-block-time: "47" - login-timeout: "48" + auto_tunnel_static_route: "enable" + banned_cipher: "RSA" + check_referer: "enable" + default_portal: " (source vpn.ssl.web.portal.name)" + deflate_compression_level: "27" + deflate_min_data_size: "28" + dns_server1: "" + dns_server2: "" + dns_suffix: "" + dtls_hello_timeout: "32" + dtls_tunnel: "enable" + force_two_factor_auth: "enable" + header_x_forwarded_for: "pass" + http_compression: "enable" + http_only_cookie: "enable" + http_request_body_timeout: "38" + http_request_header_timeout: "39" + https_redirect: "enable" + idle_timeout: "41" + ipv6_dns_server1: "" + ipv6_dns_server2: "" + ipv6_wins_server1: "" + ipv6_wins_server2: "" + login_attempt_limit: "46" + login_block_time: "47" + login_timeout: "48" port: "49" - port-precedence: "enable" + port_precedence: "enable" reqclientcert: "enable" - route-source-interface: "enable" + route_source_interface: "enable" servercert: " (source vpn.certificate.local.name)" - source-address: + source_address: - name: "default_name_55 (source firewall.address.name firewall.addrgrp.name)" - source-address-negate: "enable" - source-address6: + source_address_negate: "enable" + source_address6: - name: "default_name_58 (source firewall.address6.name firewall.addrgrp6.name)" - source-address6-negate: "enable" - source-interface: + source_address6_negate: "enable" + source_interface: - name: "default_name_61 (source system.interface.name system.zone.name)" - ssl-client-renegotiation: "disable" - ssl-insert-empty-fragment: "enable" - tlsv1-0: "enable" - tlsv1-1: "enable" - tlsv1-2: "enable" - tunnel-ip-pools: + ssl_client_renegotiation: "disable" + ssl_insert_empty_fragment: "enable" + tlsv1_0: "enable" + tlsv1_1: "enable" + tlsv1_2: "enable" + tunnel_ip_pools: - name: "default_name_68 (source firewall.address.name firewall.addrgrp.name)" - tunnel-ipv6-pools: + tunnel_ipv6_pools: - name: "default_name_70 (source firewall.address6.name firewall.addrgrp6.name)" - unsafe-legacy-renegotiation: "enable" - url-obscuration: "enable" - wins-server1: "" - wins-server2: "" - x-content-type-options: "enable" + unsafe_legacy_renegotiation: "enable" + url_obscuration: "enable" + wins_server1: "" + wins_server2: "" + x_content_type_options: "enable" ''' RETURN = ''' @@ -576,14 +657,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule - -fos = None +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -591,27 +674,27 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ssl_settings_data(json): - option_list = ['auth-timeout', 'authentication-rule', 'auto-tunnel-static-route', - 'banned-cipher', 'check-referer', 'default-portal', - 'deflate-compression-level', 'deflate-min-data-size', 'dns-server1', - 'dns-server2', 'dns-suffix', 'dtls-hello-timeout', - 'dtls-tunnel', 'force-two-factor-auth', 'header-x-forwarded-for', - 'http-compression', 'http-only-cookie', 'http-request-body-timeout', - 'http-request-header-timeout', 'https-redirect', 'idle-timeout', - 'ipv6-dns-server1', 'ipv6-dns-server2', 'ipv6-wins-server1', - 'ipv6-wins-server2', 'login-attempt-limit', 'login-block-time', - 'login-timeout', 'port', 'port-precedence', - 'reqclientcert', 'route-source-interface', 'servercert', - 'source-address', 'source-address-negate', 'source-address6', - 'source-address6-negate', 'source-interface', 'ssl-client-renegotiation', - 'ssl-insert-empty-fragment', 'tlsv1-0', 'tlsv1-1', - 'tlsv1-2', 'tunnel-ip-pools', 'tunnel-ipv6-pools', - 'unsafe-legacy-renegotiation', 'url-obscuration', 'wins-server1', - 'wins-server2', 'x-content-type-options'] + option_list = ['auth_timeout', 'authentication_rule', 'auto_tunnel_static_route', + 'banned_cipher', 'check_referer', 'default_portal', + 'deflate_compression_level', 'deflate_min_data_size', 'dns_server1', + 'dns_server2', 'dns_suffix', 'dtls_hello_timeout', + 'dtls_tunnel', 'force_two_factor_auth', 'header_x_forwarded_for', + 'http_compression', 'http_only_cookie', 'http_request_body_timeout', + 'http_request_header_timeout', 'https_redirect', 'idle_timeout', + 'ipv6_dns_server1', 'ipv6_dns_server2', 'ipv6_wins_server1', + 'ipv6_wins_server2', 'login_attempt_limit', 'login_block_time', + 'login_timeout', 'port', 'port_precedence', + 'reqclientcert', 'route_source_interface', 'servercert', + 'source_address', 'source_address_negate', 'source_address6', + 'source_address6_negate', 'source_interface', 'ssl_client_renegotiation', + 'ssl_insert_empty_fragment', 'tlsv1_0', 'tlsv1_1', + 'tlsv1_2', 'tunnel_ip_pools', 'tunnel_ipv6_pools', + 'unsafe_legacy_renegotiation', 'url_obscuration', 'wins_server1', + 'wins_server2', 'x_content_type_options'] dictionary = {} for attribute in option_list: @@ -621,17 +704,15 @@ def filter_vpn_ssl_settings_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data @@ -639,43 +720,49 @@ def flatten_multilists_attributes(data): def vpn_ssl_settings(data, fos): vdom = data['vdom'] vpn_ssl_settings_data = data['vpn_ssl_settings'] - flattened_data = flatten_multilists_attributes(vpn_ssl_settings_data) - filtered_data = filter_vpn_ssl_settings_data(flattened_data) + filtered_data = underscore_to_hyphen(filter_vpn_ssl_settings_data(vpn_ssl_settings_data)) + return fos.set('vpn.ssl', 'settings', data=filtered_data, vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ssl(data, fos): - login(data) if data['vpn_ssl_settings']: resp = vpn_ssl_settings(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, "vpn_ssl_settings": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "auth-timeout": {"required": False, "type": "int"}, - "authentication-rule": {"required": False, "type": "list", + "auth_timeout": {"required": False, "type": "int"}, + "authentication_rule": {"required": False, "type": "list", "options": { "auth": {"required": False, "type": "str", "choices": ["any", "local", "radius", "tacacs+", "ldap"]}, "cipher": {"required": False, "type": "str", "choices": ["any", "high", "medium"]}, - "client-cert": {"required": False, "type": "str", + "client_cert": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "groups": {"required": False, "type": "list", "options": { @@ -684,19 +771,19 @@ def main(): "id": {"required": True, "type": "int"}, "portal": {"required": False, "type": "str"}, "realm": {"required": False, "type": "str"}, - "source-address": {"required": False, "type": "list", + "source_address": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "source-address-negate": {"required": False, "type": "str", + "source_address_negate": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "source-address6": {"required": False, "type": "list", + "source_address6": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "source-address6-negate": {"required": False, "type": "str", + "source_address6_negate": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "source-interface": {"required": False, "type": "list", + "source_interface": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, @@ -705,94 +792,94 @@ def main(): "name": {"required": True, "type": "str"} }} }}, - "auto-tunnel-static-route": {"required": False, "type": "str", + "auto_tunnel_static_route": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "banned-cipher": {"required": False, "type": "str", + "banned_cipher": {"required": False, "type": "str", "choices": ["RSA", "DH", "DHE", "ECDH", "ECDHE", "DSS", "ECDSA", "AES", "AESGCM", "CAMELLIA", "3DES", "SHA1", "SHA256", "SHA384", "STATIC"]}, - "check-referer": {"required": False, "type": "str", + "check_referer": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "default-portal": {"required": False, "type": "str"}, - "deflate-compression-level": {"required": False, "type": "int"}, - "deflate-min-data-size": {"required": False, "type": "int"}, - "dns-server1": {"required": False, "type": "str"}, - "dns-server2": {"required": False, "type": "str"}, - "dns-suffix": {"required": False, "type": "str"}, - "dtls-hello-timeout": {"required": False, "type": "int"}, - "dtls-tunnel": {"required": False, "type": "str", + "default_portal": {"required": False, "type": "str"}, + "deflate_compression_level": {"required": False, "type": "int"}, + "deflate_min_data_size": {"required": False, "type": "int"}, + "dns_server1": {"required": False, "type": "str"}, + "dns_server2": {"required": False, "type": "str"}, + "dns_suffix": {"required": False, "type": "str"}, + "dtls_hello_timeout": {"required": False, "type": "int"}, + "dtls_tunnel": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "force-two-factor-auth": {"required": False, "type": "str", + "force_two_factor_auth": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "header-x-forwarded-for": {"required": False, "type": "str", + "header_x_forwarded_for": {"required": False, "type": "str", "choices": ["pass", "add", "remove"]}, - "http-compression": {"required": False, "type": "str", + "http_compression": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "http-only-cookie": {"required": False, "type": "str", + "http_only_cookie": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "http-request-body-timeout": {"required": False, "type": "int"}, - "http-request-header-timeout": {"required": False, "type": "int"}, - "https-redirect": {"required": False, "type": "str", + "http_request_body_timeout": {"required": False, "type": "int"}, + "http_request_header_timeout": {"required": False, "type": "int"}, + "https_redirect": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "idle-timeout": {"required": False, "type": "int"}, - "ipv6-dns-server1": {"required": False, "type": "str"}, - "ipv6-dns-server2": {"required": False, "type": "str"}, - "ipv6-wins-server1": {"required": False, "type": "str"}, - "ipv6-wins-server2": {"required": False, "type": "str"}, - "login-attempt-limit": {"required": False, "type": "int"}, - "login-block-time": {"required": False, "type": "int"}, - "login-timeout": {"required": False, "type": "int"}, + "idle_timeout": {"required": False, "type": "int"}, + "ipv6_dns_server1": {"required": False, "type": "str"}, + "ipv6_dns_server2": {"required": False, "type": "str"}, + "ipv6_wins_server1": {"required": False, "type": "str"}, + "ipv6_wins_server2": {"required": False, "type": "str"}, + "login_attempt_limit": {"required": False, "type": "int"}, + "login_block_time": {"required": False, "type": "int"}, + "login_timeout": {"required": False, "type": "int"}, "port": {"required": False, "type": "int"}, - "port-precedence": {"required": False, "type": "str", + "port_precedence": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "reqclientcert": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "route-source-interface": {"required": False, "type": "str", + "route_source_interface": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "servercert": {"required": False, "type": "str"}, - "source-address": {"required": False, "type": "list", + "source_address": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "source-address-negate": {"required": False, "type": "str", + "source_address_negate": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "source-address6": {"required": False, "type": "list", + "source_address6": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "source-address6-negate": {"required": False, "type": "str", + "source_address6_negate": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "source-interface": {"required": False, "type": "list", + "source_interface": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "ssl-client-renegotiation": {"required": False, "type": "str", + "ssl_client_renegotiation": {"required": False, "type": "str", "choices": ["disable", "enable"]}, - "ssl-insert-empty-fragment": {"required": False, "type": "str", + "ssl_insert_empty_fragment": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tlsv1-0": {"required": False, "type": "str", + "tlsv1_0": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tlsv1-1": {"required": False, "type": "str", + "tlsv1_1": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tlsv1-2": {"required": False, "type": "str", + "tlsv1_2": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tunnel-ip-pools": {"required": False, "type": "list", + "tunnel_ip_pools": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "tunnel-ipv6-pools": {"required": False, "type": "list", + "tunnel_ipv6_pools": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "unsafe-legacy-renegotiation": {"required": False, "type": "str", + "unsafe_legacy_renegotiation": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "url-obscuration": {"required": False, "type": "str", + "url_obscuration": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "wins-server1": {"required": False, "type": "str"}, - "wins-server2": {"required": False, "type": "str"}, - "x-content-type-options": {"required": False, "type": "str", + "wins_server1": {"required": False, "type": "str"}, + "wins_server2": {"required": False, "type": "str"}, + "x_content_type_options": {"required": False, "type": "str", "choices": ["enable", "disable"]} } @@ -801,15 +888,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ssl(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ssl(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ssl(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_vpn_ssl_web_portal.py b/lib/ansible/modules/network/fortios/fortios_vpn_ssl_web_portal.py index 84a89c0c154..96749e61579 100644 --- a/lib/ansible/modules/network/fortios/fortios_vpn_ssl_web_portal.py +++ b/lib/ansible/modules/network/fortios/fortios_vpn_ssl_web_portal.py @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_vpn_ssl_web_portal short_description: Portal in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify vpn_ssl_web feature and portal category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,43 +41,57 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 vpn_ssl_web_portal: description: - Portal. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - allow-user-access: + allow_user_access: description: - Allow user access to SSL-VPN applications. + type: str choices: - web - ftp @@ -92,26 +103,31 @@ options: - ping - citrix - portforward - auto-connect: + auto_connect: description: - Enable/disable automatic connect by client when system is up. + type: str choices: - enable - disable - bookmark-group: + bookmark_group: description: - Portal bookmark group. + type: list suboptions: bookmarks: description: - Bookmark table. + type: list suboptions: - additional-params: + additional_params: description: - Additional parameters. + type: str apptype: description: - Application type. + type: str choices: - citrix - ftp @@ -125,62 +141,79 @@ options: description: description: - Description. + type: str folder: description: - Network shared file folder parameter. - form-data: + type: str + form_data: description: - Form data. + type: list suboptions: name: description: - Name. required: true + type: str value: description: - Value. + type: str host: description: - Host name/IP parameter. - listening-port: + type: str + listening_port: description: - Listening port (0 - 65535). - load-balancing-info: + type: int + load_balancing_info: description: - The load balancing information or cookie which should be provided to the connection broker. - logon-password: + type: str + logon_password: description: - Logon password. - logon-user: + type: str + logon_user: description: - Logon user. + type: str name: description: - Bookmark name. required: true + type: str port: description: - Remote port. - preconnection-blob: + type: int + preconnection_blob: description: - An arbitrary string which identifies the RDP source. - preconnection-id: + type: str + preconnection_id: description: - The numeric ID of the RDP source (0-2147483648). - remote-port: + type: int + remote_port: description: - Remote port (0 - 65535). + type: int security: description: - Security mode for RDP connection. + type: str choices: - rdp - nla - tls - any - server-layout: + server_layout: description: - Server side keyboard layout. + type: str choices: - de-de-qwertz - en-gb-qwerty @@ -194,395 +227,478 @@ options: - sv-se-qwerty - tr-tr-qwerty - failsafe - show-status-window: + show_status_window: description: - Enable/disable showing of status window. + type: str choices: - enable - disable sso: description: - Single Sign-On. + type: str choices: - disable - static - auto - sso-credential: + sso_credential: description: - Single sign-on credentials. + type: str choices: - sslvpn-login - alternative - sso-credential-sent-once: + sso_credential_sent_once: description: - Single sign-on credentials are only sent once to remote server. + type: str choices: - enable - disable - sso-password: + sso_password: description: - SSO password. - sso-username: + type: str + sso_username: description: - SSO user name. + type: str url: description: - URL parameter. + type: str name: description: - Bookmark group name. required: true - custom-lang: + type: str + custom_lang: description: - Change the web portal display language. Overrides config system global set language. You can use config system custom-language and execute system custom-language to add custom language files. Source system.custom-language.name. - customize-forticlient-download-url: + type: str + customize_forticlient_download_url: description: - Enable support of customized download URL for FortiClient. + type: str choices: - enable - disable - display-bookmark: + display_bookmark: description: - Enable to display the web portal bookmark widget. + type: str choices: - enable - disable - display-connection-tools: + display_connection_tools: description: - Enable to display the web portal connection tools widget. + type: str choices: - enable - disable - display-history: + display_history: description: - Enable to display the web portal user login history widget. + type: str choices: - enable - disable - display-status: + display_status: description: - Enable to display the web portal status widget. + type: str choices: - enable - disable - dns-server1: + dns_server1: description: - IPv4 DNS server 1. - dns-server2: + type: str + dns_server2: description: - IPv4 DNS server 2. - dns-suffix: + type: str + dns_suffix: description: - DNS suffix. - exclusive-routing: + type: str + exclusive_routing: description: - Enable/disable all traffic go through tunnel only. + type: str choices: - enable - disable - forticlient-download: + forticlient_download: description: - Enable/disable download option for FortiClient. + type: str choices: - enable - disable - forticlient-download-method: + forticlient_download_method: description: - FortiClient download method. + type: str choices: - direct - ssl-vpn heading: description: - Web portal heading message. - hide-sso-credential: + type: str + hide_sso_credential: description: - Enable to prevent SSO credential being sent to client. + type: str choices: - enable - disable - host-check: + host_check: description: - Type of host checking performed on endpoints. + type: str choices: - none - av - fw - av-fw - custom - host-check-interval: + host_check_interval: description: - Periodic host check interval. Value of 0 means disabled and host checking only happens when the endpoint connects. - host-check-policy: + type: int + host_check_policy: description: - One or more policies to require the endpoint to have specific security software. + type: list suboptions: name: description: - Host check software list name. Source vpn.ssl.web.host-check-software.name. required: true - ip-mode: + type: str + ip_mode: description: - Method by which users of this SSL-VPN tunnel obtain IP addresses. + type: str choices: - range - user-group - ip-pools: + ip_pools: description: - IPv4 firewall source address objects reserved for SSL-VPN tunnel mode clients. + type: list suboptions: name: description: - Address name. Source firewall.address.name firewall.addrgrp.name. required: true - ipv6-dns-server1: + type: str + ipv6_dns_server1: description: - IPv6 DNS server 1. - ipv6-dns-server2: + type: str + ipv6_dns_server2: description: - IPv6 DNS server 2. - ipv6-exclusive-routing: + type: str + ipv6_exclusive_routing: description: - Enable/disable all IPv6 traffic go through tunnel only. + type: str choices: - enable - disable - ipv6-pools: + ipv6_pools: description: - IPv4 firewall source address objects reserved for SSL-VPN tunnel mode clients. + type: list suboptions: name: description: - Address name. Source firewall.address6.name firewall.addrgrp6.name. required: true - ipv6-service-restriction: + type: str + ipv6_service_restriction: description: - Enable/disable IPv6 tunnel service restriction. + type: str choices: - enable - disable - ipv6-split-tunneling: + ipv6_split_tunneling: description: - Enable/disable IPv6 split tunneling. + type: str choices: - enable - disable - ipv6-split-tunneling-routing-address: + ipv6_split_tunneling_routing_address: description: - IPv6 SSL-VPN tunnel mode firewall address objects that override firewall policy destination addresses to control split-tunneling access. + type: list suboptions: name: description: - Address name. Source firewall.address6.name firewall.addrgrp6.name. required: true - ipv6-tunnel-mode: + type: str + ipv6_tunnel_mode: description: - Enable/disable IPv6 SSL-VPN tunnel mode. + type: str choices: - enable - disable - ipv6-wins-server1: + ipv6_wins_server1: description: - IPv6 WINS server 1. - ipv6-wins-server2: + type: str + ipv6_wins_server2: description: - IPv6 WINS server 2. - keep-alive: + type: str + keep_alive: description: - Enable/disable automatic reconnect for FortiClient connections. + type: str choices: - enable - disable - limit-user-logins: + limit_user_logins: description: - Enable to limit each user to one SSL-VPN session at a time. + type: str choices: - enable - disable - mac-addr-action: + mac_addr_action: description: - Client MAC address action. + type: str choices: - allow - deny - mac-addr-check: + mac_addr_check: description: - Enable/disable MAC address host checking. + type: str choices: - enable - disable - mac-addr-check-rule: + mac_addr_check_rule: description: - Client MAC address check rule. + type: list suboptions: - mac-addr-list: + mac_addr_list: description: - Client MAC address list. + type: list suboptions: addr: description: - Client MAC address. required: true - mac-addr-mask: + type: str + mac_addr_mask: description: - Client MAC address mask. + type: int name: description: - Client MAC address check rule name. required: true - macos-forticlient-download-url: + type: str + macos_forticlient_download_url: description: - Download URL for Mac FortiClient. + type: str name: description: - Portal name. required: true - os-check: + type: str + os_check: description: - Enable to let the FortiGate decide action based on client OS. + type: str choices: - enable - disable - os-check-list: + os_check_list: description: - SSL VPN OS checks. + type: list suboptions: action: description: - OS check options. + type: str choices: - deny - allow - check-up-to-date - latest-patch-level: + latest_patch_level: description: - Latest OS patch level. + type: str name: description: - Name. required: true + type: str tolerance: description: - OS patch level tolerance. - redir-url: + type: int + redir_url: description: - Client login redirect URL. - save-password: + type: str + save_password: description: - Enable/disable FortiClient saving the user's password. + type: str choices: - enable - disable - service-restriction: + service_restriction: description: - Enable/disable tunnel service restriction. + type: str choices: - enable - disable - skip-check-for-unsupported-browser: + skip_check_for_unsupported_browser: description: - Enable to skip host check if browser does not support it. + type: str choices: - enable - disable - skip-check-for-unsupported-os: + skip_check_for_unsupported_os: description: - Enable to skip host check if client OS does not support it. + type: str choices: - enable - disable - smb-ntlmv1-auth: + smb_ntlmv1_auth: description: - Enable support of NTLMv1 for Samba authentication. + type: str choices: - enable - disable smbv1: description: - Enable/disable support of SMBv1 for Samba. + type: str choices: - enable - disable - split-dns: + split_dns: description: - Split DNS for SSL VPN. + type: list suboptions: - dns-server1: + dns_server1: description: - DNS server 1. - dns-server2: + type: str + dns_server2: description: - DNS server 2. + type: str domains: description: - Split DNS domains used for SSL-VPN clients separated by comma(,). + type: str id: description: - ID. required: true - ipv6-dns-server1: + type: int + ipv6_dns_server1: description: - IPv6 DNS server 1. - ipv6-dns-server2: + type: str + ipv6_dns_server2: description: - IPv6 DNS server 2. - split-tunneling: + type: str + split_tunneling: description: - Enable/disable IPv4 split tunneling. + type: str choices: - enable - disable - split-tunneling-routing-address: + split_tunneling_routing_address: description: - IPv4 SSL-VPN tunnel mode firewall address objects that override firewall policy destination addresses to control split-tunneling access. + type: list suboptions: name: description: - Address name. Source firewall.address.name firewall.addrgrp.name. required: true + type: str theme: description: - Web portal color scheme. + type: str choices: - blue - green - red - melongene - mariner - tunnel-mode: + tunnel_mode: description: - Enable/disable IPv4 SSL-VPN tunnel mode. + type: str choices: - enable - disable - user-bookmark: + user_bookmark: description: - Enable to allow web portal users to create their own bookmarks. + type: str choices: - enable - disable - user-group-bookmark: + user_group_bookmark: description: - Enable to allow web portal users to create bookmarks for all users in the same user group. + type: str choices: - enable - disable - web-mode: + web_mode: description: - Enable/disable SSL VPN web mode. + type: str choices: - enable - disable - windows-forticlient-download-url: + windows_forticlient_download_url: description: - Download URL for Windows FortiClient. - wins-server1: + type: str + wins_server1: description: - IPv4 WINS server 1. - wins-server2: + type: str + wins_server2: description: - IPv4 WINS server 1. + type: str ''' EXAMPLES = ''' @@ -592,6 +708,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Portal. fortios_vpn_ssl_web_portal: @@ -600,126 +717,126 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" vpn_ssl_web_portal: - state: "present" - allow-user-access: "web" - auto-connect: "enable" - bookmark-group: + allow_user_access: "web" + auto_connect: "enable" + bookmark_group: - bookmarks: - - additional-params: "" + additional_params: "" apptype: "citrix" description: "" folder: "" - form-data: + form_data: - name: "default_name_12" value: "" host: "" - listening-port: "15" - load-balancing-info: "" - logon-password: "" - logon-user: "" + listening_port: "15" + load_balancing_info: "" + logon_password: "" + logon_user: "" name: "default_name_19" port: "20" - preconnection-blob: "" - preconnection-id: "22" - remote-port: "23" + preconnection_blob: "" + preconnection_id: "22" + remote_port: "23" security: "rdp" - server-layout: "de-de-qwertz" - show-status-window: "enable" + server_layout: "de-de-qwertz" + show_status_window: "enable" sso: "disable" - sso-credential: "sslvpn-login" - sso-credential-sent-once: "enable" - sso-password: "" - sso-username: "" + sso_credential: "sslvpn-login" + sso_credential_sent_once: "enable" + sso_password: "" + sso_username: "" url: "myurl.com" name: "default_name_33" - custom-lang: " (source system.custom-language.name)" - customize-forticlient-download-url: "enable" - display-bookmark: "enable" - display-connection-tools: "enable" - display-history: "enable" - display-status: "enable" - dns-server1: "" - dns-server2: "" - dns-suffix: "" - exclusive-routing: "enable" - forticlient-download: "enable" - forticlient-download-method: "direct" + custom_lang: " (source system.custom-language.name)" + customize_forticlient_download_url: "enable" + display_bookmark: "enable" + display_connection_tools: "enable" + display_history: "enable" + display_status: "enable" + dns_server1: "" + dns_server2: "" + dns_suffix: "" + exclusive_routing: "enable" + forticlient_download: "enable" + forticlient_download_method: "direct" heading: "" - hide-sso-credential: "enable" - host-check: "none" - host-check-interval: "49" - host-check-policy: + hide_sso_credential: "enable" + host_check: "none" + host_check_interval: "49" + host_check_policy: - name: "default_name_51 (source vpn.ssl.web.host-check-software.name)" - ip-mode: "range" - ip-pools: + ip_mode: "range" + ip_pools: - name: "default_name_54 (source firewall.address.name firewall.addrgrp.name)" - ipv6-dns-server1: "" - ipv6-dns-server2: "" - ipv6-exclusive-routing: "enable" - ipv6-pools: + ipv6_dns_server1: "" + ipv6_dns_server2: "" + ipv6_exclusive_routing: "enable" + ipv6_pools: - name: "default_name_59 (source firewall.address6.name firewall.addrgrp6.name)" - ipv6-service-restriction: "enable" - ipv6-split-tunneling: "enable" - ipv6-split-tunneling-routing-address: + ipv6_service_restriction: "enable" + ipv6_split_tunneling: "enable" + ipv6_split_tunneling_routing_address: - name: "default_name_63 (source firewall.address6.name firewall.addrgrp6.name)" - ipv6-tunnel-mode: "enable" - ipv6-wins-server1: "" - ipv6-wins-server2: "" - keep-alive: "enable" - limit-user-logins: "enable" - mac-addr-action: "allow" - mac-addr-check: "enable" - mac-addr-check-rule: + ipv6_tunnel_mode: "enable" + ipv6_wins_server1: "" + ipv6_wins_server2: "" + keep_alive: "enable" + limit_user_logins: "enable" + mac_addr_action: "allow" + mac_addr_check: "enable" + mac_addr_check_rule: - - mac-addr-list: + mac_addr_list: - addr: "" - mac-addr-mask: "74" + mac_addr_mask: "74" name: "default_name_75" - macos-forticlient-download-url: "" + macos_forticlient_download_url: "" name: "default_name_77" - os-check: "enable" - os-check-list: + os_check: "enable" + os_check_list: - action: "deny" - latest-patch-level: "" + latest_patch_level: "" name: "default_name_82" tolerance: "83" - redir-url: "" - save-password: "enable" - service-restriction: "enable" - skip-check-for-unsupported-browser: "enable" - skip-check-for-unsupported-os: "enable" - smb-ntlmv1-auth: "enable" + redir_url: "" + save_password: "enable" + service_restriction: "enable" + skip_check_for_unsupported_browser: "enable" + skip_check_for_unsupported_os: "enable" + smb_ntlmv1_auth: "enable" smbv1: "enable" - split-dns: + split_dns: - - dns-server1: "" - dns-server2: "" + dns_server1: "" + dns_server2: "" domains: "" id: "95" - ipv6-dns-server1: "" - ipv6-dns-server2: "" - split-tunneling: "enable" - split-tunneling-routing-address: + ipv6_dns_server1: "" + ipv6_dns_server2: "" + split_tunneling: "enable" + split_tunneling_routing_address: - name: "default_name_100 (source firewall.address.name firewall.addrgrp.name)" theme: "blue" - tunnel-mode: "enable" - user-bookmark: "enable" - user-group-bookmark: "enable" - web-mode: "enable" - windows-forticlient-download-url: "" - wins-server1: "" - wins-server2: "" + tunnel_mode: "enable" + user_bookmark: "enable" + user_group_bookmark: "enable" + web_mode: "enable" + windows_forticlient_download_url: "" + wins_server1: "" + wins_server2: "" ''' RETURN = ''' @@ -782,14 +899,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule - -fos = None +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -797,30 +916,30 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_vpn_ssl_web_portal_data(json): - option_list = ['allow-user-access', 'auto-connect', 'bookmark-group', - 'custom-lang', 'customize-forticlient-download-url', 'display-bookmark', - 'display-connection-tools', 'display-history', 'display-status', - 'dns-server1', 'dns-server2', 'dns-suffix', - 'exclusive-routing', 'forticlient-download', 'forticlient-download-method', - 'heading', 'hide-sso-credential', 'host-check', - 'host-check-interval', 'host-check-policy', 'ip-mode', - 'ip-pools', 'ipv6-dns-server1', 'ipv6-dns-server2', - 'ipv6-exclusive-routing', 'ipv6-pools', 'ipv6-service-restriction', - 'ipv6-split-tunneling', 'ipv6-split-tunneling-routing-address', 'ipv6-tunnel-mode', - 'ipv6-wins-server1', 'ipv6-wins-server2', 'keep-alive', - 'limit-user-logins', 'mac-addr-action', 'mac-addr-check', - 'mac-addr-check-rule', 'macos-forticlient-download-url', 'name', - 'os-check', 'os-check-list', 'redir-url', - 'save-password', 'service-restriction', 'skip-check-for-unsupported-browser', - 'skip-check-for-unsupported-os', 'smb-ntlmv1-auth', 'smbv1', - 'split-dns', 'split-tunneling', 'split-tunneling-routing-address', - 'theme', 'tunnel-mode', 'user-bookmark', - 'user-group-bookmark', 'web-mode', 'windows-forticlient-download-url', - 'wins-server1', 'wins-server2'] + option_list = ['allow_user_access', 'auto_connect', 'bookmark_group', + 'custom_lang', 'customize_forticlient_download_url', 'display_bookmark', + 'display_connection_tools', 'display_history', 'display_status', + 'dns_server1', 'dns_server2', 'dns_suffix', + 'exclusive_routing', 'forticlient_download', 'forticlient_download_method', + 'heading', 'hide_sso_credential', 'host_check', + 'host_check_interval', 'host_check_policy', 'ip_mode', + 'ip_pools', 'ipv6_dns_server1', 'ipv6_dns_server2', + 'ipv6_exclusive_routing', 'ipv6_pools', 'ipv6_service_restriction', + 'ipv6_split_tunneling', 'ipv6_split_tunneling_routing_address', 'ipv6_tunnel_mode', + 'ipv6_wins_server1', 'ipv6_wins_server2', 'keep_alive', + 'limit_user_logins', 'mac_addr_action', 'mac_addr_check', + 'mac_addr_check_rule', 'macos_forticlient_download_url', 'name', + 'os_check', 'os_check_list', 'redir_url', + 'save_password', 'service_restriction', 'skip_check_for_unsupported_browser', + 'skip_check_for_unsupported_os', 'smb_ntlmv1_auth', 'smbv1', + 'split_dns', 'split_tunneling', 'split_tunneling_routing_address', + 'theme', 'tunnel_mode', 'user_bookmark', + 'user_group_bookmark', 'web_mode', 'windows_forticlient_download_url', + 'wins_server1', 'wins_server2'] dictionary = {} for attribute in option_list: @@ -830,244 +949,249 @@ def filter_vpn_ssl_web_portal_data(json): return dictionary -def flatten_multilists_attributes(data): - multilist_attrs = [] - - for attr in multilist_attrs: - try: - path = "data['" + "']['".join(elem for elem in attr) + "']" - current_val = eval(path) - flattened_val = ' '.join(elem for elem in current_val) - exec(path + '= flattened_val') - except BaseException: - pass +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data return data def vpn_ssl_web_portal(data, fos): vdom = data['vdom'] + state = data['state'] vpn_ssl_web_portal_data = data['vpn_ssl_web_portal'] - flattened_data = flatten_multilists_attributes(vpn_ssl_web_portal_data) - filtered_data = filter_vpn_ssl_web_portal_data(flattened_data) - if vpn_ssl_web_portal_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_vpn_ssl_web_portal_data(vpn_ssl_web_portal_data)) + + if state == "present": return fos.set('vpn.ssl.web', 'portal', data=filtered_data, vdom=vdom) - elif vpn_ssl_web_portal_data['state'] == "absent": + elif state == "absent": return fos.delete('vpn.ssl.web', 'portal', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_vpn_ssl_web(data, fos): - login(data) if data['vpn_ssl_web_portal']: resp = vpn_ssl_web_portal(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "vpn_ssl_web_portal": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "allow-user-access": {"required": False, "type": "str", + "allow_user_access": {"required": False, "type": "str", "choices": ["web", "ftp", "smb", "telnet", "ssh", "vnc", "rdp", "ping", "citrix", "portforward"]}, - "auto-connect": {"required": False, "type": "str", + "auto_connect": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "bookmark-group": {"required": False, "type": "list", + "bookmark_group": {"required": False, "type": "list", "options": { "bookmarks": {"required": False, "type": "list", "options": { - "additional-params": {"required": False, "type": "str"}, + "additional_params": {"required": False, "type": "str"}, "apptype": {"required": False, "type": "str", "choices": ["citrix", "ftp", "portforward", "rdp", "smb", "ssh", "telnet", "vnc", "web"]}, "description": {"required": False, "type": "str"}, "folder": {"required": False, "type": "str"}, - "form-data": {"required": False, "type": "list", + "form_data": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"}, "value": {"required": False, "type": "str"} }}, "host": {"required": False, "type": "str"}, - "listening-port": {"required": False, "type": "int"}, - "load-balancing-info": {"required": False, "type": "str"}, - "logon-password": {"required": False, "type": "str"}, - "logon-user": {"required": False, "type": "str"}, + "listening_port": {"required": False, "type": "int"}, + "load_balancing_info": {"required": False, "type": "str"}, + "logon_password": {"required": False, "type": "str"}, + "logon_user": {"required": False, "type": "str"}, "name": {"required": True, "type": "str"}, "port": {"required": False, "type": "int"}, - "preconnection-blob": {"required": False, "type": "str"}, - "preconnection-id": {"required": False, "type": "int"}, - "remote-port": {"required": False, "type": "int"}, + "preconnection_blob": {"required": False, "type": "str"}, + "preconnection_id": {"required": False, "type": "int"}, + "remote_port": {"required": False, "type": "int"}, "security": {"required": False, "type": "str", "choices": ["rdp", "nla", "tls", "any"]}, - "server-layout": {"required": False, "type": "str", + "server_layout": {"required": False, "type": "str", "choices": ["de-de-qwertz", "en-gb-qwerty", "en-us-qwerty", "es-es-qwerty", "fr-fr-azerty", "fr-ch-qwertz", "it-it-qwerty", "ja-jp-qwerty", "pt-br-qwerty", "sv-se-qwerty", "tr-tr-qwerty", "failsafe"]}, - "show-status-window": {"required": False, "type": "str", + "show_status_window": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "sso": {"required": False, "type": "str", "choices": ["disable", "static", "auto"]}, - "sso-credential": {"required": False, "type": "str", + "sso_credential": {"required": False, "type": "str", "choices": ["sslvpn-login", "alternative"]}, - "sso-credential-sent-once": {"required": False, "type": "str", + "sso_credential_sent_once": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "sso-password": {"required": False, "type": "str"}, - "sso-username": {"required": False, "type": "str"}, + "sso_password": {"required": False, "type": "str"}, + "sso_username": {"required": False, "type": "str"}, "url": {"required": False, "type": "str"} }}, "name": {"required": True, "type": "str"} }}, - "custom-lang": {"required": False, "type": "str"}, - "customize-forticlient-download-url": {"required": False, "type": "str", + "custom_lang": {"required": False, "type": "str"}, + "customize_forticlient_download_url": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "display-bookmark": {"required": False, "type": "str", + "display_bookmark": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "display-connection-tools": {"required": False, "type": "str", + "display_connection_tools": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "display-history": {"required": False, "type": "str", + "display_history": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "display-status": {"required": False, "type": "str", + "display_status": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "dns-server1": {"required": False, "type": "str"}, - "dns-server2": {"required": False, "type": "str"}, - "dns-suffix": {"required": False, "type": "str"}, - "exclusive-routing": {"required": False, "type": "str", + "dns_server1": {"required": False, "type": "str"}, + "dns_server2": {"required": False, "type": "str"}, + "dns_suffix": {"required": False, "type": "str"}, + "exclusive_routing": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "forticlient-download": {"required": False, "type": "str", + "forticlient_download": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "forticlient-download-method": {"required": False, "type": "str", + "forticlient_download_method": {"required": False, "type": "str", "choices": ["direct", "ssl-vpn"]}, "heading": {"required": False, "type": "str"}, - "hide-sso-credential": {"required": False, "type": "str", + "hide_sso_credential": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "host-check": {"required": False, "type": "str", + "host_check": {"required": False, "type": "str", "choices": ["none", "av", "fw", "av-fw", "custom"]}, - "host-check-interval": {"required": False, "type": "int"}, - "host-check-policy": {"required": False, "type": "list", + "host_check_interval": {"required": False, "type": "int"}, + "host_check_policy": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "ip-mode": {"required": False, "type": "str", + "ip_mode": {"required": False, "type": "str", "choices": ["range", "user-group"]}, - "ip-pools": {"required": False, "type": "list", + "ip_pools": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "ipv6-dns-server1": {"required": False, "type": "str"}, - "ipv6-dns-server2": {"required": False, "type": "str"}, - "ipv6-exclusive-routing": {"required": False, "type": "str", + "ipv6_dns_server1": {"required": False, "type": "str"}, + "ipv6_dns_server2": {"required": False, "type": "str"}, + "ipv6_exclusive_routing": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "ipv6-pools": {"required": False, "type": "list", + "ipv6_pools": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "ipv6-service-restriction": {"required": False, "type": "str", + "ipv6_service_restriction": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "ipv6-split-tunneling": {"required": False, "type": "str", + "ipv6_split_tunneling": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "ipv6-split-tunneling-routing-address": {"required": False, "type": "list", + "ipv6_split_tunneling_routing_address": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "ipv6-tunnel-mode": {"required": False, "type": "str", + "ipv6_tunnel_mode": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "ipv6-wins-server1": {"required": False, "type": "str"}, - "ipv6-wins-server2": {"required": False, "type": "str"}, - "keep-alive": {"required": False, "type": "str", + "ipv6_wins_server1": {"required": False, "type": "str"}, + "ipv6_wins_server2": {"required": False, "type": "str"}, + "keep_alive": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "limit-user-logins": {"required": False, "type": "str", + "limit_user_logins": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "mac-addr-action": {"required": False, "type": "str", + "mac_addr_action": {"required": False, "type": "str", "choices": ["allow", "deny"]}, - "mac-addr-check": {"required": False, "type": "str", + "mac_addr_check": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "mac-addr-check-rule": {"required": False, "type": "list", + "mac_addr_check_rule": {"required": False, "type": "list", "options": { - "mac-addr-list": {"required": False, "type": "list", + "mac_addr_list": {"required": False, "type": "list", "options": { "addr": {"required": True, "type": "str"} }}, - "mac-addr-mask": {"required": False, "type": "int"}, + "mac_addr_mask": {"required": False, "type": "int"}, "name": {"required": True, "type": "str"} }}, - "macos-forticlient-download-url": {"required": False, "type": "str"}, + "macos_forticlient_download_url": {"required": False, "type": "str"}, "name": {"required": True, "type": "str"}, - "os-check": {"required": False, "type": "str", + "os_check": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "os-check-list": {"required": False, "type": "list", + "os_check_list": {"required": False, "type": "list", "options": { "action": {"required": False, "type": "str", "choices": ["deny", "allow", "check-up-to-date"]}, - "latest-patch-level": {"required": False, "type": "str"}, + "latest_patch_level": {"required": False, "type": "str"}, "name": {"required": True, "type": "str"}, "tolerance": {"required": False, "type": "int"} }}, - "redir-url": {"required": False, "type": "str"}, - "save-password": {"required": False, "type": "str", + "redir_url": {"required": False, "type": "str"}, + "save_password": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "service-restriction": {"required": False, "type": "str", + "service_restriction": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "skip-check-for-unsupported-browser": {"required": False, "type": "str", + "skip_check_for_unsupported_browser": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "skip-check-for-unsupported-os": {"required": False, "type": "str", + "skip_check_for_unsupported_os": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "smb-ntlmv1-auth": {"required": False, "type": "str", + "smb_ntlmv1_auth": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "smbv1": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "split-dns": {"required": False, "type": "list", + "split_dns": {"required": False, "type": "list", "options": { - "dns-server1": {"required": False, "type": "str"}, - "dns-server2": {"required": False, "type": "str"}, + "dns_server1": {"required": False, "type": "str"}, + "dns_server2": {"required": False, "type": "str"}, "domains": {"required": False, "type": "str"}, "id": {"required": True, "type": "int"}, - "ipv6-dns-server1": {"required": False, "type": "str"}, - "ipv6-dns-server2": {"required": False, "type": "str"} + "ipv6_dns_server1": {"required": False, "type": "str"}, + "ipv6_dns_server2": {"required": False, "type": "str"} }}, - "split-tunneling": {"required": False, "type": "str", + "split_tunneling": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "split-tunneling-routing-address": {"required": False, "type": "list", + "split_tunneling_routing_address": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, "theme": {"required": False, "type": "str", "choices": ["blue", "green", "red", "melongene", "mariner"]}, - "tunnel-mode": {"required": False, "type": "str", + "tunnel_mode": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "user-bookmark": {"required": False, "type": "str", + "user_bookmark": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "user-group-bookmark": {"required": False, "type": "str", + "user_group_bookmark": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "web-mode": {"required": False, "type": "str", + "web_mode": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "windows-forticlient-download-url": {"required": False, "type": "str"}, - "wins-server1": {"required": False, "type": "str"}, - "wins-server2": {"required": False, "type": "str"} + "windows_forticlient_download_url": {"required": False, "type": "str"}, + "wins_server1": {"required": False, "type": "str"}, + "wins_server2": {"required": False, "type": "str"} } } @@ -1075,15 +1199,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_vpn_ssl_web(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_vpn_ssl_web(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_vpn_ssl_web(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_waf_profile.py b/lib/ansible/modules/network/fortios/fortios_waf_profile.py index cfa1dad2017..f80a816798f 100644 --- a/lib/ansible/modules/network/fortios/fortios_waf_profile.py +++ b/lib/ansible/modules/network/fortios/fortios_waf_profile.py @@ -26,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_waf_profile short_description: Web application firewall configuration in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify waf feature and profile category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -41,61 +41,79 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 waf_profile: description: - Web application firewall configuration. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - address-list: + address_list: description: - Black address list and white address list. + type: dict suboptions: - blocked-address: + blocked_address: description: - Blocked address. + type: list suboptions: name: description: - Address name. Source firewall.address.name firewall.addrgrp.name. required: true - blocked-log: + type: str + blocked_log: description: - Enable/disable logging on blocked addresses. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -103,46 +121,56 @@ options: status: description: - Status. + type: str choices: - enable - disable - trusted-address: + trusted_address: description: - Trusted address. + type: list suboptions: name: description: - Address name. Source firewall.address.name firewall.addrgrp.name. required: true + type: str comment: description: - Comment. + type: str constraint: description: - WAF HTTP protocol restrictions. + type: dict suboptions: - content-length: + content_length: description: - HTTP content length in request. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block length: description: - Length of HTTP content in bytes (0 to 2147483647). + type: int log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -150,31 +178,37 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable exception: description: - HTTP constraint exception. + type: list suboptions: address: description: - Host address. Source firewall.address.name firewall.addrgrp.name. - content-length: + type: str + content_length: description: - HTTP content length in request. + type: str choices: - enable - disable - header-length: + header_length: description: - HTTP header length in request. + type: str choices: - enable - disable hostname: description: - Enable/disable hostname check. + type: str choices: - enable - disable @@ -182,97 +216,115 @@ options: description: - Exception ID. required: true - line-length: + type: int + line_length: description: - HTTP line length in request. + type: str choices: - enable - disable malformed: description: - Enable/disable malformed HTTP request check. + type: str choices: - enable - disable - max-cookie: + max_cookie: description: - Maximum number of cookies in HTTP request. + type: str choices: - enable - disable - max-header-line: + max_header_line: description: - Maximum number of HTTP header line. + type: str choices: - enable - disable - max-range-segment: + max_range_segment: description: - Maximum number of range segments in HTTP range line. + type: str choices: - enable - disable - max-url-param: + max_url_param: description: - Maximum number of parameters in URL. + type: str choices: - enable - disable method: description: - Enable/disable HTTP method check. + type: str choices: - enable - disable - param-length: + param_length: description: - Maximum length of parameter in URL, HTTP POST request or HTTP body. + type: str choices: - enable - disable pattern: description: - URL pattern. + type: str regex: description: - Enable/disable regular expression based pattern match. + type: str choices: - enable - disable - url-param-length: + url_param_length: description: - Maximum length of parameter in URL. + type: str choices: - enable - disable version: description: - Enable/disable HTTP version check. + type: str choices: - enable - disable - header-length: + header_length: description: - HTTP header length in request. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block length: description: - Length of HTTP header in bytes (0 to 2147483647). + type: int log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -280,28 +332,33 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable hostname: description: - Enable/disable hostname check. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -309,31 +366,37 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable - line-length: + line_length: description: - HTTP line length in request. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block length: description: - Length of HTTP line in bytes (0 to 2147483647). + type: int log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -341,28 +404,33 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable malformed: description: - Enable/disable malformed HTTP request check. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -370,31 +438,37 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable - max-cookie: + max_cookie: description: - Maximum number of cookies in HTTP request. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block log: description: - Enable/disable logging. + type: str choices: - enable - disable - max-cookie: + max_cookie: description: - Maximum number of cookies in HTTP request (0 to 2147483647). + type: int severity: description: - Severity. + type: str choices: - high - medium @@ -402,31 +476,37 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable - max-header-line: + max_header_line: description: - Maximum number of HTTP header line. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block log: description: - Enable/disable logging. + type: str choices: - enable - disable - max-header-line: + max_header_line: description: - Maximum number HTTP header lines (0 to 2147483647). + type: int severity: description: - Severity. + type: str choices: - high - medium @@ -434,31 +514,37 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable - max-range-segment: + max_range_segment: description: - Maximum number of range segments in HTTP range line. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block log: description: - Enable/disable logging. + type: str choices: - enable - disable - max-range-segment: + max_range_segment: description: - Maximum number of range segments in HTTP range line (0 to 2147483647). + type: int severity: description: - Severity. + type: str choices: - high - medium @@ -466,31 +552,37 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable - max-url-param: + max_url_param: description: - Maximum number of parameters in URL. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block log: description: - Enable/disable logging. + type: str choices: - enable - disable - max-url-param: + max_url_param: description: - Maximum number of parameters in URL (0 to 2147483647). + type: int severity: description: - Severity. + type: str choices: - high - medium @@ -498,28 +590,33 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable method: description: - Enable/disable HTTP method check. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -527,31 +624,37 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable - param-length: + param_length: description: - Maximum length of parameter in URL, HTTP POST request or HTTP body. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block length: description: - Maximum length of parameter in URL, HTTP POST request or HTTP body in bytes (0 to 2147483647). + type: int log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -559,31 +662,37 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable - url-param-length: + url_param_length: description: - Maximum length of parameter in URL. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block length: description: - Maximum length of URL parameter in bytes (0 to 2147483647). + type: int log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -591,28 +700,33 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable version: description: - Enable/disable HTTP version check. + type: dict suboptions: action: description: - Action. + type: str choices: - allow - block log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -620,28 +734,33 @@ options: status: description: - Enable/disable the constraint. + type: str choices: - enable - disable - extended-log: + extended_log: description: - Enable/disable extended logging. + type: str choices: - enable - disable external: description: - Disable/Enable external HTTP Inspection. + type: str choices: - disable - enable method: description: - Method restriction. + type: dict suboptions: - default-allowed-methods: + default_allowed_methods: description: - Methods. + type: str choices: - get - post @@ -655,19 +774,23 @@ options: log: description: - Enable/disable logging. + type: str choices: - enable - disable - method-policy: + method_policy: description: - HTTP method policy. + type: list suboptions: address: description: - Host address. Source firewall.address.name firewall.addrgrp.name. - allowed-methods: + type: str + allowed_methods: description: - Allowed Methods. + type: str choices: - get - post @@ -682,18 +805,22 @@ options: description: - HTTP method policy ID. required: true + type: int pattern: description: - URL pattern. + type: str regex: description: - Enable/disable regular expression based pattern match. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -701,6 +828,7 @@ options: status: description: - Status. + type: str choices: - enable - disable @@ -708,39 +836,47 @@ options: description: - WAF Profile name. required: true + type: str signature: description: - WAF signatures. + type: dict suboptions: - credit-card-detection-threshold: + credit_card_detection_threshold: description: - The minimum number of Credit cards to detect violation. - custom-signature: + type: int + custom_signature: description: - Custom signature. + type: list suboptions: action: description: - Action. + type: str choices: - allow - block - erase - case-sensitivity: + case_sensitivity: description: - Case sensitivity in pattern. + type: str choices: - disable - enable direction: description: - Traffic direction. + type: str choices: - request - response log: description: - Enable/disable logging. + type: str choices: - enable - disable @@ -748,12 +884,15 @@ options: description: - Signature name. required: true + type: str pattern: description: - Match pattern. + type: str severity: description: - Severity. + type: str choices: - high - medium @@ -761,12 +900,14 @@ options: status: description: - Status. + type: str choices: - enable - disable target: description: - Match HTTP target. + type: str choices: - arg - arg-name @@ -781,29 +922,35 @@ options: - resp-body - resp-hdr - resp-status - disabled-signature: + disabled_signature: description: - Disabled signatures + type: list suboptions: id: description: - Signature ID. Source waf.signature.id. required: true - disabled-sub-class: + type: int + disabled_sub_class: description: - Disabled signature subclasses. + type: list suboptions: id: description: - Signature subclass ID. Source waf.sub-class.id. required: true - main-class: + type: int + main_class: description: - Main signature class. + type: list suboptions: action: description: - Action. + type: str choices: - allow - block @@ -812,15 +959,18 @@ options: description: - Main signature class ID. Source waf.main-class.id. required: true + type: int log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -828,42 +978,51 @@ options: status: description: - Status. + type: str choices: - enable - disable - url-access: + url_access: description: - URL access list + type: list suboptions: - access-pattern: + access_pattern: description: - URL access pattern. + type: list suboptions: id: description: - URL access pattern ID. required: true + type: int negate: description: - Enable/disable match negation. + type: str choices: - enable - disable pattern: description: - URL pattern. + type: str regex: description: - Enable/disable regular expression based pattern match. + type: str choices: - enable - disable srcaddr: description: - Source address. Source firewall.address.name firewall.addrgrp.name. + type: str action: description: - Action. + type: str choices: - bypass - permit @@ -871,19 +1030,23 @@ options: address: description: - Host address. Source firewall.address.name firewall.addrgrp.name. + type: str id: description: - URL access ID. required: true + type: int log: description: - Enable/disable logging. + type: str choices: - enable - disable severity: description: - Severity. + type: str choices: - high - medium @@ -897,6 +1060,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Web application firewall configuration. fortios_waf_profile: @@ -905,21 +1069,21 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" waf_profile: - state: "present" - address-list: - blocked-address: + address_list: + blocked_address: - name: "default_name_5 (source firewall.address.name firewall.addrgrp.name)" - blocked-log: "enable" + blocked_log: "enable" severity: "high" status: "enable" - trusted-address: + trusted_address: - name: "default_name_10 (source firewall.address.name firewall.addrgrp.name)" comment: "Comment." constraint: - content-length: + content_length: action: "allow" length: "15" log: "enable" @@ -928,23 +1092,23 @@ EXAMPLES = ''' exception: - address: " (source firewall.address.name firewall.addrgrp.name)" - content-length: "enable" - header-length: "enable" + content_length: "enable" + header_length: "enable" hostname: "enable" id: "24" - line-length: "enable" + line_length: "enable" malformed: "enable" - max-cookie: "enable" - max-header-line: "enable" - max-range-segment: "enable" - max-url-param: "enable" + max_cookie: "enable" + max_header_line: "enable" + max_range_segment: "enable" + max_url_param: "enable" method: "enable" - param-length: "enable" + param_length: "enable" pattern: "" regex: "enable" - url-param-length: "enable" + url_param_length: "enable" version: "enable" - header-length: + header_length: action: "allow" length: "39" log: "enable" @@ -955,7 +1119,7 @@ EXAMPLES = ''' log: "enable" severity: "high" status: "enable" - line-length: + line_length: action: "allow" length: "50" log: "enable" @@ -966,28 +1130,28 @@ EXAMPLES = ''' log: "enable" severity: "high" status: "enable" - max-cookie: + max_cookie: action: "allow" log: "enable" - max-cookie: "62" + max_cookie: "62" severity: "high" status: "enable" - max-header-line: + max_header_line: action: "allow" log: "enable" - max-header-line: "68" + max_header_line: "68" severity: "high" status: "enable" - max-range-segment: + max_range_segment: action: "allow" log: "enable" - max-range-segment: "74" + max_range_segment: "74" severity: "high" status: "enable" - max-url-param: + max_url_param: action: "allow" log: "enable" - max-url-param: "80" + max_url_param: "80" severity: "high" status: "enable" method: @@ -995,13 +1159,13 @@ EXAMPLES = ''' log: "enable" severity: "high" status: "enable" - param-length: + param_length: action: "allow" length: "90" log: "enable" severity: "high" status: "enable" - url-param-length: + url_param_length: action: "allow" length: "96" log: "enable" @@ -1012,15 +1176,15 @@ EXAMPLES = ''' log: "enable" severity: "high" status: "enable" - extended-log: "enable" + extended_log: "enable" external: "disable" method: - default-allowed-methods: "get" + default_allowed_methods: "get" log: "enable" - method-policy: + method_policy: - address: " (source firewall.address.name firewall.addrgrp.name)" - allowed-methods: "get" + allowed_methods: "get" id: "113" pattern: "" regex: "enable" @@ -1028,11 +1192,11 @@ EXAMPLES = ''' status: "enable" name: "default_name_118" signature: - credit-card-detection-threshold: "120" - custom-signature: + credit_card_detection_threshold: "120" + custom_signature: - action: "allow" - case-sensitivity: "disable" + case_sensitivity: "disable" direction: "request" log: "enable" name: "default_name_126" @@ -1040,22 +1204,22 @@ EXAMPLES = ''' severity: "high" status: "enable" target: "arg" - disabled-signature: + disabled_signature: - id: "132 (source waf.signature.id)" - disabled-sub-class: + disabled_sub_class: - id: "134 (source waf.sub-class.id)" - main-class: + main_class: - action: "allow" id: "137 (source waf.main-class.id)" log: "enable" severity: "high" status: "enable" - url-access: + url_access: - - access-pattern: + access_pattern: - id: "143" negate: "enable" @@ -1129,12 +1293,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -1142,13 +1310,13 @@ def login(data, fos): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_waf_profile_data(json): - option_list = ['address-list', 'comment', 'constraint', - 'extended-log', 'external', 'method', - 'name', 'signature', 'url-access'] + option_list = ['address_list', 'comment', 'constraint', + 'extended_log', 'external', 'method', + 'name', 'signature', 'url_access'] dictionary = {} for attribute in option_list: @@ -1158,59 +1326,79 @@ def filter_waf_profile_data(json): return dictionary +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data + + return data + + def waf_profile(data, fos): vdom = data['vdom'] + state = data['state'] waf_profile_data = data['waf_profile'] - filtered_data = filter_waf_profile_data(waf_profile_data) + filtered_data = underscore_to_hyphen(filter_waf_profile_data(waf_profile_data)) - if waf_profile_data['state'] == "present": + if state == "present": return fos.set('waf', 'profile', data=filtered_data, vdom=vdom) - elif waf_profile_data['state'] == "absent": + elif state == "absent": return fos.delete('waf', 'profile', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_waf(data, fos): - login(data, fos) if data['waf_profile']: resp = waf_profile(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "waf_profile": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "address-list": {"required": False, "type": "dict", + "address_list": {"required": False, "type": "dict", "options": { - "blocked-address": {"required": False, "type": "list", + "blocked_address": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }}, - "blocked-log": {"required": False, "type": "str", + "blocked_log": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "severity": {"required": False, "type": "str", "choices": ["high", "medium", "low"]}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "trusted-address": {"required": False, "type": "list", + "trusted_address": {"required": False, "type": "list", "options": { "name": {"required": True, "type": "str"} }} @@ -1218,7 +1406,7 @@ def main(): "comment": {"required": False, "type": "str"}, "constraint": {"required": False, "type": "dict", "options": { - "content-length": {"required": False, "type": "dict", + "content_length": {"required": False, "type": "dict", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block"]}, @@ -1233,38 +1421,38 @@ def main(): "exception": {"required": False, "type": "list", "options": { "address": {"required": False, "type": "str"}, - "content-length": {"required": False, "type": "str", + "content_length": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "header-length": {"required": False, "type": "str", + "header_length": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "hostname": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "id": {"required": True, "type": "int"}, - "line-length": {"required": False, "type": "str", + "line_length": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "malformed": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "max-cookie": {"required": False, "type": "str", + "max_cookie": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "max-header-line": {"required": False, "type": "str", + "max_header_line": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "max-range-segment": {"required": False, "type": "str", + "max_range_segment": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "max-url-param": {"required": False, "type": "str", + "max_url_param": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "method": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "param-length": {"required": False, "type": "str", + "param_length": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "pattern": {"required": False, "type": "str"}, "regex": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "url-param-length": {"required": False, "type": "str", + "url_param_length": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "version": {"required": False, "type": "str", "choices": ["enable", "disable"]} }}, - "header-length": {"required": False, "type": "dict", + "header_length": {"required": False, "type": "dict", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block"]}, @@ -1287,7 +1475,7 @@ def main(): "status": {"required": False, "type": "str", "choices": ["enable", "disable"]} }}, - "line-length": {"required": False, "type": "dict", + "line_length": {"required": False, "type": "dict", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block"]}, @@ -1310,49 +1498,49 @@ def main(): "status": {"required": False, "type": "str", "choices": ["enable", "disable"]} }}, - "max-cookie": {"required": False, "type": "dict", + "max_cookie": {"required": False, "type": "dict", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block"]}, "log": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "max-cookie": {"required": False, "type": "int"}, + "max_cookie": {"required": False, "type": "int"}, "severity": {"required": False, "type": "str", "choices": ["high", "medium", "low"]}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]} }}, - "max-header-line": {"required": False, "type": "dict", + "max_header_line": {"required": False, "type": "dict", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block"]}, "log": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "max-header-line": {"required": False, "type": "int"}, + "max_header_line": {"required": False, "type": "int"}, "severity": {"required": False, "type": "str", "choices": ["high", "medium", "low"]}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]} }}, - "max-range-segment": {"required": False, "type": "dict", + "max_range_segment": {"required": False, "type": "dict", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block"]}, "log": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "max-range-segment": {"required": False, "type": "int"}, + "max_range_segment": {"required": False, "type": "int"}, "severity": {"required": False, "type": "str", "choices": ["high", "medium", "low"]}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]} }}, - "max-url-param": {"required": False, "type": "dict", + "max_url_param": {"required": False, "type": "dict", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block"]}, "log": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "max-url-param": {"required": False, "type": "int"}, + "max_url_param": {"required": False, "type": "int"}, "severity": {"required": False, "type": "str", "choices": ["high", "medium", "low"]}, "status": {"required": False, "type": "str", @@ -1369,7 +1557,7 @@ def main(): "status": {"required": False, "type": "str", "choices": ["enable", "disable"]} }}, - "param-length": {"required": False, "type": "dict", + "param_length": {"required": False, "type": "dict", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block"]}, @@ -1381,7 +1569,7 @@ def main(): "status": {"required": False, "type": "str", "choices": ["enable", "disable"]} }}, - "url-param-length": {"required": False, "type": "dict", + "url_param_length": {"required": False, "type": "dict", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block"]}, @@ -1405,22 +1593,22 @@ def main(): "choices": ["enable", "disable"]} }} }}, - "extended-log": {"required": False, "type": "str", + "extended_log": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "external": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "method": {"required": False, "type": "dict", "options": { - "default-allowed-methods": {"required": False, "type": "str", + "default_allowed_methods": {"required": False, "type": "str", "choices": ["get", "post", "put", "head", "connect", "trace", "options", "delete", "others"]}, "log": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "method-policy": {"required": False, "type": "list", + "method_policy": {"required": False, "type": "list", "options": { "address": {"required": False, "type": "str"}, - "allowed-methods": {"required": False, "type": "str", + "allowed_methods": {"required": False, "type": "str", "choices": ["get", "post", "put", "head", "connect", "trace", "options", "delete", "others"]}, @@ -1437,12 +1625,12 @@ def main(): "name": {"required": True, "type": "str"}, "signature": {"required": False, "type": "dict", "options": { - "credit-card-detection-threshold": {"required": False, "type": "int"}, - "custom-signature": {"required": False, "type": "list", + "credit_card_detection_threshold": {"required": False, "type": "int"}, + "custom_signature": {"required": False, "type": "list", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block", "erase"]}, - "case-sensitivity": {"required": False, "type": "str", + "case_sensitivity": {"required": False, "type": "str", "choices": ["disable", "enable"]}, "direction": {"required": False, "type": "str", "choices": ["request", "response"]}, @@ -1461,15 +1649,15 @@ def main(): "req-uri", "resp-body", "resp-hdr", "resp-status"]} }}, - "disabled-signature": {"required": False, "type": "list", + "disabled_signature": {"required": False, "type": "list", "options": { "id": {"required": True, "type": "int"} }}, - "disabled-sub-class": {"required": False, "type": "list", + "disabled_sub_class": {"required": False, "type": "list", "options": { "id": {"required": True, "type": "int"} }}, - "main-class": {"required": False, "type": "list", + "main_class": {"required": False, "type": "list", "options": { "action": {"required": False, "type": "str", "choices": ["allow", "block", "erase"]}, @@ -1482,9 +1670,9 @@ def main(): "choices": ["enable", "disable"]} }} }}, - "url-access": {"required": False, "type": "list", + "url_access": {"required": False, "type": "list", "options": { - "access-pattern": {"required": False, "type": "list", + "access_pattern": {"required": False, "type": "list", "options": { "id": {"required": True, "type": "int"}, "negate": {"required": False, "type": "str", @@ -1510,14 +1698,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_waf(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_waf(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_waf(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_wanopt_profile.py b/lib/ansible/modules/network/fortios/fortios_wanopt_profile.py index 6e88a69fe4b..06740ae6da1 100644 --- a/lib/ansible/modules/network/fortios/fortios_wanopt_profile.py +++ b/lib/ansible/modules/network/fortios/fortios_wanopt_profile.py @@ -26,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_wanopt_profile short_description: Configure WAN optimization profiles in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify wanopt feature and profile category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -41,58 +41,75 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 wanopt_profile: description: - Configure WAN optimization profiles. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent - auth-group: + auth_group: description: - Optionally add an authentication group to restrict access to the WAN Optimization tunnel to peers in the authentication group. Source wanopt.auth-group.name. + type: str cifs: description: - Enable/disable CIFS (Windows sharing) WAN Optimization and configure CIFS WAN Optimization features. + type: dict suboptions: - byte-caching: + byte_caching: description: - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching file data sent across the WAN and in future serving if from the cache. + type: str choices: - enable - disable - log-traffic: + log_traffic: description: - Enable/disable logging. + type: str choices: - enable - disable @@ -100,27 +117,32 @@ options: description: - Single port number or port number range for CIFS. Only packets with a destination port number that matches this port number or range are accepted by this profile. - prefer-chunking: + type: int + prefer_chunking: description: - Select dynamic or fixed-size data chunking for HTTP WAN Optimization. + type: str choices: - dynamic - fix - secure-tunnel: + secure_tunnel: description: - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the same TCP port (7810). + type: str choices: - enable - disable status: description: - Enable/disable HTTP WAN Optimization. + type: str choices: - enable - disable - tunnel-sharing: + tunnel_sharing: description: - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + type: str choices: - private - shared @@ -128,20 +150,24 @@ options: comments: description: - Comment. + type: str ftp: description: - Enable/disable FTP WAN Optimization and configure FTP WAN Optimization features. + type: dict suboptions: - byte-caching: + byte_caching: description: - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching file data sent across the WAN and in future serving if from the cache. + type: str choices: - enable - disable - log-traffic: + log_traffic: description: - Enable/disable logging. + type: str choices: - enable - disable @@ -149,27 +175,32 @@ options: description: - Single port number or port number range for FTP. Only packets with a destination port number that matches this port number or range are accepted by this profile. - prefer-chunking: + type: int + prefer_chunking: description: - Select dynamic or fixed-size data chunking for HTTP WAN Optimization. + type: str choices: - dynamic - fix - secure-tunnel: + secure_tunnel: description: - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the same TCP port (7810). + type: str choices: - enable - disable status: description: - Enable/disable HTTP WAN Optimization. + type: str choices: - enable - disable - tunnel-sharing: + tunnel_sharing: description: - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + type: str choices: - private - shared @@ -177,17 +208,20 @@ options: http: description: - Enable/disable HTTP WAN Optimization and configure HTTP WAN Optimization features. + type: dict suboptions: - byte-caching: + byte_caching: description: - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching file data sent across the WAN and in future serving if from the cache. + type: str choices: - enable - disable - log-traffic: + log_traffic: description: - Enable/disable logging. + type: str choices: - enable - disable @@ -195,50 +229,59 @@ options: description: - Single port number or port number range for HTTP. Only packets with a destination port number that matches this port number or range are accepted by this profile. - prefer-chunking: + type: int + prefer_chunking: description: - Select dynamic or fixed-size data chunking for HTTP WAN Optimization. + type: str choices: - dynamic - fix - secure-tunnel: + secure_tunnel: description: - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the same TCP port (7810). + type: str choices: - enable - disable ssl: description: - Enable/disable SSL/TLS offloading (hardware acceleration) for HTTPS traffic in this tunnel. + type: str choices: - enable - disable - ssl-port: + ssl_port: description: - Port on which to expect HTTPS traffic for SSL/TLS offloading. + type: int status: description: - Enable/disable HTTP WAN Optimization. + type: str choices: - enable - disable - tunnel-non-http: + tunnel_non_http: description: - Configure how to process non-HTTP traffic when a profile configured for HTTP traffic accepts a non-HTTP session. Can occur if an application sends non-HTTP traffic using an HTTP destination port. + type: str choices: - enable - disable - tunnel-sharing: + tunnel_sharing: description: - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + type: str choices: - private - shared - express-shared - unknown-http-version: + unknown_http_version: description: - How to handle HTTP sessions that do not comply with HTTP 0.9, 1.0, or 1.1. + type: str choices: - reject - tunnel @@ -246,17 +289,20 @@ options: mapi: description: - Enable/disable MAPI email WAN Optimization and configure MAPI WAN Optimization features. + type: dict suboptions: - byte-caching: + byte_caching: description: - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching file data sent across the WAN and in future serving if from the cache. + type: str choices: - enable - disable - log-traffic: + log_traffic: description: - Enable/disable logging. + type: str choices: - enable - disable @@ -264,21 +310,25 @@ options: description: - Single port number or port number range for MAPI. Only packets with a destination port number that matches this port number or range are accepted by this profile. - secure-tunnel: + type: int + secure_tunnel: description: - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the same TCP port (7810). + type: str choices: - enable - disable status: description: - Enable/disable HTTP WAN Optimization. + type: str choices: - enable - disable - tunnel-sharing: + tunnel_sharing: description: - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + type: str choices: - private - shared @@ -287,26 +337,31 @@ options: description: - Profile name. required: true + type: str tcp: description: - Enable/disable TCP WAN Optimization and configure TCP WAN Optimization features. + type: dict suboptions: - byte-caching: + byte_caching: description: - Enable/disable byte-caching for HTTP. Byte caching reduces the amount of traffic by caching file data sent across the WAN and in future serving if from the cache. + type: str choices: - enable - disable - byte-caching-opt: + byte_caching_opt: description: - Select whether TCP byte-caching uses system memory only or both memory and disk space. + type: str choices: - mem-only - mem-disk - log-traffic: + log_traffic: description: - Enable/disable logging. + type: str choices: - enable - disable @@ -314,30 +369,36 @@ options: description: - Single port number or port number range for TCP. Only packets with a destination port number that matches this port number or range are accepted by this profile. - secure-tunnel: + type: str + secure_tunnel: description: - Enable/disable securing the WAN Opt tunnel using SSL. Secure and non-secure tunnels use the same TCP port (7810). + type: str choices: - enable - disable ssl: description: - Enable/disable SSL/TLS offloading. + type: str choices: - enable - disable - ssl-port: + ssl_port: description: - Port on which to expect HTTPS traffic for SSL/TLS offloading. + type: int status: description: - Enable/disable HTTP WAN Optimization. + type: str choices: - enable - disable - tunnel-sharing: + tunnel_sharing: description: - Tunnel sharing mode for aggressive/non-aggressive and/or interactive/non-interactive protocols. + type: str choices: - private - shared @@ -345,6 +406,7 @@ options: transparent: description: - Enable/disable transparent mode. + type: str choices: - enable - disable @@ -357,6 +419,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure WAN optimization profiles. fortios_wanopt_profile: @@ -365,56 +428,56 @@ EXAMPLES = ''' password: "{{ password }}" vdom: "{{ vdom }}" https: "False" + state: "present" wanopt_profile: - state: "present" - auth-group: " (source wanopt.auth-group.name)" + auth_group: " (source wanopt.auth-group.name)" cifs: - byte-caching: "enable" - log-traffic: "enable" + byte_caching: "enable" + log_traffic: "enable" port: "7" - prefer-chunking: "dynamic" - secure-tunnel: "enable" + prefer_chunking: "dynamic" + secure_tunnel: "enable" status: "enable" - tunnel-sharing: "private" + tunnel_sharing: "private" comments: "" ftp: - byte-caching: "enable" - log-traffic: "enable" + byte_caching: "enable" + log_traffic: "enable" port: "16" - prefer-chunking: "dynamic" - secure-tunnel: "enable" + prefer_chunking: "dynamic" + secure_tunnel: "enable" status: "enable" - tunnel-sharing: "private" + tunnel_sharing: "private" http: - byte-caching: "enable" - log-traffic: "enable" + byte_caching: "enable" + log_traffic: "enable" port: "24" - prefer-chunking: "dynamic" - secure-tunnel: "enable" + prefer_chunking: "dynamic" + secure_tunnel: "enable" ssl: "enable" - ssl-port: "28" + ssl_port: "28" status: "enable" - tunnel-non-http: "enable" - tunnel-sharing: "private" - unknown-http-version: "reject" + tunnel_non_http: "enable" + tunnel_sharing: "private" + unknown_http_version: "reject" mapi: - byte-caching: "enable" - log-traffic: "enable" + byte_caching: "enable" + log_traffic: "enable" port: "36" - secure-tunnel: "enable" + secure_tunnel: "enable" status: "enable" - tunnel-sharing: "private" + tunnel_sharing: "private" name: "default_name_40" tcp: - byte-caching: "enable" - byte-caching-opt: "mem-only" - log-traffic: "enable" + byte_caching: "enable" + byte_caching_opt: "mem-only" + log_traffic: "enable" port: "" - secure-tunnel: "enable" + secure_tunnel: "enable" ssl: "enable" - ssl-port: "48" + ssl_port: "48" status: "enable" - tunnel-sharing: "private" + tunnel_sharing: "private" transparent: "enable" ''' @@ -478,12 +541,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -491,11 +558,11 @@ def login(data, fos): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_wanopt_profile_data(json): - option_list = ['auth-group', 'cifs', 'comments', + option_list = ['auth_group', 'cifs', 'comments', 'ftp', 'http', 'mapi', 'name', 'tcp', 'transparent'] dictionary = {} @@ -507,135 +574,155 @@ def filter_wanopt_profile_data(json): return dictionary +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data + + return data + + def wanopt_profile(data, fos): vdom = data['vdom'] + state = data['state'] wanopt_profile_data = data['wanopt_profile'] - filtered_data = filter_wanopt_profile_data(wanopt_profile_data) + filtered_data = underscore_to_hyphen(filter_wanopt_profile_data(wanopt_profile_data)) - if wanopt_profile_data['state'] == "present": + if state == "present": return fos.set('wanopt', 'profile', data=filtered_data, vdom=vdom) - elif wanopt_profile_data['state'] == "absent": + elif state == "absent": return fos.delete('wanopt', 'profile', mkey=filtered_data['name'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_wanopt(data, fos): - login(data, fos) if data['wanopt_profile']: resp = wanopt_profile(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "wanopt_profile": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, - "auth-group": {"required": False, "type": "str"}, + "auth_group": {"required": False, "type": "str"}, "cifs": {"required": False, "type": "dict", "options": { - "byte-caching": {"required": False, "type": "str", + "byte_caching": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "log-traffic": {"required": False, "type": "str", + "log_traffic": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "port": {"required": False, "type": "int"}, - "prefer-chunking": {"required": False, "type": "str", + "prefer_chunking": {"required": False, "type": "str", "choices": ["dynamic", "fix"]}, - "secure-tunnel": {"required": False, "type": "str", + "secure_tunnel": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tunnel-sharing": {"required": False, "type": "str", + "tunnel_sharing": {"required": False, "type": "str", "choices": ["private", "shared", "express-shared"]} }}, "comments": {"required": False, "type": "str"}, "ftp": {"required": False, "type": "dict", "options": { - "byte-caching": {"required": False, "type": "str", + "byte_caching": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "log-traffic": {"required": False, "type": "str", + "log_traffic": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "port": {"required": False, "type": "int"}, - "prefer-chunking": {"required": False, "type": "str", + "prefer_chunking": {"required": False, "type": "str", "choices": ["dynamic", "fix"]}, - "secure-tunnel": {"required": False, "type": "str", + "secure_tunnel": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tunnel-sharing": {"required": False, "type": "str", + "tunnel_sharing": {"required": False, "type": "str", "choices": ["private", "shared", "express-shared"]} }}, "http": {"required": False, "type": "dict", "options": { - "byte-caching": {"required": False, "type": "str", + "byte_caching": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "log-traffic": {"required": False, "type": "str", + "log_traffic": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "port": {"required": False, "type": "int"}, - "prefer-chunking": {"required": False, "type": "str", + "prefer_chunking": {"required": False, "type": "str", "choices": ["dynamic", "fix"]}, - "secure-tunnel": {"required": False, "type": "str", + "secure_tunnel": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "ssl": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "ssl-port": {"required": False, "type": "int"}, + "ssl_port": {"required": False, "type": "int"}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tunnel-non-http": {"required": False, "type": "str", + "tunnel_non_http": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tunnel-sharing": {"required": False, "type": "str", + "tunnel_sharing": {"required": False, "type": "str", "choices": ["private", "shared", "express-shared"]}, - "unknown-http-version": {"required": False, "type": "str", + "unknown_http_version": {"required": False, "type": "str", "choices": ["reject", "tunnel", "best-effort"]} }}, "mapi": {"required": False, "type": "dict", "options": { - "byte-caching": {"required": False, "type": "str", + "byte_caching": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "log-traffic": {"required": False, "type": "str", + "log_traffic": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "port": {"required": False, "type": "int"}, - "secure-tunnel": {"required": False, "type": "str", + "secure_tunnel": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tunnel-sharing": {"required": False, "type": "str", + "tunnel_sharing": {"required": False, "type": "str", "choices": ["private", "shared", "express-shared"]} }}, "name": {"required": True, "type": "str"}, "tcp": {"required": False, "type": "dict", "options": { - "byte-caching": {"required": False, "type": "str", + "byte_caching": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "byte-caching-opt": {"required": False, "type": "str", + "byte_caching_opt": {"required": False, "type": "str", "choices": ["mem-only", "mem-disk"]}, - "log-traffic": {"required": False, "type": "str", + "log_traffic": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "port": {"required": False, "type": "str"}, - "secure-tunnel": {"required": False, "type": "str", + "secure_tunnel": {"required": False, "type": "str", "choices": ["enable", "disable"]}, "ssl": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "ssl-port": {"required": False, "type": "int"}, + "ssl_port": {"required": False, "type": "int"}, "status": {"required": False, "type": "str", "choices": ["enable", "disable"]}, - "tunnel-sharing": {"required": False, "type": "str", + "tunnel_sharing": {"required": False, "type": "str", "choices": ["private", "shared", "express-shared"]} }}, "transparent": {"required": False, "type": "str", @@ -647,14 +734,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_wanopt(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_wanopt(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_wanopt(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_wanopt_settings.py b/lib/ansible/modules/network/fortios/fortios_wanopt_settings.py index 5eb82b156d8..c58434375ff 100644 --- a/lib/ansible/modules/network/fortios/fortios_wanopt_settings.py +++ b/lib/ansible/modules/network/fortios/fortios_wanopt_settings.py @@ -26,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_wanopt_settings short_description: Configure WAN optimization settings in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by allowing the + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify wanopt feature and settings category. Examples include all parameters and values need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -41,46 +41,59 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool default: true + version_added: 2.9 wanopt_settings: description: - Configure WAN optimization settings. default: null + type: dict suboptions: - auto-detect-algorithm: + auto_detect_algorithm: description: - Auto detection algorithms used in tunnel negotiations. + type: str choices: - simple - diff-req-resp - host-id: + host_id: description: - Local host ID (must also be entered in the remote FortiGate's peer list). - tunnel-ssl-algorithm: + type: str + tunnel_ssl_algorithm: description: - Relative strength of encryption algorithms accepted during tunnel negotiation. + type: str choices: - low ''' @@ -92,6 +105,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure WAN optimization settings. fortios_wanopt_settings: @@ -101,9 +115,9 @@ EXAMPLES = ''' vdom: "{{ vdom }}" https: "False" wanopt_settings: - auto-detect-algorithm: "simple" - host-id: "myhostname" - tunnel-ssl-algorithm: "low" + auto_detect_algorithm: "simple" + host_id: "myhostname" + tunnel_ssl_algorithm: "low" ''' RETURN = ''' @@ -166,12 +180,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -179,11 +197,11 @@ def login(data, fos): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_wanopt_settings_data(json): - option_list = ['auto-detect-algorithm', 'host-id', 'tunnel-ssl-algorithm'] + option_list = ['auto_detect_algorithm', 'host_id', 'tunnel_ssl_algorithm'] dictionary = {} for attribute in option_list: @@ -193,10 +211,23 @@ def filter_wanopt_settings_data(json): return dictionary +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data + + return data + + def wanopt_settings(data, fos): vdom = data['vdom'] wanopt_settings_data = data['wanopt_settings'] - filtered_data = filter_wanopt_settings_data(wanopt_settings_data) + filtered_data = underscore_to_hyphen(filter_wanopt_settings_data(wanopt_settings_data)) return fos.set('wanopt', 'settings', @@ -204,30 +235,36 @@ def wanopt_settings(data, fos): vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_wanopt(data, fos): - login(data, fos) if data['wanopt_settings']: resp = wanopt_settings(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, "wanopt_settings": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "auto-detect-algorithm": {"required": False, "type": "str", + "auto_detect_algorithm": {"required": False, "type": "str", "choices": ["simple", "diff-req-resp"]}, - "host-id": {"required": False, "type": "str"}, - "tunnel-ssl-algorithm": {"required": False, "type": "str", + "host_id": {"required": False, "type": "str"}, + "tunnel_ssl_algorithm": {"required": False, "type": "str", "choices": ["low"]} } @@ -236,14 +273,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_wanopt(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_wanopt(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_wanopt(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_webfilter_content.py b/lib/ansible/modules/network/fortios/fortios_webfilter_content.py index 38eb2fc3cca..9ea812e2b4d 100644 --- a/lib/ansible/modules/network/fortios/fortios_webfilter_content.py +++ b/lib/ansible/modules/network/fortios/fortios_webfilter_content.py @@ -1,6 +1,6 @@ #!/usr/bin/python from __future__ import (absolute_import, division, print_function) -# Copyright 2018 Fortinet, Inc. +# Copyright 2019 Fortinet, Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -29,10 +26,10 @@ DOCUMENTATION = ''' module: fortios_webfilter_content short_description: Configure Web filter banned word table in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by - allowing the user to configure webfilter feature and content category. - Examples includes all options and need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the + user to set and modify webfilter feature and content category. + Examples include all parameters and values need to be adjusted to datasources before usage. + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,56 +41,73 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool - default: false + default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 webfilter_content: description: - Configure Web filter banned word table. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent comment: description: - Optional comments. + type: str entries: description: - Configure banned word entries. + type: list suboptions: action: description: - Block or exempt word when a match is found. + type: str choices: - block - exempt lang: description: - Language of banned word. + type: str choices: - western - simch @@ -108,18 +122,22 @@ options: description: - Banned word. required: true - pattern-type: + type: str + pattern_type: description: - "Banned word pattern type: wildcard pattern or Perl regular expression." + type: str choices: - wildcard - regexp score: description: - - Score, to be applied every time the word appears on a web page (0 - 4294967295, default = 10). + - Score, to be applied every time the word appears on a web page (0 - 4294967295). + type: int status: description: - Enable/disable banned word. + type: str choices: - enable - disable @@ -127,9 +145,11 @@ options: description: - ID. required: true + type: int name: description: - Name of table. + type: str ''' EXAMPLES = ''' @@ -139,6 +159,7 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure Web filter banned word table. fortios_webfilter_content: @@ -146,15 +167,16 @@ EXAMPLES = ''' username: "{{ username }}" password: "{{ password }}" vdom: "{{ vdom }}" + https: "False" + state: "present" webfilter_content: - state: "present" comment: "Optional comments." entries: - action: "block" lang: "western" name: "default_name_7" - pattern-type: "wildcard" + pattern_type: "wildcard" score: "9" status: "enable" id: "11" @@ -221,14 +243,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -fos = None - -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -236,7 +260,7 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_webfilter_content_data(json): @@ -251,48 +275,66 @@ def filter_webfilter_content_data(json): return dictionary +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data + + return data + + def webfilter_content(data, fos): vdom = data['vdom'] + state = data['state'] webfilter_content_data = data['webfilter_content'] - filtered_data = filter_webfilter_content_data(webfilter_content_data) - if webfilter_content_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_webfilter_content_data(webfilter_content_data)) + + if state == "present": return fos.set('webfilter', 'content', data=filtered_data, vdom=vdom) - elif webfilter_content_data['state'] == "absent": + elif state == "absent": return fos.delete('webfilter', 'content', mkey=filtered_data['id'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_webfilter(data, fos): - login(data) - methodlist = ['webfilter_content'] - for method in methodlist: - if data[method]: - resp = eval(method)(data, fos) - break + if data['webfilter_content']: + resp = webfilter_content(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, - "https": {"required": False, "type": "bool", "default": "False"}, + "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "webfilter_content": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, "comment": {"required": False, "type": "str"}, "entries": {"required": False, "type": "list", "options": { @@ -303,7 +345,7 @@ def main(): "japanese", "korean", "french", "thai", "spanish", "cyrillic"]}, "name": {"required": True, "type": "str"}, - "pattern-type": {"required": False, "type": "str", + "pattern_type": {"required": False, "type": "str", "choices": ["wildcard", "regexp"]}, "score": {"required": False, "type": "int"}, "status": {"required": False, "type": "str", @@ -318,15 +360,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_webfilter(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_webfilter(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_webfilter(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/lib/ansible/modules/network/fortios/fortios_webfilter_content_header.py b/lib/ansible/modules/network/fortios/fortios_webfilter_content_header.py index 3eff6886227..0d253ebcc25 100644 --- a/lib/ansible/modules/network/fortios/fortios_webfilter_content_header.py +++ b/lib/ansible/modules/network/fortios/fortios_webfilter_content_header.py @@ -1,6 +1,6 @@ #!/usr/bin/python from __future__ import (absolute_import, division, print_function) -# Copyright 2018 Fortinet, Inc. +# Copyright 2019 Fortinet, Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,9 +14,6 @@ from __future__ import (absolute_import, division, print_function) # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# the lib use python logging can get it if the following is set in your -# Ansible config. __metaclass__ = type @@ -27,12 +24,12 @@ ANSIBLE_METADATA = {'status': ['preview'], DOCUMENTATION = ''' --- module: fortios_webfilter_content_header -short_description: Configure content types used by Web filter. +short_description: Configure content types used by Web filter in Fortinet's FortiOS and FortiGate. description: - - This module is able to configure a FortiGate or FortiOS by - allowing the user to configure webfilter feature and content_header category. - Examples includes all options and need to be adjusted to datasources before usage. - Tested with FOS v6.0.2 + - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the + user to set and modify webfilter feature and content_header category. + Examples include all parameters and values need to be adjusted to datasources before usage. + Tested with FOS v6.0.5 version_added: "2.8" author: - Miguel Angel Munoz (@mamunozgonzalez) @@ -44,50 +41,66 @@ requirements: - fortiosapi>=0.9.8 options: host: - description: - - FortiOS or FortiGate ip address. - required: true + description: + - FortiOS or FortiGate IP address. + type: str + required: false username: description: - FortiOS or FortiGate username. - required: true + type: str + required: false password: description: - FortiOS or FortiGate password. + type: str default: "" vdom: description: - Virtual domain, among those defined previously. A vdom is a virtual instance of the FortiGate that can be configured and used as a different unit. + type: str default: root https: description: - - Indicates if the requests towards FortiGate must use HTTPS - protocol + - Indicates if the requests towards FortiGate must use HTTPS protocol. + type: bool + default: true + ssl_verify: + description: + - Ensures FortiGate certificate must be verified by a proper CA. type: bool - default: false + default: true + version_added: 2.9 + state: + description: + - Indicates whether to create or remove the object. + type: str + required: true + choices: + - present + - absent + version_added: 2.9 webfilter_content_header: description: - Configure content types used by Web filter. default: null + type: dict suboptions: - state: - description: - - Indicates whether to create or remove the object - choices: - - present - - absent comment: description: - Optional comments. + type: str entries: description: - Configure content types used by web filter. + type: list suboptions: action: description: - Action to take for this content type. + type: str choices: - block - allow @@ -95,17 +108,21 @@ options: category: description: - Categories that this content type applies to. + type: str pattern: description: - Content type (regular expression). required: true + type: str id: description: - ID. required: true + type: int name: description: - Name of table. + type: str ''' EXAMPLES = ''' @@ -115,15 +132,17 @@ EXAMPLES = ''' username: "admin" password: "" vdom: "root" + ssl_verify: "False" tasks: - name: Configure content types used by Web filter. fortios_webfilter_content_header: - host: "{{ host }}" + host: "{{ host }}" username: "{{ username }}" password: "{{ password }}" - vdom: "{{ vdom }}" + vdom: "{{ vdom }}" + https: "False" + state: "present" webfilter_content_header: - state: "present" comment: "Optional comments." entries: - @@ -154,7 +173,7 @@ mkey: description: Master key (id) used in the last call to FortiGate returned: success type: str - sample: "key1" + sample: "id" name: description: Name of the table used to fulfill the request returned: always @@ -194,14 +213,16 @@ version: ''' from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortios.fortios import FortiOSHandler +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -fos = None - -def login(data): +def login(data, fos): host = data['host'] username = data['username'] password = data['password'] + ssl_verify = data['ssl_verify'] fos.debug('on') if 'https' in data and not data['https']: @@ -209,7 +230,7 @@ def login(data): else: fos.https('on') - fos.login(host, username, password) + fos.login(host, username, password, verify=ssl_verify) def filter_webfilter_content_header_data(json): @@ -218,55 +239,72 @@ def filter_webfilter_content_header_data(json): dictionary = {} for attribute in option_list: - if attribute in json: + if attribute in json and json[attribute] is not None: dictionary[attribute] = json[attribute] return dictionary +def underscore_to_hyphen(data): + if isinstance(data, list): + for elem in data: + elem = underscore_to_hyphen(elem) + elif isinstance(data, dict): + new_data = {} + for k, v in data.items(): + new_data[k.replace('_', '-')] = underscore_to_hyphen(v) + data = new_data + + return data + + def webfilter_content_header(data, fos): vdom = data['vdom'] + state = data['state'] webfilter_content_header_data = data['webfilter_content_header'] - filtered_data = filter_webfilter_content_header_data( - webfilter_content_header_data) - if webfilter_content_header_data['state'] == "present": + filtered_data = underscore_to_hyphen(filter_webfilter_content_header_data(webfilter_content_header_data)) + + if state == "present": return fos.set('webfilter', 'content-header', data=filtered_data, vdom=vdom) - elif webfilter_content_header_data['state'] == "absent": + elif state == "absent": return fos.delete('webfilter', 'content-header', mkey=filtered_data['id'], vdom=vdom) +def is_successful_status(status): + return status['status'] == "success" or \ + status['http_method'] == "DELETE" and status['http_status'] == 404 + + def fortios_webfilter(data, fos): - login(data) - methodlist = ['webfilter_content_header'] - for method in methodlist: - if data[method]: - resp = eval(method)(data, fos) - break + if data['webfilter_content_header']: + resp = webfilter_content_header(data, fos) - fos.logout() - return not resp['status'] == "success", resp['status'] == "success", resp + return not is_successful_status(resp), \ + resp['status'] == "success", \ + resp def main(): fields = { - "host": {"required": True, "type": "str"}, - "username": {"required": True, "type": "str"}, - "password": {"required": False, "type": "str", "no_log": True}, + "host": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "default": "", "no_log": True}, "vdom": {"required": False, "type": "str", "default": "root"}, - "https": {"required": False, "type": "bool", "default": "False"}, + "https": {"required": False, "type": "bool", "default": True}, + "ssl_verify": {"required": False, "type": "bool", "default": True}, + "state": {"required": True, "type": "str", + "choices": ["present", "absent"]}, "webfilter_content_header": { - "required": False, "type": "dict", + "required": False, "type": "dict", "default": None, "options": { - "state": {"required": True, "type": "str", - "choices": ["present", "absent"]}, "comment": {"required": False, "type": "str"}, "entries": {"required": False, "type": "list", "options": { @@ -284,15 +322,31 @@ def main(): module = AnsibleModule(argument_spec=fields, supports_check_mode=False) - try: - from fortiosapi import FortiOSAPI - except ImportError: - module.fail_json(msg="fortiosapi module is required") - global fos - fos = FortiOSAPI() + # legacy_mode refers to using fortiosapi instead of HTTPAPI + legacy_mode = 'host' in module.params and module.params['host'] is not None and \ + 'username' in module.params and module.params['username'] is not None and \ + 'password' in module.params and module.params['password'] is not None + + if not legacy_mode: + if module._socket_path: + connection = Connection(module._socket_path) + fos = FortiOSHandler(connection) + + is_error, has_changed, result = fortios_webfilter(module.params, fos) + else: + module.fail_json(**FAIL_SOCKET_MSG) + else: + try: + from fortiosapi import FortiOSAPI + except ImportError: + module.fail_json(msg="fortiosapi module is required") + + fos = FortiOSAPI() - is_error, has_changed, result = fortios_webfilter(module.params, fos) + login(module.params, fos) + is_error, has_changed, result = fortios_webfilter(module.params, fos) + fos.logout() if not is_error: module.exit_json(changed=has_changed, meta=result) diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index cecbf9cbbf8..1592ee9bf05 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -3697,43 +3697,9 @@ lib/ansible/modules/network/fortios/fortios_switch_controller_lldp_profile.py va lib/ansible/modules/network/fortios/fortios_switch_controller_managed_switch.py validate-modules:E336 lib/ansible/modules/network/fortios/fortios_system_dhcp_server.py validate-modules:E326 lib/ansible/modules/network/fortios/fortios_system_global.py validate-modules:E326 -lib/ansible/modules/network/fortios/fortios_user_adgrp.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_user_adgrp.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_user_device.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_user_radius.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_user_radius.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_user_tacacsplus.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_user_tacacsplus.py validate-modules:E337 lib/ansible/modules/network/fortios/fortios_voip_profile.py validate-modules:E326 -lib/ansible/modules/network/fortios/fortios_voip_profile.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_voip_profile.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_concentrator.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_concentrator.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_forticlient.py validate-modules:E337 lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey.py validate-modules:E326 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey.py validate-modules:E337 lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey_interface.py validate-modules:E326 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey_interface.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_manualkey_interface.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1_interface.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase1_interface.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2_interface.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_vpn_ipsec_phase2_interface.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_vpn_ssl_settings.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_vpn_ssl_settings.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_vpn_ssl_web_portal.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_vpn_ssl_web_portal.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_waf_profile.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_waf_profile.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_wanopt_profile.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_wanopt_profile.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_wanopt_settings.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_wanopt_settings.py validate-modules:E337 lib/ansible/modules/network/fortios/fortios_web_proxy_explicit.py validate-modules:E336 lib/ansible/modules/network/fortios/fortios_web_proxy_explicit.py validate-modules:E337 lib/ansible/modules/network/fortios/fortios_web_proxy_global.py validate-modules:E336 @@ -3744,9 +3710,6 @@ lib/ansible/modules/network/fortios/fortios_webfilter.py validate-modules:E326 lib/ansible/modules/network/fortios/fortios_webfilter.py validate-modules:E328 lib/ansible/modules/network/fortios/fortios_webfilter.py validate-modules:E336 lib/ansible/modules/network/fortios/fortios_webfilter.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_webfilter_content.py validate-modules:E336 -lib/ansible/modules/network/fortios/fortios_webfilter_content.py validate-modules:E337 -lib/ansible/modules/network/fortios/fortios_webfilter_content_header.py validate-modules:E337 lib/ansible/modules/network/fortios/fortios_webfilter_fortiguard.py validate-modules:E336 lib/ansible/modules/network/fortios/fortios_webfilter_fortiguard.py validate-modules:E337 lib/ansible/modules/network/fortios/fortios_webfilter_ftgd_local_cat.py validate-modules:E337 diff --git a/test/units/modules/network/fortios/test_fortios_user_adgrp.py b/test/units/modules/network/fortios/test_fortios_user_adgrp.py new file mode 100644 index 00000000000..198ef2cb69b --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_user_adgrp.py @@ -0,0 +1,209 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_user_adgrp +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_user_adgrp.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_user_adgrp_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_adgrp': { + 'name': 'default_name_3', + 'server_name': 'test_value_4' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_adgrp.fortios_user(input_data, fos_instance) + + expected_data = { + 'name': 'default_name_3', + 'server-name': 'test_value_4' + } + + set_method_mock.assert_called_with('user', 'adgrp', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_user_adgrp_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_adgrp': { + 'name': 'default_name_3', + 'server_name': 'test_value_4' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_adgrp.fortios_user(input_data, fos_instance) + + expected_data = { + 'name': 'default_name_3', + 'server-name': 'test_value_4' + } + + set_method_mock.assert_called_with('user', 'adgrp', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_user_adgrp_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'user_adgrp': { + 'name': 'default_name_3', + 'server_name': 'test_value_4' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_adgrp.fortios_user(input_data, fos_instance) + + delete_method_mock.assert_called_with('user', 'adgrp', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_user_adgrp_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'user_adgrp': { + 'name': 'default_name_3', + 'server_name': 'test_value_4' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_adgrp.fortios_user(input_data, fos_instance) + + delete_method_mock.assert_called_with('user', 'adgrp', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_user_adgrp_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_adgrp': { + 'name': 'default_name_3', + 'server_name': 'test_value_4' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_adgrp.fortios_user(input_data, fos_instance) + + expected_data = { + 'name': 'default_name_3', + 'server-name': 'test_value_4' + } + + set_method_mock.assert_called_with('user', 'adgrp', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_user_adgrp_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_adgrp': { + 'random_attribute_not_valid': 'tag', + 'name': 'default_name_3', + 'server_name': 'test_value_4' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_adgrp.fortios_user(input_data, fos_instance) + + expected_data = { + 'name': 'default_name_3', + 'server-name': 'test_value_4' + } + + set_method_mock.assert_called_with('user', 'adgrp', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_user_device.py b/test/units/modules/network/fortios/test_fortios_user_device.py index 0608346b640..1a21c50416a 100644 --- a/test/units/modules/network/fortios/test_fortios_user_device.py +++ b/test/units/modules/network/fortios/test_fortios_user_device.py @@ -17,7 +17,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import os +import json import pytest +from mock import ANY from ansible.module_utils.network.fortios.fortios import FortiOSHandler try: @@ -45,30 +48,28 @@ def test_user_device_creation(mocker): 'username': 'admin', 'state': 'present', 'user_device': { + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'master_device': 'master', - 'alias': 'myuser', - 'mac': '00:01:04:03:ab:c3:32', - 'user': 'myuser', + 'mac': 'test_value_7', + 'master_device': 'test_value_8', 'type': 'unknown', - 'tagging': 'tag', - 'avatar': 'avatar1' + 'user': 'test_value_10' }, 'vdom': 'root'} is_error, changed, response = fortios_user_device.fortios_user(input_data, fos_instance) expected_data = { - 'alias': 'myuser', + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'mac': '00:01:04:03:ab:c3:32', + 'mac': 'test_value_7', + 'master-device': 'test_value_8', 'type': 'unknown', - 'user': 'myuser', - 'tagging': 'tag', - 'avatar': 'avatar1', - 'master-device': 'master' + 'user': 'test_value_10' } set_method_mock.assert_called_with('user', 'device', data=expected_data, vdom='root') @@ -89,30 +90,28 @@ def test_user_device_creation_fails(mocker): 'username': 'admin', 'state': 'present', 'user_device': { + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'master_device': 'master', - 'alias': 'myuser', - 'mac': '00:01:04:03:ab:c3:32', - 'user': 'myuser', + 'mac': 'test_value_7', + 'master_device': 'test_value_8', 'type': 'unknown', - 'tagging': 'tag', - 'avatar': 'avatar1' + 'user': 'test_value_10' }, 'vdom': 'root'} is_error, changed, response = fortios_user_device.fortios_user(input_data, fos_instance) expected_data = { - 'alias': 'myuser', + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'mac': '00:01:04:03:ab:c3:32', + 'mac': 'test_value_7', + 'master-device': 'test_value_8', 'type': 'unknown', - 'user': 'myuser', - 'tagging': 'tag', - 'avatar': 'avatar1', - 'master-device': 'master' + 'user': 'test_value_10' } set_method_mock.assert_called_with('user', 'device', data=expected_data, vdom='root') @@ -123,7 +122,7 @@ def test_user_device_creation_fails(mocker): assert response['http_status'] == 500 -def test_users_device_removal(mocker): +def test_user_device_removal(mocker): schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} @@ -133,21 +132,20 @@ def test_users_device_removal(mocker): 'username': 'admin', 'state': 'absent', 'user_device': { + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'master_device': 'master', - 'alias': 'myuser', - 'mac': '00:01:04:03:ab:c3:32', - 'user': 'myuser', + 'mac': 'test_value_7', + 'master_device': 'test_value_8', 'type': 'unknown', - 'tagging': 'tag', - 'avatar': 'avatar1' + 'user': 'test_value_10' }, 'vdom': 'root'} is_error, changed, response = fortios_user_device.fortios_user(input_data, fos_instance) - delete_method_mock.assert_called_with('user', 'device', mkey='myuser', vdom='root') + delete_method_mock.assert_called_with('user', 'device', mkey=ANY, vdom='root') schema_method_mock.assert_not_called() assert not is_error assert changed @@ -165,21 +163,20 @@ def test_user_device_deletion_fails(mocker): 'username': 'admin', 'state': 'absent', 'user_device': { + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'master_device': 'master', - 'alias': 'myuser', - 'mac': '00:01:04:03:ab:c3:32', - 'user': 'myuser', + 'mac': 'test_value_7', + 'master_device': 'test_value_8', 'type': 'unknown', - 'tagging': 'tag', - 'avatar': 'avatar1' + 'user': 'test_value_10' }, 'vdom': 'root'} is_error, changed, response = fortios_user_device.fortios_user(input_data, fos_instance) - delete_method_mock.assert_called_with('user', 'device', mkey='myuser', vdom='root') + delete_method_mock.assert_called_with('user', 'device', mkey=ANY, vdom='root') schema_method_mock.assert_not_called() assert is_error assert not changed @@ -197,30 +194,28 @@ def test_user_device_idempotent(mocker): 'username': 'admin', 'state': 'present', 'user_device': { + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'master_device': 'master', - 'alias': 'myuser', - 'mac': '00:01:04:03:ab:c3:32', - 'user': 'myuser', + 'mac': 'test_value_7', + 'master_device': 'test_value_8', 'type': 'unknown', - 'tagging': 'tag', - 'avatar': 'avatar1' + 'user': 'test_value_10' }, 'vdom': 'root'} is_error, changed, response = fortios_user_device.fortios_user(input_data, fos_instance) expected_data = { - 'alias': 'myuser', + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'mac': '00:01:04:03:ab:c3:32', + 'mac': 'test_value_7', + 'master-device': 'test_value_8', 'type': 'unknown', - 'user': 'myuser', - 'tagging': 'tag', - 'avatar': 'avatar1', - 'master-device': 'master' + 'user': 'test_value_10' } set_method_mock.assert_called_with('user', 'device', data=expected_data, vdom='root') @@ -231,49 +226,6 @@ def test_user_device_idempotent(mocker): assert response['http_status'] == 404 -def test_user_device_filter_null_attributes(mocker): - schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') - - set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} - set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) - - input_data = { - 'username': 'admin', - 'state': 'present', - 'user_device': { - 'category': 'none', - 'comment': 'Comment.', - 'master_device': 'master', - 'alias': 'myuser', - 'mac': '00:01:04:03:ab:c3:32', - 'user': 'myuser', - 'type': 'unknown', - 'tagging': 'tag', - 'avatar': None - }, - 'vdom': 'root'} - - is_error, changed, response = fortios_user_device.fortios_user(input_data, fos_instance) - - expected_data = { - 'alias': 'myuser', - 'category': 'none', - 'comment': 'Comment.', - 'mac': '00:01:04:03:ab:c3:32', - 'type': 'unknown', - 'user': 'myuser', - 'tagging': 'tag', - 'master-device': 'master' - } - - set_method_mock.assert_called_with('user', 'device', data=expected_data, vdom='root') - schema_method_mock.assert_not_called() - assert not is_error - assert changed - assert response['status'] == 'success' - assert response['http_status'] == 200 - - def test_user_device_filter_foreign_attributes(mocker): schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') @@ -284,31 +236,29 @@ def test_user_device_filter_foreign_attributes(mocker): 'username': 'admin', 'state': 'present', 'user_device': { + 'random_attribute_not_valid': 'tag', + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'master_device': 'master', - 'alias': 'myuser', - 'mac': '00:01:04:03:ab:c3:32', - 'user': 'myuser', + 'mac': 'test_value_7', + 'master_device': 'test_value_8', 'type': 'unknown', - 'tagging': 'tag', - 'avatar': 'avatar1', - 'random_attribute_not_valid': 'tag' + 'user': 'test_value_10' }, 'vdom': 'root'} is_error, changed, response = fortios_user_device.fortios_user(input_data, fos_instance) expected_data = { - 'alias': 'myuser', + 'alias': 'test_value_3', + 'avatar': 'test_value_4', 'category': 'none', 'comment': 'Comment.', - 'mac': '00:01:04:03:ab:c3:32', + 'mac': 'test_value_7', + 'master-device': 'test_value_8', 'type': 'unknown', - 'user': 'myuser', - 'tagging': 'tag', - 'avatar': 'avatar1', - 'master-device': 'master' + 'user': 'test_value_10' } set_method_mock.assert_called_with('user', 'device', data=expected_data, vdom='root') diff --git a/test/units/modules/network/fortios/test_fortios_user_radius.py b/test/units/modules/network/fortios/test_fortios_user_radius.py new file mode 100644 index 00000000000..6e4e07118f1 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_user_radius.py @@ -0,0 +1,539 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_user_radius +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_user_radius.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_user_radius_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_radius': {'acct_all_servers': 'enable', + 'acct_interim_interval': '4', + 'all_usergroup': 'disable', + 'auth_type': 'auto', + 'h3c_compatibility': 'enable', + 'name': 'default_name_8', + 'nas_ip': 'test_value_9', + 'password_encoding': 'auto', + 'password_renewal': 'enable', + 'radius_coa': 'enable', + 'radius_port': '13', + 'rsso': 'enable', + 'rsso_context_timeout': '15', + 'rsso_endpoint_attribute': 'User-Name', + 'rsso_endpoint_block_attribute': 'User-Name', + 'rsso_ep_one_ip_only': 'enable', + 'rsso_flush_ip_session': 'enable', + 'rsso_log_flags': 'protocol-error', + 'rsso_log_period': '21', + 'rsso_radius_response': 'enable', + 'rsso_radius_server_port': '23', + 'rsso_secret': 'test_value_24', + 'rsso_validate_request_secret': 'enable', + 'secondary_secret': 'test_value_26', + 'secondary_server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source_ip': '84.230.14.30', + 'sso_attribute': 'User-Name', + 'sso_attribute_key': 'test_value_32', + 'sso_attribute_value_override': 'enable', + 'tertiary_secret': 'test_value_34', + 'tertiary_server': 'test_value_35', + 'timeout': '36', + 'use_management_vdom': 'enable', + 'username_case_sensitive': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_radius.fortios_user(input_data, fos_instance) + + expected_data = {'acct-all-servers': 'enable', + 'acct-interim-interval': '4', + 'all-usergroup': 'disable', + 'auth-type': 'auto', + 'h3c-compatibility': 'enable', + 'name': 'default_name_8', + 'nas-ip': 'test_value_9', + 'password-encoding': 'auto', + 'password-renewal': 'enable', + 'radius-coa': 'enable', + 'radius-port': '13', + 'rsso': 'enable', + 'rsso-context-timeout': '15', + 'rsso-endpoint-attribute': 'User-Name', + 'rsso-endpoint-block-attribute': 'User-Name', + 'rsso-ep-one-ip-only': 'enable', + 'rsso-flush-ip-session': 'enable', + 'rsso-log-flags': 'protocol-error', + 'rsso-log-period': '21', + 'rsso-radius-response': 'enable', + 'rsso-radius-server-port': '23', + 'rsso-secret': 'test_value_24', + 'rsso-validate-request-secret': 'enable', + 'secondary-secret': 'test_value_26', + 'secondary-server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source-ip': '84.230.14.30', + 'sso-attribute': 'User-Name', + 'sso-attribute-key': 'test_value_32', + 'sso-attribute-value-override': 'enable', + 'tertiary-secret': 'test_value_34', + 'tertiary-server': 'test_value_35', + 'timeout': '36', + 'use-management-vdom': 'enable', + 'username-case-sensitive': 'enable' + } + + set_method_mock.assert_called_with('user', 'radius', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_user_radius_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_radius': {'acct_all_servers': 'enable', + 'acct_interim_interval': '4', + 'all_usergroup': 'disable', + 'auth_type': 'auto', + 'h3c_compatibility': 'enable', + 'name': 'default_name_8', + 'nas_ip': 'test_value_9', + 'password_encoding': 'auto', + 'password_renewal': 'enable', + 'radius_coa': 'enable', + 'radius_port': '13', + 'rsso': 'enable', + 'rsso_context_timeout': '15', + 'rsso_endpoint_attribute': 'User-Name', + 'rsso_endpoint_block_attribute': 'User-Name', + 'rsso_ep_one_ip_only': 'enable', + 'rsso_flush_ip_session': 'enable', + 'rsso_log_flags': 'protocol-error', + 'rsso_log_period': '21', + 'rsso_radius_response': 'enable', + 'rsso_radius_server_port': '23', + 'rsso_secret': 'test_value_24', + 'rsso_validate_request_secret': 'enable', + 'secondary_secret': 'test_value_26', + 'secondary_server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source_ip': '84.230.14.30', + 'sso_attribute': 'User-Name', + 'sso_attribute_key': 'test_value_32', + 'sso_attribute_value_override': 'enable', + 'tertiary_secret': 'test_value_34', + 'tertiary_server': 'test_value_35', + 'timeout': '36', + 'use_management_vdom': 'enable', + 'username_case_sensitive': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_radius.fortios_user(input_data, fos_instance) + + expected_data = {'acct-all-servers': 'enable', + 'acct-interim-interval': '4', + 'all-usergroup': 'disable', + 'auth-type': 'auto', + 'h3c-compatibility': 'enable', + 'name': 'default_name_8', + 'nas-ip': 'test_value_9', + 'password-encoding': 'auto', + 'password-renewal': 'enable', + 'radius-coa': 'enable', + 'radius-port': '13', + 'rsso': 'enable', + 'rsso-context-timeout': '15', + 'rsso-endpoint-attribute': 'User-Name', + 'rsso-endpoint-block-attribute': 'User-Name', + 'rsso-ep-one-ip-only': 'enable', + 'rsso-flush-ip-session': 'enable', + 'rsso-log-flags': 'protocol-error', + 'rsso-log-period': '21', + 'rsso-radius-response': 'enable', + 'rsso-radius-server-port': '23', + 'rsso-secret': 'test_value_24', + 'rsso-validate-request-secret': 'enable', + 'secondary-secret': 'test_value_26', + 'secondary-server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source-ip': '84.230.14.30', + 'sso-attribute': 'User-Name', + 'sso-attribute-key': 'test_value_32', + 'sso-attribute-value-override': 'enable', + 'tertiary-secret': 'test_value_34', + 'tertiary-server': 'test_value_35', + 'timeout': '36', + 'use-management-vdom': 'enable', + 'username-case-sensitive': 'enable' + } + + set_method_mock.assert_called_with('user', 'radius', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_user_radius_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'user_radius': {'acct_all_servers': 'enable', + 'acct_interim_interval': '4', + 'all_usergroup': 'disable', + 'auth_type': 'auto', + 'h3c_compatibility': 'enable', + 'name': 'default_name_8', + 'nas_ip': 'test_value_9', + 'password_encoding': 'auto', + 'password_renewal': 'enable', + 'radius_coa': 'enable', + 'radius_port': '13', + 'rsso': 'enable', + 'rsso_context_timeout': '15', + 'rsso_endpoint_attribute': 'User-Name', + 'rsso_endpoint_block_attribute': 'User-Name', + 'rsso_ep_one_ip_only': 'enable', + 'rsso_flush_ip_session': 'enable', + 'rsso_log_flags': 'protocol-error', + 'rsso_log_period': '21', + 'rsso_radius_response': 'enable', + 'rsso_radius_server_port': '23', + 'rsso_secret': 'test_value_24', + 'rsso_validate_request_secret': 'enable', + 'secondary_secret': 'test_value_26', + 'secondary_server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source_ip': '84.230.14.30', + 'sso_attribute': 'User-Name', + 'sso_attribute_key': 'test_value_32', + 'sso_attribute_value_override': 'enable', + 'tertiary_secret': 'test_value_34', + 'tertiary_server': 'test_value_35', + 'timeout': '36', + 'use_management_vdom': 'enable', + 'username_case_sensitive': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_radius.fortios_user(input_data, fos_instance) + + delete_method_mock.assert_called_with('user', 'radius', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_user_radius_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'user_radius': {'acct_all_servers': 'enable', + 'acct_interim_interval': '4', + 'all_usergroup': 'disable', + 'auth_type': 'auto', + 'h3c_compatibility': 'enable', + 'name': 'default_name_8', + 'nas_ip': 'test_value_9', + 'password_encoding': 'auto', + 'password_renewal': 'enable', + 'radius_coa': 'enable', + 'radius_port': '13', + 'rsso': 'enable', + 'rsso_context_timeout': '15', + 'rsso_endpoint_attribute': 'User-Name', + 'rsso_endpoint_block_attribute': 'User-Name', + 'rsso_ep_one_ip_only': 'enable', + 'rsso_flush_ip_session': 'enable', + 'rsso_log_flags': 'protocol-error', + 'rsso_log_period': '21', + 'rsso_radius_response': 'enable', + 'rsso_radius_server_port': '23', + 'rsso_secret': 'test_value_24', + 'rsso_validate_request_secret': 'enable', + 'secondary_secret': 'test_value_26', + 'secondary_server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source_ip': '84.230.14.30', + 'sso_attribute': 'User-Name', + 'sso_attribute_key': 'test_value_32', + 'sso_attribute_value_override': 'enable', + 'tertiary_secret': 'test_value_34', + 'tertiary_server': 'test_value_35', + 'timeout': '36', + 'use_management_vdom': 'enable', + 'username_case_sensitive': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_radius.fortios_user(input_data, fos_instance) + + delete_method_mock.assert_called_with('user', 'radius', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_user_radius_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_radius': {'acct_all_servers': 'enable', + 'acct_interim_interval': '4', + 'all_usergroup': 'disable', + 'auth_type': 'auto', + 'h3c_compatibility': 'enable', + 'name': 'default_name_8', + 'nas_ip': 'test_value_9', + 'password_encoding': 'auto', + 'password_renewal': 'enable', + 'radius_coa': 'enable', + 'radius_port': '13', + 'rsso': 'enable', + 'rsso_context_timeout': '15', + 'rsso_endpoint_attribute': 'User-Name', + 'rsso_endpoint_block_attribute': 'User-Name', + 'rsso_ep_one_ip_only': 'enable', + 'rsso_flush_ip_session': 'enable', + 'rsso_log_flags': 'protocol-error', + 'rsso_log_period': '21', + 'rsso_radius_response': 'enable', + 'rsso_radius_server_port': '23', + 'rsso_secret': 'test_value_24', + 'rsso_validate_request_secret': 'enable', + 'secondary_secret': 'test_value_26', + 'secondary_server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source_ip': '84.230.14.30', + 'sso_attribute': 'User-Name', + 'sso_attribute_key': 'test_value_32', + 'sso_attribute_value_override': 'enable', + 'tertiary_secret': 'test_value_34', + 'tertiary_server': 'test_value_35', + 'timeout': '36', + 'use_management_vdom': 'enable', + 'username_case_sensitive': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_radius.fortios_user(input_data, fos_instance) + + expected_data = {'acct-all-servers': 'enable', + 'acct-interim-interval': '4', + 'all-usergroup': 'disable', + 'auth-type': 'auto', + 'h3c-compatibility': 'enable', + 'name': 'default_name_8', + 'nas-ip': 'test_value_9', + 'password-encoding': 'auto', + 'password-renewal': 'enable', + 'radius-coa': 'enable', + 'radius-port': '13', + 'rsso': 'enable', + 'rsso-context-timeout': '15', + 'rsso-endpoint-attribute': 'User-Name', + 'rsso-endpoint-block-attribute': 'User-Name', + 'rsso-ep-one-ip-only': 'enable', + 'rsso-flush-ip-session': 'enable', + 'rsso-log-flags': 'protocol-error', + 'rsso-log-period': '21', + 'rsso-radius-response': 'enable', + 'rsso-radius-server-port': '23', + 'rsso-secret': 'test_value_24', + 'rsso-validate-request-secret': 'enable', + 'secondary-secret': 'test_value_26', + 'secondary-server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source-ip': '84.230.14.30', + 'sso-attribute': 'User-Name', + 'sso-attribute-key': 'test_value_32', + 'sso-attribute-value-override': 'enable', + 'tertiary-secret': 'test_value_34', + 'tertiary-server': 'test_value_35', + 'timeout': '36', + 'use-management-vdom': 'enable', + 'username-case-sensitive': 'enable' + } + + set_method_mock.assert_called_with('user', 'radius', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_user_radius_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_radius': { + 'random_attribute_not_valid': 'tag', 'acct_all_servers': 'enable', + 'acct_interim_interval': '4', + 'all_usergroup': 'disable', + 'auth_type': 'auto', + 'h3c_compatibility': 'enable', + 'name': 'default_name_8', + 'nas_ip': 'test_value_9', + 'password_encoding': 'auto', + 'password_renewal': 'enable', + 'radius_coa': 'enable', + 'radius_port': '13', + 'rsso': 'enable', + 'rsso_context_timeout': '15', + 'rsso_endpoint_attribute': 'User-Name', + 'rsso_endpoint_block_attribute': 'User-Name', + 'rsso_ep_one_ip_only': 'enable', + 'rsso_flush_ip_session': 'enable', + 'rsso_log_flags': 'protocol-error', + 'rsso_log_period': '21', + 'rsso_radius_response': 'enable', + 'rsso_radius_server_port': '23', + 'rsso_secret': 'test_value_24', + 'rsso_validate_request_secret': 'enable', + 'secondary_secret': 'test_value_26', + 'secondary_server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source_ip': '84.230.14.30', + 'sso_attribute': 'User-Name', + 'sso_attribute_key': 'test_value_32', + 'sso_attribute_value_override': 'enable', + 'tertiary_secret': 'test_value_34', + 'tertiary_server': 'test_value_35', + 'timeout': '36', + 'use_management_vdom': 'enable', + 'username_case_sensitive': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_radius.fortios_user(input_data, fos_instance) + + expected_data = {'acct-all-servers': 'enable', + 'acct-interim-interval': '4', + 'all-usergroup': 'disable', + 'auth-type': 'auto', + 'h3c-compatibility': 'enable', + 'name': 'default_name_8', + 'nas-ip': 'test_value_9', + 'password-encoding': 'auto', + 'password-renewal': 'enable', + 'radius-coa': 'enable', + 'radius-port': '13', + 'rsso': 'enable', + 'rsso-context-timeout': '15', + 'rsso-endpoint-attribute': 'User-Name', + 'rsso-endpoint-block-attribute': 'User-Name', + 'rsso-ep-one-ip-only': 'enable', + 'rsso-flush-ip-session': 'enable', + 'rsso-log-flags': 'protocol-error', + 'rsso-log-period': '21', + 'rsso-radius-response': 'enable', + 'rsso-radius-server-port': '23', + 'rsso-secret': 'test_value_24', + 'rsso-validate-request-secret': 'enable', + 'secondary-secret': 'test_value_26', + 'secondary-server': 'test_value_27', + 'secret': 'test_value_28', + 'server': '192.168.100.29', + 'source-ip': '84.230.14.30', + 'sso-attribute': 'User-Name', + 'sso-attribute-key': 'test_value_32', + 'sso-attribute-value-override': 'enable', + 'tertiary-secret': 'test_value_34', + 'tertiary-server': 'test_value_35', + 'timeout': '36', + 'use-management-vdom': 'enable', + 'username-case-sensitive': 'enable' + } + + set_method_mock.assert_called_with('user', 'radius', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_user_tacacsplus.py b/test/units/modules/network/fortios/test_fortios_user_tacacsplus.py new file mode 100644 index 00000000000..4df18b443d3 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_user_tacacsplus.py @@ -0,0 +1,299 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_user_tacacsplus +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_user_tacacsplus.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_user_tacacsplus_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_tacacsplus': { + 'authen_type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary_key': 'test_value_8', + 'secondary_server': 'test_value_9', + 'server': '192.168.100.10', + 'source_ip': '84.230.14.11', + 'tertiary_key': 'test_value_12', + 'tertiary_server': 'test_value_13' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_tacacsplus.fortios_user(input_data, fos_instance) + + expected_data = { + 'authen-type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary-key': 'test_value_8', + 'secondary-server': 'test_value_9', + 'server': '192.168.100.10', + 'source-ip': '84.230.14.11', + 'tertiary-key': 'test_value_12', + 'tertiary-server': 'test_value_13' + } + + set_method_mock.assert_called_with('user', 'tacacs+', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_user_tacacsplus_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_tacacsplus': { + 'authen_type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary_key': 'test_value_8', + 'secondary_server': 'test_value_9', + 'server': '192.168.100.10', + 'source_ip': '84.230.14.11', + 'tertiary_key': 'test_value_12', + 'tertiary_server': 'test_value_13' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_tacacsplus.fortios_user(input_data, fos_instance) + + expected_data = { + 'authen-type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary-key': 'test_value_8', + 'secondary-server': 'test_value_9', + 'server': '192.168.100.10', + 'source-ip': '84.230.14.11', + 'tertiary-key': 'test_value_12', + 'tertiary-server': 'test_value_13' + } + + set_method_mock.assert_called_with('user', 'tacacs+', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_user_tacacsplus_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'user_tacacsplus': { + 'authen_type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary_key': 'test_value_8', + 'secondary_server': 'test_value_9', + 'server': '192.168.100.10', + 'source_ip': '84.230.14.11', + 'tertiary_key': 'test_value_12', + 'tertiary_server': 'test_value_13' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_tacacsplus.fortios_user(input_data, fos_instance) + + delete_method_mock.assert_called_with('user', 'tacacs+', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_user_tacacsplus_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'user_tacacsplus': { + 'authen_type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary_key': 'test_value_8', + 'secondary_server': 'test_value_9', + 'server': '192.168.100.10', + 'source_ip': '84.230.14.11', + 'tertiary_key': 'test_value_12', + 'tertiary_server': 'test_value_13' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_tacacsplus.fortios_user(input_data, fos_instance) + + delete_method_mock.assert_called_with('user', 'tacacs+', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_user_tacacsplus_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_tacacsplus': { + 'authen_type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary_key': 'test_value_8', + 'secondary_server': 'test_value_9', + 'server': '192.168.100.10', + 'source_ip': '84.230.14.11', + 'tertiary_key': 'test_value_12', + 'tertiary_server': 'test_value_13' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_tacacsplus.fortios_user(input_data, fos_instance) + + expected_data = { + 'authen-type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary-key': 'test_value_8', + 'secondary-server': 'test_value_9', + 'server': '192.168.100.10', + 'source-ip': '84.230.14.11', + 'tertiary-key': 'test_value_12', + 'tertiary-server': 'test_value_13' + } + + set_method_mock.assert_called_with('user', 'tacacs+', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_user_tacacsplus_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'user_tacacsplus': { + 'random_attribute_not_valid': 'tag', + 'authen_type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary_key': 'test_value_8', + 'secondary_server': 'test_value_9', + 'server': '192.168.100.10', + 'source_ip': '84.230.14.11', + 'tertiary_key': 'test_value_12', + 'tertiary_server': 'test_value_13' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_user_tacacsplus.fortios_user(input_data, fos_instance) + + expected_data = { + 'authen-type': 'mschap', + 'authorization': 'enable', + 'key': 'test_value_5', + 'name': 'default_name_6', + 'port': '7', + 'secondary-key': 'test_value_8', + 'secondary-server': 'test_value_9', + 'server': '192.168.100.10', + 'source-ip': '84.230.14.11', + 'tertiary-key': 'test_value_12', + 'tertiary-server': 'test_value_13' + } + + set_method_mock.assert_called_with('user', 'tacacs+', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_voip_profile.py b/test/units/modules/network/fortios/test_fortios_voip_profile.py new file mode 100644 index 00000000000..3ce7064a828 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_voip_profile.py @@ -0,0 +1,219 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_voip_profile +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_voip_profile.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_voip_profile_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'voip_profile': { + 'comment': 'Comment.', + 'name': 'default_name_4', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_voip_profile.fortios_voip(input_data, fos_instance) + + expected_data = { + 'comment': 'Comment.', + 'name': 'default_name_4', + + } + + set_method_mock.assert_called_with('voip', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_voip_profile_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'voip_profile': { + 'comment': 'Comment.', + 'name': 'default_name_4', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_voip_profile.fortios_voip(input_data, fos_instance) + + expected_data = { + 'comment': 'Comment.', + 'name': 'default_name_4', + + } + + set_method_mock.assert_called_with('voip', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_voip_profile_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'voip_profile': { + 'comment': 'Comment.', + 'name': 'default_name_4', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_voip_profile.fortios_voip(input_data, fos_instance) + + delete_method_mock.assert_called_with('voip', 'profile', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_voip_profile_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'voip_profile': { + 'comment': 'Comment.', + 'name': 'default_name_4', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_voip_profile.fortios_voip(input_data, fos_instance) + + delete_method_mock.assert_called_with('voip', 'profile', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_voip_profile_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'voip_profile': { + 'comment': 'Comment.', + 'name': 'default_name_4', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_voip_profile.fortios_voip(input_data, fos_instance) + + expected_data = { + 'comment': 'Comment.', + 'name': 'default_name_4', + + } + + set_method_mock.assert_called_with('voip', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_voip_profile_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'voip_profile': { + 'random_attribute_not_valid': 'tag', + 'comment': 'Comment.', + 'name': 'default_name_4', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_voip_profile.fortios_voip(input_data, fos_instance) + + expected_data = { + 'comment': 'Comment.', + 'name': 'default_name_4', + + } + + set_method_mock.assert_called_with('voip', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ipsec_concentrator.py b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_concentrator.py new file mode 100644 index 00000000000..fb103d3c7be --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_concentrator.py @@ -0,0 +1,199 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ipsec_concentrator +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ipsec_concentrator.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ipsec_concentrator_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_concentrator': {'name': 'default_name_3', + 'src_check': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_concentrator.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = {'name': 'default_name_3', + 'src-check': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'concentrator', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_concentrator_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_concentrator': {'name': 'default_name_3', + 'src_check': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_concentrator.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = {'name': 'default_name_3', + 'src-check': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'concentrator', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_concentrator_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_concentrator': {'name': 'default_name_3', + 'src_check': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_concentrator.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'concentrator', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_concentrator_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_concentrator': {'name': 'default_name_3', + 'src_check': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_concentrator.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'concentrator', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_concentrator_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_concentrator': {'name': 'default_name_3', + 'src_check': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_concentrator.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = {'name': 'default_name_3', + 'src-check': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'concentrator', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ipsec_concentrator_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_concentrator': { + 'random_attribute_not_valid': 'tag', 'name': 'default_name_3', + 'src_check': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_concentrator.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = {'name': 'default_name_3', + 'src-check': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'concentrator', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ipsec_forticlient.py b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_forticlient.py new file mode 100644 index 00000000000..f448f3370ba --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_forticlient.py @@ -0,0 +1,229 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ipsec_forticlient +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ipsec_forticlient.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ipsec_forticlient_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_forticlient': { + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_forticlient.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'forticlient', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_forticlient_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_forticlient': { + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_forticlient.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'forticlient', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_forticlient_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_forticlient': { + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_forticlient.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'forticlient', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_forticlient_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_forticlient': { + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_forticlient.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'forticlient', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_forticlient_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_forticlient': { + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_forticlient.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'forticlient', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ipsec_forticlient_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_forticlient': { + 'random_attribute_not_valid': 'tag', + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_forticlient.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'phase2name': 'test_value_3', + 'realm': 'test_value_4', + 'status': 'enable', + 'usergroupname': 'test_value_6' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'forticlient', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ipsec_manualkey.py b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_manualkey.py new file mode 100644 index 00000000000..0bc4787ca2d --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_manualkey.py @@ -0,0 +1,289 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ipsec_manualkey +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ipsec_manualkey.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ipsec_manualkey_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_manualkey': { + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local_gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote_gw': 'test_value_11', + 'remotespi': 'test_value_12' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local-gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote-gw': 'test_value_11', + 'remotespi': 'test_value_12' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'manualkey', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_manualkey_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_manualkey': { + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local_gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote_gw': 'test_value_11', + 'remotespi': 'test_value_12' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local-gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote-gw': 'test_value_11', + 'remotespi': 'test_value_12' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'manualkey', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_manualkey_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_manualkey': { + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local_gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote_gw': 'test_value_11', + 'remotespi': 'test_value_12' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'manualkey', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_manualkey_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_manualkey': { + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local_gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote_gw': 'test_value_11', + 'remotespi': 'test_value_12' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'manualkey', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_manualkey_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_manualkey': { + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local_gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote_gw': 'test_value_11', + 'remotespi': 'test_value_12' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local-gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote-gw': 'test_value_11', + 'remotespi': 'test_value_12' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'manualkey', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ipsec_manualkey_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_manualkey': { + 'random_attribute_not_valid': 'tag', + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local_gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote_gw': 'test_value_11', + 'remotespi': 'test_value_12' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'authentication': 'null', + 'authkey': 'test_value_4', + 'enckey': 'test_value_5', + 'encryption': 'null', + 'interface': 'test_value_7', + 'local-gw': 'test_value_8', + 'localspi': 'test_value_9', + 'name': 'default_name_10', + 'remote-gw': 'test_value_11', + 'remotespi': 'test_value_12' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'manualkey', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ipsec_manualkey_interface.py b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_manualkey_interface.py new file mode 100644 index 00000000000..d08c4f21420 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_manualkey_interface.py @@ -0,0 +1,329 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ipsec_manualkey_interface +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ipsec_manualkey_interface.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ipsec_manualkey_interface_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_manualkey_interface': { + 'addr_type': '4', + 'auth_alg': 'null', + 'auth_key': 'test_value_5', + 'enc_alg': 'null', + 'enc_key': 'test_value_7', + 'interface': 'test_value_8', + 'ip_version': '4', + 'local_gw': 'test_value_10', + 'local_gw6': 'test_value_11', + 'local_spi': 'test_value_12', + 'name': 'default_name_13', + 'remote_gw': 'test_value_14', + 'remote_gw6': 'test_value_15', + 'remote_spi': 'test_value_16' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'addr-type': '4', + 'auth-alg': 'null', + 'auth-key': 'test_value_5', + 'enc-alg': 'null', + 'enc-key': 'test_value_7', + 'interface': 'test_value_8', + 'ip-version': '4', + 'local-gw': 'test_value_10', + 'local-gw6': 'test_value_11', + 'local-spi': 'test_value_12', + 'name': 'default_name_13', + 'remote-gw': 'test_value_14', + 'remote-gw6': 'test_value_15', + 'remote-spi': 'test_value_16' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'manualkey-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_manualkey_interface_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_manualkey_interface': { + 'addr_type': '4', + 'auth_alg': 'null', + 'auth_key': 'test_value_5', + 'enc_alg': 'null', + 'enc_key': 'test_value_7', + 'interface': 'test_value_8', + 'ip_version': '4', + 'local_gw': 'test_value_10', + 'local_gw6': 'test_value_11', + 'local_spi': 'test_value_12', + 'name': 'default_name_13', + 'remote_gw': 'test_value_14', + 'remote_gw6': 'test_value_15', + 'remote_spi': 'test_value_16' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'addr-type': '4', + 'auth-alg': 'null', + 'auth-key': 'test_value_5', + 'enc-alg': 'null', + 'enc-key': 'test_value_7', + 'interface': 'test_value_8', + 'ip-version': '4', + 'local-gw': 'test_value_10', + 'local-gw6': 'test_value_11', + 'local-spi': 'test_value_12', + 'name': 'default_name_13', + 'remote-gw': 'test_value_14', + 'remote-gw6': 'test_value_15', + 'remote-spi': 'test_value_16' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'manualkey-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_manualkey_interface_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_manualkey_interface': { + 'addr_type': '4', + 'auth_alg': 'null', + 'auth_key': 'test_value_5', + 'enc_alg': 'null', + 'enc_key': 'test_value_7', + 'interface': 'test_value_8', + 'ip_version': '4', + 'local_gw': 'test_value_10', + 'local_gw6': 'test_value_11', + 'local_spi': 'test_value_12', + 'name': 'default_name_13', + 'remote_gw': 'test_value_14', + 'remote_gw6': 'test_value_15', + 'remote_spi': 'test_value_16' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey_interface.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'manualkey-interface', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_manualkey_interface_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_manualkey_interface': { + 'addr_type': '4', + 'auth_alg': 'null', + 'auth_key': 'test_value_5', + 'enc_alg': 'null', + 'enc_key': 'test_value_7', + 'interface': 'test_value_8', + 'ip_version': '4', + 'local_gw': 'test_value_10', + 'local_gw6': 'test_value_11', + 'local_spi': 'test_value_12', + 'name': 'default_name_13', + 'remote_gw': 'test_value_14', + 'remote_gw6': 'test_value_15', + 'remote_spi': 'test_value_16' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey_interface.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'manualkey-interface', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_manualkey_interface_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_manualkey_interface': { + 'addr_type': '4', + 'auth_alg': 'null', + 'auth_key': 'test_value_5', + 'enc_alg': 'null', + 'enc_key': 'test_value_7', + 'interface': 'test_value_8', + 'ip_version': '4', + 'local_gw': 'test_value_10', + 'local_gw6': 'test_value_11', + 'local_spi': 'test_value_12', + 'name': 'default_name_13', + 'remote_gw': 'test_value_14', + 'remote_gw6': 'test_value_15', + 'remote_spi': 'test_value_16' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'addr-type': '4', + 'auth-alg': 'null', + 'auth-key': 'test_value_5', + 'enc-alg': 'null', + 'enc-key': 'test_value_7', + 'interface': 'test_value_8', + 'ip-version': '4', + 'local-gw': 'test_value_10', + 'local-gw6': 'test_value_11', + 'local-spi': 'test_value_12', + 'name': 'default_name_13', + 'remote-gw': 'test_value_14', + 'remote-gw6': 'test_value_15', + 'remote-spi': 'test_value_16' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'manualkey-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ipsec_manualkey_interface_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_manualkey_interface': { + 'random_attribute_not_valid': 'tag', + 'addr_type': '4', + 'auth_alg': 'null', + 'auth_key': 'test_value_5', + 'enc_alg': 'null', + 'enc_key': 'test_value_7', + 'interface': 'test_value_8', + 'ip_version': '4', + 'local_gw': 'test_value_10', + 'local_gw6': 'test_value_11', + 'local_spi': 'test_value_12', + 'name': 'default_name_13', + 'remote_gw': 'test_value_14', + 'remote_gw6': 'test_value_15', + 'remote_spi': 'test_value_16' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_manualkey_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'addr-type': '4', + 'auth-alg': 'null', + 'auth-key': 'test_value_5', + 'enc-alg': 'null', + 'enc-key': 'test_value_7', + 'interface': 'test_value_8', + 'ip-version': '4', + 'local-gw': 'test_value_10', + 'local-gw6': 'test_value_11', + 'local-spi': 'test_value_12', + 'name': 'default_name_13', + 'remote-gw': 'test_value_14', + 'remote-gw6': 'test_value_15', + 'remote-spi': 'test_value_16' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'manualkey-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase1.py b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase1.py new file mode 100644 index 00000000000..b46caff350a --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase1.py @@ -0,0 +1,1149 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ipsec_phase1 +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ipsec_phase1.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ipsec_phase1_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase1': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_negotiate': 'enable', + 'banner': 'test_value_14', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '22', + 'dns_mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd_retrycount': '26', + 'dpd_retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'enforce_unique_id': 'disable', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '33', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_35', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '38', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_41', + 'ipv4_dns_server1': 'test_value_42', + 'ipv4_dns_server2': 'test_value_43', + 'ipv4_dns_server3': 'test_value_44', + 'ipv4_end_ip': 'test_value_45', + 'ipv4_name': 'test_value_46', + 'ipv4_netmask': 'test_value_47', + 'ipv4_split_exclude': 'test_value_48', + 'ipv4_split_include': 'test_value_49', + 'ipv4_start_ip': 'test_value_50', + 'ipv4_wins_server1': 'test_value_51', + 'ipv4_wins_server2': 'test_value_52', + 'ipv6_dns_server1': 'test_value_53', + 'ipv6_dns_server2': 'test_value_54', + 'ipv6_dns_server3': 'test_value_55', + 'ipv6_end_ip': 'test_value_56', + 'ipv6_name': 'test_value_57', + 'ipv6_prefix': '58', + 'ipv6_split_exclude': 'test_value_59', + 'ipv6_split_include': 'test_value_60', + 'ipv6_start_ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local_gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate_timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_78', + 'ppk_secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret_remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_86', + 'remotegw_ddns': 'test_value_87', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_92', + 'suite_b': 'disable', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'acct-verify': 'enable', + 'add-gw-route': 'enable', + 'add-route': 'disable', + 'assign-ip': 'disable', + 'assign-ip-from': 'range', + 'authmethod': 'psk', + 'authmethod-remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto-negotiate': 'enable', + 'banner': 'test_value_14', + 'cert-id-validation': 'enable', + 'childless-ike': 'enable', + 'client-auto-negotiate': 'disable', + 'client-keep-alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital-signature-auth': 'enable', + 'distance': '22', + 'dns-mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd-retrycount': '26', + 'dpd-retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap-identity': 'use-id-payload', + 'enforce-unique-id': 'disable', + 'forticlient-enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation-mtu': '33', + 'group-authentication': 'enable', + 'group-authentication-secret': 'test_value_35', + 'ha-sync-esp-seqno': 'enable', + 'idle-timeout': 'enable', + 'idle-timeoutinterval': '38', + 'ike-version': '1', + 'include-local-lan': 'disable', + 'interface': 'test_value_41', + 'ipv4-dns-server1': 'test_value_42', + 'ipv4-dns-server2': 'test_value_43', + 'ipv4-dns-server3': 'test_value_44', + 'ipv4-end-ip': 'test_value_45', + 'ipv4-name': 'test_value_46', + 'ipv4-netmask': 'test_value_47', + 'ipv4-split-exclude': 'test_value_48', + 'ipv4-split-include': 'test_value_49', + 'ipv4-start-ip': 'test_value_50', + 'ipv4-wins-server1': 'test_value_51', + 'ipv4-wins-server2': 'test_value_52', + 'ipv6-dns-server1': 'test_value_53', + 'ipv6-dns-server2': 'test_value_54', + 'ipv6-dns-server3': 'test_value_55', + 'ipv6-end-ip': 'test_value_56', + 'ipv6-name': 'test_value_57', + 'ipv6-prefix': '58', + 'ipv6-split-exclude': 'test_value_59', + 'ipv6-split-include': 'test_value_60', + 'ipv6-start-ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local-gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid-type': 'auto', + 'mesh-selector-type': 'disable', + 'mode': 'aggressive', + 'mode-cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate-timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk-identity': 'test_value_78', + 'ppk-secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret-remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote-gw': 'test_value_86', + 'remotegw-ddns': 'test_value_87', + 'rsa-signature-format': 'pkcs1', + 'save-password': 'disable', + 'send-cert-chain': 'enable', + 'signature-hash-alg': 'sha1', + 'split-include-service': 'test_value_92', + 'suite-b': 'disable', + 'type': 'static', + 'unity-support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard-type': 'custom', + 'xauthtype': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase1', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_phase1_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase1': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_negotiate': 'enable', + 'banner': 'test_value_14', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '22', + 'dns_mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd_retrycount': '26', + 'dpd_retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'enforce_unique_id': 'disable', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '33', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_35', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '38', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_41', + 'ipv4_dns_server1': 'test_value_42', + 'ipv4_dns_server2': 'test_value_43', + 'ipv4_dns_server3': 'test_value_44', + 'ipv4_end_ip': 'test_value_45', + 'ipv4_name': 'test_value_46', + 'ipv4_netmask': 'test_value_47', + 'ipv4_split_exclude': 'test_value_48', + 'ipv4_split_include': 'test_value_49', + 'ipv4_start_ip': 'test_value_50', + 'ipv4_wins_server1': 'test_value_51', + 'ipv4_wins_server2': 'test_value_52', + 'ipv6_dns_server1': 'test_value_53', + 'ipv6_dns_server2': 'test_value_54', + 'ipv6_dns_server3': 'test_value_55', + 'ipv6_end_ip': 'test_value_56', + 'ipv6_name': 'test_value_57', + 'ipv6_prefix': '58', + 'ipv6_split_exclude': 'test_value_59', + 'ipv6_split_include': 'test_value_60', + 'ipv6_start_ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local_gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate_timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_78', + 'ppk_secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret_remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_86', + 'remotegw_ddns': 'test_value_87', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_92', + 'suite_b': 'disable', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'acct-verify': 'enable', + 'add-gw-route': 'enable', + 'add-route': 'disable', + 'assign-ip': 'disable', + 'assign-ip-from': 'range', + 'authmethod': 'psk', + 'authmethod-remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto-negotiate': 'enable', + 'banner': 'test_value_14', + 'cert-id-validation': 'enable', + 'childless-ike': 'enable', + 'client-auto-negotiate': 'disable', + 'client-keep-alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital-signature-auth': 'enable', + 'distance': '22', + 'dns-mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd-retrycount': '26', + 'dpd-retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap-identity': 'use-id-payload', + 'enforce-unique-id': 'disable', + 'forticlient-enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation-mtu': '33', + 'group-authentication': 'enable', + 'group-authentication-secret': 'test_value_35', + 'ha-sync-esp-seqno': 'enable', + 'idle-timeout': 'enable', + 'idle-timeoutinterval': '38', + 'ike-version': '1', + 'include-local-lan': 'disable', + 'interface': 'test_value_41', + 'ipv4-dns-server1': 'test_value_42', + 'ipv4-dns-server2': 'test_value_43', + 'ipv4-dns-server3': 'test_value_44', + 'ipv4-end-ip': 'test_value_45', + 'ipv4-name': 'test_value_46', + 'ipv4-netmask': 'test_value_47', + 'ipv4-split-exclude': 'test_value_48', + 'ipv4-split-include': 'test_value_49', + 'ipv4-start-ip': 'test_value_50', + 'ipv4-wins-server1': 'test_value_51', + 'ipv4-wins-server2': 'test_value_52', + 'ipv6-dns-server1': 'test_value_53', + 'ipv6-dns-server2': 'test_value_54', + 'ipv6-dns-server3': 'test_value_55', + 'ipv6-end-ip': 'test_value_56', + 'ipv6-name': 'test_value_57', + 'ipv6-prefix': '58', + 'ipv6-split-exclude': 'test_value_59', + 'ipv6-split-include': 'test_value_60', + 'ipv6-start-ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local-gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid-type': 'auto', + 'mesh-selector-type': 'disable', + 'mode': 'aggressive', + 'mode-cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate-timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk-identity': 'test_value_78', + 'ppk-secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret-remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote-gw': 'test_value_86', + 'remotegw-ddns': 'test_value_87', + 'rsa-signature-format': 'pkcs1', + 'save-password': 'disable', + 'send-cert-chain': 'enable', + 'signature-hash-alg': 'sha1', + 'split-include-service': 'test_value_92', + 'suite-b': 'disable', + 'type': 'static', + 'unity-support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard-type': 'custom', + 'xauthtype': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase1', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_phase1_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_phase1': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_negotiate': 'enable', + 'banner': 'test_value_14', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '22', + 'dns_mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd_retrycount': '26', + 'dpd_retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'enforce_unique_id': 'disable', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '33', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_35', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '38', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_41', + 'ipv4_dns_server1': 'test_value_42', + 'ipv4_dns_server2': 'test_value_43', + 'ipv4_dns_server3': 'test_value_44', + 'ipv4_end_ip': 'test_value_45', + 'ipv4_name': 'test_value_46', + 'ipv4_netmask': 'test_value_47', + 'ipv4_split_exclude': 'test_value_48', + 'ipv4_split_include': 'test_value_49', + 'ipv4_start_ip': 'test_value_50', + 'ipv4_wins_server1': 'test_value_51', + 'ipv4_wins_server2': 'test_value_52', + 'ipv6_dns_server1': 'test_value_53', + 'ipv6_dns_server2': 'test_value_54', + 'ipv6_dns_server3': 'test_value_55', + 'ipv6_end_ip': 'test_value_56', + 'ipv6_name': 'test_value_57', + 'ipv6_prefix': '58', + 'ipv6_split_exclude': 'test_value_59', + 'ipv6_split_include': 'test_value_60', + 'ipv6_start_ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local_gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate_timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_78', + 'ppk_secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret_remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_86', + 'remotegw_ddns': 'test_value_87', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_92', + 'suite_b': 'disable', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'phase1', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_phase1_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_phase1': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_negotiate': 'enable', + 'banner': 'test_value_14', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '22', + 'dns_mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd_retrycount': '26', + 'dpd_retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'enforce_unique_id': 'disable', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '33', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_35', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '38', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_41', + 'ipv4_dns_server1': 'test_value_42', + 'ipv4_dns_server2': 'test_value_43', + 'ipv4_dns_server3': 'test_value_44', + 'ipv4_end_ip': 'test_value_45', + 'ipv4_name': 'test_value_46', + 'ipv4_netmask': 'test_value_47', + 'ipv4_split_exclude': 'test_value_48', + 'ipv4_split_include': 'test_value_49', + 'ipv4_start_ip': 'test_value_50', + 'ipv4_wins_server1': 'test_value_51', + 'ipv4_wins_server2': 'test_value_52', + 'ipv6_dns_server1': 'test_value_53', + 'ipv6_dns_server2': 'test_value_54', + 'ipv6_dns_server3': 'test_value_55', + 'ipv6_end_ip': 'test_value_56', + 'ipv6_name': 'test_value_57', + 'ipv6_prefix': '58', + 'ipv6_split_exclude': 'test_value_59', + 'ipv6_split_include': 'test_value_60', + 'ipv6_start_ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local_gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate_timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_78', + 'ppk_secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret_remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_86', + 'remotegw_ddns': 'test_value_87', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_92', + 'suite_b': 'disable', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'phase1', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_phase1_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase1': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_negotiate': 'enable', + 'banner': 'test_value_14', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '22', + 'dns_mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd_retrycount': '26', + 'dpd_retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'enforce_unique_id': 'disable', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '33', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_35', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '38', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_41', + 'ipv4_dns_server1': 'test_value_42', + 'ipv4_dns_server2': 'test_value_43', + 'ipv4_dns_server3': 'test_value_44', + 'ipv4_end_ip': 'test_value_45', + 'ipv4_name': 'test_value_46', + 'ipv4_netmask': 'test_value_47', + 'ipv4_split_exclude': 'test_value_48', + 'ipv4_split_include': 'test_value_49', + 'ipv4_start_ip': 'test_value_50', + 'ipv4_wins_server1': 'test_value_51', + 'ipv4_wins_server2': 'test_value_52', + 'ipv6_dns_server1': 'test_value_53', + 'ipv6_dns_server2': 'test_value_54', + 'ipv6_dns_server3': 'test_value_55', + 'ipv6_end_ip': 'test_value_56', + 'ipv6_name': 'test_value_57', + 'ipv6_prefix': '58', + 'ipv6_split_exclude': 'test_value_59', + 'ipv6_split_include': 'test_value_60', + 'ipv6_start_ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local_gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate_timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_78', + 'ppk_secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret_remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_86', + 'remotegw_ddns': 'test_value_87', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_92', + 'suite_b': 'disable', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'acct-verify': 'enable', + 'add-gw-route': 'enable', + 'add-route': 'disable', + 'assign-ip': 'disable', + 'assign-ip-from': 'range', + 'authmethod': 'psk', + 'authmethod-remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto-negotiate': 'enable', + 'banner': 'test_value_14', + 'cert-id-validation': 'enable', + 'childless-ike': 'enable', + 'client-auto-negotiate': 'disable', + 'client-keep-alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital-signature-auth': 'enable', + 'distance': '22', + 'dns-mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd-retrycount': '26', + 'dpd-retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap-identity': 'use-id-payload', + 'enforce-unique-id': 'disable', + 'forticlient-enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation-mtu': '33', + 'group-authentication': 'enable', + 'group-authentication-secret': 'test_value_35', + 'ha-sync-esp-seqno': 'enable', + 'idle-timeout': 'enable', + 'idle-timeoutinterval': '38', + 'ike-version': '1', + 'include-local-lan': 'disable', + 'interface': 'test_value_41', + 'ipv4-dns-server1': 'test_value_42', + 'ipv4-dns-server2': 'test_value_43', + 'ipv4-dns-server3': 'test_value_44', + 'ipv4-end-ip': 'test_value_45', + 'ipv4-name': 'test_value_46', + 'ipv4-netmask': 'test_value_47', + 'ipv4-split-exclude': 'test_value_48', + 'ipv4-split-include': 'test_value_49', + 'ipv4-start-ip': 'test_value_50', + 'ipv4-wins-server1': 'test_value_51', + 'ipv4-wins-server2': 'test_value_52', + 'ipv6-dns-server1': 'test_value_53', + 'ipv6-dns-server2': 'test_value_54', + 'ipv6-dns-server3': 'test_value_55', + 'ipv6-end-ip': 'test_value_56', + 'ipv6-name': 'test_value_57', + 'ipv6-prefix': '58', + 'ipv6-split-exclude': 'test_value_59', + 'ipv6-split-include': 'test_value_60', + 'ipv6-start-ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local-gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid-type': 'auto', + 'mesh-selector-type': 'disable', + 'mode': 'aggressive', + 'mode-cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate-timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk-identity': 'test_value_78', + 'ppk-secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret-remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote-gw': 'test_value_86', + 'remotegw-ddns': 'test_value_87', + 'rsa-signature-format': 'pkcs1', + 'save-password': 'disable', + 'send-cert-chain': 'enable', + 'signature-hash-alg': 'sha1', + 'split-include-service': 'test_value_92', + 'suite-b': 'disable', + 'type': 'static', + 'unity-support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard-type': 'custom', + 'xauthtype': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase1', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ipsec_phase1_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase1': { + 'random_attribute_not_valid': 'tag', + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_negotiate': 'enable', + 'banner': 'test_value_14', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '22', + 'dns_mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd_retrycount': '26', + 'dpd_retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'enforce_unique_id': 'disable', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '33', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_35', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '38', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_41', + 'ipv4_dns_server1': 'test_value_42', + 'ipv4_dns_server2': 'test_value_43', + 'ipv4_dns_server3': 'test_value_44', + 'ipv4_end_ip': 'test_value_45', + 'ipv4_name': 'test_value_46', + 'ipv4_netmask': 'test_value_47', + 'ipv4_split_exclude': 'test_value_48', + 'ipv4_split_include': 'test_value_49', + 'ipv4_start_ip': 'test_value_50', + 'ipv4_wins_server1': 'test_value_51', + 'ipv4_wins_server2': 'test_value_52', + 'ipv6_dns_server1': 'test_value_53', + 'ipv6_dns_server2': 'test_value_54', + 'ipv6_dns_server3': 'test_value_55', + 'ipv6_end_ip': 'test_value_56', + 'ipv6_name': 'test_value_57', + 'ipv6_prefix': '58', + 'ipv6_split_exclude': 'test_value_59', + 'ipv6_split_include': 'test_value_60', + 'ipv6_start_ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local_gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate_timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_78', + 'ppk_secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret_remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_86', + 'remotegw_ddns': 'test_value_87', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_92', + 'suite_b': 'disable', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'acct-verify': 'enable', + 'add-gw-route': 'enable', + 'add-route': 'disable', + 'assign-ip': 'disable', + 'assign-ip-from': 'range', + 'authmethod': 'psk', + 'authmethod-remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto-negotiate': 'enable', + 'banner': 'test_value_14', + 'cert-id-validation': 'enable', + 'childless-ike': 'enable', + 'client-auto-negotiate': 'disable', + 'client-keep-alive': 'disable', + 'comments': 'test_value_19', + 'dhgrp': '1', + 'digital-signature-auth': 'enable', + 'distance': '22', + 'dns-mode': 'manual', + 'domain': 'test_value_24', + 'dpd': 'disable', + 'dpd-retrycount': '26', + 'dpd-retryinterval': 'test_value_27', + 'eap': 'enable', + 'eap-identity': 'use-id-payload', + 'enforce-unique-id': 'disable', + 'forticlient-enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation-mtu': '33', + 'group-authentication': 'enable', + 'group-authentication-secret': 'test_value_35', + 'ha-sync-esp-seqno': 'enable', + 'idle-timeout': 'enable', + 'idle-timeoutinterval': '38', + 'ike-version': '1', + 'include-local-lan': 'disable', + 'interface': 'test_value_41', + 'ipv4-dns-server1': 'test_value_42', + 'ipv4-dns-server2': 'test_value_43', + 'ipv4-dns-server3': 'test_value_44', + 'ipv4-end-ip': 'test_value_45', + 'ipv4-name': 'test_value_46', + 'ipv4-netmask': 'test_value_47', + 'ipv4-split-exclude': 'test_value_48', + 'ipv4-split-include': 'test_value_49', + 'ipv4-start-ip': 'test_value_50', + 'ipv4-wins-server1': 'test_value_51', + 'ipv4-wins-server2': 'test_value_52', + 'ipv6-dns-server1': 'test_value_53', + 'ipv6-dns-server2': 'test_value_54', + 'ipv6-dns-server3': 'test_value_55', + 'ipv6-end-ip': 'test_value_56', + 'ipv6-name': 'test_value_57', + 'ipv6-prefix': '58', + 'ipv6-split-exclude': 'test_value_59', + 'ipv6-split-include': 'test_value_60', + 'ipv6-start-ip': 'test_value_61', + 'keepalive': '62', + 'keylife': '63', + 'local-gw': 'test_value_64', + 'localid': 'test_value_65', + 'localid-type': 'auto', + 'mesh-selector-type': 'disable', + 'mode': 'aggressive', + 'mode-cfg': 'disable', + 'name': 'default_name_70', + 'nattraversal': 'enable', + 'negotiate-timeout': '72', + 'peer': 'test_value_73', + 'peergrp': 'test_value_74', + 'peerid': 'test_value_75', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk-identity': 'test_value_78', + 'ppk-secret': 'test_value_79', + 'priority': '80', + 'proposal': 'des-md5', + 'psksecret': 'test_value_82', + 'psksecret-remote': 'test_value_83', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote-gw': 'test_value_86', + 'remotegw-ddns': 'test_value_87', + 'rsa-signature-format': 'pkcs1', + 'save-password': 'disable', + 'send-cert-chain': 'enable', + 'signature-hash-alg': 'sha1', + 'split-include-service': 'test_value_92', + 'suite-b': 'disable', + 'type': 'static', + 'unity-support': 'disable', + 'usrgrp': 'test_value_96', + 'wizard-type': 'custom', + 'xauthtype': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase1', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase1_interface.py b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase1_interface.py new file mode 100644 index 00000000000..6a422b834c2 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase1_interface.py @@ -0,0 +1,1419 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ipsec_phase1_interface +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ipsec_phase1_interface.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ipsec_phase1_interface_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase1_interface': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_discovery_forwarder': 'enable', + 'auto_discovery_psk': 'enable', + 'auto_discovery_receiver': 'enable', + 'auto_discovery_sender': 'enable', + 'auto_negotiate': 'enable', + 'banner': 'test_value_18', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_23', + 'default_gw': 'test_value_24', + 'default_gw_priority': '25', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '28', + 'dns_mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd_retrycount': '32', + 'dpd_retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'encap_local_gw4': 'test_value_36', + 'encap_local_gw6': 'test_value_37', + 'encap_remote_gw4': 'test_value_38', + 'encap_remote_gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation_address': 'ike', + 'enforce_unique_id': 'disable', + 'exchange_interface_ip': 'enable', + 'exchange_ip_addr4': 'test_value_44', + 'exchange_ip_addr6': 'test_value_45', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '48', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_50', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '53', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_56', + 'ip_version': '4', + 'ipv4_dns_server1': 'test_value_58', + 'ipv4_dns_server2': 'test_value_59', + 'ipv4_dns_server3': 'test_value_60', + 'ipv4_end_ip': 'test_value_61', + 'ipv4_name': 'test_value_62', + 'ipv4_netmask': 'test_value_63', + 'ipv4_split_exclude': 'test_value_64', + 'ipv4_split_include': 'test_value_65', + 'ipv4_start_ip': 'test_value_66', + 'ipv4_wins_server1': 'test_value_67', + 'ipv4_wins_server2': 'test_value_68', + 'ipv6_dns_server1': 'test_value_69', + 'ipv6_dns_server2': 'test_value_70', + 'ipv6_dns_server3': 'test_value_71', + 'ipv6_end_ip': 'test_value_72', + 'ipv6_name': 'test_value_73', + 'ipv6_prefix': '74', + 'ipv6_split_exclude': 'test_value_75', + 'ipv6_split_include': 'test_value_76', + 'ipv6_start_ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local_gw': 'test_value_80', + 'local_gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor_hold_down_delay': '88', + 'monitor_hold_down_time': 'test_value_89', + 'monitor_hold_down_type': 'immediate', + 'monitor_hold_down_weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate_timeout': '94', + 'net_device': 'enable', + 'passive_mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_102', + 'ppk_secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret_remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_110', + 'remote_gw6': 'test_value_111', + 'remotegw_ddns': 'test_value_112', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_117', + 'suite_b': 'disable', + 'tunnel_search': 'selectors', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'acct-verify': 'enable', + 'add-gw-route': 'enable', + 'add-route': 'disable', + 'assign-ip': 'disable', + 'assign-ip-from': 'range', + 'authmethod': 'psk', + 'authmethod-remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto-discovery-forwarder': 'enable', + 'auto-discovery-psk': 'enable', + 'auto-discovery-receiver': 'enable', + 'auto-discovery-sender': 'enable', + 'auto-negotiate': 'enable', + 'banner': 'test_value_18', + 'cert-id-validation': 'enable', + 'childless-ike': 'enable', + 'client-auto-negotiate': 'disable', + 'client-keep-alive': 'disable', + 'comments': 'test_value_23', + 'default-gw': 'test_value_24', + 'default-gw-priority': '25', + 'dhgrp': '1', + 'digital-signature-auth': 'enable', + 'distance': '28', + 'dns-mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd-retrycount': '32', + 'dpd-retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap-identity': 'use-id-payload', + 'encap-local-gw4': 'test_value_36', + 'encap-local-gw6': 'test_value_37', + 'encap-remote-gw4': 'test_value_38', + 'encap-remote-gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation-address': 'ike', + 'enforce-unique-id': 'disable', + 'exchange-interface-ip': 'enable', + 'exchange-ip-addr4': 'test_value_44', + 'exchange-ip-addr6': 'test_value_45', + 'forticlient-enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation-mtu': '48', + 'group-authentication': 'enable', + 'group-authentication-secret': 'test_value_50', + 'ha-sync-esp-seqno': 'enable', + 'idle-timeout': 'enable', + 'idle-timeoutinterval': '53', + 'ike-version': '1', + 'include-local-lan': 'disable', + 'interface': 'test_value_56', + 'ip-version': '4', + 'ipv4-dns-server1': 'test_value_58', + 'ipv4-dns-server2': 'test_value_59', + 'ipv4-dns-server3': 'test_value_60', + 'ipv4-end-ip': 'test_value_61', + 'ipv4-name': 'test_value_62', + 'ipv4-netmask': 'test_value_63', + 'ipv4-split-exclude': 'test_value_64', + 'ipv4-split-include': 'test_value_65', + 'ipv4-start-ip': 'test_value_66', + 'ipv4-wins-server1': 'test_value_67', + 'ipv4-wins-server2': 'test_value_68', + 'ipv6-dns-server1': 'test_value_69', + 'ipv6-dns-server2': 'test_value_70', + 'ipv6-dns-server3': 'test_value_71', + 'ipv6-end-ip': 'test_value_72', + 'ipv6-name': 'test_value_73', + 'ipv6-prefix': '74', + 'ipv6-split-exclude': 'test_value_75', + 'ipv6-split-include': 'test_value_76', + 'ipv6-start-ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local-gw': 'test_value_80', + 'local-gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid-type': 'auto', + 'mesh-selector-type': 'disable', + 'mode': 'aggressive', + 'mode-cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor-hold-down-delay': '88', + 'monitor-hold-down-time': 'test_value_89', + 'monitor-hold-down-type': 'immediate', + 'monitor-hold-down-weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate-timeout': '94', + 'net-device': 'enable', + 'passive-mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk-identity': 'test_value_102', + 'ppk-secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret-remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote-gw': 'test_value_110', + 'remote-gw6': 'test_value_111', + 'remotegw-ddns': 'test_value_112', + 'rsa-signature-format': 'pkcs1', + 'save-password': 'disable', + 'send-cert-chain': 'enable', + 'signature-hash-alg': 'sha1', + 'split-include-service': 'test_value_117', + 'suite-b': 'disable', + 'tunnel-search': 'selectors', + 'type': 'static', + 'unity-support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard-type': 'custom', + 'xauthtype': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase1-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_phase1_interface_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase1_interface': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_discovery_forwarder': 'enable', + 'auto_discovery_psk': 'enable', + 'auto_discovery_receiver': 'enable', + 'auto_discovery_sender': 'enable', + 'auto_negotiate': 'enable', + 'banner': 'test_value_18', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_23', + 'default_gw': 'test_value_24', + 'default_gw_priority': '25', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '28', + 'dns_mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd_retrycount': '32', + 'dpd_retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'encap_local_gw4': 'test_value_36', + 'encap_local_gw6': 'test_value_37', + 'encap_remote_gw4': 'test_value_38', + 'encap_remote_gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation_address': 'ike', + 'enforce_unique_id': 'disable', + 'exchange_interface_ip': 'enable', + 'exchange_ip_addr4': 'test_value_44', + 'exchange_ip_addr6': 'test_value_45', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '48', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_50', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '53', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_56', + 'ip_version': '4', + 'ipv4_dns_server1': 'test_value_58', + 'ipv4_dns_server2': 'test_value_59', + 'ipv4_dns_server3': 'test_value_60', + 'ipv4_end_ip': 'test_value_61', + 'ipv4_name': 'test_value_62', + 'ipv4_netmask': 'test_value_63', + 'ipv4_split_exclude': 'test_value_64', + 'ipv4_split_include': 'test_value_65', + 'ipv4_start_ip': 'test_value_66', + 'ipv4_wins_server1': 'test_value_67', + 'ipv4_wins_server2': 'test_value_68', + 'ipv6_dns_server1': 'test_value_69', + 'ipv6_dns_server2': 'test_value_70', + 'ipv6_dns_server3': 'test_value_71', + 'ipv6_end_ip': 'test_value_72', + 'ipv6_name': 'test_value_73', + 'ipv6_prefix': '74', + 'ipv6_split_exclude': 'test_value_75', + 'ipv6_split_include': 'test_value_76', + 'ipv6_start_ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local_gw': 'test_value_80', + 'local_gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor_hold_down_delay': '88', + 'monitor_hold_down_time': 'test_value_89', + 'monitor_hold_down_type': 'immediate', + 'monitor_hold_down_weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate_timeout': '94', + 'net_device': 'enable', + 'passive_mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_102', + 'ppk_secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret_remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_110', + 'remote_gw6': 'test_value_111', + 'remotegw_ddns': 'test_value_112', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_117', + 'suite_b': 'disable', + 'tunnel_search': 'selectors', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'acct-verify': 'enable', + 'add-gw-route': 'enable', + 'add-route': 'disable', + 'assign-ip': 'disable', + 'assign-ip-from': 'range', + 'authmethod': 'psk', + 'authmethod-remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto-discovery-forwarder': 'enable', + 'auto-discovery-psk': 'enable', + 'auto-discovery-receiver': 'enable', + 'auto-discovery-sender': 'enable', + 'auto-negotiate': 'enable', + 'banner': 'test_value_18', + 'cert-id-validation': 'enable', + 'childless-ike': 'enable', + 'client-auto-negotiate': 'disable', + 'client-keep-alive': 'disable', + 'comments': 'test_value_23', + 'default-gw': 'test_value_24', + 'default-gw-priority': '25', + 'dhgrp': '1', + 'digital-signature-auth': 'enable', + 'distance': '28', + 'dns-mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd-retrycount': '32', + 'dpd-retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap-identity': 'use-id-payload', + 'encap-local-gw4': 'test_value_36', + 'encap-local-gw6': 'test_value_37', + 'encap-remote-gw4': 'test_value_38', + 'encap-remote-gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation-address': 'ike', + 'enforce-unique-id': 'disable', + 'exchange-interface-ip': 'enable', + 'exchange-ip-addr4': 'test_value_44', + 'exchange-ip-addr6': 'test_value_45', + 'forticlient-enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation-mtu': '48', + 'group-authentication': 'enable', + 'group-authentication-secret': 'test_value_50', + 'ha-sync-esp-seqno': 'enable', + 'idle-timeout': 'enable', + 'idle-timeoutinterval': '53', + 'ike-version': '1', + 'include-local-lan': 'disable', + 'interface': 'test_value_56', + 'ip-version': '4', + 'ipv4-dns-server1': 'test_value_58', + 'ipv4-dns-server2': 'test_value_59', + 'ipv4-dns-server3': 'test_value_60', + 'ipv4-end-ip': 'test_value_61', + 'ipv4-name': 'test_value_62', + 'ipv4-netmask': 'test_value_63', + 'ipv4-split-exclude': 'test_value_64', + 'ipv4-split-include': 'test_value_65', + 'ipv4-start-ip': 'test_value_66', + 'ipv4-wins-server1': 'test_value_67', + 'ipv4-wins-server2': 'test_value_68', + 'ipv6-dns-server1': 'test_value_69', + 'ipv6-dns-server2': 'test_value_70', + 'ipv6-dns-server3': 'test_value_71', + 'ipv6-end-ip': 'test_value_72', + 'ipv6-name': 'test_value_73', + 'ipv6-prefix': '74', + 'ipv6-split-exclude': 'test_value_75', + 'ipv6-split-include': 'test_value_76', + 'ipv6-start-ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local-gw': 'test_value_80', + 'local-gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid-type': 'auto', + 'mesh-selector-type': 'disable', + 'mode': 'aggressive', + 'mode-cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor-hold-down-delay': '88', + 'monitor-hold-down-time': 'test_value_89', + 'monitor-hold-down-type': 'immediate', + 'monitor-hold-down-weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate-timeout': '94', + 'net-device': 'enable', + 'passive-mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk-identity': 'test_value_102', + 'ppk-secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret-remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote-gw': 'test_value_110', + 'remote-gw6': 'test_value_111', + 'remotegw-ddns': 'test_value_112', + 'rsa-signature-format': 'pkcs1', + 'save-password': 'disable', + 'send-cert-chain': 'enable', + 'signature-hash-alg': 'sha1', + 'split-include-service': 'test_value_117', + 'suite-b': 'disable', + 'tunnel-search': 'selectors', + 'type': 'static', + 'unity-support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard-type': 'custom', + 'xauthtype': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase1-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_phase1_interface_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_phase1_interface': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_discovery_forwarder': 'enable', + 'auto_discovery_psk': 'enable', + 'auto_discovery_receiver': 'enable', + 'auto_discovery_sender': 'enable', + 'auto_negotiate': 'enable', + 'banner': 'test_value_18', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_23', + 'default_gw': 'test_value_24', + 'default_gw_priority': '25', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '28', + 'dns_mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd_retrycount': '32', + 'dpd_retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'encap_local_gw4': 'test_value_36', + 'encap_local_gw6': 'test_value_37', + 'encap_remote_gw4': 'test_value_38', + 'encap_remote_gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation_address': 'ike', + 'enforce_unique_id': 'disable', + 'exchange_interface_ip': 'enable', + 'exchange_ip_addr4': 'test_value_44', + 'exchange_ip_addr6': 'test_value_45', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '48', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_50', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '53', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_56', + 'ip_version': '4', + 'ipv4_dns_server1': 'test_value_58', + 'ipv4_dns_server2': 'test_value_59', + 'ipv4_dns_server3': 'test_value_60', + 'ipv4_end_ip': 'test_value_61', + 'ipv4_name': 'test_value_62', + 'ipv4_netmask': 'test_value_63', + 'ipv4_split_exclude': 'test_value_64', + 'ipv4_split_include': 'test_value_65', + 'ipv4_start_ip': 'test_value_66', + 'ipv4_wins_server1': 'test_value_67', + 'ipv4_wins_server2': 'test_value_68', + 'ipv6_dns_server1': 'test_value_69', + 'ipv6_dns_server2': 'test_value_70', + 'ipv6_dns_server3': 'test_value_71', + 'ipv6_end_ip': 'test_value_72', + 'ipv6_name': 'test_value_73', + 'ipv6_prefix': '74', + 'ipv6_split_exclude': 'test_value_75', + 'ipv6_split_include': 'test_value_76', + 'ipv6_start_ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local_gw': 'test_value_80', + 'local_gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor_hold_down_delay': '88', + 'monitor_hold_down_time': 'test_value_89', + 'monitor_hold_down_type': 'immediate', + 'monitor_hold_down_weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate_timeout': '94', + 'net_device': 'enable', + 'passive_mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_102', + 'ppk_secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret_remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_110', + 'remote_gw6': 'test_value_111', + 'remotegw_ddns': 'test_value_112', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_117', + 'suite_b': 'disable', + 'tunnel_search': 'selectors', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1_interface.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'phase1-interface', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_phase1_interface_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_phase1_interface': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_discovery_forwarder': 'enable', + 'auto_discovery_psk': 'enable', + 'auto_discovery_receiver': 'enable', + 'auto_discovery_sender': 'enable', + 'auto_negotiate': 'enable', + 'banner': 'test_value_18', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_23', + 'default_gw': 'test_value_24', + 'default_gw_priority': '25', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '28', + 'dns_mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd_retrycount': '32', + 'dpd_retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'encap_local_gw4': 'test_value_36', + 'encap_local_gw6': 'test_value_37', + 'encap_remote_gw4': 'test_value_38', + 'encap_remote_gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation_address': 'ike', + 'enforce_unique_id': 'disable', + 'exchange_interface_ip': 'enable', + 'exchange_ip_addr4': 'test_value_44', + 'exchange_ip_addr6': 'test_value_45', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '48', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_50', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '53', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_56', + 'ip_version': '4', + 'ipv4_dns_server1': 'test_value_58', + 'ipv4_dns_server2': 'test_value_59', + 'ipv4_dns_server3': 'test_value_60', + 'ipv4_end_ip': 'test_value_61', + 'ipv4_name': 'test_value_62', + 'ipv4_netmask': 'test_value_63', + 'ipv4_split_exclude': 'test_value_64', + 'ipv4_split_include': 'test_value_65', + 'ipv4_start_ip': 'test_value_66', + 'ipv4_wins_server1': 'test_value_67', + 'ipv4_wins_server2': 'test_value_68', + 'ipv6_dns_server1': 'test_value_69', + 'ipv6_dns_server2': 'test_value_70', + 'ipv6_dns_server3': 'test_value_71', + 'ipv6_end_ip': 'test_value_72', + 'ipv6_name': 'test_value_73', + 'ipv6_prefix': '74', + 'ipv6_split_exclude': 'test_value_75', + 'ipv6_split_include': 'test_value_76', + 'ipv6_start_ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local_gw': 'test_value_80', + 'local_gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor_hold_down_delay': '88', + 'monitor_hold_down_time': 'test_value_89', + 'monitor_hold_down_type': 'immediate', + 'monitor_hold_down_weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate_timeout': '94', + 'net_device': 'enable', + 'passive_mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_102', + 'ppk_secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret_remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_110', + 'remote_gw6': 'test_value_111', + 'remotegw_ddns': 'test_value_112', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_117', + 'suite_b': 'disable', + 'tunnel_search': 'selectors', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1_interface.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'phase1-interface', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_phase1_interface_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase1_interface': { + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_discovery_forwarder': 'enable', + 'auto_discovery_psk': 'enable', + 'auto_discovery_receiver': 'enable', + 'auto_discovery_sender': 'enable', + 'auto_negotiate': 'enable', + 'banner': 'test_value_18', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_23', + 'default_gw': 'test_value_24', + 'default_gw_priority': '25', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '28', + 'dns_mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd_retrycount': '32', + 'dpd_retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'encap_local_gw4': 'test_value_36', + 'encap_local_gw6': 'test_value_37', + 'encap_remote_gw4': 'test_value_38', + 'encap_remote_gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation_address': 'ike', + 'enforce_unique_id': 'disable', + 'exchange_interface_ip': 'enable', + 'exchange_ip_addr4': 'test_value_44', + 'exchange_ip_addr6': 'test_value_45', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '48', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_50', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '53', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_56', + 'ip_version': '4', + 'ipv4_dns_server1': 'test_value_58', + 'ipv4_dns_server2': 'test_value_59', + 'ipv4_dns_server3': 'test_value_60', + 'ipv4_end_ip': 'test_value_61', + 'ipv4_name': 'test_value_62', + 'ipv4_netmask': 'test_value_63', + 'ipv4_split_exclude': 'test_value_64', + 'ipv4_split_include': 'test_value_65', + 'ipv4_start_ip': 'test_value_66', + 'ipv4_wins_server1': 'test_value_67', + 'ipv4_wins_server2': 'test_value_68', + 'ipv6_dns_server1': 'test_value_69', + 'ipv6_dns_server2': 'test_value_70', + 'ipv6_dns_server3': 'test_value_71', + 'ipv6_end_ip': 'test_value_72', + 'ipv6_name': 'test_value_73', + 'ipv6_prefix': '74', + 'ipv6_split_exclude': 'test_value_75', + 'ipv6_split_include': 'test_value_76', + 'ipv6_start_ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local_gw': 'test_value_80', + 'local_gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor_hold_down_delay': '88', + 'monitor_hold_down_time': 'test_value_89', + 'monitor_hold_down_type': 'immediate', + 'monitor_hold_down_weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate_timeout': '94', + 'net_device': 'enable', + 'passive_mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_102', + 'ppk_secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret_remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_110', + 'remote_gw6': 'test_value_111', + 'remotegw_ddns': 'test_value_112', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_117', + 'suite_b': 'disable', + 'tunnel_search': 'selectors', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'acct-verify': 'enable', + 'add-gw-route': 'enable', + 'add-route': 'disable', + 'assign-ip': 'disable', + 'assign-ip-from': 'range', + 'authmethod': 'psk', + 'authmethod-remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto-discovery-forwarder': 'enable', + 'auto-discovery-psk': 'enable', + 'auto-discovery-receiver': 'enable', + 'auto-discovery-sender': 'enable', + 'auto-negotiate': 'enable', + 'banner': 'test_value_18', + 'cert-id-validation': 'enable', + 'childless-ike': 'enable', + 'client-auto-negotiate': 'disable', + 'client-keep-alive': 'disable', + 'comments': 'test_value_23', + 'default-gw': 'test_value_24', + 'default-gw-priority': '25', + 'dhgrp': '1', + 'digital-signature-auth': 'enable', + 'distance': '28', + 'dns-mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd-retrycount': '32', + 'dpd-retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap-identity': 'use-id-payload', + 'encap-local-gw4': 'test_value_36', + 'encap-local-gw6': 'test_value_37', + 'encap-remote-gw4': 'test_value_38', + 'encap-remote-gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation-address': 'ike', + 'enforce-unique-id': 'disable', + 'exchange-interface-ip': 'enable', + 'exchange-ip-addr4': 'test_value_44', + 'exchange-ip-addr6': 'test_value_45', + 'forticlient-enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation-mtu': '48', + 'group-authentication': 'enable', + 'group-authentication-secret': 'test_value_50', + 'ha-sync-esp-seqno': 'enable', + 'idle-timeout': 'enable', + 'idle-timeoutinterval': '53', + 'ike-version': '1', + 'include-local-lan': 'disable', + 'interface': 'test_value_56', + 'ip-version': '4', + 'ipv4-dns-server1': 'test_value_58', + 'ipv4-dns-server2': 'test_value_59', + 'ipv4-dns-server3': 'test_value_60', + 'ipv4-end-ip': 'test_value_61', + 'ipv4-name': 'test_value_62', + 'ipv4-netmask': 'test_value_63', + 'ipv4-split-exclude': 'test_value_64', + 'ipv4-split-include': 'test_value_65', + 'ipv4-start-ip': 'test_value_66', + 'ipv4-wins-server1': 'test_value_67', + 'ipv4-wins-server2': 'test_value_68', + 'ipv6-dns-server1': 'test_value_69', + 'ipv6-dns-server2': 'test_value_70', + 'ipv6-dns-server3': 'test_value_71', + 'ipv6-end-ip': 'test_value_72', + 'ipv6-name': 'test_value_73', + 'ipv6-prefix': '74', + 'ipv6-split-exclude': 'test_value_75', + 'ipv6-split-include': 'test_value_76', + 'ipv6-start-ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local-gw': 'test_value_80', + 'local-gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid-type': 'auto', + 'mesh-selector-type': 'disable', + 'mode': 'aggressive', + 'mode-cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor-hold-down-delay': '88', + 'monitor-hold-down-time': 'test_value_89', + 'monitor-hold-down-type': 'immediate', + 'monitor-hold-down-weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate-timeout': '94', + 'net-device': 'enable', + 'passive-mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk-identity': 'test_value_102', + 'ppk-secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret-remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote-gw': 'test_value_110', + 'remote-gw6': 'test_value_111', + 'remotegw-ddns': 'test_value_112', + 'rsa-signature-format': 'pkcs1', + 'save-password': 'disable', + 'send-cert-chain': 'enable', + 'signature-hash-alg': 'sha1', + 'split-include-service': 'test_value_117', + 'suite-b': 'disable', + 'tunnel-search': 'selectors', + 'type': 'static', + 'unity-support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard-type': 'custom', + 'xauthtype': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase1-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ipsec_phase1_interface_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase1_interface': { + 'random_attribute_not_valid': 'tag', + 'acct_verify': 'enable', + 'add_gw_route': 'enable', + 'add_route': 'disable', + 'assign_ip': 'disable', + 'assign_ip_from': 'range', + 'authmethod': 'psk', + 'authmethod_remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto_discovery_forwarder': 'enable', + 'auto_discovery_psk': 'enable', + 'auto_discovery_receiver': 'enable', + 'auto_discovery_sender': 'enable', + 'auto_negotiate': 'enable', + 'banner': 'test_value_18', + 'cert_id_validation': 'enable', + 'childless_ike': 'enable', + 'client_auto_negotiate': 'disable', + 'client_keep_alive': 'disable', + 'comments': 'test_value_23', + 'default_gw': 'test_value_24', + 'default_gw_priority': '25', + 'dhgrp': '1', + 'digital_signature_auth': 'enable', + 'distance': '28', + 'dns_mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd_retrycount': '32', + 'dpd_retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap_identity': 'use-id-payload', + 'encap_local_gw4': 'test_value_36', + 'encap_local_gw6': 'test_value_37', + 'encap_remote_gw4': 'test_value_38', + 'encap_remote_gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation_address': 'ike', + 'enforce_unique_id': 'disable', + 'exchange_interface_ip': 'enable', + 'exchange_ip_addr4': 'test_value_44', + 'exchange_ip_addr6': 'test_value_45', + 'forticlient_enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation_mtu': '48', + 'group_authentication': 'enable', + 'group_authentication_secret': 'test_value_50', + 'ha_sync_esp_seqno': 'enable', + 'idle_timeout': 'enable', + 'idle_timeoutinterval': '53', + 'ike_version': '1', + 'include_local_lan': 'disable', + 'interface': 'test_value_56', + 'ip_version': '4', + 'ipv4_dns_server1': 'test_value_58', + 'ipv4_dns_server2': 'test_value_59', + 'ipv4_dns_server3': 'test_value_60', + 'ipv4_end_ip': 'test_value_61', + 'ipv4_name': 'test_value_62', + 'ipv4_netmask': 'test_value_63', + 'ipv4_split_exclude': 'test_value_64', + 'ipv4_split_include': 'test_value_65', + 'ipv4_start_ip': 'test_value_66', + 'ipv4_wins_server1': 'test_value_67', + 'ipv4_wins_server2': 'test_value_68', + 'ipv6_dns_server1': 'test_value_69', + 'ipv6_dns_server2': 'test_value_70', + 'ipv6_dns_server3': 'test_value_71', + 'ipv6_end_ip': 'test_value_72', + 'ipv6_name': 'test_value_73', + 'ipv6_prefix': '74', + 'ipv6_split_exclude': 'test_value_75', + 'ipv6_split_include': 'test_value_76', + 'ipv6_start_ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local_gw': 'test_value_80', + 'local_gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid_type': 'auto', + 'mesh_selector_type': 'disable', + 'mode': 'aggressive', + 'mode_cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor_hold_down_delay': '88', + 'monitor_hold_down_time': 'test_value_89', + 'monitor_hold_down_type': 'immediate', + 'monitor_hold_down_weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate_timeout': '94', + 'net_device': 'enable', + 'passive_mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk_identity': 'test_value_102', + 'ppk_secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret_remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote_gw': 'test_value_110', + 'remote_gw6': 'test_value_111', + 'remotegw_ddns': 'test_value_112', + 'rsa_signature_format': 'pkcs1', + 'save_password': 'disable', + 'send_cert_chain': 'enable', + 'signature_hash_alg': 'sha1', + 'split_include_service': 'test_value_117', + 'suite_b': 'disable', + 'tunnel_search': 'selectors', + 'type': 'static', + 'unity_support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard_type': 'custom', + 'xauthtype': 'disable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase1_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'acct-verify': 'enable', + 'add-gw-route': 'enable', + 'add-route': 'disable', + 'assign-ip': 'disable', + 'assign-ip-from': 'range', + 'authmethod': 'psk', + 'authmethod-remote': 'psk', + 'authpasswd': 'test_value_10', + 'authusr': 'test_value_11', + 'authusrgrp': 'test_value_12', + 'auto-discovery-forwarder': 'enable', + 'auto-discovery-psk': 'enable', + 'auto-discovery-receiver': 'enable', + 'auto-discovery-sender': 'enable', + 'auto-negotiate': 'enable', + 'banner': 'test_value_18', + 'cert-id-validation': 'enable', + 'childless-ike': 'enable', + 'client-auto-negotiate': 'disable', + 'client-keep-alive': 'disable', + 'comments': 'test_value_23', + 'default-gw': 'test_value_24', + 'default-gw-priority': '25', + 'dhgrp': '1', + 'digital-signature-auth': 'enable', + 'distance': '28', + 'dns-mode': 'manual', + 'domain': 'test_value_30', + 'dpd': 'disable', + 'dpd-retrycount': '32', + 'dpd-retryinterval': 'test_value_33', + 'eap': 'enable', + 'eap-identity': 'use-id-payload', + 'encap-local-gw4': 'test_value_36', + 'encap-local-gw6': 'test_value_37', + 'encap-remote-gw4': 'test_value_38', + 'encap-remote-gw6': 'test_value_39', + 'encapsulation': 'none', + 'encapsulation-address': 'ike', + 'enforce-unique-id': 'disable', + 'exchange-interface-ip': 'enable', + 'exchange-ip-addr4': 'test_value_44', + 'exchange-ip-addr6': 'test_value_45', + 'forticlient-enforcement': 'enable', + 'fragmentation': 'enable', + 'fragmentation-mtu': '48', + 'group-authentication': 'enable', + 'group-authentication-secret': 'test_value_50', + 'ha-sync-esp-seqno': 'enable', + 'idle-timeout': 'enable', + 'idle-timeoutinterval': '53', + 'ike-version': '1', + 'include-local-lan': 'disable', + 'interface': 'test_value_56', + 'ip-version': '4', + 'ipv4-dns-server1': 'test_value_58', + 'ipv4-dns-server2': 'test_value_59', + 'ipv4-dns-server3': 'test_value_60', + 'ipv4-end-ip': 'test_value_61', + 'ipv4-name': 'test_value_62', + 'ipv4-netmask': 'test_value_63', + 'ipv4-split-exclude': 'test_value_64', + 'ipv4-split-include': 'test_value_65', + 'ipv4-start-ip': 'test_value_66', + 'ipv4-wins-server1': 'test_value_67', + 'ipv4-wins-server2': 'test_value_68', + 'ipv6-dns-server1': 'test_value_69', + 'ipv6-dns-server2': 'test_value_70', + 'ipv6-dns-server3': 'test_value_71', + 'ipv6-end-ip': 'test_value_72', + 'ipv6-name': 'test_value_73', + 'ipv6-prefix': '74', + 'ipv6-split-exclude': 'test_value_75', + 'ipv6-split-include': 'test_value_76', + 'ipv6-start-ip': 'test_value_77', + 'keepalive': '78', + 'keylife': '79', + 'local-gw': 'test_value_80', + 'local-gw6': 'test_value_81', + 'localid': 'test_value_82', + 'localid-type': 'auto', + 'mesh-selector-type': 'disable', + 'mode': 'aggressive', + 'mode-cfg': 'disable', + 'monitor': 'test_value_87', + 'monitor-hold-down-delay': '88', + 'monitor-hold-down-time': 'test_value_89', + 'monitor-hold-down-type': 'immediate', + 'monitor-hold-down-weekday': 'everyday', + 'name': 'default_name_92', + 'nattraversal': 'enable', + 'negotiate-timeout': '94', + 'net-device': 'enable', + 'passive-mode': 'enable', + 'peer': 'test_value_97', + 'peergrp': 'test_value_98', + 'peerid': 'test_value_99', + 'peertype': 'any', + 'ppk': 'disable', + 'ppk-identity': 'test_value_102', + 'ppk-secret': 'test_value_103', + 'priority': '104', + 'proposal': 'des-md5', + 'psksecret': 'test_value_106', + 'psksecret-remote': 'test_value_107', + 'reauth': 'disable', + 'rekey': 'enable', + 'remote-gw': 'test_value_110', + 'remote-gw6': 'test_value_111', + 'remotegw-ddns': 'test_value_112', + 'rsa-signature-format': 'pkcs1', + 'save-password': 'disable', + 'send-cert-chain': 'enable', + 'signature-hash-alg': 'sha1', + 'split-include-service': 'test_value_117', + 'suite-b': 'disable', + 'tunnel-search': 'selectors', + 'type': 'static', + 'unity-support': 'disable', + 'usrgrp': 'test_value_122', + 'vni': '123', + 'wizard-type': 'custom', + 'xauthtype': 'disable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase1-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase2.py b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase2.py new file mode 100644 index 00000000000..69c8c362f75 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase2.py @@ -0,0 +1,599 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ipsec_phase2 +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ipsec_phase2.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ipsec_phase2_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase2': { + 'add_route': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_9', + 'dst_end_ip6': 'test_value_10', + 'dst_name': 'test_value_11', + 'dst_name6': 'test_value_12', + 'dst_port': '13', + 'dst_start_ip': 'test_value_14', + 'dst_start_ip6': 'test_value_15', + 'dst_subnet': 'test_value_16', + 'dst_subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'selector_match': 'exact', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_34', + 'src_end_ip6': 'test_value_35', + 'src_name': 'test_value_36', + 'src_name6': 'test_value_37', + 'src_port': '38', + 'src_start_ip': 'test_value_39', + 'src_start_ip6': 'test_value_40', + 'src_subnet': 'test_value_41', + 'src_subnet6': 'test_value_42', + 'use_natip': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'add-route': 'phase1', + 'auto-negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp-ipsec': 'enable', + 'dhgrp': '1', + 'dst-addr-type': 'subnet', + 'dst-end-ip': 'test_value_9', + 'dst-end-ip6': 'test_value_10', + 'dst-name': 'test_value_11', + 'dst-name6': 'test_value_12', + 'dst-port': '13', + 'dst-start-ip': 'test_value_14', + 'dst-start-ip6': 'test_value_15', + 'dst-subnet': 'test_value_16', + 'dst-subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife-type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route-overlap': 'use-old', + 'selector-match': 'exact', + 'single-source': 'enable', + 'src-addr-type': 'subnet', + 'src-end-ip': 'test_value_34', + 'src-end-ip6': 'test_value_35', + 'src-name': 'test_value_36', + 'src-name6': 'test_value_37', + 'src-port': '38', + 'src-start-ip': 'test_value_39', + 'src-start-ip6': 'test_value_40', + 'src-subnet': 'test_value_41', + 'src-subnet6': 'test_value_42', + 'use-natip': 'enable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase2', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_phase2_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase2': { + 'add_route': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_9', + 'dst_end_ip6': 'test_value_10', + 'dst_name': 'test_value_11', + 'dst_name6': 'test_value_12', + 'dst_port': '13', + 'dst_start_ip': 'test_value_14', + 'dst_start_ip6': 'test_value_15', + 'dst_subnet': 'test_value_16', + 'dst_subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'selector_match': 'exact', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_34', + 'src_end_ip6': 'test_value_35', + 'src_name': 'test_value_36', + 'src_name6': 'test_value_37', + 'src_port': '38', + 'src_start_ip': 'test_value_39', + 'src_start_ip6': 'test_value_40', + 'src_subnet': 'test_value_41', + 'src_subnet6': 'test_value_42', + 'use_natip': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'add-route': 'phase1', + 'auto-negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp-ipsec': 'enable', + 'dhgrp': '1', + 'dst-addr-type': 'subnet', + 'dst-end-ip': 'test_value_9', + 'dst-end-ip6': 'test_value_10', + 'dst-name': 'test_value_11', + 'dst-name6': 'test_value_12', + 'dst-port': '13', + 'dst-start-ip': 'test_value_14', + 'dst-start-ip6': 'test_value_15', + 'dst-subnet': 'test_value_16', + 'dst-subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife-type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route-overlap': 'use-old', + 'selector-match': 'exact', + 'single-source': 'enable', + 'src-addr-type': 'subnet', + 'src-end-ip': 'test_value_34', + 'src-end-ip6': 'test_value_35', + 'src-name': 'test_value_36', + 'src-name6': 'test_value_37', + 'src-port': '38', + 'src-start-ip': 'test_value_39', + 'src-start-ip6': 'test_value_40', + 'src-subnet': 'test_value_41', + 'src-subnet6': 'test_value_42', + 'use-natip': 'enable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase2', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_phase2_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_phase2': { + 'add_route': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_9', + 'dst_end_ip6': 'test_value_10', + 'dst_name': 'test_value_11', + 'dst_name6': 'test_value_12', + 'dst_port': '13', + 'dst_start_ip': 'test_value_14', + 'dst_start_ip6': 'test_value_15', + 'dst_subnet': 'test_value_16', + 'dst_subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'selector_match': 'exact', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_34', + 'src_end_ip6': 'test_value_35', + 'src_name': 'test_value_36', + 'src_name6': 'test_value_37', + 'src_port': '38', + 'src_start_ip': 'test_value_39', + 'src_start_ip6': 'test_value_40', + 'src_subnet': 'test_value_41', + 'src_subnet6': 'test_value_42', + 'use_natip': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'phase2', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_phase2_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_phase2': { + 'add_route': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_9', + 'dst_end_ip6': 'test_value_10', + 'dst_name': 'test_value_11', + 'dst_name6': 'test_value_12', + 'dst_port': '13', + 'dst_start_ip': 'test_value_14', + 'dst_start_ip6': 'test_value_15', + 'dst_subnet': 'test_value_16', + 'dst_subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'selector_match': 'exact', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_34', + 'src_end_ip6': 'test_value_35', + 'src_name': 'test_value_36', + 'src_name6': 'test_value_37', + 'src_port': '38', + 'src_start_ip': 'test_value_39', + 'src_start_ip6': 'test_value_40', + 'src_subnet': 'test_value_41', + 'src_subnet6': 'test_value_42', + 'use_natip': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'phase2', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_phase2_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase2': { + 'add_route': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_9', + 'dst_end_ip6': 'test_value_10', + 'dst_name': 'test_value_11', + 'dst_name6': 'test_value_12', + 'dst_port': '13', + 'dst_start_ip': 'test_value_14', + 'dst_start_ip6': 'test_value_15', + 'dst_subnet': 'test_value_16', + 'dst_subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'selector_match': 'exact', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_34', + 'src_end_ip6': 'test_value_35', + 'src_name': 'test_value_36', + 'src_name6': 'test_value_37', + 'src_port': '38', + 'src_start_ip': 'test_value_39', + 'src_start_ip6': 'test_value_40', + 'src_subnet': 'test_value_41', + 'src_subnet6': 'test_value_42', + 'use_natip': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'add-route': 'phase1', + 'auto-negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp-ipsec': 'enable', + 'dhgrp': '1', + 'dst-addr-type': 'subnet', + 'dst-end-ip': 'test_value_9', + 'dst-end-ip6': 'test_value_10', + 'dst-name': 'test_value_11', + 'dst-name6': 'test_value_12', + 'dst-port': '13', + 'dst-start-ip': 'test_value_14', + 'dst-start-ip6': 'test_value_15', + 'dst-subnet': 'test_value_16', + 'dst-subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife-type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route-overlap': 'use-old', + 'selector-match': 'exact', + 'single-source': 'enable', + 'src-addr-type': 'subnet', + 'src-end-ip': 'test_value_34', + 'src-end-ip6': 'test_value_35', + 'src-name': 'test_value_36', + 'src-name6': 'test_value_37', + 'src-port': '38', + 'src-start-ip': 'test_value_39', + 'src-start-ip6': 'test_value_40', + 'src-subnet': 'test_value_41', + 'src-subnet6': 'test_value_42', + 'use-natip': 'enable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase2', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ipsec_phase2_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase2': { + 'random_attribute_not_valid': 'tag', + 'add_route': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_9', + 'dst_end_ip6': 'test_value_10', + 'dst_name': 'test_value_11', + 'dst_name6': 'test_value_12', + 'dst_port': '13', + 'dst_start_ip': 'test_value_14', + 'dst_start_ip6': 'test_value_15', + 'dst_subnet': 'test_value_16', + 'dst_subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'selector_match': 'exact', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_34', + 'src_end_ip6': 'test_value_35', + 'src_name': 'test_value_36', + 'src_name6': 'test_value_37', + 'src_port': '38', + 'src_start_ip': 'test_value_39', + 'src_start_ip6': 'test_value_40', + 'src_subnet': 'test_value_41', + 'src_subnet6': 'test_value_42', + 'use_natip': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'add-route': 'phase1', + 'auto-negotiate': 'enable', + 'comments': 'test_value_5', + 'dhcp-ipsec': 'enable', + 'dhgrp': '1', + 'dst-addr-type': 'subnet', + 'dst-end-ip': 'test_value_9', + 'dst-end-ip6': 'test_value_10', + 'dst-name': 'test_value_11', + 'dst-name6': 'test_value_12', + 'dst-port': '13', + 'dst-start-ip': 'test_value_14', + 'dst-start-ip6': 'test_value_15', + 'dst-subnet': 'test_value_16', + 'dst-subnet6': 'test_value_17', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife-type': 'seconds', + 'keylifekbs': '21', + 'keylifeseconds': '22', + 'l2tp': 'enable', + 'name': 'default_name_24', + 'pfs': 'enable', + 'phase1name': 'test_value_26', + 'proposal': 'null-md5', + 'protocol': '28', + 'replay': 'enable', + 'route-overlap': 'use-old', + 'selector-match': 'exact', + 'single-source': 'enable', + 'src-addr-type': 'subnet', + 'src-end-ip': 'test_value_34', + 'src-end-ip6': 'test_value_35', + 'src-name': 'test_value_36', + 'src-name6': 'test_value_37', + 'src-port': '38', + 'src-start-ip': 'test_value_39', + 'src-start-ip6': 'test_value_40', + 'src-subnet': 'test_value_41', + 'src-subnet6': 'test_value_42', + 'use-natip': 'enable' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase2', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase2_interface.py b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase2_interface.py new file mode 100644 index 00000000000..a931a4dba5e --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ipsec_phase2_interface.py @@ -0,0 +1,599 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ipsec_phase2_interface +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ipsec_phase2_interface.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ipsec_phase2_interface_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase2_interface': { + 'add_route': 'phase1', + 'auto_discovery_forwarder': 'phase1', + 'auto_discovery_sender': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_11', + 'dst_end_ip6': 'test_value_12', + 'dst_name': 'test_value_13', + 'dst_name6': 'test_value_14', + 'dst_port': '15', + 'dst_start_ip': 'test_value_16', + 'dst_start_ip6': 'test_value_17', + 'dst_subnet': 'test_value_18', + 'dst_subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_35', + 'src_end_ip6': 'test_value_36', + 'src_name': 'test_value_37', + 'src_name6': 'test_value_38', + 'src_port': '39', + 'src_start_ip': 'test_value_40', + 'src_start_ip6': 'test_value_41', + 'src_subnet': 'test_value_42', + 'src_subnet6': 'test_value_43' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'add-route': 'phase1', + 'auto-discovery-forwarder': 'phase1', + 'auto-discovery-sender': 'phase1', + 'auto-negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp-ipsec': 'enable', + 'dhgrp': '1', + 'dst-addr-type': 'subnet', + 'dst-end-ip': 'test_value_11', + 'dst-end-ip6': 'test_value_12', + 'dst-name': 'test_value_13', + 'dst-name6': 'test_value_14', + 'dst-port': '15', + 'dst-start-ip': 'test_value_16', + 'dst-start-ip6': 'test_value_17', + 'dst-subnet': 'test_value_18', + 'dst-subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife-type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route-overlap': 'use-old', + 'single-source': 'enable', + 'src-addr-type': 'subnet', + 'src-end-ip': 'test_value_35', + 'src-end-ip6': 'test_value_36', + 'src-name': 'test_value_37', + 'src-name6': 'test_value_38', + 'src-port': '39', + 'src-start-ip': 'test_value_40', + 'src-start-ip6': 'test_value_41', + 'src-subnet': 'test_value_42', + 'src-subnet6': 'test_value_43' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase2-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_phase2_interface_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase2_interface': { + 'add_route': 'phase1', + 'auto_discovery_forwarder': 'phase1', + 'auto_discovery_sender': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_11', + 'dst_end_ip6': 'test_value_12', + 'dst_name': 'test_value_13', + 'dst_name6': 'test_value_14', + 'dst_port': '15', + 'dst_start_ip': 'test_value_16', + 'dst_start_ip6': 'test_value_17', + 'dst_subnet': 'test_value_18', + 'dst_subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_35', + 'src_end_ip6': 'test_value_36', + 'src_name': 'test_value_37', + 'src_name6': 'test_value_38', + 'src_port': '39', + 'src_start_ip': 'test_value_40', + 'src_start_ip6': 'test_value_41', + 'src_subnet': 'test_value_42', + 'src_subnet6': 'test_value_43' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'add-route': 'phase1', + 'auto-discovery-forwarder': 'phase1', + 'auto-discovery-sender': 'phase1', + 'auto-negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp-ipsec': 'enable', + 'dhgrp': '1', + 'dst-addr-type': 'subnet', + 'dst-end-ip': 'test_value_11', + 'dst-end-ip6': 'test_value_12', + 'dst-name': 'test_value_13', + 'dst-name6': 'test_value_14', + 'dst-port': '15', + 'dst-start-ip': 'test_value_16', + 'dst-start-ip6': 'test_value_17', + 'dst-subnet': 'test_value_18', + 'dst-subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife-type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route-overlap': 'use-old', + 'single-source': 'enable', + 'src-addr-type': 'subnet', + 'src-end-ip': 'test_value_35', + 'src-end-ip6': 'test_value_36', + 'src-name': 'test_value_37', + 'src-name6': 'test_value_38', + 'src-port': '39', + 'src-start-ip': 'test_value_40', + 'src-start-ip6': 'test_value_41', + 'src-subnet': 'test_value_42', + 'src-subnet6': 'test_value_43' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase2-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_phase2_interface_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_phase2_interface': { + 'add_route': 'phase1', + 'auto_discovery_forwarder': 'phase1', + 'auto_discovery_sender': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_11', + 'dst_end_ip6': 'test_value_12', + 'dst_name': 'test_value_13', + 'dst_name6': 'test_value_14', + 'dst_port': '15', + 'dst_start_ip': 'test_value_16', + 'dst_start_ip6': 'test_value_17', + 'dst_subnet': 'test_value_18', + 'dst_subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_35', + 'src_end_ip6': 'test_value_36', + 'src_name': 'test_value_37', + 'src_name6': 'test_value_38', + 'src_port': '39', + 'src_start_ip': 'test_value_40', + 'src_start_ip6': 'test_value_41', + 'src_subnet': 'test_value_42', + 'src_subnet6': 'test_value_43' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2_interface.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'phase2-interface', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ipsec_phase2_interface_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ipsec_phase2_interface': { + 'add_route': 'phase1', + 'auto_discovery_forwarder': 'phase1', + 'auto_discovery_sender': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_11', + 'dst_end_ip6': 'test_value_12', + 'dst_name': 'test_value_13', + 'dst_name6': 'test_value_14', + 'dst_port': '15', + 'dst_start_ip': 'test_value_16', + 'dst_start_ip6': 'test_value_17', + 'dst_subnet': 'test_value_18', + 'dst_subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_35', + 'src_end_ip6': 'test_value_36', + 'src_name': 'test_value_37', + 'src_name6': 'test_value_38', + 'src_port': '39', + 'src_start_ip': 'test_value_40', + 'src_start_ip6': 'test_value_41', + 'src_subnet': 'test_value_42', + 'src_subnet6': 'test_value_43' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2_interface.fortios_vpn_ipsec(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ipsec', 'phase2-interface', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ipsec_phase2_interface_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase2_interface': { + 'add_route': 'phase1', + 'auto_discovery_forwarder': 'phase1', + 'auto_discovery_sender': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_11', + 'dst_end_ip6': 'test_value_12', + 'dst_name': 'test_value_13', + 'dst_name6': 'test_value_14', + 'dst_port': '15', + 'dst_start_ip': 'test_value_16', + 'dst_start_ip6': 'test_value_17', + 'dst_subnet': 'test_value_18', + 'dst_subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_35', + 'src_end_ip6': 'test_value_36', + 'src_name': 'test_value_37', + 'src_name6': 'test_value_38', + 'src_port': '39', + 'src_start_ip': 'test_value_40', + 'src_start_ip6': 'test_value_41', + 'src_subnet': 'test_value_42', + 'src_subnet6': 'test_value_43' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'add-route': 'phase1', + 'auto-discovery-forwarder': 'phase1', + 'auto-discovery-sender': 'phase1', + 'auto-negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp-ipsec': 'enable', + 'dhgrp': '1', + 'dst-addr-type': 'subnet', + 'dst-end-ip': 'test_value_11', + 'dst-end-ip6': 'test_value_12', + 'dst-name': 'test_value_13', + 'dst-name6': 'test_value_14', + 'dst-port': '15', + 'dst-start-ip': 'test_value_16', + 'dst-start-ip6': 'test_value_17', + 'dst-subnet': 'test_value_18', + 'dst-subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife-type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route-overlap': 'use-old', + 'single-source': 'enable', + 'src-addr-type': 'subnet', + 'src-end-ip': 'test_value_35', + 'src-end-ip6': 'test_value_36', + 'src-name': 'test_value_37', + 'src-name6': 'test_value_38', + 'src-port': '39', + 'src-start-ip': 'test_value_40', + 'src-start-ip6': 'test_value_41', + 'src-subnet': 'test_value_42', + 'src-subnet6': 'test_value_43' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase2-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ipsec_phase2_interface_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ipsec_phase2_interface': { + 'random_attribute_not_valid': 'tag', + 'add_route': 'phase1', + 'auto_discovery_forwarder': 'phase1', + 'auto_discovery_sender': 'phase1', + 'auto_negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp_ipsec': 'enable', + 'dhgrp': '1', + 'dst_addr_type': 'subnet', + 'dst_end_ip': 'test_value_11', + 'dst_end_ip6': 'test_value_12', + 'dst_name': 'test_value_13', + 'dst_name6': 'test_value_14', + 'dst_port': '15', + 'dst_start_ip': 'test_value_16', + 'dst_start_ip6': 'test_value_17', + 'dst_subnet': 'test_value_18', + 'dst_subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife_type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route_overlap': 'use-old', + 'single_source': 'enable', + 'src_addr_type': 'subnet', + 'src_end_ip': 'test_value_35', + 'src_end_ip6': 'test_value_36', + 'src_name': 'test_value_37', + 'src_name6': 'test_value_38', + 'src_port': '39', + 'src_start_ip': 'test_value_40', + 'src_start_ip6': 'test_value_41', + 'src_subnet': 'test_value_42', + 'src_subnet6': 'test_value_43' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ipsec_phase2_interface.fortios_vpn_ipsec(input_data, fos_instance) + + expected_data = { + 'add-route': 'phase1', + 'auto-discovery-forwarder': 'phase1', + 'auto-discovery-sender': 'phase1', + 'auto-negotiate': 'enable', + 'comments': 'test_value_7', + 'dhcp-ipsec': 'enable', + 'dhgrp': '1', + 'dst-addr-type': 'subnet', + 'dst-end-ip': 'test_value_11', + 'dst-end-ip6': 'test_value_12', + 'dst-name': 'test_value_13', + 'dst-name6': 'test_value_14', + 'dst-port': '15', + 'dst-start-ip': 'test_value_16', + 'dst-start-ip6': 'test_value_17', + 'dst-subnet': 'test_value_18', + 'dst-subnet6': 'test_value_19', + 'encapsulation': 'tunnel-mode', + 'keepalive': 'enable', + 'keylife-type': 'seconds', + 'keylifekbs': '23', + 'keylifeseconds': '24', + 'l2tp': 'enable', + 'name': 'default_name_26', + 'pfs': 'enable', + 'phase1name': 'test_value_28', + 'proposal': 'null-md5', + 'protocol': '30', + 'replay': 'enable', + 'route-overlap': 'use-old', + 'single-source': 'enable', + 'src-addr-type': 'subnet', + 'src-end-ip': 'test_value_35', + 'src-end-ip6': 'test_value_36', + 'src-name': 'test_value_37', + 'src-name6': 'test_value_38', + 'src-port': '39', + 'src-start-ip': 'test_value_40', + 'src-start-ip6': 'test_value_41', + 'src-subnet': 'test_value_42', + 'src-subnet6': 'test_value_43' + } + + set_method_mock.assert_called_with('vpn.ipsec', 'phase2-interface', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ssl_settings.py b/test/units/modules/network/fortios/test_fortios_vpn_ssl_settings.py new file mode 100644 index 00000000000..3eeca8e57a2 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ssl_settings.py @@ -0,0 +1,495 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ssl_settings +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ssl_settings.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ssl_settings_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ssl_settings': { + 'auth_timeout': '3', + 'auto_tunnel_static_route': 'enable', + 'banned_cipher': 'RSA', + 'check_referer': 'enable', + 'default_portal': 'test_value_7', + 'deflate_compression_level': '8', + 'deflate_min_data_size': '9', + 'dns_server1': 'test_value_10', + 'dns_server2': 'test_value_11', + 'dns_suffix': 'test_value_12', + 'dtls_hello_timeout': '13', + 'dtls_tunnel': 'enable', + 'force_two_factor_auth': 'enable', + 'header_x_forwarded_for': 'pass', + 'http_compression': 'enable', + 'http_only_cookie': 'enable', + 'http_request_body_timeout': '19', + 'http_request_header_timeout': '20', + 'https_redirect': 'enable', + 'idle_timeout': '22', + 'ipv6_dns_server1': 'test_value_23', + 'ipv6_dns_server2': 'test_value_24', + 'ipv6_wins_server1': 'test_value_25', + 'ipv6_wins_server2': 'test_value_26', + 'login_attempt_limit': '27', + 'login_block_time': '28', + 'login_timeout': '29', + 'port': '30', + 'port_precedence': 'enable', + 'reqclientcert': 'enable', + 'route_source_interface': 'enable', + 'servercert': 'test_value_34', + 'source_address_negate': 'enable', + 'source_address6_negate': 'enable', + 'ssl_client_renegotiation': 'disable', + 'ssl_insert_empty_fragment': 'enable', + 'tlsv1_0': 'enable', + 'tlsv1_1': 'enable', + 'tlsv1_2': 'enable', + 'unsafe_legacy_renegotiation': 'enable', + 'url_obscuration': 'enable', + 'wins_server1': 'test_value_44', + 'wins_server2': 'test_value_45', + 'x_content_type_options': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_settings.fortios_vpn_ssl(input_data, fos_instance) + + expected_data = { + 'auth-timeout': '3', + 'auto-tunnel-static-route': 'enable', + 'banned-cipher': 'RSA', + 'check-referer': 'enable', + 'default-portal': 'test_value_7', + 'deflate-compression-level': '8', + 'deflate-min-data-size': '9', + 'dns-server1': 'test_value_10', + 'dns-server2': 'test_value_11', + 'dns-suffix': 'test_value_12', + 'dtls-hello-timeout': '13', + 'dtls-tunnel': 'enable', + 'force-two-factor-auth': 'enable', + 'header-x-forwarded-for': 'pass', + 'http-compression': 'enable', + 'http-only-cookie': 'enable', + 'http-request-body-timeout': '19', + 'http-request-header-timeout': '20', + 'https-redirect': 'enable', + 'idle-timeout': '22', + 'ipv6-dns-server1': 'test_value_23', + 'ipv6-dns-server2': 'test_value_24', + 'ipv6-wins-server1': 'test_value_25', + 'ipv6-wins-server2': 'test_value_26', + 'login-attempt-limit': '27', + 'login-block-time': '28', + 'login-timeout': '29', + 'port': '30', + 'port-precedence': 'enable', + 'reqclientcert': 'enable', + 'route-source-interface': 'enable', + 'servercert': 'test_value_34', + 'source-address-negate': 'enable', + 'source-address6-negate': 'enable', + 'ssl-client-renegotiation': 'disable', + 'ssl-insert-empty-fragment': 'enable', + 'tlsv1-0': 'enable', + 'tlsv1-1': 'enable', + 'tlsv1-2': 'enable', + 'unsafe-legacy-renegotiation': 'enable', + 'url-obscuration': 'enable', + 'wins-server1': 'test_value_44', + 'wins-server2': 'test_value_45', + 'x-content-type-options': 'enable' + } + + set_method_mock.assert_called_with('vpn.ssl', 'settings', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ssl_settings_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ssl_settings': { + 'auth_timeout': '3', + 'auto_tunnel_static_route': 'enable', + 'banned_cipher': 'RSA', + 'check_referer': 'enable', + 'default_portal': 'test_value_7', + 'deflate_compression_level': '8', + 'deflate_min_data_size': '9', + 'dns_server1': 'test_value_10', + 'dns_server2': 'test_value_11', + 'dns_suffix': 'test_value_12', + 'dtls_hello_timeout': '13', + 'dtls_tunnel': 'enable', + 'force_two_factor_auth': 'enable', + 'header_x_forwarded_for': 'pass', + 'http_compression': 'enable', + 'http_only_cookie': 'enable', + 'http_request_body_timeout': '19', + 'http_request_header_timeout': '20', + 'https_redirect': 'enable', + 'idle_timeout': '22', + 'ipv6_dns_server1': 'test_value_23', + 'ipv6_dns_server2': 'test_value_24', + 'ipv6_wins_server1': 'test_value_25', + 'ipv6_wins_server2': 'test_value_26', + 'login_attempt_limit': '27', + 'login_block_time': '28', + 'login_timeout': '29', + 'port': '30', + 'port_precedence': 'enable', + 'reqclientcert': 'enable', + 'route_source_interface': 'enable', + 'servercert': 'test_value_34', + 'source_address_negate': 'enable', + 'source_address6_negate': 'enable', + 'ssl_client_renegotiation': 'disable', + 'ssl_insert_empty_fragment': 'enable', + 'tlsv1_0': 'enable', + 'tlsv1_1': 'enable', + 'tlsv1_2': 'enable', + 'unsafe_legacy_renegotiation': 'enable', + 'url_obscuration': 'enable', + 'wins_server1': 'test_value_44', + 'wins_server2': 'test_value_45', + 'x_content_type_options': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_settings.fortios_vpn_ssl(input_data, fos_instance) + + expected_data = { + 'auth-timeout': '3', + 'auto-tunnel-static-route': 'enable', + 'banned-cipher': 'RSA', + 'check-referer': 'enable', + 'default-portal': 'test_value_7', + 'deflate-compression-level': '8', + 'deflate-min-data-size': '9', + 'dns-server1': 'test_value_10', + 'dns-server2': 'test_value_11', + 'dns-suffix': 'test_value_12', + 'dtls-hello-timeout': '13', + 'dtls-tunnel': 'enable', + 'force-two-factor-auth': 'enable', + 'header-x-forwarded-for': 'pass', + 'http-compression': 'enable', + 'http-only-cookie': 'enable', + 'http-request-body-timeout': '19', + 'http-request-header-timeout': '20', + 'https-redirect': 'enable', + 'idle-timeout': '22', + 'ipv6-dns-server1': 'test_value_23', + 'ipv6-dns-server2': 'test_value_24', + 'ipv6-wins-server1': 'test_value_25', + 'ipv6-wins-server2': 'test_value_26', + 'login-attempt-limit': '27', + 'login-block-time': '28', + 'login-timeout': '29', + 'port': '30', + 'port-precedence': 'enable', + 'reqclientcert': 'enable', + 'route-source-interface': 'enable', + 'servercert': 'test_value_34', + 'source-address-negate': 'enable', + 'source-address6-negate': 'enable', + 'ssl-client-renegotiation': 'disable', + 'ssl-insert-empty-fragment': 'enable', + 'tlsv1-0': 'enable', + 'tlsv1-1': 'enable', + 'tlsv1-2': 'enable', + 'unsafe-legacy-renegotiation': 'enable', + 'url-obscuration': 'enable', + 'wins-server1': 'test_value_44', + 'wins-server2': 'test_value_45', + 'x-content-type-options': 'enable' + } + + set_method_mock.assert_called_with('vpn.ssl', 'settings', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ssl_settings_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ssl_settings': { + 'auth_timeout': '3', + 'auto_tunnel_static_route': 'enable', + 'banned_cipher': 'RSA', + 'check_referer': 'enable', + 'default_portal': 'test_value_7', + 'deflate_compression_level': '8', + 'deflate_min_data_size': '9', + 'dns_server1': 'test_value_10', + 'dns_server2': 'test_value_11', + 'dns_suffix': 'test_value_12', + 'dtls_hello_timeout': '13', + 'dtls_tunnel': 'enable', + 'force_two_factor_auth': 'enable', + 'header_x_forwarded_for': 'pass', + 'http_compression': 'enable', + 'http_only_cookie': 'enable', + 'http_request_body_timeout': '19', + 'http_request_header_timeout': '20', + 'https_redirect': 'enable', + 'idle_timeout': '22', + 'ipv6_dns_server1': 'test_value_23', + 'ipv6_dns_server2': 'test_value_24', + 'ipv6_wins_server1': 'test_value_25', + 'ipv6_wins_server2': 'test_value_26', + 'login_attempt_limit': '27', + 'login_block_time': '28', + 'login_timeout': '29', + 'port': '30', + 'port_precedence': 'enable', + 'reqclientcert': 'enable', + 'route_source_interface': 'enable', + 'servercert': 'test_value_34', + 'source_address_negate': 'enable', + 'source_address6_negate': 'enable', + 'ssl_client_renegotiation': 'disable', + 'ssl_insert_empty_fragment': 'enable', + 'tlsv1_0': 'enable', + 'tlsv1_1': 'enable', + 'tlsv1_2': 'enable', + 'unsafe_legacy_renegotiation': 'enable', + 'url_obscuration': 'enable', + 'wins_server1': 'test_value_44', + 'wins_server2': 'test_value_45', + 'x_content_type_options': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_settings.fortios_vpn_ssl(input_data, fos_instance) + + expected_data = { + 'auth-timeout': '3', + 'auto-tunnel-static-route': 'enable', + 'banned-cipher': 'RSA', + 'check-referer': 'enable', + 'default-portal': 'test_value_7', + 'deflate-compression-level': '8', + 'deflate-min-data-size': '9', + 'dns-server1': 'test_value_10', + 'dns-server2': 'test_value_11', + 'dns-suffix': 'test_value_12', + 'dtls-hello-timeout': '13', + 'dtls-tunnel': 'enable', + 'force-two-factor-auth': 'enable', + 'header-x-forwarded-for': 'pass', + 'http-compression': 'enable', + 'http-only-cookie': 'enable', + 'http-request-body-timeout': '19', + 'http-request-header-timeout': '20', + 'https-redirect': 'enable', + 'idle-timeout': '22', + 'ipv6-dns-server1': 'test_value_23', + 'ipv6-dns-server2': 'test_value_24', + 'ipv6-wins-server1': 'test_value_25', + 'ipv6-wins-server2': 'test_value_26', + 'login-attempt-limit': '27', + 'login-block-time': '28', + 'login-timeout': '29', + 'port': '30', + 'port-precedence': 'enable', + 'reqclientcert': 'enable', + 'route-source-interface': 'enable', + 'servercert': 'test_value_34', + 'source-address-negate': 'enable', + 'source-address6-negate': 'enable', + 'ssl-client-renegotiation': 'disable', + 'ssl-insert-empty-fragment': 'enable', + 'tlsv1-0': 'enable', + 'tlsv1-1': 'enable', + 'tlsv1-2': 'enable', + 'unsafe-legacy-renegotiation': 'enable', + 'url-obscuration': 'enable', + 'wins-server1': 'test_value_44', + 'wins-server2': 'test_value_45', + 'x-content-type-options': 'enable' + } + + set_method_mock.assert_called_with('vpn.ssl', 'settings', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ssl_settings_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ssl_settings': { + 'random_attribute_not_valid': 'tag', + 'auth_timeout': '3', + 'auto_tunnel_static_route': 'enable', + 'banned_cipher': 'RSA', + 'check_referer': 'enable', + 'default_portal': 'test_value_7', + 'deflate_compression_level': '8', + 'deflate_min_data_size': '9', + 'dns_server1': 'test_value_10', + 'dns_server2': 'test_value_11', + 'dns_suffix': 'test_value_12', + 'dtls_hello_timeout': '13', + 'dtls_tunnel': 'enable', + 'force_two_factor_auth': 'enable', + 'header_x_forwarded_for': 'pass', + 'http_compression': 'enable', + 'http_only_cookie': 'enable', + 'http_request_body_timeout': '19', + 'http_request_header_timeout': '20', + 'https_redirect': 'enable', + 'idle_timeout': '22', + 'ipv6_dns_server1': 'test_value_23', + 'ipv6_dns_server2': 'test_value_24', + 'ipv6_wins_server1': 'test_value_25', + 'ipv6_wins_server2': 'test_value_26', + 'login_attempt_limit': '27', + 'login_block_time': '28', + 'login_timeout': '29', + 'port': '30', + 'port_precedence': 'enable', + 'reqclientcert': 'enable', + 'route_source_interface': 'enable', + 'servercert': 'test_value_34', + 'source_address_negate': 'enable', + 'source_address6_negate': 'enable', + 'ssl_client_renegotiation': 'disable', + 'ssl_insert_empty_fragment': 'enable', + 'tlsv1_0': 'enable', + 'tlsv1_1': 'enable', + 'tlsv1_2': 'enable', + 'unsafe_legacy_renegotiation': 'enable', + 'url_obscuration': 'enable', + 'wins_server1': 'test_value_44', + 'wins_server2': 'test_value_45', + 'x_content_type_options': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_settings.fortios_vpn_ssl(input_data, fos_instance) + + expected_data = { + 'auth-timeout': '3', + 'auto-tunnel-static-route': 'enable', + 'banned-cipher': 'RSA', + 'check-referer': 'enable', + 'default-portal': 'test_value_7', + 'deflate-compression-level': '8', + 'deflate-min-data-size': '9', + 'dns-server1': 'test_value_10', + 'dns-server2': 'test_value_11', + 'dns-suffix': 'test_value_12', + 'dtls-hello-timeout': '13', + 'dtls-tunnel': 'enable', + 'force-two-factor-auth': 'enable', + 'header-x-forwarded-for': 'pass', + 'http-compression': 'enable', + 'http-only-cookie': 'enable', + 'http-request-body-timeout': '19', + 'http-request-header-timeout': '20', + 'https-redirect': 'enable', + 'idle-timeout': '22', + 'ipv6-dns-server1': 'test_value_23', + 'ipv6-dns-server2': 'test_value_24', + 'ipv6-wins-server1': 'test_value_25', + 'ipv6-wins-server2': 'test_value_26', + 'login-attempt-limit': '27', + 'login-block-time': '28', + 'login-timeout': '29', + 'port': '30', + 'port-precedence': 'enable', + 'reqclientcert': 'enable', + 'route-source-interface': 'enable', + 'servercert': 'test_value_34', + 'source-address-negate': 'enable', + 'source-address6-negate': 'enable', + 'ssl-client-renegotiation': 'disable', + 'ssl-insert-empty-fragment': 'enable', + 'tlsv1-0': 'enable', + 'tlsv1-1': 'enable', + 'tlsv1-2': 'enable', + 'unsafe-legacy-renegotiation': 'enable', + 'url-obscuration': 'enable', + 'wins-server1': 'test_value_44', + 'wins-server2': 'test_value_45', + 'x-content-type-options': 'enable' + } + + set_method_mock.assert_called_with('vpn.ssl', 'settings', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_vpn_ssl_web_portal.py b/test/units/modules/network/fortios/test_fortios_vpn_ssl_web_portal.py new file mode 100644 index 00000000000..46031edfd03 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_vpn_ssl_web_portal.py @@ -0,0 +1,689 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_vpn_ssl_web_portal +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_vpn_ssl_web_portal.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_vpn_ssl_web_portal_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ssl_web_portal': { + 'allow_user_access': 'web', + 'auto_connect': 'enable', + 'custom_lang': 'test_value_5', + 'customize_forticlient_download_url': 'enable', + 'display_bookmark': 'enable', + 'display_connection_tools': 'enable', + 'display_history': 'enable', + 'display_status': 'enable', + 'dns_server1': 'test_value_11', + 'dns_server2': 'test_value_12', + 'dns_suffix': 'test_value_13', + 'exclusive_routing': 'enable', + 'forticlient_download': 'enable', + 'forticlient_download_method': 'direct', + 'heading': 'test_value_17', + 'hide_sso_credential': 'enable', + 'host_check': 'none', + 'host_check_interval': '20', + 'ip_mode': 'range', + 'ipv6_dns_server1': 'test_value_22', + 'ipv6_dns_server2': 'test_value_23', + 'ipv6_exclusive_routing': 'enable', + 'ipv6_service_restriction': 'enable', + 'ipv6_split_tunneling': 'enable', + 'ipv6_tunnel_mode': 'enable', + 'ipv6_wins_server1': 'test_value_28', + 'ipv6_wins_server2': 'test_value_29', + 'keep_alive': 'enable', + 'limit_user_logins': 'enable', + 'mac_addr_action': 'allow', + 'mac_addr_check': 'enable', + 'macos_forticlient_download_url': 'test_value_34', + 'name': 'default_name_35', + 'os_check': 'enable', + 'redir_url': 'test_value_37', + 'save_password': 'enable', + 'service_restriction': 'enable', + 'skip_check_for_unsupported_browser': 'enable', + 'skip_check_for_unsupported_os': 'enable', + 'smb_ntlmv1_auth': 'enable', + 'smbv1': 'enable', + 'split_tunneling': 'enable', + 'theme': 'blue', + 'tunnel_mode': 'enable', + 'user_bookmark': 'enable', + 'user_group_bookmark': 'enable', + 'web_mode': 'enable', + 'windows_forticlient_download_url': 'test_value_50', + 'wins_server1': 'test_value_51', + 'wins_server2': 'test_value_52' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_web_portal.fortios_vpn_ssl_web(input_data, fos_instance) + + expected_data = { + 'allow-user-access': 'web', + 'auto-connect': 'enable', + 'custom-lang': 'test_value_5', + 'customize-forticlient-download-url': 'enable', + 'display-bookmark': 'enable', + 'display-connection-tools': 'enable', + 'display-history': 'enable', + 'display-status': 'enable', + 'dns-server1': 'test_value_11', + 'dns-server2': 'test_value_12', + 'dns-suffix': 'test_value_13', + 'exclusive-routing': 'enable', + 'forticlient-download': 'enable', + 'forticlient-download-method': 'direct', + 'heading': 'test_value_17', + 'hide-sso-credential': 'enable', + 'host-check': 'none', + 'host-check-interval': '20', + 'ip-mode': 'range', + 'ipv6-dns-server1': 'test_value_22', + 'ipv6-dns-server2': 'test_value_23', + 'ipv6-exclusive-routing': 'enable', + 'ipv6-service-restriction': 'enable', + 'ipv6-split-tunneling': 'enable', + 'ipv6-tunnel-mode': 'enable', + 'ipv6-wins-server1': 'test_value_28', + 'ipv6-wins-server2': 'test_value_29', + 'keep-alive': 'enable', + 'limit-user-logins': 'enable', + 'mac-addr-action': 'allow', + 'mac-addr-check': 'enable', + 'macos-forticlient-download-url': 'test_value_34', + 'name': 'default_name_35', + 'os-check': 'enable', + 'redir-url': 'test_value_37', + 'save-password': 'enable', + 'service-restriction': 'enable', + 'skip-check-for-unsupported-browser': 'enable', + 'skip-check-for-unsupported-os': 'enable', + 'smb-ntlmv1-auth': 'enable', + 'smbv1': 'enable', + 'split-tunneling': 'enable', + 'theme': 'blue', + 'tunnel-mode': 'enable', + 'user-bookmark': 'enable', + 'user-group-bookmark': 'enable', + 'web-mode': 'enable', + 'windows-forticlient-download-url': 'test_value_50', + 'wins-server1': 'test_value_51', + 'wins-server2': 'test_value_52' + } + + set_method_mock.assert_called_with('vpn.ssl.web', 'portal', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ssl_web_portal_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ssl_web_portal': { + 'allow_user_access': 'web', + 'auto_connect': 'enable', + 'custom_lang': 'test_value_5', + 'customize_forticlient_download_url': 'enable', + 'display_bookmark': 'enable', + 'display_connection_tools': 'enable', + 'display_history': 'enable', + 'display_status': 'enable', + 'dns_server1': 'test_value_11', + 'dns_server2': 'test_value_12', + 'dns_suffix': 'test_value_13', + 'exclusive_routing': 'enable', + 'forticlient_download': 'enable', + 'forticlient_download_method': 'direct', + 'heading': 'test_value_17', + 'hide_sso_credential': 'enable', + 'host_check': 'none', + 'host_check_interval': '20', + 'ip_mode': 'range', + 'ipv6_dns_server1': 'test_value_22', + 'ipv6_dns_server2': 'test_value_23', + 'ipv6_exclusive_routing': 'enable', + 'ipv6_service_restriction': 'enable', + 'ipv6_split_tunneling': 'enable', + 'ipv6_tunnel_mode': 'enable', + 'ipv6_wins_server1': 'test_value_28', + 'ipv6_wins_server2': 'test_value_29', + 'keep_alive': 'enable', + 'limit_user_logins': 'enable', + 'mac_addr_action': 'allow', + 'mac_addr_check': 'enable', + 'macos_forticlient_download_url': 'test_value_34', + 'name': 'default_name_35', + 'os_check': 'enable', + 'redir_url': 'test_value_37', + 'save_password': 'enable', + 'service_restriction': 'enable', + 'skip_check_for_unsupported_browser': 'enable', + 'skip_check_for_unsupported_os': 'enable', + 'smb_ntlmv1_auth': 'enable', + 'smbv1': 'enable', + 'split_tunneling': 'enable', + 'theme': 'blue', + 'tunnel_mode': 'enable', + 'user_bookmark': 'enable', + 'user_group_bookmark': 'enable', + 'web_mode': 'enable', + 'windows_forticlient_download_url': 'test_value_50', + 'wins_server1': 'test_value_51', + 'wins_server2': 'test_value_52' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_web_portal.fortios_vpn_ssl_web(input_data, fos_instance) + + expected_data = { + 'allow-user-access': 'web', + 'auto-connect': 'enable', + 'custom-lang': 'test_value_5', + 'customize-forticlient-download-url': 'enable', + 'display-bookmark': 'enable', + 'display-connection-tools': 'enable', + 'display-history': 'enable', + 'display-status': 'enable', + 'dns-server1': 'test_value_11', + 'dns-server2': 'test_value_12', + 'dns-suffix': 'test_value_13', + 'exclusive-routing': 'enable', + 'forticlient-download': 'enable', + 'forticlient-download-method': 'direct', + 'heading': 'test_value_17', + 'hide-sso-credential': 'enable', + 'host-check': 'none', + 'host-check-interval': '20', + 'ip-mode': 'range', + 'ipv6-dns-server1': 'test_value_22', + 'ipv6-dns-server2': 'test_value_23', + 'ipv6-exclusive-routing': 'enable', + 'ipv6-service-restriction': 'enable', + 'ipv6-split-tunneling': 'enable', + 'ipv6-tunnel-mode': 'enable', + 'ipv6-wins-server1': 'test_value_28', + 'ipv6-wins-server2': 'test_value_29', + 'keep-alive': 'enable', + 'limit-user-logins': 'enable', + 'mac-addr-action': 'allow', + 'mac-addr-check': 'enable', + 'macos-forticlient-download-url': 'test_value_34', + 'name': 'default_name_35', + 'os-check': 'enable', + 'redir-url': 'test_value_37', + 'save-password': 'enable', + 'service-restriction': 'enable', + 'skip-check-for-unsupported-browser': 'enable', + 'skip-check-for-unsupported-os': 'enable', + 'smb-ntlmv1-auth': 'enable', + 'smbv1': 'enable', + 'split-tunneling': 'enable', + 'theme': 'blue', + 'tunnel-mode': 'enable', + 'user-bookmark': 'enable', + 'user-group-bookmark': 'enable', + 'web-mode': 'enable', + 'windows-forticlient-download-url': 'test_value_50', + 'wins-server1': 'test_value_51', + 'wins-server2': 'test_value_52' + } + + set_method_mock.assert_called_with('vpn.ssl.web', 'portal', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ssl_web_portal_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ssl_web_portal': { + 'allow_user_access': 'web', + 'auto_connect': 'enable', + 'custom_lang': 'test_value_5', + 'customize_forticlient_download_url': 'enable', + 'display_bookmark': 'enable', + 'display_connection_tools': 'enable', + 'display_history': 'enable', + 'display_status': 'enable', + 'dns_server1': 'test_value_11', + 'dns_server2': 'test_value_12', + 'dns_suffix': 'test_value_13', + 'exclusive_routing': 'enable', + 'forticlient_download': 'enable', + 'forticlient_download_method': 'direct', + 'heading': 'test_value_17', + 'hide_sso_credential': 'enable', + 'host_check': 'none', + 'host_check_interval': '20', + 'ip_mode': 'range', + 'ipv6_dns_server1': 'test_value_22', + 'ipv6_dns_server2': 'test_value_23', + 'ipv6_exclusive_routing': 'enable', + 'ipv6_service_restriction': 'enable', + 'ipv6_split_tunneling': 'enable', + 'ipv6_tunnel_mode': 'enable', + 'ipv6_wins_server1': 'test_value_28', + 'ipv6_wins_server2': 'test_value_29', + 'keep_alive': 'enable', + 'limit_user_logins': 'enable', + 'mac_addr_action': 'allow', + 'mac_addr_check': 'enable', + 'macos_forticlient_download_url': 'test_value_34', + 'name': 'default_name_35', + 'os_check': 'enable', + 'redir_url': 'test_value_37', + 'save_password': 'enable', + 'service_restriction': 'enable', + 'skip_check_for_unsupported_browser': 'enable', + 'skip_check_for_unsupported_os': 'enable', + 'smb_ntlmv1_auth': 'enable', + 'smbv1': 'enable', + 'split_tunneling': 'enable', + 'theme': 'blue', + 'tunnel_mode': 'enable', + 'user_bookmark': 'enable', + 'user_group_bookmark': 'enable', + 'web_mode': 'enable', + 'windows_forticlient_download_url': 'test_value_50', + 'wins_server1': 'test_value_51', + 'wins_server2': 'test_value_52' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_web_portal.fortios_vpn_ssl_web(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ssl.web', 'portal', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_vpn_ssl_web_portal_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'vpn_ssl_web_portal': { + 'allow_user_access': 'web', + 'auto_connect': 'enable', + 'custom_lang': 'test_value_5', + 'customize_forticlient_download_url': 'enable', + 'display_bookmark': 'enable', + 'display_connection_tools': 'enable', + 'display_history': 'enable', + 'display_status': 'enable', + 'dns_server1': 'test_value_11', + 'dns_server2': 'test_value_12', + 'dns_suffix': 'test_value_13', + 'exclusive_routing': 'enable', + 'forticlient_download': 'enable', + 'forticlient_download_method': 'direct', + 'heading': 'test_value_17', + 'hide_sso_credential': 'enable', + 'host_check': 'none', + 'host_check_interval': '20', + 'ip_mode': 'range', + 'ipv6_dns_server1': 'test_value_22', + 'ipv6_dns_server2': 'test_value_23', + 'ipv6_exclusive_routing': 'enable', + 'ipv6_service_restriction': 'enable', + 'ipv6_split_tunneling': 'enable', + 'ipv6_tunnel_mode': 'enable', + 'ipv6_wins_server1': 'test_value_28', + 'ipv6_wins_server2': 'test_value_29', + 'keep_alive': 'enable', + 'limit_user_logins': 'enable', + 'mac_addr_action': 'allow', + 'mac_addr_check': 'enable', + 'macos_forticlient_download_url': 'test_value_34', + 'name': 'default_name_35', + 'os_check': 'enable', + 'redir_url': 'test_value_37', + 'save_password': 'enable', + 'service_restriction': 'enable', + 'skip_check_for_unsupported_browser': 'enable', + 'skip_check_for_unsupported_os': 'enable', + 'smb_ntlmv1_auth': 'enable', + 'smbv1': 'enable', + 'split_tunneling': 'enable', + 'theme': 'blue', + 'tunnel_mode': 'enable', + 'user_bookmark': 'enable', + 'user_group_bookmark': 'enable', + 'web_mode': 'enable', + 'windows_forticlient_download_url': 'test_value_50', + 'wins_server1': 'test_value_51', + 'wins_server2': 'test_value_52' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_web_portal.fortios_vpn_ssl_web(input_data, fos_instance) + + delete_method_mock.assert_called_with('vpn.ssl.web', 'portal', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_vpn_ssl_web_portal_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ssl_web_portal': { + 'allow_user_access': 'web', + 'auto_connect': 'enable', + 'custom_lang': 'test_value_5', + 'customize_forticlient_download_url': 'enable', + 'display_bookmark': 'enable', + 'display_connection_tools': 'enable', + 'display_history': 'enable', + 'display_status': 'enable', + 'dns_server1': 'test_value_11', + 'dns_server2': 'test_value_12', + 'dns_suffix': 'test_value_13', + 'exclusive_routing': 'enable', + 'forticlient_download': 'enable', + 'forticlient_download_method': 'direct', + 'heading': 'test_value_17', + 'hide_sso_credential': 'enable', + 'host_check': 'none', + 'host_check_interval': '20', + 'ip_mode': 'range', + 'ipv6_dns_server1': 'test_value_22', + 'ipv6_dns_server2': 'test_value_23', + 'ipv6_exclusive_routing': 'enable', + 'ipv6_service_restriction': 'enable', + 'ipv6_split_tunneling': 'enable', + 'ipv6_tunnel_mode': 'enable', + 'ipv6_wins_server1': 'test_value_28', + 'ipv6_wins_server2': 'test_value_29', + 'keep_alive': 'enable', + 'limit_user_logins': 'enable', + 'mac_addr_action': 'allow', + 'mac_addr_check': 'enable', + 'macos_forticlient_download_url': 'test_value_34', + 'name': 'default_name_35', + 'os_check': 'enable', + 'redir_url': 'test_value_37', + 'save_password': 'enable', + 'service_restriction': 'enable', + 'skip_check_for_unsupported_browser': 'enable', + 'skip_check_for_unsupported_os': 'enable', + 'smb_ntlmv1_auth': 'enable', + 'smbv1': 'enable', + 'split_tunneling': 'enable', + 'theme': 'blue', + 'tunnel_mode': 'enable', + 'user_bookmark': 'enable', + 'user_group_bookmark': 'enable', + 'web_mode': 'enable', + 'windows_forticlient_download_url': 'test_value_50', + 'wins_server1': 'test_value_51', + 'wins_server2': 'test_value_52' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_web_portal.fortios_vpn_ssl_web(input_data, fos_instance) + + expected_data = { + 'allow-user-access': 'web', + 'auto-connect': 'enable', + 'custom-lang': 'test_value_5', + 'customize-forticlient-download-url': 'enable', + 'display-bookmark': 'enable', + 'display-connection-tools': 'enable', + 'display-history': 'enable', + 'display-status': 'enable', + 'dns-server1': 'test_value_11', + 'dns-server2': 'test_value_12', + 'dns-suffix': 'test_value_13', + 'exclusive-routing': 'enable', + 'forticlient-download': 'enable', + 'forticlient-download-method': 'direct', + 'heading': 'test_value_17', + 'hide-sso-credential': 'enable', + 'host-check': 'none', + 'host-check-interval': '20', + 'ip-mode': 'range', + 'ipv6-dns-server1': 'test_value_22', + 'ipv6-dns-server2': 'test_value_23', + 'ipv6-exclusive-routing': 'enable', + 'ipv6-service-restriction': 'enable', + 'ipv6-split-tunneling': 'enable', + 'ipv6-tunnel-mode': 'enable', + 'ipv6-wins-server1': 'test_value_28', + 'ipv6-wins-server2': 'test_value_29', + 'keep-alive': 'enable', + 'limit-user-logins': 'enable', + 'mac-addr-action': 'allow', + 'mac-addr-check': 'enable', + 'macos-forticlient-download-url': 'test_value_34', + 'name': 'default_name_35', + 'os-check': 'enable', + 'redir-url': 'test_value_37', + 'save-password': 'enable', + 'service-restriction': 'enable', + 'skip-check-for-unsupported-browser': 'enable', + 'skip-check-for-unsupported-os': 'enable', + 'smb-ntlmv1-auth': 'enable', + 'smbv1': 'enable', + 'split-tunneling': 'enable', + 'theme': 'blue', + 'tunnel-mode': 'enable', + 'user-bookmark': 'enable', + 'user-group-bookmark': 'enable', + 'web-mode': 'enable', + 'windows-forticlient-download-url': 'test_value_50', + 'wins-server1': 'test_value_51', + 'wins-server2': 'test_value_52' + } + + set_method_mock.assert_called_with('vpn.ssl.web', 'portal', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_vpn_ssl_web_portal_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'vpn_ssl_web_portal': { + 'random_attribute_not_valid': 'tag', + 'allow_user_access': 'web', + 'auto_connect': 'enable', + 'custom_lang': 'test_value_5', + 'customize_forticlient_download_url': 'enable', + 'display_bookmark': 'enable', + 'display_connection_tools': 'enable', + 'display_history': 'enable', + 'display_status': 'enable', + 'dns_server1': 'test_value_11', + 'dns_server2': 'test_value_12', + 'dns_suffix': 'test_value_13', + 'exclusive_routing': 'enable', + 'forticlient_download': 'enable', + 'forticlient_download_method': 'direct', + 'heading': 'test_value_17', + 'hide_sso_credential': 'enable', + 'host_check': 'none', + 'host_check_interval': '20', + 'ip_mode': 'range', + 'ipv6_dns_server1': 'test_value_22', + 'ipv6_dns_server2': 'test_value_23', + 'ipv6_exclusive_routing': 'enable', + 'ipv6_service_restriction': 'enable', + 'ipv6_split_tunneling': 'enable', + 'ipv6_tunnel_mode': 'enable', + 'ipv6_wins_server1': 'test_value_28', + 'ipv6_wins_server2': 'test_value_29', + 'keep_alive': 'enable', + 'limit_user_logins': 'enable', + 'mac_addr_action': 'allow', + 'mac_addr_check': 'enable', + 'macos_forticlient_download_url': 'test_value_34', + 'name': 'default_name_35', + 'os_check': 'enable', + 'redir_url': 'test_value_37', + 'save_password': 'enable', + 'service_restriction': 'enable', + 'skip_check_for_unsupported_browser': 'enable', + 'skip_check_for_unsupported_os': 'enable', + 'smb_ntlmv1_auth': 'enable', + 'smbv1': 'enable', + 'split_tunneling': 'enable', + 'theme': 'blue', + 'tunnel_mode': 'enable', + 'user_bookmark': 'enable', + 'user_group_bookmark': 'enable', + 'web_mode': 'enable', + 'windows_forticlient_download_url': 'test_value_50', + 'wins_server1': 'test_value_51', + 'wins_server2': 'test_value_52' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_vpn_ssl_web_portal.fortios_vpn_ssl_web(input_data, fos_instance) + + expected_data = { + 'allow-user-access': 'web', + 'auto-connect': 'enable', + 'custom-lang': 'test_value_5', + 'customize-forticlient-download-url': 'enable', + 'display-bookmark': 'enable', + 'display-connection-tools': 'enable', + 'display-history': 'enable', + 'display-status': 'enable', + 'dns-server1': 'test_value_11', + 'dns-server2': 'test_value_12', + 'dns-suffix': 'test_value_13', + 'exclusive-routing': 'enable', + 'forticlient-download': 'enable', + 'forticlient-download-method': 'direct', + 'heading': 'test_value_17', + 'hide-sso-credential': 'enable', + 'host-check': 'none', + 'host-check-interval': '20', + 'ip-mode': 'range', + 'ipv6-dns-server1': 'test_value_22', + 'ipv6-dns-server2': 'test_value_23', + 'ipv6-exclusive-routing': 'enable', + 'ipv6-service-restriction': 'enable', + 'ipv6-split-tunneling': 'enable', + 'ipv6-tunnel-mode': 'enable', + 'ipv6-wins-server1': 'test_value_28', + 'ipv6-wins-server2': 'test_value_29', + 'keep-alive': 'enable', + 'limit-user-logins': 'enable', + 'mac-addr-action': 'allow', + 'mac-addr-check': 'enable', + 'macos-forticlient-download-url': 'test_value_34', + 'name': 'default_name_35', + 'os-check': 'enable', + 'redir-url': 'test_value_37', + 'save-password': 'enable', + 'service-restriction': 'enable', + 'skip-check-for-unsupported-browser': 'enable', + 'skip-check-for-unsupported-os': 'enable', + 'smb-ntlmv1-auth': 'enable', + 'smbv1': 'enable', + 'split-tunneling': 'enable', + 'theme': 'blue', + 'tunnel-mode': 'enable', + 'user-bookmark': 'enable', + 'user-group-bookmark': 'enable', + 'web-mode': 'enable', + 'windows-forticlient-download-url': 'test_value_50', + 'wins-server1': 'test_value_51', + 'wins-server2': 'test_value_52' + } + + set_method_mock.assert_called_with('vpn.ssl.web', 'portal', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_waf_profile.py b/test/units/modules/network/fortios/test_fortios_waf_profile.py new file mode 100644 index 00000000000..c31da795517 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_waf_profile.py @@ -0,0 +1,229 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_waf_profile +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_waf_profile.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_waf_profile_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'waf_profile': {'comment': 'Comment.', + 'extended_log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_waf_profile.fortios_waf(input_data, fos_instance) + + expected_data = {'comment': 'Comment.', + 'extended-log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + } + + set_method_mock.assert_called_with('waf', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_waf_profile_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'waf_profile': {'comment': 'Comment.', + 'extended_log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_waf_profile.fortios_waf(input_data, fos_instance) + + expected_data = {'comment': 'Comment.', + 'extended-log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + } + + set_method_mock.assert_called_with('waf', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_waf_profile_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'waf_profile': {'comment': 'Comment.', + 'extended_log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_waf_profile.fortios_waf(input_data, fos_instance) + + delete_method_mock.assert_called_with('waf', 'profile', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_waf_profile_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'waf_profile': {'comment': 'Comment.', + 'extended_log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_waf_profile.fortios_waf(input_data, fos_instance) + + delete_method_mock.assert_called_with('waf', 'profile', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_waf_profile_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'waf_profile': {'comment': 'Comment.', + 'extended_log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_waf_profile.fortios_waf(input_data, fos_instance) + + expected_data = {'comment': 'Comment.', + 'extended-log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + } + + set_method_mock.assert_called_with('waf', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_waf_profile_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'waf_profile': { + 'random_attribute_not_valid': 'tag', 'comment': 'Comment.', + 'extended_log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_waf_profile.fortios_waf(input_data, fos_instance) + + expected_data = {'comment': 'Comment.', + 'extended-log': 'enable', + 'external': 'disable', + 'name': 'default_name_6', + + } + + set_method_mock.assert_called_with('waf', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_wanopt_profile.py b/test/units/modules/network/fortios/test_fortios_wanopt_profile.py new file mode 100644 index 00000000000..7d149512e80 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_wanopt_profile.py @@ -0,0 +1,229 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_wanopt_profile +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_wanopt_profile.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_wanopt_profile_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'wanopt_profile': { + 'auth_group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_profile.fortios_wanopt(input_data, fos_instance) + + expected_data = { + 'auth-group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + } + + set_method_mock.assert_called_with('wanopt', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_wanopt_profile_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'wanopt_profile': { + 'auth_group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_profile.fortios_wanopt(input_data, fos_instance) + + expected_data = { + 'auth-group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + } + + set_method_mock.assert_called_with('wanopt', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_wanopt_profile_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'wanopt_profile': { + 'auth_group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_profile.fortios_wanopt(input_data, fos_instance) + + delete_method_mock.assert_called_with('wanopt', 'profile', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_wanopt_profile_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'wanopt_profile': { + 'auth_group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_profile.fortios_wanopt(input_data, fos_instance) + + delete_method_mock.assert_called_with('wanopt', 'profile', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_wanopt_profile_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'wanopt_profile': { + 'auth_group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_profile.fortios_wanopt(input_data, fos_instance) + + expected_data = { + 'auth-group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + } + + set_method_mock.assert_called_with('wanopt', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_wanopt_profile_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'wanopt_profile': { + 'random_attribute_not_valid': 'tag', + 'auth_group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_profile.fortios_wanopt(input_data, fos_instance) + + expected_data = { + 'auth-group': 'test_value_3', + 'comments': 'test_value_4', + 'name': 'default_name_5', + 'transparent': 'enable' + } + + set_method_mock.assert_called_with('wanopt', 'profile', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_wanopt_settings.py b/test/units/modules/network/fortios/test_fortios_wanopt_settings.py new file mode 100644 index 00000000000..fc61a2eeeaa --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_wanopt_settings.py @@ -0,0 +1,167 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_wanopt_settings +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_wanopt_settings.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_wanopt_settings_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'wanopt_settings': { + 'auto_detect_algorithm': 'simple', + 'host_id': 'myhostname4', + 'tunnel_ssl_algorithm': 'low' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_settings.fortios_wanopt(input_data, fos_instance) + + expected_data = { + 'auto-detect-algorithm': 'simple', + 'host-id': 'myhostname4', + 'tunnel-ssl-algorithm': 'low' + } + + set_method_mock.assert_called_with('wanopt', 'settings', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_wanopt_settings_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'wanopt_settings': { + 'auto_detect_algorithm': 'simple', + 'host_id': 'myhostname4', + 'tunnel_ssl_algorithm': 'low' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_settings.fortios_wanopt(input_data, fos_instance) + + expected_data = { + 'auto-detect-algorithm': 'simple', + 'host-id': 'myhostname4', + 'tunnel-ssl-algorithm': 'low' + } + + set_method_mock.assert_called_with('wanopt', 'settings', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_wanopt_settings_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'wanopt_settings': { + 'auto_detect_algorithm': 'simple', + 'host_id': 'myhostname4', + 'tunnel_ssl_algorithm': 'low' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_settings.fortios_wanopt(input_data, fos_instance) + + expected_data = { + 'auto-detect-algorithm': 'simple', + 'host-id': 'myhostname4', + 'tunnel-ssl-algorithm': 'low' + } + + set_method_mock.assert_called_with('wanopt', 'settings', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_wanopt_settings_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'wanopt_settings': { + 'random_attribute_not_valid': 'tag', + 'auto_detect_algorithm': 'simple', + 'host_id': 'myhostname4', + 'tunnel_ssl_algorithm': 'low' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_wanopt_settings.fortios_wanopt(input_data, fos_instance) + + expected_data = { + 'auto-detect-algorithm': 'simple', + 'host-id': 'myhostname4', + 'tunnel-ssl-algorithm': 'low' + } + + set_method_mock.assert_called_with('wanopt', 'settings', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_webfilter_content.py b/test/units/modules/network/fortios/test_fortios_webfilter_content.py new file mode 100644 index 00000000000..65e66a27424 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_webfilter_content.py @@ -0,0 +1,219 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_webfilter_content +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_webfilter_content.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_webfilter_content_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'webfilter_content': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content.fortios_webfilter(input_data, fos_instance) + + expected_data = { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + } + + set_method_mock.assert_called_with('webfilter', 'content', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_webfilter_content_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'webfilter_content': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content.fortios_webfilter(input_data, fos_instance) + + expected_data = { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + } + + set_method_mock.assert_called_with('webfilter', 'content', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_webfilter_content_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'webfilter_content': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content.fortios_webfilter(input_data, fos_instance) + + delete_method_mock.assert_called_with('webfilter', 'content', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_webfilter_content_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'webfilter_content': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content.fortios_webfilter(input_data, fos_instance) + + delete_method_mock.assert_called_with('webfilter', 'content', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_webfilter_content_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'webfilter_content': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content.fortios_webfilter(input_data, fos_instance) + + expected_data = { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + } + + set_method_mock.assert_called_with('webfilter', 'content', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_webfilter_content_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'webfilter_content': { + 'random_attribute_not_valid': 'tag', + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content.fortios_webfilter(input_data, fos_instance) + + expected_data = { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + } + + set_method_mock.assert_called_with('webfilter', 'content', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 diff --git a/test/units/modules/network/fortios/test_fortios_webfilter_content_header.py b/test/units/modules/network/fortios/test_fortios_webfilter_content_header.py new file mode 100644 index 00000000000..ba937476f55 --- /dev/null +++ b/test/units/modules/network/fortios/test_fortios_webfilter_content_header.py @@ -0,0 +1,219 @@ +# Copyright 2019 Fortinet, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import pytest +from mock import ANY +from ansible.module_utils.network.fortios.fortios import FortiOSHandler + +try: + from ansible.modules.network.fortios import fortios_webfilter_content_header +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + + +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_webfilter_content_header.Connection') + return connection_class_mock + + +fos_instance = FortiOSHandler(connection_mock) + + +def test_webfilter_content_header_creation(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'webfilter_content_header': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content_header.fortios_webfilter(input_data, fos_instance) + + expected_data = { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + } + + set_method_mock.assert_called_with('webfilter', 'content-header', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_webfilter_content_header_creation_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'webfilter_content_header': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content_header.fortios_webfilter(input_data, fos_instance) + + expected_data = { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + } + + set_method_mock.assert_called_with('webfilter', 'content-header', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_webfilter_content_header_removal(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'webfilter_content_header': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content_header.fortios_webfilter(input_data, fos_instance) + + delete_method_mock.assert_called_with('webfilter', 'content-header', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200 + + +def test_webfilter_content_header_deletion_fails(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + delete_method_result = {'status': 'error', 'http_method': 'POST', 'http_status': 500} + delete_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.delete', return_value=delete_method_result) + + input_data = { + 'username': 'admin', + 'state': 'absent', + 'webfilter_content_header': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content_header.fortios_webfilter(input_data, fos_instance) + + delete_method_mock.assert_called_with('webfilter', 'content-header', mkey=ANY, vdom='root') + schema_method_mock.assert_not_called() + assert is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 500 + + +def test_webfilter_content_header_idempotent(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'error', 'http_method': 'DELETE', 'http_status': 404} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'webfilter_content_header': { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content_header.fortios_webfilter(input_data, fos_instance) + + expected_data = { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + } + + set_method_mock.assert_called_with('webfilter', 'content-header', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert not changed + assert response['status'] == 'error' + assert response['http_status'] == 404 + + +def test_webfilter_content_header_filter_foreign_attributes(mocker): + schema_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.schema') + + set_method_result = {'status': 'success', 'http_method': 'POST', 'http_status': 200} + set_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.set', return_value=set_method_result) + + input_data = { + 'username': 'admin', + 'state': 'present', + 'webfilter_content_header': { + 'random_attribute_not_valid': 'tag', + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + }, + 'vdom': 'root'} + + is_error, changed, response = fortios_webfilter_content_header.fortios_webfilter(input_data, fos_instance) + + expected_data = { + 'comment': 'Optional comments.', + 'id': '4', + 'name': 'default_name_5' + } + + set_method_mock.assert_called_with('webfilter', 'content-header', data=expected_data, vdom='root') + schema_method_mock.assert_not_called() + assert not is_error + assert changed + assert response['status'] == 'success' + assert response['http_status'] == 200