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_system_fm 27short_description: Configure FM 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 system feature and fm 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 system_fm: 68 description: 69 - Configure FM. 70 default: null 71 type: dict 72 suboptions: 73 auto_backup: 74 description: 75 - Enable/disable automatic backup. 76 type: str 77 choices: 78 - enable 79 - disable 80 id: 81 description: 82 - ID. 83 type: str 84 ip: 85 description: 86 - IP address. 87 type: str 88 ipsec: 89 description: 90 - Enable/disable IPsec. 91 type: str 92 choices: 93 - enable 94 - disable 95 scheduled_config_restore: 96 description: 97 - Enable/disable scheduled configuration restore. 98 type: str 99 choices: 100 - enable 101 - disable 102 status: 103 description: 104 - Enable/disable FM. 105 type: str 106 choices: 107 - enable 108 - disable 109 vdom: 110 description: 111 - VDOM. Source system.vdom.name. 112 type: str 113''' 114 115EXAMPLES = ''' 116- hosts: fortigates 117 collections: 118 - fortinet.fortios 119 connection: httpapi 120 vars: 121 vdom: "root" 122 ansible_httpapi_use_ssl: yes 123 ansible_httpapi_validate_certs: no 124 ansible_httpapi_port: 443 125 tasks: 126 - name: Configure FM. 127 fortios_system_fm: 128 vdom: "{{ vdom }}" 129 system_fm: 130 auto_backup: "enable" 131 id: "4" 132 ip: "<your_own_value>" 133 ipsec: "enable" 134 scheduled_config_restore: "enable" 135 status: "enable" 136 vdom: "<your_own_value> (source system.vdom.name)" 137 138''' 139 140RETURN = ''' 141build: 142 description: Build number of the fortigate image 143 returned: always 144 type: str 145 sample: '1547' 146http_method: 147 description: Last method used to provision the content into FortiGate 148 returned: always 149 type: str 150 sample: 'PUT' 151http_status: 152 description: Last result given by FortiGate on last operation applied 153 returned: always 154 type: str 155 sample: "200" 156mkey: 157 description: Master key (id) used in the last call to FortiGate 158 returned: success 159 type: str 160 sample: "id" 161name: 162 description: Name of the table used to fulfill the request 163 returned: always 164 type: str 165 sample: "urlfilter" 166path: 167 description: Path of the table used to fulfill the request 168 returned: always 169 type: str 170 sample: "webfilter" 171revision: 172 description: Internal revision number 173 returned: always 174 type: str 175 sample: "17.0.2.10658" 176serial: 177 description: Serial number of the unit 178 returned: always 179 type: str 180 sample: "FGVMEVYYQT3AB5352" 181status: 182 description: Indication of the operation's result 183 returned: always 184 type: str 185 sample: "success" 186vdom: 187 description: Virtual domain used 188 returned: always 189 type: str 190 sample: "root" 191version: 192 description: Version of the FortiGate 193 returned: always 194 type: str 195 sample: "v5.6.3" 196 197''' 198from ansible.module_utils.basic import AnsibleModule 199from ansible.module_utils.connection import Connection 200from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler 201from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi 202from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec 203from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning 204from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG 205from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison 206from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize 207 208 209def filter_system_fm_data(json): 210 option_list = ['auto_backup', 'id', 'ip', 211 'ipsec', 'scheduled_config_restore', 'status', 212 'vdom'] 213 dictionary = {} 214 215 for attribute in option_list: 216 if attribute in json and json[attribute] is not None: 217 dictionary[attribute] = json[attribute] 218 219 return dictionary 220 221 222def underscore_to_hyphen(data): 223 if isinstance(data, list): 224 for i, elem in enumerate(data): 225 data[i] = underscore_to_hyphen(elem) 226 elif isinstance(data, dict): 227 new_data = {} 228 for k, v in data.items(): 229 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 230 data = new_data 231 232 return data 233 234 235def system_fm(data, fos): 236 vdom = data['vdom'] 237 system_fm_data = data['system_fm'] 238 filtered_data = underscore_to_hyphen(filter_system_fm_data(system_fm_data)) 239 240 return fos.set('system', 241 'fm', 242 data=filtered_data, 243 vdom=vdom) 244 245 246def is_successful_status(status): 247 return status['status'] == "success" or \ 248 status['http_method'] == "DELETE" and status['http_status'] == 404 249 250 251def fortios_system(data, fos): 252 253 if data['system_fm']: 254 resp = system_fm(data, fos) 255 else: 256 fos._module.fail_json(msg='missing task body: %s' % ('system_fm')) 257 258 return not is_successful_status(resp), \ 259 resp['status'] == "success" and \ 260 (resp['revision_changed'] if 'revision_changed' in resp else True), \ 261 resp 262 263 264versioned_schema = { 265 "type": "dict", 266 "children": { 267 "status": { 268 "type": "string", 269 "options": [ 270 { 271 "value": "enable", 272 "revisions": { 273 "v6.0.0": True, 274 "v7.0.0": True, 275 "v6.0.5": True, 276 "v6.4.4": True, 277 "v6.4.0": True, 278 "v6.4.1": True, 279 "v6.2.0": True, 280 "v6.2.3": True, 281 "v6.2.5": True, 282 "v6.2.7": True, 283 "v6.0.11": True 284 } 285 }, 286 { 287 "value": "disable", 288 "revisions": { 289 "v6.0.0": True, 290 "v7.0.0": True, 291 "v6.0.5": True, 292 "v6.4.4": True, 293 "v6.4.0": True, 294 "v6.4.1": True, 295 "v6.2.0": True, 296 "v6.2.3": True, 297 "v6.2.5": True, 298 "v6.2.7": True, 299 "v6.0.11": True 300 } 301 } 302 ], 303 "revisions": { 304 "v6.0.0": True, 305 "v7.0.0": True, 306 "v6.0.5": True, 307 "v6.4.4": True, 308 "v6.4.0": True, 309 "v6.4.1": True, 310 "v6.2.0": True, 311 "v6.2.3": True, 312 "v6.2.5": True, 313 "v6.2.7": True, 314 "v6.0.11": True 315 } 316 }, 317 "scheduled_config_restore": { 318 "type": "string", 319 "options": [ 320 { 321 "value": "enable", 322 "revisions": { 323 "v6.0.0": True, 324 "v7.0.0": True, 325 "v6.0.5": True, 326 "v6.4.4": True, 327 "v6.4.0": True, 328 "v6.4.1": True, 329 "v6.2.0": True, 330 "v6.2.3": True, 331 "v6.2.5": True, 332 "v6.2.7": True, 333 "v6.0.11": True 334 } 335 }, 336 { 337 "value": "disable", 338 "revisions": { 339 "v6.0.0": True, 340 "v7.0.0": True, 341 "v6.0.5": True, 342 "v6.4.4": True, 343 "v6.4.0": True, 344 "v6.4.1": True, 345 "v6.2.0": True, 346 "v6.2.3": True, 347 "v6.2.5": True, 348 "v6.2.7": True, 349 "v6.0.11": True 350 } 351 } 352 ], 353 "revisions": { 354 "v6.0.0": True, 355 "v7.0.0": True, 356 "v6.0.5": True, 357 "v6.4.4": True, 358 "v6.4.0": True, 359 "v6.4.1": True, 360 "v6.2.0": True, 361 "v6.2.3": True, 362 "v6.2.5": True, 363 "v6.2.7": True, 364 "v6.0.11": True 365 } 366 }, 367 "auto_backup": { 368 "type": "string", 369 "options": [ 370 { 371 "value": "enable", 372 "revisions": { 373 "v6.0.0": True, 374 "v7.0.0": True, 375 "v6.0.5": True, 376 "v6.4.4": True, 377 "v6.4.0": True, 378 "v6.4.1": True, 379 "v6.2.0": True, 380 "v6.2.3": True, 381 "v6.2.5": True, 382 "v6.2.7": True, 383 "v6.0.11": True 384 } 385 }, 386 { 387 "value": "disable", 388 "revisions": { 389 "v6.0.0": True, 390 "v7.0.0": True, 391 "v6.0.5": True, 392 "v6.4.4": True, 393 "v6.4.0": True, 394 "v6.4.1": True, 395 "v6.2.0": True, 396 "v6.2.3": True, 397 "v6.2.5": True, 398 "v6.2.7": True, 399 "v6.0.11": True 400 } 401 } 402 ], 403 "revisions": { 404 "v6.0.0": True, 405 "v7.0.0": True, 406 "v6.0.5": True, 407 "v6.4.4": True, 408 "v6.4.0": True, 409 "v6.4.1": True, 410 "v6.2.0": True, 411 "v6.2.3": True, 412 "v6.2.5": True, 413 "v6.2.7": True, 414 "v6.0.11": True 415 } 416 }, 417 "ip": { 418 "type": "string", 419 "revisions": { 420 "v6.0.0": True, 421 "v7.0.0": True, 422 "v6.0.5": True, 423 "v6.4.4": True, 424 "v6.4.0": True, 425 "v6.4.1": True, 426 "v6.2.0": True, 427 "v6.2.3": True, 428 "v6.2.5": True, 429 "v6.2.7": True, 430 "v6.0.11": True 431 } 432 }, 433 "id": { 434 "type": "string", 435 "revisions": { 436 "v6.0.0": True, 437 "v7.0.0": True, 438 "v6.0.5": True, 439 "v6.4.4": True, 440 "v6.4.0": True, 441 "v6.4.1": True, 442 "v6.2.0": True, 443 "v6.2.3": True, 444 "v6.2.5": True, 445 "v6.2.7": True, 446 "v6.0.11": True 447 } 448 }, 449 "vdom": { 450 "type": "string", 451 "revisions": { 452 "v6.0.0": True, 453 "v7.0.0": True, 454 "v6.0.5": True, 455 "v6.4.4": True, 456 "v6.4.0": True, 457 "v6.4.1": True, 458 "v6.2.0": True, 459 "v6.2.3": True, 460 "v6.2.5": True, 461 "v6.2.7": True, 462 "v6.0.11": True 463 } 464 }, 465 "ipsec": { 466 "type": "string", 467 "options": [ 468 { 469 "value": "enable", 470 "revisions": { 471 "v6.0.0": True, 472 "v7.0.0": True, 473 "v6.0.5": True, 474 "v6.4.4": True, 475 "v6.4.0": True, 476 "v6.4.1": True, 477 "v6.2.0": True, 478 "v6.2.3": True, 479 "v6.2.5": True, 480 "v6.2.7": True, 481 "v6.0.11": True 482 } 483 }, 484 { 485 "value": "disable", 486 "revisions": { 487 "v6.0.0": True, 488 "v7.0.0": True, 489 "v6.0.5": True, 490 "v6.4.4": True, 491 "v6.4.0": True, 492 "v6.4.1": True, 493 "v6.2.0": True, 494 "v6.2.3": True, 495 "v6.2.5": True, 496 "v6.2.7": True, 497 "v6.0.11": True 498 } 499 } 500 ], 501 "revisions": { 502 "v6.0.0": True, 503 "v7.0.0": True, 504 "v6.0.5": True, 505 "v6.4.4": True, 506 "v6.4.0": True, 507 "v6.4.1": True, 508 "v6.2.0": True, 509 "v6.2.3": True, 510 "v6.2.5": True, 511 "v6.2.7": True, 512 "v6.0.11": True 513 } 514 } 515 }, 516 "revisions": { 517 "v6.0.0": True, 518 "v7.0.0": True, 519 "v6.0.5": True, 520 "v6.4.4": True, 521 "v6.4.0": True, 522 "v6.4.1": True, 523 "v6.2.0": True, 524 "v6.2.3": True, 525 "v6.2.5": True, 526 "v6.2.7": True, 527 "v6.0.11": True 528 } 529} 530 531 532def main(): 533 module_spec = schema_to_module_spec(versioned_schema) 534 mkeyname = None 535 fields = { 536 "access_token": {"required": False, "type": "str", "no_log": True}, 537 "enable_log": {"required": False, "type": bool}, 538 "vdom": {"required": False, "type": "str", "default": "root"}, 539 "system_fm": { 540 "required": False, "type": "dict", "default": None, 541 "options": { 542 } 543 } 544 } 545 for attribute_name in module_spec['options']: 546 fields["system_fm"]['options'][attribute_name] = module_spec['options'][attribute_name] 547 if mkeyname and mkeyname == attribute_name: 548 fields["system_fm"]['options'][attribute_name]['required'] = True 549 550 check_legacy_fortiosapi() 551 module = AnsibleModule(argument_spec=fields, 552 supports_check_mode=False) 553 554 versions_check_result = None 555 if module._socket_path: 556 connection = Connection(module._socket_path) 557 if 'access_token' in module.params: 558 connection.set_option('access_token', module.params['access_token']) 559 560 if 'enable_log' in module.params: 561 connection.set_option('enable_log', module.params['enable_log']) 562 else: 563 connection.set_option('enable_log', False) 564 fos = FortiOSHandler(connection, module, mkeyname) 565 versions_check_result = check_schema_versioning(fos, versioned_schema, "system_fm") 566 567 is_error, has_changed, result = fortios_system(module.params, fos) 568 569 else: 570 module.fail_json(**FAIL_SOCKET_MSG) 571 572 if versions_check_result and versions_check_result['matched'] is False: 573 module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv") 574 575 if not is_error: 576 if versions_check_result and versions_check_result['matched'] is False: 577 module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result) 578 else: 579 module.exit_json(changed=has_changed, meta=result) 580 else: 581 if versions_check_result and versions_check_result['matched'] is False: 582 module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result) 583 else: 584 module.fail_json(msg="Error in repo", meta=result) 585 586 587if __name__ == '__main__': 588 main() 589