diff --git a/changelogs/fragments/migrate_cloudtrail_tests.yml b/changelogs/fragments/migrate_cloudtrail_tests.yml new file mode 100644 index 00000000000..950d43bfef5 --- /dev/null +++ b/changelogs/fragments/migrate_cloudtrail_tests.yml @@ -0,0 +1,2 @@ +trivial: +- cloudtrail - Import the functional tests (https://github.com/ansible-collections/amazon.aws/pull/1035). diff --git a/tests/integration/targets/cloudtrail/aliases b/tests/integration/targets/cloudtrail/aliases index e4280272565..3cbc2a4850b 100644 --- a/tests/integration/targets/cloudtrail/aliases +++ b/tests/integration/targets/cloudtrail/aliases @@ -1,4 +1,2 @@ # reason: missing-policy unsupported - -cloud/aws diff --git a/tests/integration/targets/cloudtrail/defaults/main.yml b/tests/integration/targets/cloudtrail/defaults/main.yml new file mode 100644 index 00000000000..2174b31aefa --- /dev/null +++ b/tests/integration/targets/cloudtrail/defaults/main.yml @@ -0,0 +1,8 @@ +cloudtrail_name: '{{ resource_prefix }}-cloudtrail' +s3_bucket_name: '{{ resource_prefix }}-cloudtrail-bucket' +kms_alias: '{{ resource_prefix }}-cloudtrail' +sns_topic: '{{ resource_prefix }}-cloudtrail-notifications' +cloudtrail_prefix: 'ansible-test-prefix' +cloudwatch_log_group: '{{ resource_prefix }}-cloudtrail' +cloudwatch_role: '{{ resource_prefix }}-cloudtrail' +cloudwatch_no_kms_role: '{{ resource_prefix }}-cloudtrail2' diff --git a/tests/integration/targets/cloudtrail/main.yml b/tests/integration/targets/cloudtrail/main.yml new file mode 100644 index 00000000000..b20eb2ad42b --- /dev/null +++ b/tests/integration/targets/cloudtrail/main.yml @@ -0,0 +1,6 @@ +--- +- hosts: localhost + gather_facts: no + #serial: 10 + roles: + - cloudtrail diff --git a/tests/integration/targets/cloudtrail/meta/main.yml b/tests/integration/targets/cloudtrail/meta/main.yml new file mode 100644 index 00000000000..32cf5dda7ed --- /dev/null +++ b/tests/integration/targets/cloudtrail/meta/main.yml @@ -0,0 +1 @@ +dependencies: [] diff --git a/tests/integration/targets/cloudtrail/runme.sh b/tests/integration/targets/cloudtrail/runme.sh new file mode 100755 index 00000000000..14d1958ff94 --- /dev/null +++ b/tests/integration/targets/cloudtrail/runme.sh @@ -0,0 +1,27 @@ +#!/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/ec2_instance/tasks/ + + +set -eux + +export ANSIBLE_ROLES_PATH=../ +tiny_prefix="$(uuidgen -r|cut -d- -f1)" + +# shellcheck disable=SC2016,SC2086 +echo ' +{ +"ansible_test": { + "environment": { + "ANSIBLE_DEBUG_BOTOCORE_LOGS": "True" + }, + "module_defaults": null +}, +"resource_prefix": "'${tiny_prefix}'", +"tiny_prefix": "'${tiny_prefix}'", +"aws_region": "us-east-2" +}' > _config-file.json + +ansible-playbook main.yml -i inventory "$@" -e @_config-file.json diff --git a/tests/integration/targets/cloudtrail/tasks/main.yml b/tests/integration/targets/cloudtrail/tasks/main.yml new file mode 100644 index 00000000000..4c7ab8e17fd --- /dev/null +++ b/tests/integration/targets/cloudtrail/tasks/main.yml @@ -0,0 +1,1342 @@ +--- +# General Tests: +# - s3_bucket_name required when state is 'present' +# - Creation / Deletion +# - Enable/Disable logging +# - Enable/Disable log file validation option +# - Manipulation of Global Event logging option +# - Manipulation of Multi-Region logging option +# - Manipulation of S3 bucket option +# - Manipulation of Encryption option +# - Manipulation of SNS options +# - Manipulation of CloudWatch Log group options +# - Manipulation of Tags +# +# Notes: +# - results include the updates, even when check_mode is true +# - Poor handling of disable global + enable multi-region +# botocore.errorfactory.InvalidParameterCombinationException: An error +# occurred (InvalidParameterCombinationException) when calling the +# UpdateTrail operation: Multi-Region trail must include global service +# events. +# - Using blank string for KMS ID doesn't remove encryption +# - Using blank string for SNS Topic doesn't remove it +# - Using blank string for CloudWatch Log Group / Role doesn't remove them +# +# Possible Bugs: + +- module_defaults: + # Add this as a default because we (almost) always need it + amazon.aws.cloudtrail: + s3_bucket_name: '{{ s3_bucket_name }}' + region: '{{ aws_region }}' + collections: + - amazon.aws + block: + + # ============================================================ + # Argument Tests + # ============================================================ + - name: 'S3 Bucket required when state is "present"' + module_defaults: { cloudtrail: {} } + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + register: output + ignore_errors: yes + - assert: + that: + - output is failed + + - name: 'CloudWatch cloudwatch_logs_log_group_arn required when cloudwatch_logs_role_arn passed' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + cloudwatch_logs_role_arn: 'SomeValue' + register: output + ignore_errors: yes + - assert: + that: + - output is failed + - '"parameters are required together" in output.msg' + - '"cloudwatch_logs_log_group_arn" in output.msg' + + - name: 'CloudWatch cloudwatch_logs_role_arn required when cloudwatch_logs_log_group_arn passed' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + cloudwatch_logs_log_group_arn: 'SomeValue' + register: output + ignore_errors: yes + - assert: + that: + - output is failed + - '"parameters are required together" in output.msg' + - '"cloudwatch_logs_role_arn" in output.msg' + + #- name: 'Global Logging must be enabled when enabling Multi-region' + # cloudtrail: + # state: present + # name: '{{ cloudtrail_name }}' + # include_global_events: no + # is_multi_region_trail: yes + # register: output + # ignore_errors: yes + #- assert: + # that: + # - output is failed + + # ============================================================ + # Preparation + # ============================================================ + - name: 'Retrieve caller facts' + aws_caller_info: {} + register: aws_caller_info + + - name: 'Create S3 bucket' + vars: + bucket_name: '{{ s3_bucket_name }}' + s3_bucket: + state: present + name: '{{ bucket_name }}' + policy: '{{ lookup("template", "s3-policy.j2") }}' + - name: 'Create second S3 bucket' + vars: + bucket_name: '{{ s3_bucket_name }}-2' + s3_bucket: + state: present + name: '{{ bucket_name }}' + policy: '{{ lookup("template", "s3-policy.j2") }}' + + - name: 'Create SNS Topic' + vars: + sns_topic_name: '{{ sns_topic }}' + sns_topic: + state: present + name: '{{ sns_topic_name }}' + display_name: 'Used for testing SNS/CloudWatch integration' + policy: "{{ lookup('template', 'sns-policy.j2') | to_json }}" + register: output_sns_topic + - name: 'Create second SNS Topic' + vars: + sns_topic_name: '{{ sns_topic }}-2' + sns_topic: + state: present + name: '{{ sns_topic_name }}' + display_name: 'Used for testing SNS/CloudWatch integration' + policy: "{{ lookup('template', 'sns-policy.j2') | to_json }}" + + - name: 'Create KMS Key' + aws_kms: + state: present + alias: '{{ kms_alias }}' + enabled: yes + policy: "{{ lookup('template', 'kms-policy.j2') | to_json }}" + register: kms_key + - name: 'Create second KMS Key' + aws_kms: + state: present + alias: '{{ kms_alias }}-2' + enabled: yes + policy: "{{ lookup('template', 'kms-policy.j2') | to_json }}" + register: kms_key2 + + - name: 'Create CloudWatch IAM Role' + iam_role: + state: present + name: '{{ cloudwatch_role }}' + assume_role_policy_document: "{{ lookup('template', 'cloudwatch-assume-policy.j2') }}" + register: output_cloudwatch_role + - name: 'Create CloudWatch Log Group' + cloudwatchlogs_log_group: + state: present + log_group_name: '{{ cloudwatch_log_group }}' + retention: 1 + register: output_cloudwatch_log_group + - name: 'Create second CloudWatch Log Group' + cloudwatchlogs_log_group: + state: present + log_group_name: '{{ cloudwatch_log_group }}-2' + retention: 1 + register: output_cloudwatch_log_group2 + - name: 'Add inline policy to CloudWatch Role' + iam_policy: + state: present + iam_type: role + iam_name: '{{ cloudwatch_role }}' + 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 + # ============================================================ + + - name: 'Create a trail (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Create a trail' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output is changed + - output.exists == True + - output.trail.name == cloudtrail_name + + - name: 'No-op update to trail' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output is not changed + - output.exists == True + # Check everything is what we expect before we start making changes + - output.trail.name == cloudtrail_name + - output.trail.home_region == aws_region + - output.trail.include_global_service_events == True + - output.trail.is_multi_region_trail == False + - output.trail.is_logging == True + - output.trail.log_file_validation_enabled == False + - output.trail.s3_bucket_name == s3_bucket_name + - output.trail.s3_key_prefix is none + - output.trail.kms_key_id is none + - output.trail.sns_topic_arn is none + - output.trail.sns_topic_name is none + - output.trail.tags | length == 0 + + # ============================================================ + + - name: 'Set S3 prefix (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '{{ cloudtrail_prefix }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Set S3 prefix' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '{{ cloudtrail_prefix }}' + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.s3_key_prefix == cloudtrail_prefix + + - name: 'Set S3 prefix (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '{{ cloudtrail_prefix }}' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.s3_key_prefix == cloudtrail_prefix + + - name: 'No-op update to trail' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.s3_key_prefix == cloudtrail_prefix + + - name: 'Update S3 prefix (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '{{ cloudtrail_prefix }}-2' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Update S3 prefix' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '{{ cloudtrail_prefix }}-2' + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - 'output.trail.s3_key_prefix == "{{ cloudtrail_prefix }}-2"' + + - name: 'Update S3 prefix (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '{{ cloudtrail_prefix }}-2' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - 'output.trail.s3_key_prefix == "{{ cloudtrail_prefix }}-2"' + + - name: 'Remove S3 prefix (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '/' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Remove S3 prefix' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '/' + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.s3_key_prefix is none + + - name: 'Remove S3 prefix (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '/' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.s3_key_prefix is none + + # ============================================================ + + - include_tasks: 'tagging.yml' + + # ============================================================ + + - name: 'Set SNS Topic (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + sns_topic_name: '{{ sns_topic }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Set SNS Topic' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + sns_topic_name: '{{ sns_topic }}' + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.sns_topic_name == sns_topic + + - name: 'Set SNS Topic (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + sns_topic_name: '{{ sns_topic }}' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.sns_topic_name == sns_topic + + - name: 'No-op update to trail' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.sns_topic_name == sns_topic + + - name: 'Update SNS Topic (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + sns_topic_name: '{{ sns_topic }}-2' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Update SNS Topic' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + sns_topic_name: '{{ sns_topic }}-2' + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - 'output.trail.sns_topic_name == "{{ sns_topic }}-2"' + + - name: 'Update SNS Topic (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + sns_topic_name: '{{ sns_topic }}-2' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - 'output.trail.sns_topic_name == "{{ sns_topic }}-2"' + + #- name: 'Remove SNS Topic (CHECK MODE)' + # cloudtrail: + # state: present + # name: '{{ cloudtrail_name }}' + # sns_topic_name: '' + # register: output + # check_mode: yes + #- assert: + # that: + # - output is changed + + #- name: 'Remove SNS Topic' + # cloudtrail: + # state: present + # name: '{{ cloudtrail_name }}' + # sns_topic_name: '' + # register: output + #- assert: + # that: + # - output is changed + # - output.trail.name == cloudtrail_name + # - output.trail.sns_topic_name is none + + #- name: 'Remove SNS Topic (no change)' + # cloudtrail: + # state: present + # name: '{{ cloudtrail_name }}' + # sns_topic_name: '' + # register: output + #- assert: + # that: + # - output is not changed + # - output.trail.name == cloudtrail_name + # - output.trail.sns_topic_name is none + + + # ============================================================ + + - name: 'Set CloudWatch Log Group (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + cloudwatch_logs_log_group_arn: '{{ output_cloudwatch_log_group.arn }}' + cloudwatch_logs_role_arn: '{{ output_cloudwatch_role.arn }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Set CloudWatch Log Group' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + cloudwatch_logs_log_group_arn: '{{ output_cloudwatch_log_group.arn }}' + cloudwatch_logs_role_arn: '{{ output_cloudwatch_role.arn }}' + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.cloud_watch_logs_log_group_arn == output_cloudwatch_log_group.arn + - output.trail.cloud_watch_logs_role_arn == output_cloudwatch_role.arn + + - name: 'Set CloudWatch Log Group (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + cloudwatch_logs_log_group_arn: '{{ output_cloudwatch_log_group.arn }}' + cloudwatch_logs_role_arn: '{{ output_cloudwatch_role.arn }}' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.cloud_watch_logs_log_group_arn == output_cloudwatch_log_group.arn + - output.trail.cloud_watch_logs_role_arn == output_cloudwatch_role.arn + + - name: 'No-op update to trail' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.cloud_watch_logs_log_group_arn == output_cloudwatch_log_group.arn + - output.trail.cloud_watch_logs_role_arn == output_cloudwatch_role.arn + + - name: 'Update CloudWatch Log Group (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + cloudwatch_logs_log_group_arn: '{{ output_cloudwatch_log_group2.arn }}' + cloudwatch_logs_role_arn: '{{ output_cloudwatch_role.arn }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + - output.trail.cloud_watch_logs_log_group_arn == output_cloudwatch_log_group2.arn + - output.trail.cloud_watch_logs_role_arn == output_cloudwatch_role.arn + + - name: 'Update CloudWatch Log Group' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + cloudwatch_logs_log_group_arn: '{{ output_cloudwatch_log_group2.arn }}' + cloudwatch_logs_role_arn: '{{ output_cloudwatch_role.arn }}' + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.cloud_watch_logs_log_group_arn == output_cloudwatch_log_group2.arn + - output.trail.cloud_watch_logs_role_arn == output_cloudwatch_role.arn + + - name: 'Update CloudWatch Log Group (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + cloudwatch_logs_log_group_arn: '{{ output_cloudwatch_log_group2.arn }}' + cloudwatch_logs_role_arn: '{{ output_cloudwatch_role.arn }}' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.cloud_watch_logs_log_group_arn == output_cloudwatch_log_group2.arn + - output.trail.cloud_watch_logs_role_arn == output_cloudwatch_role.arn + + #- name: 'Remove CloudWatch Log Group (CHECK MODE)' + # cloudtrail: + # state: present + # name: '{{ cloudtrail_name }}' + # cloudwatch_logs_log_group_arn: '' + # cloudwatch_logs_role_arn: '' + # register: output + # check_mode: yes + #- assert: + # that: + # - output is changed + # - output.trail.name == cloudtrail_name + # - output.trail.cloud_watch_logs_log_group_arn is none + # - output.trail.cloud_watch_logs_role_arn is none + + #- name: 'Remove CloudWatch Log Group' + # cloudtrail: + # state: present + # name: '{{ cloudtrail_name }}' + # cloudwatch_logs_log_group_arn: '' + # cloudwatch_logs_role_arn: '' + # register: output + #- assert: + # that: + # - output is changed + # - output.trail.name == cloudtrail_name + # - output.trail.cloud_watch_logs_log_group_arn is none + # - output.trail.cloud_watch_logs_role_arn is none + + #- name: 'Remove CloudWatch Log Group (no change)' + # cloudtrail: + # state: present + # name: '{{ cloudtrail_name }}' + # cloudwatch_logs_log_group_arn: '' + # cloudwatch_logs_role_arn: '' + # register: output + #- assert: + # that: + # - output is not changed + # - output.trail.name == cloudtrail_name + # - output.trail.cloud_watch_logs_log_group_arn is none + # - output.trail.cloud_watch_logs_role_arn is none + + # ============================================================ + + - name: 'Update S3 bucket (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_bucket_name: '{{ s3_bucket_name }}-2' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Update S3 bucket' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_bucket_name: '{{ s3_bucket_name }}-2' + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - 'output.trail.s3_bucket_name == "{{ s3_bucket_name }}-2"' + + - name: 'Update S3 bucket (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_bucket_name: '{{ s3_bucket_name }}-2' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - 'output.trail.s3_bucket_name == "{{ s3_bucket_name }}-2"' + + - name: 'Reset S3 bucket' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output.trail.name == cloudtrail_name + - output.trail.s3_bucket_name == s3_bucket_name + + # ============================================================ + + - name: 'Disable logging (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_logging: no + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Disable logging' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_logging: no + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.is_logging == False + + - name: 'Disable logging (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_logging: no + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.is_logging == False + + # Ansible Documentation lists logging as explicitly defaulting to enabled + + - name: 'Enable logging (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_logging: yes + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Enable logging' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_logging: yes + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.is_logging == True + + - name: 'Enable logging (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_logging: yes + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.is_logging == True + + # ============================================================ + + - name: 'Disable global logging (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + include_global_events: no + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Disable global logging' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + include_global_events: no + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.include_global_service_events == False + + - name: 'Disable global logging (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + include_global_events: no + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.include_global_service_events == False + + # Ansible Documentation lists Global-logging as explicitly defaulting to enabled + + - name: 'Enable global logging (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + include_global_events: yes + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Enable global logging' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + include_global_events: yes + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.include_global_service_events == True + + - name: 'Enable global logging (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + include_global_events: yes + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.include_global_service_events == True + + # ============================================================ + + - name: 'Enable multi-region logging (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + is_multi_region_trail: yes + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Enable multi-region logging' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + is_multi_region_trail: yes + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.is_multi_region_trail == True + + - name: 'Enable multi-region logging (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + is_multi_region_trail: yes + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.is_multi_region_trail == True + + # Ansible Documentation lists Multi-Region-logging as explicitly defaulting to disabled + + - name: 'Disable multi-region logging (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + is_multi_region_trail: no + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Disable multi-region logging' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + is_multi_region_trail: no + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.is_multi_region_trail == False + + - name: 'Disable multi-region logging (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + is_multi_region_trail: no + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.is_multi_region_trail == False + + # ============================================================ + + - name: 'Enable logfile validation (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_log_file_validation: yes + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Enable logfile validation' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_log_file_validation: yes + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.log_file_validation_enabled == True + + - name: 'Enable logfile validation (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_log_file_validation: yes + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.log_file_validation_enabled == True + + - name: 'No-op update to trail' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.log_file_validation_enabled == True + + - name: 'Disable logfile validation (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_log_file_validation: no + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Disable logfile validation' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_log_file_validation: no + register: output + - assert: + that: + - output is changed + - output.trail.name == cloudtrail_name + - output.trail.log_file_validation_enabled == False + + - name: 'Disable logfile validation (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + enable_log_file_validation: no + register: output + - assert: + that: + - output is not changed + - output.trail.name == cloudtrail_name + - output.trail.log_file_validation_enabled == False + + # ============================================================ + + - name: 'Enable logging encryption (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + kms_key_id: '{{ kms_key.key_arn }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Enable logging encryption' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + kms_key_id: '{{ kms_key.key_arn }}' + register: output + - assert: + that: + - output is changed + - output.trail.kms_key_id == kms_key.key_arn + + - name: 'Enable logging encryption (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + kms_key_id: '{{ kms_key.key_arn }}' + register: output + - assert: + that: + - 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 + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output is not changed + - output.trail.kms_key_id == kms_key.key_arn + + - name: 'Update logging encryption key (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + kms_key_id: '{{ kms_key2.key_arn }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Update logging encryption key' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + kms_key_id: '{{ kms_key2.key_arn }}' + register: output + - assert: + that: + - output is changed + - output.trail.kms_key_id == kms_key2.key_arn + + - name: 'Update logging encryption key (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + kms_key_id: '{{ kms_key2.key_arn }}' + register: output + - assert: + that: + - output is not changed + - output.trail.kms_key_id == kms_key2.key_arn + + - name: 'Update logging encryption to alias (CHECK MODE)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + kms_key_id: 'alias/{{ kms_alias }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Update logging encryption to alias' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + kms_key_id: 'alias/{{ kms_alias }}' + register: output + - assert: + that: + - output is changed + - output.trail.kms_key_id == kms_key.key_arn + + - name: 'Update logging encryption to alias (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + kms_key_id: 'alias/{{ kms_alias }}' + register: output + - assert: + that: + - 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" + 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 + # name: '{{ cloudtrail_name }}' + # kms_key_id: '' + # register: output + # check_mode: yes + #- assert: + # that: + # - output is changed + + #- name: 'Disable logging encryption' + # cloudtrail: + # state: present + # name: '{{ cloudtrail_name }}' + # kms_key_id: '' + # register: output + #- assert: + # that: + # - output.trail.kms_key_id == None + # - output is changed + + #- name: 'Disable logging encryption (no change)' + # cloudtrail: + # state: present + # name: '{{ cloudtrail_name }}' + # kms_key_id: '' + # register: output + #- assert: + # that: + # - output.kms_key_id == None + # - output is not changed + + # ============================================================ + + - name: 'Delete a trail without providing bucket_name (CHECK MODE)' + module_defaults: { cloudtrail: {} } + cloudtrail: + state: absent + name: '{{ cloudtrail_name }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Delete a trail while providing bucket_name (CHECK MODE)' + cloudtrail: + state: absent + name: '{{ cloudtrail_name }}' + register: output + check_mode: yes + - assert: + that: + - output is changed + + - name: 'Delete a trail' + cloudtrail: + state: absent + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output is changed + - output.exists == False + + - name: 'Delete a non-existent trail' + cloudtrail: + state: absent + name: '{{ cloudtrail_name }}' + register: output + - assert: + that: + - output is not changed + - output.exists == False + + # ============================================================ + + - name: 'Test creation of a complex Trail (all features)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '{{ cloudtrail_prefix }}' + sns_topic_name: '{{ sns_topic }}' + cloudwatch_logs_log_group_arn: '{{ output_cloudwatch_log_group.arn }}' + cloudwatch_logs_role_arn: '{{ output_cloudwatch_role.arn }}' + is_multi_region_trail: yes + include_global_events: yes + enable_log_file_validation: yes + kms_key_id: '{{ kms_key.key_arn }}' + register: output + - assert: + that: + - output is changed + #- output.exists == True + - output.trail.name == cloudtrail_name + - output.trail.home_region == aws_region + - output.trail.include_global_service_events == True + - output.trail.is_multi_region_trail == True + - output.trail.is_logging == True + - output.trail.log_file_validation_enabled == True + - output.trail.s3_bucket_name == s3_bucket_name + - output.trail.s3_key_prefix == cloudtrail_prefix + - output.trail.kms_key_id == kms_key.key_arn + - output.trail.sns_topic_arn == output_sns_topic.sns_arn + - output.trail.sns_topic_name == sns_topic + - output.trail.tags | length == 0 + + - name: 'Test creation of a complex Trail (no change)' + cloudtrail: + state: present + name: '{{ cloudtrail_name }}' + s3_key_prefix: '{{ cloudtrail_prefix }}' + sns_topic_name: '{{ sns_topic }}' + cloudwatch_logs_log_group_arn: '{{ output_cloudwatch_log_group.arn }}' + cloudwatch_logs_role_arn: '{{ output_cloudwatch_role.arn }}' + is_multi_region_trail: yes + include_global_events: yes + enable_log_file_validation: yes + kms_key_id: '{{ kms_key.key_arn }}' + register: output + - assert: + that: + - output is not changed + - output.exists == True + - output.trail.name == cloudtrail_name + - output.trail.home_region == aws_region + - output.trail.include_global_service_events == True + - output.trail.is_multi_region_trail == True + - output.trail.is_logging == True + - output.trail.log_file_validation_enabled == True + - output.trail.s3_bucket_name == s3_bucket_name + - output.trail.s3_key_prefix == cloudtrail_prefix + - output.trail.kms_key_id == kms_key.key_arn + - output.trail.sns_topic_arn == output_sns_topic.sns_arn + - output.trail.sns_topic_name == sns_topic + - output.trail.tags | length == 0 + + always: + # ============================================================ + # Cleanup + # ============================================================ + - name: 'Delete test trail' + cloudtrail: + state: absent + name: '{{ cloudtrail_name }}' + ignore_errors: yes + - name: 'Delete S3 bucket' + s3_bucket: + state: absent + name: '{{ s3_bucket_name }}' + force: yes + ignore_errors: yes + - name: 'Delete second S3 bucket' + s3_bucket: + state: absent + name: '{{ s3_bucket_name }}-2' + force: yes + ignore_errors: yes + - name: 'Delete KMS Key' + aws_kms: + state: absent + alias: '{{ kms_alias }}' + ignore_errors: yes + - name: 'Delete second KMS Key' + aws_kms: + state: absent + alias: '{{ kms_alias }}-2' + ignore_errors: yes + - name: 'Delete SNS Topic' + sns_topic: + state: absent + name: '{{ sns_topic }}' + ignore_errors: yes + - name: 'Delete second SNS Topic' + sns_topic: + state: absent + name: '{{ sns_topic }}-2' + ignore_errors: yes + - name: 'Delete CloudWatch Log Group' + cloudwatchlogs_log_group: + state: absent + log_group_name: '{{ cloudwatch_log_group }}' + ignore_errors: yes + - name: 'Delete second CloudWatch Log Group' + cloudwatchlogs_log_group: + state: absent + log_group_name: '{{ cloudwatch_log_group }}-2' + ignore_errors: yes + - name: 'Remove inline policy to CloudWatch Role' + iam_policy: + state: absent + iam_type: role + iam_name: '{{ cloudwatch_role }}' + policy_name: 'CloudWatch' + ignore_errors: yes + - name: 'Delete CloudWatch IAM Role' + iam_role: + 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 diff --git a/tests/integration/targets/cloudtrail/tasks/tagging.yml b/tests/integration/targets/cloudtrail/tasks/tagging.yml new file mode 100644 index 00000000000..df537c67e3e --- /dev/null +++ b/tests/integration/targets/cloudtrail/tasks/tagging.yml @@ -0,0 +1,252 @@ +- name: Tests relating to tagging cloudtrails + vars: + first_tags: + 'Key with Spaces': Value with spaces + CamelCaseKey: CamelCaseValue + pascalCaseKey: pascalCaseValue + snake_case_key: snake_case_value + second_tags: + 'New Key with Spaces': Value with spaces + NewCamelCaseKey: CamelCaseValue + newPascalCaseKey: pascalCaseValue + new_snake_case_key: snake_case_value + third_tags: + 'Key with Spaces': Value with spaces + CamelCaseKey: CamelCaseValue + pascalCaseKey: pascalCaseValue + snake_case_key: snake_case_value + 'New Key with Spaces': Updated Value with spaces + final_tags: + 'Key with Spaces': Value with spaces + CamelCaseKey: CamelCaseValue + pascalCaseKey: pascalCaseValue + snake_case_key: snake_case_value + 'New Key with Spaces': Updated Value with spaces + NewCamelCaseKey: CamelCaseValue + newPascalCaseKey: pascalCaseValue + new_snake_case_key: snake_case_value + # Mandatory settings + module_defaults: + amazon.aws.cloudtrail: + name: '{{ cloudtrail_name }}' + s3_bucket_name: '{{ s3_bucket_name }}' + state: present +# community.aws.cloudtrail_info: +# name: '{{ cloudtrail_name }}' + block: + + ### + + - name: test adding tags to cloudtrail (check mode) + cloudtrail: + tags: '{{ first_tags }}' + purge_tags: True + register: update_result + check_mode: yes + - name: assert that update succeeded + assert: + that: + - update_result is changed + + - name: test adding tags to cloudtrail + cloudtrail: + tags: '{{ first_tags }}' + purge_tags: True + register: update_result + - name: assert that update succeeded + assert: + that: + - update_result is changed + - update_result.trail.tags == first_tags + + - name: test adding tags to cloudtrail - idempotency (check mode) + cloudtrail: + tags: '{{ first_tags }}' + purge_tags: True + register: update_result + check_mode: yes + - name: assert that update succeeded + assert: + that: + - update_result is not changed + + - name: test adding tags to cloudtrail - idempotency + cloudtrail: + tags: '{{ first_tags }}' + purge_tags: True + register: update_result + - name: assert that update succeeded + assert: + that: + - update_result is not changed + - update_result.trail.tags == first_tags + + ### + + - name: test updating tags with purge on cloudtrail (check mode) + cloudtrail: + tags: '{{ second_tags }}' + purge_tags: True + register: update_result + check_mode: yes + - name: assert that update succeeded + assert: + that: + - update_result is changed + + - name: test updating tags with purge on cloudtrail + cloudtrail: + tags: '{{ second_tags }}' + purge_tags: True + register: update_result + - name: assert that update succeeded + assert: + that: + - update_result is changed + - update_result.trail.tags == second_tags + + - name: test updating tags with purge on cloudtrail - idempotency (check mode) + cloudtrail: + tags: '{{ second_tags }}' + purge_tags: True + register: update_result + check_mode: yes + - name: assert that update succeeded + assert: + that: + - update_result is not changed + + - name: test updating tags with purge on cloudtrail - idempotency + cloudtrail: + tags: '{{ second_tags }}' + purge_tags: True + register: update_result + - name: assert that update succeeded + assert: + that: + - update_result is not changed + - update_result.trail.tags == second_tags + + ### + + - name: test updating tags without purge on cloudtrail (check mode) + cloudtrail: + tags: '{{ third_tags }}' + purge_tags: False + register: update_result + check_mode: yes + - name: assert that update succeeded + assert: + that: + - update_result is changed + + - name: test updating tags without purge on cloudtrail + cloudtrail: + tags: '{{ third_tags }}' + purge_tags: False + register: update_result + - name: assert that update succeeded + assert: + that: + - update_result is changed + - update_result.trail.tags == final_tags + + - name: test updating tags without purge on cloudtrail - idempotency (check mode) + cloudtrail: + tags: '{{ third_tags }}' + purge_tags: False + register: update_result + check_mode: yes + - name: assert that update succeeded + assert: + that: + - update_result is not changed + + - name: test updating tags without purge on cloudtrail - idempotency + cloudtrail: + tags: '{{ third_tags }}' + purge_tags: False + register: update_result + - name: assert that update succeeded + assert: + that: + - update_result is not changed + - update_result.trail.tags == final_tags + +# ### +# +# - name: test that cloudtrail_info returns the tags +# cloudtrail_info: +# register: tag_info +# - name: assert tags present +# assert: +# that: +# - tag_info.trail.tags == final_tags +# +# ### + + - name: test no tags param cloudtrail (check mode) + cloudtrail: {} + register: update_result + check_mode: yes + - name: assert no change + assert: + that: + - update_result is not changed + - update_result.trail.tags == final_tags + + + - name: test no tags param cloudtrail + cloudtrail: {} + register: update_result + - name: assert no change + assert: + that: + - update_result is not changed + - update_result.trail.tags == final_tags + + ### + + - name: test removing tags from cloudtrail (check mode) + cloudtrail: + tags: {} + purge_tags: True + register: update_result + check_mode: yes + - name: assert that update succeeded + assert: + that: + - update_result is changed + + - name: test removing tags from cloudtrail + cloudtrail: + tags: {} + purge_tags: True + register: update_result + - name: assert that update succeeded + assert: + that: + - update_result is changed + - update_result.trail.tags == {} + + - name: test removing tags from cloudtrail - idempotency (check mode) + cloudtrail: + tags: {} + purge_tags: True + register: update_result + check_mode: yes + - name: assert that update succeeded + assert: + that: + - update_result is not changed + + - name: test removing tags from cloudtrail - idempotency + cloudtrail: + tags: {} + purge_tags: True + register: update_result + - name: assert that update succeeded + assert: + that: + - update_result is not changed + - update_result.trail.tags == {}