From 936049d19bde43379743d41c6f01ec3b5c1bef17 Mon Sep 17 00:00:00 2001 From: Joseph Torcasso <87090265+jatorcasso@users.noreply.github.com> Date: Tue, 26 Apr 2022 18:02:27 -0400 Subject: [PATCH] aws_kms - stabilize and add integration tests (#1052) aws_kms - stabilize and add integration tests SUMMARY update/add integration tests for various actions return list of policies as a list of jsons for clarity sleep on updates (no kms waiter, attempted manual waiters but still had test failures) ISSUE TYPE Feature Pull Request COMPONENT NAME aws_kms ADDITIONAL INFORMATION I tried adding manual waiters for different actions like waiting for tags to be correct, policy to be updated, etc, but would still fail ~half of the time on idempotency tests. seems like after updating the key's status is a bit buggy. Reviewed-by: Jill R Reviewed-by: Mark Chappell Reviewed-by: Joseph Torcasso Reviewed-by: Mandar Kulkarni Reviewed-by: Markus Bergholz Reviewed-by: Alina Buzachis (cherry picked from commit 558a0252a3d57657b8c6be50e65b3663d37958fa) --- ...52-aws_kms-stabilize-integration-tests.yml | 3 + plugins/modules/aws_kms.py | 77 ++- plugins/modules/aws_kms_info.py | 44 +- tests/integration/targets/aws_kms/aliases | 3 +- .../targets/aws_kms/defaults/main.yml | 3 +- .../targets/aws_kms/tasks/main.yml | 647 ++++++++++++++---- tests/sanity/ignore-2.9.txt | 1 + 7 files changed, 635 insertions(+), 143 deletions(-) create mode 100644 changelogs/fragments/1052-aws_kms-stabilize-integration-tests.yml diff --git a/changelogs/fragments/1052-aws_kms-stabilize-integration-tests.yml b/changelogs/fragments/1052-aws_kms-stabilize-integration-tests.yml new file mode 100644 index 00000000000..7a084d405d1 --- /dev/null +++ b/changelogs/fragments/1052-aws_kms-stabilize-integration-tests.yml @@ -0,0 +1,3 @@ +minor_changes: + - aws_kms - fix some bugs in integration tests and add check mode support for key rotation as well as document issues with time taken for requested changes to be reflected on AWS (https://github.com/ansible-collections/community.aws/pull/1052). + - aws_kms - add extra key/value pair to return data (key_policies) to return each policy as a dictionary rather than json string (https://github.com/ansible-collections/community.aws/pull/1052). diff --git a/plugins/modules/aws_kms.py b/plugins/modules/aws_kms.py index 41a5ee63c69..cf9c4b5eb96 100644 --- a/plugins/modules/aws_kms.py +++ b/plugins/modules/aws_kms.py @@ -199,6 +199,12 @@ - amazon.aws.aws - amazon.aws.ec2 + +notes: + - There are known inconsistencies in the amount of time required for updates of KMS keys to be fully reflected on AWS. + This can cause issues when running duplicate tasks in succession or using the aws_kms_info module to fetch key metadata + shortly after modifying keys. + For this reason, it is recommended to use the return data from this module (aws_kms) to fetch a key's metadata. ''' EXAMPLES = r''' @@ -310,6 +316,11 @@ type: str returned: always sample: false +enable_key_rotation: + description: Whether the automatic annual key rotation is enabled. Returns None if key rotation status can't be determined. + type: bool + returned: always + sample: false aliases: description: list of aliases associated with the key type: list @@ -318,9 +329,45 @@ - aws/acm - aws/ebs policies: - description: list of policy documents for the keys. Empty when access is denied even if there are policies. + description: list of policy documents for the key. Empty when access is denied even if there are policies. type: list returned: always + elements: str + sample: + Version: "2012-10-17" + Id: "auto-ebs-2" + Statement: + - Sid: "Allow access through EBS for all principals in the account that are authorized to use EBS" + Effect: "Allow" + Principal: + AWS: "*" + Action: + - "kms:Encrypt" + - "kms:Decrypt" + - "kms:ReEncrypt*" + - "kms:GenerateDataKey*" + - "kms:CreateGrant" + - "kms:DescribeKey" + Resource: "*" + Condition: + StringEquals: + kms:CallerAccount: "111111111111" + kms:ViaService: "ec2.ap-southeast-2.amazonaws.com" + - Sid: "Allow direct access to key metadata to the account" + Effect: "Allow" + Principal: + AWS: "arn:aws:iam::111111111111:root" + Action: + - "kms:Describe*" + - "kms:Get*" + - "kms:List*" + - "kms:RevokeGrant" + Resource: "*" +key_policies: + description: list of policy documents for the key. Empty when access is denied even if there are policies. + type: list + returned: always + elements: dict sample: Version: "2012-10-17" Id: "auto-ebs-2" @@ -351,6 +398,7 @@ - "kms:List*" - "kms:RevokeGrant" Resource: "*" + version_added: 3.3.0 tags: description: dictionary of tags applied to the key type: dict @@ -584,6 +632,7 @@ def get_key_details(connection, module, key_id): tags = get_kms_tags(connection, module, key_id) result['tags'] = boto3_tag_list_to_ansible_dict(tags, 'TagKey', 'TagValue') result['policies'] = get_kms_policies(connection, module, key_id) + result['key_policies'] = [json.loads(policy) for policy in result['policies']] return result @@ -817,13 +866,15 @@ def update_key_rotation(connection, module, key, enable_key_rotation): except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to get current key rotation status") - try: - if enable_key_rotation: - connection.enable_key_rotation(KeyId=key_id) - else: - connection.disable_key_rotation(KeyId=key_id) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Failed to enable/disable key rotation") + if not module.check_mode: + try: + if enable_key_rotation: + connection.enable_key_rotation(KeyId=key_id) + else: + connection.disable_key_rotation(KeyId=key_id) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Failed to enable/disable key rotation") + return True @@ -1030,6 +1081,11 @@ def canonicalize_alias_name(alias): def fetch_key_metadata(connection, module, key_id, alias): + # Note - fetching a key's metadata is very inconsistent shortly after any sort of update to a key has occurred. + # Combinations of manual waiters, checking expecting key values to actual key value, and static sleeps + # have all been exhausted, but none of those available options have solved the problem. + # Integration tests will wait for 10 seconds to combat this issue. + # See https://github.com/ansible-collections/community.aws/pull/1052. alias = canonicalize_alias_name(module.params.get('alias')) @@ -1104,10 +1160,13 @@ def main(): kms = module.client('kms') + module.deprecate("The 'policies' return key is deprecated and will be replaced by 'key_policies'. Both values are returned for now.", + date='2024-05-01', collection_name='community.aws') + key_metadata = fetch_key_metadata(kms, module, module.params.get('key_id'), module.params.get('alias')) # We can't create keys with a specific ID, if we can't access the key we'll have to fail if module.params.get('state') == 'present' and module.params.get('key_id') and not key_metadata: - module.fail_json(msg="Could not find key with id %s to update") + module.fail_json(msg="Could not find key with id {0} to update".format(module.params.get('key_id'))) if module.params.get('policy_grant_types') or mode == 'deny': module.deprecate('Managing the KMS IAM Policy via policy_mode and policy_grant_types is fragile' diff --git a/plugins/modules/aws_kms_info.py b/plugins/modules/aws_kms_info.py index 671bf6f7447..c67e58d27ec 100644 --- a/plugins/modules/aws_kms_info.py +++ b/plugins/modules/aws_kms_info.py @@ -150,9 +150,10 @@ Name: myKey Purpose: protecting_stuff policies: - description: list of policy documents for the keys. Empty when access is denied even if there are policies. + description: list of policy documents for the key. Empty when access is denied even if there are policies. type: list returned: always + elements: str sample: Version: "2012-10-17" Id: "auto-ebs-2" @@ -183,6 +184,42 @@ - "kms:List*" - "kms:RevokeGrant" Resource: "*" + key_policies: + description: list of policy documents for the key. Empty when access is denied even if there are policies. + type: list + returned: always + elements: dict + sample: + Version: "2012-10-17" + Id: "auto-ebs-2" + Statement: + - Sid: "Allow access through EBS for all principals in the account that are authorized to use EBS" + Effect: "Allow" + Principal: + AWS: "*" + Action: + - "kms:Encrypt" + - "kms:Decrypt" + - "kms:ReEncrypt*" + - "kms:GenerateDataKey*" + - "kms:CreateGrant" + - "kms:DescribeKey" + Resource: "*" + Condition: + StringEquals: + kms:CallerAccount: "111111111111" + kms:ViaService: "ec2.ap-southeast-2.amazonaws.com" + - Sid: "Allow direct access to key metadata to the account" + Effect: "Allow" + Principal: + AWS: "arn:aws:iam::111111111111:root" + Action: + - "kms:Describe*" + - "kms:Get*" + - "kms:List*" + - "kms:RevokeGrant" + Resource: "*" + version_added: 3.3.0 grants: description: list of grants associated with a key type: complex @@ -240,6 +277,7 @@ sample: arn:aws:sts::0123456789012:assumed-role/lambda_xyz/xyz ''' +import json try: import botocore @@ -418,6 +456,7 @@ def get_key_details(connection, module, key_id, tokens=None): result = camel_dict_to_snake_dict(result) result['tags'] = boto3_tag_list_to_ansible_dict(tags, 'TagKey', 'TagValue') result['policies'] = get_kms_policies(connection, module, key_id) + result['key_policies'] = [json.loads(policy) for policy in result['policies']] return result @@ -460,6 +499,9 @@ def main(): except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg='Failed to connect to AWS') + module.deprecate("The 'policies' return key is deprecated and will be replaced by 'key_policies'. Both values are returned for now.", + date='2024-05-01', collection_name='community.aws') + all_keys = get_kms_info(connection, module) filtered_keys = [key for key in all_keys if key_matches_filters(key, module.params['filters'])] ret_params = dict(kms_keys=filtered_keys) diff --git a/tests/integration/targets/aws_kms/aliases b/tests/integration/targets/aws_kms/aliases index 4e5d9c7f9e7..04f36ba6ec9 100644 --- a/tests/integration/targets/aws_kms/aliases +++ b/tests/integration/targets/aws_kms/aliases @@ -1,6 +1,7 @@ # Various race conditions - likely needs waiters # https://github.com/ansible-collections/community.aws/issues/433 -unstable +# No KMS supported waiters, and manual waiting for updates didn't fix the issue either. +# Issue likely from AWS side - added waits on updates in integration tests to workaround this. cloud/aws diff --git a/tests/integration/targets/aws_kms/defaults/main.yml b/tests/integration/targets/aws_kms/defaults/main.yml index a113d884066..a1050a0c95e 100644 --- a/tests/integration/targets/aws_kms/defaults/main.yml +++ b/tests/integration/targets/aws_kms/defaults/main.yml @@ -1,3 +1,2 @@ --- -kms_role_name: 'ansible-test-{{ resource_prefix }}-kms' -kms_key_alias: '{{ resource_prefix }}-kms' +kms_key_alias: 'ansible-test-{{ resource_prefix }}-kms' diff --git a/tests/integration/targets/aws_kms/tasks/main.yml b/tests/integration/targets/aws_kms/tasks/main.yml index 214f0ddc955..dc10cc92330 100644 --- a/tests/integration/targets/aws_kms/tasks/main.yml +++ b/tests/integration/targets/aws_kms/tasks/main.yml @@ -21,19 +21,23 @@ # to ensure it exists when we need it for updating the policies - name: create an IAM role that can do nothing iam_role: - name: '{{ kms_role_name }}' + name: '{{ kms_key_alias }}' state: present assume_role_policy_document: '{"Version": "2012-10-17", "Statement": {"Action": "sts:AssumeRole", "Principal": {"Service": "ec2.amazonaws.com"}, "Effect": "Deny"} }' register: iam_role_result # ============================================================ # TESTS + # Note - there are waits placed after each action to account for inconsistencies in what + # is being returned when fetching key metadata. + # Combinations of manual waiters, checking expecting key values to actual key value, and static sleeps + # have all been tried, but none of those available options have solved the problem. + - name: See whether key exists and its current state aws_kms_info: alias: '{{ kms_key_alias }}' - - name: create a key in check mode - check_mode: yes + - name: create a key - check mode aws_kms: alias: '{{ kms_key_alias }}-check' tags: @@ -41,6 +45,7 @@ state: present enabled: yes register: create_kms_check + check_mode: yes - name: find facts about the check mode key aws_kms_info: @@ -50,7 +55,7 @@ - name: ensure that check mode worked as expected assert: that: - - check_key["keys"]|length == 0 + - check_key.kms_keys | length == 0 - create_kms_check is changed - name: create a key @@ -60,11 +65,13 @@ Hello: World state: present enabled: yes + enable_key_rotation: no register: create_kms - name: assert that state is enabled assert: that: + - create_kms is changed - '"key_id" in create_kms' - create_kms.key_id | length >= 36 - not create_kms.key_id.startswith("arn:aws") @@ -77,12 +84,72 @@ - create_kms.key_usage == 'ENCRYPT_DECRYPT' - create_kms.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: create a key (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + register: create_kms + check_mode: yes + + - assert: + that: + - create_kms is not changed + + - name: create a key (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + register: create_kms + check_mode: yes + + - assert: + that: + - create_kms is not changed + - '"key_id" in create_kms' + - create_kms.key_id | length >= 36 + - not create_kms.key_id.startswith("arn:aws") + - '"key_arn" in create_kms' + - create_kms.key_arn.endswith(create_kms.key_id) + - create_kms.key_arn.startswith("arn:aws") + - create_kms.key_state == "Enabled" + - create_kms.tags['Hello'] == 'World' + - create_kms.enable_key_rotation == false + - create_kms.key_usage == 'ENCRYPT_DECRYPT' + - create_kms.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + + # ------------------------------------------------------------------------------------------ + - name: Save IDs for later set_fact: kms_key_id: '{{ create_kms.key_id }}' kms_key_arn: '{{ create_kms.key_arn }}' - - name: enable key rotation + - name: Enable key rotation - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: yes + register: create_kms + check_mode: yes + + - assert: + that: + - create_kms.changed + + - name: Enable key rotation aws_kms: alias: '{{ kms_key_alias }}' tags: @@ -95,20 +162,48 @@ - name: assert that key rotation is enabled assert: that: + - create_kms.changed - create_kms.key_state == "Enabled" - create_kms.tags['Hello'] == 'World' - create_kms.enable_key_rotation == true - - name: delete the key in check mode + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: Enable key rotation (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: yes + register: create_kms check_mode: yes + + - assert: + that: + - not create_kms.changed + + - name: Enable key rotation (idempotence) aws_kms: alias: '{{ kms_key_alias }}' - state: absent - register: delete_kms_check + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: yes + register: create_kms - assert: that: - - delete_kms_check is changed + - not create_kms.changed + - create_kms.key_state == "Enabled" + - create_kms.tags['Hello'] == 'World' + - create_kms.enable_key_rotation == true + + # ------------------------------------------------------------------------------------------ - name: find facts about the key (by ID) aws_kms_info: @@ -118,14 +213,25 @@ - name: check that a key was found assert: that: - - new_key["keys"]|length == 1 - - new_key["keys"][0]["enable_key_rotation"] == true - - new_key["keys"][0]["key_state"] != "PendingDeletion" + - new_key.kms_keys | length == 1 + - new_key.kms_keys[0].enable_key_rotation == true + - new_key.kms_keys[0].key_state != "PendingDeletion" + + - name: Update policy - check mode + aws_kms: + key_id: '{{ kms_key_id }}' + policy: "{{ lookup('template', 'console-policy.j2') }}" + register: kms_policy_changed + check_mode: yes + + - assert: + that: + - kms_policy_changed is changed - - name: Update Policy on key to match AWS Console generate policy + - name: Update policy aws_kms: key_id: '{{ kms_key_id }}' - policy: "{{ lookup('template', 'console-policy.j2') | to_json }}" + policy: "{{ lookup('template', 'console-policy.j2') }}" register: kms_policy_changed - name: Policy should have been changed @@ -133,46 +239,84 @@ that: - kms_policy_changed is changed - - name: Attempt to re-assert the same policy + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: Update policy (idempotence) - check mode aws_kms: alias: "alias/{{ kms_key_alias }}" - policy: "{{ lookup('template', 'console-policy.j2') | to_json }}" + policy: "{{ lookup('template', 'console-policy.j2') }}" register: kms_policy_changed + check_mode: yes - - name: Policy should not have changed since it was last set - assert: + - assert: + that: + - not kms_policy_changed.changed + + - name: Update policy (idempotence) + aws_kms: + alias: "alias/{{ kms_key_alias }}" + policy: "{{ lookup('template', 'console-policy.j2') }}" + register: kms_policy_changed + + - assert: that: - - kms_policy_changed is succeeded + - not kms_policy_changed.changed + + # ------------------------------------------------------------------------------------------ - name: grant user-style access to production secrets aws_kms: mode: grant alias: "alias/{{ kms_key_alias }}" - role_name: '{{ kms_role_name }}' + role_name: '{{ kms_key_alias }}' grant_types: "role,role grant" - - - name: find facts about the key (by ARN) - aws_kms_info: - key_id: '{{ kms_key_arn }}' register: new_key + - assert: + that: + - new_key.changed + - name: remove access to production secrets from role aws_kms: mode: deny alias: "alias/{{ kms_key_alias }}" role_arn: "{{ iam_role_result.iam_role.arn }}" - - name: find facts about the key (by alias) - aws_kms_info: + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: Add grant - check mode + aws_kms: alias: '{{ kms_key_alias }}' - register: new_key + state: present + purge_grants: yes + grants: + - name: test_grant + grantee_principal: "{{ iam_role_result.iam_role.arn }}" + retiring_principal: "{{ aws_caller_info.arn }}" + constraints: + encryption_context_equals: + environment: test + application: testapp + operations: + - Decrypt + - RetireGrant + register: grant_one + check_mode: yes + + - name: assert grant would have been added + assert: + that: + - grant_one.changed - - name: Allow the IAM role to use a specific Encryption Context + - name: Add grant aws_kms: alias: '{{ kms_key_alias }}' state: present purge_grants: yes - purge_tags: yes grants: - name: test_grant grantee_principal: "{{ iam_role_result.iam_role.arn }}" @@ -190,7 +334,57 @@ assert: that: - grant_one.changed - - grant_one.grants|length == 1 + - grant_one.grants | length == 1 + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: Add grant (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + purge_grants: yes + grants: + - name: test_grant + grantee_principal: "{{ iam_role_result.iam_role.arn }}" + retiring_principal: "{{ aws_caller_info.arn }}" + constraints: + encryption_context_equals: + environment: test + application: testapp + operations: + - Decrypt + - RetireGrant + register: grant_one + check_mode: yes + + - assert: + that: + - not grant_one.changed + + - name: Add grant (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + purge_grants: yes + grants: + - name: test_grant + grantee_principal: "{{ iam_role_result.iam_role.arn }}" + retiring_principal: "{{ aws_caller_info.arn }}" + constraints: + encryption_context_equals: + environment: test + application: testapp + operations: + - Decrypt + - RetireGrant + register: grant_one + + - assert: + that: + - not grant_one.changed + - grant_one.grants | length == 1 - name: Add a second grant aws_kms: @@ -209,12 +403,16 @@ - RetireGrant register: grant_two - - name: assert grant added + - name: Assert grant added assert: that: - grant_two.changed - grant_two.grants|length == 2 + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + - name: Add a second grant again aws_kms: alias: '{{ kms_key_alias }}' @@ -232,7 +430,7 @@ - RetireGrant register: grant_two_again - - name: assert grant added + - name: Assert grant added assert: that: - not grant_two_again.changed @@ -256,13 +454,13 @@ - RetireGrant register: grant_three - - name: assert grants replaced + - name: Assert grants replaced assert: that: - grant_three.changed - - grant_three.grants|length == 1 + - grant_three.grants | length == 1 - - name: update third grant to change encryption context equals to subset + - name: Update third grant to change encryption context equals to subset aws_kms: alias: '{{ kms_key_alias }}' state: present @@ -279,15 +477,17 @@ - RetireGrant register: grant_three_update - - name: assert grants replaced + - name: Assert grants replaced assert: that: - - "grant_three_update.changed" - - "grant_three_update.grants|length == 1" + - grant_three_update.changed + - grant_three_update.grants | length == 1 - "'encryption_context_equals' not in grant_three_update.grants[0].constraints" - "'encryption_context_subset' in grant_three_update.grants[0].constraints" - - name: tag encryption key + # ------------------------------------------------------------------------------------------ + + - name: Tag encryption key aws_kms: alias: '{{ kms_key_alias }}' state: present @@ -296,15 +496,15 @@ tag_two: tag_two register: tag_kms - - name: assert tags added and grants remain in place + - name: Assert tags added and grants remain in place assert: that: - - "tag_kms.changed" - - "tag_kms.grants|length == 1" + - tag_kms.changed + - tag_kms.grants | length == 1 - "'tag_one' in tag_kms.tags" - "'tag_two' in tag_kms.tags" - - name: add, replace, remove tags + - name: Modify tags - check mode aws_kms: alias: '{{ kms_key_alias }}' state: present @@ -312,66 +512,213 @@ tags: tag_two: tag_two_updated Tag Three: '{{ resource_prefix }}' - register: tag_kms_update + register: key + check_mode: yes - - name: assert tags correctly changed - assert: + - assert: that: - - "tag_kms_update.changed" - - "'tag_one' not in tag_kms_update.tags" - - "'tag_two' in tag_kms_update.tags" - - "tag_kms_update.tags.tag_two == 'tag_two_updated'" - - "'Tag Three' in tag_kms_update.tags" - - "tag_kms_update.tags['Tag Three'] == resource_prefix" + - key.changed - - name: make no real tag change + - name: Modify tags aws_kms: alias: '{{ kms_key_alias }}' state: present + purge_tags: yes tags: + tag_two: tag_two_updated Tag Three: '{{ resource_prefix }}' - register: tag_kms_no_update + register: key - - name: assert no change to tags + - name: Assert tags correctly changed assert: that: - - "not tag_kms_no_update.changed" - - "'tag_one' not in tag_kms_no_update.tags" - - "'tag_two' in tag_kms_no_update.tags" - - "tag_kms_no_update.tags.tag_two == 'tag_two_updated'" - - "'Tag Three' in tag_kms_update.tags" - - "tag_kms_update.tags['Tag Three'] == resource_prefix" + - key.changed + - "'tag_one' not in key.tags" + - "'tag_two' in key.tags" + - "key.tags.tag_two == 'tag_two_updated'" + - "'Tag Three' in key.tags" + - "key.tags['Tag Three'] == resource_prefix" + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: Modify tags (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + purge_tags: yes + tags: + tag_two: tag_two_updated + Tag Three: '{{ resource_prefix }}' + register: key + check_mode: yes + + - assert: + that: + - not key.changed + + - name: Modify tags (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + purge_tags: yes + tags: + tag_two: tag_two_updated + Tag Three: '{{ resource_prefix }}' + register: key + + - assert: + that: + - not key.changed + - "'tag_one' not in key.tags" + - "'tag_two' in key.tags" + - "key.tags.tag_two == 'tag_two_updated'" + - "'Tag Three' in key.tags" + - "key.tags['Tag Three'] == resource_prefix" + + # ------------------------------------------------------------------------------------------ + + - name: Update description - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + description: test key for testing + register: key + check_mode: yes + + - assert: + that: + - key.changed + + - name: Update description + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + description: test key for testing + register: key + + - assert: + that: + - key.changed + - key.description == "test key for testing" + - "'tag_one' not in key.tags" + - "'tag_two' in key.tags" + - "key.tags.tag_two == 'tag_two_updated'" + - "'Tag Three' in key.tags" + - "key.tags['Tag Three'] == resource_prefix" + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: Update description (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + description: test key for testing + register: key + check_mode: yes + + - assert: + that: + - not key.changed - - name: update the key's description and disable it + - name: Update description (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + description: test key for testing + register: key + + - assert: + that: + - not key.changed + - key.description == "test key for testing" + - "'tag_one' not in key.tags" + - "'tag_two' in key.tags" + - "key.tags.tag_two == 'tag_two_updated'" + - "'Tag Three' in key.tags" + - "key.tags['Tag Three'] == resource_prefix" + + # ------------------------------------------------------------------------------------------ + + - name: Disable key - check mode aws_kms: alias: '{{ kms_key_alias }}' state: present description: test key for testing enabled: no - register: update_key + register: key + check_mode: yes - - name: assert that state is enabled + - assert: + that: + - key.changed + + - name: Disable key + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + description: test key for testing + enabled: no + register: key + + - name: assert that state is disabled assert: that: - - update_key.description == "test key for testing" - - update_key.key_state == "Disabled" - - update_key.changed + - key.changed + - key.key_state == "Disabled" + - not key.enabled - - name: update policy to remove access to key rotation status + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: Disable key (idempotence) - check mode aws_kms: - alias: 'alias/{{ kms_key_alias }}' - policy: "{{ lookup('template', 'console-policy-no-key-rotation.j2') | to_json }}" - register: update_key + alias: '{{ kms_key_alias }}' + state: present + description: test key for testing + enabled: no + register: key + check_mode: yes - - name: find facts about the key without key rotation status - aws_kms_info: + - assert: + that: + - not key.changed + + - name: Disable key (idempotence) + aws_kms: alias: '{{ kms_key_alias }}' - register: updated_key + state: present + description: test key for testing + enabled: no + register: key - - name: assert that key rotation status is set to None - assert: + - assert: + that: + - not key.changed + - key.key_state == "Disabled" + - not key.enabled + + # ------------------------------------------------------------------------------------------ + + - name: update policy to remove access to key rotation status + aws_kms: + alias: 'alias/{{ kms_key_alias }}' + policy: "{{ lookup('template', 'console-policy-no-key-rotation.j2') }}" + register: key + + - assert: that: - - update_key.enable_key_rotation is none + - key.changed + - key.key_state == "Enabled" + - key.enable_key_rotation is none + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 - name: Perform a search for key by tag aws_kms_info: @@ -381,11 +728,10 @@ - name: Verify all expected attributes vars: - tagged_key: '{{ info_tag_filtered["keys"][0] }}' + tagged_key: '{{ info_tag_filtered.kms_keys[0] }}' assert: that: - - "'keys' in info_tag_filtered" - - info_tag_filtered["keys"] | length == 1 + - info_tag_filtered.kms_keys | length == 1 - "'aliases' in tagged_key" - kms_key_alias in tagged_key.aliases - "'aws_account_id' in tagged_key" @@ -396,8 +742,9 @@ - "'description' in tagged_key" - tagged_key.description == 'test key for testing' - "'enable_key_rotation' in tagged_key" + - tagged_key.enable_key_rotation is none - "'enabled' in tagged_key" - - tagged_key.enabled == True + - tagged_key.enabled - "'encryption_algorithms' in tagged_key" - "'SYMMETRIC_DEFAULT' in tagged_key.encryption_algorithms" - "'grants' in tagged_key" @@ -421,59 +768,114 @@ - "'tag_two' in tagged_key.tags" - tagged_key.tags['tag_two'] == 'tag_two_updated' - - name: delete the key + # ------------------------------------------------------------------------------------------ + + - name: Delete key - check mode aws_kms: alias: '{{ kms_key_alias }}' state: absent - register: delete_kms + register: key + check_mode: yes - - name: assert that state is pending deletion + - assert: + that: + - key is changed + + - name: Delete key + aws_kms: + alias: '{{ kms_key_alias }}' + state: absent + register: key + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: Assert that state is pending deletion vars: now_time: '{{ lookup("pipe", "date -u +%Y-%m-%d\ %H:%M:%S") }}' - deletion_time: '{{ delete_kms.deletion_date[:19] | to_datetime("%Y-%m-%dT%H:%M:%S") }}' + deletion_time: '{{ key.deletion_date[:19] | to_datetime("%Y-%m-%dT%H:%M:%S") }}' assert: that: - - delete_kms.key_state == "PendingDeletion" - - delete_kms.changed + - key.key_state == "PendingDeletion" + - key.changed # Times won't be perfect, allow a 24 hour window - (( deletion_time | to_datetime ) - ( now_time | to_datetime )).days <= 30 - (( deletion_time | to_datetime ) - ( now_time | to_datetime )).days >= 29 - - name: re-delete the key + - name: Delete key (idempotence) - check mode aws_kms: alias: '{{ kms_key_alias }}' state: absent - register: delete_kms + register: key + check_mode: yes - - name: assert that state is pending deletion - assert: + - assert: that: - - delete_kms.key_state == "PendingDeletion" - - delete_kms is not changed + - not key.changed - - name: undelete and enable the key + - name: Delete key (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + state: absent + register: key + + - assert: + that: + - not key.changed + - key.key_state == "PendingDeletion" + + # ------------------------------------------------------------------------------------------ + + - name: Cancel key deletion - check mode aws_kms: alias: '{{ kms_key_alias }}' state: present - enabled: yes - register: undelete_kms + register: key + check_mode: yes - - name: assert that state is enabled - assert: + - assert: that: - - undelete_kms.key_state == "Enabled" - - undelete_kms.changed + - key.changed - - name: delete a non-existant key + - name: Cancel key deletion aws_kms: - key_id: '00000000-0000-0000-0000-000000000000' - state: absent - register: delete_kms + alias: '{{ kms_key_alias }}' + state: present + register: key - - name: assert that state is unchanged - assert: + - assert: + that: + - key.changed + - key.key_state != "PendingDeletion" + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 10 + + - name: Cancel key deletion (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + register: key + check_mode: yes + + - assert: that: - - delete_kms is not changed + - not key.changed + + - name: Cancel key deletion (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + register: key + + - assert: + that: + - not key.changed + - key.key_state != "PendingDeletion" + + # ------------------------------------------------------------------------------------------ - name: delete the key with a specific deletion window aws_kms: @@ -498,7 +900,7 @@ # test different key usage and specs - name: create kms key with different specs aws_kms: - alias: '{{ kms_role_name }}-diff-spec-usage' + alias: '{{ kms_key_alias }}-diff-spec-usage' purge_grants: yes key_spec: ECC_NIST_P256 key_usage: SIGN_VERIFY @@ -519,34 +921,19 @@ always: # ============================================================ # CLEAN-UP - - name: finish off by deleting key + - name: finish off by deleting keys aws_kms: state: absent - alias: '{{ kms_key_alias }}' + alias: "{{ item }}" pending_window: 7 - register: destroy_result - ignore_errors: True - - - name: delete kms key with different specs - aws_kms: - state: absent - alias: '{{ kms_role_name }}-diff-spec-usage' - pending_window: 7 - register: destroy_result - ignore_errors: True - - # Should never exist, but just in case - - name: finish off by deleting key - aws_kms: - state: absent - alias: '{{ kms_key_alias }}-check' - pending_window: 7 - register: destroy_result ignore_errors: True + loop: + - "{{ kms_key_alias }}" + - "{{ kms_key_alias }}-diff-spec-usage" + - "{{ kms_key_alias }}-check" - name: remove the IAM role iam_role: - name: '{{ kms_role_name }}' + name: '{{ kms_key_alias }}' state: absent - register: iam_role_result ignore_errors: True diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index c9c875d60e5..c67e29bd9f3 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -1,4 +1,5 @@ plugins/modules/aws_kms.py pylint:ansible-deprecated-no-version +plugins/modules/aws_kms_info.py pylint:ansible-deprecated-no-version plugins/modules/ec2_metric_alarm.py pylint:ansible-deprecated-no-version plugins/modules/elb_network_lb.py pylint:ansible-deprecated-no-version plugins/modules/iam_policy.py pylint:ansible-deprecated-no-version