├── .github └── workflows │ └── github-repo-stats.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── THIRD-PARTY ├── assisted_log_enabler.py ├── diagrams ├── assisted_log_enabler.png ├── assisted_log_enabler_lb.png └── assisted_log_enabler_s3.png ├── permissions ├── ALE_child_account_role.yaml ├── ALE_permissions_example_cleanup_single.json ├── ALE_permissions_example_multi_account.json └── ALE_permissions_example_single_account.json └── subfunctions ├── ALE_cleanup_single.py ├── ALE_dryrun_multi.py ├── ALE_dryrun_single.py ├── ALE_multi_account.py └── ALE_single_account.py /.github/workflows/github-repo-stats.yml: -------------------------------------------------------------------------------- 1 | name: github-repo-stats 2 | 3 | on: 4 | schedule: 5 | # Run this once per day, towards the end of the day for keeping the most 6 | # recent data point most meaningful (hours are interpreted in UTC). 7 | - cron: "0 23 * * *" 8 | workflow_dispatch: # Allow for running this manually. 9 | 10 | jobs: 11 | j1: 12 | name: github-repo-stats 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: run-ghrs 16 | # Use latest release. 17 | uses: jgehrcke/github-repo-stats@RELEASE 18 | with: 19 | ghtoken: ${{ secrets.ghrs_github_api_token }} 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.0] - 2021-05-04 4 | 5 | ### Added 6 | * Assisted Log Enabler for AWS 7 | * Main file 8 | * Subfuction files 9 | * Diagram files 10 | * LICENSE file 11 | * README file 12 | * NOTICE file 13 | * THIRD-PARTY file 14 | * CODE_OF_CONDUCT file 15 | * CONTRIBUTING file 16 | 17 | ## [1.0.1] - 2021-05-04 18 | 19 | ### Added 20 | * PutPublicAccessBlock for Amazon S3 bucket created (single-account version). 21 | * Step-by-step instructions for running Assisted Log Enabler for AWS in single-account mode using AWS CloudShell within the README file. 22 | 23 | ## [1.0.2] - 2021-05-04 24 | 25 | ### Added 26 | * Error handling for AWS Organizations API call within multi-account version. 27 | 28 | ## [1.1.0] - 2021-05-11 29 | 30 | ### Added 31 | * Route 53 Resolver Query Logging for single-account mode. 32 | 33 | ### Fixed 34 | * Issue with Amazon S3 bucket creation. 35 | 36 | ### Changed 37 | * IAM Permissions examples. 38 | * Diagram to reflect Route 53 Resolver Query Logging. 39 | * Diagram to correctly reflect Amazon EKS Audit & Authentication logs going to AWS CloudWatch. 40 | * AWS CloudFormation template for deploying multi-account IAM roles. 41 | * README documentation. 42 | 43 | ## [1.1.1] - 2021-05-14 44 | 45 | ### Added 46 | * Multi-Account support for Route 53 Resolver Query Logging. 47 | * Multi-Account support for Amazon EKS Audit & Authenticator Logs. 48 | * Step-by-step instructions for running Assisted Log Enabler for AWS in multi-account mode using AWS CloudShell within the README file. 49 | 50 | ### Fixed 51 | * Issue with log file output. 52 | 53 | ### Changed 54 | * IAM Permissions examples. 55 | * AWS CloudFormation template. 56 | 57 | ## [1.1.2] - 2021-05-17 58 | 59 | ### Added 60 | * PutPublicAccessBlock for Amazon S3 bucket created (multi-account version). 61 | 62 | ### Changed 63 | * Updates to IAM Permissions examples. 64 | * Added examples for both single account and multi-account. 65 | * README documentation. 66 | 67 | ## [1.1.3] - 2021-05-18 68 | 69 | ### Fixed 70 | * Documentation details about iam:CreateServiceLinkedRole. 71 | 72 | ## [1.1.4] - 2021-05-25 73 | 74 | ### Added 75 | * Cleanup details in the README file. 76 | * Cost details in the README file. 77 | 78 | ## [1.1.5] - 2021-06-04 79 | 80 | ### Added 81 | * ap-northeast-3 Osaka to function code. 82 | 83 | ### Changed 84 | * Log output file name to show clear date. 85 | * Datetime output to show UTC time explicitly. 86 | * README documentation. 87 | 88 | ## [1.2.0] - 2021-06-21 89 | 90 | ### Added 91 | * Options for running the code for individual supported AWS services. 92 | * Maintained the ability to run for all services currently supported at once. 93 | * Documentation to reflect new supported commands. 94 | 95 | ### Changed 96 | * README documentation. 97 | 98 | ## [1.2.1] - 2021-06-29 99 | 100 | ### Added 101 | * CHANGELOG file 102 | 103 | ## [1.3.0] - 2021-07-08 104 | 105 | ### Added 106 | * Code for cleaning up AWS resources created by Assisted Log Enabler for AWS. 107 | * Amazon Route 53 Resolver Query Logging in single account mode is only currently supported. 108 | * Options for running cleanup mode within the main function. 109 | * IAM Permissions example for cleanup operations. 110 | * Information within the Step-by-Step instructions for multi-account to reflect details about AWS CloudFormation StackSets Delegated Administrator. 111 | 112 | ### Changed 113 | * README documentation. 114 | * Updated Cleanup section to reflect new cleanup capabilities. 115 | * Updated IAM Permissions examples within the README. 116 | * AWS CloudFormation template for deploying IAM Permissions to run cleanup code. 117 | * Header in files to reflect "Assisted Log Enabler for AWS", instead of "Assisted Log Enabler (ALE)". 118 | 119 | ## [1.3.1] - 2021-07-22 120 | 121 | ### Added 122 | * Randomization to the end of the Amazon S3 bucket name in both single and multi account modes. 123 | * Instructions for deploying the AWS CloudFormation Stack individually, within the AWS Organizations root account for multi-account deployment. 124 | * Link for the AWS Security Analytics Bootstrap within the README. 125 | 126 | ### Changed 127 | * Feedback section within README to contain link to Issues section. 128 | 129 | ## [1.3.2] - 2021-08-03 130 | 131 | ### Changed 132 | * README Documentation 133 | * Removed unzip steps from single and multi-account instructions. 134 | * Minor updates to various service names. 135 | 136 | ## [1.3.3] - 2021-08-10 137 | 138 | ### Added 139 | * README Documentation 140 | * Added details for the point-and-clock Amazon Athena integration for VPC Flow Logs. 141 | 142 | ## [1.4.0] - 2021-08-13 143 | 144 | ### Added 145 | * Dry Run mode for both single and multi-account modes. 146 | * Added README Documentation for Dry Run modes. 147 | 148 | ## [1.4.1] - 2021-08-23 149 | 150 | ### Added 151 | * Tagging for VPC Flow Log Resources in single account mode. 152 | * Cleanup options for VPC Flow Logs and CloudTrails created by Assisted Log Enabler for AWS. 153 | * README Documentation 154 | * Added details in the Cleanup section to reflect VPC Flow Logs and CloudTrail commands. 155 | * Added section about the Shared Responsibility Model. 156 | 157 | ## [1.4.1b] - 2021-08-24 158 | 159 | ### Added 160 | * Condition statements for if no options were selected during Dry Run and Cleanup modes. 161 | 162 | ## [1.4.2] - 2021-09-20 163 | 164 | ### Added 165 | * CloudTrail tags to show that the trail is created by Assisted Log Enabler for AWS. 166 | 167 | ### Changed 168 | * CloudTrail name to be more descriptive that it's created by Assisted Log Enabler for AWS. 169 | 170 | ## [1.4.3] - 2021-11-03 171 | 172 | ### Changed 173 | * References to Team DragonCat are now referred to Customer Incident Response Team (CIRT). 174 | * Various argparse help message to be more descriptive. 175 | 176 | ## [1.5.0] - 2021-12-01 177 | 178 | ### Added 179 | * Ability to turn on Amazon S3 Server Access logs within single and multi-account modes. 180 | * Added Dry Run capabilities for Amazon S3 Server Access logs within single and multi-account modes. 181 | * Added Cleanup capabilities for Amazon S3 Server Access logs created by Assisted Log Enabler within single account mode. 182 | * Updated help (-h) message example within the README. 183 | * Permissions examples for enabling Amazon S3 Server Access logs within the permissions directory. 184 | * Diagram for Amazon S3 Server Access Logs within the README. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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, or recently closed, 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 *main* 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' 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](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Assisted Log Enabler for AWS 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Assisted Log Enabler for AWS - Find resources that are not logging, and turn them on. 2 | Assisted Log Enabler for AWS is for customers who do not have logging turned on for various services, and lack knowledge of best practices and/or how to turn them on. 3 | 4 | With Assisted Log Enabler for AWS, logging is turned on automatically for the various AWS Services for a customer: 5 | * Amazon VPC Flow Logs (Single Account and Multi-Account using AWS Organizations) 6 | * AWS CloudTrail (Single Account Only) 7 | * Amazon Elastic Kubernetes Service (EKS) Audit and Authenticator Logs (Single Account and Multi-Account using AWS Organizations) 8 | * Amazon Route 53 Resolver Query Logs (Single Account and Multi-Account using AWS Organizations) 9 | * Amazon S3 Server Access Logs (Single Account and Multi-Account using AWS Organizations) 10 | * NEW! Elastic Load Balancing Access Logs (Single Account and Multi-Account using AWS Organizations) 11 | * NEW! GuardDuty Enablement and Export Findings (Single Account and Multi-Account using AWS Organizations) 12 | * NEW! WAFv2 Logging (Single Account and Multi-Account using AWS Organizations) 13 | 14 | Link to related AWS Open Source Blog Post: [Introducing Assisted Log Enabler for AWS](https://aws.amazon.com/blogs/opensource/introducing-assisted-log-enabler-for-aws/) 15 | 16 | ## Use Case 17 | Logging information is important for troubleshooting issues and analyzing performance, and when Amazon Web Services (AWS) customers do not have logging turned on, the ability to assist them becomes limited, to the point that performing analysis may be impossible. In some cases, customers may not have the technical expertise needed to set up logging properly for the various AWS services. 18 | 19 | Assisted Log Enabler for AWS is designed to ease the customer burden of learning how to turn on logs in the middle of a security incident. Assisted Log Enabler for AWS performs the work of creating an Amazon Simple Storage Service (S3) bucket, checking the services to see if logging is turned on, and activating logging when it's found to be off. 20 | 21 | When this work is performed, the customer can be assured that logging within their AWS environment is active to facilitate the investigation of future (and possibly ongoing) security incidents. 22 | 23 | ## Diagram 24 | The following is a simple diagram on how Assisted Log Enabler for AWS works in a single account, in order to turn on logging for customers. 25 | 26 | ![Alt text](diagrams/assisted_log_enabler.png) 27 | 28 | The following is a simple diagram on how Assisted Log Enabler for AWS works with turning on Amazon S3 Server Access Logging in a single account: 29 | 30 | ![Alt text](diagrams/assisted_log_enabler_s3.png) 31 | 32 | The following is a simple diagram on how Assisted Log Enabler for AWS works with turning on Elastic Load Balancing Access Logging in a single account: 33 | 34 | ![Alt text](diagrams/assisted_log_enabler_lb.png) 35 | 36 | ## Prerequisites 37 | ### Permissions 38 | The following permissions are needed within AWS IAM for Assisted Log Enabler for AWS to run. Please see each section for a breakdown per AWS Service and functionality: 39 | ``` 40 | # All permissions used within Assisted Log Enabler for AWS: 41 | "ec2:DescribeVpcs", 42 | "ec2:DescribeFlowLogs", 43 | "ec2:CreateFlowLogs", 44 | "ec2:CreateTags", 45 | "logs:CreateLogDelivery", 46 | "s3:GetBucketPolicy", 47 | "s3:PutBucketPolicy", 48 | "s3:PutLifecycleConfiguration" 49 | "s3:PutObject", 50 | "s3:GetObject", 51 | "s3:CreateBucket", 52 | "cloudtrail:StartLogging", 53 | "cloudtrail:CreateTrail", 54 | "cloudtrail:DescribeTrails", 55 | "eks:UpdateClusterConfig", 56 | "eks:ListClusters", 57 | "route53resolver:ListResolverQueryLogConfigAssociations", 58 | "route53resolver:CreateResolverQueryLogConfig", 59 | "route53resolver:AssociateResolverQueryLogConfig", 60 | "route53resolver:TagResource", 61 | "iam:CreateServiceLinkRole", # This is used to create the AWSServiceRoleForRoute53 Resolver, which is used for creating the Amazon Route 53 Query Logging Configurations. 62 | "route53resolver:ListResolverQueryLogConfigs", 63 | "route53resolver:ListTagsForResource", 64 | "route53resolver:DisassociateResolverQueryLogConfig", 65 | "route53resolver:DeleteResolverQueryLogConfig" 66 | "s3:PutBucketLogging", 67 | "s3:GetBucketLogging", 68 | "s3:ListBucket", 69 | "s3:ListAllMyBuckets", 70 | "s3:GetBucketLocation", 71 | "s3:GetBucketAcl", 72 | "s3:PutBucketAcl", 73 | "s3:PutBucketPublicAccessBlock", 74 | "s3:PutBucketLifecycleConfiguration", 75 | "guardduty:ListDetectors", 76 | "guardduty:TagResource", 77 | "guardduty:GetDetector", 78 | "guardduty:CreateDetector", 79 | "guardduty:UpdateDetector", 80 | "guardduty:ListPublishingDestinations", 81 | "guardduty:CreatePublishingDestination", 82 | "guardduty:DescribePublishingDestination", 83 | "wafv2:ListWebACLs", 84 | "wafv2:ListLoggingConfigurations", 85 | "wafv2:PutLoggingConfiguration" 86 | 87 | # For adding AWS CloudTrail logs: 88 | "s3:GetBucketPolicy", 89 | "s3:PutBucketPolicy", 90 | "s3:PutLifecycleConfiguration" 91 | "s3:PutObject", 92 | "s3:CreateBucket", 93 | "cloudtrail:StartLogging", 94 | "cloudtrail:CreateTrail", 95 | "cloudtrail:DescribeTrails" 96 | 97 | # For adding Amazon VPC Flow Logs: 98 | "s3:GetBucketPolicy", 99 | "s3:PutBucketPolicy", 100 | "s3:PutLifecycleConfiguration" 101 | "s3:PutObject", 102 | "s3:CreateBucket", 103 | "ec2:DescribeVpcs", 104 | "ec2:DescribeFlowLogs", 105 | "ec2:CreateFlowLogs", 106 | "ec2:CreateTags" 107 | 108 | # For adding Amazon EKS logs: 109 | "eks:UpdateClusterConfig", 110 | "eks:ListClusters", 111 | "logs:CreateLogDelivery" 112 | 113 | # For adding Amazon Route 53 Resolver Query Logs: 114 | "s3:GetBucketPolicy", 115 | "s3:PutBucketPolicy", 116 | "s3:PutLifecycleConfiguration" 117 | "s3:PutObject", 118 | "s3:CreateBucket", 119 | "ec2:DescribeVpcs", 120 | "route53resolver:ListResolverQueryLogConfigAssociations", 121 | "route53resolver:CreateResolverQueryLogConfig", 122 | "route53resolver:AssociateResolverQueryLogConfig", 123 | "route53resolver:TagResource", 124 | "iam:CreateServiceLinkRole" # This is used to create the AWSServiceRoleForRoute53 Resolver, which is used for creating the Amazon Route 53 Query Logging Configurations. 125 | 126 | # For adding Amazon S3 Server Access Logs: 127 | "s3:PutBucketLogging", 128 | "s3:GetBucketLogging", 129 | "s3:ListBucket", 130 | "s3:ListAllMyBuckets", 131 | "s3:GetBucketLocation", 132 | "s3:GetBucketAcl", 133 | "s3:PutBucketAcl", 134 | "s3:PutBucketPublicAccessBlock", 135 | "s3:PutBucketLifecycleConfiguration" 136 | 137 | # NEW! For adding Elastic Load Balancing Access Logs: 138 | "elb:DescribeLoadBalancers", 139 | "elb:DescribeLoadBalancerAttributes", 140 | "elb:ModifyLoadBalancerAttributes", 141 | "elbv2:DescribeLoadBalancers", 142 | "elbv2:DescribeLoadBalancerAttributes", 143 | "elbv2:ModifyLoadBalancerAttributes", 144 | "elasticloadbalancing:DescribeLoadBalancers", 145 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 146 | "elasticloadbalancing:ModifyLoadBalancerAttributes" 147 | 148 | # For enabling GuardDuty and export findings: 149 | "guardduty:ListDetectors", 150 | "guardduty:GetDetector", 151 | "guardduty:TagResource", 152 | "guardduty:CreateDetector", 153 | "guardduty:UpdateDetector", 154 | "guardduty:ListPublishingDestinations", 155 | "guardduty:CreatePublishingDestination", 156 | "guardduty:DescribePublishingDestination", 157 | "s3:GetObject", 158 | "s3:ListBucket", 159 | "s3:PutObject", 160 | "s3:GetBucketLocation" 161 | 162 | # For adding WAFv2 logging: 163 | "wafv2:ListWebACLs", 164 | "wafv2:ListLoggingConfigurations", 165 | "wafv2:PutLoggingConfiguration" 166 | 167 | # For cleanup of Amazon Route 53 Resolver Query Logs created by Assisted Log Enabler for AWS: 168 | "route53resolver:ListResolverQueryLogConfigs", 169 | "route53resolver:ListTagsForResource", 170 | "route53resolver:ListResolverQueryLogConfigAssociations", 171 | "route53resolver:DisassociateResolverQueryLogConfig", 172 | "route53resolver:DeleteResolverQueryLogConfig" 173 | ``` 174 | Additionally, if running from within a AWS Lambda function, the function will need the AWSLambdaBasicExecutionRole in order to run successfully. Please refer to the following link for more details: https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html 175 | 176 | 177 | ## Workflow Details 178 | The following are the details of what happens within the Assisted Log Enabler for AWS workflow: 179 | * An Amazon S3 bucket is created within the customer's account. 180 | * A Lifecycle Policy is created for the bucket, with the following parameters: 181 | * Converts files to Intelligent-Tiering storage after 90 days 182 | * Deletes files after 400 days 183 | * Block Public Access is explicitly set to On for the S3 bucket created. 184 | * Amazon VPCs are checked to see if flow logs are turned on or off. 185 | * For Amazon VPCs that do not have flow logs turned on, VPC Flow Logging is turned on, and sent to the bucket created. 186 | * Amazon VPC Flow Logs version 2, 3, 4, and 5 fields are all enabled. 187 | * AWS CloudTrail service is checked to see there is at least one CloudTrail configured. (Single Account only as of this release) 188 | * If no trail is configured, one is created and configured to log to the bucket created. (Single Account only as of this release) 189 | * If Amazon EKS Clusters exist, audit & authenticator logs are turned on. 190 | * Amazon Route 53 Query Logging is turned on for VPCs that do not have it turned on already. 191 | * Amazon S3 Server Access Logs are created for buckets that do not have it turned on already. 192 | * This does not include for S3 buckets created by Assisted Log Enabler for AWS 193 | * Amazon S3 Server Access Logs require buckets that reside in the same account & region, so additional buckets for Amazon S3 Server Access logs are created for this. 194 | * NEW! Elastic Load Balancing Access Logs are created for Application, Network and Classic Load Balancers that do not have it turned on already. 195 | * Elastic Load Balancing Access Logs require buckets that reside in the region, so additional buckets for Elastic Load Balancing Access logs are created for this. 196 | * The following table contains the account IDs to use in place of elb-account-id in the bucket policy: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html 197 | 198 | ## Running the Code 199 | The code in its current form can be ran inside the following: 200 | * AWS CloudShell (preferred) 201 | * AWS Lambda 202 | 203 | ``` 204 | python3 assisted_log_enabler.py 205 | 206 | █████  ███████ ███████ ██ ███████ ████████ ███████ ██████  207 | ██   ██ ██      ██      ██ ██         ██    ██      ██   ██  208 | ███████ ███████ ███████ ██ ███████  ██  █████  ██  ██  209 | ██   ██      ██      ██ ██      ██  ██  ██     ██  ██  210 | ██  ██ ███████ ███████ ██ ███████  ██  ███████ ██████   211 |                                                        212 | 213 | ██  ██████  ██████  214 | ██  ██    ██ ██       215 | ██  ██  ██ ██  ███  216 | ██  ██  ██ ██  ██  217 | ███████  ██████   ██████   218 |                          219 | 220 | ███████ ███  ██  █████  ██████  ██  ███████ ██████  221 | ██      ████  ██ ██   ██ ██   ██ ██  ██      ██   ██  222 | █████  ██ ██  ██ ███████ ██████  ██  █████  ██████   223 | ██     ██  ██ ██ ██   ██ ██   ██ ██  ██     ██   ██  224 | ███████ ██   ████ ██  ██ ██████  ███████ ███████ ██  ██  225 | Joshua "DozerCat" McKiddy - Customer Incident Response Team (CIRT) - AWS 226 | Cydney "StudyCat" Stude - Customer Incident Response Team (AWS) - Twitter: @cydneystude 227 | Rogerio Kasa - Security Solutions Architect (AWS) 228 | Andrew Yankowsky - Professional Services (AWS) 229 | Twitter: @jdubm31 230 | Type -h for help. 231 | 232 | No valid option selected. Please run with -h to display valid options. 233 | ``` 234 | * Options 235 | ``` 236 | python3 assisted_log_enabler.py -h 237 | usage: assisted_log_enabler.py [-h] [--mode MODE] [--bucket BUCKET] 238 | [--include_accounts ACCOUNT_NUMBERS] 239 | [--exclude_accounts ACCOUNT_NUMBERS] [--all] 240 | [--eks] [--vpcflow] [--r53querylogs] [--s3logs] 241 | [--lblogs] [--cloudtrail] [--guardduty] 242 | [--wafv2] [--single_r53querylogs] 243 | [--single_cloudtrail] [--single_vpcflow] 244 | [--single_all] [--single_s3logs] 245 | [--single_lblogs] [--single_guardduty] 246 | [--single_wafv2] [--single_account] 247 | [--multi_account] 248 | 249 | Assisted Log Enabler - Find resources that are not logging, and turn them on. 250 | 251 | optional arguments: 252 | -h, --help show this help message and exit 253 | --mode MODE Choose the mode that you want to run Assisted Log 254 | Enabler in. Available modes: single_account, 255 | multi_account, cleanup, dryrun. WARNING: For 256 | multi_account, You must have the associated 257 | CloudFormation template deployed as a StackSet. See 258 | the README file for more details. 259 | --bucket BUCKET Specify the name of a pre-existing S3 bucket that you 260 | want Assisted Log Enabler to store logs in. Otherwise, 261 | a new S3 bucket will be created (default). Only used 262 | for Amazon VPC Flow Logs, Amazon Route 53 Resolver 263 | Query Logs, AWS CloudTrail logs, and Amazon GuardDuty. 264 | WARNING: This will replace the bucket policy. 265 | --include_accounts ACCOUNT_NUMBERS 266 | Specify a comma separated list of AWS account numbers 267 | to INCLUDE for multi_account mode. 268 | --exclude_accounts ACCOUNT_NUMBERS 269 | Specify a comma separated list of AWS account numbers 270 | to EXCLUDE for multi_account mode. 271 | 272 | Single & Multi Account Options: 273 | Use these flags to choose which services you want to turn logging on for. 274 | 275 | --all Turns on all of the log types within the Assisted Log 276 | Enabler for AWS (does not include GuardDuty). 277 | --eks Turns on Amazon EKS audit & authenticator logs. 278 | --vpcflow Turns on Amazon VPC Flow Logs. 279 | --r53querylogs Turns on Amazon Route 53 Resolver Query Logs. 280 | --s3logs Turns on Amazon Bucket Logs. 281 | --lblogs Turns on Amazon Load Balancer Logs. 282 | --cloudtrail Turns on AWS CloudTrail. Only available in Single 283 | Account version. 284 | --guardduty Turns on Amazon GuardDuty and exports findings to an 285 | S3 bucket. Will used specified bucket. WARNING: This 286 | creates a KMS Key to export findings. 287 | --wafv2 Turns on AWS WAFv2 Logs. 288 | 289 | Cleanup Options: 290 | Use these flags to choose which resources you want to turn logging off 291 | for. 292 | 293 | --single_r53querylogs 294 | Removes Amazon Route 53 Resolver Query Log resources 295 | created by Assisted Log Enabler for AWS. 296 | --single_cloudtrail Removes AWS CloudTrail trails created by Assisted Log 297 | Enabler for AWS. 298 | --single_vpcflow Removes Amazon VPC Flow Log resources created by 299 | Assisted Log Enabler for AWS. 300 | --single_all Turns off all of the log types within the Assisted Log 301 | Enabler for AWS. 302 | --single_s3logs Removes Amazon Bucket Log resources created by 303 | Assisted Log Enabler for AWS. 304 | --single_lblogs Removes Amazon Load Balancer Log resources created by 305 | Assisted Log Enabler for AWS. 306 | --single_guardduty Removes Amazon GuardDuty detectors created by Assisted 307 | Log Enabler for AWS. 308 | --single_wafv2 Removes AWS WAFv2 Logging Configurations created by 309 | Assisted Log Enabler for AWS. 310 | 311 | Dry Run Options: 312 | Use these flags to run Assisted Log Enabler for AWS in Dry Run mode. 313 | 314 | --single_account Runs Assisted Log Enabler for AWS in Dry Run mode for 315 | a single AWS account. 316 | --multi_account Runs Assisted Log Enabler for AWS in Dry Run mode for 317 | a multi-account AWS environment, using AWS 318 | Organizations. 319 | ``` 320 | 321 | ### Step-by-Step Instructions (for running in AWS CloudShell, single account mode) 322 | 1. Log into the AWS Console of the account you want to run the Assisted Log Enabler for AWS. 323 | * Ensure that the principal being used to log into the AWS Console has the permissions [above](https://github.com/awslabs/assisted-log-enabler-for-aws#permissions). 324 | 2. Click on the icon for AWS Cloudshell next to the search bar. 325 | * Ensure that you're in a region where AWS CloudShell is currently available. 326 | 3. Once the session begins, download the Assisted Log Enabler for AWS within the AWS CloudShell session. 327 | ``` 328 | git clone https://github.com/awslabs/assisted-log-enabler-for-aws.git 329 | ``` 330 | 4. Change the directory to the folder cloned from the link in Step 3: 331 | ``` 332 | cd assisted-log-enabler-for-aws 333 | ``` 334 | 5. Run the following command to run the Assisted Log Enabler in single account mode, for the AWS service or services you want to check for: 335 | ``` 336 | # For all services: 337 | python3 assisted_log_enabler.py --mode single_account --all 338 | # For Amazon EKS: 339 | python3 assisted_log_enabler.py --mode single_account --eks 340 | # For Amazon VPC Flow Logs: 341 | python3 assisted_log_enabler.py --mode single_account --vpcflow 342 | # For Amazon Route 53 Resolver Query Logs: 343 | python3 assisted_log_enabler.py --mode single_account --r53querylogs 344 | # For AWS CloudTrail: 345 | python3 assisted_log_enabler.py --mode single_account --cloudtrail 346 | # For Amazon S3 Server Access Logs: 347 | python3 assisted_log_enabler.py --mode single_account --s3logs 348 | # NEW! For Elastic Load Balancing Access Logs: 349 | python3 assisted_log_enabler.py --mode single_account --lblogs 350 | # NEW! For GuardDuty: 351 | python3 assisted_log_enabler.py --mode single_account --guardduty 352 | # NEW! For WAFv2 Logs: 353 | python3 assisted_log_enabler.py --mode single_account --wafv2 354 | ``` 355 | 356 | ### Step-by-Step Instructions (for running in AWS CloudShell, multi account mode) 357 | 1. Log into the AWS Console of the account you want to run the Assisted Log Enabler for AWS. 358 | * Ensure that the AWS Account you're in is the account you want to store the logs. Additionally, ensure that the AWS account you're in has access to the AWS Organizations information within your AWS environment. 359 | * You may have to register your AWS account as a delegated administrator within AWS CloudFormation, in order to run this code in an AWS account of your choosing. Please see the following link for more details: [Register a delegated administrator](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-orgs-delegated-admin.html) 360 | 2. Within the AWS Console, go to AWS CloudFormation. 361 | 3. To deploy the IAM Permissions within all child accounts: Within AWS CloudFormation, go to StackSets. 362 | 4. Within the StackSets screen, select Create StackSet. 363 | 5. In Step 1, under Specify Template, select Upload a template file, and use the AWS CloudFormation template provided in the permissions folder. [Link to the file](https://github.com/awslabs/assisted-log-enabler-for-aws/blob/main/permissions/ALE_child_account_role.yaml) 364 | 6. In Step 2, under StackSet Name, add a descriptive name. 365 | 7. In Step 2, under Parameters, add the parameters required: 366 | * AssistedLogEnablerPolicyName: You can leave this default, but you can also change it if desired. 367 | * OrgId: Provide the AWS Organization ID 368 | * SourceAccountNumber: Provide the source AWS account number that the Assisted Log Enabler for AWS will be running. 369 | 8. In Step 3, add any tags that you desire, as well as any permissions options that you want to select. 370 | * The service-managed permissions work just fine for Assisted Log Enabler for AWS, but you can use self-service permissions if desired. 371 | 9. In Step 4, under Deployment targets, select the option that fits for your AWS Organization. 372 | * If you Deploy to Organization, it will deploy to all AWS accounts except the root AWS account. If you want to include that one, you can either deploy the template to the root AWS account directly, or use the other option (details below). 373 | * If you Deploy to organizational units (OUs), you can deploy directly to OUs that you define, including the root OU. 374 | 10. In Step 4, under Specify Regions, select US East (N.Virginia). 375 | * There's no need to select multiple regions here. This template only deploys AWS IAM resources, which are Global. 376 | 11. In Step 4, under Deployment options, leave the default settings. 377 | 12. In Step 5, review the settings you've set in the previous steps. If all is correct, check the box that states "I acknowledge that AWS CloudFormation might create IAM resources with custom names." 378 | * Once this is submitted, you'll need to wait until the StackSet is fully deployed. If there are errors, please examine the error and ensure that all the information from the above steps are correct. 379 | 13. To deploy the IAM Permissions within the AWS Account where Assisted Log Enabler for AWS is being ran (required to run in current account): Within AWS CloudFormation, go to Stacks. 380 | 14. Within the Stacks screen, go to the Create Stack dropdown, and select With new resources. 381 | 15. In Step 1, select Upload a template file, select Choose File, and use the AWS CloudFormation template provided in the permissions folder. [Link to the file](https://github.com/awslabs/assisted-log-enabler-for-aws/blob/main/permissions/ALE_child_account_role.yaml) 382 | 16. In Step 2, under Stack Name, add a descriptive name. 383 | 17. In Step 2, under Parameters, add the parameters required: 384 | * AssistedLogEnablerPolicyName: You can leave this default, but you can also change it if desired. 385 | * OrgId: Provide the AWS Organization ID 386 | * SourceAccountNumber: Provide the source AWS account number that the Assisted Log Enabler for AWS will be running. 387 | 18. In Step 3, add any tags that you desire, as well as any permissions options that you want to select. 388 | * The service-managed permissions work just fine for Assisted Log Enabler for AWS, but you can use self-service permissions if desired. 389 | 19. In Step 5, review the settings you've set in the previous steps. If all is correct, check the box that states "I acknowledge that AWS CloudFormation might create IAM resources with custom names." 390 | * Once this is submitted, you'll need to wait until the StackSet is fully deployed. If there are errors, please examine the error and ensure that all the information from the above steps are correct. 391 | 20. Once both the StackSet and Stack are successfully deployed, click on the icon for AWS Cloudshell next to the search bar. 392 | * Ensure that you're in a region where AWS CloudShell is currently available. 393 | 21. Once the session begins, download the Assisted Log Enabler for AWS within the AWS CloudShell session. 394 | ``` 395 | git clone https://github.com/awslabs/assisted-log-enabler-for-aws.git 396 | ``` 397 | 22. Change the directory to the folder cloned from the link in Step 21: 398 | ``` 399 | cd assisted-log-enabler-for-aws 400 | ``` 401 | 23. Run the following command to run the Assisted Log Enabler in multi account mode, for the AWS service or services you want to check for: 402 | ``` 403 | # For all services: 404 | python3 assisted_log_enabler.py --mode multi_account --all 405 | # For Amazon EKS: 406 | python3 assisted_log_enabler.py --mode multi_account --eks 407 | # For Amazon VPC Flow Logs: 408 | python3 assisted_log_enabler.py --mode multi_account --vpcflow 409 | # For Amazon Route 53 Resolver Query Logs: 410 | python3 assisted_log_enabler.py --mode multi_account --r53querylogs 411 | For Amazon S3 Server Access Logs: 412 | python3 assisted_log_enabler.py --mode multi_account --s3logs 413 | # NEW! For Elastic Load Balancing Access Logs: 414 | python3 assisted_log_enabler.py --mode multi_account --lblogs 415 | # NEW! For GuardDuty: 416 | python3 assisted_log_enabler.py --mode multi_account --guardduty 417 | # NEW! For WAFv2 Logs: 418 | python3 assisted_log_enabler.py --mode multi_account --wafv2 419 | 420 | ``` 421 | 422 | ### GovCloud Compatibility 423 | To run Assisted Log Enabler for AWS on GovCloud, make the following adjustments to the code: 424 | 1. Replace `region_list` in with only GovCloud regions (i.e. `region_list = ['us-gov-east-1', 'us-gov-west-1']`) in all files in the subfunctions directory. 425 | 2. Replace all ARNs to use the GovCloud ARN format (Switch `arn:aws` to `arn:aws-us-gov`) 426 | 3. Set LocationConstraint in bucket creation for WAFv2 logging function (`wafv2_logs`) to a GovCloud region, for example: 427 | ``` 428 | s3.create_bucket( 429 | Bucket=bucket_name, 430 | CreateBucketConfiguration={ 431 | "LocationConstraint": "us-gov-east-1" 432 | } 433 | ) 434 | ``` 435 | 436 | ### Logging 437 | A log file containing the detailed output of actions will be placed in the root directory of the Assisted Log Enabler for AWS tool. The format of the file will be ALE_timestamp_here.log 438 | 439 | Sample output within the log file: 440 | ``` 441 | 2021-02-23 05:31:54,207 - INFO - Creating a list of VPCs without Flow Logs on in region us-west-2. 442 | 2021-02-23 05:31:54,208 - INFO - DescribeVpcs API Call 443 | 2021-02-23 05:31:54,679 - INFO - List of VPCs found within account 111122223333, region us-west-2: 444 | 2021-02-23 05:31:54,679 - INFO - DescribeFlowLogs API Call 445 | 2021-02-23 05:31:54,849 - INFO - List of VPCs found within account 111122223333, region us-west-2 WITHOUT VPC Flow Logs: 446 | 2021-02-23 05:31:54,849 - INFO - Activating logs for VPCs that do not have them turned on. 447 | 2021-02-23 05:31:54,849 - INFO - If all VPCs have Flow Logs turned on, you will get an MissingParameter error. That is normal. 448 | 2021-02-23 05:31:54,849 - INFO - CreateFlowLogs API Call 449 | 2021-02-23 05:31:54,944 - ERROR - An error occurred (MissingParameter) when calling the CreateFlowLogs operation: The request must include the ResourceIds parameter. Add the required parameter and retry the request. 450 | 2021-02-23 05:31:54,946 - INFO - Checking to see if CloudTrail is on, and will activate if needed. 451 | 2021-02-23 05:31:54,946 - INFO - DescribeTrails API Call 452 | 2021-02-23 05:31:54,983 - INFO - There is a CloudTrail trail active. No action needed. 453 | 2021-02-23 05:31:54,984 - INFO - Turning on audit and authenticator logging for EKS clusters in region af-south-1. 454 | ``` 455 | 456 | ## Dry Run Mode 457 | Dry Run modes for single and multi-account are both available. These modes allow you to check for resources in your environment that do not have logging turned on, but does not activate the logging for said resources. 458 | 459 | To run Assisted Log Enabler for AWS in Dry Run mode, you can use the commands below: 460 | ``` 461 | # Single Account Dry Run 462 | python3 assisted_log_enabler.py --mode dryrun --single_account 463 | # Multi-Account Dry Run 464 | python3 assisted_log_enabler.py --mode dryrun --multi_account 465 | ``` 466 | 467 | ## Cleaning Up 468 | Once the logs have been enabled, you can safely remove any of the downloaded files from AWS CloudShell. 469 | * Note: The log file containing the detailed output of actions will be in the root directory of the Assisted Log Enabler for AWS tool. If you want to retain this, please download this to a safe place, either locally or to an Amazon S3 bucket, for your records. For information on how to download files from AWS CloudShell sessions, refer to the following [link](https://docs.aws.amazon.com/cloudshell/latest/userguide/working-with-cloudshell.html#files-storage). 470 | 471 | For any AWS IAM Roles that are created, either manually or using AWS CloudFormation StackSets, those can be safely deleted upon enablement of logs through the Assisted Log Enabler for AWS. 472 | 473 | A cleanup mode is available within the Assisted Log Enabler for AWS (currently only for single account). Collected logs within Amazon S3 will NOT be removed, however, logging resources can be removed by following the below commands: 474 | ``` 475 | # To remove Amazon Route 53 Resolver Query Log resources created by Assisted Log Enabler for AWS (single account): 476 | python3 assisted_log_enabler.py --mode cleanup --single_r53querylogs 477 | # To remove Amazon VPC Flow Log resources created by Assisted Log Enabler for AWS (single account): 478 | python3 assisted_log_enabler.py --mode cleanup --single_vpcflow 479 | # To remove AWS CloudTrail trails created by Assisted Log Enabler for AWS (single account): 480 | python3 assisted_log_enabler.py --mode cleanup --single_cloudtrail 481 | # To remove Amazon S3 Server Access logging created by Assisted Log Enabler for AWS (single account): 482 | python3 assisted_log_enabler.py --mode cleanup --single_s3logs 483 | # NEW! To remove Elastic Load Balancing Access logging created by Assisted Log Enabler for AWS (single account): 484 | python3 assisted_log_enabler.py --mode cleanup --single_lblogs 485 | # NEW! To remove GuardDuty detectors created by Assisted Log Enabler for AWS (single account): 486 | python3 assisted_log_enabler.py --mode cleanup --single_guardduty 487 | # NEW! To remove WAFv2 logging created by Assisted Log Enabler for AWS (single account): 488 | python3 assisted_log_enabler.py --mode cleanup --single_wafv2 489 | ``` 490 | 491 | ## Shared Responsibility Model 492 | All resources created fall into the customer side of the Shared Responsibility Model. 493 | 494 | For AWS customers, please refer to the following link for more information about the Shared Responsibility Model: [Link](https://aws.amazon.com/compliance/shared-responsibility-model/) 495 | 496 | ## Additional Tools 497 | For analyzing logs created by Assisted Log Enabler for AWS, consider taking a look at the AWS Security Analytics Bootstrap, a tool that provides an Amazon Athena analysis environment that's quick to deploy, ready to use, and easy to maintain. [Link to GitHub repository.](https://github.com/awslabs/aws-security-analytics-bootstrap) 498 | 499 | For an point-and-quick solution to analyze Amazon VPC Flow Logs, check out [this AWS blog post](https://aws.amazon.com/blogs/networking-and-content-delivery/analyze-vpc-flow-logs-with-point-and-click-amazon-athena-integration/) for instructions on how to deploy an Amazon Athena analysis environment that's compatible with your Amazon VPC Flow Logs, and provides several sample queries that can allow you to perform an investigation quickly without worrying about the format of the Amazon VPC Flow Logs. 500 | 501 | 502 | ## Costs 503 | For answers to cost-related questions involved with this solution, refer to the following links: 504 | * AWS CloudTrail Pricing: [Link](https://aws.amazon.com/cloudtrail/pricing/) 505 | * Amazon S3 Pricing: [Link](https://aws.amazon.com/s3/pricing/) 506 | * Amazon VPC Flow Logs Pricing: [Link](https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html#flow-logs-pricing) 507 | * Amazon Route 53 Pricing (look for the Route 53 Resolver Query Logs section): [Link](https://aws.amazon.com/route53/pricing/) 508 | * Amazon EKS Control Plane Logging: [Link](https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html) 509 | * Elastic Load Balancing Logging: [Link](https://aws.amazon.com/elasticloadbalancing/pricing/) 510 | * GuardDuty Pricing: [Link](https://aws.amazon.com/guardduty/pricing/) 511 | * WAFv2 Logging Pricing: [Link](https://docs.aws.amazon.com/waf/latest/developerguide/logging-pricing.html) 512 | 513 | 514 | ## Feedback 515 | Please use the [Issues](https://github.com/awslabs/assisted-log-enabler-for-aws/issues) section to submit any feedback, such as features or recommendations, as well as any bugs that are encountered. 516 | 517 | 518 | ## Security 519 | 520 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 521 | 522 | 523 | ## License 524 | 525 | This project is licensed under the Apache-2.0 License. 526 | -------------------------------------------------------------------------------- /THIRD-PARTY: -------------------------------------------------------------------------------- 1 | ** boto3; version 1.16.20 -- https://pypi.org/project/boto3/ 2 | 3 | Attribution-ShareAlike 4.0 International 4 | 5 | ======================================================================= 6 | 7 | Creative Commons Corporation ("Creative Commons") is not a law firm and 8 | does not provide legal services or legal advice. Distribution of 9 | Creative Commons public licenses does not create a lawyer-client or 10 | other relationship. Creative Commons makes its licenses and related 11 | information available on an "as-is" basis. Creative Commons gives no 12 | warranties regarding its licenses, any material licensed under their 13 | terms and conditions, or any related information. Creative Commons 14 | disclaims all liability for damages resulting from their use to the 15 | fullest extent possible. 16 | 17 | Using Creative Commons Public Licenses 18 | 19 | Creative Commons public licenses provide a standard set of terms and 20 | conditions that creators and other rights holders may use to share 21 | original works of authorship and other material subject to copyright 22 | and certain other rights specified in the public license below. The 23 | following considerations are for informational purposes only, are not 24 | exhaustive, and do not form part of our licenses. 25 | 26 | Considerations for licensors: Our public licenses are 27 | intended for use by those authorized to give the public 28 | permission to use material in ways otherwise restricted by 29 | copyright and certain other rights. Our licenses are 30 | irrevocable. Licensors should read and understand the terms 31 | and conditions of the license they choose before applying it. 32 | Licensors should also secure all rights necessary before 33 | applying our licenses so that the public can reuse the 34 | material as expected. Licensors should clearly mark any 35 | material not subject to the license. This includes other CC- 36 | licensed material, or material used under an exception or 37 | limitation to copyright. More considerations for licensors: 38 | wiki.creativecommons.org/Considerations_for_licensors 39 | 40 | Considerations for the public: By using one of our public 41 | licenses, a licensor grants the public permission to use the 42 | licensed material under specified terms and conditions. If 43 | the licensor's permission is not necessary for any reason--for 44 | example, because of any applicable exception or limitation to 45 | copyright--then that use is not regulated by the license. Our 46 | licenses grant only permissions under copyright and certain 47 | other rights that a licensor has authority to grant. Use of 48 | the licensed material may still be restricted for other 49 | reasons, including because others have copyright or other 50 | rights in the material. A licensor may make special requests, 51 | such as asking that all changes be marked or described. 52 | Although not required by our licenses, you are encouraged to 53 | respect those requests where reasonable. More considerations 54 | for the public: 55 | wiki.creativecommons.org/Considerations_for_licensees 56 | 57 | ======================================================================= 58 | 59 | Creative Commons Attribution-ShareAlike 4.0 International Public 60 | License 61 | 62 | By exercising the Licensed Rights (defined below), You accept and agree 63 | to be bound by the terms and conditions of this Creative Commons 64 | Attribution-ShareAlike 4.0 International Public License ("Public 65 | License"). To the extent this Public License may be interpreted as a 66 | contract, You are granted the Licensed Rights in consideration of Your 67 | acceptance of these terms and conditions, and the Licensor grants You 68 | such rights in consideration of benefits the Licensor receives from 69 | making the Licensed Material available under these terms and 70 | conditions. 71 | 72 | 73 | Section 1 -- Definitions. 74 | 75 | a. Adapted Material means material subject to Copyright and Similar 76 | Rights that is derived from or based upon the Licensed Material 77 | and in which the Licensed Material is translated, altered, 78 | arranged, transformed, or otherwise modified in a manner requiring 79 | permission under the Copyright and Similar Rights held by the 80 | Licensor. For purposes of this Public License, where the Licensed 81 | Material is a musical work, performance, or sound recording, 82 | Adapted Material is always produced where the Licensed Material is 83 | synched in timed relation with a moving image. 84 | 85 | b. Adapter's License means the license You apply to Your Copyright 86 | and Similar Rights in Your contributions to Adapted Material in 87 | accordance with the terms and conditions of this Public License. 88 | 89 | c. BY-SA Compatible License means a license listed at 90 | creativecommons.org/compatiblelicenses, approved by Creative 91 | Commons as essentially the equivalent of this Public License. 92 | 93 | d. Copyright and Similar Rights means copyright and/or similar rights 94 | closely related to copyright including, without limitation, 95 | performance, broadcast, sound recording, and Sui Generis Database 96 | Rights, without regard to how the rights are labeled or 97 | categorized. For purposes of this Public License, the rights 98 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 99 | Rights. 100 | 101 | e. Effective Technological Measures means those measures that, in the 102 | absence of proper authority, may not be circumvented under laws 103 | fulfilling obligations under Article 11 of the WIPO Copyright 104 | Treaty adopted on December 20, 1996, and/or similar international 105 | agreements. 106 | 107 | f. Exceptions and Limitations means fair use, fair dealing, and/or 108 | any other exception or limitation to Copyright and Similar Rights 109 | that applies to Your use of the Licensed Material. 110 | 111 | g. License Elements means the license attributes listed in the name 112 | of a Creative Commons Public License. The License Elements of this 113 | Public License are Attribution and ShareAlike. 114 | 115 | h. Licensed Material means the artistic or literary work, database, 116 | or other material to which the Licensor applied this Public 117 | License. 118 | 119 | i. Licensed Rights means the rights granted to You subject to the 120 | terms and conditions of this Public License, which are limited to 121 | all Copyright and Similar Rights that apply to Your use of the 122 | Licensed Material and that the Licensor has authority to license. 123 | 124 | j. Licensor means the individual(s) or entity(ies) granting rights 125 | under this Public License. 126 | 127 | k. Share means to provide material to the public by any means or 128 | process that requires permission under the Licensed Rights, such 129 | as reproduction, public display, public performance, distribution, 130 | dissemination, communication, or importation, and to make material 131 | available to the public including in ways that members of the 132 | public may access the material from a place and at a time 133 | individually chosen by them. 134 | 135 | l. Sui Generis Database Rights means rights other than copyright 136 | resulting from Directive 96/9/EC of the European Parliament and of 137 | the Council of 11 March 1996 on the legal protection of databases, 138 | as amended and/or succeeded, as well as other essentially 139 | equivalent rights anywhere in the world. 140 | 141 | m. You means the individual or entity exercising the Licensed Rights 142 | under this Public License. Your has a corresponding meaning. 143 | 144 | 145 | Section 2 -- Scope. 146 | 147 | a. License grant. 148 | 149 | 1. Subject to the terms and conditions of this Public License, 150 | the Licensor hereby grants You a worldwide, royalty-free, 151 | non-sublicensable, non-exclusive, irrevocable license to 152 | exercise the Licensed Rights in the Licensed Material to: 153 | 154 | a. reproduce and Share the Licensed Material, in whole or 155 | in part; and 156 | 157 | b. produce, reproduce, and Share Adapted Material. 158 | 159 | 2. Exceptions and Limitations. For the avoidance of doubt, where 160 | Exceptions and Limitations apply to Your use, this Public 161 | License does not apply, and You do not need to comply with 162 | its terms and conditions. 163 | 164 | 3. Term. The term of this Public License is specified in Section 165 | 6(a). 166 | 167 | 4. Media and formats; technical modifications allowed. The 168 | Licensor authorizes You to exercise the Licensed Rights in 169 | all media and formats whether now known or hereafter created, 170 | and to make technical modifications necessary to do so. The 171 | Licensor waives and/or agrees not to assert any right or 172 | authority to forbid You from making technical modifications 173 | necessary to exercise the Licensed Rights, including 174 | technical modifications necessary to circumvent Effective 175 | Technological Measures. For purposes of this Public License, 176 | simply making modifications authorized by this Section 2(a) 177 | (4) never produces Adapted Material. 178 | 179 | 5. Downstream recipients. 180 | 181 | a. Offer from the Licensor -- Licensed Material. Every 182 | recipient of the Licensed Material automatically 183 | receives an offer from the Licensor to exercise the 184 | Licensed Rights under the terms and conditions of this 185 | Public License. 186 | 187 | b. Additional offer from the Licensor -- Adapted Material. 188 | Every recipient of Adapted Material from You 189 | automatically receives an offer from the Licensor to 190 | exercise the Licensed Rights in the Adapted Material 191 | under the conditions of the Adapter's License You apply. 192 | 193 | c. No downstream restrictions. You may not offer or impose 194 | any additional or different terms or conditions on, or 195 | apply any Effective Technological Measures to, the 196 | Licensed Material if doing so restricts exercise of the 197 | Licensed Rights by any recipient of the Licensed 198 | Material. 199 | 200 | 6. No endorsement. Nothing in this Public License constitutes or 201 | may be construed as permission to assert or imply that You 202 | are, or that Your use of the Licensed Material is, connected 203 | with, or sponsored, endorsed, or granted official status by, 204 | the Licensor or others designated to receive attribution as 205 | provided in Section 3(a)(1)(A)(i). 206 | 207 | b. Other rights. 208 | 209 | 1. Moral rights, such as the right of integrity, are not 210 | licensed under this Public License, nor are publicity, 211 | privacy, and/or other similar personality rights; however, to 212 | the extent possible, the Licensor waives and/or agrees not to 213 | assert any such rights held by the Licensor to the limited 214 | extent necessary to allow You to exercise the Licensed 215 | Rights, but not otherwise. 216 | 217 | 2. Patent and trademark rights are not licensed under this 218 | Public License. 219 | 220 | 3. To the extent possible, the Licensor waives any right to 221 | collect royalties from You for the exercise of the Licensed 222 | Rights, whether directly or through a collecting society 223 | under any voluntary or waivable statutory or compulsory 224 | licensing scheme. In all other cases the Licensor expressly 225 | reserves any right to collect such royalties. 226 | 227 | 228 | Section 3 -- License Conditions. 229 | 230 | Your exercise of the Licensed Rights is expressly made subject to the 231 | following conditions. 232 | 233 | a. Attribution. 234 | 235 | 1. If You Share the Licensed Material (including in modified 236 | form), You must: 237 | 238 | a. retain the following if it is supplied by the Licensor 239 | with the Licensed Material: 240 | 241 | i. identification of the creator(s) of the Licensed 242 | Material and any others designated to receive 243 | attribution, in any reasonable manner requested by 244 | the Licensor (including by pseudonym if 245 | designated); 246 | 247 | ii. a copyright notice; 248 | 249 | iii. a notice that refers to this Public License; 250 | 251 | iv. a notice that refers to the disclaimer of 252 | warranties; 253 | 254 | v. a URI or hyperlink to the Licensed Material to the 255 | extent reasonably practicable; 256 | 257 | b. indicate if You modified the Licensed Material and 258 | retain an indication of any previous modifications; and 259 | 260 | c. indicate the Licensed Material is licensed under this 261 | Public License, and include the text of, or the URI or 262 | hyperlink to, this Public License. 263 | 264 | 2. You may satisfy the conditions in Section 3(a)(1) in any 265 | reasonable manner based on the medium, means, and context in 266 | which You Share the Licensed Material. For example, it may be 267 | reasonable to satisfy the conditions by providing a URI or 268 | hyperlink to a resource that includes the required 269 | information. 270 | 271 | 3. If requested by the Licensor, You must remove any of the 272 | information required by Section 3(a)(1)(A) to the extent 273 | reasonably practicable. 274 | 275 | b. ShareAlike. 276 | 277 | In addition to the conditions in Section 3(a), if You Share 278 | Adapted Material You produce, the following conditions also apply. 279 | 280 | 1. The Adapter's License You apply must be a Creative Commons 281 | license with the same License Elements, this version or 282 | later, or a BY-SA Compatible License. 283 | 284 | 2. You must include the text of, or the URI or hyperlink to, the 285 | Adapter's License You apply. You may satisfy this condition 286 | in any reasonable manner based on the medium, means, and 287 | context in which You Share Adapted Material. 288 | 289 | 3. You may not offer or impose any additional or different terms 290 | or conditions on, or apply any Effective Technological 291 | Measures to, Adapted Material that restrict exercise of the 292 | rights granted under the Adapter's License You apply. 293 | 294 | 295 | Section 4 -- Sui Generis Database Rights. 296 | 297 | Where the Licensed Rights include Sui Generis Database Rights that 298 | apply to Your use of the Licensed Material: 299 | 300 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 301 | to extract, reuse, reproduce, and Share all or a substantial 302 | portion of the contents of the database; 303 | 304 | b. if You include all or a substantial portion of the database 305 | contents in a database in which You have Sui Generis Database 306 | Rights, then the database in which You have Sui Generis Database 307 | Rights (but not its individual contents) is Adapted Material, 308 | 309 | including for purposes of Section 3(b); and 310 | c. You must comply with the conditions in Section 3(a) if You Share 311 | all or a substantial portion of the contents of the database. 312 | 313 | For the avoidance of doubt, this Section 4 supplements and does not 314 | replace Your obligations under this Public License where the Licensed 315 | Rights include other Copyright and Similar Rights. 316 | 317 | 318 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 319 | 320 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 321 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 322 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 323 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 324 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 325 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 326 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 327 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 328 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 329 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 330 | 331 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 332 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 333 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 334 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 335 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 336 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 337 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 338 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 339 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 340 | 341 | c. The disclaimer of warranties and limitation of liability provided 342 | above shall be interpreted in a manner that, to the extent 343 | possible, most closely approximates an absolute disclaimer and 344 | waiver of all liability. 345 | 346 | 347 | Section 6 -- Term and Termination. 348 | 349 | a. This Public License applies for the term of the Copyright and 350 | Similar Rights licensed here. However, if You fail to comply with 351 | this Public License, then Your rights under this Public License 352 | terminate automatically. 353 | 354 | b. Where Your right to use the Licensed Material has terminated under 355 | Section 6(a), it reinstates: 356 | 357 | 1. automatically as of the date the violation is cured, provided 358 | it is cured within 30 days of Your discovery of the 359 | violation; or 360 | 361 | 2. upon express reinstatement by the Licensor. 362 | 363 | For the avoidance of doubt, this Section 6(b) does not affect any 364 | right the Licensor may have to seek remedies for Your violations 365 | of this Public License. 366 | 367 | c. For the avoidance of doubt, the Licensor may also offer the 368 | Licensed Material under separate terms or conditions or stop 369 | distributing the Licensed Material at any time; however, doing so 370 | will not terminate this Public License. 371 | 372 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 373 | License. 374 | 375 | 376 | Section 7 -- Other Terms and Conditions. 377 | 378 | a. The Licensor shall not be bound by any additional or different 379 | terms or conditions communicated by You unless expressly agreed. 380 | 381 | b. Any arrangements, understandings, or agreements regarding the 382 | Licensed Material not stated herein are separate from and 383 | independent of the terms and conditions of this Public License. 384 | 385 | 386 | Section 8 -- Interpretation. 387 | 388 | a. For the avoidance of doubt, this Public License does not, and 389 | shall not be interpreted to, reduce, limit, restrict, or impose 390 | conditions on any use of the Licensed Material that could lawfully 391 | be made without permission under this Public License. 392 | 393 | b. To the extent possible, if any provision of this Public License is 394 | deemed unenforceable, it shall be automatically reformed to the 395 | minimum extent necessary to make it enforceable. If the provision 396 | cannot be reformed, it shall be severed from this Public License 397 | without affecting the enforceability of the remaining terms and 398 | conditions. 399 | 400 | c. No term or condition of this Public License will be waived and no 401 | failure to comply consented to unless expressly agreed to by the 402 | Licensor. 403 | 404 | d. Nothing in this Public License constitutes or may be interpreted 405 | as a limitation upon, or waiver of, any privileges and immunities 406 | that apply to the Licensor or You, including from the legal 407 | processes of any jurisdiction or authority. 408 | 409 | 410 | ======================================================================= 411 | 412 | Creative Commons is not a party to its public 413 | licenses. Notwithstanding, Creative Commons may elect to apply one of 414 | its public licenses to material it publishes and in those instances 415 | will be considered the “Licensor.” The text of the Creative Commons 416 | public licenses is dedicated to the public domain under the CC0 Public 417 | Domain Dedication. Except for the limited purpose of indicating that 418 | material is shared under a Creative Commons public license or as 419 | otherwise permitted by the Creative Commons policies published at 420 | creativecommons.org/policies, Creative Commons does not authorize the 421 | use of the trademark "Creative Commons" or any other trademark or logo 422 | of Creative Commons without its prior written consent including, 423 | without limitation, in connection with any unauthorized modifications 424 | to any of its public licenses or any other arrangements, 425 | understandings, or agreements concerning use of licensed material. For 426 | the avoidance of doubt, this paragraph does not form part of the 427 | public licenses. 428 | 429 | Creative Commons may be contacted at creativecommons.org. 430 | 431 | * For boto3 see also this required NOTICE: 432 | boto3 433 | Copyright 2013-2017 Amazon.com, Inc. or its affiliates. All Rights 434 | Reserved. -------------------------------------------------------------------------------- /assisted_log_enabler.py: -------------------------------------------------------------------------------- 1 | #// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | #// SPDX-License-Identifier: Apache-2.0 3 | # Assisted Log Enabler for AWS - Find resources that are not logging, and turn them on. 4 | 5 | import logging 6 | import os 7 | import json 8 | import boto3 9 | import time 10 | import sys 11 | import datetime 12 | import argparse 13 | from botocore.exceptions import ClientError 14 | from datetime import timezone 15 | 16 | from subfunctions import ALE_multi_account 17 | from subfunctions import ALE_single_account 18 | from subfunctions import ALE_cleanup_single 19 | from subfunctions import ALE_dryrun_single 20 | from subfunctions import ALE_dryrun_multi 21 | 22 | current_date = datetime.datetime.now(tz=timezone.utc) 23 | current_date_string = str(current_date) 24 | timestamp_date = datetime.datetime.now(tz=timezone.utc).strftime("%Y-%m-%d-%H%M%S") 25 | timestamp_date_string = str(timestamp_date) 26 | 27 | logFormatter = '%(asctime)s - %(levelname)s - %(message)s' 28 | logger = logging.getLogger() 29 | logging.basicConfig(level=logging.INFO, format=logFormatter) 30 | logger.setLevel(logging.INFO) 31 | output_handle = logging.FileHandler('ALE_' + timestamp_date_string + '.log') 32 | output_handle.setLevel(logging.INFO) 33 | logger.addHandler(output_handle) 34 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 35 | output_handle.setFormatter(formatter) 36 | 37 | 38 | def banner(): 39 | """Function for Assisted Log Enabler banner""" 40 | print(''' 41 | █████  ███████ ███████ ██ ███████ ████████ ███████ ██████  42 | ██   ██ ██      ██      ██ ██         ██    ██      ██   ██  43 | ███████ ███████ ███████ ██ ███████  ██  █████  ██  ██  44 | ██   ██      ██      ██ ██      ██  ██  ██     ██  ██  45 | ██  ██ ███████ ███████ ██ ███████  ██  ███████ ██████   46 |                                                        47 | 48 | ██  ██████  ██████  49 | ██  ██    ██ ██       50 | ██  ██  ██ ██  ███  51 | ██  ██  ██ ██  ██  52 | ███████  ██████   ██████   53 |                          54 | 55 | ███████ ███  ██  █████  ██████  ██  ███████ ██████  56 | ██      ████  ██ ██   ██ ██   ██ ██  ██      ██   ██  57 | █████  ██ ██  ██ ███████ ██████  ██  █████  ██████   58 | ██     ██  ██ ██ ██   ██ ██   ██ ██  ██     ██   ██  59 | ███████ ██   ████ ██  ██ ██████  ███████ ███████ ██  ██  60 | Joshua "DozerCat" McKiddy - Customer Incident Response Team (AWS) - Twitter: @jdubm31 61 | Cydney "StudyCat" Stude - Customer Incident Response Team (AWS) - Twitter: @cydneystude 62 | Rogerio Kasa - Security Solutions Architect (AWS) 63 | Andrew Yankowsky - Professional Services (AWS) 64 | Type -h for help. 65 | ''') 66 | 67 | 68 | def assisted_log_enabler(): 69 | """Function to run Assisted Log Enabler""" 70 | output_handle = logging.FileHandler('ALE_' + timestamp_date_string + '.log') 71 | output_handle.setLevel(logging.INFO) 72 | logger.addHandler(output_handle) 73 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 74 | output_handle.setFormatter(formatter) 75 | 76 | parser = argparse.ArgumentParser(description='Assisted Log Enabler - Find resources that are not logging, and turn them on.') 77 | parser.add_argument('--mode', help=' Choose the mode that you want to run Assisted Log Enabler in. Available modes: single_account, multi_account, cleanup, dryrun. WARNING: For multi_account, You must have the associated CloudFormation template deployed as a StackSet. See the README file for more details.') 78 | parser.add_argument('--bucket', help=' Specify the name of a pre-existing S3 bucket that you want Assisted Log Enabler to store logs in. Otherwise, a new S3 bucket will be created (default). Only used for Amazon VPC Flow Logs, Amazon Route 53 Resolver Query Logs, AWS CloudTrail logs, and Amazon GuardDuty. WARNING: This will replace the bucket policy.') 79 | parser.add_argument('--include_accounts', metavar='ACCOUNT_NUMBERS', help=' Specify a comma separated list of AWS account numbers to INCLUDE for multi_account mode.') 80 | parser.add_argument('--exclude_accounts', metavar='ACCOUNT_NUMBERS', help=' Specify a comma separated list of AWS account numbers to EXCLUDE for multi_account mode.') 81 | 82 | function_parser_group = parser.add_argument_group('Single & Multi Account Options', 'Use these flags to choose which services you want to turn logging on for.') 83 | function_parser_group.add_argument('--all', choices=['text','parquet'], help=' Turns on all of the log types within the Assisted Log Enabler for AWS (does not include GuardDuty).\nYou must select \'text\' or \'parquet\' as the log storage format for VPC Flow Logs.') 84 | function_parser_group.add_argument('--eks', action='store_true', help=' Turns on Amazon EKS audit & authenticator logs.') 85 | function_parser_group.add_argument('--vpcflow', choices=['text','parquet'], help=' Turns on Amazon VPC Flow Logs. Choose \'text\' or \'parquet\' as the format to store the flow logs.') 86 | function_parser_group.add_argument('--r53querylogs', action='store_true', help=' Turns on Amazon Route 53 Resolver Query Logs.') 87 | function_parser_group.add_argument('--s3logs', action='store_true', help=' Turns on Amazon Bucket Logs.') 88 | function_parser_group.add_argument('--lblogs', action='store_true', help=' Turns on Amazon Load Balancer Logs.') 89 | function_parser_group.add_argument('--cloudtrail', action='store_true', help=' Turns on AWS CloudTrail. Only available in Single Account version.') 90 | function_parser_group.add_argument('--guardduty', action='store_true', help=' Turns on Amazon GuardDuty and exports findings to an S3 bucket. Will used specified bucket. WARNING: This creates a KMS Key to export findings.') 91 | function_parser_group.add_argument('--wafv2', action='store_true', help=' Turns on AWS WAFv2 Logs.') 92 | 93 | cleanup_parser_group = parser.add_argument_group('Cleanup Options', 'Use these flags to choose which resources you want to turn logging off for.') 94 | cleanup_parser_group.add_argument('--single_r53querylogs', action='store_true', help=' Removes Amazon Route 53 Resolver Query Log resources created by Assisted Log Enabler for AWS.') 95 | cleanup_parser_group.add_argument('--single_cloudtrail', action='store_true', help=' Removes AWS CloudTrail trails created by Assisted Log Enabler for AWS.') 96 | cleanup_parser_group.add_argument('--single_vpcflow', action='store_true', help=' Removes Amazon VPC Flow Log resources created by Assisted Log Enabler for AWS.') 97 | cleanup_parser_group.add_argument('--single_all', action='store_true', help=' Turns off all of the log types within the Assisted Log Enabler for AWS.') 98 | cleanup_parser_group.add_argument('--single_s3logs', action='store_true', help=' Removes Amazon Bucket Log resources created by Assisted Log Enabler for AWS.') 99 | cleanup_parser_group.add_argument('--single_lblogs', action='store_true', help=' Removes Amazon Load Balancer Log resources created by Assisted Log Enabler for AWS.') 100 | cleanup_parser_group.add_argument('--single_guardduty', action='store_true', help=' Removes Amazon GuardDuty detectors created by Assisted Log Enabler for AWS.') 101 | cleanup_parser_group.add_argument('--single_wafv2', action='store_true', help=' Removes AWS WAFv2 Logging Configurations created by Assisted Log Enabler for AWS.') 102 | 103 | dryrun_parser_group = parser.add_argument_group('Dry Run Options', 'Use these flags to run Assisted Log Enabler for AWS in Dry Run mode.') 104 | dryrun_parser_group.add_argument('--single_account', action='store_true', help=' Runs Assisted Log Enabler for AWS in Dry Run mode for a single AWS account.') 105 | dryrun_parser_group.add_argument('--multi_account', action='store_true', help=' Runs Assisted Log Enabler for AWS in Dry Run mode for a multi-account AWS environment, using AWS Organizations.') 106 | 107 | args = parser.parse_args() 108 | banner() 109 | 110 | event = 'event' 111 | context = 'context' 112 | bucket_name = 'default' 113 | included_accounts = 'all' 114 | excluded_accounts = 'none' 115 | if args.mode == 'single_account': 116 | 117 | 118 | 119 | 120 | if args.bucket: 121 | bucket_name = args.bucket 122 | if args.eks: 123 | ALE_single_account.run_eks() 124 | elif args.vpcflow: 125 | ALE_single_account.run_vpc_flow_logs(bucket_name,args.vpcflow) 126 | elif args.r53querylogs: 127 | ALE_single_account.run_r53_query_logs(bucket_name) 128 | elif args.s3logs: 129 | ALE_single_account.run_s3_logs() 130 | elif args.lblogs: 131 | ALE_single_account.run_lb_logs() 132 | elif args.cloudtrail: 133 | ALE_single_account.run_cloudtrail(bucket_name) 134 | elif args.guardduty: 135 | ALE_single_account.run_guardduty(bucket_name) 136 | elif args.wafv2: 137 | ALE_single_account.run_wafv2_logs() 138 | elif args.all: 139 | ALE_single_account.lambda_handler(event, context, bucket_name, args.all) 140 | else: 141 | logging.info("No valid option selected. Please run with -h to display valid options.") 142 | elif args.mode == 'multi_account': 143 | if args.include_accounts: 144 | included_accounts_list = args.include_accounts.strip().split(",") 145 | if all(len(a) == 12 for a in included_accounts_list): 146 | logging.info("Account numbers to be included: ") 147 | print(*included_accounts_list, sep=",") 148 | included_accounts = included_accounts_list 149 | else: 150 | print("An invalid account number specified for --include_accounts. Account numbers are 12 digits long.") 151 | if args.exclude_accounts: 152 | excluded_accounts_list = args.exclude_accounts.strip().split(",") 153 | if all(len(a) == 12 for a in excluded_accounts_list): 154 | logging.info("Account numbers to be excluded: ") 155 | print(*excluded_accounts_list, sep=",") 156 | excluded_accounts = excluded_accounts_list 157 | else: 158 | sys.exit("An invalid account number was specified for --exclude_accounts. Account numbers are 12 digits long.") 159 | 160 | if args.bucket: 161 | bucket_name = args.bucket 162 | if args.eks: 163 | ALE_multi_account.run_eks(included_accounts, excluded_accounts) 164 | elif args.vpcflow: 165 | ALE_multi_account.run_vpc_flow_logs(bucket_name, included_accounts, excluded_accounts, args.vpc_flow) 166 | elif args.r53querylogs: 167 | ALE_multi_account.run_r53_query_logs(bucket_name, included_accounts, excluded_accounts) 168 | elif args.s3logs: 169 | ALE_multi_account.run_s3_logs(included_accounts, excluded_accounts) 170 | elif args.lblogs: 171 | ALE_multi_account.run_lb_logs(included_accounts, excluded_accounts) 172 | elif args.guardduty: 173 | ALE_multi_account.run_guardduty(bucket_name, included_accounts, excluded_accounts) 174 | elif args.wafv2: 175 | ALE_multi_account.run_wafv2_logs(included_accounts, excluded_accounts) 176 | elif args.all: 177 | ALE_multi_account.lambda_handler(event, context, bucket_name, included_accounts, excluded_accounts, args.all) 178 | else: 179 | logging.info("No valid option selected. Please run with -h to display valid options.") 180 | elif args.mode == 'cleanup': 181 | if args.single_r53querylogs: 182 | ALE_cleanup_single.run_r53_cleanup() 183 | elif args.single_s3logs: 184 | ALE_cleanup_single.run_s3_cleanup() 185 | elif args.single_lblogs: 186 | ALE_cleanup_single.run_lb_cleanup() 187 | elif args.single_cloudtrail: 188 | ALE_cleanup_single.run_cloudtrail_cleanup() 189 | elif args.single_vpcflow: 190 | ALE_cleanup_single.run_vpcflow_cleanup() 191 | elif args.single_guardduty: 192 | ALE_cleanup_single.run_guardduty_cleanup() 193 | elif args.single_wafv2: 194 | ALE_cleanup_single.run_wafv2_cleanup() 195 | elif args.single_all: 196 | ALE_cleanup_single.lambda_handler(event, context) 197 | else: 198 | logging.info("No valid option selected. Please run with -h to display valid options.") 199 | elif args.mode == 'dryrun': 200 | if args.single_account: 201 | ALE_dryrun_single.lambda_handler(event, context) 202 | elif args.multi_account: 203 | ALE_dryrun_multi.lambda_handler(event, context) 204 | else: 205 | logging.info("No valid option selected. Please run with -h to display valid options.") 206 | else: 207 | print("No valid option selected. Please run with -h to display valid options.") 208 | 209 | 210 | if __name__ == '__main__': 211 | assisted_log_enabler() 212 | -------------------------------------------------------------------------------- /diagrams/assisted_log_enabler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/assisted-log-enabler-for-aws/8b6c63feabc27579e21247ed2f2ba4e459a9ad55/diagrams/assisted_log_enabler.png -------------------------------------------------------------------------------- /diagrams/assisted_log_enabler_lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/assisted-log-enabler-for-aws/8b6c63feabc27579e21247ed2f2ba4e459a9ad55/diagrams/assisted_log_enabler_lb.png -------------------------------------------------------------------------------- /diagrams/assisted_log_enabler_s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/assisted-log-enabler-for-aws/8b6c63feabc27579e21247ed2f2ba4e459a9ad55/diagrams/assisted_log_enabler_s3.png -------------------------------------------------------------------------------- /permissions/ALE_child_account_role.yaml: -------------------------------------------------------------------------------- 1 | #// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | #// SPDX-License-Identifier: Apache-2.0 3 | # Assisted Log Enabler for AWS - Find resources that are not logging, and turn them on. 4 | # Joshua "DozerCat" McKiddy - Customer Incident Response Team (CIRT) - AWS 5 | # This sample template is for creating an IAM Role within child accounts, for the purpose of running Assisted Log Enabler across a multi-account environment. 6 | 7 | 8 | AWSTemplateFormatVersion: "2010-09-09" 9 | Description: > 10 | Creates the baseline IAM Role and Policy, for use with Assisted Log Enabler across multiple accounts. 11 | Parameters: 12 | AssistedLogEnablerPolicyName: 13 | Description: Please name the policy that will be used with the Assisted Log Enabler IAM Role. 14 | Type: String 15 | Default: AssistedLogEnabler_IAM_Policy 16 | SourceAccountNumber: 17 | Description: Please provide the source account that Assisted Log Enabler will be running from. 18 | Type: String 19 | OrgId: 20 | Description: Please provide the AWS Organization ID (e.g. o-abcdefg123) 21 | Type: String 22 | 23 | 24 | 25 | Resources: 26 | AssistedLogEnablerPolicy: 27 | Type: AWS::IAM::ManagedPolicy 28 | Properties: 29 | ManagedPolicyName: !Ref AssistedLogEnablerPolicyName 30 | Path: / 31 | PolicyDocument: 32 | Version: 2012-10-17 33 | Statement: 34 | - Effect: Allow 35 | Action: 36 | - logs:CreateLogDelivery 37 | - ec2:CreateFlowLogs 38 | - ec2:DescribeVpcs 39 | - s3:PutLifecycleConfiguration 40 | - ec2:DescribeFlowLogs 41 | - s3:PutBucketPolicy 42 | - s3:CreateBucket 43 | - s3:GetBucketPolicy 44 | - cloudtrail:DescribeTrails 45 | - cloudtrail:CreateTrail 46 | - s3:PutObject 47 | - cloudtrail:StartLogging 48 | - eks:UpdateClusterConfig 49 | - eks:ListClusters 50 | - route53resolver:ListResolverQueryLogConfigAssociations 51 | - route53resolver:CreateResolverQueryLogConfig 52 | - route53resolver:AssociateResolverQueryLogConfig 53 | - route53resolver:TagResource 54 | - s3:PutBucketLogging 55 | - s3:GetBucketLogging 56 | - s3:ListBucket 57 | - s3:ListAllMyBuckets 58 | - s3:GetBucketLocation 59 | - s3:GetBucketAcl 60 | - s3:PutBucketAcl 61 | - s3:PutBucketPublicAccessBlock 62 | - s3:PutBucketLifecycleConfiguration 63 | - s3:GetObject 64 | - elb:DescribeLoadBalancers 65 | - elb:DescribeLoadBalancerAttributes 66 | - elb:ModifyLoadBalancerAttributes 67 | - elbv2:DescribeLoadBalancers 68 | - elbv2:DescribeLoadBalancerAttributes 69 | - elbv2:ModifyLoadBalancerAttributes 70 | - elasticloadbalancing:DescribeLoadBalancers 71 | - elasticloadbalancing:DescribeLoadBalancerAttributes 72 | - elasticloadbalancing:ModifyLoadBalancerAttributes 73 | - eks:ListClusters 74 | - ec2:CreateTags 75 | - guardduty:ListDetectors 76 | - guardduty:GetDetector 77 | - guardduty:TagResource 78 | - guardduty:CreateDetector 79 | - guardduty:UpdateDetector 80 | - guardduty:ListPublishingDestinations 81 | - guardduty:CreatePublishingDestination 82 | - guardduty:DescribePublishingDestination 83 | - wafv2:ListWebACLs 84 | - wafv2:ListLoggingConfigurations 85 | - wafv2:PutLoggingConfiguration 86 | Resource: '*' 87 | Condition: 88 | StringEquals: 89 | 'aws:PrincipalOrgId': !Ref OrgId 90 | - Effect: Allow 91 | Action: 92 | - iam:CreateServiceLinkedRole 93 | Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/route53resolver.amazonaws.com/AWSServiceRoleForRoute53Resolver 94 | Condition: 95 | StringLike: 96 | 'iam:AWSServiceName': 'route53resolver.amazonaws.com' 97 | - Effect: Allow 98 | Action: 99 | - iam:CreateServiceLinkedRole 100 | Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty 101 | Condition: 102 | StringLike: 103 | 'iam:AWSServiceName': 104 | - 'guardduty.amazonaws.com' 105 | - 'malware-protection.guardduty.amazonaws.com' 106 | - Effect: Allow 107 | Action: 108 | - iam:GetRole 109 | Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/malware-protection.guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDutyMalwareProtection 110 | - Effect: Allow 111 | Action: 112 | - route53resolver:ListResolverQueryLogConfigs 113 | - route53resolver:ListTagsForResource 114 | - route53resolver:ListResolverQueryLogConfigAssociations 115 | - route53resolver:DisassociateResolverQueryLogConfig 116 | - route53resolver:DeleteResolverQueryLogConfig 117 | Resource: '*' 118 | Condition: 119 | StringEquals: 120 | 'aws:PrincipalOrgId': !Ref OrgId 121 | 122 | AssistedLogEnablerRole: 123 | Type: AWS::IAM::Role 124 | Properties: 125 | RoleName: Assisted_Log_Enabler_IAM_Role 126 | Description: Role to be assumed for running Assisted Log Enabler across a multi-account environment. 127 | ManagedPolicyArns: 128 | - Ref: AssistedLogEnablerPolicy 129 | MaxSessionDuration: 3600 130 | Path: / 131 | AssumeRolePolicyDocument: 132 | Version: 2012-10-17 133 | Statement: 134 | - Effect: Allow 135 | Principal: 136 | AWS: 137 | - !Ref SourceAccountNumber 138 | Action: 139 | - sts:AssumeRole 140 | -------------------------------------------------------------------------------- /permissions/ALE_permissions_example_cleanup_single.json: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // Assisted Log Enabler for AWS - Find resources that are not logging, and turn them on. 4 | // Joshua "DozerCat" McKiddy - Customer Incident Response Team (CIRT) - AWS 5 | 6 | { 7 | "Version": "2012-10-17", 8 | "Statement": [ 9 | { 10 | "Sid": "AssistedLogEnablerCleanup1", 11 | "Effect": "Allow", 12 | "Action": [ 13 | "route53resolver:ListResolverQueryLogConfigs", 14 | "route53resolver:ListTagsForResource", 15 | "route53resolver:ListResolverQueryLogConfigAssociations", 16 | "route53resolver:DisassociateResolverQueryLogConfig", 17 | "route53resolver:DeleteResolverQueryLogConfig" 18 | ], 19 | "Resource": "*", 20 | "Condition": { 21 | "StringEquals": { 22 | "aws:SourceAccount": "" 23 | } 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /permissions/ALE_permissions_example_multi_account.json: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // Assisted Log Enabler (ALE) - Find resources that are not logging, and turn them on. 4 | // Joshua "DozerCat" McKiddy - Team DragonCat - AWS 5 | 6 | { 7 | "Version": "2012-10-17", 8 | "Statement": [ 9 | { 10 | "Effect": "Allow", 11 | "Action": [ 12 | "logs:CreateLogDelivery", 13 | "ec2:CreateFlowLogs", 14 | "ec2:DescribeVpcs", 15 | "s3:PutLifecycleConfiguration", 16 | "ec2:DescribeFlowLogs", 17 | "s3:PutBucketPolicy", 18 | "s3:CreateBucket", 19 | "s3:GetBucketPolicy", 20 | "cloudtrail:DescribeTrails", 21 | "cloudtrail:CreateTrail", 22 | "s3:PutObject", 23 | "cloudtrail:StartLogging", 24 | "eks:UpdateClusterConfig", 25 | "eks:ListClusters", 26 | "route53resolver:ListResolverQueryLogConfigAssociations", 27 | "route53resolver:CreateResolverQueryLogConfig", 28 | "route53resolver:AssociateResolverQueryLogConfig", 29 | "s3:PutBucketLogging", 30 | "s3:GetBucketLogging", 31 | "s3:ListBucket", 32 | "s3:ListAllMyBuckets", 33 | "s3:GetBucketLocation", 34 | "s3:GetBucketAcl", 35 | "s3:PutBucketAcl", 36 | "s3:PutBucketPublicAccessBlock", 37 | "s3:PutBucketLifecycleConfiguration", 38 | "elb:DescribeLoadBalancers", 39 | "elb:DescribeLoadBalancerAttributes", 40 | "elb:ModifyLoadBalancerAttributes", 41 | "elbv2:DescribeLoadBalancers", 42 | "elbv2:DescribeLoadBalancerAttributes", 43 | "elbv2:ModifyLoadBalancerAttributes" 44 | "elasticloadbalancing:DescribeLoadBalancers", 45 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 46 | "elasticloadbalancing:ModifyLoadBalancerAttributes", 47 | "eks:ListClusters" 48 | ], 49 | "Resource": "*", 50 | "Condition": { 51 | "StringEquals": { 52 | "aws:SourceAccount": "" 53 | } 54 | } 55 | }, 56 | { 57 | "Effect": "Allow", 58 | "Action": [ 59 | "iam:CreateServiceLinkedRole" 60 | ], 61 | "Resource": "arn:aws:iam:::role/aws-service-role/route53resolver.amazonaws.com/AWSServiceRoleForRoute53Resolver", 62 | "Condition": { 63 | "StringLike": { 64 | "iam:AWSServiceName": "route53resolver.amazonaws.com" 65 | } 66 | } 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /permissions/ALE_permissions_example_single_account.json: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // Assisted Log Enabler for AWS - Find resources that are not logging, and turn them on. 4 | // Joshua "DozerCat" McKiddy - Customer Incident Response Team (CIRT) - AWS 5 | 6 | { 7 | "Version": "2012-10-17", 8 | "Statement": [ 9 | { 10 | "Sid": "AssistedLogEnablerPolicy1", 11 | "Effect": "Allow", 12 | "Action": [ 13 | "logs:CreateLogDelivery", 14 | "ec2:CreateFlowLogs", 15 | "ec2:DescribeVpcs", 16 | "s3:PutLifecycleConfiguration", 17 | "ec2:DescribeFlowLogs", 18 | "s3:PutBucketPolicy", 19 | "s3:CreateBucket", 20 | "s3:GetBucketPolicy", 21 | "cloudtrail:DescribeTrails", 22 | "cloudtrail:CreateTrail", 23 | "s3:PutObject", 24 | "cloudtrail:StartLogging", 25 | "eks:UpdateClusterConfig", 26 | "eks:ListClusters", 27 | "route53resolver:ListResolverQueryLogConfigAssociations", 28 | "route53resolver:CreateResolverQueryLogConfig", 29 | "route53resolver:AssociateResolverQueryLogConfig", 30 | "s3:PutBucketLogging", 31 | "s3:GetBucketLogging", 32 | "s3:ListBucket", 33 | "s3:ListAllMyBuckets", 34 | "s3:GetBucketLocation", 35 | "s3:GetBucketAcl", 36 | "s3:PutBucketAcl", 37 | "s3:PutBucketPublicAccessBlock", 38 | "s3:PutBucketLifecycleConfiguration", 39 | "elb:DescribeLoadBalancers", 40 | "elb:DescribeLoadBalancerAttributes", 41 | "elb:ModifyLoadBalancerAttributes", 42 | "elbv2:DescribeLoadBalancers", 43 | "elbv2:DescribeLoadBalancerAttributes", 44 | "elbv2:ModifyLoadBalancerAttributes" 45 | "elasticloadbalancing:DescribeLoadBalancers", 46 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 47 | "elasticloadbalancing:ModifyLoadBalancerAttributes", 48 | "eks:ListClusters" 49 | ], 50 | "Resource": "*", 51 | "Condition": { 52 | "StringEquals": { 53 | "aws:SourceAccount": "" 54 | } 55 | } 56 | }, 57 | { 58 | "Effect": "Allow", 59 | "Action": [ 60 | "iam:CreateServiceLinkedRole" 61 | ], 62 | "Resource": "arn:aws:iam:::role/aws-service-role/route53resolver.amazonaws.com/AWSServiceRoleForRoute53Resolver", 63 | "Condition": { 64 | "StringLike": { 65 | "iam:AWSServiceName": "route53resolver.amazonaws.com" 66 | } 67 | } 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /subfunctions/ALE_cleanup_single.py: -------------------------------------------------------------------------------- 1 | #// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | #// SPDX-License-Identifier: Apache-2.0 3 | # Assisted Log Enabler for AWS - Find resources that are not logging, and turn them on. 4 | # Joshua "DozerCat" McKiddy - Customer Incident Response Team (CIRT) - AWS 5 | 6 | 7 | import logging 8 | import os 9 | import json 10 | import boto3 11 | import time 12 | import datetime 13 | from botocore.exceptions import ClientError 14 | from datetime import timezone 15 | 16 | current_date = datetime.datetime.now(tz=timezone.utc) 17 | current_date_string = str(current_date) 18 | timestamp_date = datetime.datetime.now(tz=timezone.utc).strftime("%Y-%m-%d-%H%M%S") 19 | timestamp_date_string = str(timestamp_date) 20 | 21 | 22 | cloudtrail = boto3.client('cloudtrail') 23 | region = os.environ['AWS_REGION'] 24 | 25 | 26 | region_list = ['af-south-1', 'ap-east-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', 'ap-south-1', 'ap-south-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-southeast-3', 'ap-southeast-4', 'ap-southeast-5', 'ca-central-1', 'ca-west-1', 'cn-north-1', 'cn-northwest-1', 'eu-central-1', 'eu-central-2', 'eu-north-1', 'eu-south-1', 'eu-south-2', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'il-central-1', 'me-central-1', 'me-south-1', 'sa-east-1', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2'] 27 | 28 | 29 | # 1. Remove the Route 53 Resolver Query Logging Resources created by Assisted Log Enabler 30 | def r53_cleanup(): 31 | """Function to clean up Route 53 Query Logging Resources""" 32 | logging.info("Note: This script can take a while to finish, depending about how many Route 53 Query Log resources exist (about 60 seconds per Query Log resource) that were created by Assisted Log Enabler for AWS") 33 | time.sleep(1) 34 | for aws_region in region_list: 35 | logging.info("---- LINE BREAK BETWEEN REGIONS ----") 36 | logging.info("Cleaning up Route 53 Query Logging Resources in region " + aws_region + ".") 37 | route53resolver = boto3.client('route53resolver', region_name=aws_region) 38 | try: 39 | QueryLogList: list = [] 40 | QueryLogArnRemoveList: list = [] 41 | QueryLogIdRemoveList: list = [] 42 | logging.info("ListResolverQueryLogConfigs API Call") 43 | ale_r53_logs = route53resolver.list_resolver_query_log_configs() # Collecting Arn of all Query Logs 44 | for r53_arn in ale_r53_logs['ResolverQueryLogConfigs']: 45 | QueryLogList.append(r53_arn['Arn']) 46 | for r53_tag_info in QueryLogList: 47 | logging.info("Listing Tags for " + r53_tag_info) 48 | logging.info("ListTagsForResource API Call") 49 | r53_tags = route53resolver.list_tags_for_resource( # Looking at tags for each Arn collected 50 | ResourceArn=r53_tag_info 51 | ) 52 | for value in r53_tags['Tags']: 53 | if (value['Key'] == 'Workflow' and value['Value'] == 'assisted-log-enabler'): 54 | logging.info("The following Route 53 Query Logger was created by Assisted Log Enabler for AWS, and will be removed within this function: " + r53_tag_info) 55 | QueryLogArnRemoveList.append(r53_tag_info) 56 | for Id in QueryLogArnRemoveList: 57 | logging.info("Gathering Resource ID for Route 53 Query Logging Resource to be removed.") 58 | logging.info("ListResolverQueryLogConfigs API Call") 59 | r53_resource_id = route53resolver.list_resolver_query_log_configs()['ResolverQueryLogConfigs'][QueryLogArnRemoveList.index(Id)]['Id'] # Collecting Resource ID for each Arn collected 60 | QueryLogIdRemoveList.append(r53_resource_id) 61 | logging.info(r53_resource_id + " added to removal list.") 62 | logging.info("The following Resource IDs were created by Assisted Log Enabler for AWS, and will be removed within this function.") 63 | print(QueryLogIdRemoveList) 64 | for r53_remove in QueryLogIdRemoveList: 65 | logging.info("Gathering Query Log Config Associations for " + r53_remove) 66 | logging.info("ListResolverQueryLogConfigAssociations API Call") 67 | associated_vpcs = route53resolver.list_resolver_query_log_config_associations( 68 | ) 69 | if associated_vpcs['TotalCount'] > 0 and associated_vpcs['ResolverQueryLogConfigAssociations'][0]['ResolverQueryLogConfigId'] == r53_remove: 70 | logging.info("The following Route 53 Query Logger is associated with a VPC, and will be removed within this function: " + r53_remove) 71 | VPCRemovalList = [] 72 | for vpc_info in associated_vpcs['ResolverQueryLogConfigAssociations']: 73 | VPCRemovalList.append(vpc_info['ResourceId']) 74 | logging.info("List of VPCs to be disassociated:") 75 | print(VPCRemovalList) 76 | for vpc in VPCRemovalList: 77 | logging.info("Removing " + vpc + " from Route 53 Query Logging configuration " + r53_remove) 78 | logging.info("DisassociateResolverQueryLogConfig API Call") 79 | removing_vpc = route53resolver.disassociate_resolver_query_log_config( 80 | ResolverQueryLogConfigId=r53_remove, 81 | ResourceId=vpc 82 | ) 83 | logging.info(vpc + " removed from " + r53_remove) 84 | time.sleep(1) 85 | logging.info("60 second pause to ensure disassociation of Amazon VPCs...") 86 | time.sleep(60) 87 | logging.info("Removing Route 53 Query Logger: " + r53_remove) 88 | logging.info("DeleteResolverQueryLogConfig") 89 | r53_cleanup = route53resolver.delete_resolver_query_log_config( 90 | ResolverQueryLogConfigId=r53_remove 91 | ) 92 | logging.info(r53_remove + " has been removed.") 93 | time.sleep(2) 94 | else: 95 | logging.info("Removing Route 53 Query Logger: " + r53_remove) 96 | logging.info("DeleteResolverQueryLogConfig") 97 | r53_cleanup = route53resolver.delete_resolver_query_log_config( 98 | ResolverQueryLogConfigId=r53_remove 99 | ) 100 | logging.info(r53_remove + " has been removed.") 101 | time.sleep(2) 102 | except Exception as exception_handle: 103 | logging.error(exception_handle) 104 | 105 | 106 | # 2. Remove the CloudTrail Logging Resources created by Assisted Log Enabler. 107 | def cloudtrail_cleanup(): 108 | """Function to clean up CloudTrail Logs""" 109 | logging.info("Cleaning up CloudTrail Logs.") 110 | try: 111 | logging.info("Cleaning up CloudTrail Logs created by Assisted Log Enabler for AWS.") 112 | trail_list: list = [] 113 | removal_list: list = [] 114 | logging.info("DescribeTrails API Call") 115 | cloudtrail_trails = cloudtrail.describe_trails() 116 | for trail in cloudtrail_trails['trailList']: 117 | trail_list.append(trail['TrailARN']) 118 | logging.info("Listing CloudTrail trails created by Assisted Log Enabler for AWS.") 119 | print("Full trail list") 120 | print(trail_list) 121 | for removal_trail in trail_list: 122 | logging.info("Checking tags for trails created by Assisted Log Enabler for AWS.") 123 | logging.info("ListTags API Call") 124 | trail_tags = cloudtrail.list_tags( 125 | ResourceIdList=[removal_trail] 126 | ) 127 | for tag_lists in trail_tags['ResourceTagList']: 128 | for key_info in tag_lists['TagsList']: 129 | print(key_info) 130 | if key_info['Key'] == 'workflow' and key_info['Value'] == 'assisted-log-enabler': 131 | removal_list.append(removal_trail) 132 | print("Trails to be removed") 133 | print(removal_list) 134 | for delete_trail in removal_list: 135 | logging.info("Deleting trails created by Assisted Log Enabler for AWS.") 136 | logging.info("DeleteTrail API Call") 137 | cloudtrail.delete_trail( 138 | Name=delete_trail 139 | ) 140 | logging.info(delete_trail + " has been deleted.") 141 | time.sleep(1) 142 | except Exception as exception_handle: 143 | logging.error(exception_handle) 144 | 145 | 146 | # 3. Remove the VPC Flow Log Resources created by Assisted Log Enabler for AWS. 147 | def vpcflow_cleanup(): 148 | """Function to clean up VPC Flow Logs""" 149 | logging.info("Cleaning up VPC Flow Logs created by Assisted Log Enabler for AWS.") 150 | for aws_region in region_list: 151 | try: 152 | logging.info("---- LINE BREAK BETWEEN REGIONS ----") 153 | logging.info("Cleaning up VPC Flow Logs created by Assisted Log Enabler for AWS in region " + aws_region + ".") 154 | removal_list: list = [] 155 | ec2 = boto3.client('ec2', region_name=aws_region) 156 | logging.info("DescribeFlowLogs API Call") 157 | vpc_flow_logs = ec2.describe_flow_logs( 158 | Filter=[ 159 | { 160 | 'Name': 'tag:workflow', 161 | 'Values': [ 162 | 'assisted-log-enabler' 163 | ] 164 | }, 165 | ] 166 | ) 167 | for flow_log_id in vpc_flow_logs['FlowLogs']: 168 | print(flow_log_id['FlowLogId']) 169 | removal_list.append(flow_log_id['FlowLogId']) 170 | print(removal_list) 171 | logging.info("DeleteFlowLogs API Call") 172 | delete_logs = ec2.delete_flow_logs( 173 | FlowLogIds=removal_list 174 | ) 175 | logging.info("Deleted Flow Logs that were created by Assisted Log Enabler for AWS.") 176 | time.sleep(1) 177 | except Exception as exception_handle: 178 | logging.error(exception_handle) 179 | 180 | # 4. Remove the S3 Logging Resources created by Assisted Log Enabler 181 | def s3_cleanup(): 182 | """Function to clean up Bucket Logs""" 183 | logging.info("Cleaning up Bucket Logs created by Assisted Log Enabler for AWS.") 184 | for aws_region in region_list: 185 | s3 = boto3.client('s3', region_name=aws_region) 186 | try: 187 | logging.info("---- LINE BREAK BETWEEN REGIONS ----") 188 | logging.info("Cleaning up Bucket Logs created by Assisted Log Enabler for AWS in region " + aws_region + ".") 189 | removal_list: list = [] 190 | logging.info("ListBuckets API Call") 191 | buckets = s3.list_buckets() 192 | for bucket in buckets['Buckets']: 193 | s3region=s3.get_bucket_location(Bucket=bucket["Name"])['LocationConstraint'] 194 | if s3region == aws_region: 195 | if 'aws-s3-log-collection-' not in str(bucket["Name"]): 196 | logging.info("Parsed out buckets created by Assisted Log Enabler for AWS in " + aws_region) 197 | logging.info("Checking remaining buckets to see if logs were enabled by Assisted Log Enabler for AWS in " + aws_region) 198 | logging.info("GetBucketLogging API Call for " + bucket["Name"]) 199 | s3temp=s3.get_bucket_logging(Bucket=bucket["Name"]) 200 | if 'aws-s3-log-collection-' in str(s3temp): 201 | removal_list.append(bucket["Name"]) 202 | elif s3region is None and aws_region == 'us-east-1': 203 | if 'aws-s3-log-collection-' not in str(bucket["Name"]): 204 | logging.info("Parsed out buckets created by Assisted Log Enabler for AWS in " + aws_region) 205 | logging.info("Checking remaining buckets to see if logs were enabled by Assisted Log Enabler for AWS in " + aws_region) 206 | logging.info("GetBucketLogging API Call for " + bucket["Name"]) 207 | s3temp=s3.get_bucket_logging(Bucket=bucket["Name"]) 208 | if 'aws-s3-log-collection-' in str(s3temp): 209 | removal_list.append(bucket["Name"]) 210 | if removal_list != []: 211 | logging.info("List S3 Buckets with Logging enabled by by Assisted Log Enabler for AWS in " + aws_region) 212 | print(removal_list) 213 | for bucket in removal_list: 214 | logging.info("Removing S3 Bucket Logging for " + bucket) 215 | logging.info("PutBucketLogging API Call") 216 | delete_s3_log = s3.put_bucket_logging( 217 | Bucket=bucket, 218 | BucketLoggingStatus={} 219 | ) 220 | logging.info("Removed S3 Bucket Logging created by Assisted Log Enabler for AWS.") 221 | time.sleep(1) 222 | else: 223 | logging.info("There are no S3 Bucket set by Log Enabler in " + aws_region) 224 | except Exception as exception_handle: 225 | logging.error(exception_handle) 226 | 227 | # 5. Remove the Load Balancer Logging Resources created by Assisted Log Enabler 228 | def lb_cleanup(): 229 | """Function to clean up Load Balancer Logs""" 230 | logging.info("Cleaning up Load Balancer Logs created by Assisted Log Enabler for AWS.") 231 | for aws_region in region_list: 232 | elbv1client = boto3.client('elb', region_name=aws_region) 233 | elbv2client = boto3.client('elbv2', region_name=aws_region) 234 | ELBList1: list = [] 235 | ELBList2: list = [] 236 | ELBv1LogList: list = [] 237 | ELBv2LogList: list = [] 238 | removal_list: list = [] 239 | try: 240 | logging.info("---- LINE BREAK BETWEEN REGIONS ----") 241 | logging.info("Cleaning up Bucket Logs created by Assisted Log Enabler for AWS in region " + aws_region + ".") 242 | logging.info("DescribeLoadBalancers API Call") 243 | ELBList1 = elbv1client.describe_load_balancers() 244 | for lb in ELBList1['LoadBalancerDescriptions']: 245 | logging.info("DescribeLoadBalancerAttibute API Call") 246 | lblog=elbv1client.describe_load_balancer_attributes(LoadBalancerName=lb['LoadBalancerName']) 247 | logging.info("Parsing out for ELB Access Logging") 248 | if lblog['LoadBalancerAttributes']['AccessLog']['Enabled'] == True: 249 | if 'aws-lb-log-collection-' in str(lblog['LoadBalancerAttributes']['AccessLog']['S3BucketName']): 250 | ELBv1LogList.append([lb['LoadBalancerName'],'classic']) 251 | logging.info("DescribeLoadBalancers v2 API Call") 252 | ELBList2 = elbv2client.describe_load_balancers() 253 | for lb in ELBList2['LoadBalancers']: 254 | logging.info("DescribeLoadBalancerAttibute v2 API Call") 255 | lblog=elbv2client.describe_load_balancer_attributes(LoadBalancerArn=lb['LoadBalancerArn']) 256 | logging.info("Parsing out for ELBv2 Access Logging") 257 | for lbtemp in lblog['Attributes']: 258 | if lbtemp['Key'] == 'access_logs.s3.enabled': 259 | if lbtemp['Value'] == 'true': 260 | for lbtemp2 in lblog['Attributes']: 261 | if lbtemp2['Key'] == 'access_logs.s3.bucket': 262 | if 'aws-lb-log-collection-' in str(lbtemp2['Value']): 263 | ELBv2LogList.append([lb['LoadBalancerName'],lb['LoadBalancerArn']]) 264 | removal_list=ELBv1LogList+ELBv2LogList 265 | if removal_list != []: 266 | logging.info("List Load Balancers with Logging enabled by by Assisted Log Enabler for AWS in " + aws_region) 267 | print(removal_list) 268 | for elb in removal_list: 269 | logging.info(elb[0] + " has Load Balancer logging on. It will be turned on within this function.") 270 | if ELBv1LogList != []: 271 | for elb in ELBv1LogList: 272 | logging.info("Removing logs for Load Balancer " + elb[0]) 273 | logging.info("ModifyLoadBalancerAttributes API Call") 274 | remove_lb_log = elbv1client.modify_load_balancer_attributes( 275 | LoadBalancerName=elb[0], 276 | LoadBalancerAttributes={ 277 | 'AccessLog': { 278 | 'Enabled': False } 279 | } 280 | ) 281 | logging.info("Logging Disabled for Load Balancer " + elb[0]) 282 | if ELBv2LogList != []: 283 | for elb in ELBv2LogList: 284 | logging.info("Removing logs for Load Balancer " + elb[0]) 285 | logging.info("ModifyLoadBalancerAttributes v2 API Call") 286 | remove_lb_log = elbv2client.modify_load_balancer_attributes( 287 | LoadBalancerArn=elb[1], 288 | Attributes=[ 289 | { 290 | 'Key': 'access_logs.s3.enabled', 291 | 'Value': 'false' 292 | } 293 | ] 294 | ) 295 | logging.info("Logging Disabled for Load Balancer " + elb[0]) 296 | logging.info("Removed Load Balancers Logging created by Assisted Log Enabler for AWS.") 297 | time.sleep(1) 298 | else: 299 | logging.info("There are no Load Balancers Logs set by Log Enabler in " + aws_region) 300 | except Exception as exception_handle: 301 | logging.error(exception_handle) 302 | 303 | 304 | def guardduty_cleanup(): 305 | """"Function to cleanup GuardDuty detectors""" 306 | logging.info("Cleaning up GuardDuty detectors created by Assisted Log Enabler for AWS.") 307 | for aws_region in region_list: 308 | detector_list = [] 309 | removal_list = [] 310 | guardduty = boto3.client('guardduty', region_name=aws_region) 311 | try: 312 | logging.info("---- LINE BREAK BETWEEN REGIONS ----") 313 | logging.info("Cleaning up GuardDuty detectors created by Assisted Log Enabler for AWS in region " + aws_region + ".") 314 | logging.info("ListDetectors API Call") 315 | detector_list = guardduty.list_detectors()["DetectorIds"] 316 | if detector_list != []: 317 | logging.info("GuardDuty detectors found: ") 318 | print(detector_list) 319 | logging.info("Checking tags for GuardDuty detectors created by Assisted Log Enabler.") 320 | for detector_id in detector_list: 321 | logging.info("GetDetector API Call") 322 | detector = guardduty.get_detector(DetectorId=detector_id) 323 | for tag in detector["Tags"]: 324 | if tag == "workflow": 325 | if detector["Tags"]["workflow"] == "assisted-log-enabler": 326 | removal_list.append(detector_id) 327 | if removal_list != []: 328 | logging.info("GuardDuty detectors created by Assisted Log Enabler to be deleted: ") 329 | print(removal_list) 330 | for detector_id in removal_list: 331 | logging.info("Removing GuardDuty detector " + detector_id) 332 | logging.info("DeleteDetector API Call") 333 | guardduty.delete_detector(DetectorId=detector_id) 334 | else: 335 | logging.info("There are no GuardDuty detectors created by Assisted Log Enabler in region " + aws_region + ".") 336 | else: 337 | logging.info("No GuardDuty detectors enabled in region " + aws_region + ".") 338 | 339 | except Exception as exception_handle: 340 | logging.error(exception_handle) 341 | 342 | def waf_cleanup(): 343 | """Function to cleanup WAFv2 Logging Configurations""" 344 | logging.info("Cleaning up WAFv2 logging previously enabled by Assisted Log Enabler.") 345 | 346 | for aws_region in region_list: 347 | wafv2 = boto3.client('wafv2', region_name=aws_region) 348 | try: 349 | logging.info("Checking Web ACL logging configurations in region " + aws_region + ".") 350 | logging.info("ListLoggingConfigurations API Call") 351 | log_configs_regional = wafv2.list_logging_configurations(Scope='REGIONAL')["LoggingConfigurations"] 352 | for acl in log_configs_regional: 353 | for destination in acl["LogDestinationConfigs"]: 354 | if "aws-waf-logs-ale-" in destination: 355 | logging.info("Deleting logging configuration for " + acl["ResourceArn"]) 356 | logging.info("DeleteLoggingConfiguration API Call") 357 | wafv2.delete_logging_configuration(ResourceArn=acl["ResourceArn"]) 358 | 359 | if aws_region == 'us-east-1': 360 | logging.info("Checking Global Web ACL logging configurations.") 361 | logging.info("ListLoggingConfigurations API Call") 362 | log_configs_cf = wafv2.list_logging_configurations(Scope='CLOUDFRONT')["LoggingConfigurations"] 363 | for acl in log_configs_cf: 364 | for destination in acl["LogDestinationConfigs"]: 365 | if "aws-waf-logs-ale-" in destination: 366 | logging.info("Deleting logging configuration for " + acl["ResourceArn"]) 367 | logging.info("DeleteLoggingConfiguration API Call") 368 | wafv2.delete_logging_configuration(ResourceArn=acl["ResourceArn"]) 369 | 370 | except Exception as exception_handle: 371 | logging.error(exception_handle) 372 | 373 | def run_vpcflow_cleanup(): 374 | """Function to run the vpcflow_cleanup function""" 375 | vpcflow_cleanup() 376 | logging.info("This is the end of the script. Please feel free to validate that logging resources have been cleaned up.") 377 | 378 | def run_cloudtrail_cleanup(): 379 | """Function to run the cloudtrail_cleanup function""" 380 | cloudtrail_cleanup() 381 | logging.info("This is the end of the script. Please feel free to validate that logging resources have been cleaned up.") 382 | 383 | def run_r53_cleanup(): 384 | """Function to run the r53_cleanup function""" 385 | r53_cleanup() 386 | logging.info("This is the end of the script. Please feel free to validate that logging resources have been cleaned up.") 387 | 388 | def run_s3_cleanup(): 389 | """Function to run the s3_cleanup function""" 390 | s3_cleanup() 391 | logging.info("This is the end of the script. Please feel free to validate that logging resources have been cleaned up.") 392 | 393 | def run_lb_cleanup(): 394 | """Function to run the lb_cleanup function""" 395 | lb_cleanup() 396 | logging.info("This is the end of the script. Please feel free to validate that logging resources have been cleaned up.") 397 | 398 | def run_guardduty_cleanup(): 399 | """Function to run the guardduty_cleanup function""" 400 | guardduty_cleanup() 401 | logging.info("This is the end of the script. Please feel free to validate that logging resources have been cleaned up.") 402 | 403 | def run_wafv2_cleanup(): 404 | """Function to run the wafv2_cleanup function""" 405 | waf_cleanup() 406 | logging.info("This is the end of the script. Please feel free to validate that logging resources have been cleaned up.") 407 | 408 | def lambda_handler(event, context): 409 | """Function that runs all of the previously defined functions""" 410 | r53_cleanup() 411 | vpcflow_cleanup() 412 | cloudtrail_cleanup() 413 | s3_cleanup() 414 | lb_cleanup() 415 | guardduty_cleanup() 416 | waf_cleanup() 417 | logging.info("This is the end of the script. Please feel free to validate that logging resources have been cleaned up.") 418 | 419 | 420 | if __name__ == '__main__': 421 | event = "event" 422 | context = "context" 423 | lambda_handler(event, context) 424 | -------------------------------------------------------------------------------- /subfunctions/ALE_dryrun_multi.py: -------------------------------------------------------------------------------- 1 | #// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | #// SPDX-License-Identifier: Apache-2.0 3 | # Assisted Log Enabler for AWS - Find resources that are not logging, and turn them on. 4 | # Joshua "DozerCat" McKiddy - Customer Incident Response Team (CIRT) - AWS 5 | 6 | 7 | import logging 8 | import os 9 | import json 10 | import boto3 11 | import time 12 | import datetime 13 | import argparse 14 | import csv 15 | import string 16 | import random 17 | from botocore.exceptions import ClientError 18 | from datetime import timezone 19 | 20 | current_date = datetime.datetime.now(tz=timezone.utc) 21 | current_date_string = str(current_date) 22 | timestamp_date = datetime.datetime.now(tz=timezone.utc).strftime("%Y-%m-%d-%H%M%S") 23 | timestamp_date_string = str(timestamp_date) 24 | 25 | 26 | sts = boto3.client('sts') 27 | cloudtrail = boto3.client('cloudtrail') 28 | organizations = boto3.client('organizations') 29 | region = os.environ['AWS_REGION'] 30 | 31 | 32 | region_list = ['af-south-1', 'ap-east-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', 'ap-south-1', 'ap-south-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-southeast-3', 'ap-southeast-4', 'ap-southeast-5', 'ca-central-1', 'ca-west-1', 'cn-north-1', 'cn-northwest-1', 'eu-central-1', 'eu-central-2', 'eu-north-1', 'eu-south-1', 'eu-south-2', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'il-central-1', 'me-central-1', 'me-south-1', 'sa-east-1', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2'] 33 | 34 | 35 | # 1. Obtain the AWS Accounts inside of AWS Organizations. 36 | def org_account_grab(): 37 | """Function to list accounts inside of AWS Organizations""" 38 | try: 39 | OrgAccountIdList: list = [] 40 | org_account_list = organizations.list_accounts() 41 | for accounts in org_account_list['Accounts']: 42 | OrgAccountIdList.append(accounts['Id']) 43 | get_organization_id = organizations.describe_organization() 44 | organization_id = get_organization_id['Organization']['Id'] 45 | except Exception as exception_handle: 46 | logging.error(exception_handle) 47 | logging.error("Multi account mode is only for accounts using AWS Organizations.") 48 | logging.error("Please run the Assisted Log Enabler in single account mode to turn on AWS Logs.") 49 | exit() 50 | return OrgAccountIdList, organization_id 51 | 52 | 53 | # 2. Obtain the current AWS Account Number. 54 | def get_account_number(): 55 | """Function to grab AWS Account number that Assisted Log Enabler runs from.""" 56 | sts = boto3.client('sts') 57 | account_number = sts.get_caller_identity()["Account"] 58 | return account_number 59 | 60 | 61 | # 3. Find VPCs and check if VPC Flow Logs are on. 62 | def dryrun_flow_log_activator(account_number, OrgAccountIdList, region_list): 63 | """Function to define the list of VPCs without logging turned on""" 64 | logging.info("Creating a list of VPCs without Flow Logs on.") 65 | for org_account in OrgAccountIdList: 66 | for aws_region in region_list: 67 | sts = boto3.client('sts') 68 | RoleArn = 'arn:aws:iam::%s:role/Assisted_Log_Enabler_IAM_Role' % org_account 69 | logging.info('Assuming Target Role %s for Assisted Log Enabler...' % RoleArn) 70 | assisted_log_enabler_sts = sts.assume_role( 71 | RoleArn=RoleArn, 72 | RoleSessionName='assisted-log-enabler-activation', 73 | DurationSeconds=3600, 74 | ) 75 | ec2_ma = boto3.client( 76 | 'ec2', 77 | aws_access_key_id=assisted_log_enabler_sts['Credentials']['AccessKeyId'], 78 | aws_secret_access_key=assisted_log_enabler_sts['Credentials']['SecretAccessKey'], 79 | aws_session_token=assisted_log_enabler_sts['Credentials']['SessionToken'], 80 | region_name=aws_region 81 | ) 82 | logging.info("Creating a list of VPCs without Flow Logs on in region " + aws_region + ".") 83 | try: 84 | VPCList: list = [] 85 | FlowLogList: list = [] 86 | logging.info("DescribeVpcs API Call") 87 | vpcs = ec2_ma.describe_vpcs() 88 | for vpc_id in vpcs["Vpcs"]: 89 | VPCList.append(vpc_id["VpcId"]) 90 | logging.info("List of VPCs found within account " + org_account + ", region " + aws_region + ":") 91 | print(VPCList) 92 | logging.info("DescribeFlowLogs API Call") 93 | vpcflowloglist = ec2_ma.describe_flow_logs() 94 | for resource_id in vpcflowloglist["FlowLogs"]: 95 | FlowLogList.append(resource_id["ResourceId"]) 96 | working_list = (list(set(VPCList) - set(FlowLogList))) 97 | logging.info("List of VPCs found within account " + org_account + ", region " + aws_region + " WITHOUT VPC Flow Logs:") 98 | print(working_list) 99 | for no_logs in working_list: 100 | logging.info(no_logs + " does not have VPC Flow logging on. This will not be turned on within the Dry Run option.") 101 | except Exception as exception_handle: 102 | logging.error(exception_handle) 103 | 104 | 105 | # 4. List EKS Clusters for visibility. 106 | def dryrun_eks_logging(region_list, OrgAccountIdList): 107 | """Function to turn on logging for EKS Clusters""" 108 | for org_account in OrgAccountIdList: 109 | for aws_region in region_list: 110 | logging.info("Showing Amazon EKS clusters in AWS account " + org_account + ", in region " + aws_region + ".") 111 | sts = boto3.client('sts') 112 | RoleArn = 'arn:aws:iam::%s:role/Assisted_Log_Enabler_IAM_Role' % org_account 113 | logging.info('Assuming Target Role %s for Assisted Log Enabler...' % RoleArn) 114 | assisted_log_enabler_sts = sts.assume_role( 115 | RoleArn=RoleArn, 116 | RoleSessionName='assisted-log-enabler-activation', 117 | DurationSeconds=3600, 118 | ) 119 | eks_ma = boto3.client( 120 | 'eks', 121 | aws_access_key_id=assisted_log_enabler_sts['Credentials']['AccessKeyId'], 122 | aws_secret_access_key=assisted_log_enabler_sts['Credentials']['SecretAccessKey'], 123 | aws_session_token=assisted_log_enabler_sts['Credentials']['SessionToken'], 124 | region_name=aws_region 125 | ) 126 | try: 127 | logging.info("ListClusters API Call") 128 | eks_clusters = eks_ma.list_clusters() 129 | eks_cluster_list = eks_clusters ['clusters'] 130 | logging.info("EKS Clusters found in " + aws_region + ":") 131 | print(eks_cluster_list) 132 | for cluster in eks_cluster_list: 133 | logging.info("Please check if Audit and Authenticator logs are on for EKS Cluster " + cluster) 134 | except Exception as exception_handle: 135 | logging.error(exception_handle) 136 | 137 | 138 | # 6. Turn on Route 53 Query Logging. 139 | def dryrun_route_53_query_logs(region_list, account_number, OrgAccountIdList): 140 | """Function to turn on Route 53 Query Logs for VPCs""" 141 | for org_account in OrgAccountIdList: 142 | for aws_region in region_list: 143 | logging.info("Checking Route 53 Query Logging on in AWS Account " + org_account + " VPCs, in region " + aws_region + ".") 144 | sts = boto3.client('sts') 145 | RoleArn = 'arn:aws:iam::%s:role/Assisted_Log_Enabler_IAM_Role' % org_account 146 | logging.info('Assuming Target Role %s for Assisted Log Enabler...' % RoleArn) 147 | assisted_log_enabler_sts = sts.assume_role( 148 | RoleArn=RoleArn, 149 | RoleSessionName='assisted-log-enabler-activation', 150 | DurationSeconds=3600, 151 | ) 152 | ec2_ma = boto3.client( 153 | 'ec2', 154 | aws_access_key_id=assisted_log_enabler_sts['Credentials']['AccessKeyId'], 155 | aws_secret_access_key=assisted_log_enabler_sts['Credentials']['SecretAccessKey'], 156 | aws_session_token=assisted_log_enabler_sts['Credentials']['SessionToken'], 157 | region_name=aws_region 158 | ) 159 | route53resolver_ma = boto3.client( 160 | 'route53resolver', 161 | aws_access_key_id=assisted_log_enabler_sts['Credentials']['AccessKeyId'], 162 | aws_secret_access_key=assisted_log_enabler_sts['Credentials']['SecretAccessKey'], 163 | aws_session_token=assisted_log_enabler_sts['Credentials']['SessionToken'], 164 | region_name=aws_region 165 | ) 166 | try: 167 | VPCList: list = [] 168 | QueryLogList: list = [] 169 | logging.info("DescribeVpcs API Call") 170 | vpcs = ec2_ma.describe_vpcs() 171 | for vpc_id in vpcs["Vpcs"]: 172 | VPCList.append(vpc_id["VpcId"]) 173 | logging.info("List of VPCs found within account " + org_account + ", region " + aws_region + ":") 174 | print(VPCList) 175 | logging.info("ListResolverQueryLogConfigAssociations API Call") 176 | query_log_details = route53resolver_ma.list_resolver_query_log_config_associations() 177 | for query_log_vpc_id in query_log_details['ResolverQueryLogConfigAssociations']: 178 | QueryLogList.append(query_log_vpc_id['ResourceId']) 179 | r53_working_list = (list(set(VPCList) - set(QueryLogList))) 180 | logging.info("List of VPCs found within account " + org_account + ", region " + aws_region + " WITHOUT Route 53 Query Logs:") 181 | print(r53_working_list) 182 | for no_query_logs in r53_working_list: 183 | logging.info(no_query_logs + " does not have Route 53 Query logging on. Running Assisted Log Enabler for AWS will turn this on.") 184 | except Exception as exception_handle: 185 | logging.error(exception_handle) 186 | 187 | # 7. Turn on S3 Logging. 188 | def dryrun_s3_logs(region_list, account_number, OrgAccountIdList): 189 | """Function to turn on Bucket Logs for Buckets""" 190 | for org_account in OrgAccountIdList: 191 | for aws_region in region_list: 192 | logging.info("Turning on Bucket Logging on in AWS Account " + org_account + " Buckets, in region " + aws_region + ".") 193 | sts = boto3.client('sts') 194 | RoleArn = 'arn:aws:iam::%s:role/Assisted_Log_Enabler_IAM_Role' % org_account 195 | logging.info('Assuming Target Role %s for Assisted Log Enabler...' % RoleArn) 196 | assisted_log_enabler_sts = sts.assume_role( 197 | RoleArn=RoleArn, 198 | RoleSessionName='assisted-log-enabler-activation', 199 | DurationSeconds=3600, 200 | ) 201 | s3_ma = boto3.client( 202 | 's3', 203 | aws_access_key_id=assisted_log_enabler_sts['Credentials']['AccessKeyId'], 204 | aws_secret_access_key=assisted_log_enabler_sts['Credentials']['SecretAccessKey'], 205 | aws_session_token=assisted_log_enabler_sts['Credentials']['SessionToken'], 206 | region_name=aws_region 207 | ) 208 | try: 209 | S3List: list = [] 210 | S3LogList: list = [] 211 | logging.info("ListBuckets API Call") 212 | buckets = s3_ma.list_buckets() 213 | for bucket in buckets['Buckets']: 214 | s3region=s3_ma.get_bucket_location(Bucket=bucket["Name"])['LocationConstraint'] 215 | if s3region == aws_region: 216 | S3List.append(bucket["Name"]) 217 | elif s3region is None and aws_region == 'us-east-1': 218 | S3List.append(bucket["Name"]) 219 | if S3List != []: 220 | logging.info("List of Buckets found within account " + org_account + ", region " + aws_region + ":") 221 | print(S3List) 222 | logging.info("Parsed out buckets created by Assisted Log Enabler for AWS in " + aws_region) 223 | logging.info("Checking remaining buckets to see if logs were enabled by Assisted Log Enabler for AWS in " + aws_region) 224 | logging.info("GetBucketLogging API Call") 225 | for bucket in S3List: 226 | if 'aws-log-collection-' + org_account + '-' + aws_region not in str(bucket): 227 | s3temp=s3_ma.get_bucket_logging(Bucket=bucket) 228 | if 'TargetBucket' not in str(s3temp): 229 | S3LogList.append(bucket) 230 | if S3LogList != []: 231 | logging.info("List of Buckets found within account " + org_account + ", region " + aws_region + " WITHOUT S3 Bucket Logs:") 232 | print(S3LogList) 233 | for bucket in S3LogList: 234 | logging.info(bucket + " does not have S3 BUCKET logging on. It will be turned on within this function.") 235 | else: 236 | logging.info("No S3 Bucket WITHOUT Logging enabled on account " + org_account + " region " + aws_region) 237 | else: 238 | logging.info("No S3 Buckets found within account " + org_account + ", region " + aws_region + ":") 239 | except Exception as exception_handle: 240 | logging.error(exception_handle) 241 | 242 | 243 | # 8. Turn on LB Logging. 244 | def dryrun_lb_logs(region_list, account_number, OrgAccountIdList): 245 | """Function to turn on Load Balancer Logs""" 246 | for org_account in OrgAccountIdList: 247 | for aws_region in region_list: 248 | logging.info("Checking for Load Balancer Logging in the account " + org_account + " in region " + aws_region + ".") 249 | sts = boto3.client('sts') 250 | RoleArn = 'arn:aws:iam::%s:role/Assisted_Log_Enabler_IAM_Role' % org_account 251 | logging.info('Assuming Target Role %s for Assisted Log Enabler...' % RoleArn) 252 | assisted_log_enabler_sts = sts.assume_role( 253 | RoleArn=RoleArn, 254 | RoleSessionName='assisted-log-enabler-activation', 255 | DurationSeconds=3600, 256 | ) 257 | elbv1_ma = boto3.client( 258 | 'elb', 259 | aws_access_key_id=assisted_log_enabler_sts['Credentials']['AccessKeyId'], 260 | aws_secret_access_key=assisted_log_enabler_sts['Credentials']['SecretAccessKey'], 261 | aws_session_token=assisted_log_enabler_sts['Credentials']['SessionToken'], 262 | region_name=aws_region 263 | ) 264 | elbv2_ma = boto3.client( 265 | 'elbv2', 266 | aws_access_key_id=assisted_log_enabler_sts['Credentials']['AccessKeyId'], 267 | aws_secret_access_key=assisted_log_enabler_sts['Credentials']['SecretAccessKey'], 268 | aws_session_token=assisted_log_enabler_sts['Credentials']['SessionToken'], 269 | region_name=aws_region 270 | ) 271 | try: 272 | ELBList1: list = [] 273 | ELBList2: list = [] 274 | ELBLogList: list = [] 275 | ELBv1LogList: list = [] 276 | ELBv2LogList: list = [] 277 | logging.info("DescribeLoadBalancers API Call") 278 | ELBList1 = elbv1_ma.describe_load_balancers() 279 | for lb in ELBList1['LoadBalancerDescriptions']: 280 | logging.info("DescribeLoadBalancerAttibute API Call") 281 | lblog=elbv1_ma.describe_load_balancer_attributes(LoadBalancerName=lb['LoadBalancerName']) 282 | logging.info("Parsing out for ELB Access Logging") 283 | if lblog['LoadBalancerAttributes']['AccessLog']['Enabled'] == False: 284 | ELBv1LogList.append([lb['LoadBalancerName'],'classic']) 285 | logging.info("DescribeLoadBalancers v2 API Call") 286 | ELBList2 = elbv2_ma.describe_load_balancers() 287 | for lb in ELBList2['LoadBalancers']: 288 | logging.info("DescribeLoadBalancerAttibute v2 API Call") 289 | lblog=elbv2_ma.describe_load_balancer_attributes(LoadBalancerArn=lb['LoadBalancerArn']) 290 | logging.info("Parsing out for ELBv2 Access Logging") 291 | for lbtemp in lblog['Attributes']: 292 | if lbtemp['Key'] == 'access_logs.s3.enabled': 293 | if lbtemp['Value'] == 'false': 294 | ELBv2LogList.append([lb['LoadBalancerName'],lb['LoadBalancerArn']]) 295 | ELBLogList=ELBv1LogList+ELBv2LogList 296 | if ELBLogList != []: 297 | logging.info("List of Load Balancers found within account " + account_number + ", region " + aws_region + " without logging enabled:") 298 | print(ELBLogList) 299 | for elb in ELBLogList: 300 | logging.info(elb[0] + " does not have Load Balancer logging on. It will be turned on within this function.") 301 | logging.info("Creating S3 Logging Bucket for Load Balancers") 302 | else: 303 | logging.info("No Load Balancers WITHOUT logging found within account " + account_number + ", region " + aws_region + ":") 304 | except Exception as exception_handle: 305 | logging.error(exception_handle) 306 | 307 | def dryrun_check_guardduty(region_list, OrgAccountIdList): 308 | """Function to check if GuardDuty is enabled""" 309 | logging.info("Creating KMS key for GuardDuty to export findings.") 310 | logging.info("Creating /guardduty folder in S3 Bucket") 311 | for org_account in OrgAccountIdList: 312 | for aws_region in region_list: 313 | logging.info("Checking for GuardDuty detectors in the account " + org_account + " in region " + aws_region + ".") 314 | sts = boto3.client('sts') 315 | RoleArn = 'arn:aws:iam::%s:role/Assisted_Log_Enabler_IAM_Role' % org_account 316 | logging.info('Assuming Target Role %s for Assisted Log Enabler...' % RoleArn) 317 | assisted_log_enabler_sts = sts.assume_role( 318 | RoleArn=RoleArn, 319 | RoleSessionName='assisted-log-enabler-activation', 320 | DurationSeconds=3600, 321 | ) 322 | guardduty_ma = boto3.client( 323 | 'guardduty', 324 | aws_access_key_id=assisted_log_enabler_sts['Credentials']['AccessKeyId'], 325 | aws_secret_access_key=assisted_log_enabler_sts['Credentials']['SecretAccessKey'], 326 | aws_session_token=assisted_log_enabler_sts['Credentials']['SessionToken'], 327 | region_name=aws_region 328 | ) 329 | try: 330 | logging.info("ListDetectors API Call") 331 | detectors = guardduty_ma.list_detectors() 332 | if detectors["DetectorIds"] == []: 333 | logging.info("GuardDuty is not enabled in the account " + org_account + ", region " + aws_region) 334 | logging.info("Enabling GuardDuty") 335 | logging.info("Exporting GuardDuty findings to an S3 bucket.") 336 | logging.info("Setting S3 Bucket as publishing destination for GuardDuty detector.") 337 | else: 338 | detector_id = detectors["DetectorIds"][0] 339 | logging.info("GetDetector API Call") 340 | if guardduty_ma.get_detector(DetectorId=detector_id)["Status"] == "DISABLED": 341 | logging.info("GuardDuty is suspended in the account " + org_account + ", region " + aws_region) 342 | logging.info("Enabling GuardDuty") 343 | logging.info("UpdateDetector API Call") 344 | else: 345 | logging.info("GuardDuty is already enabled in the account " + org_account + ", region " + aws_region) 346 | 347 | logging.info("Checking if GuardDuty detector publishes findings to S3.") 348 | logging.info("ListPublishingDestinations API Call") 349 | gd_destinations = guardduty_ma.list_publishing_destinations(DetectorId=detector_id)["Destinations"] 350 | if gd_destinations == []: 351 | logging.info("Detector does not publish findings to a destination. Setting S3 Bucket as publishing destination for GuardDuty detector.") 352 | else: 353 | for dest in gd_destinations: 354 | if dest["DestinationType"] == "S3": 355 | dest_id = dest["DestinationId"] 356 | logging.info("DescribePublishingDestination API Call") 357 | dest_info = guardduty_ma.describe_publishing_destination( 358 | DetectorId=detector_id, 359 | DestinationId=dest_id 360 | ) 361 | dest_s3_arn = dest_info["DestinationProperties"]["DestinationArn"] 362 | logging.info("Detector already publishes findings to S3 bucket " + dest_s3_arn.split(":")[-1]) 363 | except Exception as exception_handle: 364 | logging.error(exception_handle) 365 | 366 | def dryrun_wafv2_logs(region_list, OrgAccountIdList): 367 | """Function to check WAFv2 Logging""" 368 | for org_account in OrgAccountIdList: 369 | for aws_region in region_list: 370 | logging.info("Checking for WAF Logging in the account " + org_account + ", region " + aws_region + ".") 371 | sts = boto3.client('sts') 372 | RoleArn = 'arn:aws:iam::%s:role/Assisted_Log_Enabler_IAM_Role' % org_account 373 | logging.info('Assuming Target Role %s for Assisted Log Enabler...' % RoleArn) 374 | assisted_log_enabler_sts = sts.assume_role( 375 | RoleArn=RoleArn, 376 | RoleSessionName='assisted-log-enabler-activation', 377 | DurationSeconds=3600, 378 | ) 379 | wafv2_ma = boto3.client( 380 | 'wafv2', 381 | aws_access_key_id=assisted_log_enabler_sts['Credentials']['AccessKeyId'], 382 | aws_secret_access_key=assisted_log_enabler_sts['Credentials']['SecretAccessKey'], 383 | aws_session_token=assisted_log_enabler_sts['Credentials']['SessionToken'], 384 | region_name=aws_region 385 | ) 386 | 387 | try: 388 | WAFv2List: list = [] # list of all WAFv2 ARNs 389 | WAFv2LogList: list = [] # list of WAFv2 ARNs with logging enabled 390 | WAFv2NoLogList: list = [] # list of WAFv2 ARNs to enable logging 391 | 392 | # Get regional WAFv2 Web ACLs 393 | logging.info("ListWebAcls API Call") 394 | wafv2_regional_acl_list = wafv2_ma.list_web_acls(Scope='REGIONAL')["WebACLs"] 395 | for acl in wafv2_regional_acl_list: 396 | WAFv2List.append(acl["ARN"]) 397 | 398 | if aws_region == 'us-east-1': 399 | # Get CloudFront (global) WAFv2 Web ACLs 400 | logging.info("Checking for Global (CloudFront) Web ACLs") 401 | logging.info("ListWebAcls API Call") 402 | wafv2_cf_acl_list = wafv2_ma.list_web_acls(Scope='CLOUDFRONT')["WebACLs"] 403 | for acl in wafv2_cf_acl_list: 404 | WAFv2List.append(acl["ARN"]) 405 | 406 | logging.info("List of Web ACLs found within account " + org_account + ", region " + aws_region + ":") 407 | print(WAFv2List) 408 | 409 | # ListLoggingConfigurations returns only Web ACLs with logging already enabled 410 | logging.info("ListLoggingConfigurations API Call") 411 | wafv2_regional_log_configs = wafv2_ma.list_logging_configurations(Scope='REGIONAL')["LoggingConfigurations"] 412 | for acl in wafv2_regional_log_configs: 413 | WAFv2LogList.append(acl["ResourceArn"]) 414 | 415 | if aws_region == 'us-east-1': 416 | logging.info("Checking Global (CloudFront) Web ACL Logging Configurations") 417 | logging.info("ListLoggingConfigurations API Call") 418 | wafv2_cf_log_configs = wafv2_ma.list_logging_configurations(Scope='CLOUDFRONT')["LoggingConfigurations"] 419 | for acl in wafv2_cf_log_configs: 420 | WAFv2LogList.append(acl["ResourceArn"]) 421 | 422 | WAFv2NoLogList = list(set(WAFv2List) - set(WAFv2LogList)) 423 | logging.info("List of Web ACLs found within account " + org_account + ", region " + aws_region + " WITHOUT logging enabled:") 424 | print(WAFv2NoLogList) 425 | 426 | # If an S3 bucket has been created, use it as the log destination 427 | if WAFv2NoLogList != []: 428 | for arn in WAFv2NoLogList: 429 | logging.info(arn + " does not have logging turned on. Assisted Log Enabler would enable logging.") 430 | else: 431 | logging.info("No WAFv2 Web ACLs to enable logging for in account " + org_account + ", region " + aws_region + ".") 432 | 433 | except Exception as exception_handle: 434 | logging.error(exception_handle) 435 | 436 | 437 | def lambda_handler(event, context): 438 | """Function that runs all of the previously defined functions""" 439 | account_number = get_account_number() 440 | OrgAccountIdList, organization_id = org_account_grab() 441 | dryrun_flow_log_activator(account_number, OrgAccountIdList, region_list) 442 | dryrun_eks_logging(region_list, OrgAccountIdList) 443 | dryrun_route_53_query_logs(region_list, account_number, OrgAccountIdList) 444 | dryrun_s3_logs(region_list, account_number, OrgAccountIdList) 445 | dryrun_lb_logs(region_list, account_number, OrgAccountIdList) 446 | dryrun_check_guardduty(region_list, OrgAccountIdList) 447 | dryrun_wafv2_logs(region_list, OrgAccountIdList) 448 | logging.info("This is the end of the script. Please check the logs for the resources that would be turned on outside of the Dry Run option.") 449 | 450 | 451 | if __name__ == '__main__': 452 | event = "event" 453 | context = "context" 454 | lambda_handler(event, context) 455 | -------------------------------------------------------------------------------- /subfunctions/ALE_dryrun_single.py: -------------------------------------------------------------------------------- 1 | #// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | #// SPDX-License-Identifier: Apache-2.0 3 | # Assisted Log Enabler for AWS - Find resources that are not logging, and turn them on. 4 | # Joshua "DozerCat" McKiddy - Customer Incident Response Team (CIRT) - AWS 5 | 6 | 7 | import logging 8 | import os 9 | import json 10 | import boto3 11 | import time 12 | import datetime 13 | import string 14 | import random 15 | from botocore.exceptions import ClientError 16 | from datetime import timezone 17 | 18 | current_date = datetime.datetime.now(tz=timezone.utc) 19 | current_date_string = str(current_date) 20 | timestamp_date = datetime.datetime.now(tz=timezone.utc).strftime("%Y-%m-%d-%H%M%S") 21 | timestamp_date_string = str(timestamp_date) 22 | 23 | 24 | sts = boto3.client('sts') 25 | cloudtrail = boto3.client('cloudtrail') 26 | region = os.environ['AWS_REGION'] 27 | account_number = sts.get_caller_identity()["Account"] 28 | 29 | 30 | region_list = ['af-south-1', 'ap-east-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', 'ap-south-1', 'ap-south-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-southeast-3', 'ap-southeast-4', 'ap-southeast-5', 'ca-central-1', 'ca-west-1', 'cn-north-1', 'cn-northwest-1', 'eu-central-1', 'eu-central-2', 'eu-north-1', 'eu-south-1', 'eu-south-2', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'il-central-1', 'me-central-1', 'me-south-1', 'sa-east-1', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2'] 31 | 32 | 33 | # 1. Find VPCs and check if VPC Flow Logs are on. 34 | def dryrun_flow_log_activator(region_list, account_number): 35 | """Function that turns on the VPC Flow Logs, for VPCs identifed without them""" 36 | for aws_region in region_list: 37 | ec2 = boto3.client('ec2', region_name=aws_region) 38 | logging.info("Creating a list of VPCs without Flow Logs on in region " + aws_region + ".") 39 | try: 40 | VPCList: list = [] 41 | FlowLogList: list = [] 42 | logging.info("DescribeVpcs API Call") 43 | vpcs = ec2.describe_vpcs() 44 | for vpc_id in vpcs["Vpcs"]: 45 | VPCList.append(vpc_id["VpcId"]) 46 | logging.info("List of VPCs found within account " + account_number + ", region " + aws_region + ":") 47 | print(VPCList) 48 | logging.info("DescribeFlowLogs API Call") 49 | vpcflowloglist = ec2.describe_flow_logs() 50 | for resource_id in vpcflowloglist["FlowLogs"]: 51 | FlowLogList.append(resource_id["ResourceId"]) 52 | working_list = (list(set(VPCList) - set(FlowLogList))) 53 | logging.info("List of VPCs found within account " + account_number + ", region " + aws_region + " WITHOUT VPC Flow Logs:") 54 | print(working_list) 55 | for no_logs in working_list: 56 | logging.info(no_logs + " does not have VPC Flow logging on. This will not be turned on within the Dry Run option.") 57 | except Exception as exception_handle: 58 | logging.error(exception_handle) 59 | 60 | 61 | # 2. Check to see if a CloudTrail trail is configured. 62 | def dryrun_check_cloudtrail(account_number): 63 | """Function to check if CloudTrail is enabled""" 64 | logging.info("Checking to see if CloudTrail is on, and will activate if needed.") 65 | try: 66 | logging.info("DescribeTrails API Call") 67 | cloudtrail_status = cloudtrail.describe_trails( 68 | includeShadowTrails=True 69 | ) 70 | if cloudtrail_status["trailList"][0]["Name"] == "": 71 | logging.info("There is no CloudTrail trail created within this account. Running Assisted Log Enabler for AWS will create the CloudTrail trail for this account.") 72 | else: 73 | cloudtrail_name = cloudtrail_status["trailList"][0]["Name"] 74 | logging.info("There is a CloudTrail trail active. Name: " + cloudtrail_name) 75 | except Exception as exception_handle: 76 | logging.error(exception_handle) 77 | 78 | # 3. List EKS Clusters for visibility. 79 | def dryrun_eks_logging(region_list): 80 | """Function to turn on logging for EKS Clusters""" 81 | for aws_region in region_list: 82 | logging.info("Turning on audit and authenticator logging for EKS clusters in region " + aws_region + ".") 83 | eks = boto3.client('eks', region_name=aws_region) 84 | try: 85 | logging.info("ListClusters API Call") 86 | eks_clusters = eks.list_clusters() 87 | eks_cluster_list = eks_clusters ['clusters'] 88 | logging.info("EKS Clusters found in " + aws_region + ":") 89 | print(eks_cluster_list) 90 | for cluster in eks_cluster_list: 91 | logging.info("Please check if Audit and Authenticator logs are on for EKS Cluster " + cluster) 92 | except Exception as exception_handle: 93 | logging.error(exception_handle) 94 | 95 | 96 | # 4. Check if Route 53 Query Logging is turned on. 97 | def dryrun_route_53_query_logs(region_list, account_number): 98 | """Function to turn on Route 53 Query Logs for VPCs""" 99 | for aws_region in region_list: 100 | logging.info("Turning on Route 53 Query Logging on for VPCs in region " + aws_region + ".") 101 | ec2 = boto3.client('ec2', region_name=aws_region) 102 | route53resolver = boto3.client('route53resolver', region_name=aws_region) 103 | try: 104 | VPCList: list = [] 105 | QueryLogList: list = [] 106 | logging.info("DescribeVpcs API Call") 107 | vpcs = ec2.describe_vpcs() 108 | for vpc_id in vpcs["Vpcs"]: 109 | VPCList.append(vpc_id["VpcId"]) 110 | logging.info("List of VPCs found within account " + account_number + ", region " + aws_region + ":") 111 | print(VPCList) 112 | logging.info("ListResolverQueryLogConfigAssociations API Call") 113 | query_log_details = route53resolver.list_resolver_query_log_config_associations() 114 | for query_log_vpc_id in query_log_details['ResolverQueryLogConfigAssociations']: 115 | QueryLogList.append(query_log_vpc_id['ResourceId']) 116 | r53_working_list = (list(set(VPCList) - set(QueryLogList))) 117 | logging.info("List of VPCs found within account " + account_number + ", region " + aws_region + " WITHOUT Route 53 Query Logs:") 118 | print(r53_working_list) 119 | for no_query_logs in r53_working_list: 120 | logging.info(no_query_logs + " does not have Route 53 Query logging on. Running Assisted Log Enabler for AWS will turn this on.") 121 | except Exception as exception_handle: 122 | logging.error(exception_handle) 123 | 124 | # 5. Check if S3 Logging is on. 125 | def dryrun_s3_logs(region_list, account_number): 126 | """Function to turn on S3 Logs for Buckets""" 127 | for aws_region in region_list: 128 | logging.info("Turning on S3 Logging on for Buckets in region " + aws_region + ".") 129 | s3 = boto3.client('s3', region_name=aws_region) 130 | try: 131 | S3List: list = [] 132 | S3LogList: list = [] 133 | logging.info("ListBuckets API Call") 134 | buckets = s3.list_buckets() 135 | for bucket in buckets['Buckets']: 136 | s3region=s3.get_bucket_location(Bucket=bucket["Name"])['LocationConstraint'] 137 | if s3region == aws_region: 138 | S3List.append(bucket["Name"]) 139 | elif s3region is None and aws_region == 'us-east-1': 140 | S3List.append(bucket["Name"]) 141 | if S3List != []: 142 | logging.info("List of Buckets found within account " + account_number + ", region " + aws_region + ":") 143 | print(S3List) 144 | logging.info("Parsed out buckets created by Assisted Log Enabler for AWS in " + aws_region) 145 | logging.info("Checking remaining buckets to see if logs were enabled by Assisted Log Enabler for AWS in " + aws_region) 146 | logging.info("GetBucketLogging API Call") 147 | for bucket in S3List: 148 | if 'aws-s3-log-collection-' + account_number + '-' + aws_region not in str(bucket): 149 | s3temp=s3.get_bucket_logging(Bucket=bucket) 150 | if 'TargetBucket' not in str(s3temp): 151 | S3LogList.append(bucket) 152 | if S3LogList != []: 153 | logging.info("List of Buckets found within account " + account_number + ", region " + aws_region + " WITHOUT S3 Bucket Logs:") 154 | print(S3LogList) 155 | for bucket in S3LogList: 156 | logging.info(bucket + " does not have S3 BUCKET logging on. It will be turned on within this function.") 157 | else: 158 | logging.info("No S3 Bucket WITHOUT Logging enabled on account " + account_number + " region " + aws_region) 159 | else: 160 | logging.info("No S3 Buckets found within account " + account_number + ", region " + aws_region + ":") 161 | except Exception as exception_handle: 162 | logging.error(exception_handle) 163 | 164 | # 6. Check if Load Balancer Logging is on. 165 | def dryrun_lb_logs(region_list, account_number): 166 | """Function to turn on LB Logs""" 167 | for aws_region in region_list: 168 | elbv1client = boto3.client('elb', region_name=aws_region) 169 | elbv2client = boto3.client('elbv2', region_name=aws_region) 170 | account_number = sts.get_caller_identity()["Account"] 171 | logging.info("Checking for Load Balancer Logging in the account " + account_number + ", region " + aws_region) 172 | try: 173 | ELBList1: list = [] 174 | ELBList2: list = [] 175 | ELBLogList: list = [] 176 | ELBv1LogList: list = [] 177 | ELBv2LogList: list = [] 178 | logging.info("DescribeLoadBalancers API Call") 179 | ELBList1 = elbv1client.describe_load_balancers() 180 | for lb in ELBList1['LoadBalancerDescriptions']: 181 | logging.info("DescribeLoadBalancerAttibute API Call") 182 | lblog=elbv1client.describe_load_balancer_attributes(LoadBalancerName=lb['LoadBalancerName']) 183 | logging.info("Parsing out for ELB Access Logging") 184 | if lblog['LoadBalancerAttributes']['AccessLog']['Enabled'] == False: 185 | ELBv1LogList.append([lb['LoadBalancerName'],'classic']) 186 | logging.info("DescribeLoadBalancers v2 API Call") 187 | ELBList2 = elbv2client.describe_load_balancers() 188 | for lb in ELBList2['LoadBalancers']: 189 | logging.info("DescribeLoadBalancerAttibute v2 API Call") 190 | lblog=elbv2client.describe_load_balancer_attributes(LoadBalancerArn=lb['LoadBalancerArn']) 191 | logging.info("Parsing out for ELBv2 Access Logging") 192 | for lbtemp in lblog['Attributes']: 193 | if lbtemp['Key'] == 'access_logs.s3.enabled': 194 | if lbtemp['Value'] == 'false': 195 | ELBv2LogList.append([lb['LoadBalancerName'],lb['LoadBalancerArn']]) 196 | ELBLogList=ELBv1LogList+ELBv2LogList 197 | if ELBLogList != []: 198 | logging.info("List of Load Balancers found within account " + account_number + ", region " + aws_region + " without logging enabled:") 199 | print(ELBLogList) 200 | for elb in ELBLogList: 201 | logging.info(elb[0] + " does not have Load Balancer logging on. It will be turned on within this function.") 202 | else: 203 | logging.info("No Load Balancers WITHOUT logging found within account " + account_number + ", region " + aws_region + ":") 204 | except Exception as exception_handle: 205 | logging.error(exception_handle) 206 | 207 | def dryrun_check_guardduty(region_list, account_number): 208 | """Function to check if GuardDuty is enabled""" 209 | logging.info("Creating KMS key for GuardDuty to export findings.") 210 | logging.info("Creating /guardduty folder in S3 Bucket") 211 | for aws_region in region_list: 212 | guardduty = boto3.client('guardduty', region_name=aws_region) 213 | logging.info("Checking for GuardDuty detector in the account " + account_number + ", region " + aws_region) 214 | try: 215 | logging.info("ListDetectors API Call") 216 | detectors = guardduty.list_detectors() 217 | if detectors["DetectorIds"] == []: 218 | logging.info("GuardDuty is not enabled in the account" + account_number + ", region " + aws_region) 219 | logging.info("Enabling GuardDuty") 220 | logging.info("Exporting GuardDuty findings to an S3 bucket.") 221 | logging.info("Setting S3 Bucket as publishing destination for GuardDuty detector.") 222 | else: 223 | detector_id = detectors["DetectorIds"][0] 224 | logging.info("GetDetector API Call") 225 | if guardduty.get_detector(DetectorId=detector_id)["Status"] == "DISABLED": 226 | logging.info("GuardDuty is suspended in the account " + account_number + ", region " + aws_region) 227 | logging.info("Enabling GuardDuty") 228 | logging.info("UpdateDetector API Call") 229 | else: 230 | logging.info("GuardDuty is already enabled in the account " + account_number + ", region " + aws_region) 231 | 232 | logging.info("Checking if GuardDuty detector publishes findings to S3.") 233 | logging.info("ListPublishingDestinations API Call") 234 | gd_destinations = guardduty.list_publishing_destinations(DetectorId=detector_id)["Destinations"] 235 | if gd_destinations == []: 236 | logging.info("Detector does not publish findings to a destination. Setting S3 Bucket as publishing destination for GuardDuty detector.") 237 | else: 238 | for dest in gd_destinations: 239 | if dest["DestinationType"] == "S3": 240 | dest_id = dest["DestinationId"] 241 | logging.info("DescribePublishingDestination API Call") 242 | dest_info = guardduty.describe_publishing_destination( 243 | DetectorId=detector_id, 244 | DestinationId=dest_id 245 | ) 246 | dest_s3_arn = dest_info["DestinationProperties"]["DestinationArn"] 247 | logging.info("Detector already publishes findings to S3 bucket " + dest_s3_arn.split(":")[-1]) 248 | except Exception as exception_handle: 249 | logging.error(exception_handle) 250 | 251 | def dryrun_wafv2_logs(region_list, account_number): 252 | """Function to check WAFv2 Logging""" 253 | for aws_region in region_list: 254 | wafv2 = boto3.client('wafv2', region_name=aws_region) 255 | logging.info("Checking for WAFv2 Logging in the account " + account_number + ", region " + aws_region) 256 | try: 257 | WAFv2List: list = [] # list of all WAFv2 ARNs 258 | WAFv2LogList: list = [] # list of WAFv2 ARNs with logging enabled 259 | WAFv2NoLogList: list = [] # list of WAFv2 ARNs to enable logging 260 | 261 | # Get regional WAFv2 Web ACLs 262 | logging.info("ListWebAcls API Call") 263 | wafv2_regional_acl_list = wafv2.list_web_acls(Scope='REGIONAL')["WebACLs"] 264 | for acl in wafv2_regional_acl_list: 265 | WAFv2List.append(acl["ARN"]) 266 | 267 | if aws_region == 'us-east-1': 268 | # Get CloudFront (global) WAFv2 Web ACLs 269 | logging.info("Checking for Global (CloudFront) Web ACLs") 270 | logging.info("ListWebAcls API Call") 271 | wafv2_cf_acl_list = wafv2.list_web_acls(Scope='CLOUDFRONT')["WebACLs"] 272 | for acl in wafv2_cf_acl_list: 273 | WAFv2List.append(acl["ARN"]) 274 | 275 | logging.info("List of Web ACLs found within account " + account_number + ", region " + aws_region + ":") 276 | print(WAFv2List) 277 | 278 | logging.info("ListLoggingConfigurations API Call") 279 | wafv2_regional_log_configs = wafv2.list_logging_configurations(Scope='REGIONAL')["LoggingConfigurations"] 280 | for acl in wafv2_regional_log_configs: 281 | WAFv2LogList.append(acl["ResourceArn"]) 282 | 283 | if aws_region == 'us-east-1': 284 | logging.info("Checking Global (CloudFront) Web ACL Logging Configurations") 285 | logging.info("ListLoggingConfigurations API Call") 286 | wafv2_cf_log_configs = wafv2.list_logging_configurations(Scope='CLOUDFRONT')["LoggingConfigurations"] 287 | for acl in wafv2_cf_log_configs: 288 | WAFv2LogList.append(acl["ResourceArn"]) 289 | 290 | WAFv2NoLogList = list(set(WAFv2List) - set(WAFv2LogList)) 291 | logging.info("List of Web ACLs found within account " + account_number + ", region " + aws_region + " WITHOUT logging enabled:") 292 | print(WAFv2NoLogList) 293 | 294 | if WAFv2NoLogList != []: 295 | for arn in WAFv2NoLogList: 296 | logging.info(arn + " does not have logging turned on. Assisted Log Enabler would enable logging.") 297 | else: 298 | logging.info("No WAFv2 Web ACLs to enable logging for in account " + account_number + ", region " + aws_region + ".") 299 | 300 | except Exception as exception_handle: 301 | logging.error(exception_handle) 302 | 303 | def lambda_handler(event, context): 304 | """Function that runs all of the previously defined functions""" 305 | dryrun_flow_log_activator(region_list, account_number) 306 | dryrun_check_cloudtrail(account_number) 307 | dryrun_eks_logging(region_list) 308 | dryrun_route_53_query_logs(region_list, account_number) 309 | dryrun_s3_logs(region_list, account_number) 310 | dryrun_lb_logs(region_list, account_number) 311 | dryrun_check_guardduty(region_list, account_number) 312 | dryrun_wafv2_logs(region_list, account_number) 313 | logging.info("This is the end of the script. Please check the logs for the resources that would be turned on outside of the Dry Run option.") 314 | 315 | 316 | if __name__ == '__main__': 317 | event = "event" 318 | context = "context" 319 | lambda_handler(event, context) 320 | -------------------------------------------------------------------------------- /subfunctions/ALE_single_account.py: -------------------------------------------------------------------------------- 1 | #// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | #// SPDX-License-Identifier: Apache-2.0 3 | # Assisted Log Enabler for AWS - Find resources that are not logging, and turn them on. 4 | # Joshua "DozerCat" McKiddy - Customer Incident Response Team (CIRT) - AWS 5 | 6 | 7 | import logging 8 | import os 9 | import json 10 | import boto3 11 | import time 12 | import datetime 13 | import string 14 | import random 15 | from botocore.exceptions import ClientError 16 | from datetime import timezone 17 | 18 | current_date = datetime.datetime.now(tz=timezone.utc) 19 | current_date_string = str(current_date) 20 | timestamp_date = datetime.datetime.now(tz=timezone.utc).strftime("%Y-%m-%d-%H%M%S") 21 | timestamp_date_string = str(timestamp_date) 22 | 23 | 24 | sts = boto3.client('sts') 25 | s3 = boto3.client('s3') 26 | cloudtrail = boto3.client('cloudtrail') 27 | region = os.environ['AWS_REGION'] 28 | 29 | 30 | region_list = ['af-south-1', 'ap-east-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', 'ap-south-1', 'ap-south-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-southeast-3', 'ap-southeast-4', 'ap-southeast-5', 'ca-central-1', 'ca-west-1', 'cn-north-1', 'cn-northwest-1', 'eu-central-1', 'eu-central-2', 'eu-north-1', 'eu-south-1', 'eu-south-2', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'il-central-1', 'me-central-1', 'me-south-1', 'sa-east-1', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2'] 31 | 32 | # 0. Define random string for S3 Bucket Name 33 | def random_string_generator(): 34 | lower_letters = string.ascii_lowercase 35 | numbers = string.digits 36 | unique_end = (''.join(random.choice(lower_letters + numbers) for char in range(6))) 37 | return unique_end 38 | 39 | 40 | # 1. Create a Bucket and Lifecycle Policy 41 | def create_bucket(unique_end): 42 | """Function to create the bucket for storing logs""" 43 | try: 44 | account_number = sts.get_caller_identity()["Account"] 45 | logging.info("Creating bucket in %s" % account_number) 46 | logging.info("CreateBucket API Call") 47 | bucket_name = "aws-log-collection-" + account_number + "-" + region + "-" + unique_end 48 | if region == 'us-east-1': 49 | logging_bucket_dict = s3.create_bucket( 50 | Bucket=bucket_name 51 | ) 52 | else: 53 | logging_bucket_dict = s3.create_bucket( 54 | Bucket=bucket_name, 55 | CreateBucketConfiguration={ 56 | 'LocationConstraint': region 57 | } 58 | ) 59 | logging.info("Bucket Created.") 60 | logging.info("Setting lifecycle policy.") 61 | logging.info("PutBucketLifecycleConfiguration API Call") 62 | lifecycle_policy = s3.put_bucket_lifecycle_configuration( 63 | Bucket=bucket_name, 64 | LifecycleConfiguration={ 65 | 'Rules': [ 66 | { 67 | 'Expiration': { 68 | 'Days': 400 69 | }, 70 | 'Status': 'Enabled', 71 | 'Prefix': '', 72 | 'ID': 'LogStorage', 73 | 'Transitions': [ 74 | { 75 | 'Days': 90, 76 | 'StorageClass': 'INTELLIGENT_TIERING' 77 | } 78 | ] 79 | } 80 | ] 81 | } 82 | ) 83 | logging.info("Lifecycle Policy successfully set.") 84 | logging.info("PutObject API Call") 85 | create_ct_path = s3.put_object( 86 | Bucket=bucket_name, 87 | Key='cloudtrail/AWSLogs/' + account_number + '/') 88 | logging.info("PutBucketPolicy API Call") 89 | bucket_policy = s3.put_bucket_policy( 90 | Bucket=bucket_name, 91 | Policy='{ "Version": "2012-10-17", "Statement": [ { "Sid": "AWSCloudTrailAclCheck20150319", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "s3:GetBucketAcl", "Resource": "arn:aws:s3:::' + bucket_name + '" }, { "Sid": "AWSCloudTrailWrite20150319", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::' + bucket_name + '/cloudtrail/AWSLogs/' + account_number + '/*", "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } } }, { "Sid": "AWSLogDeliveryAclCheck", "Effect": "Allow", "Principal": { "Service": "delivery.logs.amazonaws.com" }, "Action": "s3:GetBucketAcl", "Resource": "arn:aws:s3:::' + bucket_name + '" }, { "Sid": "AWSLogDeliveryWriteVPC", "Effect": "Allow", "Principal": { "Service": "delivery.logs.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::' + bucket_name + '/vpcflowlogs/*", "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } } }, { "Sid": "AWSLogDeliveryWriteR53", "Effect": "Allow", "Principal": { "Service": "delivery.logs.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::' + bucket_name + '/r53querylogs/*", "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } } }, { "Sid": "Deny non-HTTPS access", "Effect": "Deny", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:*", "Resource": "arn:aws:s3:::' + bucket_name + '/guardduty/*", "Condition": { "Bool": { "aws:SecureTransport": "false" } } }, { "Sid": "Allow PutObject", "Effect": "Allow", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::' + bucket_name + '/guardduty/*" }, { "Sid": "Allow GetBucketLocation", "Effect": "Allow", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::' + bucket_name + '" } ] }' 92 | ) 93 | logging.info("Setting the S3 bucket Public Access to Blocked") 94 | logging.info("PutPublicAccessBlock API Call") 95 | bucket_private = s3.put_public_access_block( 96 | Bucket=bucket_name, 97 | PublicAccessBlockConfiguration={ 98 | 'BlockPublicAcls': True, 99 | 'IgnorePublicAcls': True, 100 | 'BlockPublicPolicy': True, 101 | 'RestrictPublicBuckets': True 102 | }, 103 | ) 104 | except Exception as exception_handle: 105 | logging.error(exception_handle) 106 | return bucket_name 107 | 108 | # If custom bucket is supplied, update the bucket policy 109 | def update_custom_bucket_policy(bucket_name, account_number): 110 | logging.info("Pre-existing S3 bucket specified. Updating bucket policy.") 111 | logging.info("PutBucketPolicy API Call") 112 | s3.put_bucket_policy( 113 | Bucket=bucket_name, 114 | Policy='{ "Version": "2012-10-17", "Statement": [ { "Sid": "AWSCloudTrailAclCheck20150319", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "s3:GetBucketAcl", "Resource": "arn:aws:s3:::' + bucket_name + '" }, { "Sid": "AWSCloudTrailWrite20150319", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::' + bucket_name + '/cloudtrail/AWSLogs/' + account_number + '/*", "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } } }, { "Sid": "AWSLogDeliveryAclCheck", "Effect": "Allow", "Principal": { "Service": "delivery.logs.amazonaws.com" }, "Action": "s3:GetBucketAcl", "Resource": "arn:aws:s3:::' + bucket_name + '" }, { "Sid": "AWSLogDeliveryWriteVPC", "Effect": "Allow", "Principal": { "Service": "delivery.logs.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::' + bucket_name + '/vpcflowlogs/*", "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } } }, { "Sid": "AWSLogDeliveryWriteR53", "Effect": "Allow", "Principal": { "Service": "delivery.logs.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::' + bucket_name + '/r53querylogs/*", "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } } }, { "Sid": "Deny non-HTTPS access", "Effect": "Deny", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:*", "Resource": "arn:aws:s3:::' + bucket_name + '/guardduty/*", "Condition": { "Bool": { "aws:SecureTransport": "false" } } }, { "Sid": "Allow PutObject", "Effect": "Allow", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::' + bucket_name + '/guardduty/*" }, { "Sid": "Allow GetBucketLocation", "Effect": "Allow", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::' + bucket_name + '" } ] }' 115 | ) 116 | 117 | # 2. Find VPCs and turn flow logs on if not on already. 118 | def flow_log_activator(region_list, account_number, bucket_name, file_format): 119 | """Function that turns on the VPC Flow Logs, for VPCs identifed without them""" 120 | for aws_region in region_list: 121 | logging.info("Turning on VPC Flow Logs. Flow logs will be stored as " + file_format) 122 | ec2 = boto3.client('ec2', region_name=aws_region) 123 | logging.info("Creating a list of VPCs without Flow Logs on in region " + aws_region + ".") 124 | try: 125 | VPCList: list = [] 126 | FlowLogList: list = [] 127 | logging.info("DescribeVpcs API Call") 128 | vpcs = ec2.describe_vpcs() 129 | for vpc_id in vpcs["Vpcs"]: 130 | VPCList.append(vpc_id["VpcId"]) 131 | logging.info("List of VPCs found within account " + account_number + ", region " + aws_region + ":") 132 | print(VPCList) 133 | logging.info("DescribeFlowLogs API Call") 134 | vpcflowloglist = ec2.describe_flow_logs() 135 | for resource_id in vpcflowloglist["FlowLogs"]: 136 | FlowLogList.append(resource_id["ResourceId"]) 137 | working_list = (list(set(VPCList) - set(FlowLogList))) 138 | logging.info("List of VPCs found within account " + account_number + ", region " + aws_region + " WITHOUT VPC Flow Logs:") 139 | print(working_list) 140 | for no_logs in working_list: 141 | logging.info(no_logs + " does not have VPC Flow logging on. It will be turned on within this function.") 142 | logging.info("Activating logs for VPCs that do not have them turned on.") 143 | logging.info("If all VPCs have Flow Logs turned on, you will get an MissingParameter error. That is normal.") 144 | logging.info("CreateFlowLogs API Call") 145 | flow_log_on = ec2.create_flow_logs( 146 | ResourceIds=working_list, 147 | ResourceType='VPC', 148 | TrafficType='ALL', 149 | LogDestinationType='s3', 150 | LogDestination='arn:aws:s3:::' + bucket_name + '/vpcflowlogs', 151 | LogFormat='${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status} ${vpc-id} ${type} ${tcp-flags} ${subnet-id} ${sublocation-type} ${sublocation-id} ${region} ${pkt-srcaddr} ${pkt-dstaddr} ${instance-id} ${az-id} ${pkt-src-aws-service} ${pkt-dst-aws-service} ${flow-direction} ${traffic-path}', 152 | TagSpecifications=[ 153 | { 154 | 'ResourceType': 'vpc-flow-log', 155 | 'Tags': [ 156 | { 157 | 'Key': 'workflow', 158 | 'Value': 'assisted-log-enabler' 159 | }, 160 | ] 161 | } 162 | ], 163 | DestinationOptions={'FileFormat':file_format} 164 | ) 165 | # Custom format specified in same order as documentation lists them at https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html 166 | logging.info("VPC Flow Logs are turned on.") 167 | except Exception as exception_handle: 168 | logging.error(exception_handle) 169 | 170 | 171 | # 3. Check to see if a CloudTrail trail is configured, and turn it on if it is not. 172 | def check_cloudtrail(account_number, bucket_name): 173 | """Function to check if CloudTrail is enabled""" 174 | logging.info("Checking to see if CloudTrail is on, and will activate if needed.") 175 | try: 176 | logging.info("DescribeTrails API Call") 177 | cloudtrail_status = cloudtrail.describe_trails( 178 | includeShadowTrails=True 179 | ) 180 | if cloudtrail_status["trailList"] == []: 181 | logging.info("CreateTrail API Call") 182 | cloudtrail_activate = cloudtrail.create_trail( 183 | Name='assisted-log-enabler-ct-' + account_number, 184 | S3BucketName=bucket_name, 185 | S3KeyPrefix='cloudtrail', 186 | IsMultiRegionTrail=True, 187 | EnableLogFileValidation=True 188 | ) 189 | cloudtrail_name = cloudtrail_activate["Name"] 190 | cloudtrail_arn = cloudtrail_activate["TrailARN"] 191 | logging.info("AddTags API Call") 192 | cloudtrail_tags = cloudtrail.add_tags( 193 | ResourceId=cloudtrail_arn, 194 | TagsList=[ 195 | { 196 | 'Key': 'workflow', 197 | 'Value': 'assisted-log-enabler' 198 | }, 199 | ] 200 | ) 201 | logging.info("StartLogging API Call") 202 | cloudtrail_on = cloudtrail.start_logging( 203 | Name=cloudtrail_name 204 | ) 205 | logging.info("Trail " + cloudtrail_name + " is created and active.") 206 | return 207 | else: 208 | logging.info("There is a CloudTrail trail active. No action needed.") 209 | return 210 | except Exception as exception_handle: 211 | logging.error(exception_handle) 212 | 213 | 214 | # 4. Turn on EKS audit and authenticator logs. 215 | def eks_logging(region_list): 216 | """Function to turn on logging for EKS Clusters""" 217 | for aws_region in region_list: 218 | logging.info("Turning on audit and authenticator logging for EKS clusters in region " + aws_region + ".") 219 | eks = boto3.client('eks', region_name=aws_region) 220 | try: 221 | logging.info("ListClusters API Call") 222 | eks_clusters = eks.list_clusters() 223 | eks_cluster_list = eks_clusters ['clusters'] 224 | logging.info("EKS Clusters found in " + aws_region + ":") 225 | print(eks_cluster_list) 226 | for cluster in eks_cluster_list: 227 | logging.info("UpdateClusterConfig API Call") 228 | eks_activate = eks.update_cluster_config( 229 | name=cluster, 230 | logging={ 231 | 'clusterLogging': [ 232 | { 233 | 'types': [ 234 | 'audit', 235 | ], 236 | 'enabled': True 237 | }, 238 | { 239 | 'types': [ 240 | 'authenticator', 241 | ], 242 | 'enabled': True 243 | }, 244 | ] 245 | } 246 | ) 247 | if eks_activate['update']['status'] == 'InProgress': 248 | logging.info(cluster + " EKS Cluster is currently updating. Status: InProgress") 249 | elif eks_activate['update']['status'] == 'Failed': 250 | logging.info(cluster + " EKS Cluster failed to turn on logs. Please check if you have permissions to update the logging configuration of EKS. Status: Failed") 251 | elif eks_activate['update']['status'] == 'Cancelled': 252 | logging.info(cluster + " EKS Cluster log update was cancelled. Status: Cancelled.") 253 | else: 254 | logging.info(cluster + " EKS Cluster has audit and authenticator logs turned on.") 255 | except Exception as exception_handle: 256 | logging.error(exception_handle) 257 | 258 | 259 | # 5. Turn on Route 53 Query Logging. 260 | def route_53_query_logs(region_list, account_number, bucket_name): 261 | """Function to turn on Route 53 Query Logs for VPCs""" 262 | for aws_region in region_list: 263 | logging.info("Turning on Route 53 Query Logging on for VPCs in region " + aws_region + ".") 264 | ec2 = boto3.client('ec2', region_name=aws_region) 265 | route53resolver = boto3.client('route53resolver', region_name=aws_region) 266 | try: 267 | VPCList: list = [] 268 | QueryLogList: list = [] 269 | logging.info("DescribeVpcs API Call") 270 | vpcs = ec2.describe_vpcs() 271 | for vpc_id in vpcs["Vpcs"]: 272 | VPCList.append(vpc_id["VpcId"]) 273 | logging.info("List of VPCs found within account " + account_number + ", region " + aws_region + ":") 274 | print(VPCList) 275 | logging.info("ListResolverQueryLogConfigAssociations API Call") 276 | query_log_details = route53resolver.list_resolver_query_log_config_associations() 277 | for query_log_vpc_id in query_log_details['ResolverQueryLogConfigAssociations']: 278 | QueryLogList.append(query_log_vpc_id['ResourceId']) 279 | r53_working_list = (list(set(VPCList) - set(QueryLogList))) 280 | logging.info("List of VPCs found within account " + account_number + ", region " + aws_region + " WITHOUT Route 53 Query Logs:") 281 | print(r53_working_list) 282 | for no_query_logs in r53_working_list: 283 | logging.info(no_query_logs + " does not have Route 53 Query logging on. It will be turned on within this function.") 284 | logging.info("Activating logs for VPCs that do not have Route 53 Query logging turned on.") 285 | logging.info("CreateResolverQueryLogConfig API Call") 286 | create_query_log = route53resolver.create_resolver_query_log_config( 287 | Name='Assisted_Log_Enabler_Query_Logs_' + aws_region, 288 | DestinationArn='arn:aws:s3:::' + bucket_name + '/r53querylogs', 289 | CreatorRequestId=timestamp_date_string, 290 | Tags=[ 291 | { 292 | 'Key': 'Workflow', 293 | 'Value': 'assisted-log-enabler' 294 | }, 295 | ] 296 | ) 297 | r53_query_log_id = create_query_log['ResolverQueryLogConfig']['Id'] 298 | logging.info("Route 53 Query Logging Created. Resource ID:" + r53_query_log_id) 299 | for vpc in r53_working_list: 300 | logging.info("Associating " + vpc + " with the created Route 53 Query Logging.") 301 | logging.info("AssociateResolverQueryLogConfig") 302 | activate_r53_logs = route53resolver.associate_resolver_query_log_config( 303 | ResolverQueryLogConfigId=r53_query_log_id, 304 | ResourceId=vpc 305 | ) 306 | except Exception as exception_handle: 307 | logging.error(exception_handle) 308 | 309 | # 6. Turn on S3 Logging. 310 | def s3_logs(region_list, unique_end): 311 | """Function to turn on S3 Logs for Buckets""" 312 | for aws_region in region_list: 313 | logging.info("Checking for S3 Logging on for Buckets in region " + aws_region + ".") 314 | account_number = sts.get_caller_identity()["Account"] 315 | s3 = boto3.client('s3', region_name=aws_region) 316 | try: 317 | S3List: list = [] 318 | S3LogList: list = [] 319 | logging.info("ListBuckets API Call") 320 | buckets = s3.list_buckets() 321 | for bucket in buckets['Buckets']: 322 | s3region=s3.get_bucket_location(Bucket=bucket["Name"])['LocationConstraint'] 323 | if s3region == aws_region: 324 | S3List.append(bucket["Name"]) 325 | elif s3region is None and aws_region == 'us-east-1': 326 | S3List.append(bucket["Name"]) 327 | if S3List != []: 328 | logging.info("List of Buckets found within account " + account_number + ", region " + aws_region + ":") 329 | print(S3List) 330 | logging.info("Parsed out buckets created by Assisted Log Enabler for AWS in " + aws_region) 331 | logging.info("Checking remaining buckets to see if logs were enabled by Assisted Log Enabler for AWS in " + aws_region) 332 | logging.info("GetBucketLogging API Call") 333 | for bucket in S3List: 334 | if 'aws-s3-log-collection-' + account_number + '-' + aws_region not in str(bucket): 335 | s3temp=s3.get_bucket_logging(Bucket=bucket) 336 | if 'TargetBucket' not in str(s3temp): 337 | S3LogList.append(bucket) 338 | if S3LogList != []: 339 | logging.info("List of Buckets found within account " + account_number + ", region " + aws_region + " WITHOUT S3 Bucket Logs:") 340 | print(S3LogList) 341 | for bucket in S3LogList: 342 | logging.info(bucket + " does not have S3 BUCKET logging on. It will be turned on within this function.") 343 | logging.info("Creating S3 Logging Bucket") 344 | """Function to create the bucket for storing logs""" 345 | account_number = sts.get_caller_identity()["Account"] 346 | logging.info("Creating bucket in %s" % account_number) 347 | logging.info("CreateBucket API Call") 348 | if aws_region == 'us-east-1': 349 | logging_bucket_dict = s3.create_bucket( 350 | Bucket="aws-s3-log-collection-" + account_number + "-" + aws_region + "-" + unique_end 351 | ) 352 | else: 353 | logging_bucket_dict = s3.create_bucket( 354 | Bucket="aws-s3-log-collection-" + account_number + "-" + aws_region + "-" + unique_end, 355 | CreateBucketConfiguration={ 356 | 'LocationConstraint': aws_region 357 | } 358 | ) 359 | logging.info("Bucket " + "aws-s3-log-collection-" + account_number + "-" + aws_region + "-" + unique_end + " Created.") 360 | logging.info("Setting lifecycle policy.") 361 | logging.info("PutBucketLifecycleConfiguration API Call") 362 | lifecycle_policy = s3.put_bucket_lifecycle_configuration( 363 | Bucket="aws-s3-log-collection-" + account_number + "-" + aws_region + "-" + unique_end, 364 | LifecycleConfiguration={ 365 | 'Rules': [ 366 | { 367 | 'Expiration': { 368 | 'Days': 400 369 | }, 370 | 'Status': 'Enabled', 371 | 'Prefix': '', 372 | 'ID': 'LogStorage', 373 | 'Transitions': [ 374 | { 375 | 'Days': 90, 376 | 'StorageClass': 'INTELLIGENT_TIERING' 377 | } 378 | ] 379 | } 380 | ] 381 | } 382 | ) 383 | logging.info("Lifecycle Policy successfully set.") 384 | logging.info("Setting the S3 bucket Public Access to Blocked") 385 | logging.info("PutPublicAccessBlock API Call") 386 | bucket_private = s3.put_public_access_block( 387 | Bucket="aws-s3-log-collection-" + account_number + "-" + aws_region + "-" + unique_end, 388 | PublicAccessBlockConfiguration={ 389 | 'BlockPublicAcls': True, 390 | 'IgnorePublicAcls': True, 391 | 'BlockPublicPolicy': True, 392 | 'RestrictPublicBuckets': True 393 | }, 394 | ) 395 | logging.info("GetBucketAcl API Call") 396 | id=s3.get_bucket_acl(Bucket="aws-s3-log-collection-" + account_number + "-" + aws_region + "-" + unique_end)['Owner']['ID'] 397 | logging.info("PutBucketAcl API Call") 398 | s3.put_bucket_acl(Bucket="aws-s3-log-collection-" + account_number + "-" + aws_region + "-" + unique_end,GrantReadACP='uri=http://acs.amazonaws.com/groups/s3/LogDelivery',GrantWrite='uri=http://acs.amazonaws.com/groups/s3/LogDelivery',GrantFullControl='id=' + id) 399 | for bucket in S3LogList: 400 | logging.info("Activating logs for S3 Bucket " + bucket) 401 | logging.info("PutBucketLogging API Call") 402 | create_s3_log = s3.put_bucket_logging( 403 | Bucket=bucket, 404 | BucketLoggingStatus={ 405 | 'LoggingEnabled': { 406 | 'TargetBucket': 'aws-s3-log-collection-' + account_number + '-' + aws_region + '-' + unique_end, 407 | 'TargetGrants': [ 408 | { 409 | 'Permission': 'FULL_CONTROL', 410 | 'Grantee': { 411 | 'Type': 'Group', 412 | 'URI': 'http://acs.amazonaws.com/groups/s3/LogDelivery' 413 | }, 414 | }, 415 | ], 416 | 'TargetPrefix': 's3logs/' + bucket 417 | } 418 | } 419 | ) 420 | else: 421 | logging.info("No S3 Bucket WITHOUT Logging enabled on account " + account_number + " region " + aws_region) 422 | else: 423 | logging.info("No S3 Buckets found within account " + account_number + ", region " + aws_region + ":") 424 | except Exception as exception_handle: 425 | logging.error(exception_handle) 426 | 427 | # 7. Turn on Load Balancer Logging. 428 | def lb_logs(region_list, unique_end): 429 | """Function to turn on LB Logs""" 430 | for aws_region in region_list: 431 | elbv1client = boto3.client('elb', region_name=aws_region) 432 | elbv2client = boto3.client('elbv2', region_name=aws_region) 433 | account_number = sts.get_caller_identity()["Account"] 434 | logging.info("Checking for Load Balancer Logging in the account " + account_number + ", region " + aws_region) 435 | try: 436 | ELBList1: list = [] 437 | ELBList2: list = [] 438 | ELBLogList: list = [] 439 | ELBv1LogList: list = [] 440 | ELBv2LogList: list = [] 441 | logging.info("DescribeLoadBalancers API Call") 442 | ELBList1 = elbv1client.describe_load_balancers() 443 | for lb in ELBList1['LoadBalancerDescriptions']: 444 | logging.info("DescribeLoadBalancerAttibute API Call") 445 | lblog=elbv1client.describe_load_balancer_attributes(LoadBalancerName=lb['LoadBalancerName']) 446 | logging.info("Parsing out for ELB Access Logging") 447 | if lblog['LoadBalancerAttributes']['AccessLog']['Enabled'] == False: 448 | ELBv1LogList.append([lb['LoadBalancerName'],'classic']) 449 | logging.info("DescribeLoadBalancers v2 API Call") 450 | ELBList2 = elbv2client.describe_load_balancers() 451 | for lb in ELBList2['LoadBalancers']: 452 | logging.info("DescribeLoadBalancerAttibute v2 API Call") 453 | lblog=elbv2client.describe_load_balancer_attributes(LoadBalancerArn=lb['LoadBalancerArn']) 454 | logging.info("Parsing out for ELBv2 Access Logging") 455 | for lbtemp in lblog['Attributes']: 456 | if lbtemp['Key'] == 'access_logs.s3.enabled': 457 | if lbtemp['Value'] == 'false': 458 | ELBv2LogList.append([lb['LoadBalancerName'],lb['LoadBalancerArn']]) 459 | ELBLogList=ELBv1LogList+ELBv2LogList 460 | if ELBLogList != []: 461 | logging.info("List of Load Balancers found within account " + account_number + ", region " + aws_region + " without logging enabled:") 462 | print(ELBLogList) 463 | for elb in ELBLogList: 464 | logging.info(elb[0] + " does not have Load Balancer logging on. It will be turned on within this function.") 465 | logging.info("Creating S3 Logging Bucket for Load Balancers") 466 | """Function to create the bucket for storing load balancer logs""" 467 | account_number = sts.get_caller_identity()["Account"] 468 | logging.info("Creating bucket in %s" % account_number) 469 | logging.info("CreateBucket API Call") 470 | if aws_region == 'us-east-1': 471 | logging_bucket_dict = s3.create_bucket( 472 | Bucket="aws-lb-log-collection-" + account_number + "-" + aws_region + "-" + unique_end 473 | ) 474 | else: 475 | logging_bucket_dict = s3.create_bucket( 476 | Bucket="aws-lb-log-collection-" + account_number + "-" + aws_region + "-" + unique_end, 477 | CreateBucketConfiguration={ 478 | 'LocationConstraint': aws_region 479 | } 480 | ) 481 | logging.info("Bucket " + "aws-lb-log-collection-" + account_number + "-" + aws_region + "-" + unique_end + " Created.") 482 | logging.info("Setting lifecycle policy.") 483 | logging.info("PutBucketLifecycleConfiguration API Call") 484 | lifecycle_policy = s3.put_bucket_lifecycle_configuration( 485 | Bucket="aws-lb-log-collection-" + account_number + "-" + aws_region + "-" + unique_end, 486 | LifecycleConfiguration={ 487 | 'Rules': [ 488 | { 489 | 'Expiration': { 490 | 'Days': 400 491 | }, 492 | 'Status': 'Enabled', 493 | 'Prefix': '', 494 | 'ID': 'LogStorage', 495 | 'Transitions': [ 496 | { 497 | 'Days': 90, 498 | 'StorageClass': 'INTELLIGENT_TIERING' 499 | } 500 | ] 501 | } 502 | ] 503 | } 504 | ) 505 | logging.info("Lifecycle Policy successfully set.") 506 | logging.info("Checking for AWS Log Account for ELB.") 507 | logging.info("https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html") 508 | if aws_region == 'us-east-1': 509 | elb_account='127311923021' 510 | elif aws_region == 'us-east-2': 511 | elb_account='033677994240' 512 | elif aws_region == 'us-west-1': 513 | elb_account='027434742980' 514 | elif aws_region == 'us-west-2': 515 | elb_account='797873946194' 516 | elif aws_region == 'af-south-1': 517 | elb_account='098369216593' 518 | elif aws_region == 'ca-central-1': 519 | elb_account='985666609251' 520 | elif aws_region == 'eu-central-1': 521 | elb_account='054676820928' 522 | elif aws_region == 'eu-west-1': 523 | elb_account='156460612806' 524 | elif aws_region == 'eu-west-2': 525 | elb_account='652711504416' 526 | elif aws_region == 'eu-south-1': 527 | elb_account='635631232127' 528 | elif aws_region == 'eu-west-3': 529 | elb_account='009996457667' 530 | elif aws_region == 'eu-north-1': 531 | elb_account='897822967062' 532 | elif aws_region == 'ap-east-1': 533 | elb_account='754344448648' 534 | elif aws_region == 'ap-northeast-1': 535 | elb_account='582318560864' 536 | elif aws_region == 'ap-northeast-2': 537 | elb_account='600734575887' 538 | elif aws_region == 'ap-northeast-3': 539 | elb_account='383597477331' 540 | elif aws_region == 'ap-southeast-1': 541 | elb_account='114774131450' 542 | elif aws_region == 'ap-southeast-2': 543 | elb_account='783225319266' 544 | elif aws_region == 'ap-south-1': 545 | elb_account='718504428378' 546 | elif aws_region == 'me-south-1': 547 | elb_account='076674570225' 548 | elif aws_region == 'sa-east-1': 549 | elb_account='507241528517' 550 | elif aws_region == 'us-gov-west-1': 551 | elb_account='048591011584' 552 | elif aws_region == 'us-gov-east-1': 553 | elb_account='190560391635' 554 | logging.info("Checking for AWS Log Account for ELB.") 555 | logging.info("PutBucketPolicy API Call") 556 | bucket_policy = s3.put_bucket_policy( 557 | Bucket="aws-lb-log-collection-" + account_number + "-" + aws_region + "-" + unique_end, 558 | Policy='{"Version": "2012-10-17", "Statement": [{"Effect": "Allow","Principal": {"Service": "delivery.logs.amazonaws.com"},"Action": "s3:GetBucketAcl","Resource": "arn:aws:s3:::aws-lb-log-collection-' + account_number + '-' + aws_region + '-' + unique_end + '"},{"Effect": "Allow","Principal": {"Service": "delivery.logs.amazonaws.com"},"Action": "s3:PutObject","Resource": "arn:aws:s3:::aws-lb-log-collection-' + account_number + '-' + aws_region + '-' + unique_end + '/*","Condition": {"StringEquals": {"s3:x-amz-acl": "bucket-owner-full-control"}}},{"Effect": "Allow","Principal": {"AWS": "arn:aws:iam::' + elb_account + ':root"},"Action": "s3:PutObject","Resource": "arn:aws:s3:::aws-lb-log-collection-' + account_number + '-' + aws_region + '-' + unique_end + '/*"}]}' 559 | ) 560 | logging.info("Setting the S3 bucket Public Access to Blocked") 561 | logging.info("PutPublicAccessBlock API Call") 562 | bucket_private = s3.put_public_access_block( 563 | Bucket="aws-lb-log-collection-" + account_number + "-" + aws_region + "-" + unique_end, 564 | PublicAccessBlockConfiguration={ 565 | 'BlockPublicAcls': True, 566 | 'IgnorePublicAcls': True, 567 | 'BlockPublicPolicy': True, 568 | 'RestrictPublicBuckets': True 569 | }, 570 | ) 571 | if ELBv1LogList != []: 572 | for elb in ELBv1LogList: 573 | logging.info("Activating logs for Load Balancer " + elb[0]) 574 | logging.info("ModifyLoadBalancerAttributes API Call") 575 | create_lb_log = elbv1client.modify_load_balancer_attributes( 576 | LoadBalancerName=elb[0], 577 | LoadBalancerAttributes={ 578 | 'AccessLog': { 579 | 'Enabled': True, 580 | 'S3BucketName': "aws-lb-log-collection-" + account_number + "-" + aws_region + "-" + unique_end, 581 | 'EmitInterval': 5, 582 | 'S3BucketPrefix': elb[0] 583 | } 584 | } 585 | ) 586 | logging.info("Logging Enabled for Load Balancer " + elb[0]) 587 | if ELBv2LogList != []: 588 | for elb in ELBv2LogList: 589 | logging.info("Activating logs for Load Balancer " + elb[0]) 590 | logging.info("ModifyLoadBalancerAttributes v2 API Call") 591 | create_lb_log = elbv2client.modify_load_balancer_attributes( 592 | LoadBalancerArn=elb[1], 593 | Attributes=[ 594 | { 595 | 'Key': 'access_logs.s3.enabled', 596 | 'Value': 'true' 597 | }, 598 | { 599 | 'Key': 'access_logs.s3.bucket', 600 | 'Value': "aws-lb-log-collection-" + account_number + "-" + aws_region + "-" + unique_end 601 | }, 602 | { 603 | 'Key': 'access_logs.s3.prefix', 604 | 'Value': elb[0] 605 | } 606 | ] 607 | ) 608 | logging.info("Logging Enabled for Load Balancer " + elb[0]) 609 | else: 610 | logging.info("There are no Load Balancers to be set by Log Enabler in " + aws_region) 611 | except Exception as exception_handle: 612 | logging.error(exception_handle) 613 | 614 | def check_guardduty(region_list, account_number, bucket_name): 615 | """Function to turn on GuardDuty and export findings to an S3 bucket.""" 616 | 617 | logging.info("Creating KMS key for GuardDuty to export findings.") 618 | kms = boto3.client('kms') 619 | logging.info("CreateKey API Call") 620 | export_key = kms.create_key( 621 | Policy="{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Effect\": \"Allow\", \"Principal\": { \"AWS\": \"arn:aws:iam::" + account_number + ":root\" }, \"Action\": \"kms:*\", \"Resource\": \"*\" }, { \"Sid\": \"Allow GuardDuty to use the key\", \"Effect\": \"Allow\", \"Principal\": { \"Service\": \"guardduty.amazonaws.com\" }, \"Action\": \"kms:GenerateDataKey\", \"Resource\": \"*\" } ] }", 622 | KeyUsage="ENCRYPT_DECRYPT", 623 | KeySpec="SYMMETRIC_DEFAULT", 624 | Origin="AWS_KMS", 625 | MultiRegion=False 626 | ) 627 | key_arn = export_key["KeyMetadata"]["Arn"] 628 | logging.info("Created KMS Key " + key_arn) 629 | key_alias = "alias/ale-guardduty-key-" + random_string_generator() 630 | logging.info("CreateAlias API Call") 631 | kms.create_alias( 632 | AliasName=key_alias, 633 | TargetKeyId=key_arn 634 | ) 635 | logging.info("Created KMS Key Alias " + key_alias) 636 | 637 | logging.info("Creating /guardduty folder in S3 Bucket") 638 | logging.info("PutObject API Call") 639 | s3.put_object( 640 | Bucket=bucket_name, 641 | Key="guardduty/" 642 | ) 643 | 644 | for aws_region in region_list: 645 | guardduty = boto3.client('guardduty', region_name=aws_region) 646 | logging.info("Checking for GuardDuty detector in the account " + account_number + ", region " + aws_region) 647 | try: 648 | logging.info("ListDetectors API Call") 649 | detectors = guardduty.list_detectors() 650 | if detectors["DetectorIds"] == []: 651 | logging.info("GuardDuty is not enabled in the account " + account_number + ", region " + aws_region) 652 | logging.info("Enabling GuardDuty") 653 | logging.info("CreateDetector API Call") 654 | new_detector = guardduty.create_detector( 655 | Enable=True, 656 | DataSources={ 657 | 'S3Logs': { 658 | 'Enable': True 659 | }, 660 | 'Kubernetes': { 661 | 'AuditLogs': { 662 | 'Enable': True 663 | } 664 | } 665 | }, 666 | Tags={ 667 | 'workflow': 'assisted-log-enabler' 668 | } 669 | ) 670 | logging.info("Created GuardDuty detector ID " + new_detector["DetectorId"]) 671 | 672 | logging.info("Exporting GuardDuty findings to an S3 bucket.") 673 | logging.info("Setting S3 Bucket " + bucket_name + " as publishing destination for GuardDuty detector.") 674 | logging.info("CreatePublishingDestination API Call") 675 | guardduty.create_publishing_destination( 676 | DetectorId=new_detector["DetectorId"], 677 | DestinationType="S3", 678 | DestinationProperties={ 679 | "DestinationArn": "arn:aws:s3:::" + bucket_name + "/guardduty", 680 | "KmsKeyArn": key_arn 681 | } 682 | ) 683 | else: 684 | detector_id = detectors["DetectorIds"][0] 685 | logging.info("GetDetector API Call") 686 | if guardduty.get_detector(DetectorId=detector_id)["Status"] == "DISABLED": 687 | logging.info("GuardDuty is suspended in the account " + account_number + ", region " + aws_region) 688 | logging.info("Enabling GuardDuty") 689 | logging.info("UpdateDetector API Call") 690 | guardduty.update_detector( 691 | DetectorId=detector_id, 692 | Enable=True, 693 | DataSources={ 694 | "S3Logs": {"Enable": True}, 695 | "Kubernetes": {"AuditLogs": {"Enable": True}}, 696 | }, 697 | ) 698 | else: 699 | logging.info("GuardDuty is already enabled in the account " + account_number + ", region " + aws_region) 700 | 701 | logging.info("Checking if GuardDuty detector publishes findings to S3.") 702 | logging.info("ListPublishingDestinations API Call") 703 | gd_destinations = guardduty.list_publishing_destinations(DetectorId=detector_id)["Destinations"] 704 | if gd_destinations == []: 705 | logging.info("Detector does not publish findings to a destination. Setting S3 Bucket " + bucket_name + " as publishing destination for GuardDuty detector.") 706 | logging.info("CreatePublishingDestination API Call") 707 | guardduty.create_publishing_destination( 708 | DetectorId=detector_id, 709 | DestinationType="S3", 710 | DestinationProperties={ 711 | "DestinationArn": "arn:aws:s3:::" + bucket_name + "/guardduty", 712 | "KmsKeyArn": key_arn 713 | } 714 | ) 715 | else: 716 | for dest in gd_destinations: 717 | if dest["DestinationType"] == "S3": 718 | dest_id = dest["DestinationId"] 719 | logging.info("DescribePublishingDestination API Call") 720 | dest_info = guardduty.describe_publishing_destination( 721 | DetectorId=detector_id, 722 | DestinationId=dest_id 723 | ) 724 | dest_s3_arn = dest_info["DestinationProperties"]["DestinationArn"] 725 | logging.info("Detector already publishes findings to S3 bucket " + dest_s3_arn.split(":")[-1]) 726 | except Exception as exception_handle: 727 | logging.error(exception_handle) 728 | 729 | def wafv2_logs(): 730 | """Function to turn on WAFv2 Logging""" 731 | account_number = sts.get_caller_identity()["Account"] 732 | bucket_arn = "" 733 | for aws_region in region_list: 734 | wafv2 = boto3.client('wafv2', region_name=aws_region) 735 | logging.info("Checking for WAFv2 Logging in the account " + account_number + ", region " + aws_region) 736 | try: 737 | WAFv2List: list = [] # list of all WAFv2 ARNs 738 | WAFv2LogList: list = [] # list of WAFv2 ARNs with logging enabled 739 | WAFv2NoLogList: list = [] # list of WAFv2 ARNs to enable logging 740 | 741 | # Get regional WAFv2 Web ACLs 742 | logging.info("ListWebAcls API Call") 743 | wafv2_regional_acl_list = wafv2.list_web_acls(Scope='REGIONAL')["WebACLs"] 744 | for acl in wafv2_regional_acl_list: 745 | WAFv2List.append(acl["ARN"]) 746 | 747 | if aws_region == 'us-east-1': 748 | # Get CloudFront (global) WAFv2 Web ACLs 749 | logging.info("Checking for Global (CloudFront) Web ACLs") 750 | logging.info("ListWebAcls API Call") 751 | wafv2_cf_acl_list = wafv2.list_web_acls(Scope='CLOUDFRONT')["WebACLs"] 752 | for acl in wafv2_cf_acl_list: 753 | WAFv2List.append(acl["ARN"]) 754 | 755 | logging.info("List of Web ACLs found within account " + account_number + ", region " + aws_region + ":") 756 | print(WAFv2List) 757 | 758 | logging.info("ListLoggingConfigurations API Call") 759 | wafv2_regional_log_configs = wafv2.list_logging_configurations(Scope='REGIONAL')["LoggingConfigurations"] 760 | for acl in wafv2_regional_log_configs: 761 | WAFv2LogList.append(acl["ResourceArn"]) 762 | 763 | if aws_region == 'us-east-1': 764 | logging.info("Checking Global (CloudFront) Web ACL Logging Configurations") 765 | logging.info("ListLoggingConfigurations API Call") 766 | wafv2_cf_log_configs = wafv2.list_logging_configurations(Scope='CLOUDFRONT')["LoggingConfigurations"] 767 | for acl in wafv2_cf_log_configs: 768 | WAFv2LogList.append(acl["ResourceArn"]) 769 | 770 | WAFv2NoLogList = list(set(WAFv2List) - set(WAFv2LogList)) 771 | logging.info("List of Web ACLs found within account " + account_number + ", region " + aws_region + " WITHOUT logging enabled:") 772 | print(WAFv2NoLogList) 773 | 774 | # If an S3 bucket hasn't been created yet, create one 775 | if WAFv2NoLogList != [] and bucket_arn == "": 776 | logging.info("Creating S3 bucket for WAF logs enabled by Assisted Log Enabler.") 777 | unique_end = random_string_generator() 778 | bucket_name = "aws-waf-logs-ale-" + account_number + "-" + unique_end 779 | logging.info("CreateBucket API Call") 780 | s3.create_bucket(Bucket=bucket_name) 781 | logging.info("Bucket " + bucket_name + " created.") 782 | bucket_arn = "arn:aws:s3:::" + bucket_name 783 | 784 | logging.info("Setting lifecycle policy.") 785 | logging.info("PutBucketLifecycleConfiguration API Call") 786 | s3.put_bucket_lifecycle_configuration( 787 | Bucket=bucket_name, 788 | LifecycleConfiguration={ 789 | 'Rules': [ 790 | { 791 | 'Expiration': { 792 | 'Days': 400 793 | }, 794 | 'Status': 'Enabled', 795 | 'Prefix': '', 796 | 'ID': 'LogStorage', 797 | 'Transitions': [ 798 | { 799 | 'Days': 90, 800 | 'StorageClass': 'INTELLIGENT_TIERING' 801 | } 802 | ] 803 | } 804 | ] 805 | } 806 | ) 807 | logging.info("Setting the S3 bucket Public Access to Blocked") 808 | logging.info("PutPublicAccessBlock API Call") 809 | bucket_private = s3.put_public_access_block( 810 | Bucket=bucket_name, 811 | PublicAccessBlockConfiguration={ 812 | 'BlockPublicAcls': True, 813 | 'IgnorePublicAcls': True, 814 | 'BlockPublicPolicy': True, 815 | 'RestrictPublicBuckets': True 816 | }, 817 | ) 818 | 819 | # If an S3 bucket has been created, use it as the log destination 820 | if WAFv2NoLogList != [] and bucket_arn != "": 821 | for arn in WAFv2NoLogList: 822 | logging.info(arn + " does not have logging turned on. Turning on logging.") 823 | logging.info("PutLoggingConfiguration API Call") 824 | wafv2.put_logging_configuration( 825 | LoggingConfiguration={ 826 | 'ResourceArn': arn, 827 | 'LogDestinationConfigs': [ 828 | bucket_arn, 829 | ] 830 | } 831 | ) 832 | else: 833 | logging.info("No WAFv2 Web ACLs to enable logging for in account " + account_number + ", region " + aws_region + ".") 834 | 835 | except Exception as exception_handle: 836 | logging.error(exception_handle) 837 | 838 | 839 | def run_eks(): 840 | """Function that runs the defined EKS logging code""" 841 | eks_logging(region_list) 842 | logging.info("This is the end of the script. Please feel free to validate that logs have been turned on.") 843 | 844 | def run_cloudtrail(bucket_name='default'): 845 | """Function that runs the defined CloudTrail logging code""" 846 | account_number = sts.get_caller_identity()["Account"] 847 | if bucket_name == 'default': 848 | unique_end = random_string_generator() 849 | bucket_name = create_bucket(unique_end) 850 | else: 851 | update_custom_bucket_policy(bucket_name, account_number) 852 | check_cloudtrail(account_number, bucket_name) 853 | logging.info("This is the end of the script. Please feel free to validate that logs have been turned on.") 854 | 855 | def run_vpc_flow_logs(bucket_name='default', file_format='text'): 856 | """Function that runs the defined VPC Flow Log logging code""" 857 | if bucket_name == 'default': 858 | unique_end = random_string_generator() 859 | bucket_name = create_bucket(unique_end) 860 | account_number = sts.get_caller_identity()["Account"] 861 | flow_log_activator(region_list, account_number, bucket_name,file_format) 862 | logging.info("This is the end of the script. Please feel free to validate that logs have been turned on.") 863 | 864 | def run_r53_query_logs(bucket_name='default'): 865 | """Function that runs the defined R53 Query Logging code""" 866 | if bucket_name == 'default': 867 | unique_end = random_string_generator() 868 | bucket_name = create_bucket(unique_end) 869 | account_number = sts.get_caller_identity()["Account"] 870 | route_53_query_logs(region_list, account_number, bucket_name) 871 | logging.info("This is the end of the script. Please feel free to validate that logs have been turned on.") 872 | 873 | def run_s3_logs(): 874 | """Function that runs the defined S3 Logging code""" 875 | unique_end = random_string_generator() 876 | s3_logs(region_list, unique_end) 877 | logging.info("This is the end of the script. Please feel free to validate that logs have been turned on.") 878 | 879 | def run_lb_logs(): 880 | """Function that runs the defined Load Balancer Logging code""" 881 | unique_end = random_string_generator() 882 | lb_logs(region_list, unique_end) 883 | logging.info("This is the end of the script. Please feel free to validate that logs have been turned on.") 884 | 885 | def run_guardduty(bucket_name='default'): 886 | """Function that runs the defined GuardDuty enablement code and exports findings to an S3 bucket""" 887 | account_number = sts.get_caller_identity()["Account"] 888 | if bucket_name == 'default': 889 | unique_end = random_string_generator() 890 | bucket_name = create_bucket(unique_end) 891 | else: 892 | update_custom_bucket_policy(bucket_name, account_number) 893 | check_guardduty(region_list, account_number, bucket_name) 894 | logging.info("This is the end of the script. Please feel free to validate that logs have been turned on.") 895 | 896 | def run_wafv2_logs(): 897 | """Function that runs the defined WAFv2 Logging code""" 898 | wafv2_logs() 899 | logging.info("This is the end of the script. Please feel free to validate that logs have been turned on.") 900 | 901 | def lambda_handler(event, context, bucket_name='default', file_format='text'): 902 | """Function that runs all of the previously defined functions""" 903 | unique_end = random_string_generator() 904 | account_number = sts.get_caller_identity()["Account"] 905 | if bucket_name == 'default': 906 | bucket_name = create_bucket(unique_end) 907 | else: 908 | update_custom_bucket_policy(bucket_name, account_number) 909 | flow_log_activator(region_list, account_number, bucket_name, file_format) 910 | check_cloudtrail(account_number, bucket_name) 911 | eks_logging(region_list) 912 | route_53_query_logs(region_list, account_number, bucket_name) 913 | s3_logs(region_list, unique_end) 914 | lb_logs(region_list, unique_end) 915 | wafv2_logs() 916 | logging.info("This is the end of the script. Please feel free to validate that logs have been turned on.") 917 | 918 | 919 | if __name__ == '__main__': 920 | event = "event" 921 | context = "context" 922 | lambda_handler(event, context) 923 | --------------------------------------------------------------------------------