1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3# 4# Copyright: (c) 2019, F5 Networks Inc. 5# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11ANSIBLE_METADATA = {'metadata_version': '1.1', 12 'status': ['preview'], 13 'supported_by': 'certified'} 14 15DOCUMENTATION = r''' 16--- 17module: bigip_asm_dos_application 18short_description: Manage application settings for DOS profile 19description: 20 - Manages Application settings for ASM/AFM DOS profile. 21version_added: 2.9 22options: 23 profile: 24 description: 25 - Specifies the name of the profile to manage application settings in. 26 type: str 27 required: True 28 rtbh_duration: 29 description: 30 - Specifies the duration of the RTBH BGP route advertisement, in seconds. 31 - The accepted range is between 0 and 4294967295 inclusive. 32 type: int 33 rtbh_enable: 34 description: 35 - Specifies whether to enable Remote Triggered Black Hole C(RTBH) of attacking IPs by advertising BGP routes. 36 type: bool 37 scrubbing_duration: 38 description: 39 - Specifies the duration of the Traffic Scrubbing BGP route advertisement, in seconds. 40 - The accepted range is between 0 and 4294967295 inclusive. 41 type: int 42 scrubbing_enable: 43 description: 44 - Specifies whether to enable Traffic Scrubbing during attacks by advertising BGP routes. 45 type: bool 46 single_page_application: 47 description: 48 - Specifies, when C(yes), that the system supports a Single Page Applications. 49 type: bool 50 trigger_irule: 51 description: 52 - Specifies, when C(yes), that the system activates an Application DoS iRule event. 53 type: bool 54 geolocations: 55 description: 56 - Manages the geolocations countries whitelist, blacklist. 57 type: dict 58 suboptions: 59 whitelist: 60 description: 61 - A list of countries to be put on whitelist, must not have overlapping elements with C(blacklist). 62 type: list 63 blacklist: 64 description: 65 - A list of countries to be put on blacklist, must not have overlapping elements with C(whitelist). 66 type: list 67 heavy_urls: 68 description: 69 - Manages Heavy URL protection. 70 - Heavy URLs are a small number of site URLs that might consume considerable server resources per request. 71 type: dict 72 suboptions: 73 auto_detect: 74 description: 75 - Enables or disables automatic heavy URL detection. 76 type: bool 77 latency_threshold: 78 description: 79 - Specifies the latency threshold for automatic heavy URL detection. 80 - The accepted range is between 0 and 4294967295 milliseconds inclusive. 81 type: int 82 exclude: 83 description: 84 - Specifies a list of URLs or wildcards to exclude from the heavy URLs. 85 type: list 86 include: 87 description: 88 - Configures additional URLs to include in the heavy URLs that were auto detected. 89 type: list 90 suboptions: 91 url: 92 description: 93 - Specifies the URL to be added to the list of heavy URLs, in addition to the automatically detected ones. 94 type: str 95 threshold: 96 description: 97 - Specifies the threshold of requests per second, where the URL in question is considered under attack. 98 - The accepted range is between 1 and 4294967295 inclusive, or C(auto). 99 type: str 100 mobile_detection: 101 description: 102 - Configures detection of mobile applications built with the Anti-Bot Mobile SDK and defines how requests 103 from these mobile application clients are handled. 104 type: dict 105 suboptions: 106 enabled: 107 description: 108 - When C(yes), requests from mobile applications built with Anti-Bot Mobile SDK will be detected and handled 109 according to the parameters set. 110 - When C(no), these requests will be handled like any other request which may let attacks in, or cause false 111 positives. 112 type: bool 113 allow_android_rooted_device: 114 description: 115 - When C(yes) device will allow traffic from rooted Android devices. 116 type: bool 117 allow_any_android_package: 118 description: 119 - When C(yes) allows any application publisher. 120 - A publisher is identified by the certificate used to sign the application. 121 type: bool 122 allow_any_ios_package: 123 description: 124 - When C(yes) allows any iOS package. 125 - A package name is the unique identifier of the mobile application. 126 type: bool 127 allow_jailbroken_devices: 128 description: 129 - When C(yes) allows traffic from jailbroken iOS devices. 130 type: bool 131 allow_emulators: 132 description: 133 - When C(yes) allows traffic from applications run on emulators. 134 type: bool 135 client_side_challenge_mode: 136 description: 137 - Action to take when a CAPTCHA or Client Side Integrity challenge needs to be presented. 138 - The mobile application user will not see a CAPTCHA challenge and the mobile application will not be 139 presented with the Client Side Integrity challenge. The such options for mobile applications are C(pass) 140 or C(cshui). 141 - When C(pass) the traffic is passed without incident. 142 - When C(cshui) the SDK checks for human interactions with the screen in the last few seconds. 143 If none are detected, the traffic is blocked. 144 type: str 145 choices: 146 - pass 147 - cshui 148 ios_allowed_package_names: 149 description: 150 - Specifies the names of iOS packages to allow traffic on. 151 - This option has no effect when C(allow_any_ios_package) is set to C(yes). 152 type: list 153 android_publishers: 154 description: 155 - This option has no effect when C(allow_any_android_package) is set to C(yes). 156 - Specifies the allowed publisher certificates for android applications. 157 - The publisher certificate needs to be installed on the BIG-IP beforehand. 158 - "The certificate name located on a different partition than the one specified 159 in C(partition) parameter needs to be provided in C(full_path) format C(/Foo/cert.crt)." 160 type: list 161 partition: 162 description: 163 - Device partition to manage resources on. 164 type: str 165 default: Common 166 state: 167 description: 168 - When C(state) is C(present), ensures that the Application object exists. 169 - When C(state) is C(absent), ensures that the Application object is removed. 170 type: str 171 choices: 172 - present 173 - absent 174 default: present 175notes: 176 - Requires BIG-IP >= 13.1.0 177extends_documentation_fragment: f5 178author: 179 - Wojciech Wypior (@wojtek0806) 180''' 181 182EXAMPLES = r''' 183- name: Create an ASM dos application profile 184 bigip_asm_dos_application: 185 profile: dos_foo 186 geolocations: 187 blacklist: 188 - Afghanistan 189 - Andora 190 whitelist: 191 - Cuba 192 heavy_urls: 193 auto_detect: yes 194 latency_threshold: 1000 195 rtbh_duration: 3600 196 rtbh_enable: yes 197 single_page_application: yes 198 provider: 199 password: secret 200 server: lb.mydomain.com 201 user: admin 202 delegate_to: localhost 203 204- name: Update an ASM dos application profile 205 bigip_asm_dos_application: 206 profile: dos_foo 207 mobile_detection: 208 enabled: yes 209 allow_any_ios_package: yes 210 allow_emulators: yes 211 provider: 212 password: secret 213 server: lb.mydomain.com 214 user: admin 215 delegate_to: localhost 216 217- name: Remove an ASM dos application profile 218 bigip_asm_dos_application: 219 profile: dos_foo 220 state: absent 221 provider: 222 password: secret 223 server: lb.mydomain.com 224 user: admin 225 delegate_to: localhost 226''' 227 228RETURN = r''' 229rtbh_enable: 230 description: Enables Remote Triggered Black Hole of attacking IPs. 231 returned: changed 232 type: bool 233 sample: no 234rtbh_duration: 235 description: The duration of the RTBH BGP route advertisement. 236 returned: changed 237 type: int 238 sample: 3600 239scrubbing_enable: 240 description: Enables Traffic Scrubbing during attacks. 241 returned: changed 242 type: bool 243 sample: yes 244scrubbing_duration: 245 description: The duration of the Traffic Scrubbing BGP route advertisement. 246 returned: changed 247 type: int 248 sample: 3600 249single_page_application: 250 description: Enables support of a Single Page Applications. 251 returned: changed 252 type: bool 253 sample: no 254trigger_irule: 255 description: Activates an Application DoS iRule event. 256 returned: changed 257 type: bool 258 sample: yes 259geolocations: 260 description: Specifies geolocations countries whitelist, blacklist. 261 type: complex 262 returned: changed 263 contains: 264 whitelist: 265 description: A list of countries to be put on whitelist. 266 returned: changed 267 type: list 268 sample: ['United States, United Kingdom'] 269 blacklist: 270 description: A list of countries to be put on blacklist. 271 returned: changed 272 type: list 273 sample: ['Russia', 'Germany'] 274 sample: hash/dictionary of values 275heavy_urls: 276 description: Manages Heavy URL protection. 277 type: complex 278 returned: changed 279 contains: 280 auto_detect: 281 description: Enables or disables automatic heavy URL detection. 282 returned: changed 283 type: bool 284 sample: yes 285 latency_threshold: 286 description: Specifies the latency threshold for automatic heavy URL detection. 287 returned: changed 288 type: int 289 sample: 2000 290 exclude: 291 description: Specifies a list of URLs or wildcards to exclude from the heavy URLs. 292 returned: changed 293 type: list 294 sample: ['/exclude.html', '/exclude2.html'] 295 include: 296 description: Configures additional URLs to include in the heavy URLs. 297 type: complex 298 returned: changed 299 contains: 300 url: 301 description: The URL to be added to the list of heavy URLs. 302 returned: changed 303 type: str 304 sample: /include.html 305 threshold: 306 description: The threshold of requests per second 307 returned: changed 308 type: str 309 sample: auto 310 sample: hash/dictionary of values 311 sample: hash/dictionary of values 312mobile_detection: 313 description: Configures detection of mobile applications built with the Anti-Bot Mobile SDK. 314 type: complex 315 returned: changed 316 contains: 317 enable: 318 description: Enables or disables automatic mobile detection. 319 returned: changed 320 type: bool 321 sample: yes 322 allow_android_rooted_device: 323 description: Allows traffic from rooted Android devices. 324 returned: changed 325 type: bool 326 sample: no 327 allow_any_android_package: 328 description: Allows any application publisher. 329 returned: changed 330 type: bool 331 sample: no 332 allow_any_ios_package: 333 description: Allows any iOS package. 334 returned: changed 335 type: bool 336 sample: yes 337 allow_jailbroken_devices: 338 description: Allows traffic from jailbroken iOS devices. 339 returned: changed 340 type: bool 341 sample: no 342 allow_emulators: 343 description: Allows traffic from applications run on emulators. 344 returned: changed 345 type: bool 346 sample: yes 347 client_side_challenge_mode: 348 description: Action to take when a CAPTCHA or Client Side Integrity challenge needs to be presented. 349 returned: changed 350 type: str 351 sample: pass 352 ios_allowed_package_names: 353 description: The names of iOS packages to allow traffic on. 354 returned: changed 355 type: list 356 sample: ['package1','package2'] 357 android_publishers: 358 description: The allowed publisher certificates for android applications. 359 returned: changed 360 type: list 361 sample: ['/Common/cert1.crt', '/Common/cert2.crt'] 362 sample: hash/dictionary of values 363''' 364from ansible.module_utils.basic import AnsibleModule 365from ansible.module_utils.basic import env_fallback 366from distutils.version import LooseVersion 367 368try: 369 from library.module_utils.network.f5.bigip import F5RestClient 370 from library.module_utils.network.f5.common import F5ModuleError 371 from library.module_utils.network.f5.common import AnsibleF5Parameters 372 from library.module_utils.network.f5.common import fq_name 373 from library.module_utils.network.f5.common import transform_name 374 from library.module_utils.network.f5.common import flatten_boolean 375 from library.module_utils.network.f5.common import f5_argument_spec 376 from library.module_utils.network.f5.compare import compare_complex_list 377 from library.module_utils.network.f5.compare import cmp_simple_list 378 from library.module_utils.network.f5.icontrol import tmos_version 379 from library.module_utils.network.f5.icontrol import module_provisioned 380except ImportError: 381 from ansible.module_utils.network.f5.bigip import F5RestClient 382 from ansible.module_utils.network.f5.common import F5ModuleError 383 from ansible.module_utils.network.f5.common import AnsibleF5Parameters 384 from ansible.module_utils.network.f5.common import fq_name 385 from ansible.module_utils.network.f5.common import transform_name 386 from ansible.module_utils.network.f5.common import flatten_boolean 387 from ansible.module_utils.network.f5.common import f5_argument_spec 388 from ansible.module_utils.network.f5.compare import compare_complex_list 389 from ansible.module_utils.network.f5.compare import cmp_simple_list 390 from ansible.module_utils.network.f5.icontrol import tmos_version 391 from ansible.module_utils.network.f5.icontrol import module_provisioned 392 393 394class Parameters(AnsibleF5Parameters): 395 api_map = { 396 'rtbhDurationSec': 'rtbh_duration', 397 'rtbhEnable': 'rtbh_enable', 398 'scrubbingDurationSec': 'scrubbing_duration', 399 'scrubbingEnable': 'scrubbing_enable', 400 'singlePageApplication': 'single_page_application', 401 'triggerIrule': 'trigger_irule', 402 'heavyUrls': 'heavy_urls', 403 'mobileDetection': 'mobile_detection', 404 } 405 406 api_attributes = [ 407 'geolocations', 408 'rtbhDurationSec', 409 'rtbhEnable', 410 'scrubbingDurationSec', 411 'scrubbingEnable', 412 'singlePageApplication', 413 'triggerIrule', 414 'heavyUrls', 415 'mobileDetection', 416 ] 417 418 returnables = [ 419 'rtbh_duration', 420 'rtbh_enable', 421 'scrubbing_duration', 422 'scrubbing_enable', 423 'single_page_application', 424 'trigger_irule', 425 'enable_mobile_detection', 426 'allow_android_rooted_device', 427 'allow_any_android_package', 428 'allow_any_ios_package', 429 'allow_jailbroken_devices', 430 'allow_emulators', 431 'client_side_challenge_mode', 432 'ios_allowed_package_names', 433 'android_publishers', 434 'auto_detect', 435 'latency_threshold', 436 'hw_url_exclude', 437 'hw_url_include', 438 'geo_blacklist', 439 'geo_whitelist', 440 ] 441 442 updatables = [ 443 'rtbh_duration', 444 'rtbh_enable', 445 'scrubbing_duration', 446 'scrubbing_enable', 447 'single_page_application', 448 'trigger_irule', 449 'enable_mobile_detection', 450 'allow_android_rooted_device', 451 'allow_any_android_package', 452 'allow_any_ios_package', 453 'allow_jailbroken_devices', 454 'allow_emulators', 455 'client_side_challenge_mode', 456 'ios_allowed_package_names', 457 'android_publishers', 458 'auto_detect', 459 'latency_threshold', 460 'hw_url_exclude', 461 'hw_url_include', 462 'geo_blacklist', 463 'geo_whitelist', 464 ] 465 466 467class ApiParameters(Parameters): 468 @property 469 def enable_mobile_detection(self): 470 if self._values['mobile_detection'] is None: 471 return None 472 return self._values['mobile_detection']['enabled'] 473 474 @property 475 def allow_android_rooted_device(self): 476 if self._values['mobile_detection'] is None: 477 return None 478 return self._values['mobile_detection']['allowAndroidRootedDevice'] 479 480 @property 481 def allow_any_android_package(self): 482 if self._values['mobile_detection'] is None: 483 return None 484 return self._values['mobile_detection']['allowAnyAndroidPackage'] 485 486 @property 487 def allow_any_ios_package(self): 488 if self._values['mobile_detection'] is None: 489 return None 490 return self._values['mobile_detection']['allowAnyIosPackage'] 491 492 @property 493 def allow_jailbroken_devices(self): 494 if self._values['mobile_detection'] is None: 495 return None 496 return self._values['mobile_detection']['allowJailbrokenDevices'] 497 498 @property 499 def allow_emulators(self): 500 if self._values['mobile_detection'] is None: 501 return None 502 return self._values['mobile_detection']['allowEmulators'] 503 504 @property 505 def client_side_challenge_mode(self): 506 if self._values['mobile_detection'] is None: 507 return None 508 return self._values['mobile_detection']['clientSideChallengeMode'] 509 510 @property 511 def ios_allowed_package_names(self): 512 if self._values['mobile_detection'] is None: 513 return None 514 return self._values['mobile_detection'].get('iosAllowedPackageNames', None) 515 516 @property 517 def android_publishers(self): 518 if self._values['mobile_detection'] is None or 'androidPublishers' not in self._values['mobile_detection']: 519 return None 520 result = [fq_name(publisher['partition'], publisher['name']) 521 for publisher in self._values['mobile_detection']['androidPublishers']] 522 return result 523 524 @property 525 def auto_detect(self): 526 if self._values['heavy_urls'] is None: 527 return None 528 return self._values['heavy_urls']['automaticDetection'] 529 530 @property 531 def latency_threshold(self): 532 if self._values['heavy_urls'] is None: 533 return None 534 return self._values['heavy_urls']['latencyThreshold'] 535 536 @property 537 def hw_url_exclude(self): 538 if self._values['heavy_urls'] is None: 539 return None 540 return self._values['heavy_urls'].get('exclude', None) 541 542 @property 543 def hw_url_include(self): 544 if self._values['heavy_urls'] is None: 545 return None 546 return self._values['heavy_urls'].get('includeList', None) 547 548 @property 549 def geo_blacklist(self): 550 if self._values['geolocations'] is None: 551 return None 552 result = list() 553 for item in self._values['geolocations']: 554 if 'blackListed' in item and item['blackListed'] is True: 555 result.append(item['name']) 556 if result: 557 return result 558 559 @property 560 def geo_whitelist(self): 561 if self._values['geolocations'] is None: 562 return None 563 result = list() 564 for item in self._values['geolocations']: 565 if 'whiteListed' in item and item['whiteListed'] is True: 566 result.append(item['name']) 567 if result: 568 return result 569 570 571class ModuleParameters(Parameters): 572 @property 573 def rtbh_duration(self): 574 if self._values['rtbh_duration'] is None: 575 return None 576 if 0 <= self._values['rtbh_duration'] <= 4294967295: 577 return self._values['rtbh_duration'] 578 raise F5ModuleError( 579 "Valid 'rtbh_duration' must be in range 0 - 4294967295 seconds." 580 ) 581 582 @property 583 def rtbh_enable(self): 584 result = flatten_boolean(self._values['rtbh_enable']) 585 if result == 'yes': 586 return 'enabled' 587 if result == 'no': 588 return 'disabled' 589 return result 590 591 @property 592 def scrubbing_duration(self): 593 if self._values['scrubbing_duration'] is None: 594 return None 595 if 0 <= self._values['scrubbing_duration'] <= 4294967295: 596 return self._values['scrubbing_duration'] 597 raise F5ModuleError( 598 "Valid 'scrubbing_duration' must be in range 0 - 4294967295 seconds." 599 ) 600 601 @property 602 def scrubbing_enable(self): 603 result = flatten_boolean(self._values['scrubbing_enable']) 604 if result == 'yes': 605 return 'enabled' 606 if result == 'no': 607 return 'disabled' 608 return result 609 610 @property 611 def single_page_application(self): 612 result = flatten_boolean(self._values['single_page_application']) 613 if result == 'yes': 614 return 'enabled' 615 if result == 'no': 616 return 'disabled' 617 return result 618 619 @property 620 def trigger_irule(self): 621 result = flatten_boolean(self._values['trigger_irule']) 622 if result == 'yes': 623 return 'enabled' 624 if result == 'no': 625 return 'disabled' 626 return result 627 628 @property 629 def enable_mobile_detection(self): 630 if self._values['mobile_detection'] is None: 631 return None 632 result = flatten_boolean(self._values['mobile_detection']['enabled']) 633 if result == 'yes': 634 return 'enabled' 635 if result == 'no': 636 return 'disabled' 637 return result 638 639 @property 640 def allow_android_rooted_device(self): 641 if self._values['mobile_detection'] is None: 642 return None 643 result = flatten_boolean(self._values['mobile_detection']['allow_android_rooted_device']) 644 if result == 'yes': 645 return 'true' 646 if result == 'no': 647 return 'false' 648 return result 649 650 @property 651 def allow_any_android_package(self): 652 if self._values['mobile_detection'] is None: 653 return None 654 result = flatten_boolean(self._values['mobile_detection']['allow_any_android_package']) 655 if result == 'yes': 656 return 'true' 657 if result == 'no': 658 return 'false' 659 return result 660 661 @property 662 def allow_any_ios_package(self): 663 if self._values['mobile_detection'] is None: 664 return None 665 result = flatten_boolean(self._values['mobile_detection']['allow_any_ios_package']) 666 if result == 'yes': 667 return 'true' 668 if result == 'no': 669 return 'false' 670 return result 671 672 @property 673 def allow_jailbroken_devices(self): 674 if self._values['mobile_detection'] is None: 675 return None 676 result = flatten_boolean(self._values['mobile_detection']['allow_jailbroken_devices']) 677 if result == 'yes': 678 return 'true' 679 if result == 'no': 680 return 'false' 681 return result 682 683 @property 684 def allow_emulators(self): 685 if self._values['mobile_detection'] is None: 686 return None 687 result = flatten_boolean(self._values['mobile_detection']['allow_emulators']) 688 if result == 'yes': 689 return 'true' 690 if result == 'no': 691 return 'false' 692 return result 693 694 @property 695 def client_side_challenge_mode(self): 696 if self._values['mobile_detection'] is None: 697 return None 698 return self._values['mobile_detection']['client_side_challenge_mode'] 699 700 @property 701 def ios_allowed_package_names(self): 702 if self._values['mobile_detection'] is None: 703 return None 704 return self._values['mobile_detection']['ios_allowed_package_names'] 705 706 @property 707 def android_publishers(self): 708 if self._values['mobile_detection'] is None or self._values['mobile_detection']['android_publishers'] is None: 709 return None 710 result = [fq_name(self.partition, item) for item in self._values['mobile_detection']['android_publishers']] 711 return result 712 713 @property 714 def auto_detect(self): 715 if self._values['heavy_urls'] is None: 716 return None 717 result = flatten_boolean(self._values['heavy_urls']['auto_detect']) 718 if result == 'yes': 719 return 'enabled' 720 if result == 'no': 721 return 'disabled' 722 return result 723 724 @property 725 def latency_threshold(self): 726 if self._values['heavy_urls'] is None or self._values['heavy_urls']['latency_threshold'] is None: 727 return None 728 if 0 <= self._values['heavy_urls']['latency_threshold'] <= 4294967295: 729 return self._values['heavy_urls']['latency_threshold'] 730 raise F5ModuleError( 731 "Valid 'latency_threshold' must be in range 0 - 4294967295 milliseconds." 732 ) 733 734 @property 735 def hw_url_exclude(self): 736 if self._values['heavy_urls'] is None: 737 return None 738 return self._values['heavy_urls']['exclude'] 739 740 @property 741 def hw_url_include(self): 742 if self._values['heavy_urls'] is None or self._values['heavy_urls']['include'] is None: 743 return None 744 result = list() 745 for item in self._values['heavy_urls']['include']: 746 element = dict() 747 element['url'] = self._correct_url(item['url']) 748 element['name'] = 'URL{0}'.format(self._correct_url(item['url'])) 749 if 'threshold' in item: 750 element['threshold'] = self._validate_threshold(item['threshold']) 751 result.append(element) 752 return result 753 754 def _validate_threshold(self, item): 755 if item == 'auto': 756 return item 757 if 1 <= int(item) <= 4294967295: 758 return item 759 raise F5ModuleError( 760 "Valid 'url threshold' must be in range 1 - 4294967295 requests per second or 'auto'." 761 ) 762 763 def _correct_url(self, item): 764 if item.startswith('/'): 765 return item 766 return "/{0}".format(item) 767 768 @property 769 def geo_blacklist(self): 770 if self._values['geolocations'] is None: 771 return None 772 whitelist = self.geo_whitelist 773 blacklist = self._values['geolocations']['blacklist'] 774 if whitelist and blacklist: 775 if not set(whitelist).isdisjoint(set(blacklist)): 776 raise F5ModuleError('Cannot specify the same element in blacklist and whitelist.') 777 return blacklist 778 779 @property 780 def geo_whitelist(self): 781 if self._values['geolocations'] is None: 782 return None 783 return self._values['geolocations']['whitelist'] 784 785 786class Changes(Parameters): 787 def to_return(self): 788 result = {} 789 try: 790 for returnable in self.returnables: 791 result[returnable] = getattr(self, returnable) 792 result = self._filter_params(result) 793 except Exception: 794 pass 795 return result 796 797 798class UsableChanges(Changes): 799 @property 800 def geolocations(self): 801 if self._values['geo_blacklist'] is None and self._values['geo_whitelist'] is None: 802 return None 803 result = list() 804 if self._values['geo_blacklist']: 805 for item in self._values['geo_blacklist']: 806 element = dict() 807 element['name'] = item 808 element['blackListed'] = True 809 result.append(element) 810 if self._values['geo_whitelist']: 811 for item in self._values['geo_whitelist']: 812 element = dict() 813 element['name'] = item 814 element['whiteListed'] = True 815 result.append(element) 816 if result: 817 return result 818 819 @property 820 def heavy_urls(self): 821 tmp = dict() 822 tmp['automaticDetection'] = self._values['auto_detect'] 823 tmp['latencyThreshold'] = self._values['latency_threshold'] 824 tmp['exclude'] = self._values['hw_url_exclude'] 825 tmp['includeList'] = self._values['hw_url_include'] 826 result = self._filter_params(tmp) 827 if result: 828 return result 829 830 @property 831 def mobile_detection(self): 832 tmp = dict() 833 tmp['enabled'] = self._values['enable_mobile_detection'] 834 tmp['allowAndroidRootedDevice'] = self._values['allow_android_rooted_device'] 835 tmp['allowAnyAndroidPackage'] = self._values['allow_any_android_package'] 836 tmp['allowAnyIosPackage'] = self._values['allow_any_ios_package'] 837 tmp['allowJailbrokenDevices'] = self._values['allow_jailbroken_devices'] 838 tmp['allowEmulators'] = self._values['allow_emulators'] 839 tmp['clientSideChallengeMode'] = self._values['client_side_challenge_mode'] 840 tmp['iosAllowedPackageNames'] = self._values['ios_allowed_package_names'] 841 tmp['androidPublishers'] = self._values['android_publishers'] 842 result = self._filter_params(tmp) 843 if result: 844 return result 845 846 847class ReportableChanges(Changes): 848 returnables = [ 849 'rtbh_duration', 850 'rtbh_enable', 851 'scrubbing_duration', 852 'scrubbing_enable', 853 'single_page_application', 854 'trigger_irule', 855 'heavy_urls', 856 'mobile_detection', 857 'geolocations', 858 ] 859 860 def _convert_include_list(self, items): 861 result = list() 862 for item in items: 863 element = dict() 864 element['url'] = item['url'] 865 if 'threshold' in item: 866 element['threshold'] = item['threshold'] 867 result.append(element) 868 if result: 869 return result 870 871 @property 872 def geolocations(self): 873 tmp = dict() 874 tmp['blacklist'] = self._values['geo_blacklist'] 875 tmp['whitelist'] = self._values['geo_whitelist'] 876 result = self._filter_params(tmp) 877 if result: 878 return result 879 880 @property 881 def heavy_urls(self): 882 tmp = dict() 883 tmp['auto_detect'] = flatten_boolean(self._values['auto_detect']) 884 tmp['latency_threshold'] = self._values['latency_threshold'] 885 tmp['exclude'] = self._values['hw_url_exclude'] 886 tmp['include'] = self._convert_include_list(self._values['hw_url_include']) 887 result = self._filter_params(tmp) 888 if result: 889 return result 890 891 @property 892 def mobile_detection(self): 893 tmp = dict() 894 tmp['enabled'] = flatten_boolean(self._values['enable_mobile_detection']) 895 tmp['allow_android_rooted_device'] = flatten_boolean(self._values['allow_android_rooted_device']) 896 tmp['allow_any_android_package'] = flatten_boolean(self._values['allow_any_android_package']) 897 tmp['allow_any_ios_package'] = flatten_boolean(self._values['allow_any_ios_package']) 898 tmp['allow_jailbroken_devices'] = flatten_boolean(self._values['allow_jailbroken_devices']) 899 tmp['allow_emulators'] = flatten_boolean(self._values['allow_emulators']) 900 tmp['client_side_challenge_mode'] = self._values['client_side_challenge_mode'] 901 tmp['ios_allowed_package_names'] = self._values['ios_allowed_package_names'] 902 tmp['android_publishers'] = self._values['android_publishers'] 903 result = self._filter_params(tmp) 904 if result: 905 return result 906 907 @property 908 def rtbh_enable(self): 909 result = flatten_boolean(self._values['rtbh_enable']) 910 return result 911 912 @property 913 def scrubbing_enable(self): 914 result = flatten_boolean(self._values['scrubbing_enable']) 915 return result 916 917 @property 918 def single_page_application(self): 919 result = flatten_boolean(self._values['single_page_application']) 920 return result 921 922 @property 923 def trigger_irule(self): 924 result = flatten_boolean(self._values['trigger_irule']) 925 return result 926 927 928class Difference(object): 929 def __init__(self, want, have=None): 930 self.want = want 931 self.have = have 932 933 def compare(self, param): 934 try: 935 result = getattr(self, param) 936 return result 937 except AttributeError: 938 return self.__default(param) 939 940 def __default(self, param): 941 attr1 = getattr(self.want, param) 942 try: 943 attr2 = getattr(self.have, param) 944 if attr1 != attr2: 945 return attr1 946 except AttributeError: 947 return attr1 948 949 @property 950 def hw_url_include(self): 951 if self.want.hw_url_include is None: 952 return None 953 if self.have.hw_url_include is None and self.want.hw_url_include == []: 954 return None 955 if self.have.hw_url_include is None: 956 return self.want.hw_url_include 957 958 wants = self.want.hw_url_include 959 haves = list() 960 # First we remove extra keys in have for the same elements 961 for want in wants: 962 for have in self.have.hw_url_include: 963 if want['url'] == have['url']: 964 entry = self._filter_have(want, have) 965 haves.append(entry) 966 # Next we do compare the lists as normal 967 result = compare_complex_list(wants, haves) 968 return result 969 970 def _filter_have(self, want, have): 971 to_check = set(want.keys()).intersection(set(have.keys())) 972 result = dict() 973 for k in list(to_check): 974 result[k] = have[k] 975 return result 976 977 @property 978 def hw_url_exclude(self): 979 result = cmp_simple_list(self.want.hw_url_exclude, self.have.hw_url_exclude) 980 return result 981 982 @property 983 def geo_blacklist(self): 984 result = cmp_simple_list(self.want.geo_blacklist, self.have.geo_blacklist) 985 return result 986 987 @property 988 def geo_whitelist(self): 989 result = cmp_simple_list(self.want.geo_whitelist, self.have.geo_whitelist) 990 return result 991 992 @property 993 def android_publishers(self): 994 result = cmp_simple_list(self.want.android_publishers, self.have.android_publishers) 995 return result 996 997 @property 998 def ios_allowed_package_names(self): 999 result = cmp_simple_list(self.want.ios_allowed_package_names, self.have.ios_allowed_package_names) 1000 return result 1001 1002 1003class ModuleManager(object): 1004 def __init__(self, *args, **kwargs): 1005 self.module = kwargs.get('module', None) 1006 self.client = F5RestClient(**self.module.params) 1007 self.want = ModuleParameters(params=self.module.params) 1008 self.have = ApiParameters() 1009 self.changes = UsableChanges() 1010 1011 def _set_changed_options(self): 1012 changed = {} 1013 for key in Parameters.returnables: 1014 if getattr(self.want, key) is not None: 1015 changed[key] = getattr(self.want, key) 1016 if changed: 1017 self.changes = UsableChanges(params=changed) 1018 1019 def _update_changed_options(self): 1020 diff = Difference(self.want, self.have) 1021 updatables = Parameters.updatables 1022 changed = dict() 1023 for k in updatables: 1024 change = diff.compare(k) 1025 if change is None: 1026 continue 1027 else: 1028 if isinstance(change, dict): 1029 changed.update(change) 1030 else: 1031 changed[k] = change 1032 if changed: 1033 self.changes = UsableChanges(params=changed) 1034 return True 1035 return False 1036 1037 def _announce_deprecations(self, result): 1038 warnings = result.pop('__warnings', []) 1039 for warning in warnings: 1040 self.client.module.deprecate( 1041 msg=warning['msg'], 1042 version=warning['version'] 1043 ) 1044 1045 def exec_module(self): 1046 if not module_provisioned(self.client, 'asm'): 1047 raise F5ModuleError( 1048 "ASM must be provisioned to use this module." 1049 ) 1050 1051 if self.version_less_than_13_1(): 1052 raise F5ModuleError('Module supported on TMOS versions 13.1.x and above') 1053 1054 changed = False 1055 result = dict() 1056 state = self.want.state 1057 1058 if state == "present": 1059 changed = self.present() 1060 elif state == "absent": 1061 changed = self.absent() 1062 1063 reportable = ReportableChanges(params=self.changes.to_return()) 1064 changes = reportable.to_return() 1065 result.update(**changes) 1066 result.update(dict(changed=changed)) 1067 self._announce_deprecations(result) 1068 return result 1069 1070 def version_less_than_13_1(self): 1071 version = tmos_version(self.client) 1072 if LooseVersion(version) < LooseVersion('13.1.0'): 1073 return True 1074 return False 1075 1076 def present(self): 1077 if self.exists(): 1078 return self.update() 1079 else: 1080 return self.create() 1081 1082 def absent(self): 1083 if self.exists(): 1084 return self.remove() 1085 return False 1086 1087 def should_update(self): 1088 result = self._update_changed_options() 1089 if result: 1090 return True 1091 return False 1092 1093 def update(self): 1094 self.have = self.read_current_from_device() 1095 if not self.should_update(): 1096 return False 1097 if self.module.check_mode: 1098 return True 1099 self.update_on_device() 1100 return True 1101 1102 def remove(self): 1103 if self.module.check_mode: 1104 return True 1105 self.remove_from_device() 1106 if self.exists(): 1107 raise F5ModuleError("Failed to delete the resource.") 1108 return True 1109 1110 def create(self): 1111 self._set_changed_options() 1112 if self.module.check_mode: 1113 return True 1114 self.create_on_device() 1115 return True 1116 1117 def profile_exists(self): 1118 uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/".format( 1119 self.client.provider['server'], 1120 self.client.provider['server_port'], 1121 transform_name(self.want.partition, self.want.profile), 1122 ) 1123 resp = self.client.api.get(uri) 1124 try: 1125 response = resp.json() 1126 except ValueError: 1127 return False 1128 if resp.status == 404 or 'code' in response and response['code'] == 404: 1129 return False 1130 return True 1131 1132 def exists(self): 1133 if not self.profile_exists(): 1134 raise F5ModuleError( 1135 'Specified DOS profile: {0} on partition: {1} does not exist.'.format( 1136 self.want.profile, self.want.partition) 1137 ) 1138 uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/application/{3}".format( 1139 self.client.provider['server'], 1140 self.client.provider['server_port'], 1141 transform_name(self.want.partition, self.want.profile), 1142 self.want.profile 1143 ) 1144 resp = self.client.api.get(uri) 1145 try: 1146 response = resp.json() 1147 except ValueError: 1148 return False 1149 if resp.status == 404 or 'code' in response and response['code'] == 404: 1150 return False 1151 return True 1152 1153 def create_on_device(self): 1154 params = self.changes.api_params() 1155 params['name'] = self.want.profile 1156 uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/application/".format( 1157 self.client.provider['server'], 1158 self.client.provider['server_port'], 1159 transform_name(self.want.partition, self.want.profile), 1160 ) 1161 resp = self.client.api.post(uri, json=params) 1162 try: 1163 response = resp.json() 1164 except ValueError as ex: 1165 raise F5ModuleError(str(ex)) 1166 1167 if 'code' in response and response['code'] in [400, 409]: 1168 if 'message' in response: 1169 raise F5ModuleError(response['message']) 1170 else: 1171 raise F5ModuleError(resp.content) 1172 return True 1173 1174 def update_on_device(self): 1175 params = self.changes.api_params() 1176 uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/application/{3}".format( 1177 self.client.provider['server'], 1178 self.client.provider['server_port'], 1179 transform_name(self.want.partition, self.want.profile), 1180 self.want.profile 1181 ) 1182 resp = self.client.api.patch(uri, json=params) 1183 try: 1184 response = resp.json() 1185 except ValueError as ex: 1186 raise F5ModuleError(str(ex)) 1187 1188 if 'code' in response and response['code'] == 400: 1189 if 'message' in response: 1190 raise F5ModuleError(response['message']) 1191 else: 1192 raise F5ModuleError(resp.content) 1193 1194 def remove_from_device(self): 1195 uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/application/{3}".format( 1196 self.client.provider['server'], 1197 self.client.provider['server_port'], 1198 transform_name(self.want.partition, self.want.profile), 1199 self.want.profile 1200 ) 1201 response = self.client.api.delete(uri) 1202 if response.status == 200: 1203 return True 1204 raise F5ModuleError(response.content) 1205 1206 def read_current_from_device(self): 1207 uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/application/{3}".format( 1208 self.client.provider['server'], 1209 self.client.provider['server_port'], 1210 transform_name(self.want.partition, self.want.profile), 1211 self.want.profile 1212 ) 1213 resp = self.client.api.get(uri) 1214 try: 1215 response = resp.json() 1216 except ValueError as ex: 1217 raise F5ModuleError(str(ex)) 1218 1219 if 'code' in response and response['code'] == 400: 1220 if 'message' in response: 1221 raise F5ModuleError(response['message']) 1222 else: 1223 raise F5ModuleError(resp.content) 1224 return ApiParameters(params=response) 1225 1226 1227class ArgumentSpec(object): 1228 def __init__(self): 1229 self.supports_check_mode = True 1230 argument_spec = dict( 1231 profile=dict( 1232 required=True, 1233 ), 1234 geolocations=dict( 1235 type='dict', 1236 options=dict( 1237 blacklist=dict(type='list'), 1238 whitelist=dict(type='list'), 1239 ), 1240 ), 1241 heavy_urls=dict( 1242 type='dict', 1243 options=dict( 1244 auto_detect=dict(type='bool'), 1245 latency_threshold=dict(type='int'), 1246 exclude=dict(type='list'), 1247 include=dict( 1248 type='list', 1249 elements='dict', 1250 options=dict( 1251 url=dict(required=True), 1252 threshold=dict(), 1253 ), 1254 ) 1255 ), 1256 ), 1257 mobile_detection=dict( 1258 type='dict', 1259 options=dict( 1260 enabled=dict(type='bool'), 1261 allow_android_rooted_device=dict(type='bool'), 1262 allow_any_android_package=dict(type='bool'), 1263 allow_any_ios_package=dict(type='bool'), 1264 allow_jailbroken_devices=dict(type='bool'), 1265 allow_emulators=dict(type='bool'), 1266 client_side_challenge_mode=dict(choices=['cshui', 'pass']), 1267 ios_allowed_package_names=dict(type='list'), 1268 android_publishers=dict(type='list') 1269 ) 1270 ), 1271 rtbh_duration=dict(type='int'), 1272 rtbh_enable=dict(type='bool'), 1273 scrubbing_duration=dict(type='int'), 1274 scrubbing_enable=dict(type='bool'), 1275 single_page_application=dict(type='bool'), 1276 trigger_irule=dict(type='bool'), 1277 partition=dict( 1278 default='Common', 1279 fallback=(env_fallback, ['F5_PARTITION']) 1280 ), 1281 state=dict( 1282 default='present', 1283 choices=['present', 'absent'] 1284 ) 1285 ) 1286 self.argument_spec = {} 1287 self.argument_spec.update(f5_argument_spec) 1288 self.argument_spec.update(argument_spec) 1289 1290 1291def main(): 1292 spec = ArgumentSpec() 1293 1294 module = AnsibleModule( 1295 argument_spec=spec.argument_spec, 1296 supports_check_mode=spec.supports_check_mode, 1297 ) 1298 1299 try: 1300 mm = ModuleManager(module=module) 1301 results = mm.exec_module() 1302 module.exit_json(**results) 1303 except F5ModuleError as ex: 1304 module.fail_json(msg=str(ex)) 1305 1306 1307if __name__ == '__main__': 1308 main() 1309