From ebf8a068e5b1a58513f39c2fc6e4342a3f829517 Mon Sep 17 00:00:00 2001 From: Joseph Torcasso Date: Mon, 2 May 2022 17:28:14 -0400 Subject: [PATCH] add longer waits and run tests in parallel --- .../targets/aws_kms/defaults/main.yml | 2 - tests/integration/targets/aws_kms/inventory | 10 + tests/integration/targets/aws_kms/main.yml | 10 + .../aws_kms/roles/aws_kms/defaults/main.yml | 2 + .../aws_kms/roles/aws_kms/tasks/main.yml | 13 + .../roles/aws_kms/tasks/test_grants.yml | 368 +++++++ .../roles/aws_kms/tasks/test_modify.yml | 333 +++++++ .../roles/aws_kms/tasks/test_states.yml | 557 +++++++++++ .../roles/aws_kms/tasks/test_tagging.yml | 202 ++++ .../console-policy-no-key-rotation.j2 | 0 .../aws_kms}/templates/console-policy.j2 | 0 tests/integration/targets/aws_kms/runme.sh | 12 + .../targets/aws_kms/tasks/main.yml | 939 ------------------ 13 files changed, 1507 insertions(+), 941 deletions(-) delete mode 100644 tests/integration/targets/aws_kms/defaults/main.yml create mode 100644 tests/integration/targets/aws_kms/inventory create mode 100644 tests/integration/targets/aws_kms/main.yml create mode 100644 tests/integration/targets/aws_kms/roles/aws_kms/defaults/main.yml create mode 100644 tests/integration/targets/aws_kms/roles/aws_kms/tasks/main.yml create mode 100644 tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_grants.yml create mode 100644 tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_modify.yml create mode 100644 tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_states.yml create mode 100644 tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_tagging.yml rename tests/integration/targets/aws_kms/{ => roles/aws_kms}/templates/console-policy-no-key-rotation.j2 (100%) rename tests/integration/targets/aws_kms/{ => roles/aws_kms}/templates/console-policy.j2 (100%) create mode 100755 tests/integration/targets/aws_kms/runme.sh delete mode 100644 tests/integration/targets/aws_kms/tasks/main.yml diff --git a/tests/integration/targets/aws_kms/defaults/main.yml b/tests/integration/targets/aws_kms/defaults/main.yml deleted file mode 100644 index a1050a0c95e..00000000000 --- a/tests/integration/targets/aws_kms/defaults/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -kms_key_alias: 'ansible-test-{{ resource_prefix }}-kms' diff --git a/tests/integration/targets/aws_kms/inventory b/tests/integration/targets/aws_kms/inventory new file mode 100644 index 00000000000..14abc6eb267 --- /dev/null +++ b/tests/integration/targets/aws_kms/inventory @@ -0,0 +1,10 @@ +# inventory names shortened down to fit resource name length limits +[tests] +states +grants +modify +tagging + +[all:vars] +ansible_connection=local +ansible_python_interpreter="{{ ansible_playbook_python }}" diff --git a/tests/integration/targets/aws_kms/main.yml b/tests/integration/targets/aws_kms/main.yml new file mode 100644 index 00000000000..3cbf14f7bf7 --- /dev/null +++ b/tests/integration/targets/aws_kms/main.yml @@ -0,0 +1,10 @@ +--- +# Beware: most of our tests here are run in parallel. +# To add new tests you'll need to add a new host to the inventory and a matching +# '{{ inventory_hostname }}'.yml file in roles/aws_kms/tasks/ + +- hosts: all + gather_facts: no + strategy: free + roles: + - aws_kms diff --git a/tests/integration/targets/aws_kms/roles/aws_kms/defaults/main.yml b/tests/integration/targets/aws_kms/roles/aws_kms/defaults/main.yml new file mode 100644 index 00000000000..3e5e6c895ee --- /dev/null +++ b/tests/integration/targets/aws_kms/roles/aws_kms/defaults/main.yml @@ -0,0 +1,2 @@ +--- +kms_key_alias: "ansible-test-{{ inventory_hostname | replace('_','-') }}{{ tiny_prefix }}" diff --git a/tests/integration/targets/aws_kms/roles/aws_kms/tasks/main.yml b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/main.yml new file mode 100644 index 00000000000..81f3e009899 --- /dev/null +++ b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/main.yml @@ -0,0 +1,13 @@ +--- +- name: 'aws_kms integration tests' + collections: + - amazon.aws + - community.aws + module_defaults: + group/aws: + aws_access_key: '{{ aws_access_key }}' + aws_secret_key: '{{ aws_secret_key }}' + security_token: '{{ security_token | default(omit) }}' + region: '{{ aws_region }}' + block: + - include: './test_{{ inventory_hostname }}.yml' diff --git a/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_grants.yml b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_grants.yml new file mode 100644 index 00000000000..d86309e41d9 --- /dev/null +++ b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_grants.yml @@ -0,0 +1,368 @@ +- block: + # ============================================================ + # PREPARATION + # + # Get some information about who we are before starting our tests + # we'll need this as soon as we start working on the policies + - name: get ARN of calling user + aws_caller_info: + register: aws_caller_info + + # IAM Roles completes before the Role is fully instantiated, create it here + # 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_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: create a key + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: no + register: key + + - name: assert that state is enabled + assert: + that: + - key is changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + # ------------------------------------------------------------------------------------------ + + - name: Add grant - 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: key + check_mode: yes + + - name: assert grant would have been added + assert: + that: + - key.changed + + - name: Add grant + 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: key + + - name: assert grant added + assert: + that: + - key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 1 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - 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: key + check_mode: yes + + - assert: + that: + - not key.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: key + + - assert: + that: + - not key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 1 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + - name: Add a second grant + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + grants: + - name: another_grant + grantee_principal: "{{ iam_role_result.iam_role.arn }}" + retiring_principal: "{{ aws_caller_info.arn }}" + constraints: + encryption_context_equals: + Environment: second + Application: anotherapp + operations: + - Decrypt + - RetireGrant + register: key + + - name: Assert grant added + assert: + that: + - key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 2 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - name: Add a second grant again + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + grants: + - name: another_grant + grantee_principal: "{{ iam_role_result.iam_role.arn }}" + retiring_principal: "{{ aws_caller_info.arn }}" + constraints: + encryption_context_equals: + Environment: second + Application: anotherapp + operations: + - Decrypt + - RetireGrant + register: key + + - name: Assert grant added + assert: + that: + - not key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 2 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + - name: Update the grants with purge_grants set + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + purge_grants: yes + grants: + - name: third_grant + grantee_principal: "{{ iam_role_result.iam_role.arn }}" + retiring_principal: "{{ aws_caller_info.arn }}" + constraints: + encryption_context_equals: + environment: third + application: onemoreapp + operations: + - Decrypt + - RetireGrant + register: key + + - name: Assert grants replaced + assert: + that: + - key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 1 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + - name: Update third grant to change encryption context equals to subset + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + grants: + - name: third_grant + grantee_principal: "{{ iam_role_result.iam_role.arn }}" + retiring_principal: "{{ aws_caller_info.arn }}" + constraints: + encryption_context_subset: + environment: third + application: onemoreapp + operations: + - Decrypt + - RetireGrant + register: key + + - name: Assert grants replaced + assert: + that: + - key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 1 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + - "'encryption_context_equals' not in key.grants[0].constraints" + - "'encryption_context_subset' in key.grants[0].constraints" + + always: + # ============================================================ + # CLEAN-UP + - name: finish off by deleting keys + aws_kms: + state: absent + alias: "{{ kms_key_alias }}" + pending_window: 7 + ignore_errors: True + + - name: remove the IAM role + iam_role: + name: '{{ kms_key_alias }}' + state: absent + ignore_errors: True diff --git a/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_modify.yml b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_modify.yml new file mode 100644 index 00000000000..50ebec718a6 --- /dev/null +++ b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_modify.yml @@ -0,0 +1,333 @@ +- block: + # ============================================================ + # PREPARATION + # + # Get some information about who we are before starting our tests + # we'll need this as soon as we start working on the policies + - name: get ARN of calling user + aws_caller_info: + register: aws_caller_info + + # IAM Roles completes before the Role is fully instantiated, create it here + # 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_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: create a key + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: no + register: key + + - name: assert that state is enabled + assert: + that: + - key is changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + # ------------------------------------------------------------------------------------------ + + - name: Save IDs for later + set_fact: + kms_key_id: '{{ key.key_id }}' + kms_key_arn: '{{ key.key_arn }}' + + - name: find facts about the key (by ID) + aws_kms_info: + key_id: '{{ kms_key_id }}' + register: new_key + + - name: check that a key was found + assert: + that: + - '"key_id" in new_key.kms_keys[0]' + - new_key.kms_keys[0].key_id | length >= 36 + - not new_key.kms_keys[0].key_id.startswith("arn:aws") + - '"key_arn" in new_key.kms_keys[0]' + - new_key.kms_keys[0].key_arn.endswith(new_key.kms_keys[0].key_id) + - new_key.kms_keys[0].key_arn.startswith("arn:aws") + - new_key.kms_keys[0].key_state == "Enabled" + - new_key.kms_keys[0].enabled == True + - new_key.kms_keys[0].tags | length == 1 + - new_key.kms_keys[0].tags['Hello'] == 'World' + - new_key.kms_keys[0].enable_key_rotation == False + - new_key.kms_keys[0].key_usage == 'ENCRYPT_DECRYPT' + - new_key.kms_keys[0].customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - new_key.kms_keys[0].grants | length == 0 + - new_key.kms_keys[0].key_policies | length == 1 + - new_key.kms_keys[0].key_policies[0].Id == 'key-default-1' + - new_key.kms_keys[0].description == '' + + - name: Update policy - check mode + aws_kms: + key_id: '{{ kms_key_id }}' + policy: "{{ lookup('template', 'console-policy.j2') }}" + register: key + check_mode: yes + + - assert: + that: + - key is changed + + - name: Update policy + aws_kms: + key_id: '{{ kms_key_id }}' + policy: "{{ lookup('template', 'console-policy.j2') }}" + register: key + + - name: Policy should have been changed + assert: + that: + - key is changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-consolepolicy-3' + - key.description == '' + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - name: Update policy (idempotence) - check mode + aws_kms: + alias: "alias/{{ kms_key_alias }}" + policy: "{{ lookup('template', 'console-policy.j2') }}" + register: key + check_mode: yes + + - assert: + that: + - not key.changed + + - name: Update policy (idempotence) + aws_kms: + alias: "alias/{{ kms_key_alias }}" + policy: "{{ lookup('template', 'console-policy.j2') }}" + register: key + + - assert: + that: + - not key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-consolepolicy-3' + - key.description == '' + + # ------------------------------------------------------------------------------------------ + + - 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_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-consolepolicy-3' + - key.description == 'test key for testing' + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - 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 description (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + description: test key for testing + register: key + + - assert: + that: + - not key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-consolepolicy-3' + - key.description == 'test key for testing' + + # ------------------------------------------------------------------------------------------ + + - name: grant user-style access to production secrets + aws_kms: + mode: grant + alias: "alias/{{ kms_key_alias }}" + role_name: '{{ kms_key_alias }}' + grant_types: "role,role grant" + register: new_key + + - assert: + that: + - new_key.changed + - "'changes_needed' in new_key" + + - 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 }}" + register: new_key + + - assert: + that: + - new_key.changed + - "'changes_needed' in new_key" + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + # ------------------------------------------------------------------------------------------ + + - 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: + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation is none + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-consolepolicy-3' + - key.description == 'test key for testing' + - "'Disable access to key rotation status' in {{ key.key_policies[0].Statement | map(attribute='Sid') }}" + + always: + # ============================================================ + # CLEAN-UP + - name: finish off by deleting keys + aws_kms: + state: absent + alias: "{{ kms_key_alias }}" + pending_window: 7 + ignore_errors: True + + - name: remove the IAM role + iam_role: + name: '{{ kms_key_alias }}' + state: absent + ignore_errors: True diff --git a/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_states.yml b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_states.yml new file mode 100644 index 00000000000..31e91d5921d --- /dev/null +++ b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_states.yml @@ -0,0 +1,557 @@ +- block: + # ============================================================ + # PREPARATION + # + # Get some information about who we are before starting our tests + # we'll need this as soon as we start working on the policies + - name: get ARN of calling user + aws_caller_info: + register: aws_caller_info + + # ============================================================ + # 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 - check mode + aws_kms: + alias: '{{ kms_key_alias }}-check' + tags: + Hello: World + state: present + enabled: yes + register: key_check + check_mode: yes + + - name: find facts about the check mode key + aws_kms_info: + alias: '{{ kms_key_alias }}-check' + register: check_key + + - name: ensure that check mode worked as expected + assert: + that: + - check_key.kms_keys | length == 0 + - key_check is changed + + - name: create a key + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: no + register: key + + - name: assert that state is enabled + assert: + that: + - key is changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - name: create a key (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + register: key + check_mode: yes + + - assert: + that: + - key is not changed + + - name: create a key (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + register: key + check_mode: yes + + - assert: + that: + - key is not changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + # ------------------------------------------------------------------------------------------ + + - name: Save IDs for later + set_fact: + kms_key_id: '{{ key.key_id }}' + kms_key_arn: '{{ key.key_arn }}' + + - name: Enable key rotation - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: yes + register: key + check_mode: yes + + - assert: + that: + - key.changed + + - name: Enable key rotation + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: yes + register: key + + - name: assert that key rotation is enabled + assert: + that: + - key is changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == True + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - 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: key + check_mode: yes + + - assert: + that: + - not key.changed + + - name: Enable key rotation (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: yes + register: key + + - assert: + that: + - not key is changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == True + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + # ------------------------------------------------------------------------------------------ + + - name: Disable key - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + enabled: no + register: key + check_mode: yes + + - assert: + that: + - key.changed + + - name: Disable key + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + enabled: no + register: key + + - name: assert that state is disabled + assert: + that: + - key is changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Disabled" + - key.enabled == False + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == True + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - name: Disable key (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + enabled: no + register: key + check_mode: yes + + - assert: + that: + - not key.changed + + - name: Disable key (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + enabled: no + register: key + + - assert: + that: + - not key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Disabled" + - key.enabled == False + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == True + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + # ------------------------------------------------------------------------------------------ + + - name: Delete key - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: absent + register: key + check_mode: yes + + - 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: 45 + + - name: Assert that state is pending deletion + vars: + now_time: '{{ lookup("pipe", "date -u +%Y-%m-%d\ %H:%M:%S") }}' + deletion_time: '{{ key.deletion_date[:19] | to_datetime("%Y-%m-%dT%H:%M:%S") }}' + assert: + that: + - key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "PendingDeletion" + - key.enabled == False + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == False + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + # 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: Delete key (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: absent + register: key + check_mode: yes + + - assert: + that: + - not key.changed + + - name: Delete key (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + state: absent + register: key + + - vars: + now_time: '{{ lookup("pipe", "date -u +%Y-%m-%d\ %H:%M:%S") }}' + deletion_time: '{{ key.deletion_date[:19] | to_datetime("%Y-%m-%dT%H:%M:%S") }}' + assert: + that: + - not key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "PendingDeletion" + - key.enabled == False + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == False + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + # 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: Cancel key deletion - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + register: key + check_mode: yes + + - assert: + that: + - key.changed + + - name: Cancel key deletion + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + register: key + + - assert: + that: + - key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == True + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + - "'deletion_date' not in key" + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - name: Cancel key deletion (idempotence) - check mode + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + register: key + check_mode: yes + + - assert: + that: + - not key.changed + + - name: Cancel key deletion (idempotence) + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + register: key + + - assert: + that: + - not key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == True + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + - "'deletion_date' not in key" + + # ------------------------------------------------------------------------------------------ + + - name: delete the key with a specific deletion window + aws_kms: + alias: '{{ kms_key_alias }}' + state: absent + pending_window: 7 + register: delete_kms + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - 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") }}' + assert: + that: + - delete_kms.key_state == "PendingDeletion" + - delete_kms.changed + # Times won't be perfect, allow a 24 hour window + - (( deletion_time | to_datetime ) - ( now_time | to_datetime )).days <= 7 + - (( deletion_time | to_datetime ) - ( now_time | to_datetime )).days >= 6 + + # ============================================================ + # test different key usage and specs + - name: create kms key with different specs + aws_kms: + alias: '{{ kms_key_alias }}-diff-spec-usage' + purge_grants: yes + key_spec: ECC_NIST_P256 + key_usage: SIGN_VERIFY + register: create_diff_kms + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - name: verify different specs on kms key + assert: + that: + - '"key_id" in create_diff_kms' + - create_diff_kms.key_id | length >= 36 + - not create_diff_kms.key_id.startswith("arn:aws") + - '"key_arn" in create_diff_kms' + - create_diff_kms.key_arn.endswith(create_diff_kms.key_id) + - create_diff_kms.key_arn.startswith("arn:aws") + - create_diff_kms.key_usage == 'SIGN_VERIFY' + - create_diff_kms.customer_master_key_spec == 'ECC_NIST_P256' + + always: + # ============================================================ + # CLEAN-UP + - name: finish off by deleting keys + aws_kms: + state: absent + alias: "{{ item }}" + pending_window: 7 + ignore_errors: True + loop: + - "{{ kms_key_alias }}" + - "{{ kms_key_alias }}-diff-spec-usage" + - "{{ kms_key_alias }}-check" diff --git a/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_tagging.yml b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_tagging.yml new file mode 100644 index 00000000000..60cb0dd837e --- /dev/null +++ b/tests/integration/targets/aws_kms/roles/aws_kms/tasks/test_tagging.yml @@ -0,0 +1,202 @@ +- block: + # ============================================================ + # PREPARATION + # + # Get some information about who we are before starting our tests + # we'll need this as soon as we start working on the policies + - name: get ARN of calling user + aws_caller_info: + register: aws_caller_info + + # ============================================================ + # 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: create a key + aws_kms: + alias: '{{ kms_key_alias }}' + tags: + Hello: World + state: present + enabled: yes + enable_key_rotation: no + register: key + + - name: assert that state is enabled + assert: + that: + - key is changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 1 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + + # ------------------------------------------------------------------------------------------ + + - name: Tag encryption key + aws_kms: + alias: '{{ kms_key_alias }}' + state: present + tags: + tag_one: tag_one + tag_two: tag_two + register: key + + - name: Assert tags added + assert: + that: + - key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 3 + - key.tags['Hello'] == 'World' + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + - "'tag_one' in key.tags" + - "'tag_two' in key.tags" + + - name: Sleep to wait for updates to propagate + wait_for: + timeout: 45 + + - name: Modify tags - 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: + - key.changed + + - 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: key + + - name: Assert tags correctly changed + assert: + that: + - key.changed + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 2 + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + - "'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: 45 + + - 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 + - '"key_id" in key' + - key.key_id | length >= 36 + - not key.key_id.startswith("arn:aws") + - '"key_arn" in key' + - key.key_arn.endswith(key.key_id) + - key.key_arn.startswith("arn:aws") + - key.key_state == "Enabled" + - key.enabled == True + - key.tags | length == 2 + - key.enable_key_rotation == false + - key.key_usage == 'ENCRYPT_DECRYPT' + - key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' + - key.grants | length == 0 + - key.key_policies | length == 1 + - key.key_policies[0].Id == 'key-default-1' + - key.description == '' + - "'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" + + always: + # ============================================================ + # CLEAN-UP + - name: finish off by deleting keys + aws_kms: + state: absent + alias: "{{ kms_key_alias }}" + pending_window: 7 + ignore_errors: True diff --git a/tests/integration/targets/aws_kms/templates/console-policy-no-key-rotation.j2 b/tests/integration/targets/aws_kms/roles/aws_kms/templates/console-policy-no-key-rotation.j2 similarity index 100% rename from tests/integration/targets/aws_kms/templates/console-policy-no-key-rotation.j2 rename to tests/integration/targets/aws_kms/roles/aws_kms/templates/console-policy-no-key-rotation.j2 diff --git a/tests/integration/targets/aws_kms/templates/console-policy.j2 b/tests/integration/targets/aws_kms/roles/aws_kms/templates/console-policy.j2 similarity index 100% rename from tests/integration/targets/aws_kms/templates/console-policy.j2 rename to tests/integration/targets/aws_kms/roles/aws_kms/templates/console-policy.j2 diff --git a/tests/integration/targets/aws_kms/runme.sh b/tests/integration/targets/aws_kms/runme.sh new file mode 100755 index 00000000000..5b5b69fbd1d --- /dev/null +++ b/tests/integration/targets/aws_kms/runme.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# +# Beware: most of our tests here are run in parallel. +# To add new tests you'll need to add a new host to the inventory and a matching +# '{{ inventory_hostname }}'.yml file in roles/aws_kms/tasks/ + + +set -eux + +export ANSIBLE_ROLES_PATH=../ + +ansible-playbook main.yml -i inventory "$@" diff --git a/tests/integration/targets/aws_kms/tasks/main.yml b/tests/integration/targets/aws_kms/tasks/main.yml deleted file mode 100644 index dc10cc92330..00000000000 --- a/tests/integration/targets/aws_kms/tasks/main.yml +++ /dev/null @@ -1,939 +0,0 @@ -- module_defaults: - group/aws: - region: "{{ aws_region }}" - aws_access_key: "{{ aws_access_key }}" - aws_secret_key: "{{ aws_secret_key }}" - security_token: "{{ security_token | default(omit) }}" - collections: - - amazon.aws - - block: - # ============================================================ - # PREPARATION - # - # Get some information about who we are before starting our tests - # we'll need this as soon as we start working on the policies - - name: get ARN of calling user - aws_caller_info: - register: aws_caller_info - - # IAM Roles completes before the Role is fully instantiated, create it here - # 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_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 - check mode - aws_kms: - alias: '{{ kms_key_alias }}-check' - tags: - Hello: World - state: present - enabled: yes - register: create_kms_check - check_mode: yes - - - name: find facts about the check mode key - aws_kms_info: - alias: '{{ kms_key_alias }}-check' - register: check_key - - - name: ensure that check mode worked as expected - assert: - that: - - check_key.kms_keys | length == 0 - - create_kms_check is changed - - - name: create a key - aws_kms: - alias: '{{ kms_key_alias }}' - tags: - 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") - - '"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: 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 - 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: - Hello: World - state: present - enabled: yes - enable_key_rotation: yes - register: create_kms - - - 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: 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 }}' - tags: - Hello: World - state: present - enabled: yes - enable_key_rotation: yes - register: create_kms - - - assert: - that: - - 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: - key_id: '{{ kms_key_id }}' - register: new_key - - - name: check that a key was found - assert: - that: - - 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 - aws_kms: - key_id: '{{ kms_key_id }}' - policy: "{{ lookup('template', 'console-policy.j2') }}" - register: kms_policy_changed - - - name: Policy should have been changed - assert: - that: - - kms_policy_changed is changed - - - 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') }}" - register: kms_policy_changed - check_mode: yes - - - 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: - - 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_key_alias }}' - grant_types: "role,role grant" - 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: Sleep to wait for updates to propagate - wait_for: - timeout: 10 - - - name: Add grant - 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 - - - name: assert grant would have been added - assert: - that: - - grant_one.changed - - - name: Add grant - 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 - - - name: assert grant added - assert: - that: - - grant_one.changed - - 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: - alias: '{{ kms_key_alias }}' - state: present - grants: - - name: another_grant - grantee_principal: "{{ iam_role_result.iam_role.arn }}" - retiring_principal: "{{ aws_caller_info.arn }}" - constraints: - encryption_context_equals: - Environment: second - Application: anotherapp - operations: - - Decrypt - - RetireGrant - register: grant_two - - - 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 }}' - state: present - grants: - - name: another_grant - grantee_principal: "{{ iam_role_result.iam_role.arn }}" - retiring_principal: "{{ aws_caller_info.arn }}" - constraints: - encryption_context_equals: - Environment: second - Application: anotherapp - operations: - - Decrypt - - RetireGrant - register: grant_two_again - - - name: Assert grant added - assert: - that: - - not grant_two_again.changed - - grant_two_again.grants|length == 2 - - - name: Update the grants with purge_grants set - aws_kms: - alias: '{{ kms_key_alias }}' - state: present - purge_grants: yes - grants: - - name: third_grant - grantee_principal: "{{ iam_role_result.iam_role.arn }}" - retiring_principal: "{{ aws_caller_info.arn }}" - constraints: - encryption_context_equals: - environment: third - application: onemoreapp - operations: - - Decrypt - - RetireGrant - register: grant_three - - - name: Assert grants replaced - assert: - that: - - grant_three.changed - - grant_three.grants | length == 1 - - - name: Update third grant to change encryption context equals to subset - aws_kms: - alias: '{{ kms_key_alias }}' - state: present - grants: - - name: third_grant - grantee_principal: "{{ iam_role_result.iam_role.arn }}" - retiring_principal: "{{ aws_caller_info.arn }}" - constraints: - encryption_context_subset: - environment: third - application: onemoreapp - operations: - - Decrypt - - RetireGrant - register: grant_three_update - - - name: Assert grants replaced - assert: - that: - - 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 - aws_kms: - alias: '{{ kms_key_alias }}' - state: present - tags: - tag_one: tag_one - tag_two: tag_two - register: tag_kms - - - name: Assert tags added and grants remain in place - assert: - that: - - tag_kms.changed - - tag_kms.grants | length == 1 - - "'tag_one' in tag_kms.tags" - - "'tag_two' in tag_kms.tags" - - - name: Modify tags - 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: - - key.changed - - - 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: key - - - name: Assert tags correctly changed - assert: - that: - - 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 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: key - check_mode: yes - - - 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: - - key.changed - - key.key_state == "Disabled" - - not key.enabled - - - name: Sleep to wait for updates to propagate - wait_for: - timeout: 10 - - - name: Disable key (idempotence) - check mode - aws_kms: - alias: '{{ kms_key_alias }}' - state: present - description: test key for testing - enabled: no - register: key - check_mode: yes - - - assert: - that: - - not key.changed - - - name: Disable key (idempotence) - aws_kms: - alias: '{{ kms_key_alias }}' - state: present - description: test key for testing - enabled: no - register: key - - - 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: - - 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: - filters: - "tag:Tag Three": "{{ resource_prefix }}" - register: info_tag_filtered - - - name: Verify all expected attributes - vars: - tagged_key: '{{ info_tag_filtered.kms_keys[0] }}' - assert: - that: - - info_tag_filtered.kms_keys | length == 1 - - "'aliases' in tagged_key" - - kms_key_alias in tagged_key.aliases - - "'aws_account_id' in tagged_key" - - tagged_key.aws_account_id == aws_caller_info.account - - "'creation_date' in tagged_key" - - "'customer_master_key_spec' in tagged_key" - - tagged_key.customer_master_key_spec == 'SYMMETRIC_DEFAULT' - - "'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 - - "'encryption_algorithms' in tagged_key" - - "'SYMMETRIC_DEFAULT' in tagged_key.encryption_algorithms" - - "'grants' in tagged_key" - - "'key_arn' in tagged_key" - - tagged_key.key_arn == kms_key_arn - - "'key_id' in tagged_key" - - tagged_key.key_id == kms_key_id - - "'key_manager' in tagged_key" - - tagged_key.key_manager == 'CUSTOMER' - - "'key_state' in tagged_key" - - tagged_key.key_state == 'Enabled' - - "'key_usage' in tagged_key" - - tagged_key.key_usage == 'ENCRYPT_DECRYPT' - - "'origin' in tagged_key" - - tagged_key.origin == "AWS_KMS" - - "'policies' in tagged_key" - - tagged_key.policies | length == 1 - - "'tags' in tagged_key" - - "'Tag Three' in tagged_key.tags" - - tagged_key.tags['Tag Three'] == resource_prefix - - "'tag_two' in tagged_key.tags" - - tagged_key.tags['tag_two'] == 'tag_two_updated' - - # ------------------------------------------------------------------------------------------ - - - name: Delete key - check mode - aws_kms: - alias: '{{ kms_key_alias }}' - state: absent - register: key - check_mode: yes - - - 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: '{{ key.deletion_date[:19] | to_datetime("%Y-%m-%dT%H:%M:%S") }}' - assert: - that: - - 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: Delete key (idempotence) - check mode - aws_kms: - alias: '{{ kms_key_alias }}' - state: absent - register: key - check_mode: yes - - - assert: - that: - - not key.changed - - - 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 - register: key - check_mode: yes - - - assert: - that: - - key.changed - - - name: Cancel key deletion - aws_kms: - alias: '{{ kms_key_alias }}' - state: present - register: key - - - 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: - - 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: - alias: '{{ kms_key_alias }}' - state: absent - pending_window: 7 - register: delete_kms - - - 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") }}' - assert: - that: - - delete_kms.key_state == "PendingDeletion" - - delete_kms.changed - # Times won't be perfect, allow a 24 hour window - - (( deletion_time | to_datetime ) - ( now_time | to_datetime )).days <= 7 - - (( deletion_time | to_datetime ) - ( now_time | to_datetime )).days >= 6 - - # ============================================================ - # test different key usage and specs - - name: create kms key with different specs - aws_kms: - alias: '{{ kms_key_alias }}-diff-spec-usage' - purge_grants: yes - key_spec: ECC_NIST_P256 - key_usage: SIGN_VERIFY - register: create_diff_kms - - - name: verify different specs on kms key - assert: - that: - - '"key_id" in create_diff_kms' - - create_diff_kms.key_id | length >= 36 - - not create_diff_kms.key_id.startswith("arn:aws") - - '"key_arn" in create_diff_kms' - - create_diff_kms.key_arn.endswith(create_diff_kms.key_id) - - create_diff_kms.key_arn.startswith("arn:aws") - - create_diff_kms.key_usage == 'SIGN_VERIFY' - - create_diff_kms.customer_master_key_spec == 'ECC_NIST_P256' - - always: - # ============================================================ - # CLEAN-UP - - name: finish off by deleting keys - aws_kms: - state: absent - alias: "{{ item }}" - pending_window: 7 - 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_key_alias }}' - state: absent - ignore_errors: True