Server IP : 85.214.239.14 / Your IP : 3.144.237.52 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /lib/python3/dist-packages/ansible_collections/community/aws/plugins/modules/ |
Upload File : |
#!/usr/bin/python # Copyright: Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = ''' --- module: wafv2_web_acl version_added: 1.5.0 author: - "Markus Bergholz (@markuman)" short_description: Create and delete WAF Web ACLs description: - Create, modify or delete AWS WAF v2 web ACLs (not for classic WAF). - See docs at U(https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html) options: state: description: - Whether the rule is present or absent. choices: ["present", "absent"] required: true type: str name: description: - The name of the web acl. required: true type: str scope: description: - Geographical scope of the web acl. required: true choices: ["CLOUDFRONT", "REGIONAL"] type: str description: description: - Description of wafv2 web acl. type: str default_action: description: - Default action of the wafv2 web acl. choices: ["Block", "Allow"] type: str sampled_requests: description: - Whether to store a sample of the web requests, true or false. type: bool default: false cloudwatch_metrics: description: - Enable cloudwatch metric for wafv2 web acl. type: bool default: true metric_name: description: - Name of cloudwatch metrics. - If not given and cloudwatch_metrics is enabled, the name of the web acl itself will be taken. type: str rules: description: - The Rule statements used to identify the web requests that you want to allow, block, or count. - For a list of managed rules see U(https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html). type: list elements: dict suboptions: name: description: - The name of the wafv2 rule type: str priority: description: - The rule priority type: int action: description: - Wether a rule is blocked, allowed or counted. type: dict visibility_config: description: - Visibility of single wafv2 rule. type: dict statement: description: - Rule configuration. type: dict custom_response_bodies: description: - A map of custom response keys and content bodies. Define response bodies here and reference them in the rules by providing - the key of the body dictionary element. - Each element must have a unique dict key and in the dict two keys for I(content_type) and I(content). - Requires botocore >= 1.20.40 type: dict version_added: 3.1.0 purge_rules: description: - When set to C(no), keep the existing load balancer rules in place. Will modify and add, but will not delete. default: true type: bool notes: - Support for the I(purge_tags) parameter was added in release 4.0.0. extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 - amazon.aws.tags - amazon.aws.boto3 ''' EXAMPLES = ''' - name: Create test web acl community.aws.wafv2_web_acl: name: test05 description: hallo eins scope: REGIONAL default_action: Allow sampled_requests: false cloudwatch_metrics: true metric_name: test05-acl-metric rules: - name: zwei priority: 0 action: block: {} visibility_config: sampled_requests_enabled: true cloud_watch_metrics_enabled: true metric_name: ddos statement: xss_match_statement: field_to_match: body: {} text_transformations: - type: NONE priority: 0 - name: admin_protect priority: 1 override_action: none: {} visibility_config: sampled_requests_enabled: true cloud_watch_metrics_enabled: true metric_name: fsd statement: managed_rule_group_statement: vendor_name: AWS name: AWSManagedRulesAdminProtectionRuleSet # AWS Managed Bad Input Rule Set # but allow PROPFIND_METHOD used e.g. by webdav - name: bad_input_protect_whitelist_webdav priority: 2 override_action: none: {} visibility_config: sampled_requests_enabled: true cloud_watch_metrics_enabled: true metric_name: bad_input_protect statement: managed_rule_group_statement: vendor_name: AWS name: AWSManagedRulesKnownBadInputsRuleSet excluded_rules: - name: PROPFIND_METHOD # Rate Limit example. 1500 req/5min # counted for two domains via or_statement. login.mydomain.tld and api.mydomain.tld - name: rate_limit_example priority: 3 action: block: {} visibility_config: sampled_requests_enabled: true cloud_watch_metrics_enabled: true metric_name: mydomain-ratelimit statement: rate_based_statement: limit: 1500 aggregate_key_type: IP scope_down_statement: or_statement: statements: - byte_match_statement: search_string: login.mydomain.tld positional_constraint: CONTAINS field_to_match: single_header: name: host text_transformations: - type: LOWERCASE priority: 0 - byte_match_dtatement: search_string: api.mydomain.tld positional_constraint: CONTAINS field_to_match: single_header: name: host text_transformations: - type: LOWERCASE priority: 0 purge_rules: true tags: A: B C: D state: present - name: Create IP filtering web ACL community.aws.wafv2_web_acl: name: ip-filtering-traffic description: ACL that filters web traffic based on rate limits and whitelists some IPs scope: REGIONAL default_action: Allow sampled_requests: true cloudwatch_metrics: true metric_name: ip-filtering-traffic rules: - name: whitelist-own-IPs priority: 0 action: allow: {} statement: ip_set_reference_statement: arn: 'arn:aws:wafv2:us-east-1:123456789012:regional/ipset/own-public-ips/1c4bdfc4-0f77-3b23-5222-123123123' visibility_config: sampled_requests_enabled: true cloud_watch_metrics_enabled: true metric_name: waf-acl-rule-whitelist-own-IPs - name: rate-limit-per-IP priority: 1 action: block: custom_response: response_code: 429 custom_response_body_key: too_many_requests statement: rate_based_statement: limit: 5000 aggregate_key_type: IP visibility_config: sampled_requests_enabled: true cloud_watch_metrics_enabled: true metric_name: waf-acl-rule-rate-limit-per-IP purge_rules: true custom_response_bodies: too_many_requests: content_type: APPLICATION_JSON content: '{ message: "Your request has been blocked due to too many HTTP requests coming from your IP" }' region: us-east-1 state: present ''' RETURN = """ arn: description: web acl arn sample: arn:aws:wafv2:eu-central-1:123456789012:regional/webacl/test05/318c1ab9-fa74-4b3b-a974-f92e25106f61 type: str returned: Always, as long as the web acl exists description: description: Description of the web acl sample: Some web acl description returned: Always, as long as the web acl exists type: str capacity: description: Current capacity of the web acl sample: 140 returned: Always, as long as the web acl exists type: int name: description: Web acl name sample: test02 returned: Always, as long as the web acl exists type: str default_action: description: Default action of ACL returned: Always, as long as the web acl exists sample: allow: {} type: dict rules: description: Current rules of the web acl returned: Always, as long as the web acl exists type: list sample: - name: admin_protect override_action: none: {} priority: 1 statement: managed_rule_group_statement: name: AWSManagedRulesAdminProtectionRuleSet vendor_name: AWS visibility_config: cloud_watch_metrics_enabled: true metric_name: admin_protect sampled_requests_enabled: true custom_response_bodies: description: Custom response body configurations to be used in rules type: dict sample: too_many_requests: content_type: APPLICATION_JSON content: '{ message: "Your request has been blocked due to too many HTTP requests coming from your IP" }' returned: Always, as long as the web acl exists visibility_config: description: Visibility config of the web acl returned: Always, as long as the web acl exists type: dict sample: cloud_watch_metrics_enabled: true metric_name: blub sampled_requests_enabled: false """ try: from botocore.exceptions import ClientError, BotoCoreError except ImportError: pass # caught by AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_tag_list from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict from ansible_collections.amazon.aws.plugins.module_utils.ec2 import snake_dict_to_camel_dict from ansible_collections.community.aws.plugins.module_utils.wafv2 import compare_priority_rules from ansible_collections.community.aws.plugins.module_utils.wafv2 import describe_wafv2_tags from ansible_collections.community.aws.plugins.module_utils.wafv2 import ensure_wafv2_tags from ansible_collections.community.aws.plugins.module_utils.wafv2 import wafv2_list_web_acls from ansible_collections.community.aws.plugins.module_utils.wafv2 import wafv2_snake_dict_to_camel_dict class WebACL: def __init__(self, wafv2, name, scope, fail_json_aws): self.wafv2 = wafv2 self.name = name self.scope = scope self.fail_json_aws = fail_json_aws self.existing_acl, self.id, self.locktoken = self.get_web_acl() def update(self, default_action, description, rules, sampled_requests, cloudwatch_metrics, metric_name, custom_response_bodies): req_obj = { 'Name': self.name, 'Scope': self.scope, 'Id': self.id, 'DefaultAction': default_action, 'Rules': rules, 'VisibilityConfig': { 'SampledRequestsEnabled': sampled_requests, 'CloudWatchMetricsEnabled': cloudwatch_metrics, 'MetricName': metric_name }, 'LockToken': self.locktoken } if description: req_obj['Description'] = description if custom_response_bodies: req_obj['CustomResponseBodies'] = custom_response_bodies try: response = self.wafv2.update_web_acl(**req_obj) except (BotoCoreError, ClientError) as e: self.fail_json_aws(e, msg="Failed to update wafv2 web acl.") self.existing_acl, self.id, self.locktoken = self.get_web_acl() return self.existing_acl def remove(self): try: response = self.wafv2.delete_web_acl( Name=self.name, Scope=self.scope, Id=self.id, LockToken=self.locktoken ) except (BotoCoreError, ClientError) as e: self.fail_json_aws(e, msg="Failed to remove wafv2 web acl.") return response def get(self): if self.existing_acl: return self.existing_acl return None def get_web_acl(self): id = None locktoken = None arn = None existing_acl = None response = self.list() for item in response.get('WebACLs'): if item.get('Name') == self.name: id = item.get('Id') locktoken = item.get('LockToken') arn = item.get('ARN') if id: try: existing_acl = self.wafv2.get_web_acl( Name=self.name, Scope=self.scope, Id=id ) except (BotoCoreError, ClientError) as e: self.fail_json_aws(e, msg="Failed to get wafv2 web acl.") tags = describe_wafv2_tags(self.wafv2, arn, self.fail_json_aws) existing_acl['tags'] = tags return existing_acl, id, locktoken def list(self): return wafv2_list_web_acls(self.wafv2, self.scope, self.fail_json_aws) def create(self, default_action, rules, sampled_requests, cloudwatch_metrics, metric_name, tags, description, custom_response_bodies): req_obj = { 'Name': self.name, 'Scope': self.scope, 'DefaultAction': default_action, 'Rules': rules, 'VisibilityConfig': { 'SampledRequestsEnabled': sampled_requests, 'CloudWatchMetricsEnabled': cloudwatch_metrics, 'MetricName': metric_name } } if custom_response_bodies: req_obj['CustomResponseBodies'] = custom_response_bodies if description: req_obj['Description'] = description if tags: req_obj['Tags'] = ansible_dict_to_boto3_tag_list(tags) try: response = self.wafv2.create_web_acl(**req_obj) except (BotoCoreError, ClientError) as e: self.fail_json_aws(e, msg="Failed to create wafv2 web acl.") self.existing_acl, self.id, self.locktoken = self.get_web_acl() return self.existing_acl def format_result(result): # We were returning details of the Web ACL inside a "web_acl" parameter on # creation, keep returning it to avoid breaking existing playbooks, but also # return what the docs said we return (and returned when no change happened) retval = dict(result) if "WebACL" in retval: retval.update(retval["WebACL"]) return camel_dict_to_snake_dict(retval, ignore_list=['tags']) def main(): arg_spec = dict( state=dict(type='str', required=True, choices=['present', 'absent']), name=dict(type='str', required=True), scope=dict(type='str', required=True, choices=['CLOUDFRONT', 'REGIONAL']), description=dict(type='str'), default_action=dict(type='str', choices=['Block', 'Allow']), rules=dict(type='list', elements='dict'), sampled_requests=dict(type='bool', default=False), cloudwatch_metrics=dict(type='bool', default=True), metric_name=dict(type='str'), tags=dict(type='dict', aliases=['resource_tags']), purge_tags=dict(default=True, type='bool'), custom_response_bodies=dict(type='dict'), purge_rules=dict(default=True, type='bool'), ) module = AnsibleAWSModule( argument_spec=arg_spec, supports_check_mode=True, required_if=[['state', 'present', ['default_action', 'rules']]] ) state = module.params.get("state") name = module.params.get("name") scope = module.params.get("scope") description = module.params.get("description") default_action = module.params.get("default_action") rules = module.params.get("rules") sampled_requests = module.params.get("sampled_requests") cloudwatch_metrics = module.params.get("cloudwatch_metrics") metric_name = module.params.get("metric_name") tags = module.params.get("tags") purge_tags = module.params.get("purge_tags") purge_rules = module.params.get("purge_rules") check_mode = module.check_mode custom_response_bodies = module.params.get("custom_response_bodies") if custom_response_bodies: module.require_botocore_at_least('1.20.40', reason='to set custom response bodies') custom_response_bodies = {} for custom_name, body in module.params.get("custom_response_bodies").items(): custom_response_bodies[custom_name] = snake_dict_to_camel_dict(body, capitalize_first=True) if default_action == 'Block': default_action = {'Block': {}} elif default_action == 'Allow': default_action = {'Allow': {}} if rules: rules = [] for rule in module.params.get("rules"): rules.append(wafv2_snake_dict_to_camel_dict(snake_dict_to_camel_dict(rule, capitalize_first=True))) if not metric_name: metric_name = name wafv2 = module.client('wafv2') web_acl = WebACL(wafv2, name, scope, module.fail_json_aws) change = False retval = {} if state == 'present': if web_acl.get(): tags_changed = ensure_wafv2_tags(wafv2, web_acl.get().get('WebACL').get('ARN'), tags, purge_tags, module.fail_json_aws, module.check_mode) change, rules = compare_priority_rules(web_acl.get().get('WebACL').get('Rules'), rules, purge_rules, state) change = change or (description and web_acl.get().get('WebACL').get('Description') != description) change = change or (default_action and web_acl.get().get('WebACL').get('DefaultAction') != default_action) if change and not check_mode: retval = web_acl.update( default_action, description, rules, sampled_requests, cloudwatch_metrics, metric_name, custom_response_bodies ) elif tags_changed: retval, id, locktoken = web_acl.get_web_acl() else: retval = web_acl.get() change |= tags_changed else: change = True if not check_mode: retval = web_acl.create( default_action, rules, sampled_requests, cloudwatch_metrics, metric_name, tags, description, custom_response_bodies ) elif state == 'absent': if web_acl.get(): if rules: if len(rules) > 0: change, rules = compare_priority_rules(web_acl.get().get('WebACL').get('Rules'), rules, purge_rules, state) if change and not check_mode: retval = web_acl.update( default_action, description, rules, sampled_requests, cloudwatch_metrics, metric_name, custom_response_bodies ) else: change = True if not check_mode: retval = web_acl.remove() module.exit_json(changed=change, **format_result(retval)) if __name__ == '__main__': main()