Skip to content

Commit

Permalink
kms_key: Add multi region support to create_key (ansible-collections#…
Browse files Browse the repository at this point in the history
…1290)

kms_key: Add multi region support to create_key

Signed-off-by: GomathiselviS [email protected]
SUMMARY


Fixes ansible-collections#1281
ISSUE TYPE


Feature Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: Gonéri Le Bouder <[email protected]>
Reviewed-by: Jill R <None>
Reviewed-by: Mark Chappell <None>
Reviewed-by: Alina Buzachis <None>
Reviewed-by: GomathiselviS <None>
  • Loading branch information
GomathiselviS authored Dec 6, 2022
1 parent e823f89 commit a4ab720
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 25 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/1290-create_multi_region_key.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- kms_key - Add multi_region option to create_key (https://github.com/ansible-collections/amazon.aws/pull/1290).
128 changes: 103 additions & 25 deletions plugins/modules/kms_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@
- A description of the CMK.
- Use a description that helps you decide whether the CMK is appropriate for a task.
type: str
multi_region:
description:
- Whether to create a multi-Region primary key or not.
default: False
type: bool
version_added: 5.2.0
pending_window:
description:
- The number of days between requesting deletion of the CMK and when it will actually be deleted.
Expand Down Expand Up @@ -158,6 +164,14 @@
Name: myKey
Purpose: protect_stuff
# Create a new multi-region KMS key
- amazon.aws.kms_key:
alias: mykey
multi_region: true
tags:
Name: myKey
Purpose: protect_stuff
# Update previous key with more tags
- amazon.aws.kms_key:
alias: mykey
Expand Down Expand Up @@ -409,6 +423,16 @@
description: Whether there are invalid (non-ARN) entries in the KMS entry. These don't count as a change, but will be removed if any changes are being made.
type: bool
returned: always
multi_region:
description:
- Indicates whether the CMK is a multi-Region C(True) or regional C(False) key.
- This value is True for multi-Region primary and replica CMKs and False for regional CMKs.
type: bool
version_added: 5.2.0
returned: always
sample: False
'''

# these mappings are used to go from simple labels to the actual 'Sid' values returned
Expand Down Expand Up @@ -519,16 +543,18 @@ def get_kms_tags(connection, module, key_id):
def get_kms_policies(connection, module, key_id):
try:
policies = list_key_policies_with_backoff(connection, key_id)['PolicyNames']
return [get_key_policy_with_backoff(connection, key_id, policy)['Policy'] for
policy in policies]
return [
get_key_policy_with_backoff(connection, key_id, policy)['Policy']
for policy in policies
]
except is_boto3_error_code('AccessDeniedException'):
return []
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Failed to obtain key policies")


def camel_to_snake_grant(grant):
''' camel_to_snake_grant snakifies everything except the encryption context '''
'''camel_to_snake_grant snakifies everything except the encryption context '''
constraints = grant.get('Constraints', {})
result = camel_dict_to_snake_dict(grant)
if 'EncryptionContextEquals' in constraints:
Expand Down Expand Up @@ -561,8 +587,10 @@ def get_key_details(connection, module, key_id):

# grants and tags get snakified differently
try:
result['grants'] = [camel_to_snake_grant(grant) for grant in
get_kms_grants_with_backoff(connection, key_id)['Grants']]
result['grants'] = [
camel_to_snake_grant(grant)
for grant in get_kms_grants_with_backoff(connection, key_id)['Grants']
]
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Failed to obtain key grants")
tags = get_kms_tags(connection, module, key_id)
Expand All @@ -582,8 +610,9 @@ def get_kms_facts(connection, module):


def convert_grant_params(grant, key):
grant_params = dict(KeyId=key['key_arn'],
GranteePrincipal=grant['grantee_principal'])
grant_params = dict(
KeyId=key['key_arn'], GranteePrincipal=grant['grantee_principal']
)
if grant.get('operations'):
grant_params['Operations'] = grant['operations']
if grant.get('retiring_principal'):
Expand Down Expand Up @@ -751,7 +780,11 @@ def update_tags(connection, module, key, desired_tags, purge_tags):
module.fail_json_aws(e, msg="Unable to remove tag")
if to_add:
try:
tags = ansible_dict_to_boto3_tag_list(module.params['tags'], tag_name_key_name='TagKey', tag_value_key_name='TagValue')
tags = ansible_dict_to_boto3_tag_list(
module.params['tags'],
tag_name_key_name='TagKey',
tag_value_key_name='TagValue',
)
connection.tag_resource(KeyId=key_id, Tags=tags)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to add tag to key")
Expand Down Expand Up @@ -859,16 +892,21 @@ def update_key(connection, module, key):
def create_key(connection, module):
key_usage = module.params.get('key_usage')
key_spec = module.params.get('key_spec')
multi_region = module.params.get('multi_region')
tags_list = ansible_dict_to_boto3_tag_list(
module.params['tags'] or {},
# KMS doesn't use "Key" and "Value" as other APIs do.
tag_name_key_name='TagKey', tag_value_key_name='TagValue'
# KMS doesn't use 'Key' and 'Value' as other APIs do.
tag_name_key_name='TagKey',
tag_value_key_name='TagValue',
)
params = dict(
BypassPolicyLockoutSafetyCheck=False,
Tags=tags_list,
KeyUsage=key_usage,
CustomerMasterKeySpec=key_spec,
Origin='AWS_KMS',
MultiRegion=multi_region,
)
params = dict(BypassPolicyLockoutSafetyCheck=False,
Tags=tags_list,
KeyUsage=key_usage,
CustomerMasterKeySpec=key_spec,
Origin='AWS_KMS')

if module.check_mode:
return {'changed': True}
Expand All @@ -877,7 +915,6 @@ def create_key(connection, module):
params['Description'] = module.params['description']
if module.params.get('policy'):
params['Policy'] = module.params['policy']

try:
result = connection.create_key(**params)['KeyMetadata']
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
Expand Down Expand Up @@ -940,7 +977,29 @@ def fetch_key_metadata(connection, module, key_id, alias):
except connection.exceptions.NotFoundException:
return None
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, 'Failed to fetch key metadata.')
module.fail_json_aws(e, "Failed to fetch key metadata.")


def validate_params(module, key_metadata):
# 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 {0} to update'.format(
module.params.get('key_id')
)
)
if (
module.params.get('multi_region')
and key_metadata
and module.params.get('state') == 'present'
):
module.fail_json(
msg='You cannot change the multi-region property on an existing key.'
)


def main():
Expand All @@ -950,16 +1009,34 @@ def main():
key_id=dict(aliases=['key_arn']),
description=dict(),
enabled=dict(type='bool', default=True),
multi_region=dict(type='bool', default=False),
tags=dict(type='dict', aliases=['resource_tags']),
purge_tags=dict(type='bool', default=True),
grants=dict(type='list', default=[], elements='dict'),
policy=dict(type='json'),
purge_grants=dict(type='bool', default=False),
state=dict(default='present', choices=['present', 'absent']),
enable_key_rotation=(dict(type='bool')),
key_spec=dict(type='str', default='SYMMETRIC_DEFAULT', aliases=['customer_master_key_spec'],
choices=['SYMMETRIC_DEFAULT', 'RSA_2048', 'RSA_3072', 'RSA_4096', 'ECC_NIST_P256', 'ECC_NIST_P384', 'ECC_NIST_P521', 'ECC_SECG_P256K1']),
key_usage=dict(type='str', default='ENCRYPT_DECRYPT', choices=['ENCRYPT_DECRYPT', 'SIGN_VERIFY']),
key_spec=dict(
type='str',
default='SYMMETRIC_DEFAULT',
aliases=['customer_master_key_spec'],
choices=[
'SYMMETRIC_DEFAULT',
'RSA_2048',
'RSA_3072',
'RSA_4096',
'ECC_NIST_P256',
'ECC_NIST_P384',
'ECC_NIST_P521',
'ECC_SECG_P256K1',
],
),
key_usage=dict(
type='str',
default='ENCRYPT_DECRYPT',
choices=['ENCRYPT_DECRYPT', 'SIGN_VERIFY'],
),
)

module = AnsibleAWSModule(
Expand All @@ -970,13 +1047,14 @@ 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='amazon.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='amazon.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 {0} to update".format(module.params.get('key_id')))
validate_params(module, key_metadata)

if module.params.get('state') == 'absent':
if key_metadata is None:
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/targets/kms_key/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ states
grants
modify
tagging
# CI's AWS account doesnot support multi region
# multi_region

[all:vars]
ansible_connection=local
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
- 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
- name: See whether key exists and its current state
kms_key_info:
alias: '{{ kms_key_alias }}'
- name: create a multi region key - check mode
kms_key:
alias: '{{ kms_key_alias }}-check'
tags:
Hello: World
state: present
multi_region: True
enabled: yes
register: key_check
check_mode: yes
- name: find facts about the check mode key
kms_key_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 multi region key
kms_key:
alias: '{{ kms_key_alias }}'
tags:
Hello: World
state: present
enabled: yes
multi_region: True
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 == ''
- key.multi_region == True

- name: Sleep to wait for updates to propagate
wait_for:
timeout: 45

- name: create a key (expect failure)
kms_key:
alias: '{{ kms_key_alias }}'
tags:
Hello: World
state: present
enabled: yes
multi_region: True
register: result
ignore_errors: True

- assert:
that:
- result is failed
- result.msg != "MODULE FAILURE"
- result.changed == False
- '"You cannot change the multi-region property on an existing key." in result.msg'

always:
# ============================================================
# CLEAN-UP
- name: finish off by deleting keys
kms_key:
state: absent
alias: '{{ item }}'
pending_window: 7
ignore_errors: true
loop:
- '{{ kms_key_alias }}'
- '{{ kms_key_alias }}-diff-spec-usage'
- '{{ kms_key_alias }}-check'
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
- key.key_policies | length == 1
- key.key_policies[0].Id == 'key-default-1'
- key.description == ''
- key.multi_region == False

- name: Sleep to wait for updates to propagate
wait_for:
Expand Down Expand Up @@ -105,6 +106,7 @@
- key.key_policies | length == 1
- key.key_policies[0].Id == 'key-default-1'
- key.description == ''
- key.multi_region == False

# ------------------------------------------------------------------------------------------

Expand Down
Loading

0 comments on commit a4ab720

Please sign in to comment.