#!/usr/local/bin/python3.8 from __future__ import (absolute_import, division, print_function) # Copyright 2019-2020 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 this program. If not, see . __metaclass__ = type ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'community', 'metadata_version': '1.1'} DOCUMENTATION = ''' --- module: fortios_system_api_user short_description: Configure API users in Fortinet's FortiOS and FortiGate. description: - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the user to set and modify system feature and api_user category. Examples include all parameters and values need to be adjusted to datasources before usage. Tested with FOS v6.0.0 version_added: "2.10" author: - Link Zheng (@chillancezen) - Jie Xue (@JieX19) - Hongbin Lu (@fgtdev-hblu) - Frank Shen (@frankshen01) - Miguel Angel Munoz (@mamunozgonzalez) - Nicolas Thomas (@thomnico) notes: - Legacy fortiosapi has been deprecated, httpapi is the preferred way to run playbooks requirements: - ansible>=2.9.0 options: access_token: description: - Token-based authentication. Generated from GUI of Fortigate. type: str required: false enable_log: description: - Enable/Disable logging for task. type: bool required: false default: false 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 state: description: - Indicates whether to create or remove the object. type: str required: true choices: - present - absent system_api_user: description: - Configure API users. default: null type: dict suboptions: accprofile: description: - Admin user access profile. Source system.accprofile.name. type: str api_key: description: - Admin user password. type: str comments: description: - Comment. type: str cors_allow_origin: description: - Value for Access-Control-Allow-Origin on API responses. Avoid using "*" if possible. type: str name: description: - User name. required: true type: str peer_auth: description: - Enable/disable peer authentication. type: str choices: - enable - disable peer_group: description: - Peer group name. type: str schedule: description: - Schedule name. type: str trusthost: description: - Trusthost. type: list suboptions: id: description: - Table ID. required: true type: int ipv4_trusthost: description: - IPv4 trusted host address. type: str ipv6_trusthost: description: - IPv6 trusted host address. type: str type: description: - Trusthost type. type: str choices: - ipv4-trusthost - ipv6-trusthost vdom: description: - Virtual domains. type: list suboptions: name: description: - Virtual domain name. Source system.vdom.name. required: true type: str ''' EXAMPLES = ''' - hosts: fortigates collections: - fortinet.fortios connection: httpapi vars: vdom: "root" ansible_httpapi_use_ssl: yes ansible_httpapi_validate_certs: no ansible_httpapi_port: 443 tasks: - name: Configure API users. fortios_system_api_user: vdom: "{{ vdom }}" state: "present" access_token: "" system_api_user: accprofile: " (source system.accprofile.name)" api_key: "" comments: "" cors_allow_origin: "" name: "default_name_7" peer_auth: "enable" peer_group: "" schedule: "" trusthost: - id: "12" ipv4_trusthost: "" ipv6_trusthost: "" type: "ipv4-trusthost" vdom: - name: "default_name_17 (source system.vdom.name)" ''' RETURN = ''' build: description: Build number of the fortigate image returned: always type: str sample: '1547' http_method: description: Last method used to provision the content into FortiGate returned: always type: str sample: 'PUT' http_status: description: Last result given by FortiGate on last operation applied returned: always type: str sample: "200" mkey: description: Master key (id) used in the last call to FortiGate returned: success type: str sample: "id" name: description: Name of the table used to fulfill the request returned: always type: str sample: "urlfilter" path: description: Path of the table used to fulfill the request returned: always type: str sample: "webfilter" revision: description: Internal revision number returned: always type: str sample: "17.0.2.10658" serial: description: Serial number of the unit returned: always type: str sample: "FGVMEVYYQT3AB5352" status: description: Indication of the operation's result returned: always type: str sample: "success" vdom: description: Virtual domain used returned: always type: str sample: "root" version: description: Version of the FortiGate returned: always type: str sample: "v5.6.3" ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.connection import Connection from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize def filter_system_api_user_data(json): option_list = ['accprofile', 'api_key', 'comments', 'cors_allow_origin', 'name', 'peer_auth', 'peer_group', 'schedule', 'trusthost', 'vdom'] dictionary = {} for attribute in option_list: 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 i, elem in enumerate(data): data[i] = 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 system_api_user(data, fos, check_mode=False): vdom = data['vdom'] state = data['state'] system_api_user_data = data['system_api_user'] filtered_data = underscore_to_hyphen(filter_system_api_user_data(system_api_user_data)) # check_mode starts from here if check_mode: mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom) current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey) is_existed = current_data and current_data.get('http_status') == 200 \ and isinstance(current_data.get('results'), list) \ and len(current_data['results']) > 0 # 2. if it exists and the state is 'present' then compare current settings with desired if state == 'present' or state is True: if mkey is None: return False, True, filtered_data # if mkey exists then compare each other # record exits and they're matched or not if is_existed: is_same = is_same_comparison( serialize(current_data['results'][0]), serialize(filtered_data)) return False, not is_same, filtered_data # record does not exist return False, True, filtered_data if state == 'absent': if mkey is None: return False, False, filtered_data if is_existed: return False, True, filtered_data return False, False, filtered_data return True, False, {'reason: ': 'Must provide state parameter'} if state == "present" or state is True: return fos.set('system', 'api-user', data=filtered_data, vdom=vdom) elif state == "absent": return fos.delete('system', 'api-user', mkey=filtered_data['name'], vdom=vdom) else: fos._module.fail_json(msg='state must be present or absent!') def is_successful_status(status): return status['status'] == "success" or \ status['http_method'] == "DELETE" and status['http_status'] == 404 def fortios_system(data, fos, check_mode): if data['system_api_user']: resp = system_api_user(data, fos, check_mode) else: fos._module.fail_json(msg='missing task body: %s' % ('system_api_user')) if check_mode: return resp return not is_successful_status(resp), \ resp['status'] == "success" and \ (resp['revision_changed'] if 'revision_changed' in resp else True), \ resp versioned_schema = { "type": "list", "children": { "peer_auth": { "type": "string", "options": [ { "value": "enable", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, { "value": "disable", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } } ], "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "name": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "schedule": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "comments": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "peer_group": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "accprofile": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "trusthost": { "type": "list", "children": { "ipv6_trusthost": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "type": { "type": "string", "options": [ { "value": "ipv4-trusthost", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, { "value": "ipv6-trusthost", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } } ], "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "id": { "type": "integer", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "ipv4_trusthost": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } } }, "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "cors_allow_origin": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "api_key": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } }, "vdom": { "type": "list", "children": { "name": { "type": "string", "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } } }, "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } } }, "revisions": { "v6.0.0": True, "v7.0.0": True, "v6.0.5": True, "v6.4.4": True, "v6.4.0": True, "v6.4.1": True, "v6.2.0": True, "v6.2.3": True, "v6.2.5": True, "v6.2.7": True, "v6.0.11": True } } def main(): module_spec = schema_to_module_spec(versioned_schema) mkeyname = 'name' fields = { "access_token": {"required": False, "type": "str", "no_log": True}, "enable_log": {"required": False, "type": bool}, "vdom": {"required": False, "type": "str", "default": "root"}, "state": {"required": True, "type": "str", "choices": ["present", "absent"]}, "system_api_user": { "required": False, "type": "dict", "default": None, "options": { } } } for attribute_name in module_spec['options']: fields["system_api_user"]['options'][attribute_name] = module_spec['options'][attribute_name] if mkeyname and mkeyname == attribute_name: fields["system_api_user"]['options'][attribute_name]['required'] = True check_legacy_fortiosapi() module = AnsibleModule(argument_spec=fields, supports_check_mode=True) versions_check_result = None if module._socket_path: connection = Connection(module._socket_path) if 'access_token' in module.params: connection.set_option('access_token', module.params['access_token']) if 'enable_log' in module.params: connection.set_option('enable_log', module.params['enable_log']) else: connection.set_option('enable_log', False) fos = FortiOSHandler(connection, module, mkeyname) versions_check_result = check_schema_versioning(fos, versioned_schema, "system_api_user") is_error, has_changed, result = fortios_system(module.params, fos, module.check_mode) else: module.fail_json(**FAIL_SOCKET_MSG) if versions_check_result and versions_check_result['matched'] is False: module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv") if not is_error: if versions_check_result and versions_check_result['matched'] is False: module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result) else: module.exit_json(changed=has_changed, meta=result) else: if versions_check_result and versions_check_result['matched'] is False: module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result) else: module.fail_json(msg="Error in repo", meta=result) if __name__ == '__main__': main()