From 15e52407a78620c72fcc14f3e2e6a72d9d35232a Mon Sep 17 00:00:00 2001 From: jillr Date: Mon, 2 Mar 2020 19:25:18 +0000 Subject: [PATCH 01/14] Initial commit This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/eb75681585a23ea79e642b86a0f8e64e0f40a6d7 --- plugins/modules/cloudtrail.py | 609 ++++++++++++++++++ tests/integration/targets/cloudtrail/aliases | 2 + .../templates/cloudwatch-assume-policy.j2 | 13 + .../cloudtrail/templates/cloudwatch-policy.j2 | 17 + .../cloudtrail/templates/kms-policy.j2 | 34 + .../targets/cloudtrail/templates/s3-policy.j2 | 34 + .../cloudtrail/templates/sns-policy.j2 | 34 + 7 files changed, 743 insertions(+) create mode 100644 plugins/modules/cloudtrail.py create mode 100644 tests/integration/targets/cloudtrail/aliases create mode 100644 tests/integration/targets/cloudtrail/templates/cloudwatch-assume-policy.j2 create mode 100644 tests/integration/targets/cloudtrail/templates/cloudwatch-policy.j2 create mode 100644 tests/integration/targets/cloudtrail/templates/kms-policy.j2 create mode 100644 tests/integration/targets/cloudtrail/templates/s3-policy.j2 create mode 100644 tests/integration/targets/cloudtrail/templates/sns-policy.j2 diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py new file mode 100644 index 00000000000..0dc8feb64af --- /dev/null +++ b/plugins/modules/cloudtrail.py @@ -0,0 +1,609 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: cloudtrail +short_description: manage CloudTrail create, delete, update +description: + - Creates, deletes, or updates CloudTrail configuration. Ensures logging is also enabled. +author: + - Ansible Core Team + - Ted Timmons (@tedder) + - Daniel Shepherd (@shepdelacreme) +requirements: + - boto3 + - botocore +options: + state: + description: + - Add or remove CloudTrail configuration. + - 'The following states have been preserved for backwards compatibility: I(state=enabled) and I(state=disabled).' + - I(state=enabled) is equivalet to I(state=present). + - I(state=disabled) is equivalet to I(state=absent). + type: str + choices: ['present', 'absent', 'enabled', 'disabled'] + default: present + name: + description: + - Name for the CloudTrail. + - Names are unique per-region unless the CloudTrail is a multi-region trail, in which case it is unique per-account. + type: str + default: default + enable_logging: + description: + - Start or stop the CloudTrail logging. If stopped the trail will be paused and will not record events or deliver log files. + default: true + type: bool + s3_bucket_name: + description: + - An existing S3 bucket where CloudTrail will deliver log files. + - This bucket should exist and have the proper policy. + - See U(https://docs.aws.amazon.com/awscloudtrail/latest/userguide/aggregating_logs_regions_bucket_policy.html). + - Required when I(state=present). + type: str + s3_key_prefix: + description: + - S3 Key prefix for delivered log files. A trailing slash is not necessary and will be removed. + type: str + is_multi_region_trail: + description: + - Specify whether the trail belongs only to one region or exists in all regions. + default: false + type: bool + enable_log_file_validation: + description: + - Specifies whether log file integrity validation is enabled. + - CloudTrail will create a hash for every log file delivered and produce a signed digest file that can be used to ensure log files have not been tampered. + type: bool + aliases: [ "log_file_validation_enabled" ] + include_global_events: + description: + - Record API calls from global services such as IAM and STS. + default: true + type: bool + aliases: [ "include_global_service_events" ] + sns_topic_name: + description: + - SNS Topic name to send notifications to when a log file is delivered. + type: str + cloudwatch_logs_role_arn: + description: + - Specifies a full ARN for an IAM role that assigns the proper permissions for CloudTrail to create and write to the log group. + - See U(https://docs.aws.amazon.com/awscloudtrail/latest/userguide/send-cloudtrail-events-to-cloudwatch-logs.html). + - Required when C(cloudwatch_logs_log_group_arn). + type: str + cloudwatch_logs_log_group_arn: + description: + - A full ARN specifying a valid CloudWatch log group to which CloudTrail logs will be delivered. The log group should already exist. + - See U(https://docs.aws.amazon.com/awscloudtrail/latest/userguide/send-cloudtrail-events-to-cloudwatch-logs.html). + - Required when C(cloudwatch_logs_role_arn). + type: str + kms_key_id: + description: + - Specifies the KMS key ID to use to encrypt the logs delivered by CloudTrail. This also has the effect of enabling log file encryption. + - The value can be an alias name prefixed by "alias/", a fully specified ARN to an alias, a fully specified ARN to a key, or a globally unique identifier. + - See U(https://docs.aws.amazon.com/awscloudtrail/latest/userguide/encrypting-cloudtrail-log-files-with-aws-kms.html). + type: str + tags: + description: + - A hash/dictionary of tags to be applied to the CloudTrail resource. + - Remove completely or specify an empty dictionary to remove all tags. + default: {} + type: dict + +extends_documentation_fragment: +- ansible.amazon.aws +- ansible.amazon.ec2 + +''' + +EXAMPLES = ''' +- name: create single region cloudtrail + cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + s3_key_prefix: cloudtrail + region: us-east-1 + +- name: create multi-region trail with validation and tags + cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: true + cloudwatch_logs_role_arn: "arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role" + cloudwatch_logs_log_group_arn: "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:*" + kms_key_id: "alias/MyAliasName" + tags: + environment: dev + Name: default + +- name: show another valid kms_key_id + cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + kms_key_id: "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012" + # simply "12345678-1234-1234-1234-123456789012" would be valid too. + +- name: pause logging the trail we just created + cloudtrail: + state: present + name: default + enable_logging: false + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: true + tags: + environment: dev + Name: default + +- name: delete a trail + cloudtrail: + state: absent + name: default +''' + +RETURN = ''' +exists: + description: whether the resource exists + returned: always + type: bool + sample: true +trail: + description: CloudTrail resource details + returned: always + type: complex + sample: hash/dictionary of values + contains: + trail_arn: + description: Full ARN of the CloudTrail resource + returned: success + type: str + sample: arn:aws:cloudtrail:us-east-1:123456789012:trail/default + name: + description: Name of the CloudTrail resource + returned: success + type: str + sample: default + is_logging: + description: Whether logging is turned on or paused for the Trail + returned: success + type: bool + sample: True + s3_bucket_name: + description: S3 bucket name where log files are delivered + returned: success + type: str + sample: myBucket + s3_key_prefix: + description: Key prefix in bucket where log files are delivered (if any) + returned: success when present + type: str + sample: myKeyPrefix + log_file_validation_enabled: + description: Whether log file validation is enabled on the trail + returned: success + type: bool + sample: true + include_global_service_events: + description: Whether global services (IAM, STS) are logged with this trail + returned: success + type: bool + sample: true + is_multi_region_trail: + description: Whether the trail applies to all regions or just one + returned: success + type: bool + sample: true + has_custom_event_selectors: + description: Whether any custom event selectors are used for this trail. + returned: success + type: bool + sample: False + home_region: + description: The home region where the trail was originally created and must be edited. + returned: success + type: str + sample: us-east-1 + sns_topic_name: + description: The SNS topic name where log delivery notifications are sent. + returned: success when present + type: str + sample: myTopic + sns_topic_arn: + description: Full ARN of the SNS topic where log delivery notifications are sent. + returned: success when present + type: str + sample: arn:aws:sns:us-east-1:123456789012:topic/myTopic + cloud_watch_logs_log_group_arn: + description: Full ARN of the CloudWatch Logs log group where events are delivered. + returned: success when present + type: str + sample: arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:* + cloud_watch_logs_role_arn: + description: Full ARN of the IAM role that CloudTrail assumes to deliver events. + returned: success when present + type: str + sample: arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role + kms_key_id: + description: Full ARN of the KMS Key used to encrypt log files. + returned: success when present + type: str + sample: arn:aws:kms::123456789012:key/12345678-1234-1234-1234-123456789012 + tags: + description: hash/dictionary of tags applied to this resource + returned: success + type: dict + sample: {'environment': 'dev', 'Name': 'default'} +''' + +try: + from botocore.exceptions import ClientError, BotoCoreError +except ImportError: + pass # Handled by AnsibleAWSModule + +from ansible_collections.ansible.amazon.plugins.module_utils.aws.core import AnsibleAWSModule +from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import (camel_dict_to_snake_dict, + ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict) + + +def create_trail(module, client, ct_params): + """ + Creates a CloudTrail + + module : AnsibleAWSModule object + client : boto3 client connection object + ct_params : The parameters for the Trail to create + """ + resp = {} + try: + resp = client.create_trail(**ct_params) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to create Trail") + + return resp + + +def tag_trail(module, client, tags, trail_arn, curr_tags=None, dry_run=False): + """ + Creates, updates, removes tags on a CloudTrail resource + + module : AnsibleAWSModule object + client : boto3 client connection object + tags : Dict of tags converted from ansible_dict to boto3 list of dicts + trail_arn : The ARN of the CloudTrail to operate on + curr_tags : Dict of the current tags on resource, if any + dry_run : true/false to determine if changes will be made if needed + """ + adds = [] + removes = [] + updates = [] + changed = False + + if curr_tags is None: + # No current tags so just convert all to a tag list + adds = ansible_dict_to_boto3_tag_list(tags) + else: + curr_keys = set(curr_tags.keys()) + new_keys = set(tags.keys()) + add_keys = new_keys - curr_keys + remove_keys = curr_keys - new_keys + update_keys = dict() + for k in curr_keys.intersection(new_keys): + if curr_tags[k] != tags[k]: + update_keys.update({k: tags[k]}) + + adds = get_tag_list(add_keys, tags) + removes = get_tag_list(remove_keys, curr_tags) + updates = get_tag_list(update_keys, tags) + + if removes or updates: + changed = True + if not dry_run: + try: + client.remove_tags(ResourceId=trail_arn, TagsList=removes + updates) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to remove tags from Trail") + + if updates or adds: + changed = True + if not dry_run: + try: + client.add_tags(ResourceId=trail_arn, TagsList=updates + adds) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to add tags to Trail") + + return changed + + +def get_tag_list(keys, tags): + """ + Returns a list of dicts with tags to act on + keys : set of keys to get the values for + tags : the dict of tags to turn into a list + """ + tag_list = [] + for k in keys: + tag_list.append({'Key': k, 'Value': tags[k]}) + + return tag_list + + +def set_logging(module, client, name, action): + """ + Starts or stops logging based on given state + + module : AnsibleAWSModule object + client : boto3 client connection object + name : The name or ARN of the CloudTrail to operate on + action : start or stop + """ + if action == 'start': + try: + client.start_logging(Name=name) + return client.get_trail_status(Name=name) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to start logging") + elif action == 'stop': + try: + client.stop_logging(Name=name) + return client.get_trail_status(Name=name) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to stop logging") + else: + module.fail_json(msg="Unsupported logging action") + + +def get_trail_facts(module, client, name): + """ + Describes existing trail in an account + + module : AnsibleAWSModule object + client : boto3 client connection object + name : Name of the trail + """ + # get Trail info + try: + trail_resp = client.describe_trails(trailNameList=[name]) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to describe Trail") + + # Now check to see if our trail exists and get status and tags + if len(trail_resp['trailList']): + trail = trail_resp['trailList'][0] + try: + status_resp = client.get_trail_status(Name=trail['Name']) + tags_list = client.list_tags(ResourceIdList=[trail['TrailARN']]) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to describe Trail") + + trail['IsLogging'] = status_resp['IsLogging'] + trail['tags'] = boto3_tag_list_to_ansible_dict(tags_list['ResourceTagList'][0]['TagsList']) + # Check for non-existent values and populate with None + optional_vals = set(['S3KeyPrefix', 'SnsTopicName', 'SnsTopicARN', 'CloudWatchLogsLogGroupArn', 'CloudWatchLogsRoleArn', 'KmsKeyId']) + for v in optional_vals - set(trail.keys()): + trail[v] = None + return trail + + else: + # trail doesn't exist return None + return None + + +def delete_trail(module, client, trail_arn): + """ + Delete a CloudTrail + + module : AnsibleAWSModule object + client : boto3 client connection object + trail_arn : Full CloudTrail ARN + """ + try: + client.delete_trail(Name=trail_arn) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to delete Trail") + + +def update_trail(module, client, ct_params): + """ + Delete a CloudTrail + + module : AnsibleAWSModule object + client : boto3 client connection object + ct_params : The parameters for the Trail to update + """ + try: + client.update_trail(**ct_params) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to update Trail") + + +def main(): + argument_spec = dict( + state=dict(default='present', choices=['present', 'absent', 'enabled', 'disabled']), + name=dict(default='default'), + enable_logging=dict(default=True, type='bool'), + s3_bucket_name=dict(), + s3_key_prefix=dict(), + sns_topic_name=dict(), + is_multi_region_trail=dict(default=False, type='bool'), + enable_log_file_validation=dict(type='bool', aliases=['log_file_validation_enabled']), + include_global_events=dict(default=True, type='bool', aliases=['include_global_service_events']), + cloudwatch_logs_role_arn=dict(), + cloudwatch_logs_log_group_arn=dict(), + kms_key_id=dict(), + tags=dict(default={}, type='dict'), + ) + + required_if = [('state', 'present', ['s3_bucket_name']), ('state', 'enabled', ['s3_bucket_name'])] + required_together = [('cloudwatch_logs_role_arn', 'cloudwatch_logs_log_group_arn')] + + module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True, required_together=required_together, required_if=required_if) + + # collect parameters + if module.params['state'] in ('present', 'enabled'): + state = 'present' + elif module.params['state'] in ('absent', 'disabled'): + state = 'absent' + tags = module.params['tags'] + enable_logging = module.params['enable_logging'] + ct_params = dict( + Name=module.params['name'], + S3BucketName=module.params['s3_bucket_name'], + IncludeGlobalServiceEvents=module.params['include_global_events'], + IsMultiRegionTrail=module.params['is_multi_region_trail'], + ) + + if module.params['s3_key_prefix']: + ct_params['S3KeyPrefix'] = module.params['s3_key_prefix'].rstrip('/') + + if module.params['sns_topic_name']: + ct_params['SnsTopicName'] = module.params['sns_topic_name'] + + if module.params['cloudwatch_logs_role_arn']: + ct_params['CloudWatchLogsRoleArn'] = module.params['cloudwatch_logs_role_arn'] + + if module.params['cloudwatch_logs_log_group_arn']: + ct_params['CloudWatchLogsLogGroupArn'] = module.params['cloudwatch_logs_log_group_arn'] + + if module.params['enable_log_file_validation'] is not None: + ct_params['EnableLogFileValidation'] = module.params['enable_log_file_validation'] + + if module.params['kms_key_id']: + ct_params['KmsKeyId'] = module.params['kms_key_id'] + + client = module.client('cloudtrail') + region = module.region + + results = dict( + changed=False, + exists=False + ) + + # Get existing trail facts + trail = get_trail_facts(module, client, ct_params['Name']) + + # If the trail exists set the result exists variable + if trail is not None: + results['exists'] = True + + if state == 'absent' and results['exists']: + # If Trail exists go ahead and delete + results['changed'] = True + results['exists'] = False + results['trail'] = dict() + if not module.check_mode: + delete_trail(module, client, trail['TrailARN']) + + elif state == 'present' and results['exists']: + # If Trail exists see if we need to update it + do_update = False + for key in ct_params: + tkey = str(key) + # boto3 has inconsistent parameter naming so we handle it here + if key == 'EnableLogFileValidation': + tkey = 'LogFileValidationEnabled' + # We need to make an empty string equal None + if ct_params.get(key) == '': + val = None + else: + val = ct_params.get(key) + if val != trail.get(tkey): + do_update = True + 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)}) + + if not module.check_mode and do_update: + update_trail(module, client, ct_params) + trail = get_trail_facts(module, client, ct_params['Name']) + + # Check if we need to start/stop logging + if enable_logging and not trail['IsLogging']: + results['changed'] = True + trail['IsLogging'] = True + if not module.check_mode: + set_logging(module, client, name=ct_params['Name'], action='start') + if not enable_logging and trail['IsLogging']: + results['changed'] = True + trail['IsLogging'] = False + if not module.check_mode: + set_logging(module, client, name=ct_params['Name'], action='stop') + + # Check if we need to update tags on resource + tag_dry_run = False + if module.check_mode: + tag_dry_run = True + tags_changed = tag_trail(module, client, tags=tags, trail_arn=trail['TrailARN'], curr_tags=trail['tags'], dry_run=tag_dry_run) + if tags_changed: + results['changed'] = True + trail['tags'] = tags + # Populate trail facts in output + results['trail'] = camel_dict_to_snake_dict(trail) + + elif state == 'present' and not results['exists']: + # Trail doesn't exist just go create it + results['changed'] = 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) + # Apply tags + tag_trail(module, client, tags=tags, trail_arn=created_trail['TrailARN']) + # Get the trail status + try: + status_resp = client.get_trail_status(Name=created_trail['Name']) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to fetch Trail statuc") + # Set the logging state for the trail to desired value + if enable_logging and not status_resp['IsLogging']: + set_logging(module, client, name=ct_params['Name'], action='start') + if not enable_logging and status_resp['IsLogging']: + set_logging(module, client, name=ct_params['Name'], action='stop') + # Get facts for newly created Trail + trail = get_trail_facts(module, client, ct_params['Name']) + + # If we are in check mode create a fake return structure for the newly minted trail + if module.check_mode: + acct_id = '123456789012' + try: + sts_client = module.client('sts') + acct_id = sts_client.get_caller_identity()['Account'] + except (BotoCoreError, ClientError): + pass + trail = dict() + trail.update(ct_params) + if 'EnableLogFileValidation' not in ct_params: + ct_params['EnableLogFileValidation'] = False + trail['EnableLogFileValidation'] = ct_params['EnableLogFileValidation'] + trail.pop('EnableLogFileValidation') + fake_arn = 'arn:aws:cloudtrail:' + region + ':' + acct_id + ':trail/' + ct_params['Name'] + trail['HasCustomEventSelectors'] = False + trail['HomeRegion'] = region + trail['TrailARN'] = fake_arn + trail['IsLogging'] = enable_logging + trail['tags'] = tags + # Populate trail facts in output + results['trail'] = camel_dict_to_snake_dict(trail) + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/cloudtrail/aliases b/tests/integration/targets/cloudtrail/aliases new file mode 100644 index 00000000000..56927195182 --- /dev/null +++ b/tests/integration/targets/cloudtrail/aliases @@ -0,0 +1,2 @@ +cloud/aws +unsupported diff --git a/tests/integration/targets/cloudtrail/templates/cloudwatch-assume-policy.j2 b/tests/integration/targets/cloudtrail/templates/cloudwatch-assume-policy.j2 new file mode 100644 index 00000000000..6d7fb7b8894 --- /dev/null +++ b/tests/integration/targets/cloudtrail/templates/cloudwatch-assume-policy.j2 @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AssumeFromCloudTrails", + "Effect": "Allow", + "Principal": { + "Service": "cloudtrail.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} diff --git a/tests/integration/targets/cloudtrail/templates/cloudwatch-policy.j2 b/tests/integration/targets/cloudtrail/templates/cloudwatch-policy.j2 new file mode 100644 index 00000000000..8f354a70281 --- /dev/null +++ b/tests/integration/targets/cloudtrail/templates/cloudwatch-policy.j2 @@ -0,0 +1,17 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "CloudTrail2CloudWatch", + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": [ + "arn:aws:logs:{{ aws_region }}:{{ aws_caller_info.account }}:log-group:{{ cloudwatch_log_group }}:log-stream:*", + "arn:aws:logs:{{ aws_region }}:{{ aws_caller_info.account }}:log-group:{{ cloudwatch_log_group }}-2:log-stream:*" + ] + } + ] +} diff --git a/tests/integration/targets/cloudtrail/templates/kms-policy.j2 b/tests/integration/targets/cloudtrail/templates/kms-policy.j2 new file mode 100644 index 00000000000..35730f1d2fe --- /dev/null +++ b/tests/integration/targets/cloudtrail/templates/kms-policy.j2 @@ -0,0 +1,34 @@ +{ + "Version": "2012-10-17", + "Id": "CloudTrailPolicy", + "Statement": [ + { + "Sid": "EncryptLogs", + "Effect": "Allow", + "Principal": { "Service": "cloudtrail.amazonaws.com" }, + "Action": "kms:GenerateDataKey*", + "Resource": "*", + "Condition": { + "StringLike": { + "kms:EncryptionContext:aws:cloudtrail:arn": [ + "arn:aws:cloudtrail:*:{{ aws_caller_info.account }}:trail/{{ resource_prefix }}*" + ] + } + } + }, + { + "Sid": "DescribeKey", + "Effect": "Allow", + "Principal": { "Service": "cloudtrail.amazonaws.com" }, + "Action": "kms:DescribeKey", + "Resource": "*" + }, + { + "Sid": "AnsibleTestManage", + "Effect": "Allow", + "Principal": { "AWS": "{{ aws_caller_info.arn }}" }, + "Action": "*", + "Resource": "*" + } + ] +} diff --git a/tests/integration/targets/cloudtrail/templates/s3-policy.j2 b/tests/integration/targets/cloudtrail/templates/s3-policy.j2 new file mode 100644 index 00000000000..78c056e30bc --- /dev/null +++ b/tests/integration/targets/cloudtrail/templates/s3-policy.j2 @@ -0,0 +1,34 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "CloudTrailCheckAcl", + "Effect": "Allow", + "Principal": { "Service": "cloudtrail.amazonaws.com" }, + "Action": "s3:GetBucketAcl", + "Resource": "arn:aws:s3:::{{ bucket_name }}", + }, + { + "Sid": "CloudTrailWriteLogs", + "Effect": "Allow", + "Principal": { "Service": "cloudtrail.amazonaws.com" }, + "Action": "s3:PutObject", + "Resource": [ + "arn:aws:s3:::{{ bucket_name }}/AWSLogs/{{ aws_caller_info.account }}/*", + "arn:aws:s3:::{{ bucket_name }}/{{ cloudtrail_prefix }}*/AWSLogs/{{ aws_caller_info.account }}/*" + ], + "Condition": { + "StringEquals": { + "s3:x-amz-acl": "bucket-owner-full-control" + } + } + }, + { + "Sid": "AnsibleTestManage", + "Effect": "Allow", + "Principal": { "AWS": "{{ aws_caller_info.arn }}" }, + "Action": "*", + "Resource": "arn:aws:s3:::{{ bucket_name }}" + } + ] +} diff --git a/tests/integration/targets/cloudtrail/templates/sns-policy.j2 b/tests/integration/targets/cloudtrail/templates/sns-policy.j2 new file mode 100644 index 00000000000..3c267b80041 --- /dev/null +++ b/tests/integration/targets/cloudtrail/templates/sns-policy.j2 @@ -0,0 +1,34 @@ +{ + "Version": "2008-10-17", + "Id": "AnsibleSNSTesting", + "Statement": [ + { + "Sid": "CloudTrailSNSPolicy", + "Effect": "Allow", + "Principal": { + "Service": "cloudtrail.amazonaws.com" + }, + "Action": "sns:Publish", + "Resource": "arn:aws:sns:{{ aws_region }}:{{ aws_caller_info.account }}:{{ sns_topic_name }}" + }, + { + "Sid": "AnsibleTestManage", + "Effect": "Allow", + "Principal": { + "AWS": "{{ aws_caller_info.arn }}" + }, + "Action": [ + "sns:Subscribe", + "sns:ListSubscriptionsByTopic", + "sns:DeleteTopic", + "sns:GetTopicAttributes", + "sns:Publish", + "sns:RemovePermission", + "sns:AddPermission", + "sns:Receive", + "sns:SetTopicAttributes" + ], + "Resource": "arn:aws:sns:{{ aws_region }}:{{ aws_caller_info.account }}:{{ sns_topic_name }}" + } + ] +} From 17eb077604c5e0378c753a430b68d3d2b8da98a7 Mon Sep 17 00:00:00 2001 From: jillr Date: Tue, 3 Mar 2020 19:43:21 +0000 Subject: [PATCH 02/14] migration test cleanup This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/13b104b912784bb31a0bff23eed4c27b0f5e0283 --- plugins/modules/cloudtrail.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index 0dc8feb64af..087419f6917 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -260,7 +260,9 @@ from ansible_collections.ansible.amazon.plugins.module_utils.aws.core import AnsibleAWSModule from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import (camel_dict_to_snake_dict, - ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict) + ansible_dict_to_boto3_tag_list, + boto3_tag_list_to_ansible_dict, + ) def create_trail(module, client, ct_params): From c9676e051e02fb6daa1ec827f9240766a919f420 Mon Sep 17 00:00:00 2001 From: Jill R <4121322+jillr@users.noreply.github.com> Date: Wed, 25 Mar 2020 15:39:40 -0700 Subject: [PATCH 03/14] Rename collection (#12) * Rename core collection Rename references to ansible.amazon to amazon.aws. * Rename community.amazon to community.aws Fix pep8 line lengths for rewritten amazon.aws imports * Missed a path in shippable.sh * Dependency repos moved This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/235c5db571cc45db5839476c94356c9b91e1f228 --- plugins/modules/cloudtrail.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index 087419f6917..5fb0858d9ab 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -103,8 +103,8 @@ type: dict extends_documentation_fragment: -- ansible.amazon.aws -- ansible.amazon.ec2 +- amazon.aws.aws +- amazon.aws.ec2 ''' @@ -258,11 +258,11 @@ except ImportError: pass # Handled by AnsibleAWSModule -from ansible_collections.ansible.amazon.plugins.module_utils.aws.core import AnsibleAWSModule -from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import (camel_dict_to_snake_dict, - ansible_dict_to_boto3_tag_list, - boto3_tag_list_to_ansible_dict, - ) +from ansible_collections.amazon.aws.plugins.module_utils.aws.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import (camel_dict_to_snake_dict, + ansible_dict_to_boto3_tag_list, + boto3_tag_list_to_ansible_dict, + ) def create_trail(module, client, ct_params): From 8453005eb973a2e5228e639bd300779ed722919b Mon Sep 17 00:00:00 2001 From: Jill R <4121322+jillr@users.noreply.github.com> Date: Tue, 19 May 2020 16:06:12 -0700 Subject: [PATCH 04/14] Remove METADATA and cleanup galaxy.yml (#70) * Remove ANSIBLE_METADATA entirely, see ansible/ansible/pull/69454. Remove `license` field from galaxy.yml, in favor of `license_file`. This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/05672a64e2362cc2d865b5af6a57da6bc3cd08e3 --- plugins/modules/cloudtrail.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index 5fb0858d9ab..c4a5f2e6e74 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -6,11 +6,6 @@ __metaclass__ = type -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - DOCUMENTATION = ''' --- module: cloudtrail From d1102c98b7fa3ccc68c70d06612cbf08ad14a833 Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Wed, 17 Jun 2020 01:24:54 +0530 Subject: [PATCH 05/14] Update Examples with FQCN (#67) Updated module examples with FQCN Signed-off-by: Abhijeet Kasurde This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/98173aefbbceed7fc0d9db62687b73f96a55a999 --- plugins/modules/cloudtrail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index c4a5f2e6e74..83e6cc0b0f1 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -105,7 +105,7 @@ EXAMPLES = ''' - name: create single region cloudtrail - cloudtrail: + community.aws.cloudtrail: state: present name: default s3_bucket_name: mylogbucket @@ -113,7 +113,7 @@ region: us-east-1 - name: create multi-region trail with validation and tags - cloudtrail: + community.aws.cloudtrail: state: present name: default s3_bucket_name: mylogbucket @@ -128,7 +128,7 @@ Name: default - name: show another valid kms_key_id - cloudtrail: + community.aws.cloudtrail: state: present name: default s3_bucket_name: mylogbucket @@ -136,7 +136,7 @@ # simply "12345678-1234-1234-1234-123456789012" would be valid too. - name: pause logging the trail we just created - cloudtrail: + community.aws.cloudtrail: state: present name: default enable_logging: false @@ -149,7 +149,7 @@ Name: default - name: delete a trail - cloudtrail: + community.aws.cloudtrail: state: absent name: default ''' From 6f00a572c7a50f3e35bf6d67b4f2f15831264586 Mon Sep 17 00:00:00 2001 From: flowerysong Date: Tue, 16 Jun 2020 19:30:00 -0400 Subject: [PATCH 06/14] Update module_utils paths to remove aws subdir (#23) Co-authored-by: Ezekiel Hendrickson This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/10853d9441a586ba177006dd889325cfb24a3dd6 --- plugins/modules/cloudtrail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index 83e6cc0b0f1..fe8d500a4c3 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -253,7 +253,7 @@ except ImportError: pass # Handled by AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.aws.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.ec2 import (camel_dict_to_snake_dict, ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict, From 58e717d3864f2eb769019687bfe5c6d568b4e076 Mon Sep 17 00:00:00 2001 From: Jill R <4121322+jillr@users.noreply.github.com> Date: Wed, 17 Jun 2020 09:31:32 -0700 Subject: [PATCH 07/14] Update docs (#99) * Update docs Remove .git from repo url so links in readme will generate correctly Add required ansible version Run latest version of add_docs.py Add version_added string to modules * galaxy.yml was missing authors This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/96ee268e5267f5b12c3d59892bc1279f75aa3135 --- plugins/modules/cloudtrail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index fe8d500a4c3..c0bf3f4db07 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -9,6 +9,7 @@ DOCUMENTATION = ''' --- module: cloudtrail +version_added: 1.0.0 short_description: manage CloudTrail create, delete, update description: - Creates, deletes, or updates CloudTrail configuration. Ensures logging is also enabled. From d9ccb25e5ca9c62ad67a4eb8edc8dee3193407a8 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Sat, 13 Feb 2021 14:19:21 +0100 Subject: [PATCH 08/14] Add comments to the disabled/unsupported integration test aliase files (#411) Add some additional comments so we know *why* the various tests aren't running. Looks like most of them just need policy updates This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/0d245596966100516f1eeab5dcf26ee0ebca24b5 --- tests/integration/targets/cloudtrail/aliases | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/targets/cloudtrail/aliases b/tests/integration/targets/cloudtrail/aliases index 56927195182..e4280272565 100644 --- a/tests/integration/targets/cloudtrail/aliases +++ b/tests/integration/targets/cloudtrail/aliases @@ -1,2 +1,4 @@ -cloud/aws +# reason: missing-policy unsupported + +cloud/aws From f267b0b5801e5261e53d76f70b787b260b33bc5c Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sat, 13 Mar 2021 20:10:09 +0100 Subject: [PATCH 09/14] More no_log=False to fix sanity tests (#474) * Add no_log=False to mark some more false-positives of the no_log check. * More false-positives confirmed by tremble. This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/67b6d751cc4ee5728577af2fe7b330f8cc7cd5c2 --- plugins/modules/cloudtrail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index c0bf3f4db07..5f8aa5ae03f 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -438,7 +438,7 @@ def main(): name=dict(default='default'), enable_logging=dict(default=True, type='bool'), s3_bucket_name=dict(), - s3_key_prefix=dict(), + s3_key_prefix=dict(no_log=False), sns_topic_name=dict(), is_multi_region_trail=dict(default=False, type='bool'), enable_log_file_validation=dict(type='bool', aliases=['log_file_validation_enabled']), From 0ea141f90e2b2a0b14c615f3b0ef4d29f6eb340f Mon Sep 17 00:00:00 2001 From: msven Date: Wed, 31 Mar 2021 14:30:50 -0500 Subject: [PATCH 10/14] Fix changed always true when kms key alias used (#506) Fix output.exists value on create Fix tags being changed to lower case add changelog This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/b825069762e30510a22e19ed8f9a929f818708b5 --- plugins/modules/cloudtrail.py | 50 +++++++++++++++++-- .../cloudtrail-no-kms-assume-policy.j2 | 11 ++++ .../templates/cloudtrail-no-kms-policy.j2 | 11 ++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 tests/integration/targets/cloudtrail/templates/cloudtrail-no-kms-assume-policy.j2 create mode 100644 tests/integration/targets/cloudtrail/templates/cloudtrail-no-kms-policy.j2 diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index 5f8aa5ae03f..a2f2076993f 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -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 @@ -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 @@ -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)}) @@ -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 @@ -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) @@ -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) diff --git a/tests/integration/targets/cloudtrail/templates/cloudtrail-no-kms-assume-policy.j2 b/tests/integration/targets/cloudtrail/templates/cloudtrail-no-kms-assume-policy.j2 new file mode 100644 index 00000000000..f3bfd14ec6f --- /dev/null +++ b/tests/integration/targets/cloudtrail/templates/cloudtrail-no-kms-assume-policy.j2 @@ -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" + } + ] +} diff --git a/tests/integration/targets/cloudtrail/templates/cloudtrail-no-kms-policy.j2 b/tests/integration/targets/cloudtrail/templates/cloudtrail-no-kms-policy.j2 new file mode 100644 index 00000000000..d85b650b70f --- /dev/null +++ b/tests/integration/targets/cloudtrail/templates/cloudtrail-no-kms-policy.j2 @@ -0,0 +1,11 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "kmsDeny", + "Effect": "Deny", + "Action": [ "kms:*" ], + "Resource": [ "*" ] + } + ] +} From c88f3c848f5c8f82ce6c61ebc4ee40000821c808 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 6 May 2021 21:01:46 +0200 Subject: [PATCH 11/14] Update the default module requirements from python 2.6/boto to python 3.6/boto3 This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/c097c55293be0834a2b9d394733ec28965d142d7 --- plugins/modules/cloudtrail.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index a2f2076993f..d30466710eb 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -17,9 +17,6 @@ - Ansible Core Team - Ted Timmons (@tedder) - Daniel Shepherd (@shepdelacreme) -requirements: - - boto3 - - botocore options: state: description: From 60c6f5e0b197695d0643e82ed7229b947f369c43 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Tue, 7 Jun 2022 09:34:41 +0200 Subject: [PATCH 12/14] cloudtrail - add support for purge_tags (#1219) cloudtrail - add support for purge_tags SUMMARY Move to tagging docs fragment Update tagging code so that "tags" must be explicitly passed to remove tags add purge_tags parameter add resource_tags as an alias for tags Update tagging code so that tags are set as part of the create call rather than tagging after creation ISSUE TYPE Feature Pull Request COMPONENT NAME cloudtrail ADDITIONAL INFORMATION Note: tests are currently not run in CI. Reviewed-by: Joseph Torcasso This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/53e3bb2b81d2ab56585d005b96757c2cf3c75247 --- plugins/modules/cloudtrail.py | 115 ++++++++++++++++------------------ 1 file changed, 54 insertions(+), 61 deletions(-) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index d30466710eb..df95d5bfb7b 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -14,9 +14,9 @@ description: - Creates, deletes, or updates CloudTrail configuration. Ensures logging is also enabled. author: - - Ansible Core Team - - Ted Timmons (@tedder) - - Daniel Shepherd (@shepdelacreme) + - Ansible Core Team + - Ted Timmons (@tedder) + - Daniel Shepherd (@shepdelacreme) options: state: description: @@ -88,16 +88,13 @@ - The value can be an alias name prefixed by "alias/", a fully specified ARN to an alias, a fully specified ARN to a key, or a globally unique identifier. - See U(https://docs.aws.amazon.com/awscloudtrail/latest/userguide/encrypting-cloudtrail-log-files-with-aws-kms.html). type: str - tags: - description: - - A hash/dictionary of tags to be applied to the CloudTrail resource. - - Remove completely or specify an empty dictionary to remove all tags. - default: {} - type: dict +notes: + - The I(purge_tags) option was added in release 4.0.0 extends_documentation_fragment: -- amazon.aws.aws -- amazon.aws.ec2 + - amazon.aws.aws + - amazon.aws.ec2 + - amazon.aws.tags ''' @@ -251,11 +248,12 @@ except ImportError: pass # Handled by AnsibleAWSModule +from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict + from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import (camel_dict_to_snake_dict, - ansible_dict_to_boto3_tag_list, - boto3_tag_list_to_ansible_dict, - ) +from ansible_collections.amazon.aws.plugins.module_utils.tagging import ansible_dict_to_boto3_tag_list +from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict +from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags def get_kms_key_aliases(module, client, keyId): @@ -293,7 +291,7 @@ def create_trail(module, client, ct_params): return resp -def tag_trail(module, client, tags, trail_arn, curr_tags=None, dry_run=False): +def tag_trail(module, client, tags, trail_arn, curr_tags=None, purge_tags=True): """ Creates, updates, removes tags on a CloudTrail resource @@ -304,45 +302,35 @@ def tag_trail(module, client, tags, trail_arn, curr_tags=None, dry_run=False): curr_tags : Dict of the current tags on resource, if any dry_run : true/false to determine if changes will be made if needed """ - adds = [] - removes = [] - updates = [] - changed = False - - if curr_tags is None: - # No current tags so just convert all to a tag list - adds = ansible_dict_to_boto3_tag_list(tags) - else: - curr_keys = set(curr_tags.keys()) - new_keys = set(tags.keys()) - add_keys = new_keys - curr_keys - remove_keys = curr_keys - new_keys - update_keys = dict() - for k in curr_keys.intersection(new_keys): - if curr_tags[k] != tags[k]: - update_keys.update({k: tags[k]}) - - adds = get_tag_list(add_keys, tags) - removes = get_tag_list(remove_keys, curr_tags) - updates = get_tag_list(update_keys, tags) - - if removes or updates: - changed = True - if not dry_run: - try: - client.remove_tags(ResourceId=trail_arn, TagsList=removes + updates) - except (BotoCoreError, ClientError) as err: - module.fail_json_aws(err, msg="Failed to remove tags from Trail") - if updates or adds: - changed = True - if not dry_run: - try: - client.add_tags(ResourceId=trail_arn, TagsList=updates + adds) - except (BotoCoreError, ClientError) as err: - module.fail_json_aws(err, msg="Failed to add tags to Trail") + if tags is None: + return False + + curr_tags = curr_tags or {} - return changed + tags_to_add, tags_to_remove = compare_aws_tags(curr_tags, tags, purge_tags=purge_tags) + if not tags_to_add and not tags_to_remove: + return False + + if module.check_mode: + return True + + if tags_to_remove: + remove = {k: curr_tags[k] for k in tags_to_remove} + tags_to_remove = ansible_dict_to_boto3_tag_list(remove) + try: + client.remove_tags(ResourceId=trail_arn, TagsList=tags_to_remove) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to remove tags from Trail") + + if tags_to_add: + tags_to_add = ansible_dict_to_boto3_tag_list(tags_to_add) + try: + client.add_tags(ResourceId=trail_arn, TagsList=tags_to_add) + except (BotoCoreError, ClientError) as err: + module.fail_json_aws(err, msg="Failed to add tags to Trail") + + return True def get_tag_list(keys, tags): @@ -461,7 +449,8 @@ def main(): cloudwatch_logs_role_arn=dict(), cloudwatch_logs_log_group_arn=dict(), kms_key_id=dict(), - tags=dict(default={}, type='dict'), + tags=dict(type='dict', aliases=['resource_tags']), + purge_tags=dict(default=True, type='bool') ) required_if = [('state', 'present', ['s3_bucket_name']), ('state', 'enabled', ['s3_bucket_name'])] @@ -475,6 +464,7 @@ def main(): elif module.params['state'] in ('absent', 'disabled'): state = 'absent' tags = module.params['tags'] + purge_tags = module.params['purge_tags'] enable_logging = module.params['enable_logging'] ct_params = dict( Name=module.params['name'], @@ -586,13 +576,16 @@ def main(): set_logging(module, client, name=ct_params['Name'], action='stop') # Check if we need to update tags on resource - tag_dry_run = False - if module.check_mode: - tag_dry_run = True - tags_changed = tag_trail(module, client, tags=tags, trail_arn=trail['TrailARN'], curr_tags=trail['tags'], dry_run=tag_dry_run) + tags_changed = tag_trail(module, client, tags=tags, trail_arn=trail['TrailARN'], curr_tags=trail['tags'], + purge_tags=purge_tags) if tags_changed: + updated_tags = dict() + if not purge_tags: + updated_tags = trail['tags'] + updated_tags.update(tags) results['changed'] = True - trail['tags'] = tags + trail['tags'] = updated_tags + # Populate trail facts in output results['trail'] = camel_dict_to_snake_dict(trail, ignore_list=['tags']) @@ -601,10 +594,10 @@ def main(): results['changed'] = True results['exists'] = True if not module.check_mode: + if tags: + ct_params['TagList'] = ansible_dict_to_boto3_tag_list(tags) # If we aren't in check_mode then actually create it created_trail = create_trail(module, client, ct_params) - # Apply tags - tag_trail(module, client, tags=tags, trail_arn=created_trail['TrailARN']) # Get the trail status try: status_resp = client.get_trail_status(Name=created_trail['Name']) From d73b3954f63ae88c4ea9cf64ce5faeaeeb404482 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Mon, 15 Aug 2022 20:39:10 +0200 Subject: [PATCH 13/14] Minor sanity test fixes. (#1410) Minor sanity test fixes (new devel) SUMMARY ansible-devel has added a new PEP test (missing whitespace after keyword), this adds the fixes before the devel sanity tests are 'voting'. Additionally fixes: unused variables broad catching of Exception ISSUE TYPE Bugfix Pull Request COMPONENT NAME plugins/modules/autoscaling_group_info.py plugins/modules/cloudfront_distribution.py plugins/modules/cloudfront_origin_access_identity.py plugins/modules/cloudtrail.py plugins/modules/ec2_vpc_nacl.py plugins/modules/eks_fargate_profile.py plugins/modules/redshift.py plugins/modules/s3_bucket_info.py ADDITIONAL INFORMATION cloudfront_distribution still has a lot of catch Exception but it's part of parameter validation which should be overhauled separately, unfortunately the tests are rather b0rked. Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/3d4736bb8c0fa47159a961fdc0d13ed2184b4823 --- plugins/modules/cloudtrail.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/modules/cloudtrail.py b/plugins/modules/cloudtrail.py index df95d5bfb7b..aa3b637cee5 100644 --- a/plugins/modules/cloudtrail.py +++ b/plugins/modules/cloudtrail.py @@ -266,7 +266,7 @@ def get_kms_key_aliases(module, client, keyId): """ try: key_resp = client.list_aliases(KeyId=keyId) - except (BotoCoreError, ClientError) as err: + except (BotoCoreError, ClientError): # Don't fail here, just return [] to maintain backwards compat # in case user doesn't have kms:ListAliases permissions return [] @@ -558,9 +558,7 @@ def main(): # 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): + 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 From 8484b2cc1ad8898683b4a08c211ff9ec33bc77ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A9ri=20Le=20Bouder?= Date: Wed, 7 Sep 2022 08:43:21 -0400 Subject: [PATCH 14/14] cloudtrail: add the entry in meta/runtime.yml --- meta/runtime.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/meta/runtime.yml b/meta/runtime.yml index a97577d34b8..17f07211260 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -6,6 +6,7 @@ action_groups: - aws_s3 - cloudformation - cloudformation_info + - cloudtrail - ec2_ami - ec2_ami_info - ec2_elb_lb