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