From bcb92ca511a7ca9007bf947add60421dd891da05 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Mon, 11 Oct 2021 14:34:52 +0200 Subject: [PATCH 01/18] Add iam_access_key(_info) new modules This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/dd3e527a1b9a3e367be10c96763f958e918833f0 --- plugins/modules/iam_access_key.py | 316 +++++++ plugins/modules/iam_access_key_info.py | 127 +++ .../targets/iam_access_key/aliases | 9 + .../targets/iam_access_key/defaults/main.yml | 2 + .../targets/iam_access_key/meta/main.yml | 3 + .../targets/iam_access_key/tasks/main.yml | 808 ++++++++++++++++++ 6 files changed, 1265 insertions(+) create mode 100644 plugins/modules/iam_access_key.py create mode 100644 plugins/modules/iam_access_key_info.py create mode 100644 tests/integration/targets/iam_access_key/aliases create mode 100644 tests/integration/targets/iam_access_key/defaults/main.yml create mode 100644 tests/integration/targets/iam_access_key/meta/main.yml create mode 100644 tests/integration/targets/iam_access_key/tasks/main.yml diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py new file mode 100644 index 00000000000..1d5701e9d74 --- /dev/null +++ b/plugins/modules/iam_access_key.py @@ -0,0 +1,316 @@ +#!/usr/bin/python +# Copyright (c) 2021 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 = r''' +--- +module: iam_access_key +version_added: 2.1.0 +short_description: Manage AWS IAM User access keys +description: + - Manage AWS IAM user access keys. +author: Mark Chappell (@tremble) +options: + user_name: + description: + - The name of the IAM User to which the key belongs. + required: true + type: str + aliases: ['username'] + id: + description: + - The ID of the access key. + - Required when I(state=absent). + - Mutually exclusive with I(rotate_keys). + required: false + type: str + state: + description: + - Create or remove the access key. + - When I(state=present) and I(id) is not defined a new key will be created. + required: false + type: str + default: 'present' + choices: [ 'present', 'absent' ] + active: + description: + - Whether the key should be enabled or disabled. + - Defaults to C(true) when creating a new key. + required: false + type: bool + aliases: ['enabled'] + rotate_keys: + description: + - When there are already 2 access keys attached to the IAM user the oldest + key will be removed and a new key created. + - Ignored if I(state=absent) + - Mutually exclusive with I(id). + required: false + type: bool + default: false + +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 +''' + +EXAMPLES = r''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +- name: Create a new access key + community.aws.iam_access_key: + user_name: example_user + state: present + +- name: Delete the access_key + community.aws.iam_access_key: + name: example_user + access_key_id: AKIA1EXAMPLE1EXAMPLE + state: absent +''' + +RETURN = r''' +access_key: + description: A dictionary containing all the access key information. + returned: When the key exists. + type: complex + contains: + access_key_id: + description: The ID for the access key. + returned: success + type: str + sample: AKIA1EXAMPLE1EXAMPLE + create_date: + description: The date and time, in ISO 8601 date-time format, when the access key was created. + returned: success + type: str + sample: "2021-10-09T13:25:42+00:00" + user_name: + description: The name of the IAM user to which the key is attached. + returned: success + type: str + sample: example_user + status: + description: + - The status of the key. + - C(Active) means it can be used. + - C(Inactive) means it can not be used. + returned: success + type: str + sample: Inactive +secret_access_key: + description: + - The secret access key. + - A secret access key is the equivalent of a password which can not be changed and as such should be considered sensitive data. + - Secret access keys can only be accessed at creation time. + returned: When a new key is created. + type: str + sample: example/Example+EXAMPLE+example/Example +deleted_access_key_id: + description: + - The access key deleted during rotation. + returned: When a key was deleted during the rotation of access keys + type: str + sample: AKIA1EXAMPLE1EXAMPLE +''' + +try: + import botocore +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict + +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.core import normalize_boto3_result +from ansible_collections.amazon.aws.plugins.module_utils.core import scrub_none_parameters +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry + + +def delete_access_key(access_keys, user, access_key_id): + if not access_key_id: + return False + + if access_key_id not in access_keys: + return False + + if module.check_mode: + return True + + try: + client.delete_access_key( + aws_retry=True, + UserName=user, + AccessKeyId=access_key_id, + ) + except is_boto3_error_code('NoSuchEntityException'): + # Generally occurs when race conditions have happened and someone + # deleted the key while we were checking to see if it existed. + return False + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + module.fail_json_aws( + e, msg='Failed to delete access key "{0}" for user "{1}"'.format(access_key_id, user) + ) + + return True + + +def update_access_key(access_keys, user, access_key_id, enabled): + if access_key_id not in access_keys: + module.fail_json( + msg='Access key "{0}" not found attached to User "{1}"'.format(access_key_id, user), + ) + + changes = dict() + access_key = access_keys.get(access_key_id) + + if enabled is not None: + desired_status = 'Active' if enabled else 'Inactive' + if access_key.get('status') != desired_status: + changes['Status'] = desired_status + + if not changes: + return False + + if module.check_mode: + return True + + try: + client.update_access_key( + aws_retry=True, + UserName=user, + AccessKeyId=access_key_id, + **changes + ) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws( + e, changes=changes, + msg='Failed to update access key "{0}" for user "{1}"'.format(access_key_id, user), + ) + return True + + +def create_access_key(access_keys, user, rotate_keys, enabled): + changed = False + oldest_key = False + + if len(access_keys) > 1 and rotate_keys: + sorted_keys = sorted(list(access_keys), key=lambda k: access_keys[k].get('create_date', None)) + oldest_key = sorted_keys[0] + changed |= delete_access_key(access_keys, user, oldest_key) + + if module.check_mode: + if changed: + return dict(deleted_access_key=oldest_key) + return True + + try: + results = client.create_access_key(aws_retry=True, UserName=user) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg='Failed to create access key for user "{0}"'.format(user)) + results = camel_dict_to_snake_dict(results) + access_key = results.get('access_key') + access_key = normalize_boto3_result(access_key) + + # Update settings which can't be managed on creation + if enabled is False: + access_key_id = access_key['access_key_id'] + access_keys = {access_key_id: access_key} + update_access_key(access_keys, user, access_key_id, enabled) + access_key['status'] = 'Inactive' + + if oldest_key: + access_key['deleted_access_key'] = oldest_key + + return access_key + + +def get_access_keys(user): + try: + results = client.list_access_keys(aws_retry=True, UserName=user) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws( + e, msg='Failed to get access keys for user "{0}"'.format(user) + ) + if not results: + return None + + results = camel_dict_to_snake_dict(results) + access_keys = results.get('access_key_metadata', []) + if not access_keys: + return [] + + access_keys = normalize_boto3_result(access_keys) + access_keys = {k['access_key_id']: k for k in access_keys} + return access_keys + + +def main(): + + global module + global client + + argument_spec = dict( + user_name=dict(required=True, type='str', aliases=['username']), + id=dict(required=False, type='str'), + state=dict(required=False, choices=['present', 'absent'], default='present'), + active=dict(required=False, type='bool', aliases=['enabled']), + rotate_keys=dict(required=False, type='bool', default=False), + ) + + required_if = [ + ['state', 'absent', ('id')], + ] + mutually_exclusive = [ + ['rotate_keys', 'id'], + ] + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) + + changed = False + state = module.params.get('state') + user = module.params.get('user_name') + access_key_id = module.params.get('id') + rotate_keys = module.params.get('rotate_keys') + enabled = module.params.get('active') + + access_keys = get_access_keys(user) + results = dict() + + if state == 'absent': + changed |= delete_access_key(access_keys, user, access_key_id) + else: + # If we have an ID then we should try to update it + if access_key_id: + changed |= update_access_key(access_keys, user, access_key_id, enabled) + access_keys = get_access_keys(user) + results['access_key'] = access_keys.get(access_key_id, None) + # Otherwise we try to create a new one + else: + secret_key = create_access_key(access_keys, user, rotate_keys, enabled) + if isinstance(secret_key, bool): + changed |= secret_key + else: + changed = True + results['access_key_id'] = secret_key.get('access_key_id', None) + results['secret_access_key'] = secret_key.pop('secret_access_key', None) + results['deleted_access_key_id'] = secret_key.pop('deleted_access_key', None) + if secret_key: + results['access_key'] = secret_key + results = scrub_none_parameters(results) + + module.exit_json(changed=changed, **results) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py new file mode 100644 index 00000000000..9251cb846f6 --- /dev/null +++ b/plugins/modules/iam_access_key_info.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# Copyright (c) 2021 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 = r''' +--- +module: iam_access_key_info +version_added: 2.1.0 +short_description: fetch information about AWS IAM User access keys +description: + - 'Fetches information AWS IAM user access keys.' + - 'Note: It is not possible to fetch the secret access key.' +author: Mark Chappell (@tremble) +options: + user_name: + description: + - The name of the IAM User to which the keys belong. + required: true + type: str + aliases: ['username'] + +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 +''' + +EXAMPLES = r''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +- name: Fetch Access keys for a user + community.aws.iam_access_key_info: + user_name: example_user +''' + +RETURN = r''' +access_key: + description: A dictionary containing all the access key information. + returned: When the key exists. + type: list + elements: dict + contains: + access_key_id: + description: The ID for the access key. + returned: success + type: str + sample: AKIA1EXAMPLE1EXAMPLE + create_date: + description: The date and time, in ISO 8601 date-time format, when the access key was created. + returned: success + type: str + sample: "2021-10-09T13:25:42+00:00" + user_name: + description: The name of the IAM user to which the key is attached. + returned: success + type: str + sample: example_user + status: + description: + - The status of the key. + - C(Active) means it can be used. + - C(Inactive) means it can not be used. + returned: success + type: str + sample: Inactive +''' + +try: + import botocore +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict + +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.core import normalize_boto3_result +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry + + +def get_access_keys(user): + try: + results = client.list_access_keys(aws_retry=True, UserName=user) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws( + e, msg='Failed to get access keys for user "{0}"'.format(user) + ) + if not results: + return None + + results = camel_dict_to_snake_dict(results) + access_keys = results.get('access_key_metadata', []) + if not access_keys: + return [] + + access_keys = normalize_boto3_result(access_keys) + access_keys = sorted(access_keys, key=lambda d: d.get('create_date', None)) + return access_keys + + +def main(): + + global module + global client + + argument_spec = dict( + user_name=dict(required=True, type='str', aliases=['username']), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) + + changed = False + user = module.params.get('user_name') + access_keys = get_access_keys(user) + + module.exit_json(changed=changed, access_keys=access_keys) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/iam_access_key/aliases b/tests/integration/targets/iam_access_key/aliases new file mode 100644 index 00000000000..ffceccfcc41 --- /dev/null +++ b/tests/integration/targets/iam_access_key/aliases @@ -0,0 +1,9 @@ +# reason: missing-policy +# It should be possible to test iam_user by limiting which policies can be +# attached to the users. +# Careful review is needed prior to adding this to the main CI. +unsupported + +cloud/aws + +iam_access_key_info diff --git a/tests/integration/targets/iam_access_key/defaults/main.yml b/tests/integration/targets/iam_access_key/defaults/main.yml new file mode 100644 index 00000000000..eaaa3523e19 --- /dev/null +++ b/tests/integration/targets/iam_access_key/defaults/main.yml @@ -0,0 +1,2 @@ +--- +test_user: '{{ resource_prefix }}' diff --git a/tests/integration/targets/iam_access_key/meta/main.yml b/tests/integration/targets/iam_access_key/meta/main.yml new file mode 100644 index 00000000000..1f64f1169a9 --- /dev/null +++ b/tests/integration/targets/iam_access_key/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - prepare_tests + - setup_ec2 diff --git a/tests/integration/targets/iam_access_key/tasks/main.yml b/tests/integration/targets/iam_access_key/tasks/main.yml new file mode 100644 index 00000000000..a7fcc633ce9 --- /dev/null +++ b/tests/integration/targets/iam_access_key/tasks/main.yml @@ -0,0 +1,808 @@ +--- +- name: AWS AuthN details + 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 }}" + collections: + - amazon.aws + - community.aws + block: + # ================================================================================== + # Preparation + # ================================================================================== + # We create an IAM user with no attached permissions. The *only* thing the + # user will be able to do is call sts.get_caller_identity + # https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html + - name: Create test user + iam_user: + name: '{{ test_user }}' + state: present + register: iam_user + + - assert: + that: + - iam_user is successful + - iam_user is changed + + # ================================================================================== + + - name: Fetch IAM key info (no keys) + iam_access_key_info: + user_name: '{{ test_user }}' + register: access_key_info + + - assert: + that: + - access_key_info is successful + - '"access_keys" in access_key_info' + - access_key_info.access_keys | length == 0 + + # ================================================================================== + + - name: Create a key (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + state: present + register: create_key_1 + check_mode: true + + - assert: + that: + - create_key_1 is successful + - create_key_1 is changed + + - name: Create a key + iam_access_key: + user_name: '{{ test_user }}' + state: present + register: create_key_1 + + - assert: + that: + - create_key_1 is successful + - create_key_1 is changed + - '"access_key" in create_key_1' + - '"secret_access_key" in create_key_1' + - '"deleted_access_key_id" not in create_key_1' + - '"access_key_id" in create_key_1.access_key' + - '"create_date" in create_key_1.access_key' + - '"user_name" in create_key_1.access_key' + - '"status" in create_key_1.access_key' + - create_key_1.access_key.user_name == test_user + - create_key_1.access_key.status == 'Active' + + - name: Fetch IAM key info (1 key) + iam_access_key_info: + user_name: '{{ test_user }}' + register: access_key_info + + - assert: + that: + - access_key_info is successful + - '"access_keys" in access_key_info' + - access_key_info.access_keys | length == 1 + - '"access_key_id" in access_key_1' + - '"create_date" in access_key_1' + - '"user_name" in access_key_1' + - '"status" in access_key_1' + - access_key_1.user_name == test_user + - access_key_1.access_key_id == create_key_1.access_key.access_key_id + - access_key_1.create_date == create_key_1.access_key.create_date + - access_key_1.status == 'Active' + vars: + access_key_1: '{{ access_key_info.access_keys[0] }}' + + # ================================================================================== + + - name: Create a second key (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + state: present + register: create_key_2 + check_mode: true + + - assert: + that: + - create_key_2 is successful + - create_key_2 is changed + + - name: Create a second key + iam_access_key: + user_name: '{{ test_user }}' + state: present + register: create_key_2 + + - assert: + that: + - create_key_2 is successful + - create_key_2 is changed + - '"access_key" in create_key_2' + - '"secret_access_key" in create_key_2' + - '"deleted_access_key_id" not in create_key_2' + - '"access_key_id" in create_key_2.access_key' + - '"create_date" in create_key_2.access_key' + - '"user_name" in create_key_2.access_key' + - '"status" in create_key_2.access_key' + - create_key_2.access_key.user_name == test_user + - create_key_2.access_key.status == 'Active' + + - name: Fetch IAM key info (2 keys) + iam_access_key_info: + user_name: '{{ test_user }}' + register: access_key_info + + - assert: + that: + - access_key_info is successful + - '"access_keys" in access_key_info' + - access_key_info.access_keys | length == 2 + - '"access_key_id" in access_key_1' + - '"create_date" in access_key_1' + - '"user_name" in access_key_1' + - '"status" in access_key_1' + - access_key_1.user_name == test_user + - access_key_1.access_key_id == create_key_1.access_key.access_key_id + - access_key_1.create_date == create_key_1.access_key.create_date + - access_key_1.status == 'Active' + - '"access_key_id" in access_key_2' + - '"create_date" in access_key_2' + - '"user_name" in access_key_2' + - '"status" in access_key_2' + - access_key_2.user_name == test_user + - access_key_2.access_key_id == create_key_2.access_key.access_key_id + - access_key_2.create_date == create_key_2.access_key.create_date + - access_key_2.status == 'Active' + vars: + access_key_1: '{{ access_key_info.access_keys[0] }}' + access_key_2: '{{ access_key_info.access_keys[1] }}' + + # ================================================================================== + + # We don't block the attempt to create a third access key - should AWS change + # the limits this will "JustWork". + + # - name: Create a third key (check_mode) + # iam_access_key: + # user_name: '{{ test_user }}' + # state: present + # register: create_key_3 + # ignore_errors: True + # check_mode: true + + # - assert: + # that: + # - create_key_3 is successful + # - create_key_3 is changed + + - name: Create a third key without rotation + iam_access_key: + user_name: '{{ test_user }}' + state: present + register: create_key_3 + ignore_errors: True + + - assert: + that: + # If Amazon update the limits we may need to change the expectation here. + - create_key_3 is failed + + - name: Fetch IAM key info (2 keys - not changed) + iam_access_key_info: + user_name: '{{ test_user }}' + register: access_key_info + + - assert: + that: + - access_key_info is successful + - '"access_keys" in access_key_info' + - access_key_info.access_keys | length == 2 + - '"access_key_id" in access_key_1' + - '"create_date" in access_key_1' + - '"user_name" in access_key_1' + - '"status" in access_key_1' + - access_key_1.user_name == test_user + - access_key_1.access_key_id == create_key_1.access_key.access_key_id + - access_key_1.create_date == create_key_1.access_key.create_date + - access_key_1.status == 'Active' + - '"access_key_id" in access_key_2' + - '"create_date" in access_key_2' + - '"user_name" in access_key_2' + - '"status" in access_key_2' + - access_key_2.user_name == test_user + - access_key_2.access_key_id == create_key_2.access_key.access_key_id + - access_key_2.create_date == create_key_2.access_key.create_date + - access_key_2.status == 'Active' + vars: + access_key_1: '{{ access_key_info.access_keys[0] }}' + access_key_2: '{{ access_key_info.access_keys[1] }}' + + # ================================================================================== + + - name: Create a third key - rotation enabled (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + state: present + rotate_keys: true + register: create_key_3 + check_mode: true + + - assert: + that: + - create_key_3 is successful + - create_key_3 is changed + - '"deleted_access_key_id" in create_key_3' + - create_key_3.deleted_access_key_id == create_key_1.access_key.access_key_id + + - name: Create a second key + iam_access_key: + user_name: '{{ test_user }}' + state: present + rotate_keys: true + register: create_key_3 + + - assert: + that: + - create_key_3 is successful + - create_key_3 is changed + - '"access_key" in create_key_3' + - '"secret_access_key" in create_key_3' + - '"deleted_access_key_id" in create_key_3' + - create_key_3.deleted_access_key_id == create_key_1.access_key.access_key_id + - '"access_key_id" in create_key_3.access_key' + - '"create_date" in create_key_3.access_key' + - '"user_name" in create_key_3.access_key' + - '"status" in create_key_3.access_key' + - create_key_3.access_key.user_name == test_user + - create_key_3.access_key.status == 'Active' + + - name: Fetch IAM key info (2 keys - oldest rotated) + iam_access_key_info: + user_name: '{{ test_user }}' + register: access_key_info + + - assert: + that: + - access_key_info is successful + - '"access_keys" in access_key_info' + - access_key_info.access_keys | length == 2 + - '"access_key_id" in access_key_1' + - '"create_date" in access_key_1' + - '"user_name" in access_key_1' + - '"status" in access_key_1' + - access_key_1.user_name == test_user + - access_key_1.access_key_id == create_key_2.access_key.access_key_id + - access_key_1.create_date == create_key_2.access_key.create_date + - access_key_1.status == 'Active' + - '"access_key_id" in access_key_2' + - '"create_date" in access_key_2' + - '"user_name" in access_key_2' + - '"status" in access_key_2' + - access_key_2.user_name == test_user + - access_key_2.access_key_id == create_key_3.access_key.access_key_id + - access_key_2.create_date == create_key_3.access_key.create_date + - access_key_2.status == 'Active' + vars: + access_key_1: '{{ access_key_info.access_keys[0] }}' + access_key_2: '{{ access_key_info.access_keys[1] }}' + + # ================================================================================== + + - name: Disable third key (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + enabled: False + register: disable_key + check_mode: true + + - assert: + that: + - disable_key is successful + - disable_key is changed + + - name: Disable third key + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + enabled: False + register: disable_key + + - assert: + that: + - disable_key is successful + - disable_key is changed + - '"access_key" in disable_key' + - '"secret_access_key" not in disable_key' + - '"deleted_access_key_id" not in disable_key' + - '"access_key_id" in disable_key.access_key' + - '"create_date" in disable_key.access_key' + - '"user_name" in disable_key.access_key' + - '"status" in disable_key.access_key' + - disable_key.access_key.user_name == test_user + - disable_key.access_key.status == 'Inactive' + + - name: Disable third key - idempotency (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + enabled: False + register: disable_key + check_mode: true + + - assert: + that: + - disable_key is successful + - disable_key is not changed + + - name: Disable third key - idempotency + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + enabled: False + register: disable_key + + - assert: + that: + - disable_key is successful + - disable_key is not changed + - '"access_key" in disable_key' + - '"secret_access_key" not in disable_key' + - '"deleted_access_key_id" not in disable_key' + - '"access_key_id" in disable_key.access_key' + - '"create_date" in disable_key.access_key' + - '"user_name" in disable_key.access_key' + - '"status" in disable_key.access_key' + - disable_key.access_key.user_name == test_user + - disable_key.access_key.status == 'Inactive' + + - name: Fetch IAM key info (2 keys - 1 disabled) + iam_access_key_info: + user_name: '{{ test_user }}' + register: access_key_info + + - assert: + that: + - access_key_info is successful + - '"access_keys" in access_key_info' + - access_key_info.access_keys | length == 2 + - '"access_key_id" in access_key_1' + - '"create_date" in access_key_1' + - '"user_name" in access_key_1' + - '"status" in access_key_1' + - access_key_1.user_name == test_user + - access_key_1.access_key_id == create_key_2.access_key.access_key_id + - access_key_1.create_date == create_key_2.access_key.create_date + - access_key_1.status == 'Active' + - '"access_key_id" in access_key_2' + - '"create_date" in access_key_2' + - '"user_name" in access_key_2' + - '"status" in access_key_2' + - access_key_2.user_name == test_user + - access_key_2.access_key_id == create_key_3.access_key.access_key_id + - access_key_2.create_date == create_key_3.access_key.create_date + - access_key_2.status == 'Inactive' + vars: + access_key_1: '{{ access_key_info.access_keys[0] }}' + access_key_2: '{{ access_key_info.access_keys[1] }}' + + # ================================================================================== + + - name: Touch third key - no change (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + register: touch_key + check_mode: true + + - assert: + that: + - touch_key is successful + - touch_key is not changed + + - name: Touch third key - no change + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + register: touch_key + + - assert: + that: + - touch_key is successful + - touch_key is not changed + - '"access_key" in touch_key' + - '"secret_access_key" not in touch_key' + - '"deleted_access_key_id" not in touch_key' + - '"access_key_id" in touch_key.access_key' + - '"create_date" in touch_key.access_key' + - '"user_name" in touch_key.access_key' + - '"status" in touch_key.access_key' + - touch_key.access_key.user_name == test_user + - touch_key.access_key.status == 'Inactive' + + # ================================================================================== + + - name: Enable third key (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + enabled: True + register: enable_key + check_mode: true + + - assert: + that: + - enable_key is successful + - enable_key is changed + + - name: Enable third key + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + enabled: True + register: enable_key + + - assert: + that: + - enable_key is successful + - enable_key is changed + - '"access_key" in enable_key' + - '"secret_access_key" not in enable_key' + - '"deleted_access_key_id" not in enable_key' + - '"access_key_id" in enable_key.access_key' + - '"create_date" in enable_key.access_key' + - '"user_name" in enable_key.access_key' + - '"status" in enable_key.access_key' + - enable_key.access_key.user_name == test_user + - enable_key.access_key.status == 'Active' + + - name: Enable third key - idempotency (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + enabled: True + register: enable_key + check_mode: true + + - assert: + that: + - enable_key is successful + - enable_key is not changed + + - name: Enable third key - idempotency + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + enabled: True + register: enable_key + + - assert: + that: + - enable_key is successful + - enable_key is not changed + - '"access_key" in enable_key' + - '"secret_access_key" not in enable_key' + - '"deleted_access_key_id" not in enable_key' + - '"access_key_id" in enable_key.access_key' + - '"create_date" in enable_key.access_key' + - '"user_name" in enable_key.access_key' + - '"status" in enable_key.access_key' + - enable_key.access_key.user_name == test_user + - enable_key.access_key.status == 'Active' + + # ================================================================================== + + - name: Touch third key again - no change (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + register: touch_key + check_mode: true + + - assert: + that: + - touch_key is successful + - touch_key is not changed + + - name: Touch third key again - no change + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + register: touch_key + + - assert: + that: + - touch_key is successful + - touch_key is not changed + - '"access_key" in touch_key' + - '"secret_access_key" not in touch_key' + - '"deleted_access_key_id" not in touch_key' + - '"access_key_id" in touch_key.access_key' + - '"create_date" in touch_key.access_key' + - '"user_name" in touch_key.access_key' + - '"status" in touch_key.access_key' + - touch_key.access_key.user_name == test_user + - touch_key.access_key.status == 'Active' + + # ================================================================================== + + - name: Re-Disable third key + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + enabled: False + register: redisable_key + + - assert: + that: + - redisable_key is successful + - redisable_key is changed + - redisable_key.access_key.status == 'Inactive' + + - pause: + seconds: 10 + + # ================================================================================== + + - name: Test GetCallerIdentity - Key 2 + aws_caller_info: + aws_access_key: "{{ create_key_2.access_key.access_key_id }}" + aws_secret_key: "{{ create_key_2.secret_access_key }}" + security_token: "{{ omit }}" + register: caller_identity_2 + + - assert: + that: + - caller_identity_2 is successful + - caller_identity_2.arn == iam_user.iam_user.user.arn + + - name: Test GetCallerIdentity - Key 1 (gone) + aws_caller_info: + aws_access_key: "{{ create_key_1.access_key.access_key_id }}" + aws_secret_key: "{{ create_key_1.secret_access_key }}" + security_token: "{{ omit }}" + register: caller_identity_1 + ignore_errors: true + + - assert: + that: + - caller_identity_1 is failed + - caller_identity_1.error.code == 'InvalidClientTokenId' + + - name: Test GetCallerIdentity - Key 3 (disabled) + aws_caller_info: + aws_access_key: "{{ create_key_3.access_key.access_key_id }}" + aws_secret_key: "{{ create_key_3.secret_access_key }}" + security_token: "{{ omit }}" + register: caller_identity_3 + ignore_errors: true + + - assert: + that: + - caller_identity_3 is failed + - caller_identity_3.error.code == 'InvalidClientTokenId' + + # ================================================================================== + + - name: Delete active key (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_2.access_key.access_key_id }}' + state: absent + register: delete_active_key + check_mode: true + + - assert: + that: + - delete_active_key is successful + - delete_active_key is changed + + - name: Delete active key + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_2.access_key.access_key_id }}' + state: absent + register: delete_active_key + + - assert: + that: + - delete_active_key is successful + - delete_active_key is changed + + - name: Delete active key - idempotency (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_2.access_key.access_key_id }}' + state: absent + register: delete_active_key + check_mode: true + + - assert: + that: + - delete_active_key is successful + - delete_active_key is not changed + + - name: Delete active key - idempotency + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_2.access_key.access_key_id }}' + state: absent + register: delete_active_key + + - assert: + that: + - delete_active_key is successful + - delete_active_key is not changed + + # ================================================================================== + + - name: Delete inactive key (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + state: absent + register: delete_inactive_key + check_mode: true + + - assert: + that: + - delete_inactive_key is successful + - delete_inactive_key is changed + + - name: Delete inactive key + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + state: absent + register: delete_inactive_key + + - assert: + that: + - delete_inactive_key is successful + - delete_inactive_key is changed + + - name: Delete inactive key - idempotency (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + state: absent + register: delete_inactive_key + check_mode: true + + - assert: + that: + - delete_inactive_key is successful + - delete_inactive_key is not changed + + - name: Delete inactive key - idempotency + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_3.access_key.access_key_id }}' + state: absent + register: delete_inactive_key + + - assert: + that: + - delete_inactive_key is successful + - delete_inactive_key is not changed + + # ================================================================================== + + - name: Fetch IAM key info (no keys) + iam_access_key_info: + user_name: '{{ test_user }}' + register: access_key_info + + - assert: + that: + - access_key_info is successful + - '"access_keys" in access_key_info' + - access_key_info.access_keys | length == 0 + + # ================================================================================== + + - name: Create an inactive key (check_mode) + iam_access_key: + user_name: '{{ test_user }}' + state: present + enabled: false + register: create_key_4 + check_mode: true + + - assert: + that: + - create_key_4 is successful + - create_key_4 is changed + + - name: Create a key + iam_access_key: + user_name: '{{ test_user }}' + state: present + enabled: false + register: create_key_4 + + - assert: + that: + - create_key_4 is successful + - create_key_4 is changed + - '"access_key" in create_key_4' + - '"secret_access_key" in create_key_4' + - '"deleted_access_key_id" not in create_key_4' + - '"access_key_id" in create_key_4.access_key' + - '"create_date" in create_key_4.access_key' + - '"user_name" in create_key_4.access_key' + - '"status" in create_key_4.access_key' + - create_key_4.access_key.user_name == test_user + - create_key_4.access_key.status == 'Inactive' + + - name: Fetch IAM key info (1 inactive key) + iam_access_key_info: + user_name: '{{ test_user }}' + register: access_key_info + + - assert: + that: + - access_key_info is successful + - '"access_keys" in access_key_info' + - access_key_info.access_keys | length == 1 + - '"access_key_id" in access_key_1' + - '"create_date" in access_key_1' + - '"user_name" in access_key_1' + - '"status" in access_key_1' + - access_key_1.user_name == test_user + - access_key_1.access_key_id == create_key_4.access_key.access_key_id + - access_key_1.create_date == create_key_4.access_key.create_date + - access_key_1.status == 'Inactive' + vars: + access_key_1: '{{ access_key_info.access_keys[0] }}' + + # We already tested the idempotency of disabling keys, use this to verify that + # the key is disabled + - name: Disable new key + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_4.access_key.access_key_id }}' + enabled: False + register: disable_new_key + + - assert: + that: + - disable_new_key is successful + - disable_new_key is not changed + - '"access_key" in disable_new_key' + + # ================================================================================== + # Cleanup + + - name: Delete new key + iam_access_key: + user_name: '{{ test_user }}' + id: '{{ create_key_4.access_key.access_key_id }}' + state: absent + register: delete_new_key + + - assert: + that: + - delete_new_key is successful + - delete_new_key is changed + + - name: Remove test user + iam_user: + name: '{{ test_user }}' + state: absent + register: delete_user + + - assert: + that: + - delete_user is successful + - delete_user is changed + + always: + + - name: Remove test user + iam_user: + name: '{{ test_user }}' + state: absent + ignore_errors: yes From 1f0fd56360437c1574e248354fc56d5922e820a5 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Fri, 22 Apr 2022 11:44:07 +0200 Subject: [PATCH 02/18] Integration test dependency cleanup (#1086) Integration test dependency cleanup SUMMARY remove dependencies on setup_remote_tmp_dir where it's not used (often just copy & paste from another test) remove setup_ec2 (no main.yml means it's not doing anything) remove prepare_tests (empty main.yml means it's not doing anything) ISSUE TYPE Feature Pull Request COMPONENT NAME tests/integration/targets ADDITIONAL INFORMATION By cleaning up what we have we reduce the chance of people copying things about "because that's what test XYZ did". Reviewed-by: Alina Buzachis Reviewed-by: Mark Woolley This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/dd12046a1e2d5f39692b1890ff07e06c56b3bf0e --- tests/integration/targets/iam_access_key/meta/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integration/targets/iam_access_key/meta/main.yml b/tests/integration/targets/iam_access_key/meta/main.yml index 1f64f1169a9..32cf5dda7ed 100644 --- a/tests/integration/targets/iam_access_key/meta/main.yml +++ b/tests/integration/targets/iam_access_key/meta/main.yml @@ -1,3 +1 @@ -dependencies: - - prepare_tests - - setup_ec2 +dependencies: [] From a92b233c50a1b357db59f7caa6590d7f1a4e7d8a Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Wed, 5 Oct 2022 17:04:40 +0200 Subject: [PATCH 03/18] Update extends_documentation_fragment with amazon.aws.boto3 (#1459) Update extends_documentation_fragment with amazon.aws.boto3 Depends-On: ansible/ansible-zuul-jobs#1654 SUMMARY As per ansible-collections/amazon.aws#985 add amazon.aws.boto3. ISSUE TYPE Docs Pull Request COMPONENT NAME several Reviewed-by: Jill R Reviewed-by: Mark Chappell Reviewed-by: Markus Bergholz This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/bd3c03fcba0848f593b86309740fa73e986a9646 --- plugins/modules/iam_access_key.py | 1 + plugins/modules/iam_access_key_info.py | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index 1d5701e9d74..3207741ab94 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -56,6 +56,7 @@ extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 +- amazon.aws.boto3 ''' EXAMPLES = r''' diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py index 9251cb846f6..91429eff940 100644 --- a/plugins/modules/iam_access_key_info.py +++ b/plugins/modules/iam_access_key_info.py @@ -26,6 +26,7 @@ extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 +- amazon.aws.boto3 ''' EXAMPLES = r''' From 48bf7437576e3718ef103746d4348ce267d6e8ca Mon Sep 17 00:00:00 2001 From: Bikouo Aubin <79859644+abikouo@users.noreply.github.com> Date: Tue, 10 Jan 2023 19:22:13 +0100 Subject: [PATCH 04/18] Ansible User-Agent identification for community.aws (#1632) Ansible User-Agent identification for community.aws SUMMARY The value will be similar to this APN/1.0 Ansible/2.14.1 community.aws/6.0.0-dev0 ISSUE TYPE Feature Pull Request Reviewed-by: Mark Chappell Reviewed-by: Bikouo Aubin Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/a8cbce24071bcc62fe4594c38aff1baf18bd2862 --- plugins/modules/iam_access_key.py | 2 +- plugins/modules/iam_access_key_info.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index 3207741ab94..ab3e9110604 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -126,7 +126,7 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code from ansible_collections.amazon.aws.plugins.module_utils.core import normalize_boto3_result from ansible_collections.amazon.aws.plugins.module_utils.core import scrub_none_parameters diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py index 91429eff940..9d7363b420a 100644 --- a/plugins/modules/iam_access_key_info.py +++ b/plugins/modules/iam_access_key_info.py @@ -76,7 +76,7 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.core import normalize_boto3_result from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry From 7c6c9f71dc2509cddd5815a129e28405e51d9fd5 Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Wed, 8 Feb 2023 07:34:38 +0100 Subject: [PATCH 05/18] fix (#1711) iam_access_key - fix example docs SUMMARY Closes #1710 ISSUE TYPE Docs Pull Request COMPONENT NAME iam_access_key Reviewed-by: Mark Chappell This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/88872b39fba1b979780d260ea5b0f9f5dcd5aeae --- plugins/modules/iam_access_key.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index ab3e9110604..32220a216e3 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -69,8 +69,8 @@ - name: Delete the access_key community.aws.iam_access_key: - name: example_user - access_key_id: AKIA1EXAMPLE1EXAMPLE + user_name: example_user + id: AKIA1EXAMPLE1EXAMPLE state: absent ''' From 5989a4fbf0c5b61771ad894ac5da4214ed9ce748 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Wed, 8 Mar 2023 12:07:26 +0100 Subject: [PATCH 06/18] Cleanup headers and imports (#1738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleanup headers and imports SUMMARY Mass update of imports, docs fragments and file headers Many of the amazon.aws module_utils and docs fragments got moved about, update community.aws to reflect this. Consistently apply the comment headers as documented at https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_documenting.html#python-shebang-utf-8-coding ISSUE TYPE Docs Pull Request Feature Pull Request COMPONENT NAME ADDITIONAL INFORMATION Header cleanup based upon: https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_documenting.html#python-shebang-utf-8-coding Begin your Ansible module with #!/usr/bin/python - this “shebang” allows ansible_python_interpreter to work. Follow the shebang immediately with # -*- coding: utf-8 -*- to clarify that the file is UTF-8 encoded. and https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_documenting.html#copyright-and-license After the shebang and UTF-8 coding, add a copyright line with the original copyright holder and a license declaration. The license declaration should be ONLY one line, not the full GPL prefix. ... Additions to the module (for instance, rewrites) are not permitted to add additional copyright lines other than the default copyright statement if missing: Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/a4f20bf114bfab19b1c84c4ecf42efd5614ab80c --- plugins/modules/iam_access_key.py | 36 +++++++++++++------------- plugins/modules/iam_access_key_info.py | 32 +++++++++++------------ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index 32220a216e3..af472fbe8c6 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -1,19 +1,18 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + # Copyright (c) 2021 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 = r''' +DOCUMENTATION = r""" --- module: iam_access_key version_added: 2.1.0 short_description: Manage AWS IAM User access keys description: - Manage AWS IAM user access keys. -author: Mark Chappell (@tremble) +author: + - Mark Chappell (@tremble) options: user_name: description: @@ -54,12 +53,12 @@ default: false extends_documentation_fragment: -- amazon.aws.aws -- amazon.aws.ec2 -- amazon.aws.boto3 -''' + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" -EXAMPLES = r''' +EXAMPLES = r""" # Note: These examples do not set authentication details, see the AWS Guide for details. - name: Create a new access key @@ -72,9 +71,9 @@ user_name: example_user id: AKIA1EXAMPLE1EXAMPLE state: absent -''' +""" -RETURN = r''' +RETURN = r""" access_key: description: A dictionary containing all the access key information. returned: When the key exists. @@ -117,7 +116,7 @@ returned: When a key was deleted during the rotation of access keys type: str sample: AKIA1EXAMPLE1EXAMPLE -''' +""" try: import botocore @@ -126,11 +125,12 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.botocore import normalize_boto3_result +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.transformation import scrub_none_parameters + from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code -from ansible_collections.amazon.aws.plugins.module_utils.core import normalize_boto3_result -from ansible_collections.amazon.aws.plugins.module_utils.core import scrub_none_parameters -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry def delete_access_key(access_keys, user, access_key_id): diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py index 9d7363b420a..6573e657a18 100644 --- a/plugins/modules/iam_access_key_info.py +++ b/plugins/modules/iam_access_key_info.py @@ -1,12 +1,10 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + # Copyright (c) 2021 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 = r''' +DOCUMENTATION = r""" --- module: iam_access_key_info version_added: 2.1.0 @@ -14,7 +12,8 @@ description: - 'Fetches information AWS IAM user access keys.' - 'Note: It is not possible to fetch the secret access key.' -author: Mark Chappell (@tremble) +author: + - Mark Chappell (@tremble) options: user_name: description: @@ -24,20 +23,20 @@ aliases: ['username'] extends_documentation_fragment: -- amazon.aws.aws -- amazon.aws.ec2 -- amazon.aws.boto3 -''' + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" -EXAMPLES = r''' +EXAMPLES = r""" # Note: These examples do not set authentication details, see the AWS Guide for details. - name: Fetch Access keys for a user community.aws.iam_access_key_info: user_name: example_user -''' +""" -RETURN = r''' +RETURN = r""" access_key: description: A dictionary containing all the access key information. returned: When the key exists. @@ -67,7 +66,7 @@ returned: success type: str sample: Inactive -''' +""" try: import botocore @@ -76,9 +75,10 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.botocore import normalize_boto3_result +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry + from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.core import normalize_boto3_result -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry def get_access_keys(user): From 5e8380744129dc4ad78f40234db37bf65a38083c Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Wed, 26 Apr 2023 19:26:07 +0200 Subject: [PATCH 07/18] Big Black PR (#1784) * Black prep * Black * changelog * Fix pylint unused-import in tests * Split SSM connection plugin changes * disable glue tests - bucket's missing * Disable s3_logging and s3_sync tests This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/2c4575c248776c65d66b06cd60fa09b0dae1cd6f --- plugins/modules/iam_access_key.py | 88 ++++++++++++-------------- plugins/modules/iam_access_key_info.py | 22 +++---- 2 files changed, 49 insertions(+), 61 deletions(-) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index af472fbe8c6..a8f03d7bced 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -149,14 +149,15 @@ def delete_access_key(access_keys, user, access_key_id): UserName=user, AccessKeyId=access_key_id, ) - except is_boto3_error_code('NoSuchEntityException'): + except is_boto3_error_code("NoSuchEntityException"): # Generally occurs when race conditions have happened and someone # deleted the key while we were checking to see if it existed. return False - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except - module.fail_json_aws( - e, msg='Failed to delete access key "{0}" for user "{1}"'.format(access_key_id, user) - ) + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, msg='Failed to delete access key "{0}" for user "{1}"'.format(access_key_id, user)) return True @@ -171,9 +172,9 @@ def update_access_key(access_keys, user, access_key_id, enabled): access_key = access_keys.get(access_key_id) if enabled is not None: - desired_status = 'Active' if enabled else 'Inactive' - if access_key.get('status') != desired_status: - changes['Status'] = desired_status + desired_status = "Active" if enabled else "Inactive" + if access_key.get("status") != desired_status: + changes["Status"] = desired_status if not changes: return False @@ -182,15 +183,11 @@ def update_access_key(access_keys, user, access_key_id, enabled): return True try: - client.update_access_key( - aws_retry=True, - UserName=user, - AccessKeyId=access_key_id, - **changes - ) + client.update_access_key(aws_retry=True, UserName=user, AccessKeyId=access_key_id, **changes) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( - e, changes=changes, + e, + changes=changes, msg='Failed to update access key "{0}" for user "{1}"'.format(access_key_id, user), ) return True @@ -201,7 +198,7 @@ def create_access_key(access_keys, user, rotate_keys, enabled): oldest_key = False if len(access_keys) > 1 and rotate_keys: - sorted_keys = sorted(list(access_keys), key=lambda k: access_keys[k].get('create_date', None)) + sorted_keys = sorted(list(access_keys), key=lambda k: access_keys[k].get("create_date", None)) oldest_key = sorted_keys[0] changed |= delete_access_key(access_keys, user, oldest_key) @@ -215,18 +212,18 @@ def create_access_key(access_keys, user, rotate_keys, enabled): except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg='Failed to create access key for user "{0}"'.format(user)) results = camel_dict_to_snake_dict(results) - access_key = results.get('access_key') + access_key = results.get("access_key") access_key = normalize_boto3_result(access_key) # Update settings which can't be managed on creation if enabled is False: - access_key_id = access_key['access_key_id'] + access_key_id = access_key["access_key_id"] access_keys = {access_key_id: access_key} update_access_key(access_keys, user, access_key_id, enabled) - access_key['status'] = 'Inactive' + access_key["status"] = "Inactive" if oldest_key: - access_key['deleted_access_key'] = oldest_key + access_key["deleted_access_key"] = oldest_key return access_key @@ -235,67 +232,64 @@ def get_access_keys(user): try: results = client.list_access_keys(aws_retry=True, UserName=user) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws( - e, msg='Failed to get access keys for user "{0}"'.format(user) - ) + module.fail_json_aws(e, msg='Failed to get access keys for user "{0}"'.format(user)) if not results: return None results = camel_dict_to_snake_dict(results) - access_keys = results.get('access_key_metadata', []) + access_keys = results.get("access_key_metadata", []) if not access_keys: return [] access_keys = normalize_boto3_result(access_keys) - access_keys = {k['access_key_id']: k for k in access_keys} + access_keys = {k["access_key_id"]: k for k in access_keys} return access_keys def main(): - global module global client argument_spec = dict( - user_name=dict(required=True, type='str', aliases=['username']), - id=dict(required=False, type='str'), - state=dict(required=False, choices=['present', 'absent'], default='present'), - active=dict(required=False, type='bool', aliases=['enabled']), - rotate_keys=dict(required=False, type='bool', default=False), + user_name=dict(required=True, type="str", aliases=["username"]), + id=dict(required=False, type="str"), + state=dict(required=False, choices=["present", "absent"], default="present"), + active=dict(required=False, type="bool", aliases=["enabled"]), + rotate_keys=dict(required=False, type="bool", default=False), ) required_if = [ - ['state', 'absent', ('id')], + ["state", "absent", ("id")], ] mutually_exclusive = [ - ['rotate_keys', 'id'], + ["rotate_keys", "id"], ] module = AnsibleAWSModule( argument_spec=argument_spec, - supports_check_mode=True + supports_check_mode=True, ) - client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) + client = module.client("iam", retry_decorator=AWSRetry.jittered_backoff()) changed = False - state = module.params.get('state') - user = module.params.get('user_name') - access_key_id = module.params.get('id') - rotate_keys = module.params.get('rotate_keys') - enabled = module.params.get('active') + state = module.params.get("state") + user = module.params.get("user_name") + access_key_id = module.params.get("id") + rotate_keys = module.params.get("rotate_keys") + enabled = module.params.get("active") access_keys = get_access_keys(user) results = dict() - if state == 'absent': + if state == "absent": changed |= delete_access_key(access_keys, user, access_key_id) else: # If we have an ID then we should try to update it if access_key_id: changed |= update_access_key(access_keys, user, access_key_id, enabled) access_keys = get_access_keys(user) - results['access_key'] = access_keys.get(access_key_id, None) + results["access_key"] = access_keys.get(access_key_id, None) # Otherwise we try to create a new one else: secret_key = create_access_key(access_keys, user, rotate_keys, enabled) @@ -303,15 +297,15 @@ def main(): changed |= secret_key else: changed = True - results['access_key_id'] = secret_key.get('access_key_id', None) - results['secret_access_key'] = secret_key.pop('secret_access_key', None) - results['deleted_access_key_id'] = secret_key.pop('deleted_access_key', None) + results["access_key_id"] = secret_key.get("access_key_id", None) + results["secret_access_key"] = secret_key.pop("secret_access_key", None) + results["deleted_access_key_id"] = secret_key.pop("deleted_access_key", None) if secret_key: - results['access_key'] = secret_key + results["access_key"] = secret_key results = scrub_none_parameters(results) module.exit_json(changed=changed, **results) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py index 6573e657a18..22bbd564cb0 100644 --- a/plugins/modules/iam_access_key_info.py +++ b/plugins/modules/iam_access_key_info.py @@ -85,44 +85,38 @@ def get_access_keys(user): try: results = client.list_access_keys(aws_retry=True, UserName=user) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws( - e, msg='Failed to get access keys for user "{0}"'.format(user) - ) + module.fail_json_aws(e, msg='Failed to get access keys for user "{0}"'.format(user)) if not results: return None results = camel_dict_to_snake_dict(results) - access_keys = results.get('access_key_metadata', []) + access_keys = results.get("access_key_metadata", []) if not access_keys: return [] access_keys = normalize_boto3_result(access_keys) - access_keys = sorted(access_keys, key=lambda d: d.get('create_date', None)) + access_keys = sorted(access_keys, key=lambda d: d.get("create_date", None)) return access_keys def main(): - global module global client argument_spec = dict( - user_name=dict(required=True, type='str', aliases=['username']), + user_name=dict(required=True, type="str", aliases=["username"]), ) - module = AnsibleAWSModule( - argument_spec=argument_spec, - supports_check_mode=True - ) + module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) - client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) + client = module.client("iam", retry_decorator=AWSRetry.jittered_backoff()) changed = False - user = module.params.get('user_name') + user = module.params.get("user_name") access_keys = get_access_keys(user) module.exit_json(changed=changed, access_keys=access_keys) -if __name__ == '__main__': +if __name__ == "__main__": main() From d748002f976126a0390bce0353ec013d6d74d7bb Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Mon, 8 May 2023 19:21:22 +0200 Subject: [PATCH 08/18] Bulk migration to Python 3.6 f-strings (#1810) Bulk migration to Python 3.6 f-strings SUMMARY We've dropped support for Python <3.6, bulk migrate to fstrings and perform some general string cleanup A combination of black --preview flynt some manual cleanup ISSUE TYPE Feature Pull Request COMPONENT NAME plugins/ tests/ ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/de338210dc1b0bb2eecee1dc16e073163b2d1df7 --- plugins/modules/iam_access_key.py | 12 ++++++------ plugins/modules/iam_access_key_info.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index a8f03d7bced..ae3e9e7dd11 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -157,7 +157,7 @@ def delete_access_key(access_keys, user, access_key_id): botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, ) as e: # pylint: disable=duplicate-except - module.fail_json_aws(e, msg='Failed to delete access key "{0}" for user "{1}"'.format(access_key_id, user)) + module.fail_json_aws(e, msg=f'Failed to delete access key "{access_key_id}" for user "{user}"') return True @@ -165,7 +165,7 @@ def delete_access_key(access_keys, user, access_key_id): def update_access_key(access_keys, user, access_key_id, enabled): if access_key_id not in access_keys: module.fail_json( - msg='Access key "{0}" not found attached to User "{1}"'.format(access_key_id, user), + msg=f'Access key "{access_key_id}" not found attached to User "{user}"', ) changes = dict() @@ -188,7 +188,7 @@ def update_access_key(access_keys, user, access_key_id, enabled): module.fail_json_aws( e, changes=changes, - msg='Failed to update access key "{0}" for user "{1}"'.format(access_key_id, user), + msg=f'Failed to update access key "{access_key_id}" for user "{user}"', ) return True @@ -210,7 +210,7 @@ def create_access_key(access_keys, user, rotate_keys, enabled): try: results = client.create_access_key(aws_retry=True, UserName=user) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg='Failed to create access key for user "{0}"'.format(user)) + module.fail_json_aws(e, msg=f'Failed to create access key for user "{user}"') results = camel_dict_to_snake_dict(results) access_key = results.get("access_key") access_key = normalize_boto3_result(access_key) @@ -232,7 +232,7 @@ def get_access_keys(user): try: results = client.list_access_keys(aws_retry=True, UserName=user) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg='Failed to get access keys for user "{0}"'.format(user)) + module.fail_json_aws(e, msg=f'Failed to get access keys for user "{user}"') if not results: return None @@ -259,7 +259,7 @@ def main(): ) required_if = [ - ["state", "absent", ("id")], + ["state", "absent", ("id",)], ] mutually_exclusive = [ ["rotate_keys", "id"], diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py index 22bbd564cb0..0ea8b514122 100644 --- a/plugins/modules/iam_access_key_info.py +++ b/plugins/modules/iam_access_key_info.py @@ -85,7 +85,7 @@ def get_access_keys(user): try: results = client.list_access_keys(aws_retry=True, UserName=user) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg='Failed to get access keys for user "{0}"'.format(user)) + module.fail_json_aws(e, msg=f'Failed to get access keys for user "{user}"') if not results: return None From ac59ed257df4446931625573af629896a6af8ffb Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 31 Aug 2023 17:58:59 +0200 Subject: [PATCH 09/18] Mass update of docs and tests (credentials/session tokens) (#1921) Mass update of docs and tests (credentials/session tokens) SUMMARY We had a cleanup of credentials/session parameters which included a batch of deprecations and renames. Ensure that all of our tests and docs are using the 'canonical' names ISSUE TYPE Docs Pull Request COMPONENT NAME plugins/modules/batch_compute_environment.py plugins/modules/cloudformation_exports_info.py plugins/modules/ec2_vpc_vpn.py plugins/modules/elasticache.py plugins/modules/elasticache_parameter_group.py plugins/modules/elasticache_snapshot.py plugins/modules/ses_rule_set.py plugins/modules/sts_assume_role.py plugins/modules/sts_session_token.py tests/integration ADDITIONAL INFORMATION See also ansible-collections/amazon.aws#1172 ansible-collections/amazon.aws#1714 Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/4a5b50e9b9c0d6ca1a1f802f3b03d4f503c16885 --- .../targets/iam_access_key/tasks/main.yml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/integration/targets/iam_access_key/tasks/main.yml b/tests/integration/targets/iam_access_key/tasks/main.yml index a7fcc633ce9..572e53cc8a3 100644 --- a/tests/integration/targets/iam_access_key/tasks/main.yml +++ b/tests/integration/targets/iam_access_key/tasks/main.yml @@ -2,9 +2,9 @@ - name: AWS AuthN details module_defaults: group/aws: - aws_access_key: "{{ aws_access_key }}" - aws_secret_key: "{{ aws_secret_key }}" - security_token: "{{ security_token | default(omit) }}" + access_key: "{{ aws_access_key }}" + secret_key: "{{ aws_secret_key }}" + session_token: "{{ security_token | default(omit) }}" region: "{{ aws_region }}" collections: - amazon.aws @@ -548,9 +548,9 @@ - name: Test GetCallerIdentity - Key 2 aws_caller_info: - aws_access_key: "{{ create_key_2.access_key.access_key_id }}" - aws_secret_key: "{{ create_key_2.secret_access_key }}" - security_token: "{{ omit }}" + access_key: "{{ create_key_2.access_key.access_key_id }}" + secret_key: "{{ create_key_2.secret_access_key }}" + session_token: "{{ omit }}" register: caller_identity_2 - assert: @@ -560,9 +560,9 @@ - name: Test GetCallerIdentity - Key 1 (gone) aws_caller_info: - aws_access_key: "{{ create_key_1.access_key.access_key_id }}" - aws_secret_key: "{{ create_key_1.secret_access_key }}" - security_token: "{{ omit }}" + access_key: "{{ create_key_1.access_key.access_key_id }}" + secret_key: "{{ create_key_1.secret_access_key }}" + session_token: "{{ omit }}" register: caller_identity_1 ignore_errors: true @@ -573,9 +573,9 @@ - name: Test GetCallerIdentity - Key 3 (disabled) aws_caller_info: - aws_access_key: "{{ create_key_3.access_key.access_key_id }}" - aws_secret_key: "{{ create_key_3.secret_access_key }}" - security_token: "{{ omit }}" + access_key: "{{ create_key_3.access_key.access_key_id }}" + secret_key: "{{ create_key_3.secret_access_key }}" + session_token: "{{ omit }}" register: caller_identity_3 ignore_errors: true From bbaae8469977e0ca6acf422c3d6760d771687cac Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Wed, 27 Sep 2023 17:58:09 +0200 Subject: [PATCH 10/18] Update runtime --- meta/runtime.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meta/runtime.yml b/meta/runtime.yml index da395988b6f..0cc7affc04c 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -68,6 +68,8 @@ action_groups: - elb_application_lb_info - elb_classic_lb - execute_lambda + - iam_access_key + - iam_access_key_info - iam_group - iam_instance_profile - iam_instance_profile_info From c8e36732bc9a58cfe739dc31d7075278b29fd02f Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Wed, 27 Sep 2023 17:58:09 +0200 Subject: [PATCH 11/18] Update FQDN --- plugins/modules/iam_access_key.py | 4 ++-- plugins/modules/iam_access_key_info.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index ae3e9e7dd11..f53d3bbfc5d 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -62,12 +62,12 @@ # Note: These examples do not set authentication details, see the AWS Guide for details. - name: Create a new access key - community.aws.iam_access_key: + amazon.aws.iam_access_key: user_name: example_user state: present - name: Delete the access_key - community.aws.iam_access_key: + amazon.aws.iam_access_key: user_name: example_user id: AKIA1EXAMPLE1EXAMPLE state: absent diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py index 0ea8b514122..65833bb8c88 100644 --- a/plugins/modules/iam_access_key_info.py +++ b/plugins/modules/iam_access_key_info.py @@ -32,7 +32,7 @@ # Note: These examples do not set authentication details, see the AWS Guide for details. - name: Fetch Access keys for a user - community.aws.iam_access_key_info: + amazon.aws.iam_access_key_info: user_name: example_user """ From 336c2a6822fe24e3c9261739ff8febf4b3ed6e58 Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Wed, 27 Sep 2023 17:58:09 +0200 Subject: [PATCH 12/18] Remove collection reference inside the tests --- .../targets/iam_access_key/defaults/main.yml | 3 +- .../targets/iam_access_key/tasks/main.yml | 134 ++++-------------- 2 files changed, 26 insertions(+), 111 deletions(-) diff --git a/tests/integration/targets/iam_access_key/defaults/main.yml b/tests/integration/targets/iam_access_key/defaults/main.yml index eaaa3523e19..d5d0baf6f6d 100644 --- a/tests/integration/targets/iam_access_key/defaults/main.yml +++ b/tests/integration/targets/iam_access_key/defaults/main.yml @@ -1,2 +1 @@ ---- -test_user: '{{ resource_prefix }}' +test_user: '{{ resource_prefix }}' diff --git a/tests/integration/targets/iam_access_key/tasks/main.yml b/tests/integration/targets/iam_access_key/tasks/main.yml index 572e53cc8a3..6628ef65eb7 100644 --- a/tests/integration/targets/iam_access_key/tasks/main.yml +++ b/tests/integration/targets/iam_access_key/tasks/main.yml @@ -1,14 +1,12 @@ ---- - name: AWS AuthN details module_defaults: group/aws: - access_key: "{{ aws_access_key }}" - secret_key: "{{ aws_secret_key }}" - session_token: "{{ security_token | default(omit) }}" - region: "{{ aws_region }}" + access_key: '{{ aws_access_key }}' + secret_key: '{{ aws_secret_key }}' + session_token: '{{ security_token | default(omit) }}' + region: '{{ aws_region }}' collections: - - amazon.aws - - community.aws + - community.aws block: # ================================================================================== # Preparation @@ -21,7 +19,6 @@ name: '{{ test_user }}' state: present register: iam_user - - assert: that: - iam_user is successful @@ -33,7 +30,6 @@ iam_access_key_info: user_name: '{{ test_user }}' register: access_key_info - - assert: that: - access_key_info is successful @@ -48,7 +44,6 @@ state: present register: create_key_1 check_mode: true - - assert: that: - create_key_1 is successful @@ -59,7 +54,6 @@ user_name: '{{ test_user }}' state: present register: create_key_1 - - assert: that: - create_key_1 is successful @@ -78,7 +72,6 @@ iam_access_key_info: user_name: '{{ test_user }}' register: access_key_info - - assert: that: - access_key_info is successful @@ -94,16 +87,12 @@ - access_key_1.status == 'Active' vars: access_key_1: '{{ access_key_info.access_keys[0] }}' - - # ================================================================================== - - name: Create a second key (check_mode) iam_access_key: user_name: '{{ test_user }}' state: present register: create_key_2 check_mode: true - - assert: that: - create_key_2 is successful @@ -114,7 +103,6 @@ user_name: '{{ test_user }}' state: present register: create_key_2 - - assert: that: - create_key_2 is successful @@ -133,7 +121,6 @@ iam_access_key_info: user_name: '{{ test_user }}' register: access_key_info - - assert: that: - access_key_info is successful @@ -158,32 +145,12 @@ vars: access_key_1: '{{ access_key_info.access_keys[0] }}' access_key_2: '{{ access_key_info.access_keys[1] }}' - - # ================================================================================== - - # We don't block the attempt to create a third access key - should AWS change - # the limits this will "JustWork". - - # - name: Create a third key (check_mode) - # iam_access_key: - # user_name: '{{ test_user }}' - # state: present - # register: create_key_3 - # ignore_errors: True - # check_mode: true - - # - assert: - # that: - # - create_key_3 is successful - # - create_key_3 is changed - - name: Create a third key without rotation iam_access_key: user_name: '{{ test_user }}' state: present register: create_key_3 - ignore_errors: True - + ignore_errors: true - assert: that: # If Amazon update the limits we may need to change the expectation here. @@ -193,7 +160,6 @@ iam_access_key_info: user_name: '{{ test_user }}' register: access_key_info - - assert: that: - access_key_info is successful @@ -218,9 +184,6 @@ vars: access_key_1: '{{ access_key_info.access_keys[0] }}' access_key_2: '{{ access_key_info.access_keys[1] }}' - - # ================================================================================== - - name: Create a third key - rotation enabled (check_mode) iam_access_key: user_name: '{{ test_user }}' @@ -228,7 +191,6 @@ rotate_keys: true register: create_key_3 check_mode: true - - assert: that: - create_key_3 is successful @@ -242,7 +204,6 @@ state: present rotate_keys: true register: create_key_3 - - assert: that: - create_key_3 is successful @@ -262,7 +223,6 @@ iam_access_key_info: user_name: '{{ test_user }}' register: access_key_info - - assert: that: - access_key_info is successful @@ -287,17 +247,13 @@ vars: access_key_1: '{{ access_key_info.access_keys[0] }}' access_key_2: '{{ access_key_info.access_keys[1] }}' - - # ================================================================================== - - name: Disable third key (check_mode) iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' - enabled: False + enabled: false register: disable_key check_mode: true - - assert: that: - disable_key is successful @@ -307,9 +263,8 @@ iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' - enabled: False + enabled: false register: disable_key - - assert: that: - disable_key is successful @@ -328,10 +283,9 @@ iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' - enabled: False + enabled: false register: disable_key check_mode: true - - assert: that: - disable_key is successful @@ -341,9 +295,8 @@ iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' - enabled: False + enabled: false register: disable_key - - assert: that: - disable_key is successful @@ -362,7 +315,6 @@ iam_access_key_info: user_name: '{{ test_user }}' register: access_key_info - - assert: that: - access_key_info is successful @@ -387,16 +339,12 @@ vars: access_key_1: '{{ access_key_info.access_keys[0] }}' access_key_2: '{{ access_key_info.access_keys[1] }}' - - # ================================================================================== - - name: Touch third key - no change (check_mode) iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' register: touch_key check_mode: true - - assert: that: - touch_key is successful @@ -407,7 +355,6 @@ user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' register: touch_key - - assert: that: - touch_key is successful @@ -428,10 +375,9 @@ iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' - enabled: True + enabled: true register: enable_key check_mode: true - - assert: that: - enable_key is successful @@ -441,9 +387,8 @@ iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' - enabled: True + enabled: true register: enable_key - - assert: that: - enable_key is successful @@ -462,10 +407,9 @@ iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' - enabled: True + enabled: true register: enable_key check_mode: true - - assert: that: - enable_key is successful @@ -475,9 +419,8 @@ iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' - enabled: True + enabled: true register: enable_key - - assert: that: - enable_key is successful @@ -500,7 +443,6 @@ id: '{{ create_key_3.access_key.access_key_id }}' register: touch_key check_mode: true - - assert: that: - touch_key is successful @@ -511,7 +453,6 @@ user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' register: touch_key - - assert: that: - touch_key is successful @@ -532,9 +473,8 @@ iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_3.access_key.access_key_id }}' - enabled: False + enabled: false register: redisable_key - - assert: that: - redisable_key is successful @@ -543,16 +483,12 @@ - pause: seconds: 10 - - # ================================================================================== - - name: Test GetCallerIdentity - Key 2 aws_caller_info: - access_key: "{{ create_key_2.access_key.access_key_id }}" - secret_key: "{{ create_key_2.secret_access_key }}" - session_token: "{{ omit }}" + access_key: '{{ create_key_2.access_key.access_key_id }}' + secret_key: '{{ create_key_2.secret_access_key }}' + session_token: '{{ omit }}' register: caller_identity_2 - - assert: that: - caller_identity_2 is successful @@ -560,12 +496,11 @@ - name: Test GetCallerIdentity - Key 1 (gone) aws_caller_info: - access_key: "{{ create_key_1.access_key.access_key_id }}" - secret_key: "{{ create_key_1.secret_access_key }}" - session_token: "{{ omit }}" + access_key: '{{ create_key_1.access_key.access_key_id }}' + secret_key: '{{ create_key_1.secret_access_key }}' + session_token: '{{ omit }}' register: caller_identity_1 ignore_errors: true - - assert: that: - caller_identity_1 is failed @@ -573,12 +508,11 @@ - name: Test GetCallerIdentity - Key 3 (disabled) aws_caller_info: - access_key: "{{ create_key_3.access_key.access_key_id }}" - secret_key: "{{ create_key_3.secret_access_key }}" - session_token: "{{ omit }}" + access_key: '{{ create_key_3.access_key.access_key_id }}' + secret_key: '{{ create_key_3.secret_access_key }}' + session_token: '{{ omit }}' register: caller_identity_3 ignore_errors: true - - assert: that: - caller_identity_3 is failed @@ -593,7 +527,6 @@ state: absent register: delete_active_key check_mode: true - - assert: that: - delete_active_key is successful @@ -605,7 +538,6 @@ id: '{{ create_key_2.access_key.access_key_id }}' state: absent register: delete_active_key - - assert: that: - delete_active_key is successful @@ -618,7 +550,6 @@ state: absent register: delete_active_key check_mode: true - - assert: that: - delete_active_key is successful @@ -630,7 +561,6 @@ id: '{{ create_key_2.access_key.access_key_id }}' state: absent register: delete_active_key - - assert: that: - delete_active_key is successful @@ -645,7 +575,6 @@ state: absent register: delete_inactive_key check_mode: true - - assert: that: - delete_inactive_key is successful @@ -657,7 +586,6 @@ id: '{{ create_key_3.access_key.access_key_id }}' state: absent register: delete_inactive_key - - assert: that: - delete_inactive_key is successful @@ -670,7 +598,6 @@ state: absent register: delete_inactive_key check_mode: true - - assert: that: - delete_inactive_key is successful @@ -682,7 +609,6 @@ id: '{{ create_key_3.access_key.access_key_id }}' state: absent register: delete_inactive_key - - assert: that: - delete_inactive_key is successful @@ -694,7 +620,6 @@ iam_access_key_info: user_name: '{{ test_user }}' register: access_key_info - - assert: that: - access_key_info is successful @@ -710,7 +635,6 @@ enabled: false register: create_key_4 check_mode: true - - assert: that: - create_key_4 is successful @@ -722,7 +646,6 @@ state: present enabled: false register: create_key_4 - - assert: that: - create_key_4 is successful @@ -741,7 +664,6 @@ iam_access_key_info: user_name: '{{ test_user }}' register: access_key_info - - assert: that: - access_key_info is successful @@ -757,16 +679,12 @@ - access_key_1.status == 'Inactive' vars: access_key_1: '{{ access_key_info.access_keys[0] }}' - - # We already tested the idempotency of disabling keys, use this to verify that - # the key is disabled - name: Disable new key iam_access_key: user_name: '{{ test_user }}' id: '{{ create_key_4.access_key.access_key_id }}' - enabled: False + enabled: false register: disable_new_key - - assert: that: - disable_new_key is successful @@ -782,7 +700,6 @@ id: '{{ create_key_4.access_key.access_key_id }}' state: absent register: delete_new_key - - assert: that: - delete_new_key is successful @@ -793,7 +710,6 @@ name: '{{ test_user }}' state: absent register: delete_user - - assert: that: - delete_user is successful From a6a42a3c574a47255548700b4bbc6b43ad366e63 Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Wed, 27 Sep 2023 17:58:09 +0200 Subject: [PATCH 13/18] Add changelog fragment --- changelogs/fragments/migrate_iam_access_key.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelogs/fragments/migrate_iam_access_key.yml diff --git a/changelogs/fragments/migrate_iam_access_key.yml b/changelogs/fragments/migrate_iam_access_key.yml new file mode 100644 index 00000000000..4d1f5bea3a9 --- /dev/null +++ b/changelogs/fragments/migrate_iam_access_key.yml @@ -0,0 +1,7 @@ +major_changes: +- iam_access_key - The module has been migrated from the ``community.aws`` collection. + Playbooks using the Fully Qualified Collection Name for this module should be updated + to use ``amazon.aws.iam_access_key``. +- iam_access_key_info - The module has been migrated from the ``community.aws`` collection. + Playbooks using the Fully Qualified Collection Name for this module should be updated + to use ``amazon.aws.iam_access_key_info``. From f20540a83e18e3c1a3cd3fd1ee2a8cfa0d5c1ac0 Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Wed, 27 Sep 2023 18:03:30 +0200 Subject: [PATCH 14/18] Add version_added_collection: community.aws Signed-off-by: Alina Buzachis --- plugins/modules/iam_access_key.py | 1 + plugins/modules/iam_access_key_info.py | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index f53d3bbfc5d..aa16855818f 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -8,6 +8,7 @@ --- module: iam_access_key version_added: 2.1.0 +version_added_collection: community.aws short_description: Manage AWS IAM User access keys description: - Manage AWS IAM user access keys. diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py index 65833bb8c88..3f0129b5bbb 100644 --- a/plugins/modules/iam_access_key_info.py +++ b/plugins/modules/iam_access_key_info.py @@ -8,6 +8,7 @@ --- module: iam_access_key_info version_added: 2.1.0 +version_added_collection: community.aws short_description: fetch information about AWS IAM User access keys description: - 'Fetches information AWS IAM user access keys.' From bb424bcf1b8078f681be988caf7be87036e17610 Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Wed, 27 Sep 2023 18:20:23 +0200 Subject: [PATCH 15/18] Add no_log: true Signed-off-by: Alina Buzachis --- plugins/modules/iam_access_key.py | 5 ++++- tests/integration/targets/iam_access_key/tasks/main.yml | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index aa16855818f..6857b0d08df 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -52,7 +52,9 @@ required: false type: bool default: false - +notes: + - For security reasons, this module should be used with B(no_log=true) and (register) functionalities + when creating new access key. extends_documentation_fragment: - amazon.aws.common.modules - amazon.aws.region.modules @@ -66,6 +68,7 @@ amazon.aws.iam_access_key: user_name: example_user state: present + no_log: true - name: Delete the access_key amazon.aws.iam_access_key: diff --git a/tests/integration/targets/iam_access_key/tasks/main.yml b/tests/integration/targets/iam_access_key/tasks/main.yml index 6628ef65eb7..6326ea1dbd6 100644 --- a/tests/integration/targets/iam_access_key/tasks/main.yml +++ b/tests/integration/targets/iam_access_key/tasks/main.yml @@ -53,6 +53,7 @@ iam_access_key: user_name: '{{ test_user }}' state: present + no_log: true register: create_key_1 - assert: that: @@ -102,6 +103,7 @@ iam_access_key: user_name: '{{ test_user }}' state: present + no_log: true register: create_key_2 - assert: that: @@ -149,6 +151,7 @@ iam_access_key: user_name: '{{ test_user }}' state: present + no_log: true register: create_key_3 ignore_errors: true - assert: @@ -203,6 +206,7 @@ user_name: '{{ test_user }}' state: present rotate_keys: true + no_log: true register: create_key_3 - assert: that: @@ -645,6 +649,7 @@ user_name: '{{ test_user }}' state: present enabled: false + no_log: true register: create_key_4 - assert: that: From 3705dd2f7d03d683c0e3aa1f54e4cbb0704b134b Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Thu, 28 Sep 2023 15:15:49 +0200 Subject: [PATCH 16/18] Use the amazon.aws path for importing AnsibleAWSModule Signed-off-by: Alina Buzachis --- plugins/modules/iam_access_key.py | 2 +- plugins/modules/iam_access_key_info.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index 6857b0d08df..ed4c2c7ebe0 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -134,7 +134,7 @@ from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry from ansible_collections.amazon.aws.plugins.module_utils.transformation import scrub_none_parameters -from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule def delete_access_key(access_keys, user, access_key_id): diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py index 3f0129b5bbb..3a53d57215c 100644 --- a/plugins/modules/iam_access_key_info.py +++ b/plugins/modules/iam_access_key_info.py @@ -79,7 +79,7 @@ from ansible_collections.amazon.aws.plugins.module_utils.botocore import normalize_boto3_result from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry -from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule def get_access_keys(user): From efefb7e88fe971da5af0f5e279272efc58d5ac49 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 31 Aug 2023 17:58:59 +0200 Subject: [PATCH 17/18] Mass update of docs and tests (credentials/session tokens) (#1921) Mass update of docs and tests (credentials/session tokens) SUMMARY We had a cleanup of credentials/session parameters which included a batch of deprecations and renames. Ensure that all of our tests and docs are using the 'canonical' names ISSUE TYPE Docs Pull Request COMPONENT NAME plugins/modules/batch_compute_environment.py plugins/modules/cloudformation_exports_info.py plugins/modules/ec2_vpc_vpn.py plugins/modules/elasticache.py plugins/modules/elasticache_parameter_group.py plugins/modules/elasticache_snapshot.py plugins/modules/ses_rule_set.py plugins/modules/sts_assume_role.py plugins/modules/sts_session_token.py tests/integration ADDITIONAL INFORMATION See also ansible-collections/amazon.aws#1172 ansible-collections/amazon.aws#1714 Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/4a5b50e9b9c0d6ca1a1f802f3b03d4f503c16885 --- tests/integration/targets/iam_access_key/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/iam_access_key/tasks/main.yml b/tests/integration/targets/iam_access_key/tasks/main.yml index 6326ea1dbd6..027c9bb0921 100644 --- a/tests/integration/targets/iam_access_key/tasks/main.yml +++ b/tests/integration/targets/iam_access_key/tasks/main.yml @@ -726,4 +726,4 @@ iam_user: name: '{{ test_user }}' state: absent - ignore_errors: yes + ignore_errors: true From 62ae05650555fc74153502e4afe10bc2413022c9 Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Mon, 2 Oct 2023 16:34:37 +0200 Subject: [PATCH 18/18] Apply isort to imports Signed-off-by: Alina Buzachis --- plugins/modules/iam_access_key.py | 3 +-- plugins/modules/iam_access_key_info.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/modules/iam_access_key.py b/plugins/modules/iam_access_key.py index ed4c2c7ebe0..f486d57c091 100644 --- a/plugins/modules/iam_access_key.py +++ b/plugins/modules/iam_access_key.py @@ -131,11 +131,10 @@ from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code from ansible_collections.amazon.aws.plugins.module_utils.botocore import normalize_boto3_result +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry from ansible_collections.amazon.aws.plugins.module_utils.transformation import scrub_none_parameters -from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule - def delete_access_key(access_keys, user, access_key_id): if not access_key_id: diff --git a/plugins/modules/iam_access_key_info.py b/plugins/modules/iam_access_key_info.py index 3a53d57215c..2b7c064d690 100644 --- a/plugins/modules/iam_access_key_info.py +++ b/plugins/modules/iam_access_key_info.py @@ -77,9 +77,8 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict from ansible_collections.amazon.aws.plugins.module_utils.botocore import normalize_boto3_result -from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry - from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry def get_access_keys(user):