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