Skip to content

Commit

Permalink
kms_key - Finish deprecation of policy_grant_types and related keys (#…
Browse files Browse the repository at this point in the history
…1344)

kms_key - Finish deprecation of policy_grant_types and related keys

SUMMARY
Managing the KMS IAM Policy via policy_mode and policy_grant_types was fragile and previously deprecated.
Complete the deprecation and remove the options in in favour of the policy option.
ISSUE TYPE

Feature Pull Request

COMPONENT NAME
plugins/modules/kms_key.py
ADDITIONAL INFORMATION
Original deprecation: ansible/ansible#60561

Reviewed-by: Markus Bergholz <git@osuv.de>
Reviewed-by: Alina Buzachis <None>
  • Loading branch information
tremble authored Jul 11, 2022
1 parent 5097a76 commit 9fb435d
Show file tree
Hide file tree
Showing 3 changed files with 2 additions and 243 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/1344-kms_key-policy_grant_types.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
breaking_changes:
- kms_key - managing the KMS IAM Policy via ``policy_mode`` and ``policy_grant_types`` was previously deprecated and has been removed in favor of the ``policy`` option (https://github.com/ansible-collections/community.aws/pull/1344).
212 changes: 0 additions & 212 deletions plugins/modules/kms_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,62 +43,6 @@
- Whether the key should be automatically rotated every year.
required: false
type: bool
policy_mode:
description:
- (deprecated) Grant or deny access.
- Used for modifying the Key Policy rather than modifying a grant and only
works on the default policy created through the AWS Console.
- This option has been deprecated, and will be removed in a release after 2021-12-01. Use I(policy) instead.
default: grant
choices: [ grant, deny ]
aliases:
- mode
type: str
policy_role_name:
description:
- (deprecated) Role to allow/deny access.
- One of I(policy_role_name) or I(policy_role_arn) are required.
- Used for modifying the Key Policy rather than modifying a grant and only
works on the default policy created through the AWS Console.
- This option has been deprecated, and will be removed in a release after 2021-12-01. Use I(policy) instead.
required: false
aliases:
- role_name
type: str
policy_role_arn:
description:
- (deprecated) ARN of role to allow/deny access.
- One of I(policy_role_name) or I(policy_role_arn) are required.
- Used for modifying the Key Policy rather than modifying a grant and only
works on the default policy created through the AWS Console.
- This option has been deprecated, and will be removed in a release after 2021-12-01. Use I(policy) instead.
type: str
required: false
aliases:
- role_arn
policy_grant_types:
description:
- (deprecated) List of grants to give to user/role. Likely "role,role grant" or "role,role grant,admin".
- Required when I(policy_mode=grant).
- Used for modifying the Key Policy rather than modifying a grant and only
works on the default policy created through the AWS Console.
- This option has been deprecated, and will be removed in a release after 2021-12-01. Use I(policy) instead.
required: false
aliases:
- grant_types
type: list
elements: str
policy_clean_invalid_entries:
description:
- (deprecated) If adding/removing a role and invalid grantees are found, remove them. These entries will cause an update to fail in all known cases.
- Only cleans if changes are being made.
- Used for modifying the Key Policy rather than modifying a grant and only
works on the default policy created through the AWS Console.
- This option has been deprecated, and will be removed in a release after 2021-12-01. Use I(policy) instead.
type: bool
default: true
aliases:
- clean_invalid_entries
state:
description:
- Whether a key should be present or absent.
Expand Down Expand Up @@ -205,22 +149,6 @@
'''

EXAMPLES = r'''
# Managing the KMS IAM Policy via policy_mode and policy_grant_types is fragile
# and has been deprecated in favour of the policy option.
- name: grant user-style access to production secrets
community.aws.kms_key:
args:
alias: "alias/my_production_secrets"
policy_mode: grant
policy_role_name: "prod-appServerRole-1R5AQG2BSEL6L"
policy_grant_types: "role,role grant"
- name: remove access to production secrets from role
community.aws.kms_key:
args:
alias: "alias/my_production_secrets"
policy_mode: deny
policy_role_name: "prod-appServerRole-1R5AQG2BSEL6L"
# Create a new KMS key
- community.aws.kms_key:
alias: mykey
Expand Down Expand Up @@ -488,15 +416,12 @@
}

import json
import re

try:
import botocore
except ImportError:
pass # caught by AnsibleAWSModule

from ansible.module_utils.six import string_types

from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry
Expand Down Expand Up @@ -987,106 +912,6 @@ def get_arn_from_role_name(iam, rolename):
raise Exception('could not find arn for name {0}.'.format(rolename))


def _clean_statement_principals(statement, clean_invalid_entries):

# create Principal and 'AWS' so we can safely use them later.
if not isinstance(statement.get('Principal'), dict):
statement['Principal'] = dict()

# If we have a single AWS Principal, ensure we still have a list (to manipulate)
if 'AWS' in statement['Principal'] and isinstance(statement['Principal']['AWS'], string_types):
statement['Principal']['AWS'] = [statement['Principal']['AWS']]
if not isinstance(statement['Principal'].get('AWS'), list):
statement['Principal']['AWS'] = list()

valid_princ = re.compile('^arn:aws:(iam|sts)::')

invalid_entries = [item for item in statement['Principal']['AWS'] if not valid_princ.match(item)]
valid_entries = [item for item in statement['Principal']['AWS'] if valid_princ.match(item)]

