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