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