Skip to content

Commit

Permalink
Fix changed always true when kms key alias used
Browse files Browse the repository at this point in the history
Fix output.exists value on create

Fix tags being changed to lower case
  • Loading branch information
msven committed Mar 27, 2021
1 parent 0412f00 commit cb4c983
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 11 deletions.
50 changes: 47 additions & 3 deletions plugins/modules/cloudtrail.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,24 @@
)


def get_kms_key_aliases(module, client, keyId):
"""
get list of key aliases
module : AnsibleAWSModule object
client : boto3 client connection object for kms
keyId : keyId to get aliases for
"""
try:
key_resp = client.list_aliases(KeyId=keyId)
except (BotoCoreError, ClientError) as err:
# Don't fail here, just return [] to maintain backwards compat
# in case user doesn't have kms:ListAliases permissions
return []

return key_resp['Aliases']


def create_trail(module, client, ct_params):
"""
Creates a CloudTrail
Expand Down Expand Up @@ -500,6 +518,7 @@ def main():
# If the trail exists set the result exists variable
if trail is not None:
results['exists'] = True
initial_kms_key_id = trail.get('KmsKeyId')

if state == 'absent' and results['exists']:
# If Trail exists go ahead and delete
Expand All @@ -524,7 +543,11 @@ def main():
val = ct_params.get(key)
if val != trail.get(tkey):
do_update = True
results['changed'] = True
if tkey != 'KmsKeyId':
# We'll check if the KmsKeyId casues changes later since
# user could've provided a key alias, alias arn, or key id
# and trail['KmsKeyId'] is always a key arn
results['changed'] = True
# If we are in check mode copy the changed values to the trail facts in result output to show what would change.
if module.check_mode:
trail.update({tkey: ct_params.get(key)})
Expand All @@ -533,6 +556,26 @@ def main():
update_trail(module, client, ct_params)
trail = get_trail_facts(module, client, ct_params['Name'])

# Determine if KmsKeyId changed
if not module.check_mode:
if initial_kms_key_id != trail.get('KmsKeyId'):
results['changed'] = True
else:
new_key = ct_params.get('KmsKeyId')
if initial_kms_key_id != new_key:
# Assume changed for a moment
results['changed'] = True

# However, new_key could be a key id, alias arn, or alias name
# that maps back to the key arn in initial_kms_key_id. So check
# all aliases for a match.
initial_aliases = get_kms_key_aliases(module, module.client('kms'), initial_kms_key_id)
for a in initial_aliases:
if(a['AliasName'] == new_key or
a['AliasArn'] == new_key or
a['TargetKeyId'] == new_key):
results['changed'] = False

# Check if we need to start/stop logging
if enable_logging and not trail['IsLogging']:
results['changed'] = True
Expand All @@ -554,11 +597,12 @@ def main():
results['changed'] = True
trail['tags'] = tags
# Populate trail facts in output
results['trail'] = camel_dict_to_snake_dict(trail)
results['trail'] = camel_dict_to_snake_dict(trail, ignore_list=['tags'])

elif state == 'present' and not results['exists']:
# Trail doesn't exist just go create it
results['changed'] = True
results['exists'] = True
if not module.check_mode:
# If we aren't in check_mode then actually create it
created_trail = create_trail(module, client, ct_params)
Expand Down Expand Up @@ -598,7 +642,7 @@ def main():
trail['IsLogging'] = enable_logging
trail['tags'] = tags
# Populate trail facts in output
results['trail'] = camel_dict_to_snake_dict(trail)
results['trail'] = camel_dict_to_snake_dict(trail, ignore_list=['tags'])

module.exit_json(**results)

Expand Down
1 change: 1 addition & 0 deletions tests/integration/targets/cloudtrail/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ sns_topic: '{{ resource_prefix }}-cloudtrail-notifications'
cloudtrail_prefix: 'test-prefix'
cloudwatch_log_group: '{{ resource_prefix }}-cloudtrail'
cloudwatch_role: '{{ resource_prefix }}-cloudtrail'
cloudwatch_no_kms_role: '{{ resource_prefix }}-cloudtrail2'
96 changes: 88 additions & 8 deletions tests/integration/targets/cloudtrail/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
# - Using blank string for CloudWatch Log Group / Role doesn't remove them
#
# Possible Bugs:
# - output.exists == false when creating
# - Changed reports true when using a KMS alias
# - Tags Keys are being lower-cased

- module_defaults:
group/aws:
Expand Down Expand Up @@ -175,6 +172,27 @@
policy_name: 'CloudWatch'
policy_json: "{{ lookup('template', 'cloudwatch-policy.j2') | to_json }}"

- name: 'Create CloudWatch IAM Role with no kms permissions'
iam_role:
state: present
name: '{{ cloudwatch_no_kms_role }}'
assume_role_policy_document: "{{ lookup('template', 'cloudtrail-no-kms-assume-policy.j2') }}"
managed_policies:
- "arn:aws:iam::aws:policy/AWSCloudTrail_FullAccess"
register: output_cloudwatch_no_kms_role

- name: pause to ensure role exists before attaching policy
pause:
seconds: 15

- name: 'Add inline policy to CloudWatch Role'
iam_policy:
state: present
iam_type: role
iam_name: '{{ cloudwatch_no_kms_role }}'
policy_name: 'CloudWatchNokms'
policy_json: "{{ lookup('template', 'cloudtrail-no-kms-policy.j2') }}"

# ============================================================
# Tests
# ============================================================
Expand All @@ -197,8 +215,7 @@
- assert:
that:
- output is changed
# XXX This appears to be a bug...
#- output.exists == True
- output.exists == True
- output.trail.name == cloudtrail_name

- name: 'No-op update to trail'
Expand Down Expand Up @@ -451,7 +468,7 @@
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 2
- '("tag2" in output.trail.tags) and (output.trail.tags["tag2"] == "Value2")'
#- '("Tag3" in output.trail.tags) and (output.trail.tags["Tag3"] == "Value3")'
- '("Tag3" in output.trail.tags) and (output.trail.tags["Tag3"] == "Value3")'

- name: 'Change tags (no change)'
cloudtrail:
Expand All @@ -467,7 +484,7 @@
- output.trail.name == cloudtrail_name
- output.trail.tags | length == 2
- '("tag2" in output.trail.tags) and (output.trail.tags["tag2"] == "Value2")'
#- '("Tag3" in output.trail.tags) and (output.trail.tags["Tag3"] == "Value3")'
- '("Tag3" in output.trail.tags) and (output.trail.tags["Tag3"] == "Value3")'

- name: 'Remove tags (CHECK MODE)'
cloudtrail:
Expand Down Expand Up @@ -1146,6 +1163,18 @@
- output is not changed
- output.trail.kms_key_id == kms_key.key_arn

- name: 'Enable logging encryption (no change, check mode)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
kms_key_id: '{{ kms_key.key_arn }}'
check_mode: yes
register: output
- assert:
that:
- output is not changed
- output.trail.kms_key_id == kms_key.key_arn

- name: 'No-op update to trail'
cloudtrail:
state: present
Expand Down Expand Up @@ -1219,9 +1248,48 @@
register: output
- assert:
that:
# - output is not changed
- output is not changed
- output.trail.kms_key_id == kms_key.key_arn

- name: 'Update logging encryption to alias (CHECK MODE, no change)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
kms_key_id: '{{ kms_key.key_id }}' # Test when using key id
register: output
check_mode: yes
- assert:
that:
- output is not changed
- output.trail.kms_key_id == kms_key.key_id

# Assume role to a role with Denied access to KMS

- community.aws.sts_assume_role:
role_arn: '{{ output_cloudwatch_no_kms_role.arn }}'
role_session_name: "cloudtrailNoKms"
aws_access_key: '{{ aws_access_key }}'
aws_secret_key: '{{ aws_secret_key }}'
security_token: '{{ security_token | default(omit) }}'
region: '{{ aws_region }}'
register: noKms_assumed_role

- name: 'Enable logging encryption w/ alias (no change, no kms permmissions, check mode)'
cloudtrail:
state: present
name: '{{ cloudtrail_name }}'
kms_key_id: 'alias/{{ kms_alias }}'
aws_access_key: "{{ noKms_assumed_role.sts_creds.access_key }}"
aws_secret_key: "{{ noKms_assumed_role.sts_creds.secret_key }}"
security_token: "{{ noKms_assumed_role.sts_creds.session_token }}"
check_mode: yes
register: output
- assert:
that:
- output is changed
# when using check_mode, with no kms permissions, and not giving kms_key_id as a key arn
# output will always be marked as changed.

#- name: 'Disable logging encryption (CHECK MODE)'
# cloudtrail:
# state: present
Expand Down Expand Up @@ -1423,3 +1491,15 @@
state: absent
name: '{{ cloudwatch_role }}'
ignore_errors: yes
- name: 'Remove inline policy to CloudWatch Role'
iam_policy:
state: absent
iam_type: role
iam_name: '{{ cloudwatch_no_kms_role }}'
policy_name: 'CloudWatchNokms'
ignore_errors: yes
- name: 'Delete CloudWatch No KMS IAM Role'
iam_role:
state: absent
name: '{{ cloudwatch_no_kms_role }}'
ignore_errors: yes
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeRole",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::{{ aws_caller_info.account }}:root" },
"Action": "sts:AssumeRole"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "kmsDeny",
"Effect": "Deny",
"Action": [ "kms:*" ],
"Resource": [ "*" ]
}
]
}

0 comments on commit cb4c983

Please sign in to comment.