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_firewall_vipgrp6 27short_description: Configure IPv6 virtual IP groups 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 firewall feature and vipgrp6 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 firewall_vipgrp6: 76 description: 77 - Configure IPv6 virtual IP groups. 78 default: null 79 type: dict 80 suboptions: 81 color: 82 description: 83 - Integer value to determine the color of the icon in the GUI (range 1 to 32). 84 type: int 85 comments: 86 description: 87 - Comment. 88 type: str 89 member: 90 description: 91 - Member VIP objects of the group (Separate multiple objects with a space). 92 type: list 93 suboptions: 94 name: 95 description: 96 - IPv6 VIP name. Source firewall.vip6.name. 97 required: true 98 type: str 99 name: 100 description: 101 - IPv6 VIP group name. 102 required: true 103 type: str 104 uuid: 105 description: 106 - Universally Unique Identifier (UUID; automatically assigned but can be manually reset). 107 type: str 108''' 109 110EXAMPLES = ''' 111- hosts: fortigates 112 collections: 113 - fortinet.fortios 114 connection: httpapi 115 vars: 116 vdom: "root" 117 ansible_httpapi_use_ssl: yes 118 ansible_httpapi_validate_certs: no 119 ansible_httpapi_port: 443 120 tasks: 121 - name: Configure IPv6 virtual IP groups. 122 fortios_firewall_vipgrp6: 123 vdom: "{{ vdom }}" 124 state: "present" 125 access_token: "<your_own_value>" 126 firewall_vipgrp6: 127 color: "3" 128 comments: "<your_own_value>" 129 member: 130 - 131 name: "default_name_6 (source firewall.vip6.name)" 132 name: "default_name_7" 133 uuid: "<your_own_value>" 134 135''' 136 137RETURN = ''' 138build: 139 description: Build number of the fortigate image 140 returned: always 141 type: str 142 sample: '1547' 143http_method: 144 description: Last method used to provision the content into FortiGate 145 returned: always 146 type: str 147 sample: 'PUT' 148http_status: 149 description: Last result given by FortiGate on last operation applied 150 returned: always 151 type: str 152 sample: "200" 153mkey: 154 description: Master key (id) used in the last call to FortiGate 155 returned: success 156 type: str 157 sample: "id" 158name: 159 description: Name of the table used to fulfill the request 160 returned: always 161 type: str 162 sample: "urlfilter" 163path: 164 description: Path of the table used to fulfill the request 165 returned: always 166 type: str 167 sample: "webfilter" 168revision: 169 description: Internal revision number 170 returned: always 171 type: str 172 sample: "17.0.2.10658" 173serial: 174 description: Serial number of the unit 175 returned: always 176 type: str 177 sample: "FGVMEVYYQT3AB5352" 178status: 179 description: Indication of the operation's result 180 returned: always 181 type: str 182 sample: "success" 183vdom: 184 description: Virtual domain used 185 returned: always 186 type: str 187 sample: "root" 188version: 189 description: Version of the FortiGate 190 returned: always 191 type: str 192 sample: "v5.6.3" 193 194''' 195from ansible.module_utils.basic import AnsibleModule 196from ansible.module_utils.connection import Connection 197from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler 198from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi 199from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec 200from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning 201from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG 202from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison 203from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize 204 205 206def filter_firewall_vipgrp6_data(json): 207 option_list = ['color', 'comments', 'member', 208 'name', 'uuid'] 209 dictionary = {} 210 211 for attribute in option_list: 212 if attribute in json and json[attribute] is not None: 213 dictionary[attribute] = json[attribute] 214 215 return dictionary 216 217 218def underscore_to_hyphen(data): 219 if isinstance(data, list): 220 for i, elem in enumerate(data): 221 data[i] = underscore_to_hyphen(elem) 222 elif isinstance(data, dict): 223 new_data = {} 224 for k, v in data.items(): 225 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 226 data = new_data 227 228 return data 229 230 231def firewall_vipgrp6(data, fos, check_mode=False): 232 233 vdom = data['vdom'] 234 235 state = data['state'] 236 237 firewall_vipgrp6_data = data['firewall_vipgrp6'] 238 filtered_data = underscore_to_hyphen(filter_firewall_vipgrp6_data(firewall_vipgrp6_data)) 239 240 # check_mode starts from here 241 if check_mode: 242 mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom) 243 current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey) 244 is_existed = current_data and current_data.get('http_status') == 200 \ 245 and isinstance(current_data.get('results'), list) \ 246 and len(current_data['results']) > 0 247 248 # 2. if it exists and the state is 'present' then compare current settings with desired 249 if state == 'present' or state is True: 250 if mkey is None: 251 return False, True, filtered_data 252 253 # if mkey exists then compare each other 254 # record exits and they're matched or not 255 if is_existed: 256 is_same = is_same_comparison( 257 serialize(current_data['results'][0]), serialize(filtered_data)) 258 return False, not is_same, filtered_data 259 260 # record does not exist 261 return False, True, filtered_data 262 263 if state == 'absent': 264 if mkey is None: 265 return False, False, filtered_data 266 267 if is_existed: 268 return False, True, filtered_data 269 return False, False, filtered_data 270 271 return True, False, {'reason: ': 'Must provide state parameter'} 272 273 if state == "present" or state is True: 274 return fos.set('firewall', 275 'vipgrp6', 276 data=filtered_data, 277 vdom=vdom) 278 279 elif state == "absent": 280 return fos.delete('firewall', 281 'vipgrp6', 282 mkey=filtered_data['name'], 283 vdom=vdom) 284 else: 285 fos._module.fail_json(msg='state must be present or absent!') 286 287 288def is_successful_status(status): 289 return status['status'] == "success" or \ 290 status['http_method'] == "DELETE" and status['http_status'] == 404 291 292 293def fortios_firewall(data, fos, check_mode): 294 295 if data['firewall_vipgrp6']: 296 resp = firewall_vipgrp6(data, fos, check_mode) 297 else: 298 fos._module.fail_json(msg='missing task body: %s' % ('firewall_vipgrp6')) 299 if check_mode: 300 return resp 301 return not is_successful_status(resp), \ 302 resp['status'] == "success" and \ 303 (resp['revision_changed'] if 'revision_changed' in resp else True), \ 304 resp 305 306 307versioned_schema = { 308 "type": "list", 309 "children": { 310 "color": { 311 "type": "integer", 312 "revisions": { 313 "v6.0.0": True, 314 "v7.0.0": True, 315 "v6.0.5": True, 316 "v6.4.4": True, 317 "v6.4.0": True, 318 "v6.4.1": True, 319 "v6.2.0": True, 320 "v6.2.3": True, 321 "v6.2.5": True, 322 "v6.2.7": True, 323 "v6.0.11": True 324 } 325 }, 326 "member": { 327 "type": "list", 328 "children": { 329 "name": { 330 "type": "string", 331 "revisions": { 332 "v6.0.0": True, 333 "v7.0.0": True, 334 "v6.0.5": True, 335 "v6.4.4": True, 336 "v6.4.0": True, 337 "v6.4.1": True, 338 "v6.2.0": True, 339 "v6.2.3": True, 340 "v6.2.5": True, 341 "v6.2.7": True, 342 "v6.0.11": True 343 } 344 } 345 }, 346 "revisions": { 347 "v6.0.0": True, 348 "v7.0.0": True, 349 "v6.0.5": True, 350 "v6.4.4": True, 351 "v6.4.0": True, 352 "v6.4.1": True, 353 "v6.2.0": True, 354 "v6.2.3": True, 355 "v6.2.5": True, 356 "v6.2.7": True, 357 "v6.0.11": True 358 } 359 }, 360 "name": { 361 "type": "string", 362 "revisions": { 363 "v6.0.0": True, 364 "v7.0.0": True, 365 "v6.0.5": True, 366 "v6.4.4": True, 367 "v6.4.0": True, 368 "v6.4.1": True, 369 "v6.2.0": True, 370 "v6.2.3": True, 371 "v6.2.5": True, 372 "v6.2.7": True, 373 "v6.0.11": True 374 } 375 }, 376 "comments": { 377 "type": "string", 378 "revisions": { 379 "v6.0.0": True, 380 "v7.0.0": True, 381 "v6.0.5": True, 382 "v6.4.4": True, 383 "v6.4.0": True, 384 "v6.4.1": True, 385 "v6.2.0": True, 386 "v6.2.3": True, 387 "v6.2.5": True, 388 "v6.2.7": True, 389 "v6.0.11": True 390 } 391 }, 392 "uuid": { 393 "type": "string", 394 "revisions": { 395 "v6.0.0": True, 396 "v7.0.0": True, 397 "v6.0.5": True, 398 "v6.4.4": True, 399 "v6.4.0": True, 400 "v6.4.1": True, 401 "v6.2.0": True, 402 "v6.2.3": True, 403 "v6.2.5": True, 404 "v6.2.7": True, 405 "v6.0.11": True 406 } 407 } 408 }, 409 "revisions": { 410 "v6.0.0": True, 411 "v7.0.0": True, 412 "v6.0.5": True, 413 "v6.4.4": True, 414 "v6.4.0": True, 415 "v6.4.1": True, 416 "v6.2.0": True, 417 "v6.2.3": True, 418 "v6.2.5": True, 419 "v6.2.7": True, 420 "v6.0.11": True 421 } 422} 423 424 425def main(): 426 module_spec = schema_to_module_spec(versioned_schema) 427 mkeyname = 'name' 428 fields = { 429 "access_token": {"required": False, "type": "str", "no_log": True}, 430 "enable_log": {"required": False, "type": bool}, 431 "vdom": {"required": False, "type": "str", "default": "root"}, 432 "state": {"required": True, "type": "str", 433 "choices": ["present", "absent"]}, 434 "firewall_vipgrp6": { 435 "required": False, "type": "dict", "default": None, 436 "options": { 437 } 438 } 439 } 440 for attribute_name in module_spec['options']: 441 fields["firewall_vipgrp6"]['options'][attribute_name] = module_spec['options'][attribute_name] 442 if mkeyname and mkeyname == attribute_name: 443 fields["firewall_vipgrp6"]['options'][attribute_name]['required'] = True 444 445 check_legacy_fortiosapi() 446 module = AnsibleModule(argument_spec=fields, 447 supports_check_mode=True) 448 449 versions_check_result = None 450 if module._socket_path: 451 connection = Connection(module._socket_path) 452 if 'access_token' in module.params: 453 connection.set_option('access_token', module.params['access_token']) 454 455 if 'enable_log' in module.params: 456 connection.set_option('enable_log', module.params['enable_log']) 457 else: 458 connection.set_option('enable_log', False) 459 fos = FortiOSHandler(connection, module, mkeyname) 460 versions_check_result = check_schema_versioning(fos, versioned_schema, "firewall_vipgrp6") 461 462 is_error, has_changed, result = fortios_firewall(module.params, fos, module.check_mode) 463 464 else: 465 module.fail_json(**FAIL_SOCKET_MSG) 466 467 if versions_check_result and versions_check_result['matched'] is False: 468 module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv") 469 470 if not is_error: 471 if versions_check_result and versions_check_result['matched'] is False: 472 module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result) 473 else: 474 module.exit_json(changed=has_changed, meta=result) 475 else: 476 if versions_check_result and versions_check_result['matched'] is False: 477 module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result) 478 else: 479 module.fail_json(msg="Error in repo", meta=result) 480 481 482if __name__ == '__main__': 483 main() 484