1#!/usr/local/bin/python3.8 2from __future__ import (absolute_import, division, print_function) 3# Copyright 2019-2020 Fortinet, Inc. 4# 5# This program is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18__metaclass__ = type 19 20ANSIBLE_METADATA = {'status': ['preview'], 21 'supported_by': 'community', 22 'metadata_version': '1.1'} 23 24DOCUMENTATION = ''' 25--- 26module: fortios_endpoint_control_forticlient_registration_sync 27short_description: Configure FortiClient registration synchronization settings in Fortinet's FortiOS and FortiGate. 28description: 29 - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the 30 user to set and modify endpoint_control feature and forticlient_registration_sync category. 31 Examples include all parameters and values need to be adjusted to datasources before usage. 32 Tested with FOS v6.0.0 33version_added: "2.10" 34author: 35 - Link Zheng (@chillancezen) 36 - Jie Xue (@JieX19) 37 - Hongbin Lu (@fgtdev-hblu) 38 - Frank Shen (@frankshen01) 39 - Miguel Angel Munoz (@mamunozgonzalez) 40 - Nicolas Thomas (@thomnico) 41notes: 42 - Legacy fortiosapi has been deprecated, httpapi is the preferred way to run playbooks 43 44requirements: 45 - ansible>=2.9.0 46options: 47 access_token: 48 description: 49 - Token-based authentication. 50 Generated from GUI of Fortigate. 51 type: str 52 required: false 53 enable_log: 54 description: 55 - Enable/Disable logging for task. 56 type: bool 57 required: false 58 default: false 59 vdom: 60 description: 61 - Virtual domain, among those defined previously. A vdom is a 62 virtual instance of the FortiGate that can be configured and 63 used as a different unit. 64 type: str 65 default: root 66 67 state: 68 description: 69 - Indicates whether to create or remove the object. 70 type: str 71 required: true 72 choices: 73 - present 74 - absent 75 endpoint_control_forticlient_registration_sync: 76 description: 77 - Configure FortiClient registration synchronization settings. 78 default: null 79 type: dict 80 suboptions: 81 peer_ip: 82 description: 83 - IP address of the peer FortiGate for endpoint license synchronization. 84 type: str 85 peer_name: 86 description: 87 - Peer name. 88 type: str 89''' 90 91EXAMPLES = ''' 92- hosts: fortigates 93 collections: 94 - fortinet.fortios 95 connection: httpapi 96 vars: 97 vdom: "root" 98 ansible_httpapi_use_ssl: yes 99 ansible_httpapi_validate_certs: no 100 ansible_httpapi_port: 443 101 tasks: 102 - name: Configure FortiClient registration synchronization settings. 103 fortios_endpoint_control_forticlient_registration_sync: 104 vdom: "{{ vdom }}" 105 state: "present" 106 access_token: "<your_own_value>" 107 endpoint_control_forticlient_registration_sync: 108 peer_ip: "<your_own_value>" 109 peer_name: "<your_own_value>" 110 111''' 112 113RETURN = ''' 114build: 115 description: Build number of the fortigate image 116 returned: always 117 type: str 118 sample: '1547' 119http_method: 120 description: Last method used to provision the content into FortiGate 121 returned: always 122 type: str 123 sample: 'PUT' 124http_status: 125 description: Last result given by FortiGate on last operation applied 126 returned: always 127 type: str 128 sample: "200" 129mkey: 130 description: Master key (id) used in the last call to FortiGate 131 returned: success 132 type: str 133 sample: "id" 134name: 135 description: Name of the table used to fulfill the request 136 returned: always 137 type: str 138 sample: "urlfilter" 139path: 140 description: Path of the table used to fulfill the request 141 returned: always 142 type: str 143 sample: "webfilter" 144revision: 145 description: Internal revision number 146 returned: always 147 type: str 148 sample: "17.0.2.10658" 149serial: 150 description: Serial number of the unit 151 returned: always 152 type: str 153 sample: "FGVMEVYYQT3AB5352" 154status: 155 description: Indication of the operation's result 156 returned: always 157 type: str 158 sample: "success" 159vdom: 160 description: Virtual domain used 161 returned: always 162 type: str 163 sample: "root" 164version: 165 description: Version of the FortiGate 166 returned: always 167 type: str 168 sample: "v5.6.3" 169 170''' 171from ansible.module_utils.basic import AnsibleModule 172from ansible.module_utils.connection import Connection 173from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler 174from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi 175from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec 176from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning 177from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG 178from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison 179from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize 180 181 182def filter_endpoint_control_forticlient_registration_sync_data(json): 183 option_list = ['peer_ip', 'peer_name'] 184 dictionary = {} 185 186 for attribute in option_list: 187 if attribute in json and json[attribute] is not None: 188 dictionary[attribute] = json[attribute] 189 190 return dictionary 191 192 193def underscore_to_hyphen(data): 194 if isinstance(data, list): 195 for i, elem in enumerate(data): 196 data[i] = underscore_to_hyphen(elem) 197 elif isinstance(data, dict): 198 new_data = {} 199 for k, v in data.items(): 200 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 201 data = new_data 202 203 return data 204 205 206def endpoint_control_forticlient_registration_sync(data, fos, check_mode=False): 207 208 vdom = data['vdom'] 209 210 state = data['state'] 211 212 endpoint_control_forticlient_registration_sync_data = data['endpoint_control_forticlient_registration_sync'] 213 filtered_data = underscore_to_hyphen(filter_endpoint_control_forticlient_registration_sync_data(endpoint_control_forticlient_registration_sync_data)) 214 215 # check_mode starts from here 216 if check_mode: 217 mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom) 218 current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey) 219 is_existed = current_data and current_data.get('http_status') == 200 \ 220 and isinstance(current_data.get('results'), list) \ 221 and len(current_data['results']) > 0 222 223 # 2. if it exists and the state is 'present' then compare current settings with desired 224 if state == 'present' or state is True: 225 if mkey is None: 226 return False, True, filtered_data 227 228 # if mkey exists then compare each other 229 # record exits and they're matched or not 230 if is_existed: 231 is_same = is_same_comparison( 232 serialize(current_data['results'][0]), serialize(filtered_data)) 233 return False, not is_same, filtered_data 234 235 # record does not exist 236 return False, True, filtered_data 237 238 if state == 'absent': 239 if mkey is None: 240 return False, False, filtered_data 241 242 if is_existed: 243 return False, True, filtered_data 244 return False, False, filtered_data 245 246 return True, False, {'reason: ': 'Must provide state parameter'} 247 248 if state == "present" or state is True: 249 return fos.set('endpoint-control', 250 'forticlient-registration-sync', 251 data=filtered_data, 252 vdom=vdom) 253 254 elif state == "absent": 255 return fos.delete('endpoint-control', 256 'forticlient-registration-sync', 257 mkey=filtered_data['peer-name'], 258 vdom=vdom) 259 else: 260 fos._module.fail_json(msg='state must be present or absent!') 261 262 263def is_successful_status(status): 264 return status['status'] == "success" or \ 265 status['http_method'] == "DELETE" and status['http_status'] == 404 266 267 268def fortios_endpoint_control(data, fos, check_mode): 269 270 if data['endpoint_control_forticlient_registration_sync']: 271 resp = endpoint_control_forticlient_registration_sync(data, fos, check_mode) 272 else: 273 fos._module.fail_json(msg='missing task body: %s' % ('endpoint_control_forticlient_registration_sync')) 274 if check_mode: 275 return resp 276 return not is_successful_status(resp), \ 277 resp['status'] == "success" and \ 278 (resp['revision_changed'] if 'revision_changed' in resp else True), \ 279 resp 280 281 282versioned_schema = { 283 "type": "list", 284 "children": { 285 "peer_ip": { 286 "type": "string", 287 "revisions": { 288 "v6.0.11": True, 289 "v6.0.0": True, 290 "v6.0.5": True 291 } 292 }, 293 "peer_name": { 294 "type": "string", 295 "revisions": { 296 "v6.0.11": True, 297 "v6.0.0": True, 298 "v6.0.5": True 299 } 300 } 301 }, 302 "revisions": { 303 "v6.0.11": True, 304 "v6.0.0": True, 305 "v6.0.5": True 306 } 307} 308 309 310def main(): 311 module_spec = schema_to_module_spec(versioned_schema) 312 mkeyname = 'peer-name' 313 fields = { 314 "access_token": {"required": False, "type": "str", "no_log": True}, 315 "enable_log": {"required": False, "type": bool}, 316 "vdom": {"required": False, "type": "str", "default": "root"}, 317 "state": {"required": True, "type": "str", 318 "choices": ["present", "absent"]}, 319 "endpoint_control_forticlient_registration_sync": { 320 "required": False, "type": "dict", "default": None, 321 "options": { 322 } 323 } 324 } 325 for attribute_name in module_spec['options']: 326 fields["endpoint_control_forticlient_registration_sync"]['options'][attribute_name] = module_spec['options'][attribute_name] 327 if mkeyname and mkeyname == attribute_name: 328 fields["endpoint_control_forticlient_registration_sync"]['options'][attribute_name]['required'] = True 329 330 check_legacy_fortiosapi() 331 module = AnsibleModule(argument_spec=fields, 332 supports_check_mode=True) 333 334 versions_check_result = None 335 if module._socket_path: 336 connection = Connection(module._socket_path) 337 if 'access_token' in module.params: 338 connection.set_option('access_token', module.params['access_token']) 339 340 if 'enable_log' in module.params: 341 connection.set_option('enable_log', module.params['enable_log']) 342 else: 343 connection.set_option('enable_log', False) 344 fos = FortiOSHandler(connection, module, mkeyname) 345 versions_check_result = check_schema_versioning(fos, versioned_schema, "endpoint_control_forticlient_registration_sync") 346 347 is_error, has_changed, result = fortios_endpoint_control(module.params, fos, module.check_mode) 348 349 else: 350 module.fail_json(**FAIL_SOCKET_MSG) 351 352 if versions_check_result and versions_check_result['matched'] is False: 353 module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv") 354 355 if not is_error: 356 if versions_check_result and versions_check_result['matched'] is False: 357 module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result) 358 else: 359 module.exit_json(changed=has_changed, meta=result) 360 else: 361 if versions_check_result and versions_check_result['matched'] is False: 362 module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result) 363 else: 364 module.fail_json(msg="Error in repo", meta=result) 365 366 367if __name__ == '__main__': 368 main() 369