From 35a4eb33cd8321b0e490de73ee89a72cefa8dd9f Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Mon, 1 Mar 2021 10:09:00 +0100 Subject: [PATCH 1/9] add wafv2 ip set module --- plugins/modules/wafv2_ip_set.py | 326 ++++++++++++++++++ plugins/modules/wafv2_ip_set_info.py | 147 ++++++++ .../integration/targets/wafv2_ip_set/aliases | 5 + .../targets/wafv2_ip_set/defaults/main.yml | 2 + .../targets/wafv2_ip_set/tasks/main.yml | 212 ++++++++++++ 5 files changed, 692 insertions(+) create mode 100644 plugins/modules/wafv2_ip_set.py create mode 100644 plugins/modules/wafv2_ip_set_info.py create mode 100644 tests/integration/targets/wafv2_ip_set/aliases create mode 100644 tests/integration/targets/wafv2_ip_set/defaults/main.yml create mode 100644 tests/integration/targets/wafv2_ip_set/tasks/main.yml diff --git a/plugins/modules/wafv2_ip_set.py b/plugins/modules/wafv2_ip_set.py new file mode 100644 index 00000000000..df393c4a9ea --- /dev/null +++ b/plugins/modules/wafv2_ip_set.py @@ -0,0 +1,326 @@ +#!/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_ip_set +version_added: 1.5.0 +author: + - "Markus Bergholz (@markuman)" +short_description: wafv2_ip_set +description: + - Create, modify and delete IP sets for WAFv2. +requirements: + - boto3 + - botocore +options: + state: + description: + - Whether the rule is present or absent. + choices: ["present", "absent"] + required: true + type: str + name: + description: + - The name of the IP set. + required: true + type: str + description: + description: + - Description of the IP set. + required: false + type: str + scope: + description: + - Specifies whether this is for an AWS CloudFront distribution or for a regional application, + such as API Gateway or Application LoadBalancer. + choices: ["CLOUDFRONT","REGIONAL"] + required: true + type: str + ip_address_version: + description: + - Specifies whether this is an IPv4 or an IPv6 IP set. + - Required when I(state=present). + choices: ["IPV4","IPV6"] + type: str + addresses: + description: + - Contains an array of strings that specify one or more IP addresses or blocks of IP addresses in + Classless Inter-Domain Routing (CIDR) notation. + - Required when I(state=present). + - When I(state=absent) and I(addresses) is defined, only the given IP addresses will be removed + from the IP set. The entire IP set itself will stay present. + type: list + elements: str + tags: + description: + - Key value pairs to associate with the resource. + - Currently tags are not visible. Nor in the web ui, nor via cli and nor in boto3. + required: false + type: dict + purge_addresses: + description: + - When set to C(no), keep the existing addresses in place. Will modify and add, but will not delete. + default: yes + type: bool + +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 + +''' + +EXAMPLES = ''' +- name: test ip set + wafv2_ip_set: + name: test02 + state: present + description: hallo eins + scope: REGIONAL + ip_address_version: IPV4 + addresses: + - 8.8.8.8/32 + - 8.8.4.4/32 + tags: + A: B + C: D +''' + +RETURN = """ +addresses: + description: Current addresses of the ip set + sample: + - 8.8.8.8/32 + - 8.8.4.4/32 + returned: Always, as long as the ip set exists + type: list +arn: + description: IP set arn + sample: "arn:aws:wafv2:eu-central-1:11111111:regional/ipset/test02/4b007330-2934-4dc5-af24-82dcb3aeb127" + type: str + returned: Always, as long as the ip set exists +description: + description: Description of the ip set + sample: Some IP set description + returned: Always, as long as the ip set exists + type: str +ip_address_version: + description: IP version of the ip set + sample: IPV4 + type: str + returned: Always, as long as the ip set exists +name: + description: IP set name + sample: test02 + returned: Always, as long as the ip set exists + type: str +""" +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule, is_boto3_error_code, get_boto3_client_method_parameters +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict, ansible_dict_to_boto3_tag_list + +try: + from botocore.exceptions import ClientError, BotoCoreError, WaiterError +except ImportError: + pass # caught by AnsibleAWSModule + + +class IpSet: + def __init__(self, wafv2, name, scope): + self.wafv2 = wafv2 + self.name = name + self.scope = scope + self.existing_set, self.id, self.locktoken = self.get_set() + + def description(self): + return self.existing_set.get('Description') + + def get(self): + if self.existing_set: + return camel_dict_to_snake_dict(self.existing_set) + return None + + def remove(self): + response = self.wafv2.delete_ip_set( + Name=self.name, + Scope=self.scope, + Id=self.id, + LockToken=self.locktoken + ) + return {} + + def create(self, description, ip_address_version, addresses, tags): + req_obj = { + 'Name': self.name, + 'Scope': self.scope, + 'IPAddressVersion': ip_address_version, + 'Addresses': addresses, + } + + if description: + req_obj['Description'] = description + + if tags: + req_obj['Tags'] = ansible_dict_to_boto3_tag_list(tags) + + response = self.wafv2.create_ip_set(**req_obj) + self.existing_set, self.id, self.locktoken = self.get_set() + return camel_dict_to_snake_dict(self.existing_set) + + def update(self, description, addresses): + req_obj = { + 'Name': self.name, + 'Scope': self.scope, + 'Id': self.id, + 'Addresses': addresses, + 'LockToken': self.locktoken + } + + if description: + req_obj['Description'] = description + + response = self.wafv2.update_ip_set(**req_obj) + self.existing_set, self.id, self.locktoken = self.get_set() + return camel_dict_to_snake_dict(self.existing_set) + + def get_set(self): + response = self.list() + existing_set = None + id = None + locktoken = None + for item in response.get('IPSets'): + if item.get('Name') == self.name: + id = item.get('Id') + locktoken = item.get('LockToken') + arn = item.get('ARN') + if id: + existing_set = self.wafv2.get_ip_set( + Name=self.name, + Scope=self.scope, + Id=id + ).get('IPSet') + + return existing_set, id, locktoken + + def list(self, Nextmarker=None): + # there is currently no paginator for wafv2 + req_obj = { + 'Scope': self.scope, + 'Limit': 100 + } + + if Nextmarker: + req_obj['NextMarker'] = Nextmarker + + response = self.wafv2.list_ip_sets(**req_obj) + + if response.get('NextMarker'): + response['IPSets'] += self.list(Nextmarker=response.get('NextMarker')).get('IPSets') + + return response + + +def compare(existing_set, addresses, purge_addresses, state): + diff = False + new_rules = [] + existing_rules = existing_set.get('addresses') + if state == 'present': + if purge_addresses: + new_rules = addresses + if sorted(addresses) != sorted(existing_set.get('addresses')): + diff = True + + else: + for requested_rule in addresses: + if requested_rule not in existing_rules: + diff = True + new_rules.append(requested_rule) + + new_rules += existing_rules + else: + if purge_addresses and addresses: + for requested_rule in addresses: + if requested_rule in existing_rules: + diff = True + existing_rules.pop(existing_rules.index(requested_rule)) + new_rules = existing_rules + + return diff, new_rules + + +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'), + ip_address_version=dict(type='str', choices=['IPV4', 'IPV6']), + addresses=dict(type='list', elements='str'), + tags=dict(type='dict'), + purge_addresses=dict(type='bool', default=True) + ) + + module = AnsibleAWSModule( + argument_spec=arg_spec, + supports_check_mode=True, + required_if=[['state', 'present', ['ip_address_version', 'addresses']]] + ) + + state = module.params.get("state") + name = module.params.get("name") + scope = module.params.get("scope") + description = module.params.get("description") + ip_address_version = module.params.get("ip_address_version") + addresses = module.params.get("addresses") + tags = module.params.get("tags") + purge_addresses = module.params.get("purge_addresses") + + wafv2 = module.client('wafv2') + + change = False + retval = {} + + ip_set = IpSet(wafv2, name, scope) + + if state == 'present': + if ip_set.get(): + change, addresses = compare(ip_set.get(), addresses, purge_addresses, state) + if change or ip_set.description() != description: + retval = ip_set.update( + description=description, + addresses=addresses + ) + else: + retval = ip_set.get() + else: + retval = ip_set.create( + description=description, + ip_address_version=ip_address_version, + addresses=addresses, + tags=tags + ) + change = True + + if state == 'absent': + if ip_set.get(): + if addresses: + if len(addresses) > 0: + change, addresses = compare(ip_set.get(), addresses, purge_addresses, state) + if change: + retval = ip_set.update( + description=description, + addresses=addresses + ) + else: + retval = ip_set.remove() + change = True + + module.exit_json(changed=change, **retval) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/wafv2_ip_set_info.py b/plugins/modules/wafv2_ip_set_info.py new file mode 100644 index 00000000000..c4f35f4aa2d --- /dev/null +++ b/plugins/modules/wafv2_ip_set_info.py @@ -0,0 +1,147 @@ +#!/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_ip_set_info +version_added: 1.4.0 +author: + - "Markus Bergholz (@markuman)" +short_description: Get information about wafv2 ip sets +description: + - Get information about existing wafv2 ip sets. +requirements: + - boto3 + - botocore +options: + name: + description: + - The name of the IP set. + required: true + type: str + scope: + description: + - Specifies whether this is for an AWS CloudFront distribution or for a regional application. + choices: ["CLOUDFRONT","REGIONAL"] + required: true + type: str + +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 + +''' + +EXAMPLES = ''' +- name: test ip set + wafv2_ip_set_info: + name: test02 + scope: REGIONAL +''' + +RETURN = """ +addresses: + description: Current addresses of the ip set + sample: + - 8.8.8.8/32 + - 8.8.4.4/32 + returned: Always, as long as the ip set exists + type: list +arn: + description: IP set arn + sample: "arn:aws:wafv2:eu-central-1:11111111:regional/ipset/test02/4b007330-2934-4dc5-af24-82dcb3aeb127" + type: str + returned: Always, as long as the ip set exists +description: + description: Description of the ip set + sample: Some IP set description + returned: Always, as long as the ip set exists + type: str +ip_address_version: + description: IP version of the ip set + sample: IPV4 + type: str + returned: Always, as long as the ip set exists +name: + description: IP set name + sample: test02 + returned: Always, as long as the ip set exists + type: str +""" +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule, is_boto3_error_code, get_boto3_client_method_parameters +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict + +try: + from botocore.exceptions import ClientError, BotoCoreError, WaiterError +except ImportError: + pass # caught by AnsibleAWSModule + + +def list_ip_sets(self, Nextmarker=None): + # there is currently no paginator for wafv2 + req_obj = { + 'Scope': self.scope, + 'Limit': 100 + } + + if Nextmarker: + req_obj['NextMarker'] = Nextmarker + + response = self.wafv2.list_ip_sets(**req_obj) + + if response.get('NextMarker'): + response['IPSets'] += self.list(Nextmarker=response.get('NextMarker')).get('IPSets') + + return response + + +def get_ip_set(wafv2, name, scope, id): + response = wafv2.get_ip_set( + Name=name, + Scope=scope, + Id=id + ) + return response + + +def main(): + + arg_spec = dict( + name=dict(type='str', required=True), + scope=dict(type='str', required=True, choices=['CLOUDFRONT', 'REGIONAL']) + ) + + module = AnsibleAWSModule( + argument_spec=arg_spec, + supports_check_mode=True, + ) + + name = module.params.get("name") + scope = module.params.get("scope") + + wafv2 = module.client('wafv2') + + # check if ip set exist + response = list_ip_sets(wafv2, scope) + + id = None + + for item in response.get('IPSets'): + if item.get('Name') == name: + id = item.get('Id') + + retval = {} + existing_set = None + if id: + existing_set = get_ip_set(wafv2, name, scope, id) + retval = camel_dict_to_snake_dict(existing_set.get('IPSet')) + + module.exit_json(**retval) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/wafv2_ip_set/aliases b/tests/integration/targets/wafv2_ip_set/aliases new file mode 100644 index 00000000000..12354953063 --- /dev/null +++ b/tests/integration/targets/wafv2_ip_set/aliases @@ -0,0 +1,5 @@ +cloud/aws +shippable/aws/group1 + +wafv2_ip_set +wafv2_ip_set_info diff --git a/tests/integration/targets/wafv2_ip_set/defaults/main.yml b/tests/integration/targets/wafv2_ip_set/defaults/main.yml new file mode 100644 index 00000000000..11f2faed964 --- /dev/null +++ b/tests/integration/targets/wafv2_ip_set/defaults/main.yml @@ -0,0 +1,2 @@ +--- +ip_set_name: '{{ resource_prefix }}-ipset' diff --git a/tests/integration/targets/wafv2_ip_set/tasks/main.yml b/tests/integration/targets/wafv2_ip_set/tasks/main.yml new file mode 100644 index 00000000000..0eea2c1f80f --- /dev/null +++ b/tests/integration/targets/wafv2_ip_set/tasks/main.yml @@ -0,0 +1,212 @@ +--- +- name: ec2_vpc_endpoint tests + collections: + - amazon.aws + + module_defaults: + group/aws: + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token | default(omit) }}" + region: "{{ aws_region }}" + + block: + - name: check_mode create ip set + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: present + description: hallo eins + scope: REGIONAL + ip_address_version: IPV4 + addresses: + - 8.8.8.8/32 + - 8.8.4.4/32 + tags: + A: B + C: D + register: out + check_mode: yes + + - name: verify create + assert: + that: + - out is changed + + - name: create ip set + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: present + description: hallo eins + scope: REGIONAL + ip_address_version: IPV4 + addresses: + - 8.8.8.8/32 + - 8.8.4.4/32 + tags: + A: B + C: D + register: out + + - name: verify create + assert: + that: + - out is changed + + - name: change ip set + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: present + description: hallo eins + scope: REGIONAL + ip_address_version: IPV4 + addresses: + - 8.8.8.8/32 + - 8.8.4.4/32 + - 10.0.0.0/8 + tags: + A: B + C: D + register: out + + - name: verify create + assert: + that: + - out is changed + + - name: test ip set immutable + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: present + description: hallo eins + scope: REGIONAL + ip_address_version: IPV4 + addresses: + - 8.8.8.8/32 + - 8.8.4.4/32 + - 10.0.0.0/8 + tags: + A: B + C: D + register: out + + - name: verify immutable create + assert: + that: + - out is not changed + - out.addresses | count == 3 + + - name: add one ip + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: present + description: hallo eins + scope: REGIONAL + ip_address_version: IPV4 + purge_addresses: no + addresses: + - 127.0.0.1/32 + register: out + + - name: verify change + assert: + that: + - out is changed + - out.addresses | count == 4 + - "'127.0.0.1/32' in out.addresses" + + + - name: remove one ip + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: absent + description: hallo eins + scope: REGIONAL + ip_address_version: IPV4 + purge_addresses: yes + addresses: + - 127.0.0.1/32 + register: out + + - name: verify change + assert: + that: + - out is changed + - out.addresses | count == 3 + - "'127.0.0.1/32' not in out.addresses" + + - name: get ip set info + community.aws.wafv2_ip_set_info: + name: "{{ ip_set_name }}" + scope: REGIONAL + register: out + + - name: verify rules + assert: + that: + - out.addresses | count == 3 + + + - name: purge all but one + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: present + description: hallo eins + scope: REGIONAL + ip_address_version: IPV4 + purge_addresses: yes + addresses: + - 127.0.0.1/32 + register: out + + - name: verify change + assert: + that: + - out is changed + - out.addresses | count == 1 + + - name: get ip set info + community.aws.wafv2_ip_set_info: + name: "{{ ip_set_name }}" + scope: REGIONAL + register: out + + - name: verify rules + assert: + that: + - out.addresses | count == 1 + + - name: delete ip set + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: absent + scope: REGIONAL + ip_address_version: IPV4 + register: out + + - name: verify delete + assert: + that: + - out is changed + + - name: delete ip set immutable + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: absent + scope: REGIONAL + ip_address_version: IPV4 + register: out + + - name: verify immutable delete + assert: + that: + - out is not changed + + + always: + - name: always delete ip set + community.aws.wafv2_ip_set: + name: "{{ ip_set_name }}" + state: absent + scope: REGIONAL + ip_address_version: IPV4 + ignore_errors: true From 0bc43459b05f1ab7a1d13d5b53b53b2608948449 Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Mon, 1 Mar 2021 12:13:09 +0100 Subject: [PATCH 2/9] remove ignoring errors --- tests/integration/targets/wafv2_ip_set/tasks/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/targets/wafv2_ip_set/tasks/main.yml b/tests/integration/targets/wafv2_ip_set/tasks/main.yml index 0eea2c1f80f..991b464d04e 100644 --- a/tests/integration/targets/wafv2_ip_set/tasks/main.yml +++ b/tests/integration/targets/wafv2_ip_set/tasks/main.yml @@ -209,4 +209,3 @@ state: absent scope: REGIONAL ip_address_version: IPV4 - ignore_errors: true From 5e874d856776f25c67e63a31f5006f2e2b2b5185 Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Mon, 1 Mar 2021 12:24:26 +0100 Subject: [PATCH 3/9] expand meta/runtime with wafv2_ip_set modules --- meta/runtime.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meta/runtime.yml b/meta/runtime.yml index e8153ff35c4..e17798ce3a6 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -217,6 +217,8 @@ action_groups: - sqs_queue - sts_assume_role - sts_session_token + - wafv2_ip_set + - wafv2_ip_set_info plugin_routing: modules: From f975d7f7cfb7cf01f5474ae7d2052837084ef527 Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Mon, 1 Mar 2021 12:41:41 +0100 Subject: [PATCH 4/9] fix integrationtest, complete check_mode --- plugins/modules/wafv2_ip_set.py | 29 +++++++++--------- plugins/modules/wafv2_ip_set_info.py | 12 +++----- .../targets/wafv2_ip_set/tasks/main.yml | 30 ++++++++----------- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/plugins/modules/wafv2_ip_set.py b/plugins/modules/wafv2_ip_set.py index df393c4a9ea..4b98d397f44 100644 --- a/plugins/modules/wafv2_ip_set.py +++ b/plugins/modules/wafv2_ip_set.py @@ -208,18 +208,14 @@ def get_set(self): def list(self, Nextmarker=None): # there is currently no paginator for wafv2 req_obj = { - 'Scope': self.scope, - 'Limit': 100 + 'Scope': self.scope, + 'Limit': 100 } - if Nextmarker: req_obj['NextMarker'] = Nextmarker - response = self.wafv2.list_ip_sets(**req_obj) - if response.get('NextMarker'): response['IPSets'] += self.list(Nextmarker=response.get('NextMarker')).get('IPSets') - return response @@ -278,6 +274,7 @@ def main(): addresses = module.params.get("addresses") tags = module.params.get("tags") purge_addresses = module.params.get("purge_addresses") + check_mode = module.check_mode wafv2 = module.client('wafv2') @@ -289,7 +286,7 @@ def main(): if state == 'present': if ip_set.get(): change, addresses = compare(ip_set.get(), addresses, purge_addresses, state) - if change or ip_set.description() != description: + if (change or ip_set.description() != description) and not check_mode: retval = ip_set.update( description=description, addresses=addresses @@ -297,12 +294,13 @@ def main(): else: retval = ip_set.get() else: - retval = ip_set.create( - description=description, - ip_address_version=ip_address_version, - addresses=addresses, - tags=tags - ) + if not check_mode: + retval = ip_set.create( + description=description, + ip_address_version=ip_address_version, + addresses=addresses, + tags=tags + ) change = True if state == 'absent': @@ -310,13 +308,14 @@ def main(): if addresses: if len(addresses) > 0: change, addresses = compare(ip_set.get(), addresses, purge_addresses, state) - if change: + if change and not check_mode: retval = ip_set.update( description=description, addresses=addresses ) else: - retval = ip_set.remove() + if not check_mode: + retval = ip_set.remove() change = True module.exit_json(changed=change, **retval) diff --git a/plugins/modules/wafv2_ip_set_info.py b/plugins/modules/wafv2_ip_set_info.py index c4f35f4aa2d..52a874edf53 100644 --- a/plugins/modules/wafv2_ip_set_info.py +++ b/plugins/modules/wafv2_ip_set_info.py @@ -8,7 +8,7 @@ DOCUMENTATION = ''' --- module: wafv2_ip_set_info -version_added: 1.4.0 +version_added: 1.5.0 author: - "Markus Bergholz (@markuman)" short_description: Get information about wafv2 ip sets @@ -81,21 +81,17 @@ pass # caught by AnsibleAWSModule -def list_ip_sets(self, Nextmarker=None): +def list_ip_sets(wafv2, scope, Nextmarker=None): # there is currently no paginator for wafv2 req_obj = { - 'Scope': self.scope, - 'Limit': 100 + 'Scope': self.scope, + 'Limit': 100 } - if Nextmarker: req_obj['NextMarker'] = Nextmarker - response = self.wafv2.list_ip_sets(**req_obj) - if response.get('NextMarker'): response['IPSets'] += self.list(Nextmarker=response.get('NextMarker')).get('IPSets') - return response diff --git a/tests/integration/targets/wafv2_ip_set/tasks/main.yml b/tests/integration/targets/wafv2_ip_set/tasks/main.yml index 991b464d04e..2d5d4660fe7 100644 --- a/tests/integration/targets/wafv2_ip_set/tasks/main.yml +++ b/tests/integration/targets/wafv2_ip_set/tasks/main.yml @@ -1,9 +1,5 @@ --- -- name: ec2_vpc_endpoint tests - collections: - - amazon.aws - - module_defaults: +- module_defaults: group/aws: aws_access_key: "{{ aws_access_key }}" aws_secret_key: "{{ aws_secret_key }}" @@ -12,7 +8,7 @@ block: - name: check_mode create ip set - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: present description: hallo eins @@ -33,7 +29,7 @@ - out is changed - name: create ip set - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: present description: hallo eins @@ -53,7 +49,7 @@ - out is changed - name: change ip set - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: present description: hallo eins @@ -74,7 +70,7 @@ - out is changed - name: test ip set immutable - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: present description: hallo eins @@ -96,7 +92,7 @@ - out.addresses | count == 3 - name: add one ip - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: present description: hallo eins @@ -116,7 +112,7 @@ - name: remove one ip - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: absent description: hallo eins @@ -135,7 +131,7 @@ - "'127.0.0.1/32' not in out.addresses" - name: get ip set info - community.aws.wafv2_ip_set_info: + wafv2_ip_set_info: name: "{{ ip_set_name }}" scope: REGIONAL register: out @@ -147,7 +143,7 @@ - name: purge all but one - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: present description: hallo eins @@ -165,7 +161,7 @@ - out.addresses | count == 1 - name: get ip set info - community.aws.wafv2_ip_set_info: + wafv2_ip_set_info: name: "{{ ip_set_name }}" scope: REGIONAL register: out @@ -176,7 +172,7 @@ - out.addresses | count == 1 - name: delete ip set - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: absent scope: REGIONAL @@ -189,7 +185,7 @@ - out is changed - name: delete ip set immutable - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: absent scope: REGIONAL @@ -204,7 +200,7 @@ always: - name: always delete ip set - community.aws.wafv2_ip_set: + wafv2_ip_set: name: "{{ ip_set_name }}" state: absent scope: REGIONAL From 4fcaa81cfd77677d97b9297a3058ed083a8cbf72 Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Mon, 1 Mar 2021 12:47:23 +0100 Subject: [PATCH 5/9] syntax and sanity fix --- plugins/modules/wafv2_ip_set_info.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/modules/wafv2_ip_set_info.py b/plugins/modules/wafv2_ip_set_info.py index 52a874edf53..019d2bbb622 100644 --- a/plugins/modules/wafv2_ip_set_info.py +++ b/plugins/modules/wafv2_ip_set_info.py @@ -84,14 +84,14 @@ def list_ip_sets(wafv2, scope, Nextmarker=None): # there is currently no paginator for wafv2 req_obj = { - 'Scope': self.scope, - 'Limit': 100 + 'Scope': scope, + 'Limit': 100 } if Nextmarker: req_obj['NextMarker'] = Nextmarker - response = self.wafv2.list_ip_sets(**req_obj) + response = wafv2.list_ip_sets(**req_obj) if response.get('NextMarker'): - response['IPSets'] += self.list(Nextmarker=response.get('NextMarker')).get('IPSets') + response['IPSets'] += list_ip_sets(wafv2, scope, Nextmarker=response.get('NextMarker')).get('IPSets') return response From 5fe382cb82fdd6fb53d7de247923789330b12c47 Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Mon, 1 Mar 2021 12:50:43 +0100 Subject: [PATCH 6/9] sanity fix --- plugins/modules/wafv2_ip_set.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/wafv2_ip_set.py b/plugins/modules/wafv2_ip_set.py index 4b98d397f44..319620b08c7 100644 --- a/plugins/modules/wafv2_ip_set.py +++ b/plugins/modules/wafv2_ip_set.py @@ -208,8 +208,8 @@ def get_set(self): def list(self, Nextmarker=None): # there is currently no paginator for wafv2 req_obj = { - 'Scope': self.scope, - 'Limit': 100 + 'Scope': self.scope, + 'Limit': 100 } if Nextmarker: req_obj['NextMarker'] = Nextmarker From e47483034ca9702bbe79885b2b9a99deca1ee849 Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Thu, 18 Mar 2021 19:23:16 +0100 Subject: [PATCH 7/9] try <<: *aws_connection_info --- .../targets/wafv2_ip_set/tasks/main.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/integration/targets/wafv2_ip_set/tasks/main.yml b/tests/integration/targets/wafv2_ip_set/tasks/main.yml index 2d5d4660fe7..0bb01f8b63d 100644 --- a/tests/integration/targets/wafv2_ip_set/tasks/main.yml +++ b/tests/integration/targets/wafv2_ip_set/tasks/main.yml @@ -7,8 +7,17 @@ region: "{{ aws_region }}" block: + - name: set up aws connection info + set_fact: + aws_connection_info: &aws_connection_info + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token }}" + region: "{{ aws_region }}" + - name: check_mode create ip set wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -30,6 +39,7 @@ - name: create ip set wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -50,6 +60,7 @@ - name: change ip set wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -71,6 +82,7 @@ - name: test ip set immutable wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -93,6 +105,7 @@ - name: add one ip wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -113,6 +126,7 @@ - name: remove one ip wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: absent description: hallo eins @@ -132,6 +146,7 @@ - name: get ip set info wafv2_ip_set_info: + <<: *aws_connection_info name: "{{ ip_set_name }}" scope: REGIONAL register: out @@ -144,6 +159,7 @@ - name: purge all but one wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -162,6 +178,7 @@ - name: get ip set info wafv2_ip_set_info: + <<: *aws_connection_info name: "{{ ip_set_name }}" scope: REGIONAL register: out @@ -173,6 +190,7 @@ - name: delete ip set wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: absent scope: REGIONAL @@ -186,6 +204,7 @@ - name: delete ip set immutable wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: absent scope: REGIONAL @@ -201,6 +220,7 @@ always: - name: always delete ip set wafv2_ip_set: + <<: *aws_connection_info name: "{{ ip_set_name }}" state: absent scope: REGIONAL From c57c0c9219bde3ffd72718c6671b6f4ca0afbd5d Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Fri, 19 Mar 2021 20:38:19 +0100 Subject: [PATCH 8/9] add exceptions --- plugins/modules/wafv2_ip_set.py | 58 +++++++++++++------ plugins/modules/wafv2_ip_set_info.py | 33 ++++++----- .../targets/wafv2_ip_set/tasks/main.yml | 21 +------ 3 files changed, 60 insertions(+), 52 deletions(-) diff --git a/plugins/modules/wafv2_ip_set.py b/plugins/modules/wafv2_ip_set.py index 319620b08c7..f183211bcf8 100644 --- a/plugins/modules/wafv2_ip_set.py +++ b/plugins/modules/wafv2_ip_set.py @@ -123,16 +123,17 @@ from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict, ansible_dict_to_boto3_tag_list try: - from botocore.exceptions import ClientError, BotoCoreError, WaiterError + from botocore.exceptions import ClientError, BotoCoreError except ImportError: pass # caught by AnsibleAWSModule class IpSet: - def __init__(self, wafv2, name, scope): + 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_set, self.id, self.locktoken = self.get_set() def description(self): @@ -144,12 +145,15 @@ def get(self): return None def remove(self): - response = self.wafv2.delete_ip_set( - Name=self.name, - Scope=self.scope, - Id=self.id, - LockToken=self.locktoken - ) + try: + response = self.wafv2.delete_ip_set( + 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 ip set.") return {} def create(self, description, ip_address_version, addresses, tags): @@ -166,7 +170,11 @@ def create(self, description, ip_address_version, addresses, tags): if tags: req_obj['Tags'] = ansible_dict_to_boto3_tag_list(tags) - response = self.wafv2.create_ip_set(**req_obj) + try: + response = self.wafv2.create_ip_set(**req_obj) + except (BotoCoreError, ClientError) as e: + self.fail_json_aws(e, msg="Failed to create wafv2 ip set.") + self.existing_set, self.id, self.locktoken = self.get_set() return camel_dict_to_snake_dict(self.existing_set) @@ -182,7 +190,11 @@ def update(self, description, addresses): if description: req_obj['Description'] = description - response = self.wafv2.update_ip_set(**req_obj) + try: + response = self.wafv2.update_ip_set(**req_obj) + except (BotoCoreError, ClientError) as e: + self.fail_json_aws(e, msg="Failed to update wafv2 ip set.") + self.existing_set, self.id, self.locktoken = self.get_set() return camel_dict_to_snake_dict(self.existing_set) @@ -197,11 +209,14 @@ def get_set(self): locktoken = item.get('LockToken') arn = item.get('ARN') if id: - existing_set = self.wafv2.get_ip_set( - Name=self.name, - Scope=self.scope, - Id=id - ).get('IPSet') + try: + existing_set = self.wafv2.get_ip_set( + Name=self.name, + Scope=self.scope, + Id=id + ).get('IPSet') + except (BotoCoreError, ClientError) as e: + self.fail_json_aws(e, msg="Failed to get wafv2 ip set.") return existing_set, id, locktoken @@ -213,9 +228,14 @@ def list(self, Nextmarker=None): } if Nextmarker: req_obj['NextMarker'] = Nextmarker - response = self.wafv2.list_ip_sets(**req_obj) - if response.get('NextMarker'): - response['IPSets'] += self.list(Nextmarker=response.get('NextMarker')).get('IPSets') + + try: + response = self.wafv2.list_ip_sets(**req_obj) + if response.get('NextMarker'): + response['IPSets'] += self.list(Nextmarker=response.get('NextMarker')).get('IPSets') + except (BotoCoreError, ClientError) as e: + self.fail_json_aws(e, msg="Failed to list wafv2 ip set.") + return response @@ -281,7 +301,7 @@ def main(): change = False retval = {} - ip_set = IpSet(wafv2, name, scope) + ip_set = IpSet(wafv2, name, scope, module.fail_json_aws) if state == 'present': if ip_set.get(): diff --git a/plugins/modules/wafv2_ip_set_info.py b/plugins/modules/wafv2_ip_set_info.py index 019d2bbb622..23b3abed4ec 100644 --- a/plugins/modules/wafv2_ip_set_info.py +++ b/plugins/modules/wafv2_ip_set_info.py @@ -76,12 +76,12 @@ from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict try: - from botocore.exceptions import ClientError, BotoCoreError, WaiterError + from botocore.exceptions import ClientError, BotoCoreError except ImportError: pass # caught by AnsibleAWSModule -def list_ip_sets(wafv2, scope, Nextmarker=None): +def list_ip_sets(wafv2, scope, fail_json_aws, Nextmarker=None): # there is currently no paginator for wafv2 req_obj = { 'Scope': scope, @@ -89,18 +89,25 @@ def list_ip_sets(wafv2, scope, Nextmarker=None): } if Nextmarker: req_obj['NextMarker'] = Nextmarker - response = wafv2.list_ip_sets(**req_obj) - if response.get('NextMarker'): - response['IPSets'] += list_ip_sets(wafv2, scope, Nextmarker=response.get('NextMarker')).get('IPSets') + + try: + response = wafv2.list_ip_sets(**req_obj) + if response.get('NextMarker'): + response['IPSets'] += list_ip_sets(wafv2, scope, fail_json_aws, Nextmarker=response.get('NextMarker')).get('IPSets') + except (BotoCoreError, ClientError) as e: + fail_json_aws(e, msg="Failed to list wafv2 ip set.") return response -def get_ip_set(wafv2, name, scope, id): - response = wafv2.get_ip_set( - Name=name, - Scope=scope, - Id=id - ) +def get_ip_set(wafv2, name, scope, id, fail_json_aws): + try: + response = wafv2.get_ip_set( + Name=name, + Scope=scope, + Id=id + ) + except (BotoCoreError, ClientError) as e: + fail_json_aws(e, msg="Failed to get wafv2 ip set.") return response @@ -122,7 +129,7 @@ def main(): wafv2 = module.client('wafv2') # check if ip set exist - response = list_ip_sets(wafv2, scope) + response = list_ip_sets(wafv2, scope, module.fail_json_aws) id = None @@ -133,7 +140,7 @@ def main(): retval = {} existing_set = None if id: - existing_set = get_ip_set(wafv2, name, scope, id) + existing_set = get_ip_set(wafv2, name, scope, id, module.fail_json_aws) retval = camel_dict_to_snake_dict(existing_set.get('IPSet')) module.exit_json(**retval) diff --git a/tests/integration/targets/wafv2_ip_set/tasks/main.yml b/tests/integration/targets/wafv2_ip_set/tasks/main.yml index 0bb01f8b63d..f4748402e71 100644 --- a/tests/integration/targets/wafv2_ip_set/tasks/main.yml +++ b/tests/integration/targets/wafv2_ip_set/tasks/main.yml @@ -7,17 +7,8 @@ region: "{{ aws_region }}" block: - - name: set up aws connection info - set_fact: - aws_connection_info: &aws_connection_info - aws_access_key: "{{ aws_access_key }}" - aws_secret_key: "{{ aws_secret_key }}" - security_token: "{{ security_token }}" - region: "{{ aws_region }}" - - name: check_mode create ip set wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -39,7 +30,6 @@ - name: create ip set wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -53,6 +43,7 @@ C: D register: out + - debug: msg="{{ out }}" - name: verify create assert: that: @@ -60,7 +51,6 @@ - name: change ip set wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -82,7 +72,6 @@ - name: test ip set immutable wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -105,7 +94,6 @@ - name: add one ip wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -126,7 +114,6 @@ - name: remove one ip wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: absent description: hallo eins @@ -146,7 +133,6 @@ - name: get ip set info wafv2_ip_set_info: - <<: *aws_connection_info name: "{{ ip_set_name }}" scope: REGIONAL register: out @@ -159,7 +145,6 @@ - name: purge all but one wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: present description: hallo eins @@ -178,7 +163,6 @@ - name: get ip set info wafv2_ip_set_info: - <<: *aws_connection_info name: "{{ ip_set_name }}" scope: REGIONAL register: out @@ -190,7 +174,6 @@ - name: delete ip set wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: absent scope: REGIONAL @@ -204,7 +187,6 @@ - name: delete ip set immutable wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: absent scope: REGIONAL @@ -220,7 +202,6 @@ always: - name: always delete ip set wafv2_ip_set: - <<: *aws_connection_info name: "{{ ip_set_name }}" state: absent scope: REGIONAL From cfd8912062185fdff14af3748d2a3e0bd7a403af Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Fri, 19 Mar 2021 21:07:00 +0100 Subject: [PATCH 9/9] fix integration test --- tests/integration/targets/wafv2_ip_set/tasks/main.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration/targets/wafv2_ip_set/tasks/main.yml b/tests/integration/targets/wafv2_ip_set/tasks/main.yml index f4748402e71..6436fc81cc4 100644 --- a/tests/integration/targets/wafv2_ip_set/tasks/main.yml +++ b/tests/integration/targets/wafv2_ip_set/tasks/main.yml @@ -43,11 +43,14 @@ C: D register: out - - debug: msg="{{ out }}" - name: verify create assert: that: - out is changed + - "'8.8.8.8/32' in out.addresses" + - out.ip_address_version == 'IPV4' + - out.addresses | count == 2 + - out.description == 'hallo eins' - name: change ip set wafv2_ip_set: @@ -69,6 +72,7 @@ assert: that: - out is changed + - "'10.0.0.0/8' in out.addresses" - name: test ip set immutable wafv2_ip_set: @@ -130,6 +134,7 @@ - out is changed - out.addresses | count == 3 - "'127.0.0.1/32' not in out.addresses" + - "'8.8.8.8/32' in out.addresses" - name: get ip set info wafv2_ip_set_info: