├── UnassociatedElasticIPs ├── __init__.py ├── remove_elastic_IP │ ├── __init__.py │ └── app.py ├── images │ └── architecture.png ├── events │ └── EventBridge_Example.json ├── samconfig.toml ├── template.yaml └── README.md ├── IAMPasswordPolicy ├── set_password_policy │ ├── __init__.py │ └── app.py ├── samconfig.toml ├── template.yaml └── README.md ├── TA-WellArchitected ├── tawa-optimization-starter │ ├── __init__.py │ ├── src │ │ └── tawa │ │ │ ├── __init__.py │ │ │ └── requirements.txt │ └── static │ │ └── images │ │ ├── Shot01.png │ │ ├── Shot02.png │ │ ├── Shot03.png │ │ ├── Shot04.png │ │ ├── Shot05.png │ │ ├── Shot06.png │ │ ├── Shot07.png │ │ ├── Shot08.png │ │ ├── prompts.png │ │ ├── subscribe.png │ │ ├── TA-WA-Optimizer-HighArch.png │ │ └── TA-WA-Optimizer-HighArch2.png └── tawa-eisenhower-matrix-app │ ├── public │ ├── robots.txt │ └── favicon.ico │ ├── static │ └── images │ │ ├── aws_amplify_cleanup_1.png │ │ ├── aws_amplify_cleanup_2.png │ │ ├── s3_download_json_file_1.png │ │ ├── s3_download_json_file_2.png │ │ ├── s3_download_json_file_3.png │ │ ├── aws_amplify_get_started_1.png │ │ ├── aws_amplify_get_started_2.png │ │ ├── aws_amplify_get_started_3.png │ │ ├── aws_amplify_get_started_4.png │ │ ├── aws_prioritization_matrix_app_sample.png │ │ ├── aws_prioritization_matrix_app_sample_start.png │ │ ├── aws_prioritization_matrix_app_sample_upload.png │ │ ├── aws_prioritization_matrix_app_sample_comments.png │ │ └── aws_prioritization_matrix_app_sample_ta_checks_selection.png │ ├── src │ ├── index.jsx │ ├── utils │ │ ├── utilities.js │ │ └── toolbox-table-config.jsx │ └── example-styles.css │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── index.html │ └── vite.config.js ├── S3IncompleteMPUAbort └── ta-s3-incomplete-mpu-abort │ ├── __init__.py │ ├── tests │ ├── __init__.py │ ├── unit │ │ ├── __init__.py │ │ └── test_handler.py │ ├── integration │ │ ├── __init__.py │ │ └── test_ta_event.py │ └── requirements.txt │ ├── apply_lifecycle_function │ ├── __init__.py │ ├── model │ │ ├── __init__.py │ │ └── aws │ │ │ ├── __init__.py │ │ │ └── ta │ │ │ ├── __init__.py │ │ │ ├── ta_state_change_notification.py │ │ │ ├── aws_event.py │ │ │ └── marshaller.py │ ├── apply_lifecycle │ │ ├── __init__.py │ │ └── app.py │ └── requirements.txt │ ├── conftest.py │ ├── events │ └── event.json │ ├── samconfig.toml │ ├── template.yaml │ └── README.md ├── images ├── AmazonRDSIdleDBInstances-TA.png ├── cloudformation-launch-stack.png └── LowUtilizationEC2InstancesArchitecture.jpg ├── ExposedAccessKeys ├── images │ └── Architecture.png ├── terraform │ ├── ta-automation.png │ ├── member │ │ ├── variables.tf │ │ └── main.tf │ ├── main │ │ ├── variables.tf │ │ └── src │ │ │ ├── ta-12Fnkpl8Y5-deactivateiamkey.py │ │ │ ├── ta-12Fnkpl8Y5-snsmessage.py │ │ │ └── ta-12Fnkpl8Y5-cloudtraileventlookup.py │ └── README.md ├── stepbystep │ ├── images │ │ ├── diagram.png │ │ ├── step_cw.png │ │ ├── step_iam.png │ │ ├── step_sns.png │ │ ├── step_lambda.png │ │ ├── step_ec2_console.png │ │ ├── step_stepfunctions.png │ │ └── step_subscription.png │ └── mockpayload.json ├── lambda_functions │ ├── delete_access_key_pair.py │ ├── lookup_cloudtrail_events.py │ └── notify_security.py └── cloudformation │ ├── exposed_access_keys.serverless.yaml │ └── exposed_access_keys_one_click.serverless.yaml ├── UnderutilzedEBSVolumes ├── TAEBSVolDel.py.zip ├── TAT-UEBS-v1.0.zip ├── TASnapandDelete.zip ├── TAUnderutilizedEBS.yaml.png ├── CloudWatchEventPattern-snapshot.json ├── CloudWatchEventPattern.json ├── snapshot_complete_event.json ├── event_example.json ├── TAUnderutilizedEBS.drawio └── TASnapandDeleteEBS-1click.yaml ├── HighUtilizationEC2Instances ├── images │ ├── diagram.png │ ├── step1.png │ ├── step11.png │ ├── step12.png │ ├── step5.png │ ├── step6.png │ ├── step6a.png │ ├── step7.png │ ├── step8.png │ ├── step9.png │ ├── step0-diag-build.png │ ├── step1-diag-build.png │ ├── step2-diag-build.png │ ├── step3-diag-build.png │ └── step4-diag-build.png └── mockpayload.json ├── AmazonEBSSnapshots ├── stepbystep │ ├── images │ │ ├── diagram.png │ │ ├── step_lambda.png │ │ ├── step_cloudwatch.png │ │ ├── step_create_tag.png │ │ ├── step_lifecycle.png │ │ └── step_ec2_console.png │ ├── IAMPolicy │ ├── mockpayload.json │ └── LambdaFunction.py ├── CloudwatchEventPattern ├── IAMPolicy ├── README.md └── LambdaFunction.js ├── TA-Responder ├── static │ └── images │ │ ├── invoke_model_url_screenshot.png │ │ ├── detailed_ta_responder_diagram.png │ │ ├── high_level_ta_responder_diagram.png │ │ └── invoke_model_ssm_output_screenshot.png ├── cfn_templates │ └── tatools-ta-responder-infra-setup.yaml ├── lambda_handlers │ ├── SSMAutomationExecutionEventsHandler.py │ └── TrustedAdvisorCheckTrackerFunction.py └── automation_docs_scripts │ └── InvokeModelExecutionScript.py ├── TA-Integrations ├── TA-Red-Cost-Slack-Webhook │ ├── TA-Slack-Arch.PNG │ ├── TA-Red-Slack-Webhook.py │ └── README.md └── README.md ├── .github └── PULL_REQUEST_TEMPLATE.md ├── S3BucketVersioning ├── samconfig.toml ├── LambdaTestEvent.json ├── template.yaml ├── lambda │ └── app.py └── README.md ├── CODE_OF_CONDUCT.md ├── LowUtilizationEC2Instances ├── CloudwatchEventPattern ├── IAMPolicy ├── README.md └── LambdaFunction.js ├── .gitignore ├── AmazonRDSIdleDBInstances ├── sample_event.json ├── template.yaml ├── README.md └── src │ └── remove_idle_rds_databases.py ├── README.md └── CONTRIBUTING.md /UnassociatedElasticIPs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /IAMPasswordPolicy/set_password_policy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UnassociatedElasticIPs/remove_elastic_IP/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/src/tawa/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/model/aws/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/apply_lifecycle/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-mock 3 | boto3 -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/src/tawa/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | bs4 3 | requests 4 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | six 3 | regex -------------------------------------------------------------------------------- /images/AmazonRDSIdleDBInstances-TA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/images/AmazonRDSIdleDBInstances-TA.png -------------------------------------------------------------------------------- /images/cloudformation-launch-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/images/cloudformation-launch-stack.png -------------------------------------------------------------------------------- /ExposedAccessKeys/images/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/images/Architecture.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/TAEBSVolDel.py.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/UnderutilzedEBSVolumes/TAEBSVolDel.py.zip -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/TAT-UEBS-v1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/UnderutilzedEBSVolumes/TAT-UEBS-v1.0.zip -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/TASnapandDelete.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/UnderutilzedEBSVolumes/TASnapandDelete.zip -------------------------------------------------------------------------------- /ExposedAccessKeys/terraform/ta-automation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/terraform/ta-automation.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/diagram.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step1.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step11.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step12.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step5.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step6.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step6a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step6a.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step7.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step8.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step9.png -------------------------------------------------------------------------------- /UnassociatedElasticIPs/images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/UnassociatedElasticIPs/images/architecture.png -------------------------------------------------------------------------------- /AmazonEBSSnapshots/stepbystep/images/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/AmazonEBSSnapshots/stepbystep/images/diagram.png -------------------------------------------------------------------------------- /ExposedAccessKeys/stepbystep/images/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/stepbystep/images/diagram.png -------------------------------------------------------------------------------- /ExposedAccessKeys/stepbystep/images/step_cw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/stepbystep/images/step_cw.png -------------------------------------------------------------------------------- /ExposedAccessKeys/stepbystep/images/step_iam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/stepbystep/images/step_iam.png -------------------------------------------------------------------------------- /ExposedAccessKeys/stepbystep/images/step_sns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/stepbystep/images/step_sns.png -------------------------------------------------------------------------------- /ExposedAccessKeys/stepbystep/images/step_lambda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/stepbystep/images/step_lambda.png -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/conftest.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | 3 | here = os.path.abspath("apply_lifecycle_function") 4 | sys.path.insert(0, here) -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/TAUnderutilizedEBS.yaml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/UnderutilzedEBSVolumes/TAUnderutilizedEBS.yaml.png -------------------------------------------------------------------------------- /images/LowUtilizationEC2InstancesArchitecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/images/LowUtilizationEC2InstancesArchitecture.jpg -------------------------------------------------------------------------------- /AmazonEBSSnapshots/stepbystep/images/step_lambda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/AmazonEBSSnapshots/stepbystep/images/step_lambda.png -------------------------------------------------------------------------------- /AmazonEBSSnapshots/stepbystep/images/step_cloudwatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/AmazonEBSSnapshots/stepbystep/images/step_cloudwatch.png -------------------------------------------------------------------------------- /AmazonEBSSnapshots/stepbystep/images/step_create_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/AmazonEBSSnapshots/stepbystep/images/step_create_tag.png -------------------------------------------------------------------------------- /AmazonEBSSnapshots/stepbystep/images/step_lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/AmazonEBSSnapshots/stepbystep/images/step_lifecycle.png -------------------------------------------------------------------------------- /ExposedAccessKeys/stepbystep/images/step_ec2_console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/stepbystep/images/step_ec2_console.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step0-diag-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step0-diag-build.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step1-diag-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step1-diag-build.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step2-diag-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step2-diag-build.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step3-diag-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step3-diag-build.png -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/images/step4-diag-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/HighUtilizationEC2Instances/images/step4-diag-build.png -------------------------------------------------------------------------------- /AmazonEBSSnapshots/stepbystep/images/step_ec2_console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/AmazonEBSSnapshots/stepbystep/images/step_ec2_console.png -------------------------------------------------------------------------------- /ExposedAccessKeys/stepbystep/images/step_stepfunctions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/stepbystep/images/step_stepfunctions.png -------------------------------------------------------------------------------- /ExposedAccessKeys/stepbystep/images/step_subscription.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/ExposedAccessKeys/stepbystep/images/step_subscription.png -------------------------------------------------------------------------------- /TA-Responder/static/images/invoke_model_url_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-Responder/static/images/invoke_model_url_screenshot.png -------------------------------------------------------------------------------- /TA-Integrations/TA-Red-Cost-Slack-Webhook/TA-Slack-Arch.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-Integrations/TA-Red-Cost-Slack-Webhook/TA-Slack-Arch.PNG -------------------------------------------------------------------------------- /TA-Responder/static/images/detailed_ta_responder_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-Responder/static/images/detailed_ta_responder_diagram.png -------------------------------------------------------------------------------- /TA-Responder/static/images/high_level_ta_responder_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-Responder/static/images/high_level_ta_responder_diagram.png -------------------------------------------------------------------------------- /TA-Responder/static/images/invoke_model_ssm_output_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-Responder/static/images/invoke_model_ssm_output_screenshot.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/public/favicon.ico -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/Shot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/Shot01.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/Shot02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/Shot02.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/Shot03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/Shot03.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/Shot04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/Shot04.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/Shot05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/Shot05.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/Shot06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/Shot06.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/Shot07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/Shot07.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/Shot08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/Shot08.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/prompts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/prompts.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/subscribe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/subscribe.png -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_cleanup_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_cleanup_1.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_cleanup_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_cleanup_2.png -------------------------------------------------------------------------------- /ExposedAccessKeys/terraform/member/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS Region" 3 | default = "eu-west-1" 4 | } 5 | 6 | variable "main_aws_account_id"{ 7 | description = "AWS Account ID of the main account" 8 | } -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/s3_download_json_file_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/s3_download_json_file_1.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/s3_download_json_file_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/s3_download_json_file_2.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/s3_download_json_file_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/s3_download_json_file_3.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/TA-WA-Optimizer-HighArch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/TA-WA-Optimizer-HighArch.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-optimization-starter/static/images/TA-WA-Optimizer-HighArch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-optimization-starter/static/images/TA-WA-Optimizer-HighArch2.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_get_started_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_get_started_1.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_get_started_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_get_started_2.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_get_started_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_get_started_3.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_get_started_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_amplify_get_started_4.png -------------------------------------------------------------------------------- /ExposedAccessKeys/terraform/main/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "AWS Region" 3 | default = "eu-west-1" 4 | } 5 | 6 | variable "email" { 7 | type = string 8 | description = "Email address for security notification" 9 | } -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/model/aws/ta/__init__.py: -------------------------------------------------------------------------------- 1 | from .marshaller import Marshaller 2 | from .aws_event import AWSEvent 3 | from .ta_state_change_notification import TAStateChangeNotification 4 | 5 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample_start.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample_upload.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample_comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample_comments.png -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/src/index.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | import ToolboxLayout from './hri_dash'; 3 | 4 | const root = ReactDOM.createRoot(document.getElementById('root')); 5 | root.render( 6 | 7 | ); 8 | 9 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample_ta_checks_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Trusted-Advisor-Tools/HEAD/TA-WellArchitected/tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample_ta_checks_selection.png -------------------------------------------------------------------------------- /S3BucketVersioning/samconfig.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | [default.deploy.parameters] 3 | stack_name = "TA-S3-Versioning" 4 | resolve_s3 = true 5 | s3_prefix = "TA-S3-Versioning" 6 | region = "us-east-1" 7 | confirm_changeset = true 8 | capabilities = "CAPABILITY_IAM" 9 | image_repositories = [] 10 | -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/CloudWatchEventPattern-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": [ 3 | "aws.ec2" 4 | ], 5 | "detail-type": [ 6 | "EBS Snapshot Notification" 7 | ], 8 | "detail": { 9 | "event": [ 10 | "createSnapshot" 11 | ], 12 | "result": [ 13 | "succeeded" 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /AmazonEBSSnapshots/CloudwatchEventPattern: -------------------------------------------------------------------------------- 1 | { 2 | "source": [ 3 | "aws.trustedadvisor" 4 | ], 5 | "detail-type": [ 6 | "Trusted Advisor Check Item Refresh Notification" 7 | ], 8 | "detail": { 9 | "status": [ 10 | "ERROR" 11 | ], 12 | "check-name": [ 13 | "Amazon EBS Snapshots" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/CloudWatchEventPattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": [ 3 | "aws.trustedadvisor" 4 | ], 5 | "detail-type": [ 6 | "Trusted Advisor Check Item Refresh Notification" 7 | ], 8 | "detail": { 9 | "status": [ 10 | "WARN" 11 | ], 12 | "check-name": [ 13 | "Underutilized Amazon EBS Volumes" 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /LowUtilizationEC2Instances/CloudwatchEventPattern: -------------------------------------------------------------------------------- 1 | { 2 | "source": [ 3 | "aws.trustedadvisor" 4 | ], 5 | "detail-type": [ 6 | "Trusted Advisor Check Item Refresh Notification" 7 | ], 8 | "detail": { 9 | "status": [ 10 | "WARN" 11 | ], 12 | "check-name": [ 13 | "Low Utilization Amazon EC2 Instances" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/README.md: -------------------------------------------------------------------------------- 1 | # Eisenhower Matrix App (Prioritization Matrix) 2 | 3 | > [!NOTE] 4 | > **Important Note:** To use this solution please follow the instructions of the [**TA-WA Optimization - starter**](../tawa-optimization-starter) tool in this repository (including the Bonus section). 5 | 6 | ![aws_prioritization_matrix_app_sample_comments.png](../tawa-eisenhower-matrix-app/static/images/aws_prioritization_matrix_app_sample_comments.png) -------------------------------------------------------------------------------- /HighUtilizationEC2Instances/mockpayload.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "DetailType": "Trusted Advisor Check Item Refresh Notification", 4 | "Source": "awsmock.trustedadvisor", 5 | "Time": "2017-02-07T00:55:52Z", 6 | "Resources": [], 7 | "Detail": "{\"check-name\":\"High Utilization Amazon EC2 Instances\",\"check-item-detail\":{\"Instance ID\":\"i-0615f7b2706b23427\"},\"status\":\"WARN\",\"resource_id\":\"arn:aws:ec2:ap-southeast-2:23232324324:instance/i-0615f7b2706b23427\"}" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | *.DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent 21 | 22 | # Directories potentially created on remote AFP share 23 | .AppleDB 24 | .AppleDesktop 25 | Network Trash Folder 26 | Temporary Items 27 | .apdisk 28 | 29 | */.aws-sam 30 | */.python-version 31 | -------------------------------------------------------------------------------- /AmazonEBSSnapshots/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1477516473539", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "arn:aws:logs:*:*:*" 13 | }, 14 | { 15 | "Sid": "Stmt1477680111144", 16 | "Action": [ 17 | "ec2:CreateSnapshot", 18 | "ec2:DescribeTags" 19 | ], 20 | "Effect": "Allow", 21 | "Resource": "*" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /LowUtilizationEC2Instances/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1477516473539", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "arn:aws:logs:*:*:*" 13 | }, 14 | { 15 | "Sid": "Stmt1477680111144", 16 | "Action": [ 17 | "ec2:StopInstances", 18 | "ec2:DescribeTags" 19 | ], 20 | "Effect": "Allow", 21 | "Resource": "*" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /ExposedAccessKeys/stepbystep/mockpayload.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "DetailType": "Trusted Advisor Check Item Refresh Notification", 4 | "Source": "awsmock.trustedadvisor", 5 | "Time": "2018-11-22T11:38:24Z", 6 | "Resources": [], 7 | "Detail": "{ \"check-name\": \"Exposed Access Keys\", \"check-item-detail\": { \"Case ID\": \"12345678-1234-1234-abcd-1234567890ab\", \"Usage (USD per Day)\": \"0\", \"User Name (IAM or Root)\": \"delete-me\", \"Deadline\": \"1440453299248\", \"Access Key ID\": \"AKIAIX52WBZJNV4XNNAQ\", \"Time Updated\": \"1440021299248\", \"Fraud Type\": \"Exposed\", \"Location\": \"www.example.com\"}, \"status\": \"ERROR\", \"resource_id\": \"\", \"uuid\": \"aa12345f-55c7-498e-b7ac-123456789012\"}" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /UnassociatedElasticIPs/events/EventBridge_Example.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "5aafb434-a5d2-8e73-f0b0-6d21e533fb2e", 4 | "detail-type": "Trusted Advisor Check Item Refresh Notification", 5 | "source": "aws.trustedadvisor", 6 | "account": "123456789012", 7 | "time": "2023-08-28T19:39:15Z", 8 | "region": "us-east-1", 9 | "resources": [], 10 | "detail": { 11 | "check-name": "Unassociated Elastic IP Addresses", 12 | "check-item-detail": { 13 | "IP Address": "123.456.789.012", 14 | "Region": "us-east-1" 15 | }, 16 | "status": "WARN", 17 | "resource_id": "", 18 | "uuid": "22df99e5-9c89-4270-a647-34b8cd6adddc" 19 | } 20 | } -------------------------------------------------------------------------------- /AmazonEBSSnapshots/stepbystep/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1477516473539", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "arn:aws:logs:*:*:*" 13 | }, 14 | { 15 | "Sid": "Stmt1477680111144", 16 | "Action": [ 17 | "ec2:CreateSnapshot", 18 | "ec2:DescribeTags", 19 | "ec2:CreateTags" 20 | ], 21 | "Effect": "Allow", 22 | "Resource": "*" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /AmazonEBSSnapshots/stepbystep/mockpayload.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "DetailType": "Trusted Advisor Check Item Refresh Notification", 4 | "Source": "awsmock.trustedadvisor", 5 | "Time": "2018-11-05T14:46:52Z", 6 | "Resources": [], 7 | "Detail": "{ \"check-name\": \"Amazon EBS Snapshots\", \"check-item-detail\": { \"Status\": \"Red\", \"Volume Attachment\": \"\", \"Volume ID\": \"\", \"Snapshot Name\": null, \"Region\": \"\", \"Snapshot ID\": null, \"Volume Name\": null, \"Snapshot Age\": null, \"Reason\": \"No snapshot\" }, \"status\": \"ERROR\", \"resource_id\": \"arn:aws:ec2:::volume/\", \"uuid\": \"e880a3df-b2ca-47c3-8e10-1b5c445b0620\" }" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /S3BucketVersioning/LambdaTestEvent.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "16d7b3e0-e74b-bc3d-7196-58b604d997ae", 4 | "detail-type": "Trusted Advisor Check Item Refresh Notification", 5 | "source": "aws.trustedadvisor", 6 | "account": "083010608567", 7 | "time": "2018-09-26T17:25:07Z", 8 | "region": "us-east-1", 9 | "resources": [], 10 | "detail": { 11 | "check-name": "Amazon S3 Bucket Versioning", 12 | "check-item-detail": { 13 | "Status": "Yellow", 14 | "Versioning": "Not enabled", 15 | "MFA Delete Enabled": "No", 16 | "Region": "us-east-1", 17 | "Bucket Name": "tpbucket" 18 | }, 19 | "status": "WARN", 20 | "resource_id": "arn:aws:s3:::tpbucket", 21 | "uuid": "b485c38e-2eca-415c-a4b7-3a53fc5e2afe" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/snapshot_complete_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "458304a3-ff5b-eb83-2a12-84609327d4d6", 4 | "detail-type": "EBS Snapshot Notification", 5 | "source": "aws.ec2", 6 | "account": "123412341234", 7 | "time": "2019-04-03T15:31:50Z", 8 | "region": "us-east-1", 9 | "resources": [ 10 | "arn:aws:ec2::us-east-1:snapshot/snap-078ea06de73e621d2" 11 | ], 12 | "detail": { 13 | "event": "createSnapshot", 14 | "result": "succeeded", 15 | "cause": "", 16 | "request-id": "", 17 | "snapshot_id": "arn:aws:ec2::us-east-1:snapshot/snap-078ea06de73e621d2", 18 | "source": "arn:aws:ec2::us-east-1:volume/vol-0250ad32224ccc9f1", 19 | "startTime": "2019-04-03T15:13:06.000Z", 20 | "endTime": "2019-04-03T15:31:50.888Z" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/events/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", 4 | "detail-type": "Trusted Advisor Check Item Refresh Notification", 5 | "source": "aws.trustedadvisor", 6 | "account": "123456789012", 7 | "time": "2023-04-15T10:30:00Z", 8 | "region": "us-east-1", 9 | "resources": [], 10 | "detail": { 11 | "check-name": "Amazon S3 Bucket Lifecycle Configuration", 12 | "check-item-detail": { 13 | "Status": "Yellow", 14 | "Region": "us-east-1", 15 | "Bucket Name": "example-bucket", 16 | "Lifecycle Rule for Deleting Incomplete MPU": "No", 17 | "Days After Initiation": "" 18 | }, 19 | "status": "WARN", 20 | "resource_id": "arn:aws:s3:::example-bucket", 21 | "uuid": "c1cj39rr6v-1234-5678-9abc-def012345678" 22 | } 23 | } -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/samconfig.toml: -------------------------------------------------------------------------------- 1 | # More information about the configuration file can be found here: 2 | # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html 3 | version = 0.1 4 | 5 | [default] 6 | [default.global.parameters] 7 | stack_name = "ta-s3-incomplete-mpu-abort" 8 | 9 | [default.build.parameters] 10 | cached = true 11 | parallel = true 12 | 13 | [default.validate.parameters] 14 | lint = true 15 | 16 | [default.deploy.parameters] 17 | capabilities = "CAPABILITY_IAM" 18 | confirm_changeset = true 19 | resolve_s3 = true 20 | 21 | [default.package.parameters] 22 | resolve_s3 = true 23 | 24 | [default.sync.parameters] 25 | watch = true 26 | 27 | [default.local_start_api.parameters] 28 | warm_containers = "EAGER" 29 | 30 | [default.local_start_lambda.parameters] 31 | warm_containers = "EAGER" 32 | -------------------------------------------------------------------------------- /IAMPasswordPolicy/samconfig.toml: -------------------------------------------------------------------------------- 1 | # More information about the configuration file can be found here: 2 | # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html 3 | version = 0.1 4 | 5 | [default] 6 | [default.global.parameters] 7 | stack_name = "iam-password-policy" 8 | 9 | [default.build.parameters] 10 | cached = true 11 | parallel = true 12 | 13 | [default.validate.parameters] 14 | lint = true 15 | 16 | [default.deploy.parameters] 17 | capabilities = "CAPABILITY_IAM" 18 | confirm_changeset = true 19 | resolve_s3 = true 20 | s3_prefix = "iam-password-policy" 21 | region = "us-east-1" 22 | image_repositories = [] 23 | 24 | [default.package.parameters] 25 | resolve_s3 = true 26 | 27 | [default.sync.parameters] 28 | watch = true 29 | 30 | [default.local_start_api.parameters] 31 | warm_containers = "EAGER" 32 | 33 | [default.local_start_lambda.parameters] 34 | warm_containers = "EAGER" 35 | -------------------------------------------------------------------------------- /UnassociatedElasticIPs/samconfig.toml: -------------------------------------------------------------------------------- 1 | # More information about the configuration file can be found here: 2 | # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html 3 | version = 0.1 4 | 5 | [default] 6 | [default.global.parameters] 7 | stack_name = "unassociated-elastic-ips" 8 | 9 | [default.build.parameters] 10 | cached = true 11 | parallel = true 12 | 13 | [default.validate.parameters] 14 | lint = true 15 | 16 | [default.deploy.parameters] 17 | capabilities = "CAPABILITY_IAM" 18 | confirm_changeset = true 19 | resolve_s3 = true 20 | s3_prefix = "unassociated-elastic-ips" 21 | region = "us-east-1" 22 | image_repositories = [] 23 | 24 | [default.package.parameters] 25 | resolve_s3 = true 26 | 27 | [default.sync.parameters] 28 | watch = true 29 | 30 | [default.local_start_api.parameters] 31 | warm_containers = "EAGER" 32 | 33 | [default.local_start_lambda.parameters] 34 | warm_containers = "EAGER" 35 | -------------------------------------------------------------------------------- /AmazonRDSIdleDBInstances/sample_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "8dee56b0-b19f-441a-a05c-aa26e583c6c4", 4 | "detail-type": "Trusted Advisor Check Item Refresh Notification", 5 | "source": "aws.trustedadvisor", 6 | "account": "123456789012", 7 | "time": "2016-11-13T13:31:34Z", 8 | "region": "us-east-1", 9 | "resources": [], 10 | "detail": { 11 | "check-name": "Amazon RDS Idle DB Instances", 12 | "check-item-detail": { 13 | "Instance Type": "db.r3.large", 14 | "Region": "eu-west-1", 15 | "Days Since Last Connection": "14+", 16 | "Storage Provisioned (GB)": "1", 17 | "DB Instance Name": "sam-eu-west-1a", 18 | "Estimated Monthly Savings (On Demand)": "$230", 19 | "Multi-AZ": "No" 20 | }, 21 | "status": "WARN", 22 | "resource_id": "arn:aws:rds:eu-west-1:123456789012:db:sam-eu-west-1a", 23 | "uuid": "25437dc9-fb3e-4c88-af9e-030e39efeba5" 24 | } 25 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Trusted Advisor Tools 2 | 3 | ### Overview 4 | AWS Trusted Advisor provides real time guidance to help users provision their resources following AWS best practices. You can now create configurable, rule-based events for automated actions based on AWS Trusted Advisor’s library of best-practice checks using Amazon EventBridge. 5 | The sample functions provided help to automate Trusted Advisor best practices. 6 | 7 | ### Setup and Usage 8 | 9 | Setup and usage instructions are present for each tool in its respective directory:
10 | [Stop Amazon EC2 instances with low utilization](LowUtilizationEC2Instances/)
11 | [Create snapshots for EBS volumes with no recent backup](AmazonEBSSnapshots/)
12 | [Delete exposed IAM Keys and monitor usage](ExposedAccessKeys/)
13 | [Enable S3 bucket Versioning](S3BucketVersioning/)
14 | 15 | More information about Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 16 | 17 | ### License 18 | Trusted Advisor Tools is licensed under the Apache 2.0 License. 19 | -------------------------------------------------------------------------------- /S3BucketVersioning/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | 4 | Resources: 5 | SetBucketVersioningFunction: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | Runtime: python3.11 9 | CodeUri: lambda/ 10 | Handler: app.lambda_handler 11 | Events: 12 | CloudWatchEvent: 13 | Type: CloudWatchEvent 14 | Properties: 15 | Pattern: 16 | source: 17 | - "aws.trustedadvisor" 18 | detail-type: 19 | - "Trusted Advisor Check Item Refresh Notification" 20 | detail: 21 | status: 22 | - "WARN" 23 | check-name: 24 | - "Amazon S3 Bucket Versioning" 25 | State: "ENABLED" 26 | Policies: 27 | - Version: "2012-10-17" 28 | Statement: 29 | - Effect: "Allow" 30 | Action: 31 | - "s3:PutBucketVersioning" 32 | - "s3:GetBucketTagging" 33 | Resource: "*" -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-react-dash", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@cloudscape-design/components": "^3.0.416", 7 | "@cloudscape-design/global-styles": "^1.0.15", 8 | "lodash-es": "^4.17.21", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-grid-layout": "^1.4.2" 12 | }, 13 | "scripts": { 14 | "start": "vite", 15 | "build": "vite build", 16 | "test": "react-scripts test", 17 | "eject": "react-scripts eject", 18 | "dev": "vite" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | }, 38 | "devDependencies": { 39 | "@vitejs/plugin-react": "^4.2.1", 40 | "vite": "^5.4.20" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/src/utils/utilities.js: -------------------------------------------------------------------------------- 1 | export const replaceHtmlTags = (text) => { 2 | text = text.replace(/\s?(

)\s?/g, ""); 3 | text = text.replace(/\s?(<\/h4>)\s?/g, ""); 4 | text = text.replace(/\s?()\s?/g, "\r\n"); 5 | return text 6 | } 7 | 8 | export const copyToClipboard = (text) => { 9 | // get currently selected element 10 | const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false; 11 | const textArea = document.createElement('textarea'); 12 | textArea.textContent = text; 13 | document.body.appendChild(textArea); 14 | textArea.select(); 15 | try { 16 | document.execCommand('copy'); 17 | } catch (err) { 18 | /* tslint:disable-next-line:no-console */ 19 | console.error('Unable to copy:', err); 20 | } 21 | textArea.remove(); 22 | if (selected) { 23 | // if there was an element selected, re-select it 24 | document.getSelection().removeAllRanges(); 25 | document.getSelection().addRange(selected); 26 | } 27 | }; -------------------------------------------------------------------------------- /S3BucketVersioning/lambda/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | 3 | 4 | def lambda_handler(event, context) -> dict: 5 | bucket_name = event["detail"]["check-item-detail"]["Bucket Name"] 6 | 7 | # Connect to S3 8 | s3 = boto3.client("s3") 9 | 10 | # If bucket has tag "DisableVersioning", do not enable versioning 11 | try: 12 | tags = s3.get_bucket_tagging(Bucket=bucket_name) 13 | if "DisableVersioning" in [tag["Key"] for tag in tags["TagSet"]]: 14 | return { 15 | "statusCode": 200, 16 | "body": f"Bucket versioning is intentionally disabled for {bucket_name}. You can exclude this bucket from this check via the Trusted Advisor console", 17 | } 18 | except Exception: 19 | pass 20 | 21 | # Set bucket versioning to enabled 22 | try: 23 | s3.put_bucket_versioning( 24 | Bucket=bucket_name, VersioningConfiguration={"Status": "Enabled"} 25 | ) 26 | return { 27 | "statusCode": 200, 28 | "body": f"Bucket versioning enabled for {bucket_name}", 29 | } 30 | except Exception as e: 31 | print(e) 32 | return {"statusCode": 500, "body": f"Error: {e}"} 33 | -------------------------------------------------------------------------------- /IAMPasswordPolicy/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | 4 | Globals: 5 | Function: 6 | Timeout: 3 7 | MemorySize: 128 8 | Runtime: python3.11 9 | Architectures: 10 | - arm64 11 | 12 | Resources: 13 | SetPasswordPolicyFunction: 14 | Type: AWS::Serverless::Function 15 | Properties: 16 | CodeUri: set_password_policy/ 17 | Handler: app.lambda_handler 18 | Policies: 19 | - AWSLambdaExecute 20 | - Version: "2012-10-17" 21 | Statement: 22 | - Effect: Allow 23 | Action: 24 | - "iam:UpdateAccountPasswordPolicy" 25 | - "iam:GetAccountPasswordPolicy" 26 | Resource: "*" 27 | Events: 28 | CloudWatchEvent: 29 | Type: CloudWatchEvent 30 | Properties: 31 | Pattern: 32 | source: 33 | - "aws.trustedadvisor" 34 | detail-type: 35 | - "Trusted Advisor Check Item Refresh Notification" 36 | detail: 37 | status: 38 | - "WARN" 39 | - "ERROR" 40 | check-name: 41 | - "IAM Password Policy" 42 | State: "ENABLED" 43 | 44 | -------------------------------------------------------------------------------- /IAMPasswordPolicy/set_password_policy/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | 4 | 5 | def lambda_handler(event, context): 6 | # Connect to IAM 7 | iam = boto3.client("iam") 8 | 9 | # Get status of Trusted Advisor Check from EventBridge 10 | check_status = event["detail"]["status"] 11 | current_policy = {} 12 | if check_status == "WARN": 13 | # Get details of current password policy 14 | current_policy = iam.get_account_password_policy()["PasswordPolicy"] 15 | 16 | # Set password policy for IAM 17 | response = iam.update_account_password_policy( 18 | MinimumPasswordLength=current_policy.get("MinimumPasswordLength", 12), 19 | RequireSymbols=True, 20 | RequireNumbers=True, 21 | RequireUppercaseCharacters=True, 22 | RequireLowercaseCharacters=True, 23 | AllowUsersToChangePassword=current_policy.get( 24 | "AllowUsersToChangePassword", True 25 | ), 26 | PasswordReusePrevention=current_policy.get("PasswordReusePrevention", 12), 27 | MaxPasswordAge=current_policy.get("MaxPasswordAge", 90), 28 | HardExpiry=current_policy.get("HardExpiry", False), 29 | ) 30 | 31 | return { 32 | "statusCode": 200, 33 | "body": json.dumps( 34 | { 35 | "response": response, 36 | } 37 | ), 38 | } 39 | -------------------------------------------------------------------------------- /ExposedAccessKeys/lambda_functions/delete_access_key_pair.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | 3 | iam = boto3.client('iam') 4 | 5 | 6 | def lambda_handler(event, context): 7 | account_id = event['account'] 8 | time_discovered = event['time'] 9 | details = event['detail']['check-item-detail'] 10 | username = details['User Name (IAM or Root)'] 11 | access_key_id = details['Access Key ID'] 12 | exposed_location = details['Location'] 13 | print('Deleting exposed access key pair...') 14 | delete_exposed_key_pair(username, access_key_id) 15 | return { 16 | "account_id": account_id, 17 | "time_discovered": time_discovered, 18 | "username": username, 19 | "deleted_key": access_key_id, 20 | "exposed_location": exposed_location 21 | } 22 | 23 | 24 | def delete_exposed_key_pair(username, access_key_id): 25 | """ Deletes IAM access key pair identified by access key ID for specified user. 26 | 27 | Args: 28 | username (string): Username of IAM user to delete key pair for. 29 | access_key_id (string): IAM access key ID to identify key pair to delete. 30 | 31 | Returns: 32 | (None) 33 | 34 | """ 35 | try: 36 | iam.delete_access_key( 37 | UserName=username, 38 | AccessKeyId=access_key_id 39 | ) 40 | except Exception as e: 41 | print(e) 42 | print('Unable to delete access key "{}" for user "{}".'.format(access_key_id, username)) 43 | raise(e) 44 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | Priority Matrix 22 | 23 | 24 | 25 |
26 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /UnassociatedElasticIPs/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | unassociated-elastic-ips 5 | 6 | Sample SAM Template for unassociated-elastic-ips 7 | 8 | # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst 9 | Globals: 10 | Function: 11 | Architectures: 12 | - arm64 13 | Timeout: 3 14 | MemorySize: 128 15 | Runtime: python3.10 16 | 17 | Resources: 18 | 19 | RemoveElasticIPFunction: 20 | Type: "AWS::Serverless::Function" 21 | Properties: 22 | Description: Removes unassociated elastic IP unless the IP has the tag "" 23 | CodeUri: ./remove_elastic_IP/ 24 | Handler: app.lambda_handler 25 | Policies: 26 | - AWSLambdaExecute 27 | - Version: "2012-10-17" 28 | Statement: 29 | - Effect: Allow 30 | Action: 31 | - "ec2:ReleaseAddress" 32 | - "ec2:DescribeAddresses" 33 | - "ec2:DescribeTags" 34 | Resource: "*" 35 | Events: 36 | CloudWatchEvent: 37 | Type: CloudWatchEvent 38 | Properties: 39 | Pattern: 40 | source: 41 | - "aws.trustedadvisor" 42 | detail-type: 43 | - "Trusted Advisor Check Item Refresh Notification" 44 | detail: 45 | status: 46 | - "WARN" 47 | check-name: 48 | - "Unassociated Elastic IP Addresses" 49 | State: "ENABLED" 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /UnassociatedElasticIPs/remove_elastic_IP/app.py: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # This script is used to release an Elastic IP address 3 | # if the ElasticIP address does not have a 4 | # TrustedAdvisorAutomate tag set to "false". After testing, 5 | # modify the script to remove the DryRun=True parameter. 6 | ############################################################ 7 | 8 | import boto3 9 | 10 | DRY_RUN = True 11 | 12 | 13 | def lambda_handler(event, context): 14 | region = event["detail"]["check-item-detail"]["Region"] 15 | eip = event["detail"]["check-item-detail"]["IP Address"] 16 | 17 | ec2 = boto3.client("ec2", region_name=region) 18 | allocation_id = ec2.describe_addresses(PublicIps=[eip])["Addresses"][0][ 19 | "AllocationId" 20 | ] 21 | # Grab tags for Elastic IP 22 | tags = ec2.describe_tags( 23 | Filters=[{"Name": "resource-id", "Values": [allocation_id]}] 24 | )["Tags"] 25 | 26 | # Release the Elastic IP if the TrustedAdvisorAutomate tag is set to True 27 | if tags: 28 | for tag in tags: 29 | if tag["Key"] == "TrustedAdvisorAutomate": 30 | if tag["Value"].lower() == "false": 31 | return { 32 | "statusCode": 200, 33 | "body": f"Elastic IP {eip} has not been released.", 34 | } 35 | 36 | # DryRun=True will not actually release the Elastic IP 37 | # but will return the result of the dry run 38 | try: 39 | result = ec2.release_address(DryRun=DRY_RUN, AllocationId=allocation_id) 40 | return { 41 | "statusCode": 200, 42 | "body": f"Elastic IP {eip} has been released. {result}", 43 | } 44 | except Exception as e: 45 | print(e) 46 | raise (e) 47 | -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/event_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "0fd296eb-ccf3-55d6-e4ea-c2dda37276bc", 4 | "detail-type": "Trusted Advisor Check Item Refresh Notification", 5 | "source": "aws.trustedadvisor", 6 | "account": "123412341234", 7 | "time": "2019-04-02T18:24:41Z", 8 | "region": "us-east-1", 9 | "resources": [], 10 | "detail": { 11 | "check-name": "Underutilized Amazon EBS Volumes", 12 | "check-item-detail": { 13 | "Volume Type": "General purpose(SSD)", 14 | "Volume ID": "vol-0c5a286715785d467", 15 | "Volume Size": "1000", 16 | "Snapshot Name": null, 17 | "Region": "us-east-1", 18 | "Snapshot ID": null, 19 | "Monthly Storage Cost": "$100.00", 20 | "Volume Name": null, 21 | "Snapshot Age": null 22 | }, 23 | "status": "WARN", 24 | "resource_id": "arn:aws:ec2:us-east-1:123412341234:volume/vol-0c5a286715785d467", 25 | "uuid": "177de169-e24c-4221-8f9b-48c738a139de" 26 | } 27 | } 28 | 29 | { 30 | "version": "0", 31 | "id": "0fd296eb-ccf3-55d6-e4ea-c2dda37276bc", 32 | "detail-type": "Trusted Advisor Check Item Refresh Notification", 33 | "source": "aws.trustedadvisor", 34 | "account": "123412341234", 35 | "time": "2019-04-02T18:24:41Z", 36 | "region": "us-east-2", 37 | "resources": [], 38 | "detail": { 39 | "check-name": "Underutilized Amazon EBS Volumes", 40 | "check-item-detail": { 41 | "Volume Type": "General purpose(SSD)", 42 | "Volume ID": "vol-0d84605d40b6932fc", 43 | "Volume Size": "1000", 44 | "Snapshot Name": null, 45 | "Region": "us-east-2", 46 | "Snapshot ID": null, 47 | "Monthly Storage Cost": "$100.00", 48 | "Volume Name": null, 49 | "Snapshot Age": null 50 | }, 51 | "status": "WARN", 52 | "resource_id": "arn:aws:ec2:us-east-2:123412341234:volume/vol-0d84605d40b6932fc", 53 | "uuid": "177de169-e24c-4221-8f9b-48c738a139de" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | ta-s3-incomplete-mpu-abort 5 | 6 | SAM Template for ta-s3-incomplete-mpu-abort to apply lifecycle policies based on Trusted Advisor notifications 7 | 8 | Globals: 9 | Function: 10 | Timeout: 60 # Increased timeout to allow for cross-account operations 11 | MemorySize: 128 12 | 13 | Resources: 14 | ApplyLifecycleFunction: 15 | Type: AWS::Serverless::Function 16 | Properties: 17 | CodeUri: apply_lifecycle_function 18 | Handler: apply_lifecycle.app.lambda_handler 19 | Runtime: python3.12 20 | Architectures: 21 | - arm64 22 | Events: 23 | TrustedAdvisorNotification: 24 | Type: CloudWatchEvent 25 | Properties: 26 | Pattern: 27 | source: 28 | - aws.trustedadvisor 29 | detail-type: 30 | - Trusted Advisor Check Item Refresh Notification 31 | detail: 32 | status: 33 | - WARN 34 | check-name: 35 | - Amazon S3 Bucket Lifecycle Configuration 36 | Policies: 37 | - AWSLambdaBasicExecutionRole 38 | - Version: '2012-10-17' 39 | Statement: 40 | - Effect: Allow 41 | Action: 42 | - sts:AssumeRole 43 | Resource: 'arn:aws:iam::*:role/CrossAccountS3AccessRole' 44 | 45 | Outputs: 46 | ApplyLifecycleFunction: 47 | Description: "S3 incomplete Multi-part uploads fix (Apply Lifecycle Function) ARN" 48 | Value: !GetAtt ApplyLifecycleFunction.Arn 49 | ApplyLifecycleFunctionIamRole: 50 | Description: "Implicit IAM Role created for Apply Lifecycle function" 51 | Value: !GetAtt ApplyLifecycleFunctionRole.Arn -------------------------------------------------------------------------------- /TA-Integrations/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Trusted Advisor 3rd-Party Integration Solutions 2 | 3 | ##### Contributors: AWS Internal TA Champions Team 4 | 5 | ### Description/Use-case 6 | Use these solutions to integrate AWS Trusted Advisor with 3rd party tools, for example Slack. These solutions provide sample code for AWS customers to review, test and implement, as needed. 7 | 8 | Use any of these sample automated solutions to integrate and get notified for AWS Trusted Advisor findings to your respecive 3rd party tools. Customers can develop their own to integrate with additional tools of their choice. 9 | 10 | High priority Trusted Advisor checks require further investigation as they help you secure and optimize your account to align with AWS best practices. Notifications are classified by risk category (Security, Fault Tolerance, Performance, Cost and Service Limits) and sent to your preferred monitoring or DevOps tools at a preconfigured interval. Configure the notification interval as a scheduled event rule in Amazon EventBridge. Modify the included python script to customize the solution further to meet your requirements. 11 | 12 | ### Solution Overview 13 | Deploying this solution automates the process of checking, and delivery of specific alerts from Trusted Advisor to with your preferred 3rd paty tools. 14 | 15 | ### How it works 16 | 17 | Review the README files specific to the solution in the respective solutions folder. 18 | 19 | More information about AWS Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 20 | 21 | ### Deploy the solution 22 | 23 | Review the README files specific to the solution in the solutions folder. 24 | 25 | ### Security 26 | 27 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 28 | 29 | ### License 30 | 31 | This library is licensed under the MIT-0 License. See the LICENSE file. 32 | 33 | -------------------------------------------------------------------------------- /TA-Responder/cfn_templates/tatools-ta-responder-infra-setup.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: Trusted Advisor Responder (infra setup) - Only to be deploy if Security Hub and AWS Config is NOT already enable in a particular account and region. This templates creates the necessary resources to enable Security Hub and AWS Config in the region where the stack is deployed. 3 | 4 | Parameters: 5 | EnvironmentName: 6 | Description: Environment name that is prefixed to resource names 7 | Type: String 8 | Default: taresponder-infra-setup 9 | 10 | Resources: 11 | ConfigBucket: 12 | Type: AWS::S3::Bucket 13 | DeletionPolicy: Retain 14 | Properties: 15 | BucketName: !Sub 'config-bucket-${EnvironmentName}-${AWS::AccountId}-${AWS::Region}' 16 | 17 | ConfigRecorder: 18 | DependsOn: ConfigBucket 19 | Type: AWS::Config::ConfigurationRecorder 20 | Properties: 21 | Name: !Sub '${EnvironmentName}-${AWS::Region}' 22 | RecordingGroup: 23 | AllSupported: true 24 | RoleARN: 25 | Fn::GetAtt: 26 | - ConfigRole 27 | - Arn 28 | 29 | DeliveryChannel: 30 | DependsOn: ConfigBucket 31 | Type: AWS::Config::DeliveryChannel 32 | Properties: 33 | ConfigSnapshotDeliveryProperties: 34 | DeliveryFrequency: "One_Hour" 35 | S3BucketName: !Ref ConfigBucket 36 | 37 | ConfigRole: 38 | Type: AWS::IAM::Role 39 | Properties: 40 | RoleName: ConfigRecorderRole 41 | AssumeRolePolicyDocument: 42 | Version: "2012-10-17" 43 | Statement: 44 | - Effect: Allow 45 | Principal: 46 | Service: config.amazonaws.com 47 | Action: sts:AssumeRole 48 | ManagedPolicyArns: 49 | - 'arn:aws:iam::aws:policy/service-role/AWS_ConfigRole' 50 | - 'arn:aws:iam::aws:policy/AmazonS3FullAccess' 51 | 52 | MySecurityHub: 53 | DependsOn: ConfigRecorder 54 | Type: AWS::SecurityHub::Hub 55 | Properties: 56 | EnableDefaultStandards: true 57 | ControlFindingGenerator: 'SECURITY_CONTROL' 58 | -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/TAUnderutilizedEBS.drawio: -------------------------------------------------------------------------------- 1 | 7Vpbc+I2FP41PPQhjO+XxwCbTWd62Zls28eMsAVWI1uuLAfYX18JS2BLZpckhkBamGHsI1m2zvedqxm503z9mYIy+5WkEI8cK12P3NnIcWLX4r9CsGkEgec1giVFaSOy94IH9A1KobxuWaMUVp2JjBDMUNkVJqQoYMI6MkApWXWnLQju3rUES2gIHhKATelfKGWZlNqWtR+4h2iZyVtHvhzIgZosBVUGUrJqidxPI3dKCWHNUb6eQix0p/TSXHd3YHT3YBQW7JgL0P3fwcT7mpUP8e3c+6dY3aT1jd+s8gxwLTcsH5ZtlAZIzTAq4HSnYGvkTlJQZTCVJ8+QMsRV9guYQ/yFVIghUvCxOWGM5K0JtxgtxQAjJZcCeZbwDUDKBRnLMT+3+SFXVinun6+XglZjsKrcMQb5PAWPi7pImjtMFgjjKcGEbh/VvfMj3/W4nF+UIr6sGitIwVebmCqTWhQPCNctkVThZ0hyyOiGT5GjXuiPpdIko51ILrLa8yOIG1HWYkYoVQskI5e7tfeg8QOJ2wswjF+J4YLsFcSVx7934q6G8vhYYN26btgamyEKJQhcuVTsWwfDCUPbDgSWjJIn2BpZbD9nYJHYoXQptqPOpVbELUFVNupYoLV4jj7aeWMKK1LTBP6cbDnHT5uj7iyIQcUf73GOSfL0WDFCB2JcaMddvoWRwTcl6vAtOhHflGs+GeGktR7HJzn5OqhUFaCsMuHxB2GGCq4qtloGMfzgnMSwDWJ8pXXFuD4c6zZ9RhVHzAmw2P5cHC3F0W+EoQUHY4uRMWowi+uGdaHhBom+gfkOipKggm335k9G/kyAUzNOgga8Fs4YLthBMlQlSFCx/CpOZjfeMIjZYahFD7sneuySizZqCuvhUXPexZwnU9v1r9yc4TNf+rH5nYNqO3EIlkSRxpI4fmeX7/7PkQvjiOFJXDt8Z5IEBkkeVLxzrCnJSwwZfHkA+K+FBM+ylKQdFJyeoOCdLChEPzZ4WKS3osIW5sOT3wolXRAOKqdJp48ISQzQJWTfmSjVBNNOEW/qup0R9ahRyShP4hl67pb+fbqVd/giaLWHMtLSMR2dZt/yonalrq1ja3mdH2sLNXoxFtoivdv1G8A3K0qF3BuNTve2up3lKE3F9RMs3PgEJE9LSuoi7SseTXp9n8m6Re56RvKRR+22TJ+lWmPbUT5OInPjvo04agpZLCp4EiyV3bax/Imf/1GkkNYMYe4DRXr+J8F13uOcZ9xlJ9sM/jRu+NoZ4VvDMmLTe8EJ+XFEMX86R+8f6efVghfi6AOn66C94JWePgi1hezzenrHzOsd4R2mGUyeRNHOGEiyXOBq2P89Ek2uzQdyDDtbeLNjuLHGVhx0+3b2xYcKs31jQPnWRvKd63mR87JG8oEq8KM2khNM6pRRgPAwBYYXBFqB0dMnPGuh6HgG0VzhdVrVIstELgJyAUwxr8reQvBQznKBAUz59QsJYKHVjTuuXmEcG8DCoLvQrko9VwA74gXm6dCPrxP9SEffeiX6u5fd6tWUf2b0zY7TGdFXTdEr61I4sdZdCF+JvutpC53b9kMDfU8LI/umo/U+L57eI4tVRjFEFmtbltcB2RuEg+crb80+pi84MoOSFk2icb2JRXxRvkXrgIa6R3htBzTQfdSpXcup/1PT/76Lf7xJfOXvu/LhKpfQ7vqeWLq2HxQuqvAevHBRrLzwZtll+YRASxJ9vcd1dK2hpa1G1+3ETsE1+yOBiCXbPGJzKJb8viog/UDpxc4GBuieO1GgvcMehHE3A6QX/HT/19xm+v7/ze6nfwE= -------------------------------------------------------------------------------- /AmazonEBSSnapshots/stepbystep/LambdaFunction.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | 4 | 5 | def create_snapshot(volume_id, region): 6 | ec2 = boto3.client('ec2', region_name=region) 7 | # the function will only consider volumes with the tag 'ta-ebs' 8 | allowed_tag = 'ta-ebs' 9 | 10 | describe_tags_params = [ 11 | { 12 | 'Name' : 'resource-id', 13 | 'Values': [volume_id], 14 | }, 15 | { 16 | 'Name': 'key', 17 | 'Values': [allowed_tag] 18 | } 19 | ] 20 | 21 | # we check if the volume has the tag 'ta-ebs' 22 | print ('Checking tags for volume: %s' % volume_id) 23 | describe_response = ec2.describe_tags(Filters=describe_tags_params) 24 | print (describe_response) 25 | 26 | if len(describe_response['Tags']) >0: 27 | 28 | snapshot_description = 'Automated Snapshot by TA automation for volume %s' % volume_id 29 | response = ec2.create_snapshot(Description=snapshot_description, VolumeId=volume_id ) 30 | print (response) 31 | 32 | # tag the volume with the tag used by Data Lifecycle Manager 33 | resources=[ 34 | volume_id 35 | ] 36 | 37 | tags = [ 38 | { 39 | 'Key': 'ta-snapshot', 40 | 'Value': 'true' 41 | } 42 | ] 43 | 44 | response = ec2.create_tags(Resources=resources, Tags=tags) 45 | print (response) 46 | 47 | print ('Snapshot initiated and volume tagged for snapshot lifecycle management') 48 | else: 49 | print ('Volume %s in region %s did not match tag, skipping.' % (volume_id, region)) 50 | 51 | def lambda_handler(event, context): 52 | 53 | print(json.dumps(event)) 54 | 55 | check_name = event['detail']['check-name']; 56 | region = event['detail']["check-item-detail"]["Region"]; 57 | volume_id = event['detail']['check-item-detail']['Volume ID'] 58 | 59 | ta_success_msg = 'Successfully got details from Trusted Advisor check, %s and executed automated action.' % check_name 60 | print (ta_success_msg) 61 | create_snapshot(volume_id, region) 62 | 63 | return None -------------------------------------------------------------------------------- /AmazonEBSSnapshots/README.md: -------------------------------------------------------------------------------- 1 | ## Amazon EBS Snapshots Fault Tolerance 2 | 3 | ### Trusted Advisor Check Description 4 | Checks the age of the snapshots for your Amazon Elastic Block Store (Amazon EBS) volumes (available or in-use). Even though Amazon EBS volumes are replicated, failures can occur. Snapshots are persisted to Amazon Simple Storage Service (Amazon S3) for durable storage and point-in-time recovery. 5 | 6 | ### Setup and Usage 7 | You can automatically create EBS snapshots for volumes that do not have a recent backup as recommended by Trusted Advisor using Amazon Cloudwatch events and AWS Lambda using the following instructions: 8 | 9 | 1. Create an Amazon IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 10 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 11 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 12 | 13 | 2. Create a Lambda Node javascript function using the [sample](LambdaFunction.js) provided and choose the IAM role created in step 1. Make sure to set the appropriate tags and region per your requirements in configuration section of the Lambda function. 14 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 15 | 16 | 3. Create a Cloudwatch event rule to trigger the Lambda function created in step 2 matching the ERROR status and the Amazon EBS Snapshot Trusted Advisor check. An example of this is highlighted in the sample [Cloudwatch Event Pattern](CloudwatchEventPattern). 17 | Documentation on to create a Trusted Advisor Cloudwatch events rule is available here: http://docs.aws.amazon.com/awssupport/latest/user/cloudwatch-events-ta.html 18 | 19 | More information about Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 20 | 21 | Please note that this is a just an example of how to setup automation with Trusted Advisor, Cloudwatch and Lambda. We recommend testing it and tailoring to your environment before using in your production envirnment. 22 | 23 | -------------------------------------------------------------------------------- /AmazonRDSIdleDBInstances/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Globals: 4 | Function: 5 | Architectures: 6 | - "arm64" 7 | Runtime: python3.11 8 | 9 | 10 | Parameters: 11 | MINAGE: 12 | Type: Number 13 | Default: "14" 14 | Description: "Minimum time since last connection in days. Valid values are between 7-14" 15 | MinValue: "7" 16 | MaxValue: "14" 17 | ConstraintDescription: "Must be between 7 and 14" 18 | TERMINATIONMETHOD: 19 | Type: String 20 | Default: "delete" 21 | Description: "Method to use to terminate idle RDS instances. Valid values are 'delete' or 'stop'" 22 | AllowedValues: 23 | - "delete" 24 | - "stop" 25 | ConstraintDescription: "Must be either 'delete' or 'stop'" 26 | SNSTOPICARN: 27 | Type: String 28 | Description: "SNS Topic ARN to send notifications to. If left blank, this will be skipped" 29 | Default: "" 30 | ConstraintDescription: "Must be a valid SNS Topic ARN" 31 | 32 | 33 | 34 | Resources: 35 | RemoveIdleRDSDatabases: 36 | Type: AWS::Serverless::Function 37 | Properties: 38 | CodeUri: src/ 39 | Handler: remove_idle_rds_databases.lambda_handler 40 | Environment: 41 | Variables: 42 | SNS_TOPIC_ARN: !Ref SNSTOPICARN 43 | MIN_AGE: !Ref MINAGE 44 | TERMINATION_METHOD: !Ref TERMINATIONMETHOD 45 | ACCOUNT_ID: !Ref AWS::AccountId 46 | Policies: 47 | - Statement: 48 | - Sid: AllowRDSTermination 49 | Effect: Allow 50 | Action: 51 | - rds:StopDBInstance 52 | - rds:DeleteDBInstance 53 | Resource: "*" 54 | - Sid: AllowSNSPublish 55 | Effect: Allow 56 | Action: sns:Publish 57 | Resource: !Ref SNSTOPICARN 58 | Events: 59 | CloudWatchEvent: 60 | Type: CloudWatchEvent 61 | Properties: 62 | Pattern: 63 | source: 64 | - aws.trustedadvisor 65 | detail-type: 66 | - "Trusted Advisor Check Item Refresh Notification" 67 | detail: 68 | check-name: 69 | - "Amazon RDS Idle DB Instances" 70 | check-status: 71 | - "WARN" 72 | -------------------------------------------------------------------------------- /IAMPasswordPolicy/README.md: -------------------------------------------------------------------------------- 1 | ## IAM Password Policy 2 | 3 | ### Trusted Advisor Check Description 4 | 5 | 6 | Checks the password policy for your account and warns when a password policy is not enabled, or if password content requirements have not been enabled. 7 | 8 | Password content requirements increase the overall security of your AWS environment by enforcing the creation of strong user passwords. When you create or change a password policy, the change is enforced immediately for new users but does not require existing users to change their passwords. 9 | 10 | ### Alert Criteria 11 | 12 | * Yellow/Warning: A password policy is enabled, but at least one content requirement is not enabled. 13 | 14 | * Red/Error: No password policy is enabled. 15 | 16 | --- 17 | 18 | ### About the Architecture 19 | Amazon EventBridge captures the Trusted Advisor Check Item Refresh Notification for IAM Password Policy. An AWS Lambda function is triggered via Amazon EventBridge to update the required fields flagged by Trusted Advisor or create a password policy if one doesn't exist. 20 | 21 | 22 | ### Installation 23 | 24 | #### AWS SAM 25 | If you havent already, [install AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html). Ensure you are in the `IAMPasswordPolicy` folder then `build` and `deploy` your package 26 | 27 | ```bash 28 | cd IAMPasswordPolicy 29 | sam build && sam deploy -g 30 | ``` 31 | 32 | #### Cloudformation - CLI 33 | ```bash 34 | S3BUCKET=[REPLACE_WITH_YOUR_BUCKET] 35 | ``` 36 | 37 | Ensure you are in the `IAMPasswordPolicy` folder and use the `aws cloudformation package` utility 38 | 39 | ```bash 40 | cd IAMPasswordPolicy 41 | 42 | aws cloudformation package --region us-east-1 --s3-bucket $S3BUCKET --template template.yaml --output-template-file template.output.yaml 43 | ``` 44 | Last, deploy the stack with the resulting yaml (`template.output.yaml`) through the CloudFormation Console or command line: 45 | 46 | ```bash 47 | aws cloudformation deploy --region us-east-1 --template-file template.output.yaml --stack-name iam-password-policy --capabilities CAPABILITY_NAMED_IAM 48 | ``` 49 | 50 | 51 | More information about Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 52 | 53 | Please note that this is a just an example of how to setup automation with Trusted Advisor, Cloudwatch and Lambda. We recommend testing it and tailoring to your environment before using in your production envirnment. 54 | 55 | -------------------------------------------------------------------------------- /TA-Responder/lambda_handlers/SSMAutomationExecutionEventsHandler.py: -------------------------------------------------------------------------------- 1 | """SSMAutomationExecutionEventsHandler - Lambda function for handling events sourced from the EventBridge rule related to SSM Automation Execution completions""" 2 | 3 | import logging 4 | 5 | import boto3 6 | 7 | logging.getLogger().setLevel(logging.INFO) 8 | logger = logging.getLogger() 9 | 10 | dynamodb_client = boto3.resource("dynamodb") 11 | ssm_client = boto3.client("ssm") 12 | 13 | table_name = "AutomationExecutionTrackerTable" 14 | 15 | 16 | def lambda_handler(event, context): 17 | # Get the detail from the event 18 | detail = event["detail"] 19 | execution_id = detail["ExecutionId"] 20 | automation_execution_document_name = detail["Definition"] 21 | automation_execution_status = detail["Status"] 22 | 23 | # Get the item from the DynamoDB table 24 | table = dynamodb_client.Table(table_name) 25 | response = table.get_item(Key={"automationExecutionId": execution_id}) 26 | item = response.get("Item") 27 | 28 | if item: 29 | ops_item_id = item["opsItemId"] 30 | region = item["region"] 31 | 32 | if automation_execution_status == "Success": 33 | # Update the OpsItem with Resolved status 34 | ssm_client.update_ops_item( 35 | OpsItemId=ops_item_id, 36 | Status="Resolved", 37 | OperationalData={ 38 | "trustedAdvisorCheckAutoRemediation": { 39 | "Type": "String", 40 | "Value": f"DocumentName: {automation_execution_document_name}, ExecutionId: {execution_id}, Status: {automation_execution_status}, Region: {region}", 41 | } 42 | }, 43 | ) 44 | else: 45 | # Update the OpsItem with the execution status 46 | ssm_client.update_ops_item( 47 | OpsItemId=ops_item_id, 48 | OperationalData={ 49 | "trustedAdvisorCheckAutoRemediation": { 50 | "Type": "String", 51 | "Value": f"DocumentName: {automation_execution_document_name}, ExecutionId: {execution_id}, Status: {automation_execution_status}, Region: {region}", 52 | } 53 | }, 54 | ) 55 | # Delete the item from the DynamoDB table 56 | table.delete_item(Key={"automationExecutionId": execution_id}) 57 | logger.info( 58 | f"Deleted item with automationExecutionId {execution_id} from DynamoDB table" 59 | ) 60 | 61 | return 62 | -------------------------------------------------------------------------------- /AmazonRDSIdleDBInstances/README.md: -------------------------------------------------------------------------------- 1 | ## Amazon RDS Idle DB Instances 2 | 3 | ### Trusted Advisor Check Description 4 | Checks the configuration of your Amazon Relational Database Service (Amazon RDS) for any database (DB) instances that appear to be idle. 5 | If a DB instance has not had a connection for a prolonged period of time, you can delete the instance to reduce costs. A DB instance is considered idle if the instance hasn't had a connection in the past 7 days. If persistent storage is needed for data on the instance, you can use lower-cost options such as taking and retaining a DB snapshot. Manually created DB snapshots are retained until you delete them. 6 | 7 | ![ArchitectureDiagram](../images/AmazonRDSIdleDBInstances-TA.png) 8 | ### About the Architecture 9 | Amazon EventBridge captures the Trusted Advisor Check Item Refresh Notification for Amazon RDS Idle DB Instances. An AWS Lambda function is triggered via Amazon EventBridge to either stop or delete the flagged RDS instance depending on your selection in the CloudFormation template. If you choose to delete the instance, the application will also take a final snapshot. You are also notified via SNS of any RDS databases that are terminated. 10 | 11 | ### Parameters 12 | - MINAGE: Trusted Advisor flags databases that have not had a connection in the last 7 days. You can choose a value between 7-14 to only affect databases that haven't been connected to in at least a certain number of days. Example: If you set this value to 10, only databases that haven't had a connection in over 10 days will be terminated. 13 | - SNSTOPICARN: The ARN of the SNS topic to send notifications to. If this is left blank, no SNS notifiation will be sent. 14 | - TERMINATIONMETHOD: Choose to either stop or delete flagged resources. If you choose to delete the databse, a final snapshot will be taken and its identifier will be included in the SNS topic email notification. 15 | 16 | 17 | ### Installation 18 | 19 | #### AWS SAM 20 | If you havent already, [install AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html). Ensure you are in the `AmazonRDSIdleDBInstances` folder then `build` and `deploy` your package 21 | 22 | ```bash 23 | cd AmazonRDSIdleDBInstances 24 | sam build && sam deploy -g 25 | ``` 26 | 27 | 28 | More information about Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 29 | 30 | Please note that this is a just an example of how to setup automation with Trusted Advisor, Cloudwatch and Lambda. We recommend testing it and tailoring to your environment before using in your production envirnment. 31 | 32 | -------------------------------------------------------------------------------- /ExposedAccessKeys/terraform/main/src/ta-12Fnkpl8Y5-deactivateiamkey.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | 3 | #iam = boto3.client('iam') 4 | 5 | def lambda_handler(event, context): 6 | event_account_id = event['account'] 7 | time_discovered = event['time'] 8 | details = event['detail']['check-item-detail'] 9 | username = details['User Name (IAM or Root)'] 10 | access_key_id = details['Access Key ID'] 11 | exposed_location = details['Location'] 12 | current_account_id = context.invoked_function_arn.split(":")[4] 13 | 14 | print('Deactivating exposed access key pair in the current account...') 15 | deactivate_exposed_key_pair(event_account_id, current_account_id, username, access_key_id) 16 | return { 17 | "account_id": event_account_id, 18 | "time_discovered": time_discovered, 19 | "username": username, 20 | "deactivated_key": access_key_id, 21 | "exposed_location": exposed_location 22 | } 23 | 24 | def deactivate_exposed_key_pair(event_account_id, current_account_id, username, access_key_id): 25 | """ Deactivates IAM access key pair identified by access key ID for specified user. 26 | Args: 27 | current_account_id (string): The ID of the current AWS account. 28 | event_account_id (string): The ID of the AWS account where the event occurred. 29 | username (string): Username of IAM user to deactivate key pair for. 30 | access_key_id (string): IAM access key ID to identify key pair to deactivate. 31 | Returns: 32 | (None) 33 | """ 34 | if event_account_id != current_account_id: 35 | print("Assuming role in another account") 36 | sts = boto3.client("sts") 37 | response = sts.assume_role( 38 | RoleArn="arn:aws:iam::"+ event_account_id +":role/ta-12Fnkpl8Y5-crossaccount-iam-role", 39 | RoleSessionName="ta-12Fnkpl8Y5-cloudtraileventlookup-session" 40 | ) 41 | session = boto3.Session( 42 | aws_access_key_id=response['Credentials']['AccessKeyId'], 43 | aws_secret_access_key=response['Credentials']['SecretAccessKey'], 44 | aws_session_token=response['Credentials']['SessionToken'] 45 | ) 46 | else: 47 | print("Using current account") 48 | session = boto3.Session() 49 | 50 | iam = session.client("iam") 51 | 52 | try: 53 | iam.update_access_key( 54 | UserName=username, 55 | Status='Inactive', 56 | AccessKeyId=access_key_id 57 | ) 58 | except Exception as e: 59 | print(e) 60 | print('Unable to deactivate access key "{}" for user "{}".'.format(access_key_id, username)) 61 | raise(e) -------------------------------------------------------------------------------- /UnassociatedElasticIPs/README.md: -------------------------------------------------------------------------------- 1 | ## Unassociated Elastic IP Addresses 2 | 3 | ### Trusted Advisor Check Description 4 | Checks for Elastic IP addresses (EIPs) that are not associated with a running Amazon Elastic Compute Cloud (Amazon EC2) instance. 5 | 6 | EIPs are static IP addresses designed for dynamic cloud computing. Unlike traditional static IP addresses, EIPs mask the failure of an instance or Availability Zone by remapping a public IP address to another instance in your account. A nominal charge is imposed for an EIP that is not associated with a running instance. 7 | 8 | 9 | ### About the Architecture 10 | Amazon EventBridge captures the Trusted Advisor Check Item Refresh Notification for Unassociated Elastic IPs. An AWS Lambda function is triggered via Amazon EventBridge to release the Elastic IP address. The Lambda function will ignore Elastic IP addresses that are tagged with the key "TrustedAdvisorAutomate" with a value of "false". 11 | 12 | ![screenshot for instruction](images/Architecture.png) 13 | 14 | ### Important Note 15 | This script has the DryRun flag set to True by default for the [ec2.release_address()](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/release_address.html) API call. When you are ready to proceed after tagging resources you intend to keep, you can change this value to False 16 | 17 | 18 | ### Installation 19 | 20 | #### AWS SAM 21 | If you havent already, [install AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html). Ensure you are in the `UnassociatedElasticIPs` folder then `build` and `deploy` your package 22 | 23 | ```bash 24 | cd UnassociatedElasticIPs 25 | sam build && sam deploy -g 26 | ``` 27 | 28 | #### Cloudformation - CLI 29 | ```bash 30 | S3BUCKET=[REPLACE_WITH_YOUR_BUCKET] 31 | ``` 32 | 33 | Ensure you are in the `UnassociatedElasticIPs` folder and use the `aws cloudformation package` utility 34 | 35 | ```bash 36 | cd UnassociatedElasticIPs 37 | 38 | aws cloudformation package --region us-east-1 --s3-bucket $S3BUCKET --template template.yaml --output-template-file template.output.yaml 39 | ``` 40 | Last, deploy the stack with the resulting yaml (`template.output.yaml`) through the CloudFormation Console or command line: 41 | 42 | ```bash 43 | aws cloudformation deploy --region us-east-1 --template-file exposed_access_keys.output.yaml --stack-name unassociated-elastic-ips --capabilities CAPABILITY_NAMED_IAM 44 | ``` 45 | 46 | 47 | More information about Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 48 | 49 | Please note that this is a just an example of how to setup automation with Trusted Advisor, Cloudwatch and Lambda. We recommend testing it and tailoring to your environment before using in your production envirnment. 50 | 51 | -------------------------------------------------------------------------------- /LowUtilizationEC2Instances/README.md: -------------------------------------------------------------------------------- 1 | ## Low Utilization Amazon EC2 Instances 2 | 3 | ### Trusted Advisor Check Description 4 | Checks the Amazon Elastic Compute Cloud (Amazon EC2) instances that were running at any time during the last 14 days and alerts you if the daily CPU utilization was 10% or less and network I/O was 5 MB or less on 4 or more days. 5 | 6 | ### Setup and Usage 7 | You can automatically stop EC2 instances that have low utilization recommended by Trusted Advisor using Amazon Cloudwatch events and AWS Lambda to reduce cost using the following instructions: 8 | 9 | Choose **Launch Stack** to launch the CloudFormation template in the US East (N. Virginia) Region in your account: 10 | 11 | [![Launch Stop Low Utilization EC2 Instances](../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=StopLowUtilizationEC2Instances&templateURL=https://s3-us-west-2.amazonaws.com/aws-trusted-advisor-open-source/cloudformation-templates/TALowUtilizationEC2Instances.template) 12 | 13 | Make sure to set the appropriate tags and region per your requirements in configuration section of the Lambda function. Set the Dryrun flag to true during testing. 14 | 15 | Alternatively, you can manually create each resource if needed using the following instructions: 16 | 17 | 1. Create an Amazon IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 18 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 19 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 20 | 21 | 2. Create a Lambda javascript function using the [sample](LambdaFunction.js) provided and choose the IAM role created in step 1. Make sure to set the appropriate tags and region per your requirements in configuration section of the Lambda function. 22 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 23 | 24 | 3. Create a Cloudwatch event rule to trigger the Lambda function created in step 2 matching the WARN status and the Low Utilization EC2 Instances Trusted Advisor check. An example of this is highlighted in the sample [Cloudwatch Event Pattern](CloudwatchEventPattern). 25 | Documentation on to create a Trusted Advisor Cloudwatch events rule is available here: http://docs.aws.amazon.com/awssupport/latest/user/cloudwatch-events-ta.html 26 | 27 | More information about Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 28 | 29 | Please note that this is a just an example of how to setup automation with Trusted Advisor, Cloudwatch and Lambda. We recommend testing it and tailoring to your environment before using in your production envirnment. 30 | 31 | -------------------------------------------------------------------------------- /S3BucketVersioning/README.md: -------------------------------------------------------------------------------- 1 | ## Amazon S3 Bucket Versioning 2 | 3 | ### Trusted Advisor Check Description 4 | Checks for Amazon Simple Storage Service buckets that do not have versioning enabled, or have versioning suspended. When versioning is enabled, you can easily recover from both unintended user actions and application failures. Versioning allows you to preserve, retrieve, and restore any version of any object stored in a bucket. You can use lifecycle rules to manage all versions of your objects as well as their associated costs by automatically archiving objects to the Glacier storage class or removing them after a specified time period. You can also choose to require multi-factor authentication (MFA) for any object deletions or configuration changes to your buckets. 5 | 6 | ### Setup and Usage 7 | You can automatically enable S3 bucket versioning when recommended by Trusted Advisor using Amazon EventBridge and AWS Lambda for fault tolerance. For buckets that you intend to leave versioning disabled, add the "DisableVersioning" tag to the bucket. You do not need to have a value for the tag. Deploy using the following instructions: 8 | 9 | #### CloudFormation Launch Stack 10 | Choose **Launch Stack** to launch the CloudFormation template in the US East (N. Virginia) Region in your account: 11 | 12 | [![Launch Stop Low Utilization EC2 Instances](../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=TAS3BucketVersioning&templateURL=https://s3-us-west-2.amazonaws.com/aws-trusted-advisor-open-source/cloudformation-templates/TAS3BucketVersioning.json) 13 | 14 | #### AWS SAM 15 | If you havent already, [install AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html). Ensure you are in the `S3BucketVersioning` folder then `build` and `deploy` your package 16 | 17 | ```bash 18 | cd S3BucketVersioning 19 | sam build && sam deploy -g 20 | ``` 21 | 22 | #### Cloudformation - CLI 23 | ```bash 24 | S3BUCKET=[REPLACE_WITH_YOUR_BUCKET] 25 | ``` 26 | 27 | Ensure you are in the `S3BucketVersioning` folder and use the `aws cloudformation package` utility 28 | 29 | ```bash 30 | cd S3BucketVersioning 31 | 32 | aws cloudformation package --region us-east-1 --s3-bucket $S3BUCKET --template template.yaml --output-template-file template.output.yaml 33 | ``` 34 | Last, deploy the stack with the resulting yaml (`template.output.yaml`) through the CloudFormation Console or command line: 35 | 36 | ```bash 37 | aws cloudformation deploy --region us-east-1 --template-file template.output.yaml --stack-name TAS3BucketVersioning --capabilities CAPABILITY_NAMED_IAM 38 | ``` 39 | 40 | 41 | 42 | More information about Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 43 | 44 | Please note that this is a just an example of how to setup automation with Trusted Advisor, Cloudwatch and Lambda. We recommend testing it and tailoring to your environment before using in your production envirnment. 45 | 46 | -------------------------------------------------------------------------------- /TA-Responder/lambda_handlers/TrustedAdvisorCheckTrackerFunction.py: -------------------------------------------------------------------------------- 1 | """TrustedAdvisorCheckTrackerFunction - Logic for tracking the most recent status of TA checks in a DDB table""" 2 | 3 | import hashlib 4 | import logging 5 | import time 6 | 7 | import boto3 8 | import dateutil.parser 9 | 10 | logging.getLogger().setLevel(logging.INFO) 11 | logger = logging.getLogger() 12 | 13 | DDB_TABLE_NAME = "TrustedAdvisorCheckTrackerTable" 14 | 15 | 16 | def convert_to_epoch(datetime_str): 17 | return int(time.mktime(dateutil.parser.parse(datetime_str).timetuple())) 18 | 19 | 20 | def lambda_handler(event, context): 21 | # Parse the Trusted Advisor event data 22 | detail = event.get("detail", {}) 23 | check_name = detail.get("check-name") 24 | check_item_detail = detail.get("check-item-detail", {}) 25 | status = check_item_detail.get("Status") 26 | last_updated_time_str = check_item_detail.get("Last Updated Time") 27 | last_updated_time_epoch = convert_to_epoch(last_updated_time_str) 28 | resource = check_item_detail.get("Resource") 29 | region = check_item_detail.get("Region") 30 | 31 | # Create a hash key from check_name, resource and region 32 | hash_key = hashlib.sha256( 33 | (check_name + resource + region).encode("utf-8") 34 | ).hexdigest() 35 | 36 | dynamodb = boto3.resource("dynamodb") 37 | table = dynamodb.Table(DDB_TABLE_NAME) 38 | 39 | # Check if the item already exists in the table 40 | existing_item = table.get_item(Key={"hashKey": hash_key, "resource": resource}).get( 41 | "Item" 42 | ) 43 | 44 | if existing_item: 45 | existing_time_epoch = existing_item.get("lastUpdatedTimeEpoch") 46 | 47 | # Update the existing item with new values. Only if the check update is more recent than 48 | # what is recorded in the DDB TrustedAdvisorCheckTrackerTable 49 | if last_updated_time_epoch > existing_time_epoch: 50 | table.put_item( 51 | Item={ 52 | "hashKey": hash_key, 53 | "checkName": check_name, 54 | "resourceStatus": status, 55 | "lastUpdatedTime": last_updated_time_str, 56 | "lastUpdatedTimeEpoch": last_updated_time_epoch, 57 | "resource": resource, 58 | "region": region, 59 | } 60 | ) 61 | else: 62 | logger.info( 63 | f"Skipping update for {hash_key}/{check_name} as the existing time is more recent." 64 | ) 65 | else: 66 | # Create a new item in the table 67 | table.put_item( 68 | Item={ 69 | "hashKey": hash_key, 70 | "checkName": check_name, 71 | "resourceStatus": status, 72 | "lastUpdatedTime": last_updated_time_str, 73 | "lastUpdatedTimeEpoch": last_updated_time_epoch, 74 | "resource": resource, 75 | "region": region, 76 | } 77 | ) 78 | 79 | return 80 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/tests/unit/test_handler.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from apply_lifecycle import app 3 | from model.aws.ta import AWSEvent 4 | from model.aws.ta import TAStateChangeNotification 5 | from model.aws.ta import Marshaller 6 | 7 | @pytest.fixture() 8 | def eventBridgeTANotificationEvent(): 9 | """ Generates EventBridge Trusted Advisor Notification Event""" 10 | return { 11 | "version": "0", 12 | "id": "89877641-0939-4fd1-9c4c-2f2d5d0c9d5c", 13 | "detail-type": "Trusted Advisor Check Item Refresh Notification", 14 | "source": "aws.trustedadvisor", 15 | "account": "123456789012", 16 | "time": "2023-04-15T18:43:48Z", 17 | "region": "us-east-1", 18 | "resources": [], 19 | "detail": { 20 | "check-name": "Amazon S3 Bucket Lifecycle Configuration", 21 | "check-item-detail": { 22 | "Status": "Yellow", 23 | "Region": "us-east-1", 24 | "Bucket Name": "example-bucket", 25 | "Lifecycle Rule for Deleting Incomplete MPU": "No", 26 | "Days After Initiation": "" 27 | }, 28 | "status": "WARN", 29 | "resource_id": "arn:aws:s3:::example-bucket", 30 | "uuid": "c1cj39rr6v-1234-5678-9abc-def012345678" 31 | } 32 | } 33 | 34 | def test_lambda_handler(eventBridgeTANotificationEvent, mocker): 35 | # Mock the apply_lifecycle_policy function 36 | mocker.patch('apply_lifecycle.app.apply_lifecycle_policy') 37 | 38 | # Call the lambda handler 39 | ret = app.lambda_handler(eventBridgeTANotificationEvent, "") 40 | 41 | # Unmarshall the returned event 42 | awsEventRet: AWSEvent = Marshaller.unmarshall(ret, AWSEvent) 43 | detailRet: TAStateChangeNotification = awsEventRet.detail 44 | 45 | # Assert that the function processed the event correctly 46 | assert detailRet.check_name == "Amazon S3 Bucket Lifecycle Configuration" 47 | assert detailRet.status == "WARN" 48 | assert detailRet.resource_id == "arn:aws:s3:::example-bucket" 49 | assert awsEventRet.detail_type.startswith("TALifecyclePolicyFunction processed event of ") 50 | 51 | # Assert that apply_lifecycle_policy was called with correct arguments 52 | app.apply_lifecycle_policy.assert_called_once_with("123456789012", "example-bucket") 53 | 54 | def test_lambda_handler_ignore_compliant(eventBridgeTANotificationEvent, mocker): 55 | # Modify the event to simulate a compliant bucket 56 | eventBridgeTANotificationEvent['detail']['status'] = 'OK' 57 | 58 | # Mock the apply_lifecycle_policy function 59 | mock_apply = mocker.patch('apply_lifecycle.app.apply_lifecycle_policy') 60 | 61 | # Call the lambda handler 62 | ret = app.lambda_handler(eventBridgeTANotificationEvent, "") 63 | 64 | # Assert that apply_lifecycle_policy was not called 65 | mock_apply.assert_not_called() 66 | 67 | # Unmarshall the returned event 68 | awsEventRet: AWSEvent = Marshaller.unmarshall(ret, AWSEvent) 69 | 70 | # Assert that the function processed the event correctly 71 | assert awsEventRet.detail_type.startswith("TALifecyclePolicyFunction processed event of ") -------------------------------------------------------------------------------- /AmazonRDSIdleDBInstances/src/remove_idle_rds_databases.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | import os 4 | 5 | 6 | def lambda_handler(event, context): 7 | # Get Trusted Advisor event details 8 | region = event["detail"]["check-item-detail"]["Region"] 9 | last_connection = int( 10 | str(event["detail"]["check-item-detail"]["Days Since Last Connection"]).strip( 11 | "+" 12 | ) 13 | ) 14 | min_age = int(os.environ["MIN_AGE"]) 15 | termination_method = os.environ["TERMINATION_METHOD"] 16 | db_instance_name = event["detail"]["check-item-detail"]["DB Instance Name"] 17 | 18 | if last_connection < min_age: 19 | print( 20 | f"Database instance {db_instance_name} does not meet the minimum threshold for termination. Skipping." 21 | ) 22 | return 23 | 24 | if termination_method == "delete": 25 | return delete_db_instance( 26 | db_instance_name, boto3.client("rds", region_name=region) 27 | ) 28 | elif termination_method == "stop": 29 | return stop_db_instance( 30 | db_instance_name, boto3.client("rds", region_name=region) 31 | ) 32 | 33 | 34 | def send_sns_message(message): 35 | sns_topic = os.environ["SNS_TOPIC_ARN"] 36 | if sns_topic == "": 37 | print("SNS topic not set. Skipping SNS message.") 38 | return 39 | try: 40 | sns = boto3.client("sns") 41 | sns.publish( 42 | TopicArn=sns_topic, 43 | Subject=f"RDS Idle Database Termination Notification ({os.environ['ACCOUNT_ID']})", 44 | Message=message, 45 | MessageStructure="string", 46 | ) 47 | 48 | except Exception as e: 49 | print(f"Error sending SNS message - {e}") 50 | 51 | 52 | def delete_db_instance(db_instance_name, rds_client) -> dict: 53 | final_snapshot_identifier = db_instance_name + "-final-snapshot" 54 | try: 55 | rds_client.delete_db_instance( 56 | DBInstanceIdentifier=db_instance_name, 57 | FinalDBSnapshotIdentifier=final_snapshot_identifier, 58 | ) 59 | print(f"Database instance deleted - {db_instance_name}") 60 | message = f"Database instance {db_instance_name} has been deleted.\nFinal snapshot: {final_snapshot_identifier}" 61 | send_sns_message(message) 62 | return { 63 | "statusCode": 200, 64 | "body": json.dumps({"message": "Database instance deleted"}), 65 | } 66 | except Exception as e: 67 | print(e) 68 | return { 69 | "statusCode": 500, 70 | "body": json.dumps({"message": "Error deleting database instance"}), 71 | } 72 | 73 | 74 | def stop_db_instance(db_instance_name, rds_client) -> dict: 75 | try: 76 | rds_client.stop_db_instance(DBInstanceIdentifier=db_instance_name) 77 | print(f"Database instance stopped - {db_instance_name}") 78 | message = f"Database instance {db_instance_name} has been stopped." 79 | send_sns_message(message) 80 | return { 81 | "statusCode": 200, 82 | "body": json.dumps({"message": "Database instance stopped"}), 83 | } 84 | except Exception as e: 85 | print(e) 86 | return { 87 | "statusCode": 500, 88 | "body": json.dumps({"message": "Error stopping database instance"}), 89 | } 90 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/model/aws/ta/ta_state_change_notification.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import pprint 3 | import six 4 | 5 | class TAStateChangeNotification(object): 6 | _types = { 7 | 'check_name': 'str', 8 | 'check_item_detail': 'dict(str, str)', 9 | 'status': 'str', 10 | 'resource_id': 'str', 11 | 'uuid': 'str' 12 | } 13 | 14 | _attribute_map = { 15 | 'check_name': 'check-name', 16 | 'check_item_detail': 'check-item-detail', 17 | 'status': 'status', 18 | 'resource_id': 'resource_id', 19 | 'uuid': 'uuid' 20 | } 21 | 22 | def __init__(self, check_name=None, check_item_detail=None, status=None, resource_id=None, uuid=None): 23 | self._check_name = None 24 | self._check_item_detail = None 25 | self._status = None 26 | self._resource_id = None 27 | self._uuid = None 28 | self.discriminator = None 29 | self.check_name = check_name 30 | self.check_item_detail = check_item_detail 31 | self.status = status 32 | self.resource_id = resource_id 33 | self.uuid = uuid 34 | 35 | @property 36 | def check_name(self): 37 | return self._check_name 38 | 39 | @check_name.setter 40 | def check_name(self, check_name): 41 | self._check_name = check_name 42 | 43 | @property 44 | def check_item_detail(self): 45 | return self._check_item_detail 46 | 47 | @check_item_detail.setter 48 | def check_item_detail(self, check_item_detail): 49 | self._check_item_detail = check_item_detail 50 | 51 | @property 52 | def status(self): 53 | return self._status 54 | 55 | @status.setter 56 | def status(self, status): 57 | self._status = status 58 | 59 | @property 60 | def resource_id(self): 61 | return self._resource_id 62 | 63 | @resource_id.setter 64 | def resource_id(self, resource_id): 65 | self._resource_id = resource_id 66 | 67 | @property 68 | def uuid(self): 69 | return self._uuid 70 | 71 | @uuid.setter 72 | def uuid(self, uuid): 73 | self._uuid = uuid 74 | 75 | def to_dict(self): 76 | result = {} 77 | for attr, _ in six.iteritems(self._types): 78 | value = getattr(self, attr) 79 | if isinstance(value, list): 80 | result[attr] = list(map( 81 | lambda x: x.to_dict() if hasattr(x, "to_dict") else x, 82 | value 83 | )) 84 | elif hasattr(value, "to_dict"): 85 | result[attr] = value.to_dict() 86 | elif isinstance(value, dict): 87 | result[attr] = dict(map( 88 | lambda item: (item[0], item[1].to_dict()) 89 | if hasattr(item[1], "to_dict") else item, 90 | value.items() 91 | )) 92 | else: 93 | result[attr] = value 94 | return result 95 | 96 | def to_str(self): 97 | return pprint.pformat(self.to_dict()) 98 | 99 | def __repr__(self): 100 | return self.to_str() 101 | 102 | def __eq__(self, other): 103 | if not isinstance(other, TAStateChangeNotification): 104 | return False 105 | return self.__dict__ == other.__dict__ 106 | 107 | def __ne__(self, other): 108 | return not self == other -------------------------------------------------------------------------------- /ExposedAccessKeys/lambda_functions/lookup_cloudtrail_events.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import collections 3 | import boto3 4 | 5 | cloudtrail = boto3.client('cloudtrail') 6 | 7 | 8 | def lambda_handler(event, context): 9 | account_id = event['account_id'] 10 | time_discovered = event['time_discovered'] 11 | username = event['username'] 12 | deleted_key = event['deleted_key'] 13 | exposed_location = event['exposed_location'] 14 | endtime = datetime.datetime.now() # Create start and end time for CloudTrail lookup 15 | interval = datetime.timedelta(hours=24) 16 | starttime = endtime - interval 17 | print('Retrieving events...') 18 | events = get_events(username, starttime, endtime) 19 | print('Summarizing events...') 20 | event_names, resource_names, resource_types = get_events_summaries(events) 21 | return { 22 | "account_id": account_id, 23 | "time_discovered": time_discovered, 24 | "username": username, 25 | "deleted_key": deleted_key, 26 | "exposed_location": exposed_location, 27 | "event_names": event_names, 28 | "resource_names": resource_names, 29 | "resource_types": resource_types 30 | } 31 | 32 | 33 | def get_events(username, starttime, endtime): 34 | """ Retrieves detailed list of CloudTrail events that occured between the specified time interval. 35 | 36 | Args: 37 | username (string): Username to lookup CloudTrail events for. 38 | starttime(datetime): Start of interval to lookup CloudTrail events between. 39 | endtime(datetime): End of interval to lookup CloudTrail events between. 40 | 41 | Returns: 42 | (dict) 43 | Dictionary containing list of CloudTrail events occuring between the start and end time with detailed information for each event. 44 | 45 | """ 46 | try: 47 | response = cloudtrail.lookup_events( 48 | LookupAttributes=[ 49 | { 50 | 'AttributeKey': 'Username', 51 | 'AttributeValue': username 52 | }, 53 | ], 54 | StartTime=starttime, 55 | EndTime=endtime, 56 | MaxResults=50 57 | ) 58 | except Exception as e: 59 | print(e) 60 | print('Unable to retrieve CloudTrail events for user "{}"'.format(username)) 61 | raise(e) 62 | return response 63 | 64 | 65 | def get_events_summaries(events): 66 | """ Summarizes CloudTrail events list by reducing into counters of occurences for each event, resource name, and resource type in list. 67 | 68 | Args: 69 | events (dict): Dictionary containing list of CloudTrail events to be summarized. 70 | 71 | Returns: 72 | (list, list, list) 73 | Lists containing name:count tuples of most common occurences of events, resource names, and resource types in events list. 74 | 75 | """ 76 | event_name_counter = collections.Counter() 77 | resource_name_counter = collections.Counter() 78 | resource_type_counter = collections.Counter() 79 | for event in events['Events']: 80 | resources = event.get("Resources") 81 | event_name_counter.update([event.get('EventName')]) 82 | if resources is not None: 83 | resource_name_counter.update([resource.get("ResourceName") for resource in resources]) 84 | resource_type_counter.update([resource.get("ResourceType") for resource in resources]) 85 | return event_name_counter.most_common(10), resource_name_counter.most_common(10), resource_type_counter.most_common(10) 86 | -------------------------------------------------------------------------------- /LowUtilizationEC2Instances/LambdaFunction.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2016. Amazon Web Services, Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | // Sample Lambda Function to get Trusted Advisor Low Utilization Amazon EC2 Instances check details from Cloudwatch events and execute the EC2 stop instance recommendation 16 | var AWS = require('aws-sdk'); 17 | 18 | // define configuration 19 | const tagKey ='environment'; 20 | const tagValue ='dev'; 21 | const regionSpecification = 'us-east-2'; //Specify a region to restrict the EC2 Stop Instances action to. Use 'all' for all regions 22 | 23 | //main function which gets Trusted Advisor data from Cloudwatch event 24 | exports.handler = (event, context, callback) => { 25 | //extract details from Cloudwatch event 26 | checkName = event.detail["check-name"]; 27 | instanceId = event.detail["check-item-detail"]["Instance ID"]; 28 | region = event.detail["check-item-detail"]["Region/AZ"].slice(0, -1); 29 | const trustedAdvisorSuccessMessage = `Successfully got details from Trusted Advisor check, ${checkName} and executed automated action.`; 30 | 31 | //check if the EC2 instance is in the right region 32 | if (region == regionSpecification || regionSpecification == 'all') { stopInstances(instanceId, region); } 33 | else { console.log ("No EC2 instance found in specifed region"); } 34 | callback(null, trustedAdvisorSuccessMessage); //return success 35 | }; 36 | 37 | //Sample function which stops EC2 Instances after checking their tags 38 | function stopInstances (instanceId, region) { 39 | AWS.config.update({region: region}); 40 | var ec2 = new AWS.EC2(); 41 | 42 | //get tags for the instances highlighted by Trusted Advisor 43 | var describeTagsparams = { 44 | Filters: [ 45 | { 46 | Name: "resource-id", 47 | Values: [instanceId] 48 | }, 49 | { 50 | Name: "key", 51 | Values: [tagKey] 52 | } 53 | ] 54 | }; 55 | ec2.describeTags(describeTagsparams, function(err, data) { 56 | if (err) console.log(err, err.stack); // an error occurred 57 | else { 58 | if (data.Tags == "") {data = {Tags: [{value: "empty"}]} }; 59 | 60 | //if the tag value matches what's configured, then stop the instance 61 | if (data.Tags[0].Value == tagValue) 62 | { 63 | var stopInstancesParams = { 64 | InstanceIds: [instanceId], 65 | DryRun: true //set to true for testing 66 | }; 67 | ec2.stopInstances(stopInstancesParams, function(err, data) { 68 | if (err) console.log(instanceId, region, err, err.stack); // an error occurred 69 | else console.log("Instance stopped: ", instanceId, region); // successful response 70 | }); 71 | } 72 | else console.log ("Instance did not match tag: ", instanceId, region); 73 | } 74 | }); 75 | } 76 | 77 | -------------------------------------------------------------------------------- /ExposedAccessKeys/cloudformation/exposed_access_keys.serverless.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | 4 | Parameters: 5 | SlackWebhookURL: 6 | Type: String 7 | Description: "Enter the Slack Webhook URL as the input event to the Lambda function in JSON format {\"SlackWebhookURL\":\"\"}" 8 | Default: "{\"SlackWebhookURL\":\"\"}" 9 | 10 | Globals: 11 | Function: 12 | Runtime: python3.11 13 | Architectures: 14 | - "arm64" 15 | 16 | Resources: 17 | 18 | ExposedKeyStepFunction: 19 | Type: AWS::Serverless::StateMachine 20 | Properties: 21 | Definition: 22 | StartAt: DeleteAccessKeyPair 23 | States: 24 | DeleteAccessKeyPair: 25 | Type: Task 26 | Resource: !GetAtt DeleteAccessKeyPair.Arn 27 | Next: LookupCloudTrailEvents 28 | LookupCloudTrailEvents: 29 | Type: Task 30 | Resource: !GetAtt LookupCloudTrailEvents.Arn 31 | Next: NotifySecurity 32 | NotifySecurity: 33 | Type: Task 34 | Resource: !GetAtt NotifySecurity.Arn 35 | End: True 36 | Events: 37 | CloudWatchEvent: 38 | Type: CloudWatchEvent 39 | Properties: 40 | Pattern: 41 | source: 42 | - "aws.trustedadvisor" 43 | detail-type: 44 | - "Trusted Advisor Check Item Refresh Notification" 45 | detail: 46 | status: 47 | - "ERROR" 48 | check-name: 49 | - "Exposed Access Keys" 50 | State: "ENABLED" 51 | Policies: 52 | - LambdaInvokePolicy: 53 | FunctionName: !Ref DeleteAccessKeyPair 54 | - LambdaInvokePolicy: 55 | FunctionName: !Ref LookupCloudTrailEvents 56 | - LambdaInvokePolicy: 57 | FunctionName: !Ref NotifySecurity 58 | 59 | DeleteAccessKeyPair: 60 | Type: AWS::Serverless::Function 61 | Properties: 62 | CodeUri: ../lambda_functions/ 63 | Handler: delete_access_key_pair.lambda_handler 64 | Policies: 65 | - Version: "2012-10-17" 66 | Statement: 67 | - Effect: "Allow" 68 | Action: 69 | - "iam:DeleteAccessKey" 70 | Resource: "*" 71 | 72 | LookupCloudTrailEvents: 73 | Type: AWS::Serverless::Function 74 | Properties: 75 | CodeUri: ../lambda_functions/ 76 | Handler: lookup_cloudtrail_events.lambda_handler 77 | Policies: 78 | - Version: "2012-10-17" 79 | Statement: 80 | - Effect: "Allow" 81 | Action: 82 | - "cloudtrail:LookupEvents" 83 | Resource: "*" 84 | 85 | NotifySecurity: 86 | Type: AWS::Serverless::Function 87 | Properties: 88 | CodeUri: ../lambda_functions/ 89 | Handler: notify_security.lambda_handler 90 | Policies: 91 | - SNSPublishMessagePolicy: 92 | TopicName: !GetAtt NotificationTopic.TopicName 93 | Environment: 94 | Variables: 95 | TOPIC_ARN: !Ref NotificationTopic 96 | SlackWebhook_URL: !Ref SlackWebhookURL 97 | 98 | NotificationTopic: 99 | Type: "AWS::SNS::Topic" 100 | Properties: 101 | DisplayName: "SecurityNotificationTopic" -------------------------------------------------------------------------------- /AmazonEBSSnapshots/LambdaFunction.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2016. Amazon Web Services, Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | // Sample Lambda Function to get Trusted Advisor Amazon EBS Snapshots check details from Cloudwatch events and execute the EBS create Snapshot recommendation 16 | var AWS = require('aws-sdk'); 17 | 18 | // define configuration 19 | const tagKey ='environment'; 20 | const tagValue ='prod'; 21 | const regionSpecification = 'eu-west-1'; //Specify a region to restrict the EBS create Snapshot action to. Use 'all' for all regions 22 | 23 | //main function which gets Trusted Advisor data from Cloudwatch event 24 | exports.handler = (event, context, callback) => { 25 | //extract details from Cloudwatch event 26 | checkName = event.detail["check-name"]; 27 | volumeId = event.detail["check-item-detail"]["Volume ID"]; 28 | region = event.detail["check-item-detail"]["Region"]; 29 | const trustedAdvisorSuccessMessage = `Successfully got details from Trusted Advisor check, ${checkName} and executed automated action.`; 30 | 31 | //check if the volume is in the right region 32 | if (region == regionSpecification || regionSpecification == 'all') { createSnapshot (volumeId, region); } 33 | else { console.log ("No EBS volume found in specifed region"); } 34 | callback(null, trustedAdvisorSuccessMessage); //return success 35 | }; 36 | 37 | //Sample function which creates snapshots for volumes that have no backup after checking their tags 38 | function createSnapshot (volumeId, region) { 39 | AWS.config.update({region: region}); 40 | var ec2 = new AWS.EC2(); 41 | 42 | //get tags for the volumes highlighted by Trusted Advisor 43 | var describeTagsparams = { 44 | Filters: [ 45 | { 46 | Name: "resource-id", 47 | Values: [volumeId] 48 | }, 49 | { 50 | Name: "key", 51 | Values: [tagKey] 52 | } 53 | ] 54 | }; 55 | ec2.describeTags(describeTagsparams, function(err, data) { 56 | if (err) console.log(err, err.stack); // an error occurred 57 | else { 58 | if (data.Tags == "") {data = {Tags: [{value: "empty"}]} }; 59 | 60 | //if the tag value matches what's configured, then create snapshot for the volume 61 | if (data.Tags[0].Value == tagValue) 62 | { 63 | const snapshotDescription = `Snapshot for volume, ${volumeId}`; 64 | var createSnapshotParams = { 65 | Description: snapshotDescription, 66 | VolumeId: volumeId 67 | }; 68 | ec2.createSnapshot(createSnapshotParams, function(err, data) { 69 | if (err) console.log(volumeId, region, err, err.stack); // an error occurred 70 | else console.log("Started creation for volume: ", volumeId, region, data); // successful response 71 | }); 72 | } 73 | else console.log ("Volume did not match tag: ", volumeId, region); 74 | } 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /ExposedAccessKeys/terraform/main/src/ta-12Fnkpl8Y5-snsmessage.py: -------------------------------------------------------------------------------- 1 | import os 2 | import boto3 3 | 4 | TOPIC_ARN = os.environ['TOPIC_ARN'] # ARN for SNS topic to post message to 5 | 6 | TEMPLATE = ''' 7 | Trusted Advisor Automation 8 | EXPOSED IAM ACCESS KEY ALERT 9 | 10 | At {} the IAM access key "{}" for user "{}" on account "{}" was DEACTIVATED after it was found to have been exposed at the URL "{}". 11 | 12 | Affected Resources 13 | Account ID: {} 14 | IAM User: {} 15 | Access Key ID: {} 16 | Exposed Location: {} 17 | 18 | 19 | Summary of Recent Actions found on Cloudtrail: 20 | 21 | Below are summaries of the most recent actions, resource names, and resource types associated with this user over the last 24 hours. 22 | 23 | Actions: 24 | {} 25 | Resource Names: 26 | {} 27 | Resource Types: 28 | {} 29 | 30 | These are summaries of only the most recent API calls made by this user. 31 | Please ensure your account remains secure by further reviewing the API calls made by this user in CloudTrail. 32 | 33 | This email was sent by AWS Trusted Advisor Automation 34 | 35 | ''' 36 | 37 | sns = boto3.client('sns') 38 | 39 | 40 | def lambda_handler(event, context): 41 | account_id = event['account_id'] 42 | username = event['username'] 43 | deactivated_key = event['deactivated_key'] 44 | exposed_location = event['exposed_location'] 45 | time_discovered = event['time_discovered'] 46 | event_names = event['event_names'] 47 | resource_names = event['resource_names'] 48 | resource_types = event['resource_types'] 49 | subject = 'Security Alert: Exposed IAM Key For User "{}" On Account "{}"'.format(username, account_id) 50 | print("Generating message body...") 51 | event_summary = generate_summary_str(event_names) 52 | rname_summary = generate_summary_str(resource_names) 53 | rtype_summary = generate_summary_str(resource_types) 54 | message = TEMPLATE.format(time_discovered, 55 | deactivated_key, 56 | username, 57 | account_id, 58 | exposed_location, 59 | account_id, 60 | username, 61 | deactivated_key, 62 | exposed_location, 63 | event_summary, 64 | rname_summary, 65 | rtype_summary 66 | ) 67 | print("Publishing message...") 68 | publish_msg(subject, message) 69 | 70 | 71 | def generate_summary_str(summary_items): 72 | """ Generates formatted string containing CloudTrail summary info. 73 | Args: 74 | summary_items (list): List of tuples containing CloudTrail summary info. 75 | Returns: 76 | (string) 77 | Formatted string containing CloudTrail summary info. 78 | """ 79 | return '\t' + '\n\t'.join('{}: {}'.format(item[0], item[1]) for item in summary_items) 80 | 81 | 82 | def publish_msg(subject, message): 83 | """ Publishes message to SNS topic. 84 | Args: 85 | subject (string): Subject of message to be published to topic. 86 | message (string): Content of message to be published to topic. 87 | Returns: 88 | (None) 89 | """ 90 | try: 91 | sns.publish( 92 | TopicArn=TOPIC_ARN, 93 | Message=message, 94 | Subject=subject, 95 | MessageStructure='string' 96 | ) 97 | except Exception as e: 98 | print(e) 99 | print('Could not publish message to SNS topic "{}"'.format(TOPIC_ARN)) 100 | raise e -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/model/aws/ta/aws_event.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import pprint 4 | import six 5 | from model.aws.ta.ta_state_change_notification import TAStateChangeNotification 6 | 7 | class AWSEvent(object): 8 | _types = { 9 | 'detail': 'TAStateChangeNotification', 10 | 'detail_type': 'str', 11 | 'resources': 'list[str]', 12 | 'id': 'str', 13 | 'source': 'str', 14 | 'time': 'datetime', 15 | 'region': 'str', 16 | 'version': 'str', 17 | 'account': 'str' 18 | } 19 | 20 | _attribute_map = { 21 | 'detail': 'detail', 22 | 'detail_type': 'detail-type', 23 | 'resources': 'resources', 24 | 'id': 'id', 25 | 'source': 'source', 26 | 'time': 'time', 27 | 'region': 'region', 28 | 'version': 'version', 29 | 'account': 'account' 30 | } 31 | 32 | def __init__(self, detail=None, detail_type=None, resources=None, id=None, source=None, time=None, region=None, version=None, account=None): 33 | self._detail = None 34 | self._detail_type = None 35 | self._resources = None 36 | self._id = None 37 | self._source = None 38 | self._time = None 39 | self._region = None 40 | self._version = None 41 | self._account = None 42 | self.discriminator = None 43 | self.detail = detail 44 | self.detail_type = detail_type 45 | self.resources = resources 46 | self.id = id 47 | self.source = source 48 | self.time = time 49 | self.region = region 50 | self.version = version 51 | self.account = account 52 | 53 | @property 54 | def detail(self): 55 | return self._detail 56 | 57 | @detail.setter 58 | def detail(self, detail): 59 | if detail is None: 60 | raise ValueError("Invalid value for `detail`, must not be `None`") 61 | self._detail = detail 62 | 63 | @property 64 | def detail_type(self): 65 | return self._detail_type 66 | 67 | @detail_type.setter 68 | def detail_type(self, detail_type): 69 | if detail_type is None: 70 | raise ValueError("Invalid value for `detail_type`, must not be `None`") 71 | self._detail_type = detail_type 72 | 73 | # Add similar property and setter methods for other attributes 74 | 75 | def to_dict(self): 76 | result = {} 77 | for attr, _ in six.iteritems(self._types): 78 | value = getattr(self, attr) 79 | if isinstance(value, list): 80 | result[attr] = list(map( 81 | lambda x: x.to_dict() if hasattr(x, "to_dict") else x, 82 | value 83 | )) 84 | elif hasattr(value, "to_dict"): 85 | result[attr] = value.to_dict() 86 | elif isinstance(value, dict): 87 | result[attr] = dict(map( 88 | lambda item: (item[0], item[1].to_dict()) 89 | if hasattr(item[1], "to_dict") else item, 90 | value.items() 91 | )) 92 | else: 93 | result[attr] = value 94 | return result 95 | 96 | def to_str(self): 97 | return pprint.pformat(self.to_dict()) 98 | 99 | def __repr__(self): 100 | return self.to_str() 101 | 102 | def __eq__(self, other): 103 | if not isinstance(other, AWSEvent): 104 | return False 105 | return self.__dict__ == other.__dict__ 106 | 107 | def __ne__(self, other): 108 | return not self == other -------------------------------------------------------------------------------- /ExposedAccessKeys/cloudformation/exposed_access_keys_one_click.serverless.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | 4 | Parameters: 5 | SlackWebhookURL: 6 | Type: String 7 | Description: "Enter the Slack Webhook URL as the input event to the Lambda function in JSON format {\"SlackWebhookURL\":\"\"}" 8 | Default: "{\"SlackWebhookURL\":\"\"}" 9 | 10 | Globals: 11 | Function: 12 | Runtime: python3.11 13 | Architectures: 14 | - "arm64" 15 | 16 | Resources: 17 | 18 | ExposedKeyStepFunction: 19 | Type: AWS::Serverless::StateMachine 20 | Properties: 21 | Definition: 22 | StartAt: DeleteAccessKeyPair 23 | States: 24 | DeleteAccessKeyPair: 25 | Type: Task 26 | Resource: !GetAtt DeleteAccessKeyPair.Arn 27 | Next: LookupCloudTrailEvents 28 | LookupCloudTrailEvents: 29 | Type: Task 30 | Resource: !GetAtt LookupCloudTrailEvents.Arn 31 | Next: NotifySecurity 32 | NotifySecurity: 33 | Type: Task 34 | Resource: !GetAtt NotifySecurity.Arn 35 | End: True 36 | Events: 37 | CloudWatchEvent: 38 | Type: CloudWatchEvent 39 | Properties: 40 | Pattern: 41 | source: 42 | - "aws.trustedadvisor" 43 | detail-type: 44 | - "Trusted Advisor Check Item Refresh Notification" 45 | detail: 46 | status: 47 | - "ERROR" 48 | check-name: 49 | - "Exposed Access Keys" 50 | State: "ENABLED" 51 | Policies: 52 | - LambdaInvokePolicy: 53 | FunctionName: !Ref DeleteAccessKeyPair 54 | - LambdaInvokePolicy: 55 | FunctionName: !Ref LookupCloudTrailEvents 56 | - LambdaInvokePolicy: 57 | FunctionName: !Ref NotifySecurity 58 | 59 | DeleteAccessKeyPair: 60 | Type: AWS::Serverless::Function 61 | Properties: 62 | CodeUri: s3://aws-trusted-advisor-open-source-us-east-1/ExposedAccessKeys/lambda_functions.zip 63 | Handler: delete_access_key_pair.lambda_handler 64 | Policies: 65 | - Version: "2012-10-17" 66 | Statement: 67 | - Effect: "Allow" 68 | Action: 69 | - "iam:DeleteAccessKey" 70 | Resource: "*" 71 | 72 | LookupCloudTrailEvents: 73 | Type: AWS::Serverless::Function 74 | Properties: 75 | CodeUri: s3://aws-trusted-advisor-open-source-us-east-1/ExposedAccessKeys/lambda_functions.zip 76 | Handler: lookup_cloudtrail_events.lambda_handler 77 | Policies: 78 | - Version: "2012-10-17" 79 | Statement: 80 | - Effect: "Allow" 81 | Action: 82 | - "cloudtrail:LookupEvents" 83 | Resource: "*" 84 | 85 | NotifySecurity: 86 | Type: AWS::Serverless::Function 87 | Properties: 88 | CodeUri: s3://aws-trusted-advisor-open-source-us-east-1/ExposedAccessKeys/lambda_functions.zip 89 | Handler: notify_security.lambda_handler 90 | Policies: 91 | - SNSPublishMessagePolicy: 92 | TopicName: !GetAtt NotificationTopic.TopicName 93 | Environment: 94 | Variables: 95 | TOPIC_ARN: !Ref NotificationTopic 96 | SlackWebhook_URL: !Ref SlackWebhookURL 97 | 98 | NotificationTopic: 99 | Type: "AWS::SNS::Topic" 100 | Properties: 101 | DisplayName: "SecurityNotificationTopic" -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws/Trusted-Advisor-Tools/issues), or [recently closed](https://github.com/aws/Trusted-Advisor-Tools/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/Trusted-Advisor-Tools/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws/Trusted-Advisor-Tools/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/README.md: -------------------------------------------------------------------------------- 1 | # Amazon S3 Incomplete Multipart Upload Abort 2 | 3 | ## Trusted Advisor Check Description 4 | This check identifies Amazon S3 buckets that do not have appropriate lifecycle policies to abort incomplete multipart uploads. Incomplete multipart uploads can accumulate over time and consume storage space, potentially leading to unnecessary costs. By implementing a lifecycle policy to abort incomplete multipart uploads after a specific period, you can manage your S3 storage more efficiently and reduce costs. 5 | 6 | ## Setup and Usage 7 | You can automatically apply appropriate lifecycle policies to S3 buckets when recommended by Trusted Advisor using Amazon EventBridge and AWS Lambda for fault tolerance. This solution will add a lifecycle rule to abort incomplete multipart uploads after 7 days for buckets flagged by Trusted Advisor. Deploy using the following instructions: 8 | 9 | ### CloudFormation Launch Stack 10 | Choose **Launch Stack** to launch the CloudFormation template in the US East (N. Virginia) Region in your account: 11 | 12 | [![Launch S3 Incomplete Multipart upload abort Solution](../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=TAS3BucketVersioning&templateURL=https://aws-trusted-advisor-open-source.s3.us-west-2.amazonaws.com/cloudformation-templates/ta-s3-incomplete-mpu-abort/template.yaml) 13 | ### AWS SAM 14 | If you haven't already, [install AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html). Ensure you are in the `ta-s3-incomplete-mpu-abort` folder then `build` and `deploy` your package: 15 | 16 | ```bash 17 | cd ta-s3-incomplete-mpu-abort 18 | sam build && sam deploy --guided 19 | ``` 20 | 21 | Follow the prompts in the deploy process to set up your stack name, AWS Region, and other parameters. 22 | 23 | ### CloudFormation - CLI 24 | First, set up your S3 bucket for the CloudFormation package: 25 | 26 | ```bash 27 | S3BUCKET=[REPLACE_WITH_YOUR_BUCKET] 28 | ``` 29 | 30 | Ensure you are in the `ta-s3-incomplete-mpu-abort` folder and use the `aws cloudformation package` utility: 31 | 32 | ```bash 33 | cd ta-s3-incomplete-mpu-abort 34 | aws cloudformation package --region us-east-1 --s3-bucket $S3BUCKET --template template.yaml --output-template-file template.output.yaml 35 | ``` 36 | 37 | Last, deploy the stack with the resulting yaml (`template.output.yaml`) through the CloudFormation Console or command line: 38 | 39 | ```bash 40 | aws cloudformation deploy --region us-east-1 --template-file template.output.yaml --stack-name TAS3IncompleteMPUAbort --capabilities CAPABILITY_NAMED_IAM 41 | ``` 42 | 43 | ## Solution Details 44 | This solution deploys a Lambda function that is triggered by Trusted Advisor check results via EventBridge. When a bucket is identified as non-compliant (lacking an appropriate lifecycle policy for incomplete multipart uploads), the function will: 45 | 46 | 1. Assume a role in the account where the bucket is located. 47 | 2. Check the current lifecycle configuration of the bucket. 48 | 3. Add a new rule to abort incomplete multipart uploads after 7 days if such a rule doesn't already exist. 49 | 50 | ## Testing 51 | To run the unit tests for this project: 52 | 53 | ```bash 54 | pip install -r tests/requirements.txt 55 | python -m pytest tests/unit -v 56 | ``` 57 | 58 | ## Cleanup 59 | To delete the deployed resources, you can use the SAM CLI: 60 | 61 | ```bash 62 | sam delete --stack-name TAS3IncompleteMPUAbort 63 | ``` 64 | 65 | More information about Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 66 | 67 | Please note that this is just an example of how to set up automation with Trusted Advisor, EventBridge, and Lambda. We recommend testing it and tailoring it to your environment before using it in your production environment. -------------------------------------------------------------------------------- /ExposedAccessKeys/terraform/member/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 5.67.0" 6 | } 7 | } 8 | } 9 | 10 | # Configure the AWS Provider 11 | provider "aws" { 12 | region = var.region 13 | default_tags { 14 | tags = { 15 | project="ta-automation" 16 | ta_check_id="12Fnkpl8Y5" 17 | ta_check_name="Exposed Access Keys" 18 | deployment="terraform" 19 | auto-delete="no" 20 | } 21 | } 22 | } 23 | 24 | #Create IAM role for lambda functions 25 | 26 | resource "aws_iam_role" "ta-12Fnkpl8Y5-crossaccount-iam-role" { 27 | name = "ta-12Fnkpl8Y5-crossaccount-iam-role" 28 | 29 | assume_role_policy = jsonencode({ 30 | Version: "2012-10-17", 31 | Statement: [ 32 | { 33 | Effect: "Allow", 34 | Principal: { 35 | "AWS": [ 36 | "arn:aws:sts::${var.main_aws_account_id}:assumed-role/ta-12Fnkpl8Y5-lambda-role/ta-12Fnkpl8Y5-deactivateiamkey", 37 | "arn:aws:sts::${var.main_aws_account_id}:assumed-role/ta-12Fnkpl8Y5-lambda-role/ta-12Fnkpl8Y5-cloudtraileventlookup" 38 | ] 39 | }, 40 | Action: "sts:AssumeRole", 41 | Condition: {} 42 | } 43 | ] 44 | }) 45 | 46 | inline_policy { 47 | name = "ta-12Fnkpl8Y5-lambda-permissions" 48 | policy = jsonencode({ 49 | Version = "2012-10-17" 50 | Statement = [ 51 | 52 | { 53 | "Sid": "Statement1", 54 | "Action": [ 55 | "iam:UpdateAccessKey" 56 | ], 57 | "Effect": "Allow", 58 | "Resource": "*" 59 | }, 60 | { 61 | "Sid": "Statement2", 62 | "Action": [ 63 | "cloudtrail:LookupEvents" 64 | ], 65 | "Effect": "Allow", 66 | "Resource": "*" 67 | }, 68 | 69 | ] 70 | }) 71 | } 72 | } 73 | 74 | #Create IAM Role for Eventbridge rule 75 | resource "aws_iam_role" "ta-12Fnkpl8Y5-events-role" { 76 | name = "ta-12Fnkpl8Y5-events-role" 77 | 78 | assume_role_policy = jsonencode({ 79 | Version: "2012-10-17", 80 | Statement: [ 81 | { 82 | Action: "sts:AssumeRole", 83 | Principal: { 84 | Service: "events.amazonaws.com" 85 | }, 86 | Effect: "Allow", 87 | Sid: "" 88 | } 89 | ] 90 | }) 91 | 92 | inline_policy { 93 | name = "ta-12Fnkpl8Y5-events-permissions" 94 | policy = jsonencode({ 95 | Version: "2012-10-17", 96 | Statement: [ 97 | { 98 | Sid: "ActionsForResource", 99 | Effect: "Allow", 100 | Action: [ 101 | "events:PutEvents" 102 | ], 103 | Resource: [ 104 | "arn:aws:events:eu-west-1:${var.main_aws_account_id}:event-bus/ta-12Fnkpl8Y5-automation-event-bus" 105 | ] 106 | } 107 | ] 108 | }) 109 | } 110 | } 111 | 112 | 113 | # Create EventBridge Rule 114 | resource "aws_cloudwatch_event_rule" "ta-12Fnkpl8Y5-automation-event-rule" { 115 | name = "ta-12Fnkpl8Y5-automation-event-rule" 116 | description = "Send TA event to main account" 117 | event_bus_name = "default" 118 | 119 | event_pattern = jsonencode({ 120 | detail: { 121 | check-name: ["Exposed Access Keys"] 122 | }, 123 | detail-type: ["Trusted Advisor Check Item Refresh Notification"], 124 | source: ["aws.trustedadvisor"] 125 | }) 126 | } 127 | 128 | # New EventBridge Target resource 129 | resource "aws_cloudwatch_event_target" "step_function_target" { 130 | rule = aws_cloudwatch_event_rule.ta-12Fnkpl8Y5-automation-event-rule.name 131 | target_id = "ExecuteStepFunction" 132 | arn = "arn:aws:events:eu-west-1:${var.main_aws_account_id}:event-bus/ta-12Fnkpl8Y5-automation-event-bus" 133 | role_arn = aws_iam_role.ta-12Fnkpl8Y5-events-role.arn 134 | event_bus_name = "default" 135 | } 136 | 137 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/apply_lifecycle/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | from botocore.exceptions import ClientError 3 | from model.aws.ta import Marshaller 4 | from model.aws.ta import AWSEvent 5 | from model.aws.ta import TAStateChangeNotification 6 | 7 | def lambda_handler(event, context): 8 | """Lambda function reacting to Trusted Advisor state change notifications 9 | 10 | Parameters 11 | ---------- 12 | event: dict, required 13 | EventBridge Trusted Advisor State Change Event Format 14 | 15 | context: object, required 16 | Lambda Context runtime methods and attributes 17 | 18 | Returns 19 | ------ 20 | The same input event with updated detail_type 21 | """ 22 | 23 | # Deserialize event into strongly typed object 24 | aws_event: AWSEvent = Marshaller.unmarshall(event, AWSEvent) 25 | ta_state_change_notification: TAStateChangeNotification = aws_event.detail 26 | 27 | # Execute business logic 28 | process_ta_notification(ta_state_change_notification, aws_event) 29 | 30 | # Make updates to event payload 31 | aws_event.detail_type = "TALifecyclePolicyFunction processed event of " + aws_event.detail_type 32 | 33 | # Return event for further processing 34 | return Marshaller.marshall(aws_event) 35 | 36 | def process_ta_notification(notification: TAStateChangeNotification, aws_event: AWSEvent): 37 | if notification.check_name != "Amazon S3 Bucket Lifecycle Configuration": 38 | print(f"Ignoring notification for check: {notification.check_name}") 39 | return 40 | 41 | bucket_name = notification.check_item_detail.get("Bucket Name") 42 | account_id = aws_event.account # Assuming the account ID is in the main event object 43 | 44 | if notification.status == "WARN": 45 | apply_lifecycle_policy(account_id, bucket_name) 46 | else: 47 | print(f"Bucket {bucket_name} in account {account_id} is compliant") 48 | 49 | def apply_lifecycle_policy(account_id, bucket_name): 50 | # Assume role in the target account 51 | sts_client = boto3.client('sts') 52 | assumed_role_object = sts_client.assume_role( 53 | RoleArn=f"arn:aws:iam::{account_id}:role/CrossAccountS3AccessRole", 54 | RoleSessionName="AssumeRoleSession" 55 | ) 56 | 57 | # Create an S3 client using the assumed role credentials 58 | s3_client = boto3.client( 59 | 's3', 60 | aws_access_key_id=assumed_role_object['Credentials']['AccessKeyId'], 61 | aws_secret_access_key=assumed_role_object['Credentials']['SecretAccessKey'], 62 | aws_session_token=assumed_role_object['Credentials']['SessionToken'] 63 | ) 64 | 65 | # Check if a lifecycle policy already exists 66 | try: 67 | existing_policy = s3_client.get_bucket_lifecycle_configuration(Bucket=bucket_name) 68 | rules = existing_policy.get('Rules', []) 69 | except ClientError as e: 70 | if e.response['Error']['Code'] == 'NoSuchLifecycleConfiguration': 71 | rules = [] 72 | else: 73 | print(f"Error getting lifecycle policy for bucket {bucket_name} in account {account_id}: {e}") 74 | return 75 | 76 | # Check if the required rule already exists 77 | required_rule_exists = any( 78 | rule.get('AbortIncompleteMultipartUpload', {}).get('DaysAfterInitiation') == 7 79 | for rule in rules 80 | ) 81 | 82 | if not required_rule_exists: 83 | # Add the new rule 84 | new_rule = { 85 | 'ID': 'AbortIncompleteMultipartUploads', 86 | 'Status': 'Enabled', 87 | 'Filter': {}, 88 | 'AbortIncompleteMultipartUpload': { 89 | 'DaysAfterInitiation': 7 90 | } 91 | } 92 | rules.append(new_rule) 93 | 94 | # Apply the updated lifecycle configuration 95 | try: 96 | s3_client.put_bucket_lifecycle_configuration( 97 | Bucket=bucket_name, 98 | LifecycleConfiguration={'Rules': rules} 99 | ) 100 | print(f"Applied lifecycle policy to bucket {bucket_name} in account {account_id}") 101 | except ClientError as e: 102 | print(f"Error applying lifecycle policy to bucket {bucket_name} in account {account_id}: {e}") 103 | else: 104 | print(f"Required rule already exists for bucket {bucket_name} in account {account_id}") -------------------------------------------------------------------------------- /ExposedAccessKeys/terraform/README.md: -------------------------------------------------------------------------------- 1 | # Trusted Advisor Automation for Exposed Access Keys 2 | 3 | This Terraform configuration sets up an automation system to respond to Trusted Advisor alerts for exposed AWS access keys [Trusted Advisor Check ID: 12Fnkpl8Y5](https://docs.aws.amazon.com/awssupport/latest/user/security-checks.html#exposed-access-keys). It creates the necessary AWS resources to detect and react to these security events. 4 | 5 | This can be deployed in Single Account or Multiple Account scenario. 6 | 7 | ![Diagram](./ta-automation.png) 8 | 9 | Terraform code for each scenario is in different directories. 10 | ``` 11 | |_main 12 | |_src 13 | |_ta-12Fnkpl8Y5-deactivateiamkey.py 14 | |_ta-12Fnkpl8Y5-cloudtraileventlookup.py 15 | |_ta-12Fnkpl8Y5-snsmessage.py 16 | |_main.tf 17 | |_variables.tf 18 | |_member 19 | |_main.tf 20 | |_variables.tf 21 | |_readme.md (this file) 22 | |_mockevent.json (fake event used for testing) 23 | |_mockevent-child.json (fake event used for testing) 24 | ``` 25 | 26 | ## Prerequisites 27 | 28 | - AWS CLI configured with appropriate permissions 29 | - Terraform v0.12+ installed 30 | 31 | 32 | ## Main Account AWS Resources Created (Mandatory) 33 | 34 | - IAM Roles: 35 | - Lambda execution role 36 | - Step Functions execution role 37 | - EventBridge rule role 38 | 39 | - Lambda Functions: 40 | - Deactivate IAM Key 41 | - CloudTrail Event Lookup 42 | - SNS Message Sender 43 | 44 | - SNS Topic: 45 | - For sending notifications about exposed keys 46 | 47 | - Step Function: 48 | - Orchestrates the execution of Lambda functions 49 | 50 | - EventBridge: 51 | - Custom event bus 52 | - Rule to trigger Step Function on Trusted Advisor alerts 53 | 54 | ### Usage 55 | 56 | 1. Initialize the Terraform working directory: 57 | 58 | terraform init 59 | 60 | 61 | 2. Review the planned changes: 62 | 63 | terraform plan 64 | 65 | 3. Apply the Terraform configuration: 66 | 67 | terraform apply 68 | 69 | 4. When prompted, enter the required variables or provide a `.tfvars` file. 70 | 71 | #### Variables 72 | 73 | - `email`: Email address for SNS notifications 74 | 75 | NOTE: You will have to confirm the subscription by following the link in the email "AWS Notification - Subscription Confirmation" 76 | 77 | #### Outputs 78 | 79 | - `region`: AWS region where resources will be created (default:eu-west-1) 80 | - `event_bus_arn`: ARN of the created EventBridge bus 81 | 82 | #### File Structure 83 | 84 | - `main.tf`: Main Terraform configuration file 85 | - `variables.tf`: Variable definitions 86 | - `outputs.tf`: Output definitions 87 | - `src/`: Directory containing Lambda function source code 88 | 89 | 90 | ## Member Account AWS Resources (Optional) 91 | 92 | 1. IAM Roles: 93 | - Cross-account IAM role for Lambda function execution 94 | - IAM role for EventBridge rule execution 95 | 96 | 2. EventBridge (CloudWatch Events): 97 | - Rule to detect Trusted Advisor notifications for exposed access keys 98 | - Target to trigger a response (likely a Step Function, defined elsewhere) 99 | 100 | ### Usage 101 | 102 | 1. Initialize the Terraform working directory: 103 | 104 | terraform init 105 | 106 | 107 | 2. Review the planned changes: 108 | 109 | terraform plan 110 | 111 | 3. Apply the Terraform configuration: 112 | 113 | terraform apply 114 | 115 | 4. When prompted, enter the required variables or provide a `.tfvars` file. 116 | 117 | #### Variables 118 | 119 | - `region`: AWS region where resources will be created (default:eu-west-1) 120 | - `event_bus_arn`: ARN of the event bus where events will be sent 121 | 122 | #### File Structure 123 | 124 | - `main.tf`: Main Terraform configuration file 125 | - `variables.tf`: Variable definitions 126 | 127 | 128 | ## Customization 129 | 130 | To customize this configuration: 131 | 132 | 1. Modify the Lambda function code in the `src/` directory. 133 | 2. Adjust the IAM permissions in the role definitions as needed. 134 | 3. Update the Step Function definition to change the automation workflow. 135 | 4. Modify the EventBridge rule pattern to trigger on different events. 136 | 137 | ## Security Considerations 138 | 139 | - Review and adjust IAM permissions to adhere to the principle of least privilege. 140 | - Ensure that SNS topics and other resources are properly secured. 141 | - Regularly review and update the automation logic to match your security policies. 142 | 143 | ## Contributing 144 | 145 | For any improvements or issues, please open an issue or pull request in the repository. 146 | -------------------------------------------------------------------------------- /ExposedAccessKeys/terraform/main/src/ta-12Fnkpl8Y5-cloudtraileventlookup.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import collections 3 | import boto3 4 | 5 | def lambda_handler(event, context): 6 | event_account_id = event['account_id'] 7 | time_discovered = event['time_discovered'] 8 | username = event['username'] 9 | deactivated_key = event['deactivated_key'] 10 | exposed_location = event['exposed_location'] 11 | endtime = datetime.datetime.now() # Create start and end time for CloudTrail lookup 12 | interval = datetime.timedelta(hours=24) 13 | starttime = endtime - interval 14 | current_account_id = context.invoked_function_arn.split(":")[4] 15 | 16 | print('Retrieving events...') 17 | events = get_events(current_account_id, event_account_id, username, starttime, endtime) 18 | print('Summarizing events...') 19 | event_names, resource_names, resource_types = get_events_summaries(events) 20 | return { 21 | "account_id": event_account_id, 22 | "time_discovered": time_discovered, 23 | "username": username, 24 | "deactivated_key": deactivated_key, 25 | "exposed_location": exposed_location, 26 | "event_names": event_names, 27 | "resource_names": resource_names, 28 | "resource_types": resource_types 29 | } 30 | 31 | def get_events(current_account_id, event_account_id, username, starttime, endtime): 32 | """ Retrieves detailed list of CloudTrail events that occurred between the specified time interval. 33 | Args: 34 | current_account_id (string): The ID of the current AWS account. 35 | event_account_id (string): The ID of the AWS account where the event occurred. 36 | username (string): Username to lookup CloudTrail events for. 37 | starttime(datetime): Start of interval to lookup CloudTrail events between. 38 | endtime(datetime): End of interval to lookup CloudTrail events between. 39 | Returns: 40 | (dict) 41 | Dictionary containing list of CloudTrail events occurring between the start and end time with detailed information for each event. 42 | """ 43 | 44 | if event_account_id != current_account_id: 45 | print("Assuming role in another account") 46 | sts = boto3.client("sts") 47 | response = sts.assume_role( 48 | RoleArn="arn:aws:iam::"+ event_account_id +":role/ta-12Fnkpl8Y5-crossaccount-iam-role", 49 | RoleSessionName="ta-12Fnkpl8Y5-cloudtraileventlookup-session" 50 | ) 51 | session = boto3.Session( 52 | aws_access_key_id=response['Credentials']['AccessKeyId'], 53 | aws_secret_access_key=response['Credentials']['SecretAccessKey'], 54 | aws_session_token=response['Credentials']['SessionToken'] 55 | ) 56 | else: 57 | print("Using current account") 58 | session = boto3.Session() 59 | 60 | cloudtrail = session.client("cloudtrail") 61 | 62 | try: 63 | response = cloudtrail.lookup_events( 64 | LookupAttributes=[ 65 | { 66 | 'AttributeKey': 'Username', 67 | 'AttributeValue': username 68 | }, 69 | ], 70 | StartTime=starttime, 71 | EndTime=endtime, 72 | MaxResults=50 73 | ) 74 | except Exception as e: 75 | print(e) 76 | print('Unable to retrieve CloudTrail events for user "{}"'.format(username)) 77 | raise(e) 78 | return response 79 | 80 | def get_events_summaries(events): 81 | """ Summarizes CloudTrail events list by reducing into counters of occurrences for each event, resource name, and resource type in list. 82 | Args: 83 | events (dict): Dictionary containing list of CloudTrail events to be summarized. 84 | Returns: 85 | (list, list, list) 86 | Lists containing name:count tuples of most common occurrences of events, resource names, and resource types in events list. 87 | """ 88 | event_name_counter = collections.Counter() 89 | resource_name_counter = collections.Counter() 90 | resource_type_counter = collections.Counter() 91 | for event in events['Events']: 92 | resources = event.get("Resources") 93 | event_name_counter.update([event.get('EventName')]) 94 | if resources is not None: 95 | resource_name_counter.update([resource.get("ResourceName") for resource in resources]) 96 | resource_type_counter.update([resource.get("ResourceType") for resource in resources]) 97 | return event_name_counter.most_common(10), resource_name_counter.most_common(10), resource_type_counter.most_common(10) -------------------------------------------------------------------------------- /ExposedAccessKeys/lambda_functions/notify_security.py: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | #Author: Manas Satpathi 3 | #Company: AWS 4 | #Date: January, 2023 5 | #Notes: Updated to send email & slack notification 6 | ############################################################ 7 | import os 8 | import sys 9 | import boto3 10 | 11 | import json 12 | import urllib.parse 13 | import urllib.request 14 | 15 | TOPIC_ARN = os.environ['TOPIC_ARN'] # ARN for SNS topic to post message to 16 | slack_webhook_url = os.environ['SlackWebhook_URL'] 17 | 18 | TEMPLATE = '''At {} the IAM access key {} for user {} on account {} was deleted after it was found to have been exposed at the URL {}. 19 | Below are summaries of the most recent actions, resource names, and resource types associated with this user over the last 24 hours. 20 | 21 | Actions: 22 | {} 23 | 24 | Resource Names: 25 | {} 26 | 27 | Resource Types: 28 | {} 29 | 30 | These are summaries of only the most recent API calls made by this user. Please ensure your account remains secure by further reviewing the API calls made by this user in CloudTrail.''' 31 | 32 | sns = boto3.client('sns') 33 | 34 | def lambda_handler(event, context): 35 | account_id = event['account_id'] 36 | username = event['username'] 37 | deleted_key = event['deleted_key'] 38 | exposed_location = event['exposed_location'] 39 | time_discovered = event['time_discovered'] 40 | event_names = event['event_names'] 41 | resource_names = event['resource_names'] 42 | resource_types = event['resource_types'] 43 | subject = 'Security Alert! IAM Access Key Exposed For User {} On Account {}!!'.format(username, account_id) 44 | subject2 = ' An email is sent with details.' 45 | print("Generating message body...") 46 | event_summary = generate_summary_str(event_names) 47 | rname_summary = generate_summary_str(resource_names) 48 | rtype_summary = generate_summary_str(resource_types) 49 | message = TEMPLATE.format(time_discovered, 50 | deleted_key, 51 | username, 52 | account_id, 53 | exposed_location, 54 | event_summary, 55 | rname_summary, 56 | rtype_summary 57 | ) 58 | print("Publishing message...") 59 | publish_msg(subject, message) 60 | 61 | if(len(slack_webhook_url) == 0): 62 | print("Slack_URL is empty!") 63 | else: 64 | print("Sending Slack notification") 65 | print("Got SlackWebhookURL {}".format(slack_webhook_url)) 66 | notify_slack(subject, subject2) 67 | 68 | return { 69 | 'statusCode': 200 70 | } 71 | 72 | def generate_summary_str(summary_items): 73 | """ Generates formatted string containing CloudTrail summary info. 74 | 75 | Args: 76 | summary_items (list): List of tuples containing CloudTrail summary info. 77 | 78 | Returns: 79 | (string) 80 | Formatted string containing CloudTrail summary info. 81 | 82 | """ 83 | return '\t' + '\n\t'.join('{}: {}'.format(item[0], item[1]) for item in summary_items) 84 | 85 | def publish_msg(subject, message): 86 | """ Publishes message to SNS topic. 87 | 88 | Args: 89 | subject (string): Subject of message to be published to topic. 90 | message (string): Content of message to be published to topic. 91 | 92 | Returns: 93 | (None) 94 | 95 | """ 96 | try: 97 | sns.publish( 98 | TopicArn=TOPIC_ARN, 99 | Message=message, 100 | Subject=subject, 101 | MessageStructure='string' 102 | ) 103 | except Exception as e: 104 | print(e) 105 | print('Could not publish message to SNS topic "{}"'.format(TOPIC_ARN)) 106 | raise e 107 | 108 | def notify_slack(subject, subject2): 109 | 110 | data = "{content:" + '"' + subject + subject2 +'"}' 111 | 112 | headers = { 113 | 'Content-type': 'application/json' 114 | } 115 | 116 | ## USING Python "urllib" instead 117 | 118 | data = data.encode('ascii') 119 | 120 | headers = {} 121 | headers['Content-Type'] = "application/json" 122 | 123 | ## Send the request 124 | print("URL = ", slack_webhook_url) 125 | req = urllib.request.Request(slack_webhook_url, data=data, headers=headers) 126 | resp = urllib.request.urlopen(req) 127 | 128 | ## Receive the response 129 | print("RESPONSE: ", resp) 130 | -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/apply_lifecycle_function/model/aws/ta/marshaller.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import re 3 | import six 4 | import model.aws.ta 5 | 6 | class Marshaller: 7 | PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types 8 | 9 | NATIVE_TYPES_MAPPING = { 10 | 'int': int, 11 | 'long': int if six.PY3 else long, 12 | 'float': float, 13 | 'str': str, 14 | 'bool': bool, 15 | 'date': datetime.date, 16 | 'datetime': datetime.datetime, 17 | 'object': object, 18 | } 19 | 20 | @classmethod 21 | def marshall(cls, obj): 22 | if obj is None: 23 | return None 24 | elif isinstance(obj, cls.PRIMITIVE_TYPES): 25 | return obj 26 | elif isinstance(obj, list): 27 | return [cls.marshall(sub_obj) 28 | for sub_obj in obj] 29 | elif isinstance(obj, tuple): 30 | return tuple(cls.marshall(sub_obj) 31 | for sub_obj in obj) 32 | elif isinstance(obj, (datetime.datetime, datetime.date)): 33 | return obj.isoformat() 34 | 35 | if isinstance(obj, dict): 36 | obj_dict = obj 37 | else: 38 | obj_dict = {obj._attribute_map[attr]: getattr(obj, attr) 39 | for attr, _ in six.iteritems(obj._types) 40 | if getattr(obj, attr) is not None} 41 | 42 | return {key: cls.marshall(val) 43 | for key, val in six.iteritems(obj_dict)} 44 | 45 | @classmethod 46 | def unmarshall(cls, data, typeName): 47 | if data is None: 48 | return None 49 | 50 | if type(typeName) == str: 51 | if typeName.startswith('list['): 52 | sub_kls = re.match(r'list\[(.*)\]', typeName).group(1) 53 | return [cls.unmarshall(sub_data, sub_kls) 54 | for sub_data in data] 55 | 56 | if typeName.startswith('dict('): 57 | sub_kls = re.match(r'dict\(([^,]*), (.*)\)', typeName).group(2) 58 | return {k: cls.unmarshall(v, sub_kls) 59 | for k, v in six.iteritems(data)} 60 | 61 | if typeName in cls.NATIVE_TYPES_MAPPING: 62 | typeName = cls.NATIVE_TYPES_MAPPING[typeName] 63 | else: 64 | typeName = getattr(model.aws.ta, typeName) 65 | 66 | if typeName in cls.PRIMITIVE_TYPES: 67 | return cls.__unmarshall_primitive(data, typeName) 68 | elif typeName == object: 69 | return cls.__unmarshall_object(data) 70 | elif typeName == datetime.date: 71 | return cls.__unmarshall_date(data) 72 | elif typeName == datetime.datetime: 73 | return cls.__unmarshall_datatime(data) 74 | else: 75 | return cls.__unmarshall_model(data, typeName) 76 | 77 | @classmethod 78 | def __unmarshall_primitive(cls, data, typeName): 79 | try: 80 | return typeName(data) 81 | except UnicodeEncodeError: 82 | return six.text_type(data) 83 | except TypeError: 84 | return data 85 | 86 | @classmethod 87 | def __unmarshall_object(cls, value): 88 | return value 89 | 90 | @classmethod 91 | def __unmarshall_date(cls, string): 92 | try: 93 | from dateutil.parser import parse 94 | return parse(string).date() 95 | except ImportError: 96 | return string 97 | 98 | @classmethod 99 | def __unmarshall_datatime(cls, string): 100 | try: 101 | from dateutil.parser import parse 102 | return parse(string) 103 | except ImportError: 104 | return string 105 | 106 | @classmethod 107 | def __unmarshall_model(cls, data, typeName): 108 | if (not typeName._types and 109 | not cls.__hasattr(typeName, 'get_real_child_model')): 110 | return data 111 | 112 | kwargs = {} 113 | if typeName._types is not None: 114 | for attr, attr_type in six.iteritems(typeName._types): 115 | if (data is not None and 116 | typeName._attribute_map[attr] in data and 117 | isinstance(data, (list, dict))): 118 | value = data[typeName._attribute_map[attr]] 119 | kwargs[attr] = cls.unmarshall(value, attr_type) 120 | 121 | instance = typeName(**kwargs) 122 | 123 | if (isinstance(instance, dict) and 124 | typeName._types is not None and 125 | isinstance(data, dict)): 126 | for key, value in data.items(): 127 | if key not in typeName._types: 128 | instance[key] = value 129 | if cls.__hasattr(instance, 'get_real_child_model'): 130 | type_name = instance.get_real_child_model(data) 131 | if type_name: 132 | instance = cls.unmarshall(data, type_name) 133 | return instance 134 | 135 | @classmethod 136 | def __hasattr(cls, object, name): 137 | return name in object.__class__.__dict__ -------------------------------------------------------------------------------- /TA-Integrations/TA-Red-Cost-Slack-Webhook/TA-Red-Slack-Webhook.py: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | #Authors: Manas Satpathi & Sandeep Mohanty 3 | #Company: AWS 4 | #Date: June 20, 2022 5 | ############################################################ 6 | 7 | import boto3 8 | import json 9 | #import requests 10 | import urllib.parse 11 | import urllib.request 12 | 13 | def lambda_handler(event, context): 14 | 15 | print(json.dumps(event)) 16 | slack_webhook_url = event["SlackWebhookURL"] 17 | client = boto3.client('support', region_name='us-east-1') 18 | response = client.describe_trusted_advisor_checks(language='en') 19 | 20 | # Account number from STS get-caller-identity 21 | sts_client = boto3.client('sts', region_name='us-east-1') 22 | account_num = boto3.client('sts').get_caller_identity().get('Account') 23 | print (account_num) 24 | 25 | ##Number of elements returned 26 | num_checks = len(response["checks"]) 27 | 28 | ##Create List of TA CheckIds 29 | checks = [] 30 | #check_security_category = 0 31 | ta_checks_dict = {} 32 | for x in range(num_checks): 33 | checks.append(response["checks"][x]["id"]) 34 | 35 | ##Store TA Checks in nested dict for cross referencing via TA check Id 36 | ta_checks_dict[response["checks"][x]["id"]] = {"name":response["checks"][x]["name"],"category":response["checks"][x]["category"]} 37 | #if (response["checks"][x]["category"] == "security"): 38 | # check_security_category += 1 39 | # print (check_security_category) 40 | # continue 41 | ##Get TA Check Summaries 42 | result = client.describe_trusted_advisor_check_summaries(checkIds=checks) 43 | 44 | count_ok = 0 45 | count_critical = 0 46 | count_warn = 0 47 | check_security_category = 0 48 | check_fault_tolerance_category = 0 49 | check_performance_category = 0 50 | check_cost_optimizing_category = 0 51 | check_service_limits_category = 0 52 | 53 | message = "" 54 | summary = "" 55 | 56 | for x in range(num_checks): 57 | check_status = result['summaries'][x]['status'] 58 | check_id = result['summaries'][x]['checkId'] 59 | #print(check_status) 60 | 61 | if(check_status == 'ok'): 62 | count_ok += 1 63 | elif(check_status == 'warning'): 64 | count_warn += 1 65 | elif(check_status == 'error'): 66 | count_critical += 1 67 | message += "HIGH RISK - " + "[" + str(ta_checks_dict[check_id]['category'])+ "] " + str(ta_checks_dict[check_id]['name']) + "\n" 68 | print (response["checks"][x]) 69 | if (ta_checks_dict[check_id]['category'] == 'fault_tolerance'): 70 | check_fault_tolerance_category += 1 71 | if (ta_checks_dict[check_id]['category'] == 'security'): 72 | check_security_category += 1 73 | if (ta_checks_dict[check_id]['category'] == 'performance'): 74 | check_performance_category += 1 75 | if (ta_checks_dict[check_id]['category'] == 'cost_optimizing'): 76 | check_cost_optimizing_category += 1 77 | if (ta_checks_dict[check_id]['category'] == 'service_limits'): 78 | check_service_limits_category += 1 79 | else: 80 | continue 81 | 82 | check_category_dict = {"Security" : check_security_category, "Fault-Tolerance" : check_fault_tolerance_category, \ 83 | "Performance" : check_performance_category, "Cost_optimizing" : check_cost_optimizing_category, \ 84 | "Service_limits" : check_service_limits_category} 85 | 86 | summary += "\n=== Summary of TA High Risk (RED) Findings for " + str (account_num) + " ===\n\n" 87 | 88 | summary += "Total High Risk (RED) Findings: " + str(count_critical) + " (" 89 | 90 | for i in range (len(check_category_dict.keys())): 91 | if list(check_category_dict.values())[i] > 0: 92 | summary += list(check_category_dict.keys())[i] + ": " + str(list(check_category_dict.values())[i]) + ", " 93 | 94 | summary = summary[:-2] 95 | check_estimatedMonthlySavings_total = 0 96 | num_checkIds = 0 97 | 98 | for x in range (len(result["summaries"])): 99 | try: 100 | check_estimatedMonthlySavings = result['summaries'][x]['categorySpecificSummary']['costOptimizing']['estimatedMonthlySavings'] 101 | check_estimatedMonthlySavings_total += check_estimatedMonthlySavings 102 | num_checkIds += 1 103 | except KeyError: 104 | pass 105 | 106 | check_estimatedMonthlySavings_total = round(check_estimatedMonthlySavings_total * 100)/100 107 | 108 | print ("Total estiated monthly savings:", check_estimatedMonthlySavings_total) 109 | print ("No. of Checks:", num_checkIds) 110 | print ("Account No:", account_num) 111 | 112 | summary += ")\n" 113 | summary += "Total Estimated Monthly Savings: $" + str(check_estimatedMonthlySavings_total) + ".\n\n" 114 | 115 | print("============= Post to Slack ===========") 116 | 117 | headers = { 118 | 'Content-type': 'application/json' 119 | } 120 | 121 | data = "{content:" + '"' + summary + message + '"}' 122 | 123 | ## NOT USING Python "requests" library to avoid creating Lambda Layer in CloudFormation. Preserve CF portability 124 | #response = requests.post(slack_webhook_url, headers=headers, data=data) 125 | 126 | ## USING Python "urllib" instead 127 | 128 | data = data.encode('ascii') 129 | 130 | headers = {} 131 | headers['Content-Type'] = "application/json" 132 | 133 | ## Send the request 134 | print("URL = ", slack_webhook_url) 135 | req = urllib.request.Request(slack_webhook_url, data=data, headers=headers) 136 | resp = urllib.request.urlopen(req) 137 | 138 | ## Receive the response 139 | #respData = resp.read() 140 | #print("RESPONSE: ", respData) 141 | 142 | return { 143 | 'statusCode': 200 144 | } 145 | 146 | ############################## 147 | 148 | -------------------------------------------------------------------------------- /TA-Responder/automation_docs_scripts/InvokeModelExecutionScript.py: -------------------------------------------------------------------------------- 1 | """This is the main Python script used by the 'TaResponderAutomationDocumentInvokeModel' SSM Automation document to get recommendations for TA check findings""" 2 | 3 | import json 4 | 5 | import boto3 6 | 7 | 8 | def get_trusted_advisor_check(check_name): 9 | # boto3 client for Trusted Advisor 10 | client = boto3.client("trustedadvisor") 11 | 12 | paginator = client.get_paginator("list_checks") 13 | 14 | # Initialize an empty list to store all check summaries 15 | all_check_summaries = [] 16 | 17 | # Paginate through all results 18 | for page in paginator.paginate(): 19 | all_check_summaries.extend(page["checkSummaries"]) 20 | 21 | # Find the check summary that matches the provided name 22 | matching_check = next( 23 | (check for check in all_check_summaries if check["name"] == check_name), None 24 | ) 25 | 26 | return matching_check 27 | 28 | 29 | def array_to_string(arr): 30 | if len(arr) == 0: 31 | return "" 32 | elif len(arr) == 1: 33 | return arr[0] 34 | elif len(arr) == 2: 35 | return f"{arr[0]} and {arr[1]}" 36 | else: 37 | return ", ".join(arr[:-1]) + f", and {arr[-1]}" 38 | 39 | 40 | def invoke_bedrock_and_extract( 41 | affected_resource_arn, check_name, model_id, check_details 42 | ): 43 | bedrock_runtime = boto3.client("bedrock-runtime") 44 | 45 | related_aws_services = array_to_string(check_details.get("awsServices", [])) 46 | 47 | related_pillars = array_to_string(check_details.get("pillars", [])) 48 | 49 | check_description = check_details.get("description", []) 50 | 51 | system_prompt = f"""You are an AWS Cloud Solutions Architect who specializes in the AWS Well-Architected Framework, and in running a process called the Well-Architected Framework Review (WAFR). The WAFR process consists of evaluating a cloud workload against the 6 pillars of the Well-Architected Framework, namely - Operational Excellence Pillar, Security Pillar, Reliability Pillar, Performance Efficiency Pillar, Cost Optimization Pillar, and Sustainability Pillar - all of this in order to establish good architectural habits, manage and eliminate risk and increase the adoption of best practices. 52 | 53 | You are also a subject matter expert in the {related_aws_services} AWS services, and have experience in troubleshooting and resolving complex technical problems on these services. You are able to review findings from AWS Trusted Advisor checks, and suggest possible root cause and solutions for the issues found. 54 | 55 | Follow the instructions listed under "instructions" section below. 56 | 57 | 58 | 1) In the section, you are provided with the name of a specific Trusted Advisor Check related to the {related_pillars} pillar(s), which has found a resource to be not compliance with its evaluation (e.g. the resource was flagged as either Red or Yellow by the check evaluation). 59 | 2) In the section, you are provided with more details about this Trusted Advisor Check. This section contains a description about what this check is evaluating, it details the "Alert Criteria" used by this check to evaluate whether a resource is flagged as either Red/Yellow/Green, and it also provides with "Recommended Action" information and "Additional Resources" with useful links. 60 | 3) In the section, you are provided with the ARN or unique identifier of the resource that was flagged by the Trusted Advisor Check. This is the resource that needs to be remediated. 61 | 4) In alignment with the Trusted Advisor Check and related AWS Well-Architected Framework best practices, provide recommendations on how to fix the issue identified by the Trusted Advisor Check. 62 | 5) When possible, provide with step by step instruction on how to fix the issue. Also, when applicable, include AWS CLI commands that can be used to fix the issue (this should be tailored to the specific resource identified in the section). 63 | 6) Finally, make sure to include a disclaimer at the beginning of your response, stating that it was generated by a Gen AI model. 64 | 65 | """ 66 | 67 | prompt = f""" 68 | 69 | {check_name} 70 | 71 | 72 | 73 | {check_description} 74 | 75 | 76 | 77 | {affected_resource_arn} 78 | 79 | """ 80 | 81 | body = json.dumps( 82 | { 83 | "anthropic_version": "bedrock-2023-05-31", 84 | "max_tokens": 2000, 85 | "temperature": 0.5, 86 | "system": system_prompt, 87 | "messages": [ 88 | {"role": "user", "content": [{"type": "text", "text": prompt}]} 89 | ], 90 | } 91 | ) 92 | 93 | response = bedrock_runtime.invoke_model( 94 | body=body, 95 | modelId=model_id, 96 | accept="application/json", 97 | contentType="application/json", 98 | ) 99 | 100 | response_body = json.loads(response["body"].read()) 101 | answer = response_body["content"][0]["text"] 102 | 103 | return {"Answer": answer} 104 | 105 | 106 | def handler(events, context): 107 | affected_resource_arn = events["affectedResourceArn"] 108 | check_name = events["checkName"] 109 | model_id = events["modelId"] 110 | 111 | # Get the Trusted Advisor check details 112 | check_details = get_trusted_advisor_check(check_name) 113 | 114 | # Invoke the Bedrock model and get the response 115 | model_response = invoke_bedrock_and_extract( 116 | affected_resource_arn, check_name, model_id, check_details 117 | ) 118 | 119 | # Return the model response 120 | return model_response 121 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { readFileSync } from "node:fs"; 3 | import { defineConfig, loadEnv } from "vite"; 4 | import react from "@vitejs/plugin-react"; 5 | // https://vitejs.dev/config/ 6 | export default defineConfig(({ mode }) => { 7 | setEnv(mode); 8 | return { 9 | plugins: [ 10 | react(), 11 | envPlugin(), 12 | devServerPlugin(), 13 | sourcemapPlugin(), 14 | buildPathPlugin(), 15 | basePlugin(), 16 | importPrefixPlugin(), 17 | htmlPlugin(mode), 18 | ], 19 | }; 20 | }); 21 | function setEnv(mode) { 22 | Object.assign(process.env, loadEnv(mode, ".", ["REACT_APP_", "NODE_ENV", "PUBLIC_URL"])); 23 | process.env.NODE_ENV ||= mode; 24 | const { homepage } = JSON.parse(readFileSync("package.json", "utf-8")); 25 | process.env.PUBLIC_URL ||= homepage 26 | ? `${homepage.startsWith("http") || homepage.startsWith("/") 27 | ? homepage 28 | : `/${homepage}`}`.replace(/\/$/, "") 29 | : ""; 30 | } 31 | // Expose `process.env` environment variables to your client code 32 | // Migration guide: Follow the guide below to replace process.env with import.meta.env in your app, you may also need to rename your environment variable to a name that begins with VITE_ instead of REACT_APP_ 33 | // https://vitejs.dev/guide/env-and-mode.html#env-variables 34 | function envPlugin() { 35 | return { 36 | name: "env-plugin", 37 | config(_, { mode }) { 38 | const env = loadEnv(mode, ".", ["REACT_APP_", "NODE_ENV", "PUBLIC_URL"]); 39 | return { 40 | define: Object.fromEntries(Object.entries(env).map(([key, value]) => [ 41 | `process.env.${key}`, 42 | JSON.stringify(value), 43 | ])), 44 | }; 45 | }, 46 | }; 47 | } 48 | // Setup HOST, SSL, PORT 49 | // Migration guide: Follow the guides below 50 | // https://vitejs.dev/config/server-options.html#server-host 51 | // https://vitejs.dev/config/server-options.html#server-https 52 | // https://vitejs.dev/config/server-options.html#server-port 53 | function devServerPlugin() { 54 | return { 55 | name: "dev-server-plugin", 56 | config(_, { mode }) { 57 | const { HOST, PORT, HTTPS, SSL_CRT_FILE, SSL_KEY_FILE } = loadEnv(mode, ".", ["HOST", "PORT", "HTTPS", "SSL_CRT_FILE", "SSL_KEY_FILE"]); 58 | const https = HTTPS === "true"; 59 | return { 60 | server: { 61 | host: HOST || "localhost", 62 | port: parseInt(PORT || "3000", 10), 63 | open: true, 64 | ...(https && 65 | SSL_CRT_FILE && 66 | SSL_KEY_FILE && { 67 | https: { 68 | cert: readFileSync(resolve(SSL_CRT_FILE)), 69 | key: readFileSync(resolve(SSL_KEY_FILE)), 70 | }, 71 | }), 72 | }, 73 | }; 74 | }, 75 | }; 76 | } 77 | // Migration guide: Follow the guide below 78 | // https://vitejs.dev/config/build-options.html#build-sourcemap 79 | function sourcemapPlugin() { 80 | return { 81 | name: "sourcemap-plugin", 82 | config(_, { mode }) { 83 | const { GENERATE_SOURCEMAP } = loadEnv(mode, ".", [ 84 | "GENERATE_SOURCEMAP", 85 | ]); 86 | return { 87 | build: { 88 | sourcemap: GENERATE_SOURCEMAP === "true", 89 | }, 90 | }; 91 | }, 92 | }; 93 | } 94 | // Migration guide: Follow the guide below 95 | // https://vitejs.dev/config/build-options.html#build-outdir 96 | function buildPathPlugin() { 97 | return { 98 | name: "build-path-plugin", 99 | config(_, { mode }) { 100 | const { BUILD_PATH } = loadEnv(mode, ".", [ 101 | "BUILD_PATH", 102 | ]); 103 | return { 104 | build: { 105 | outDir: BUILD_PATH || "build", 106 | }, 107 | }; 108 | }, 109 | }; 110 | } 111 | // Migration guide: Follow the guide below and remove homepage field in package.json 112 | // https://vitejs.dev/config/shared-options.html#base 113 | function basePlugin() { 114 | return { 115 | name: "base-plugin", 116 | config(_, { mode }) { 117 | const { PUBLIC_URL } = loadEnv(mode, ".", ["PUBLIC_URL"]); 118 | return { 119 | base: PUBLIC_URL || "", 120 | }; 121 | }, 122 | }; 123 | } 124 | // To resolve modules from node_modules, you can prefix paths with ~ 125 | // https://create-react-app.dev/docs/adding-a-sass-stylesheet 126 | // Migration guide: Follow the guide below 127 | // https://vitejs.dev/config/shared-options.html#resolve-alias 128 | function importPrefixPlugin() { 129 | return { 130 | name: "import-prefix-plugin", 131 | config() { 132 | return { 133 | resolve: { 134 | alias: [{ find: /^~([^/])/, replacement: "$1" }], 135 | }, 136 | }; 137 | }, 138 | }; 139 | } 140 | // Replace %ENV_VARIABLES% in index.html 141 | // https://vitejs.dev/guide/api-plugin.html#transformindexhtml 142 | // Migration guide: Follow the guide below, you may need to rename your environment variable to a name that begins with VITE_ instead of REACT_APP_ 143 | // https://vitejs.dev/guide/env-and-mode.html#html-env-replacement 144 | function htmlPlugin(mode) { 145 | const env = loadEnv(mode, ".", ["REACT_APP_", "NODE_ENV", "PUBLIC_URL"]); 146 | return { 147 | name: "html-plugin", 148 | transformIndexHtml: { 149 | order: "pre", 150 | handler(html) { 151 | return html.replace(/%(.*?)%/g, (match, p1) => env[p1] ?? match); 152 | }, 153 | }, 154 | }; 155 | } 156 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/src/utils/toolbox-table-config.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link, Box, SpaceBetween, Button } from '@cloudscape-design/components'; 3 | import { replaceHtmlTags } from './utilities'; 4 | 5 | export function toolboxGetMatchesCountText(count) { 6 | return count === 1 ? `1 match` : `${count} matches`; 7 | } 8 | 9 | function toolboxCreateLabelFunction(columnName) { 10 | return ({ sorted, descending }) => { 11 | const sortState = sorted ? `sorted ${descending ? 'descending' : 'ascending'}` : 'not sorted'; 12 | return `${columnName}, ${sortState}.`; 13 | }; 14 | } 15 | 16 | const toolboxFullColumnDefinitions = [ 17 | { 18 | id: "name", 19 | header: "Name", 20 | cell: (item) => [{parseInt(item.i) + 1}] {item.TrustedAdvisorCheckName} ({item.resourceId}) || "-", 21 | ariaLabel: toolboxCreateLabelFunction('Name'), 22 | sortingField: 'name' 23 | }, 24 | { 25 | id: "description", 26 | header: "Description", 27 | cell: (item) => {replaceHtmlTags(item.TrustedAdvisorCheckDesc)} || "-", 28 | ariaLabel: toolboxCreateLabelFunction('Description'), 29 | sortingField: 'description' 30 | }, 31 | { 32 | id: "bestPractice", 33 | header: "Best Practice", 34 | cell: (item) => ( 35 | {item.WABestPracticeTitle || "-"} 36 | ), 37 | ariaLabel: toolboxCreateLabelFunction('Best Practice'), 38 | sortingField: 'bestPractice' 39 | }, 40 | { 41 | id: "pillar", 42 | header: "Pillar", 43 | cell: (item) => item.WAPillarId || "-", 44 | ariaLabel: toolboxCreateLabelFunction('Pillar'), 45 | sortingField: 'pillar' 46 | }, 47 | { 48 | id: "businessRisk", 49 | header: "Business Risk", 50 | cell: (item) => item.WABestPracticeRisk || "-", 51 | ariaLabel: toolboxCreateLabelFunction('Business Risk'), 52 | sortingField: 'businessRisk' 53 | }, 54 | { 55 | id: "taCheckStatus", 56 | header: "Check Status", 57 | cell: (item) => item.resultStatus || "-", 58 | ariaLabel: toolboxCreateLabelFunction('Check Status'), 59 | sortingField: 'taCheckStatus' 60 | }, 61 | { 62 | id: "resourceId", 63 | header: "Resource at Risk", 64 | cell: (item) => item.resourceId || "-", 65 | ariaLabel: toolboxCreateLabelFunction('Resource at Risk'), 66 | sortingField: 'resourceId' 67 | }, 68 | { 69 | id: "resourceRaw", 70 | header: "Resource at Risk (Raw)", 71 | cell: (item) => JSON.stringify(item.FlaggedResources, null, 4) || "-", 72 | ariaLabel: toolboxCreateLabelFunction('Resource at Risk (Raw)'), 73 | sortingField: 'resourceRaw' 74 | }, 75 | ]; 76 | 77 | export const toolboxPaginationLabels = { 78 | nextPageLabel: 'Next page', 79 | pageLabel: pageNumber => `Go to page ${pageNumber}`, 80 | previousPageLabel: 'Previous page', 81 | }; 82 | 83 | const pageSizePreference = { 84 | title: 'Select page size', 85 | options: [ 86 | { value: 10, label: '10 resources' }, 87 | { value: 20, label: '20 resources' }, 88 | ], 89 | }; 90 | 91 | const visibleContentPreference = { 92 | title: 'Select visible content', 93 | options: [ 94 | { 95 | label: 'Main properties', 96 | options: toolboxFullColumnDefinitions.map(({ id, header }) => ({ id, label: header, editable: id !== 'name' })), 97 | }, 98 | ], 99 | }; 100 | 101 | export const toolboxCollectionPreferencesProps = { 102 | pageSizePreference, 103 | visibleContentPreference, 104 | cancelLabel: 'Cancel', 105 | confirmLabel: 'Confirm', 106 | title: 'Preferences', 107 | }; 108 | 109 | export const toolboxFilteringProperties = [ 110 | { 111 | propertyLabel: 'Name', 112 | key: 'TrustedAdvisorCheckName', 113 | groupValuesLabel: 'Name values', 114 | operators: [':', '!:', '=', '!=', '^'], 115 | }, 116 | { 117 | propertyLabel: 'Best Practice', 118 | key: 'WABestPracticeId', 119 | groupValuesLabel: 'Best Practice values', 120 | operators: [':', '!:', '=', '!=', '^'], 121 | }, 122 | { 123 | propertyLabel: 'Pillar', 124 | key: 'WAPillarId', 125 | groupValuesLabel: 'Pillar values', 126 | operators: [':', '!:', '=', '!=', '^'], 127 | }, 128 | { 129 | propertyLabel: 'Business Risk', 130 | key: 'WABestPracticeRisk', 131 | groupValuesLabel: 'Business Risk values', 132 | operators: [':', '!:', '=', '!=', '^'], 133 | }, 134 | { 135 | propertyLabel: 'Check Status', 136 | key: 'resultStatus', 137 | groupValuesLabel: 'Check Status values', 138 | operators: [':', '!:', '=', '!=', '^'], 139 | }, 140 | { 141 | propertyLabel: 'Resource at Risk', 142 | key: 'resourceId', 143 | groupValuesLabel: 'Resource at Risk values', 144 | operators: [':', '!:', '=', '!=', '^'], 145 | }, 146 | { 147 | propertyLabel: 'Region', 148 | key: 'region', 149 | groupValuesLabel: 'Region values', 150 | operators: [':', '!:', '=', '!=', '^'], 151 | }, 152 | ]; 153 | 154 | export const propertyFilterI18nStrings = { 155 | filteringAriaLabel: "Find checks", 156 | filteringPlaceholder: "Find checks", 157 | clearFiltersText: "Clear filters", 158 | cancelActionText: "Cancel", 159 | applyActionText: "Apply", 160 | operationAndText: "and", 161 | operationOrText: "or", 162 | operatorContainsText: "Contains", 163 | operatorDoesNotContainText: "Does not contain", 164 | operatorEqualsText: "Equals", 165 | operatorDoesNotEqualText: "Does not equal", 166 | operatorStartsWithText: "Starts with", 167 | }; 168 | 169 | export const TableEmptyState = ({ resourceName }) => ( 170 | 171 | 172 |
173 | No {resourceName.toLowerCase()} loaded 174 |
175 |
176 |
177 | ); 178 | 179 | export const TableNoMatchState = ({ onClearFilter }) => ( 180 | 181 | 182 |
183 | No matches 184 |
185 | 186 |
187 |
188 | ); 189 | -------------------------------------------------------------------------------- /TA-WellArchitected/tawa-eisenhower-matrix-app/src/example-styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 20px; 3 | font-family: sans-serif; 4 | zoom: 85%; 5 | } 6 | #content { 7 | width: 100%; 8 | } 9 | .react-grid-layout { 10 | margin-top: 10px; 11 | } 12 | .verticalLine { 13 | position: absolute; 14 | background-color: gray; 15 | width: 3px; 16 | top: 8%; 17 | bottom: 0%; 18 | left: 50%; 19 | z-index: -1; 20 | } 21 | .horizontalLine { 22 | position: absolute; 23 | display: flex; 24 | flex-direction: row; 25 | top: 52%; 26 | z-index: -1; 27 | } 28 | /* High Impact */ 29 | .yAxisTop { 30 | left: 48%; 31 | top: 8%; 32 | position: absolute; 33 | color: red; 34 | background-color: rgb(255, 255, 255); 35 | font-weight: bold; 36 | } 37 | /* Low Impact */ 38 | .yAxisBottom { 39 | left: 48%; 40 | bottom: 0%; 41 | position: absolute; 42 | color: green; 43 | background-color: rgb(255, 255, 255); 44 | font-weight: bold; 45 | } 46 | /* High Urgency */ 47 | .xAxisLeft { 48 | left: 1%; 49 | bottom: 44.5%; 50 | position: absolute; 51 | color: red; 52 | background-color: rgb(255, 255, 255); 53 | font-weight: bold; 54 | } 55 | /* Low Urgency */ 56 | .xAxisRight { 57 | left: 94%; 58 | bottom: 44.5%; 59 | position: absolute; 60 | color: green; 61 | background-color: rgb(255, 255, 255); 62 | font-weight: bold; 63 | } 64 | /* Do it Now */ 65 | .doItNow { 66 | left: 24%; 67 | top: 30%; 68 | position: absolute; 69 | color: rgb(82, 82, 82); 70 | background-color: rgb(255, 255, 255); 71 | font-weight: bold; 72 | } 73 | /* Do it Next */ 74 | .doItNext { 75 | left: 24%; 76 | top: 75%; 77 | position: absolute; 78 | color: rgb(82, 82, 82); 79 | background-color: rgb(255, 255, 255); 80 | font-weight: bold; 81 | } 82 | /* Schedule it First */ 83 | .scheduleFirst { 84 | left: 70%; 85 | top: 30%; 86 | position: absolute; 87 | color: rgb(82, 82, 82); 88 | background-color: rgb(255, 255, 255); 89 | font-weight: bold; 90 | } 91 | /* Schedule it Last */ 92 | .scheduleLast { 93 | left: 70%; 94 | top: 75%; 95 | position: absolute; 96 | color: rgb(82, 82, 82); 97 | background-color: rgb(255, 255, 255); 98 | font-weight: bold; 99 | } 100 | .hidden { 101 | opacity: 0; 102 | } 103 | .polarisInputButton { 104 | position: absolute; 105 | } 106 | .inputButton { 107 | z-index: 1; 108 | width: 100%; 109 | height: 100%; 110 | margin-left: -29%; 111 | margin-top: -3%; 112 | position: absolute; 113 | } 114 | .column { 115 | float: left; 116 | width: 50%; 117 | } 118 | .styled-table { 119 | border-collapse: collapse; 120 | margin: 25px 0; 121 | font-size: 0.9em; 122 | font-family: sans-serif; 123 | min-width: 400px; 124 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); 125 | border: 1px solid black; 126 | } 127 | .styled-table thead tr { 128 | background-color: #f79b02; 129 | color: #ffffff; 130 | text-align: left; 131 | border: 1px solid black; 132 | } 133 | .styled-table th, 134 | .styled-table td { 135 | padding: 12px 15px; 136 | border: 1px solid black; 137 | vertical-align:top; 138 | } 139 | .styled-table tbody tr { 140 | border-bottom: 1px solid #dddddd; 141 | border: 1px solid black; 142 | } 143 | .styled-table tbody tr:nth-of-type(even) { 144 | background-color: #f3f3f3; 145 | border: 1px solid black; 146 | } 147 | .styled-table tbody tr:last-of-type { 148 | border-bottom: 2px solid #009879; 149 | border: 1px solid black; 150 | } 151 | .styled-table tbody tr.active-row { 152 | font-weight: bold; 153 | color: #009879; 154 | border: 1px solid black; 155 | } 156 | .layoutJSON { 157 | background: #ddd; 158 | border: 1px solid black; 159 | margin-top: 10px; 160 | padding: 10px; 161 | } 162 | .columns { 163 | -moz-columns: 120px; 164 | -webkit-columns: 120px; 165 | columns: 120px; 166 | } 167 | .react-grid-item { 168 | box-sizing: border-box; 169 | } 170 | .react-grid-item:not(.react-grid-placeholder) { 171 | /* background: #ccccccdd; */ 172 | /* border: 1px solid black; */ 173 | } 174 | .react-grid-item.resizing { 175 | opacity: 0.9; 176 | } 177 | .react-grid-item.static { 178 | background: #cce; 179 | } 180 | .react-grid-item .text { 181 | font-size: 20px; 182 | text-align: center; 183 | position: absolute; 184 | top: 0; 185 | bottom: 0; 186 | left: 0; 187 | right: 0; 188 | margin: auto; 189 | height: 24px; 190 | } 191 | .react-grid-item .minMax { 192 | font-size: 12px; 193 | } 194 | .react-grid-item .add { 195 | cursor: pointer; 196 | } 197 | .react-grid-dragHandleExample { 198 | cursor: move; /* fallback if grab cursor is unsupported */ 199 | cursor: grab; 200 | cursor: -moz-grab; 201 | cursor: -webkit-grab; 202 | } 203 | 204 | .toolbox { 205 | background-color: #ffffff; 206 | width: 100%; 207 | height: 130px; 208 | overflow-y: scroll; 209 | } 210 | 211 | .toolbox-tableview { 212 | background-color: #ffffff; 213 | width: 100%; 214 | height: 260px; 215 | overflow-y: scroll; 216 | } 217 | 218 | .hide-button { 219 | cursor: pointer; 220 | position: absolute; 221 | font-size: 30px; 222 | top: -7%; 223 | right: 91%; 224 | z-index: 10; 225 | } 226 | 227 | .toolbox__title { 228 | font-size: 24px; 229 | margin-bottom: 5px; 230 | } 231 | .toolbox__items { 232 | display: block; 233 | } 234 | .toolbox__items__item { 235 | display: inline-block; 236 | text-align: center; 237 | line-height: 40px; 238 | cursor: pointer; 239 | width: auto; 240 | height: 40px; 241 | padding: 10px; 242 | margin: 5px; 243 | /* border: 1px solid black; 244 | background-color: #ddd; */ 245 | } 246 | /* Tooltip text */ 247 | .toolbox__items__item .toolbox__items__item_tooltip { 248 | visibility: hidden; 249 | width: 200px; 250 | background-color: black; 251 | color: #fff; 252 | text-align: center; 253 | padding: 5px 0; 254 | border-radius: 6px; 255 | position: absolute; 256 | top: 10%; 257 | z-index: 1; 258 | line-height: normal; 259 | } 260 | .toolbox__items__item:hover .toolbox__items__item_tooltip { 261 | visibility: visible; 262 | } 263 | .droppable-element { 264 | width: 150px; 265 | text-align: center; 266 | background: #fdd; 267 | border: 1px solid black; 268 | margin: 10px 0; 269 | padding: 10px; 270 | } -------------------------------------------------------------------------------- /S3IncompleteMPUAbort/ta-s3-incomplete-mpu-abort/tests/integration/test_ta_event.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from time import sleep, time 4 | from unittest import TestCase 5 | 6 | import boto3 7 | from botocore.exceptions import ClientError 8 | 9 | """ 10 | Make sure env variable AWS_SAM_STACK_NAME exists with the name of the stack we are going to test. 11 | """ 12 | 13 | class TestS3LifecyclePolicy(TestCase): 14 | """ 15 | This integration test will create an S3 bucket without a lifecycle policy, 16 | trigger the Trusted Advisor check, and verify the lambda function is invoked 17 | by checking the cloudwatch log and the applied lifecycle policy. 18 | The S3 bucket will be deleted when the test completes. 19 | """ 20 | 21 | function_name: str 22 | bucket_name: str # temporary S3 bucket name 23 | 24 | @classmethod 25 | def get_and_verify_stack_name(cls) -> str: 26 | stack_name = os.environ.get("AWS_SAM_STACK_NAME") 27 | if not stack_name: 28 | raise Exception( 29 | "Cannot find env var AWS_SAM_STACK_NAME. \n" 30 | "Please setup this environment variable with the stack name where we are running integration tests." 31 | ) 32 | 33 | # Verify stack exists 34 | client = boto3.client("cloudformation") 35 | try: 36 | client.describe_stacks(StackName=stack_name) 37 | except Exception as e: 38 | raise Exception( 39 | f"Cannot find stack {stack_name}. \n" f'Please make sure stack with the name "{stack_name}" exists.' 40 | ) from e 41 | 42 | return stack_name 43 | 44 | @classmethod 45 | def setUpClass(cls) -> None: 46 | stack_name = TestS3LifecyclePolicy.get_and_verify_stack_name() 47 | 48 | client = boto3.client("cloudformation") 49 | response = client.list_stack_resources(StackName=stack_name) 50 | resources = response["StackResourceSummaries"] 51 | function_resources = [ 52 | resource for resource in resources if resource["LogicalResourceId"] == "ApplyLifecycleFunction" 53 | ] 54 | if not function_resources: 55 | raise Exception("Cannot find ApplyLifecycleFunction") 56 | 57 | cls.function_name = function_resources[0]["PhysicalResourceId"] 58 | 59 | def setUp(self) -> None: 60 | self.s3_client = boto3.client("s3") 61 | self.bucket_name = f"test-bucket-{int(time())}" 62 | self.s3_client.create_bucket(Bucket=self.bucket_name) 63 | 64 | def tearDown(self) -> None: 65 | # Delete all objects in the bucket 66 | response = self.s3_client.list_objects_v2(Bucket=self.bucket_name) 67 | if 'Contents' in response: 68 | for obj in response['Contents']: 69 | self.s3_client.delete_object(Bucket=self.bucket_name, Key=obj['Key']) 70 | 71 | # Delete the bucket 72 | self.s3_client.delete_bucket(Bucket=self.bucket_name) 73 | 74 | def test_s3_lifecycle_policy(self): 75 | log_group_name = f"/aws/lambda/{self.function_name}" 76 | 77 | # Trigger Trusted Advisor check (this is a placeholder, as we can't directly trigger TA checks) 78 | # In a real scenario, you might need to wait for the next TA refresh or use the Support API if available 79 | self._simulate_ta_check() 80 | 81 | # Wait for the Lambda function to be invoked (adjust the wait time as needed) 82 | sleep(30) 83 | 84 | # Verify the lifecycle policy was applied 85 | try: 86 | response = self.s3_client.get_bucket_lifecycle_configuration(Bucket=self.bucket_name) 87 | rules = response.get('Rules', []) 88 | self.assertTrue(any(rule.get('ID') == 'AbortIncompleteMultipartUploads' for rule in rules), 89 | "Expected lifecycle rule was not found") 90 | except ClientError as e: 91 | if e.response['Error']['Code'] == 'NoSuchLifecycleConfiguration': 92 | self.fail("No lifecycle configuration was applied") 93 | else: 94 | raise 95 | 96 | # Verify Lambda function logs 97 | self._verify_lambda_logs(log_group_name) 98 | 99 | def _simulate_ta_check(self): 100 | # This is a placeholder method to simulate triggering a Trusted Advisor check 101 | # In a real integration test, you might need to use the AWS Support API to refresh the check 102 | # or wait for the next scheduled refresh 103 | pass 104 | 105 | def _verify_lambda_logs(self, log_group_name: str): 106 | # Verify that the Lambda function was invoked and processed our test bucket 107 | retries = 5 108 | start_time = int(time() - 300) * 1000 # Look at logs from the last 5 minutes 109 | while retries >= 0: 110 | log_stream_name = self._get_latest_log_stream_name(log_group_name) 111 | if not log_stream_name: 112 | sleep(10) 113 | continue 114 | 115 | match_events = self._get_matched_events(log_group_name, log_stream_name, start_time) 116 | if match_events: 117 | return 118 | else: 119 | logging.info(f"Cannot find matching events containing bucket name {self.bucket_name}, waiting") 120 | retries -= 1 121 | sleep(10) 122 | 123 | self.fail(f"Cannot find matching events containing bucket name {self.bucket_name} after 5 retries") 124 | 125 | def _get_latest_log_stream_name(self, log_group_name: str): 126 | client = boto3.client("logs") 127 | try: 128 | response = client.describe_log_streams( 129 | logGroupName=log_group_name, 130 | orderBy="LastEventTime", 131 | descending=True, 132 | ) 133 | except ClientError as e: 134 | if e.response["Error"]["Code"] == "ResourceNotFoundException": 135 | logging.info(f"Cannot find log group {log_group_name}, waiting") 136 | return None 137 | raise e 138 | 139 | log_streams = response["logStreams"] 140 | self.assertTrue(log_streams, "Cannot find log streams") 141 | 142 | return log_streams[0]["logStreamName"] 143 | 144 | def _get_matched_events(self, log_group_name, log_stream_name, start_time): 145 | client = boto3.client("logs") 146 | response = client.get_log_events( 147 | logGroupName=log_group_name, 148 | logStreamName=log_stream_name, 149 | startTime=start_time, 150 | endTime=int(time()) * 1000, 151 | startFromHead=False, 152 | ) 153 | events = response["events"] 154 | return [event for event in events if self.bucket_name in event["message"]] -------------------------------------------------------------------------------- /TA-Integrations/TA-Red-Cost-Slack-Webhook/README.md: -------------------------------------------------------------------------------- 1 | ## Slack Integration to post Trusted Advisor Red Findings, w/ Monthly Estimated Cost Savings 2 | 3 | ##### Authors: Manas Satpathi and Sandeep Mohanty 4 | 5 | ### Description/Use-case 6 | 7 | Use this solution to integrate AWS Trusted Advisor (TA) with your preferred Slack channel. It posts TA High Risk (RED) findings, and the estimated monthly savings reported by Trusted Advisor, for an AWS Account. 8 | 9 | Use this automated solution to get notified for AWS Trusted Advisor findings with status red/error (actions required). High priority Trusted Advisor checks require further investigation as they help you secure and optimize your account to align with AWS best practices. Notifications are classified by risk category (Security, Fault Tolerance, Performance, Cost and Service Limits) and sent to Slack at a preconfigured interval. Configure the notification interval as a scheduled event rule in Amazon EventBridge. Modify the included python script to customize the solution further to meet your requirements. 10 | 11 | ### An example Slack channel posting 12 | 13 | === Summary of TA High Risk (RED) Findings === 14 | Total High Risk (RED) Findings: 18 (Security: 8, Fault-Tolerance: 4, Performance: 6) 15 | Total Estimated Monthly Savings: $3,200.32. 16 | 17 | HIGH RISK - [security] AWS Lambda Functions Using Deprecated Runtimes 18 | HIGH RISK - [security] Security Groups - Specific Ports Unrestricted 19 | HIGH RISK - [security] Security Groups - Unrestricted Access 20 | HIGH RISK - [fault_tolerance] Amazon EBS Snapshots 21 | HIGH RISK - [fault_tolerance] Auto Scaling Group Resources 22 | 23 | ### Solution Overview 24 | Deploying this solution automates the process of checking, and delivery of RED alerts from Trusted Advisor to a preconfigured Slack channel via an incoming webhook. 25 | 26 | The following diagram illustrates how the solution works, 27 | 28 | ![image](./TA-Slack-Arch.PNG) 29 | 30 | ### Prerequisites 31 | Create an incoming Slack Webhook. Incoming webhooks are a simple way to post messages from 3rd party apps into Slack. Creating an incoming Webhook gives you a unique URL to which you send a JSON payload with the message text and some options. The Account you're using should have AWS Business, On-RAMP, or Enterprise Support to get the estimated monthly savings, and additional TA checks. 32 | 33 | Refer to the link below for instructions to create an incoming Slack webhook https://api.slack.com/messaging/webhooks 34 | 35 | Copy and save the Slack webhook URL somewhere in a local text file. We will use it later when deploying the solution. Pass this URL to the Lambda function to post curated events from Trusted Advisor to a Slack channel. 36 | 37 | The webhook URL should look something like this https://hooks.slack.com/workflows/T01234ABCD/A03PQRST/12345678/ab6c20hdWBZabcd 38 | 39 | More information about AWS Trusted Advisor is available here: https://aws.amazon.com/premiumsupport/trustedadvisor/ 40 | 41 | ### How it works 42 | 1. An EventBridge rule is configured to invoke a Lambda function on a pre-configured schedule. For example, hourly, every 12 hours,daily, etc. 43 | 44 | 2. EventBridge invokes the Lambda function and passes the Slack Webhook URL as an argument to the Lambda function.The JSON input event to the Lambda function look something like this, 45 | { 46 | "SlackWebhookURL": "https://hooks.slack.com/workflows/T01234ABCD/A03PQRST/12345678/" 47 | } 48 | 49 | 3. Lambda invokes Trusted Advisor APIs to get the current point in time status of all checks that are in the RED (Action Required) state 50 | 51 | 4. Lambda formats the response from Trusted Advisor, and sends a summary of all checks, along with details of all open high-risk items to Slack, and estimated cost savings for the account. 52 | 53 | ### Deploy the solution 54 | 55 | The solution includes the following files 56 | 57 | **TA-Red-Slack-Webhook.py** 58 | Python script for the Lambda function. 59 | 60 | **CF-TA-Red-Slack-Webhook.yml** 61 | CloudFormation template, with inline code to automatically deploy the solution. 62 | 63 | ### Using CloudFormation 64 | 65 | Use the CloudFormation template CF-TA-Red-Slack-Webhook.yml to deploy the solution. Provide the following inputs to CloudFormation input parameters when prompted, 66 | 67 | **1. ScheduleExpression** 68 | Enter an interval for the scheduler to run, e.g. every 12 hours, etc., or an EventBridge cron pattern. 69 | Ref: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html#eb-rate-expressions 70 | 71 | **2. SlackWebhookURL** 72 | Enter the Slack Webhook URL you created earlier as the input event to the Lambda function in JSON, as shown below, 73 | { 74 | "SlackWebhookURL":"https://hooks.slack.com/workflows/T01234ABCD/A03PQRST/12345678/" 75 | } 76 | 77 | ### Manual Deployment – Step by Step 78 | 79 | The steps below let you manually deploy and customize the solution to meet your needs 80 | 81 | 1. Create a Slack Webhook corresponding to a channel in Slack 82 | 83 | Refer to the link below for instructions to create an incoming Slack webhook 84 | https://api.slack.com/messaging/webhooks 85 | 86 | 2. Create the Lambda function 87 | - Using the provided Python script TA-Red-Slack-Webhook.py create a Lambda function in your account. 88 | - To give Lambda access to Trusted Advisor, add the permissions below to the default Lambda execution role. 89 | { 90 | "Version": "2012-10-17", 91 | "Statement": [ 92 | { 93 | "Action": [ 94 | "support:DescribeTrustedAdvisorCheckRefreshStatuses", 95 | "support:DescribeTrustedAdvisorCheckResult", 96 | "support:DescribeTrustedAdvisorCheckSummaries", 97 | "support:DescribeTrustedAdvisorChecks" 98 | ], 99 | "Resource": "*", 100 | "Effect": "Allow" 101 | } 102 | ] 103 | } 104 | 105 | For additional details on configuring the Lambda execution role, see 106 | 107 | ### AWS Lambda execution role 108 | https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html 109 | 110 | 3. Create an Amazon EventBridge rule 111 | - Create a rule in Amazon EventBridge that runs on a schedule 112 | - You may choose the “default” Event bus for this rule 113 | - Enter an interval for the scheduler to run, e.g. every 12 hours, etc., or an EventBridge cron pattern. 114 | Ref: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html#eb-rate-expressions 115 | 116 | - Configure a Lambda target. Select the Lambda function created in Step 1. 117 | - Under “Additional settings” for the target, select “Constant (JSON text)”, and then enter the input event to the Lambda function in JSON as follows, 118 | { 119 | "SlackWebhookURL": "" 120 | } 121 | - You may leave other settings as default, or refer to the product documentation for additional details at, 122 | Ref: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rules.html 123 | - Review and Create rule. 124 | 125 | For more information about creating an Amazon EventBridge rule that runs on a schedule, see 126 | https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html 127 | 128 | ### Security 129 | 130 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 131 | 132 | ### License 133 | 134 | This library is licensed under the MIT-0 License. See the LICENSE file. 135 | 136 | -------------------------------------------------------------------------------- /UnderutilzedEBSVolumes/TASnapandDeleteEBS-1click.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: >- 3 | Automatically snapshot and delete EBS volumes that have not been attached to an instance for more than 30 days. This action is triggered by Trusted Advisor using Amazon Cloudwatch events and AWS Lambda. 4 | 5 | Warning: do not install or activate this automation in environments where EBS volumes should not be deleted (e.g. where the data is needed immediately and restoring from snapshot is not an option). 6 | Metadata: 7 | LICENSE: >- 8 | Copyright 2019 Amazon Web Services, Inc. or its affiliates. All Rights 9 | Reserved. This file is licensed to you under the AWS Customer Agreement (the 10 | "License"). You may not use this file except in compliance with the License. 11 | A copy of the License is located at http://aws.amazon.com/agreement/ . This 12 | file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 13 | ANY KIND, express or implied. See the License for the specific language 14 | governing permissions and limitations under the License. 15 | Parameters: 16 | Mailto: 17 | Description: Email address to send reports to. 18 | Type: 'String' 19 | Mailfrom: 20 | Description: Email address to send reports from. You must validate this email address in SES. 21 | Type: 'String' 22 | IdleThresh: 23 | Description: Minimum number of days a volume is unattached before being deleted by this automation. 24 | Type: 'String' 25 | Default: '30' 26 | EnableActions: 27 | Description: Indicates whether the automation is active. Note that snapshots are taken regardless of this setting. This controls the active deletion of the volume. 28 | Type: 'String' 29 | Default: 'False' 30 | Resources: 31 | LambdaIAMRole: 32 | Type: 'AWS::IAM::Role' 33 | Properties: 34 | RoleName: 'TAEBSVolumeSnapDelRole' 35 | AssumeRolePolicyDocument: 36 | Version: 2012-10-17 37 | Statement: 38 | - Effect: Allow 39 | Principal: 40 | Service: lambda.amazonaws.com 41 | Action: 'sts:AssumeRole' 42 | Path: / 43 | Policies: 44 | - PolicyName: TAEBSVolumeSnapDel 45 | PolicyDocument: 46 | Version: 2012-10-17 47 | Statement: 48 | - Sid: LambdaLogging 49 | Effect: Allow 50 | Action: 51 | - 'logs:CreateLogGroup' 52 | - 'logs:CreateLogStream' 53 | - 'logs:PutLogEvents' 54 | Resource: 55 | - 'arn:aws:logs:*:*:*' 56 | - Sid: Ec2Actions 57 | Action: 58 | - 'cloudtrail:LookupEvents' 59 | - 'ec2:Describe*' 60 | - 'ec2:DeleteVolume' 61 | Effect: Allow 62 | Resource: '*' 63 | - Sid: SESPerms 64 | Action: 65 | - 'ses:SendEmail' 66 | Effect: Allow 67 | Resource: '*' 68 | - Sid: Snapshots 69 | Action: 70 | - 'ec2:ModifySnapshotAttribute' 71 | - 'ec2:CreateSnapshot' 72 | - 'ec2:DeleteSnapshot' 73 | Effect: Allow 74 | Resource: '*' 75 | - Sid: SnapTags 76 | Action: 77 | - 'ec2:CreateTags' 78 | Effect: Allow 79 | Resource: 'arn:aws:ec2:*::snapshot/*' 80 | - Sid: CWEvents 81 | Action: 82 | - 'events:DescribeRule' 83 | - 'events:PutRule' 84 | - 'events:DeleteRule' 85 | - 'events:EnableRule' 86 | - 'events:DisableRule' 87 | - 'events:PutTargets' 88 | - 'events:RemoveTargets' 89 | Effect: Allow 90 | Resource: 'arn:aws:events:*:*:rule/EBSSnapshotComplete' 91 | - Sid: SNSTopic 92 | Action: 93 | - 'sns:CreateTopic' 94 | - 'sns:Subscribe' 95 | - 'sns:SetTopicAttributes' 96 | - 'sns:Get*' 97 | - 'sns:List*' 98 | Effect: Allow 99 | Resource: '*' 100 | - Sid: MyLambda 101 | Action: 102 | - 'lambda:AddPermission' 103 | Effect: Allow 104 | Resource: 'arn:aws:lambda:us-east-1:*:function:TAEBSVolumeSnapDelete' 105 | TAEBSVolumeSnapDelLambda: 106 | Type: 'AWS::Lambda::Function' 107 | Properties: 108 | FunctionName: "TAEBSVolumeSnapDelete" 109 | Environment: 110 | Variables: 111 | 'IdleThresh': !Ref IdleThresh 112 | 'MailTo': !Ref Mailto 113 | 'FromEmail': !Ref Mailfrom 114 | 'EnableActions': !Ref EnableActions 115 | Code: 116 | S3Bucket: 'aws-trusted-advisor-open-source-us-east-1' 117 | S3Key: 'cloudformation-templates/TAT-UEBS/TAEBSVolDel.py.zip' 118 | Handler: 'TAEBSVolDel.lambda_handler' 119 | Role: !GetAtt 120 | - LambdaIAMRole 121 | - Arn 122 | Runtime: 'python3.7' 123 | Timeout: '120' 124 | SNSTopic: 125 | Type: "AWS::SNS::Topic" 126 | Properties: 127 | DisplayName: TAEBSVolSnapDelTopic 128 | TopicName: TAEBSVolSnapDelTopic 129 | Subscription: 130 | - 131 | Endpoint: 132 | !GetAtt 133 | - TAEBSVolumeSnapDelLambda 134 | - Arn 135 | Protocol: "Lambda" 136 | SNSTopicPolicy: 137 | Type: "AWS::SNS::TopicPolicy" 138 | Properties: 139 | PolicyDocument: 140 | Version: 2012-10-17 141 | Statement: 142 | - Sid: TrustCloudWatchRules 143 | Principal: 144 | Service: 'events.amazonaws.com' 145 | Effect: Allow 146 | Action: 147 | - 'sns:Publish' 148 | Resource: 149 | - !Ref SNSTopic 150 | Topics: 151 | - !Ref SNSTopic 152 | CloudWatchEventRuleTA: 153 | Type: 'AWS::Events::Rule' 154 | Properties: 155 | Name: 'TAUnderutilizedEBS' 156 | Description: Underutilized EBS Volume TA Refresh Notification 157 | EventPattern: 158 | source: 159 | - aws.trustedadvisor 160 | detail-type: 161 | - Trusted Advisor Check Item Refresh Notification 162 | detail: 163 | status: 164 | - WARN 165 | check-name: 166 | - Underutilized Amazon EBS Volumes 167 | State: "ENABLED" 168 | Targets: 169 | - Arn: !Ref SNSTopic 170 | Id: "TAEBSUnderutilized" 171 | DependsOn: "TAEBSVolumeSnapDelLambda" 172 | CloudWatchEventRuleSnap: 173 | Type: 'AWS::Events::Rule' 174 | Properties: 175 | Name: 'EBSSnapshotComplete' 176 | Description: Snapshot complete Notification 177 | EventPattern: 178 | source: 179 | - aws.ec2 180 | detail-type: 181 | - EBS Snapshot Notification 182 | detail: 183 | event: 184 | - createSnapshot 185 | result: 186 | - succeeded 187 | State: "ENABLED" 188 | Targets: 189 | - Arn: !Ref SNSTopic 190 | Id: "SnapshotCompleted" 191 | DependsOn: "TAEBSVolumeSnapDelLambda" 192 | SnsToLambdaPermissions: 193 | Type: "AWS::Lambda::Permission" 194 | Properties: 195 | FunctionName: !GetAtt 196 | - TAEBSVolumeSnapDelLambda 197 | - Arn 198 | Action: "lambda:InvokeFunction" 199 | Principal: "sns.amazonaws.com" 200 | SourceArn: !Ref SNSTopic 201 | --------------------------------------------------------------------------------