if bool(invalid_entries) and clean_invalid_entries:
statement['Principal']['AWS'] = valid_entries
return True

return False


def _do_statement_grant(statement, role_arn, grant_types, mode, grant_type):

if mode == 'grant':
if grant_type in grant_types:
if role_arn not in statement['Principal']['AWS']: # needs to be added.
statement['Principal']['AWS'].append(role_arn)
return 'add'
elif role_arn in statement['Principal']['AWS']: # not one the places the role should be
statement['Principal']['AWS'].remove(role_arn)
return 'remove'
return None

if mode == 'deny' and role_arn in statement['Principal']['AWS']:
# we don't selectively deny. that's a grant with a
# smaller list. so deny=remove all of this arn.
statement['Principal']['AWS'].remove(role_arn)
return 'remove'
return None


def do_policy_grant(module, kms, keyarn, role_arn, grant_types, mode='grant', dry_run=True, clean_invalid_entries=True):
ret = {}
policy = json.loads(get_key_policy_with_backoff(kms, keyarn, 'default')['Policy'])

changes_needed = {}
assert_policy_shape(module, policy)
had_invalid_entries = False
for statement in policy['Statement']:
# We already tested that these are the only types in the statements
for grant_type in statement_label:
# Are we on this grant type's statement?
if statement['Sid'] != statement_label[grant_type]:
continue

had_invalid_entries |= _clean_statement_principals(statement, clean_invalid_entries)
change = _do_statement_grant(statement, role_arn, grant_types, mode, grant_type)
if change:
changes_needed[grant_type] = change

ret['changes_needed'] = changes_needed
ret['had_invalid_entries'] = had_invalid_entries
ret['new_policy'] = policy
ret['changed'] = bool(changes_needed)

if dry_run or not ret['changed']:
return ret

try:
policy_json_string = json.dumps(policy)
kms.put_key_policy(KeyId=keyarn, PolicyName='default', Policy=policy_json_string)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg='Could not update key_policy', new_policy=policy_json_string)

return ret


def assert_policy_shape(module, policy):
'''Since the policy seems a little, uh, fragile, make sure we know approximately what we're looking at.'''
errors = []
if policy['Version'] != "2012-10-17":
errors.append('Unknown version/date ({0}) of policy. Things are probably different than we assumed they were.'.format(policy['Version']))

found_statement_type = {}
for statement in policy['Statement']:
for label, sidlabel in statement_label.items():
if statement['Sid'] == sidlabel:
found_statement_type[label] = True

for statementtype in statement_label:
if not found_statement_type.get(statementtype):
errors.append('Policy is missing {0}.'.format(statementtype))

if errors:
module.fail_json(msg='Problems asserting policy shape. Cowardly refusing to modify it', errors=errors, policy=policy)


def canonicalize_alias_name(alias):
if alias is None:
return None
Expand Down Expand Up @@ -1117,38 +942,9 @@ def fetch_key_metadata(connection, module, key_id, alias):
module.fail_json_aws(e, 'Failed to fetch key metadata.')


def update_policy_grants(connection, module, key_metadata, mode):
iam = module.client('iam')
key_id = key_metadata['Arn']

if module.params.get('policy_role_name') and not module.params.get('policy_role_arn'):
module.params['policy_role_arn'] = get_arn_from_role_name(iam, module.params['policy_role_name'])
if not module.params.get('policy_role_arn'):
module.fail_json(msg='policy_role_arn or policy_role_name is required to {0}'.format(module.params['policy_mode']))

# check the grant types for 'grant' only.
if mode == 'grant':
for grant_type in module.params['policy_grant_types']:
if grant_type not in statement_label:
module.fail_json(msg='{0} is an unknown grant type.'.format(grant_type))

return do_policy_grant(module, connection,
key_id,
module.params['policy_role_arn'],
module.params['policy_grant_types'],
mode=mode,
dry_run=module.check_mode,
clean_invalid_entries=module.params['policy_clean_invalid_entries'])


def main():
argument_spec = dict(
alias=dict(aliases=['key_alias']),
policy_mode=dict(aliases=['mode'], choices=['grant', 'deny'], default='grant'),
policy_role_name=dict(aliases=['role_name']),
policy_role_arn=dict(aliases=['role_arn']),
policy_grant_types=dict(aliases=['grant_types'], type='list', elements='str'),
policy_clean_invalid_entries=dict(aliases=['clean_invalid_entries'], type='bool', default=True),
pending_window=dict(aliases=['deletion_delay'], type='int'),
key_id=dict(aliases=['key_arn']),
description=dict(),
Expand All @@ -1171,8 +967,6 @@ def main():
required_one_of=[['alias', 'key_id']],
)

mode = module.params['policy_mode']

kms = module.client('kms')

if module.params.get('purge_tags') is None:
Expand All @@ -1191,12 +985,6 @@ def main():
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('policy_grant_types') or mode == 'deny':
module.deprecate('Managing the KMS IAM Policy via policy_mode and policy_grant_types is fragile'
' and has been deprecated in favour of the policy option.', date='2021-12-01', collection_name='community.aws')
result = update_policy_grants(kms, module, key_metadata, mode)
module.exit_json(**result)

if module.params.get('state') == 'absent':
if key_metadata is None:
module.exit_json(changed=False)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,37 +258,6 @@

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

- 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 }}'
Expand Down

0 comments on commit 9fb435d

Please sign in to comment.