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_internet_service 27short_description: Show Internet Service application 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 internet_service 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_internet_service: 76 description: 77 - Show Internet Service application. 78 default: null 79 type: dict 80 suboptions: 81 database: 82 description: 83 - Database name this Internet Service belongs to. 84 type: str 85 choices: 86 - isdb 87 - irdb 88 direction: 89 description: 90 - How this service may be used in a firewall policy (source, destination or both). 91 type: str 92 choices: 93 - src 94 - dst 95 - both 96 entry: 97 description: 98 - Entries in the Internet Service database. 99 type: list 100 suboptions: 101 id: 102 description: 103 - Entry ID. 104 required: true 105 type: int 106 ip_number: 107 description: 108 - Total number of IP addresses. 109 type: int 110 ip_range_number: 111 description: 112 - Total number of IP ranges. 113 type: int 114 port: 115 description: 116 - Integer value for the TCP/IP port (0 - 65535). 117 type: int 118 protocol: 119 description: 120 - Integer value for the protocol type as defined by IANA (0 - 255). 121 type: int 122 extra_ip_range_number: 123 description: 124 - Extra number of IP ranges. 125 type: int 126 icon_id: 127 description: 128 - Icon ID of Internet Service. 129 type: int 130 id: 131 description: 132 - Internet Service ID. 133 required: true 134 type: int 135 ip_number: 136 description: 137 - Total number of IP addresses. 138 type: int 139 ip_range_number: 140 description: 141 - Number of IP ranges. 142 type: int 143 name: 144 description: 145 - Internet Service name. 146 type: str 147 obsolete: 148 description: 149 - Indicates whether the Internet Service can be used. 150 type: int 151 offset: 152 description: 153 - Offset of Internet Service ID. 154 type: int 155 reputation: 156 description: 157 - Reputation level of the Internet Service. 158 type: int 159 singularity: 160 description: 161 - Singular level of the Internet Service. 162 type: int 163 sld_id: 164 description: 165 - Second Level Domain. 166 type: int 167''' 168 169EXAMPLES = ''' 170- hosts: fortigates 171 collections: 172 - fortinet.fortios 173 connection: httpapi 174 vars: 175 vdom: "root" 176 ansible_httpapi_use_ssl: yes 177 ansible_httpapi_validate_certs: no 178 ansible_httpapi_port: 443 179 tasks: 180 - name: Show Internet Service application. 181 fortios_firewall_internet_service: 182 vdom: "{{ vdom }}" 183 state: "present" 184 access_token: "<your_own_value>" 185 firewall_internet_service: 186 database: "isdb" 187 direction: "src" 188 entry: 189 - 190 id: "6" 191 ip_number: "7" 192 ip_range_number: "8" 193 port: "9" 194 protocol: "10" 195 extra_ip_range_number: "11" 196 icon_id: "12" 197 id: "13" 198 ip_number: "14" 199 ip_range_number: "15" 200 name: "default_name_16" 201 obsolete: "17" 202 offset: "18" 203 reputation: "19" 204 singularity: "20" 205 sld_id: "21" 206 207''' 208 209RETURN = ''' 210build: 211 description: Build number of the fortigate image 212 returned: always 213 type: str 214 sample: '1547' 215http_method: 216 description: Last method used to provision the content into FortiGate 217 returned: always 218 type: str 219 sample: 'PUT' 220http_status: 221 description: Last result given by FortiGate on last operation applied 222 returned: always 223 type: str 224 sample: "200" 225mkey: 226 description: Master key (id) used in the last call to FortiGate 227 returned: success 228 type: str 229 sample: "id" 230name: 231 description: Name of the table used to fulfill the request 232 returned: always 233 type: str 234 sample: "urlfilter" 235path: 236 description: Path of the table used to fulfill the request 237 returned: always 238 type: str 239 sample: "webfilter" 240revision: 241 description: Internal revision number 242 returned: always 243 type: str 244 sample: "17.0.2.10658" 245serial: 246 description: Serial number of the unit 247 returned: always 248 type: str 249 sample: "FGVMEVYYQT3AB5352" 250status: 251 description: Indication of the operation's result 252 returned: always 253 type: str 254 sample: "success" 255vdom: 256 description: Virtual domain used 257 returned: always 258 type: str 259 sample: "root" 260version: 261 description: Version of the FortiGate 262 returned: always 263 type: str 264 sample: "v5.6.3" 265 266''' 267from ansible.module_utils.basic import AnsibleModule 268from ansible.module_utils.connection import Connection 269from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler 270from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi 271from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec 272from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning 273from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG 274from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison 275from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize 276 277 278def filter_firewall_internet_service_data(json): 279 option_list = ['database', 'direction', 'entry', 280 'extra_ip_range_number', 'icon_id', 'id', 281 'ip_number', 'ip_range_number', 'name', 282 'obsolete', 'offset', 'reputation', 283 'singularity', 'sld_id'] 284 dictionary = {} 285 286 for attribute in option_list: 287 if attribute in json and json[attribute] is not None: 288 dictionary[attribute] = json[attribute] 289 290 return dictionary 291 292 293def underscore_to_hyphen(data): 294 if isinstance(data, list): 295 for i, elem in enumerate(data): 296 data[i] = underscore_to_hyphen(elem) 297 elif isinstance(data, dict): 298 new_data = {} 299 for k, v in data.items(): 300 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 301 data = new_data 302 303 return data 304 305 306def firewall_internet_service(data, fos, check_mode=False): 307 308 vdom = data['vdom'] 309 310 state = data['state'] 311 312 firewall_internet_service_data = data['firewall_internet_service'] 313 filtered_data = underscore_to_hyphen(filter_firewall_internet_service_data(firewall_internet_service_data)) 314 315 # check_mode starts from here 316 if check_mode: 317 mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom) 318 current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey) 319 is_existed = current_data and current_data.get('http_status') == 200 \ 320 and isinstance(current_data.get('results'), list) \ 321 and len(current_data['results']) > 0 322 323 # 2. if it exists and the state is 'present' then compare current settings with desired 324 if state == 'present' or state is True: 325 if mkey is None: 326 return False, True, filtered_data 327 328 # if mkey exists then compare each other 329 # record exits and they're matched or not 330 if is_existed: 331 is_same = is_same_comparison( 332 serialize(current_data['results'][0]), serialize(filtered_data)) 333 return False, not is_same, filtered_data 334 335 # record does not exist 336 return False, True, filtered_data 337 338 if state == 'absent': 339 if mkey is None: 340 return False, False, filtered_data 341 342 if is_existed: 343 return False, True, filtered_data 344 return False, False, filtered_data 345 346 return True, False, {'reason: ': 'Must provide state parameter'} 347 348 if state == "present" or state is True: 349 return fos.set('firewall', 350 'internet-service', 351 data=filtered_data, 352 vdom=vdom) 353 354 elif state == "absent": 355 return fos.delete('firewall', 356 'internet-service', 357 mkey=filtered_data['id'], 358 vdom=vdom) 359 else: 360 fos._module.fail_json(msg='state must be present or absent!') 361 362 363def is_successful_status(status): 364 return status['status'] == "success" or \ 365 status['http_method'] == "DELETE" and status['http_status'] == 404 366 367 368def fortios_firewall(data, fos, check_mode): 369 370 if data['firewall_internet_service']: 371 resp = firewall_internet_service(data, fos, check_mode) 372 else: 373 fos._module.fail_json(msg='missing task body: %s' % ('firewall_internet_service')) 374 if check_mode: 375 return resp 376 return not is_successful_status(resp), \ 377 resp['status'] == "success" and \ 378 (resp['revision_changed'] if 'revision_changed' in resp else True), \ 379 resp 380 381 382versioned_schema = { 383 "type": "list", 384 "children": { 385 "direction": { 386 "type": "string", 387 "options": [ 388 { 389 "value": "src", 390 "revisions": { 391 "v6.0.0": True, 392 "v7.0.0": True, 393 "v6.0.5": True, 394 "v6.4.4": True, 395 "v6.4.0": True, 396 "v6.4.1": True, 397 "v6.2.0": True, 398 "v6.2.3": True, 399 "v6.2.5": True, 400 "v6.2.7": True, 401 "v6.0.11": True 402 } 403 }, 404 { 405 "value": "dst", 406 "revisions": { 407 "v6.0.0": True, 408 "v7.0.0": True, 409 "v6.0.5": True, 410 "v6.4.4": True, 411 "v6.4.0": True, 412 "v6.4.1": True, 413 "v6.2.0": True, 414 "v6.2.3": True, 415 "v6.2.5": True, 416 "v6.2.7": True, 417 "v6.0.11": True 418 } 419 }, 420 { 421 "value": "both", 422 "revisions": { 423 "v6.0.0": True, 424 "v7.0.0": True, 425 "v6.0.5": True, 426 "v6.4.4": True, 427 "v6.4.0": True, 428 "v6.4.1": True, 429 "v6.2.0": True, 430 "v6.2.3": True, 431 "v6.2.5": True, 432 "v6.2.7": True, 433 "v6.0.11": True 434 } 435 } 436 ], 437 "revisions": { 438 "v6.0.0": True, 439 "v7.0.0": True, 440 "v6.0.5": True, 441 "v6.4.4": True, 442 "v6.4.0": True, 443 "v6.4.1": True, 444 "v6.2.0": True, 445 "v6.2.3": True, 446 "v6.2.5": True, 447 "v6.2.7": True, 448 "v6.0.11": True 449 } 450 }, 451 "name": { 452 "type": "string", 453 "revisions": { 454 "v6.0.0": True, 455 "v7.0.0": True, 456 "v6.0.5": True, 457 "v6.4.4": True, 458 "v6.4.0": True, 459 "v6.4.1": True, 460 "v6.2.0": True, 461 "v6.2.3": True, 462 "v6.2.5": True, 463 "v6.2.7": True, 464 "v6.0.11": True 465 } 466 }, 467 "database": { 468 "type": "string", 469 "options": [ 470 { 471 "value": "isdb", 472 "revisions": { 473 "v6.0.0": True, 474 "v7.0.0": True, 475 "v6.0.5": True, 476 "v6.4.4": True, 477 "v6.4.0": True, 478 "v6.4.1": True, 479 "v6.2.0": True, 480 "v6.2.3": True, 481 "v6.2.5": True, 482 "v6.2.7": True, 483 "v6.0.11": True 484 } 485 }, 486 { 487 "value": "irdb", 488 "revisions": { 489 "v6.0.0": True, 490 "v7.0.0": True, 491 "v6.0.5": True, 492 "v6.4.4": True, 493 "v6.4.0": True, 494 "v6.4.1": True, 495 "v6.2.0": True, 496 "v6.2.3": True, 497 "v6.2.5": True, 498 "v6.2.7": True, 499 "v6.0.11": True 500 } 501 } 502 ], 503 "revisions": { 504 "v6.0.0": True, 505 "v7.0.0": True, 506 "v6.0.5": True, 507 "v6.4.4": True, 508 "v6.4.0": True, 509 "v6.4.1": True, 510 "v6.2.0": True, 511 "v6.2.3": True, 512 "v6.2.5": True, 513 "v6.2.7": True, 514 "v6.0.11": True 515 } 516 }, 517 "extra_ip_range_number": { 518 "type": "integer", 519 "revisions": { 520 "v7.0.0": True, 521 "v6.4.4": True, 522 "v6.4.0": True, 523 "v6.4.1": True, 524 "v6.2.0": True, 525 "v6.2.3": True, 526 "v6.2.5": True, 527 "v6.2.7": True 528 } 529 }, 530 "icon_id": { 531 "type": "integer", 532 "revisions": { 533 "v6.0.0": True, 534 "v7.0.0": True, 535 "v6.0.5": True, 536 "v6.4.4": True, 537 "v6.4.0": True, 538 "v6.4.1": True, 539 "v6.2.0": True, 540 "v6.2.3": True, 541 "v6.2.5": True, 542 "v6.2.7": True, 543 "v6.0.11": True 544 } 545 }, 546 "sld_id": { 547 "type": "integer", 548 "revisions": { 549 "v6.0.0": True, 550 "v7.0.0": False, 551 "v6.0.5": True, 552 "v6.4.4": False, 553 "v6.4.0": False, 554 "v6.4.1": False, 555 "v6.2.0": True, 556 "v6.2.3": True, 557 "v6.2.5": True, 558 "v6.2.7": True, 559 "v6.0.11": True 560 } 561 }, 562 "obsolete": { 563 "type": "integer", 564 "revisions": { 565 "v7.0.0": True, 566 "v6.4.4": True, 567 "v6.4.0": True, 568 "v6.4.1": True, 569 "v6.2.0": True, 570 "v6.2.3": True, 571 "v6.2.5": True, 572 "v6.2.7": True 573 } 574 }, 575 "reputation": { 576 "type": "integer", 577 "revisions": { 578 "v6.0.0": True, 579 "v7.0.0": False, 580 "v6.0.5": True, 581 "v6.4.4": False, 582 "v6.4.0": False, 583 "v6.4.1": False, 584 "v6.2.0": True, 585 "v6.2.3": True, 586 "v6.2.5": True, 587 "v6.2.7": True, 588 "v6.0.11": True 589 } 590 }, 591 "ip_number": { 592 "type": "integer", 593 "revisions": { 594 "v7.0.0": True, 595 "v6.4.4": True, 596 "v6.4.0": True, 597 "v6.4.1": True, 598 "v6.2.0": True, 599 "v6.2.3": True, 600 "v6.2.5": True, 601 "v6.2.7": True 602 } 603 }, 604 "offset": { 605 "type": "integer", 606 "revisions": { 607 "v6.0.0": True, 608 "v7.0.0": False, 609 "v6.0.5": True, 610 "v6.4.4": False, 611 "v6.4.0": False, 612 "v6.4.1": False, 613 "v6.2.0": False, 614 "v6.2.3": False, 615 "v6.2.5": False, 616 "v6.2.7": False, 617 "v6.0.11": True 618 } 619 }, 620 "entry": { 621 "type": "list", 622 "children": { 623 "protocol": { 624 "type": "integer", 625 "revisions": { 626 "v6.0.11": True, 627 "v6.0.0": True, 628 "v6.0.5": True 629 } 630 }, 631 "ip_range_number": { 632 "type": "integer", 633 "revisions": { 634 "v6.0.11": True, 635 "v6.0.0": True, 636 "v6.0.5": True 637 } 638 }, 639 "id": { 640 "type": "integer", 641 "revisions": { 642 "v6.0.11": True, 643 "v6.0.0": True, 644 "v6.0.5": True 645 } 646 }, 647 "ip_number": { 648 "type": "integer", 649 "revisions": { 650 "v6.0.11": True, 651 "v6.0.0": True, 652 "v6.0.5": True 653 } 654 }, 655 "port": { 656 "type": "integer", 657 "revisions": { 658 "v6.0.11": True, 659 "v6.0.0": True, 660 "v6.0.5": True 661 } 662 } 663 }, 664 "revisions": { 665 "v6.0.0": True, 666 "v7.0.0": False, 667 "v6.0.5": True, 668 "v6.4.4": False, 669 "v6.4.0": False, 670 "v6.4.1": False, 671 "v6.2.0": False, 672 "v6.2.3": False, 673 "v6.2.5": False, 674 "v6.2.7": False, 675 "v6.0.11": True 676 } 677 }, 678 "singularity": { 679 "type": "integer", 680 "revisions": { 681 "v7.0.0": True, 682 "v6.4.4": True, 683 "v6.4.0": True, 684 "v6.4.1": True, 685 "v6.2.0": True, 686 "v6.2.3": True, 687 "v6.2.5": True, 688 "v6.2.7": True 689 } 690 }, 691 "ip_range_number": { 692 "type": "integer", 693 "revisions": { 694 "v7.0.0": True, 695 "v6.4.4": True, 696 "v6.4.0": True, 697 "v6.4.1": True, 698 "v6.2.0": True, 699 "v6.2.3": True, 700 "v6.2.5": True, 701 "v6.2.7": True 702 } 703 }, 704 "id": { 705 "type": "integer", 706 "revisions": { 707 "v6.0.0": True, 708 "v7.0.0": True, 709 "v6.0.5": True, 710 "v6.4.4": True, 711 "v6.4.0": True, 712 "v6.4.1": True, 713 "v6.2.0": True, 714 "v6.2.3": True, 715 "v6.2.5": True, 716 "v6.2.7": True, 717 "v6.0.11": True 718 } 719 } 720 }, 721 "revisions": { 722 "v6.0.0": True, 723 "v7.0.0": True, 724 "v6.0.5": True, 725 "v6.4.4": True, 726 "v6.4.0": True, 727 "v6.4.1": True, 728 "v6.2.0": True, 729 "v6.2.3": True, 730 "v6.2.5": True, 731 "v6.2.7": True, 732 "v6.0.11": True 733 } 734} 735 736 737def main(): 738 module_spec = schema_to_module_spec(versioned_schema) 739 mkeyname = 'id' 740 fields = { 741 "access_token": {"required": False, "type": "str", "no_log": True}, 742 "enable_log": {"required": False, "type": bool}, 743 "vdom": {"required": False, "type": "str", "default": "root"}, 744 "state": {"required": True, "type": "str", 745 "choices": ["present", "absent"]}, 746 "firewall_internet_service": { 747 "required": False, "type": "dict", "default": None, 748 "options": { 749 } 750 } 751 } 752 for attribute_name in module_spec['options']: 753 fields["firewall_internet_service"]['options'][attribute_name] = module_spec['options'][attribute_name] 754 if mkeyname and mkeyname == attribute_name: 755 fields["firewall_internet_service"]['options'][attribute_name]['required'] = True 756 757 check_legacy_fortiosapi() 758 module = AnsibleModule(argument_spec=fields, 759 supports_check_mode=True) 760 761 versions_check_result = None 762 if module._socket_path: 763 connection = Connection(module._socket_path) 764 if 'access_token' in module.params: 765 connection.set_option('access_token', module.params['access_token']) 766 767 if 'enable_log' in module.params: 768 connection.set_option('enable_log', module.params['enable_log']) 769 else: 770 connection.set_option('enable_log', False) 771 fos = FortiOSHandler(connection, module, mkeyname) 772 versions_check_result = check_schema_versioning(fos, versioned_schema, "firewall_internet_service") 773 774 is_error, has_changed, result = fortios_firewall(module.params, fos, module.check_mode) 775 776 else: 777 module.fail_json(**FAIL_SOCKET_MSG) 778 779 if versions_check_result and versions_check_result['matched'] is False: 780 module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv") 781 782 if not is_error: 783 if versions_check_result and versions_check_result['matched'] is False: 784 module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result) 785 else: 786 module.exit_json(changed=has_changed, meta=result) 787 else: 788 if versions_check_result and versions_check_result['matched'] is False: 789 module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result) 790 else: 791 module.fail_json(msg="Error in repo", meta=result) 792 793 794if __name__ == '__main__': 795 main() 796