├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── config ├── nuke_config_update.py └── nuke_generic_config.yaml ├── images └── architecture-overview.png └── nuke-cfn-stack.yaml /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 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AWS Account Cleanser framework using aws-nuke 3 | 4 | ### AWS Nuke is an open source tool created by rebuy.de 5 | ### https://github.com/rebuy-de/aws-nuke 6 | ### AWS Nuke searches for deleteable resources in the provided AWS acccount and deletes those which are not considered "Default" or "AWS-Managed" 7 | ### In short, it will take your account back to Day1 with few exceptions 8 | 9 | 10 | ## __WARNING__ 11 | This is a very destructive tool and should not be deployed without fully understanding the impact it will have on the AWS accounts you allow it to interface with. 12 | The aws-nuke binary if not configured correctly, can delete unintended resources in your AWS accounts. Please use caution and configure this tool to delete unused resources only in your lower test/sandbox environment accounts. 13 | 14 | ## Overview 15 | 16 | The code in this repository helps you set up the following target architecture. 17 | 18 | ![infrastructure-overview](images/architecture-overview.png) 19 | 20 | * The approach covered in this pattern is suitable for customers needing an automated mechanism to clean up their obsolete resources from test or sandbox accounts periodically. It is quite common that customers will have a set of Dev/Sandbox accounts where developers can create and experiment with various services and resources, which are then left unattended or obsolete, and can quickly lead to high and unnecessary AWS Cost/Spending ( example: Developers created an expensive DB instance ( RDS) or EBS/EFS volume for testing and missed to terminate those resources ). 21 | 22 | 23 | * This solution sets up an automated mechanism using the binary aws-nuke along with AWS Step Functions , EventBridge and AWS CodeBuild which can run on a daily scheduled basis to scan and delete the resources from the Sandbox account across each region in a scalable manner. A CodeBuild project is invoked from the Step Functions map state for each region of an account to delete resources specific to that region, thus providing scalability and better control in terms of time and monitoring efficiency. 24 | 25 | This architecture provides the following features: 26 | 27 | 1. The workflow is kicked off based off a scheduled event trigger set up in AWS EventBridge which invokes the Step Functions. 28 | 2. The orchestration of this pattern using Step Functions serves the purpose of handling resources in each region using a separate invocation of CodeBuild Project ( the region attribute in the nuke config file required by the aws-nuke binary is dynamically updated using a custom python class inside the CodeBuild project ) with the region parameter and the customized nuke config , thus providing dynamic parallelism with the Map state that fans out for all the regions as needed within the sandbox account and thus saves time and provide scalability to handle a lot of resources. 29 | 3. Generally the aws-nuke command line need to assume STS temporary credentials for handling the resources in each account using IAM Role-chaining which limits it's max time to 60 minutes most of the times and hence if multiple regions are configured to run in one CodeBuild project execution , there could be lot of stale resources still left out without being destroyed. 30 | - This pattern takes care of that by executing the clean up for each region in a single account in parallel using the Step Functions Map state. 31 | - This also uses the credentials for the aws-nuke binary configured with the profile, that can be configured with the shared aws config file ( ~/.aws/config) which doesn't expire with a 1 hour session limit. 32 | - Also this increases the default CodeBuild project time out to about 2-4 hours , allowing more time for the nuke to complete deleting all resources for each reg 33 | 4. The aws-nuke binary works based off the nuke-config.yaml file , which is dynamically updated in this pattern using a python filtering class , to provide flexibility in handling the resource filters and region constraints based on the supplied override parameters. 34 | 5. This workflow invokes the CodeBuild project synchronously and waits for Success. It also has a retry mechanism to trigger the CodeBuild project again with a configured amount of time , if in case it errors out, making sure all the resources are handled in the daily run without any manual intervention. 35 | 6. The workflow also sends out a detailed report to an SNS topic subscription on what resources were deleted after the job is successful for each region which simplifies traversing and parsing the complex logs written out by the aws-nuke binary. 36 | 37 | ## Prerequisites 38 | 39 | 1. https://github.com/rebuy-de/aws-nuke --> Open source library staged/downloaded to artifactory or S3. This aws-nuke binary is owned by rebuy-de 40 | 41 | 2. AWS Account alias needs to exist in the IAM Dashboard for the target sandbox account for 'aws-nuke' to work 42 | 43 | 3. AWS CodeBuild project --> for runtime/compute 44 | 45 | 4. AWS S3 Bucket --> for storing the nuke config file and the aws-nuke binary ( latest version ) if needed. Make sure to source the latest 'aws-nuke' binary downloaded in S3 (or from your internal artifactory) 46 | 47 | 5. AWS StepFunctions --> For orchestration for fan out of multi-region parallel CodeBuild invocation targets 48 | 49 | 6. AWS SNS Topic --> An active email address with SNS topic subscription to send the CodeBuild job status and the detailed report of resources nuked daily 50 | 51 | 7. AWS EventBridge Rule --> Configured with the required cron schedule to trigger the workflow periodically. The input parameters to invoke the Rule target should be updated with the required region lists as needed 52 | 53 | 8. Make sure you have sufficient network connectivity from the VPC where this is run, as CodeBuild downloads the nuke binary from github. If running in restricted environment, have the binary uploaded to S3 bucket or artifactory and reference that. 54 | 55 | ## Design 56 | 57 | * CodeBuild provides a super-handy way for us to spin up a container and run a script without having to worry about provisioning and maintaining resources. We’re using CloudFormation to define the whole project, so the code sample below displays the bulk of the Project resource configuration for a CloudFormation template. In short, we’re building a standard AWS Linux Docker container, configuring the log output channel, assigning an AWS Role for the nuke binary to assume the account roles (mentioned earlier) to start the clean up based on the region and config file. 58 | 59 | * AWS EventBridge Events provides both event-based and scheduled triggers for automated actions in other services. This is basically a fancy Cron job to schedule the build project. As with the CodeBuild Project, the example below is a resource defined within a CloudFormation template. In short, it defines the schedule expression for the Cron job (3:00a EST Mon-Fri), a role to allow the event trigger to run and kick off the StepFunctions workflow as a target which will orchestrate the CodeBuild project invocation based on the supplied region list parameter and the dynamic nuke config file modified during runtime. 60 | 61 | * AWS StepFunctions provides this design with scalability and help achieve dynamic parallelism across accounts/regions using the Map State. The orchestration of this pattern using Step Functions serves the purpose of handling resources in each region using a separate invocation of CodeBuild Project ( the region attribute in the nuke config file required by the aws-nuke binary is dynamically updated using a custom python class inside the CodeBuild project ) with the region parameter and the customized nuke config , thus providing dynamic parallelism with the Map state that fans out for all the regions as needed within the sandbox account and thus saves time and provide scalability to handle a lot of resources. 62 | 63 | ## Dry Runs vs Production 64 | 65 | By default, this script will not take any destructive action on any resources in your account(s). It will provide a log of the “dry run” output as if it actually completed the actions specified. When you’ve thoroughly tested this and whitelisted any resources in your own aws-nuke-config.yaml, you need to add the –-no-dry-run flag to the aws-nuke command in this script to force a destructive run. 66 | 67 | ```sh 68 | $ aws-nuke -c $line.yaml --force --no-dry-run --access-key-id $ACCESS_KEY_ID --secret-access-key $SECRET_ACCESS_KEY --session-token $SESSION_TOKEN |tee -a aws-nuke.log; 69 | ``` 70 | 71 | ## Setup and Installation 72 | 73 | * Clone the repo 74 | * Determine the ID of the account to be deployed for clean up ( This is only to be deployed to Dev/Test/Sandbox environments ) 75 | * Verify and Update your nuke configuration file as needed with specific filters for the resources/accounts 76 | * Deploy the stack using the below command. You can run it in any desired region. 77 | ```sh 78 | aws cloudformation create-stack --stack-name NukeCleanser --template-body file://nuke-cfn-stack.yaml --region us-east-2 --capabilities CAPABILITY_NAMED_IAM 79 | ``` 80 | * Once the stack is created, upload the nuke generic config file and the python script to the S3 bucket using the commands below. You can find the name of the S3 bucket generated from the CloudFormation console `Outputs` tab. 81 | ```sh 82 | aws s3 cp config/nuke_generic_config.yaml --region us-east-2 s3://{your-bucket-name} 83 | aws s3 cp config/nuke_config_update.py --region us-east-2 s3://{your-bucket-name} 84 | ``` 85 | * Run the stack manually by triggering the StepFunctions with the below sample input payload. (which is pre-configured in the EventBridge Target as a Constant JSON input). You can configure this to run in parallel on the required number of regions by updating the region_list parameter. 86 | 87 | ```sh 88 | { 89 | "InputPayLoad": { 90 | "nuke_dry_run": "true", 91 | "nuke_version": "2.21.2", 92 | "region_list": [ 93 | "global", 94 | "us-west-1", 95 | "us-east-1" 96 | ] 97 | } 98 | } 99 | ``` 100 | 101 | * The tool is currently configured to run at a schedule as desired typically off hours 3:00a EST. It can be easily configured with a rate() or cron() expression by editing the cfn template file 102 | 103 | * The workflow also sends out a detailed report to an SNS topic with an active email subscription on what resources were deleted after the job is successful for each region which simplifies traversing and parsing the complex logs spit out by the aws-nuke binary. 104 | 105 | * If the workflow is successful , the stack will send out 106 | - One email for each of the regions where nuke CodeBuild job was invoked with details of the build execution , the list of resources which was deleted along with the log file path. 107 | - The StepFunctions workflow also sends out another email when the whole Map state process completes successfully. Sample email template given below. 108 | 109 | ```sh 110 | Account Cleansing Process Completed; 111 | 112 | ------------------------------------------------------------------ 113 | Summary of the process: 114 | ------------------------------------------------------------------ 115 | DryRunMode : true 116 | Account ID : 123456789012 117 | Target Region : us-west-1 118 | Build State : JOB SUCCEEDED 119 | Build ID : AccountNuker-NukeCleanser:4509a9b5 120 | CodeBuild Project Name : AccountNuker-NukeCleanser 121 | Process Start Time : Thu Feb 23 04:05:21 UTC 2023 122 | Process End Time : Thu Feb 23 04:05:54 UTC 2023 123 | Log Stream Path : AccountNuker-NukeCleanser/logPath 124 | ------------------------------------------------------------------ 125 | ################ Nuke Cleanser Logs ################# 126 | 127 | FAILED RESOURCES 128 | ------------------------------- 129 | Total number of Resources that would be removed: 130 | 3 131 | us-west-1 - SQSQueue - https://sqs.us-east-1.amazonaws.com/123456789012/test-nuke-queue - would remove 132 | us-west-1 - SNSTopic - TopicARN: arn:aws:sns:us-east-1:123456789012:test-nuke-topic - [TopicARN: "arn:aws:sns:us-east-1:123456789012:test-topic"] - would remove 133 | us-west-1 - S3Bucket - s3://test-nuke-bucket-us-west-1 - [CreationDate: "2023-01-25 11:13:14 +0000 UTC", Name: "test-nuke-bucket-us-west-1"] - would remove 134 | 135 | ``` 136 | * By default the stack runs aws-nuke in DryRun mode, To actually delete resources update the stack with AWSNukeDryRunFlag parameter flipped to false OR udpate manually in the CodeBuild environment variables section. 137 | 138 | ## Monitoring queries 139 | 140 | * Using aws-cli cloudwatch logs 141 | 142 | ```sh 143 | aws logs filter-log-events \ 144 | --log-group-name AccountNuker-nuke-auto-account-cleanser \ 145 | --start-time 1628838256000 --end-time 1628839216000 \ 146 | --log-stream-names "10409c89-a90f-4af7-9642-0df9bc9f0855" \ 147 | --filter-pattern removed \ 148 | --no-interleaved \ 149 | --output text \ 150 | --limit 5 151 | ``` 152 | 153 | * Using awslogs for analyzing output from aws-nuke runs 154 | 155 | ```sh 156 | awslogs get AccountNuker-nuke-auto-account-cleanser --filter-pattern '"Scan complete: "' --start='1d ago' --timestamp 157 | awslogs get AccountNuker-nuke-auto-account-cleanser --filter-pattern '"Error: failed"' --start='1d ago' | sort -u 158 | awslogs get AccountNuker-nuke-auto-account-cleanser --filter-pattern '"Removal requested: 0 waiting"' --start='1d ago' | sort -u 159 | awslogs get AccountNuker-nuke-auto-account-cleanser --filter-pattern '"AccessDenied"' --start='1d ago' | sort -u | wc -l 160 | ``` 161 | 162 | 163 | * Using CW Logs Insights query 164 | 165 | ```sh 166 | fields @timestamp, @message 167 | | filter userIdentity.sessionContext.sessionIssuer.userName = "nuke-auto-account-cleanser" and ispresent(errorCode) 168 | | sort @timestamp desc 169 | | limit 500 170 | 171 | 172 | fields @timestamp, @message 173 | | filter ispresent(errorCode) and userIdentity.sessionContext.sessionIssuer.userName = "nuke-auto-account-cleanser" 174 | and errorCode != "AccessDenied" and eventName like "Delete" 175 | | sort @timestamp desc 176 | | limit 500 177 | 178 | 179 | fields @timestamp, @message 180 | | filter ispresent(errorCode) and userIdentity.sessionContext.sessionIssuer.userName = "nuke-auto-account-cleanser" 181 | and errorCode == "AccessDenied" and eventName like "Delete" 182 | | sort @timestamp desc 183 | | limit 500 184 | ``` 185 | 186 | ## Security 187 | 188 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 189 | 190 | ## License 191 | 192 | This library is licensed under the MIT-0 License. See the LICENSE file. 193 | 194 | -------------------------------------------------------------------------------- /config/nuke_config_update.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python class responsible for updating the nuke generic config , based on exceptions to be filtered 3 | and also updates dynamically the region attribute passed in from the StepFunctions invocation. This should be modified to suit your needs. 4 | """ 5 | import boto3 6 | import yaml 7 | import argparse 8 | import copy 9 | 10 | GLOBAL_RESOURCE_EXCEPTIONS = [ 11 | {"property": "tag:DoNotNuke", "value": "True"}, 12 | {"property": "tag:Permanent", "value": "True"}, 13 | {"type": "regex", "value": ".*auto-account-cleanser*.*"}, 14 | {"type": "regex", "value": ".*nuke-account-cleanser*.*"}, 15 | {"type": "regex", "value": ".*securityhub*.*"}, 16 | {"type": "regex", "value": ".*aws-prod*.*"}, 17 | ] 18 | 19 | 20 | class StackInfo: 21 | 22 | def __init__(self, account, target_regions): 23 | self.session = boto3.Session(profile_name="nuke") 24 | # Regions to be targeted set from the Stepfunctions/CodeBuild workflow 25 | self.regions = target_regions 26 | self.resources = {} 27 | self.config = {} 28 | self.account = account 29 | 30 | def Populate(self): 31 | self.UpdateCFNStackList() 32 | self.OverrideDefaultConfig() 33 | 34 | def UpdateCFNStackList(self): 35 | try: 36 | for region in self.regions: 37 | cfn_client = self.session.client("cloudformation", region_name=region) 38 | stack_paginator = cfn_client.get_paginator("list_stacks") 39 | responses = stack_paginator.paginate( 40 | StackStatusFilter=[ 41 | "CREATE_COMPLETE", 42 | "UPDATE_COMPLETE", 43 | "UPDATE_ROLLBACK_COMPLETE", 44 | "IMPORT_COMPLETE", 45 | "IMPORT_ROLLBACK_COMPLETE", 46 | ] 47 | ) 48 | for page in responses: 49 | for stack in page.get("StackSummaries"): 50 | self.GetCFNResources(stack, cfn_client) 51 | self.BuildIamExclusionList(region) 52 | except Exception as e: 53 | print("Error in calling UpdateCFNStackList:\n {}".format(e)) 54 | 55 | def GetCFNResources(self, stack, cfn_client): 56 | try: 57 | stack_name = stack.get("StackName") 58 | 59 | if stack_name is None: 60 | stack_name = stack.get("PhysicalResourceId") 61 | 62 | stack_description = cfn_client.describe_stacks(StackName=stack_name) 63 | print("Stack Description: ", stack_description) 64 | tags = stack_description.get("Stacks")[0].get("Tags") 65 | for tag in tags: 66 | key = tag.get("Key") 67 | value = tag.get("Value") 68 | if key == "AWS_Solutions" and value == "LandingZoneStackSet": 69 | if "CloudFormationStack" in self.resources: 70 | self.resources["CloudFormationStack"].append( 71 | stack.get("StackName") 72 | ) 73 | else: 74 | self.resources["CloudFormationStack"] = [stack.get("StackName")] 75 | stack_resources = cfn_client.list_stack_resources( 76 | StackName=stack_name 77 | ) 78 | for resource in stack_resources.get("StackResourceSummaries"): 79 | if resource.get("ResourceType") == "AWS::CloudFormation::Stack": 80 | self.GetCFNResources(resource, cfn_client) 81 | else: 82 | nuke_type = self.UpdateResourceName(resource["ResourceType"]) 83 | if nuke_type in self.resources: 84 | self.resources[nuke_type].append( 85 | { 86 | "type": "regex", 87 | "value": resource["PhysicalResourceId"], 88 | } 89 | ) 90 | else: 91 | self.resources[nuke_type] = [ 92 | { 93 | "type": "regex", 94 | "value": resource["PhysicalResourceId"], 95 | } 96 | ] 97 | except Exception as e: 98 | print("Error calling GetCFNResources:\n {}".format(e)) 99 | 100 | def UpdateResourceName(self, resource): 101 | nuke_type = str.replace(resource, "AWS::", "") 102 | nuke_type = str.replace(nuke_type, "::", "") 103 | nuke_type = str.replace(nuke_type, "Config", "ConfigService", 1) 104 | return nuke_type 105 | 106 | def BuildIamExclusionList(self, region): 107 | # This excludes and appends to the config IAMRole resources , the roles that are federated principals 108 | # You can add any other custom filterting logic based on regions for IAM/Global roles that should be excluded 109 | iam_client = self.session.client("iam", region_name=region) 110 | iam_paginator = iam_client.get_paginator("list_roles") 111 | responses = iam_paginator.paginate() 112 | for page in responses: 113 | for role in page["Roles"]: 114 | apd = role.get("AssumeRolePolicyDocument") 115 | if apd is not None: 116 | for item in apd.get("Statement"): 117 | if item is not None: 118 | for principal in item.get("Principal"): 119 | if principal == "Federated": 120 | if "IAMRole" in self.resources: 121 | self.resources["IAMRole"].append( 122 | role.get("RoleName") 123 | ) 124 | else: 125 | self.resources["IAMRole"] = [ 126 | role.get("RoleName") 127 | ] 128 | 129 | def OverrideDefaultConfig(self): 130 | # Open the nuke_generic_config.yaml and merge the captured resources/exclusions with it 131 | try: 132 | with open(r"nuke_generic_config.yaml") as config_file: 133 | self.config = yaml.load(config_file) 134 | # Not all resources handled by the tool, but we will add them to the exclusion anyhow. 135 | for resource in self.resources: 136 | if resource in self.config["accounts"]["ACCOUNT"]["filters"]: 137 | self.config["accounts"]["ACCOUNT"]["filters"][resource].extend( 138 | self.resources[resource] 139 | ) 140 | else: 141 | self.config["accounts"]["ACCOUNT"]["filters"][ 142 | resource 143 | ] = self.resources[resource] 144 | self.config["accounts"][self.account] = copy.deepcopy( 145 | self.config["accounts"]["ACCOUNT"] 146 | ) 147 | if "ACCOUNT" in self.config["accounts"]: 148 | self.config["accounts"].pop("ACCOUNT", None) 149 | # Global exclusions apply to every type of resource 150 | for resource in self.config["accounts"][self.account]["filters"]: 151 | for exception in GLOBAL_RESOURCE_EXCEPTIONS: 152 | self.config["accounts"][self.account]["filters"][resource].append( 153 | exception.copy() 154 | ) 155 | config_file.close() 156 | except Exception as e: 157 | print("Failed merging nuke-config-test.yaml with error {}".format(e)) 158 | exit(1) 159 | 160 | def WriteConfig(self): 161 | # CodeBuild script updates the target region in the generic config and is validated here. 162 | try: 163 | for region in self.config["regions"]: 164 | local_config = stackInfo.config.copy() 165 | local_config["regions"] = [region] 166 | filename = "nuke_config_{}.yaml".format(region) 167 | with open(filename, "w") as output_file: 168 | output = yaml.dump(local_config, output_file) 169 | output_file.close() 170 | except Exception as e: 171 | print("Failed opening nuke_config.yaml for writing with error {}".format(e)) 172 | 173 | 174 | try: 175 | parser = argparse.ArgumentParser() 176 | parser.add_argument("--account", dest="account", help="Account to nuke") # Account and Region from StepFunctions - CodeBuild overridden params 177 | parser.add_argument("--region", dest="region", help="Region to target for nuke") 178 | args = parser.parse_args() 179 | if not args.account or not args.region: 180 | parser.print_help() 181 | exit(1) 182 | except Exception as e: 183 | print(e) 184 | exit(1) 185 | 186 | if __name__ == "__main__": 187 | print("Incoming Args: ", args) 188 | stackInfo = StackInfo(args.account, [args.region]) 189 | stackInfo.Populate() 190 | stackInfo.WriteConfig() 191 | -------------------------------------------------------------------------------- /config/nuke_generic_config.yaml: -------------------------------------------------------------------------------- 1 | regions: 2 | # add `global` here to include IAM entities to be nuked 3 | - TARGET_REGION # will be overridden during run time based on region parameter 4 | 5 | account-blocklist: 6 | - 123456789012 #prod 7 | 8 | # optional: restrict nuking to these resources 9 | resource-types: 10 | excludes: # exclude this as will be already handled when you include S3Bucket/DynamoDBTable. Else takes a lot of time and logs get filled up 11 | - S3Object 12 | - DynamoDBTableItem 13 | targets: 14 | - IAMUser 15 | - IAMUserPolicyAttachment 16 | - IAMUserAccessKey 17 | - S3Bucket 18 | - SNSTopic 19 | - SQSQueue 20 | - CloudTrailTrail 21 | 22 | accounts: 23 | ACCOUNT: # will be overridden during run time based on account param 24 | filters: 25 | IAMRole: 26 | - "ProdRoles" 27 | - "DoNotDeleteRoles" 28 | - type: regex 29 | value: ".*" 30 | IAMUser: 31 | - "admin" 32 | - type: regex 33 | value: ".*" 34 | IAMUserPolicyAttachment: 35 | - property: RoleName 36 | value: "admin" 37 | IAMUserAccessKey: 38 | - property: UserName 39 | value: "admin" 40 | S3Bucket: 41 | - "s3://my-bucket" 42 | CloudTrailTrail: # filter all CloudTrail 43 | - type: regex 44 | value: ".*" 45 | SNSTopic: # SNS is protected based on global exception tags inside the nuke_config_update.py 46 | - type: regex 47 | value: ".*" 48 | SQSQueue: [] # delete all SQS 49 | -------------------------------------------------------------------------------- /images/architecture-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-nuke-account-cleanser-example/5d59d89fbdf6851d848fbaa9c9196970ad898e48/images/architecture-overview.png -------------------------------------------------------------------------------- /nuke-cfn-stack.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: 2010-09-09 3 | Description: > 4 | This CFN Template creates a StepFunction state machine definition , to invoke a CodeBuild Project and 5 | associated resources for setting up a single-account script that cleanses the resources across supplied regions 6 | via AWS-Nuke. It also creates the role for the nuke cleanser which is used for configuring the credentials 7 | for aws-nuke binary to cleanse resources within that account/region. 8 | 9 | Parameters: 10 | BucketName: 11 | Description: The name of the bucket where the nuke binary and config files are stored 12 | Default: "nuke-account-cleanser-config" 13 | Type: String 14 | NukeCleanserRoleName: 15 | Description: The name of the Nuke Role to be assumed within each account, providing permissions to cleanse the account(s) 16 | Type: String 17 | Default: 'nuke-auto-account-cleanser' 18 | IAMPath: 19 | Type: String 20 | Default: / 21 | Description: IAM Path 22 | AWSNukeDryRunFlag: 23 | Description: The dry run flag to run for the aws-nuke. By default it is set to True which will not delete any resources. 24 | Type: String 25 | Default: 'true' 26 | AWSNukeVersion: 27 | Description: The aws-nuke latest version to be used from internal artifactory/S3. Make sure to check the latest releases 28 | and any resource additions added. As you update the version, you will have to handle filtering any new resources that gets 29 | updated with that new version. 30 | Type: String 31 | Default: 2.21.2 32 | NukeTopicName: 33 | Description: The SNS Topic name to publish the nuke output logs email 34 | Type: String 35 | Default: nuke-cleanser-notify-topic 36 | Owner: 37 | Type: String 38 | Default: OpsAdmin 39 | Description: The Owner of the account to be used for tagging purpose 40 | 41 | Resources: 42 | NukeAccountCleanserPolicy: 43 | Type: AWS::IAM::ManagedPolicy 44 | Properties: 45 | ManagedPolicyName: NukeAccountCleanser 46 | PolicyDocument: 47 | Statement: 48 | - Action: # Make sure to restrict this to the services/permissions as needed for your accounts 49 | - access-analyzer:* 50 | - autoscaling:* 51 | - aws-portal:* 52 | - budgets:* 53 | - cloudtrail:* 54 | - cloudwatch:* 55 | - config:* 56 | - ec2:* 57 | - ec2messages:* 58 | - elasticloadbalancing:* 59 | - eks:* 60 | - elasticache:* 61 | - events:* 62 | - firehose:* 63 | - guardduty:* 64 | - iam:* 65 | - inspector:* 66 | - kinesis:* 67 | - kms:* 68 | - lambda:* 69 | - logs:* 70 | - organizations:* 71 | - pricing:* 72 | - s3:* 73 | - secretsmanager:* 74 | - securityhub:* 75 | - sns:* 76 | - sqs:* 77 | - ssm:* 78 | - ssmmessages:* 79 | - sts:* 80 | - support:* 81 | - tag:* 82 | - trustedadvisor:* 83 | - waf-regional:* 84 | - wafv2:* 85 | - cloudformation:* 86 | Effect: Allow 87 | Resource: "*" 88 | Sid: WhitelistedServices 89 | Version: "2012-10-17" 90 | Description: Managed policy for nuke account cleansing 91 | Path: 92 | Ref: IAMPath 93 | NukeAccountCleanserRole: 94 | Type: AWS::IAM::Role 95 | Properties: 96 | RoleName: !Ref NukeCleanserRoleName 97 | Description: Nuke Auto account cleanser role for Dev/Sandbox accounts 98 | MaxSessionDuration: 7200 99 | Tags: 100 | - Key: privileged 101 | Value: 'true' 102 | - Key: DoNotNuke 103 | Value: 'True' 104 | - Key: description 105 | Value: 'PrivilegedReadWrite:auto-account-cleanser-role' 106 | - Key: owner 107 | Value: !Ref Owner 108 | AssumeRolePolicyDocument: 109 | Statement: 110 | - Action: sts:AssumeRole 111 | Effect: Allow 112 | Principal: 113 | AWS: 114 | - !GetAtt NukeCodeBuildProjectRole.Arn 115 | Version: "2012-10-17" 116 | ManagedPolicyArns: 117 | - Ref: NukeAccountCleanserPolicy 118 | Path: 119 | Ref: IAMPath 120 | 121 | EventBridgeNukeScheduleRole: 122 | Type: AWS::IAM::Role 123 | Properties: 124 | RoleName: !Sub EventBridgeNukeSchedule-${AWS::StackName} 125 | AssumeRolePolicyDocument: 126 | Version: 2012-10-17 127 | Statement: 128 | - Effect: Allow 129 | Principal: 130 | Service: events.amazonaws.com 131 | Action: sts:AssumeRole 132 | Tags: 133 | - Key: DoNotNuke 134 | Value: 'True' 135 | - Key: owner 136 | Value: !Ref Owner 137 | Policies: 138 | - 139 | PolicyName: EventBridgeNukeStateMachineExecutionPolicy 140 | PolicyDocument: 141 | Version: 2012-10-17 142 | Statement: 143 | - 144 | Effect: Allow 145 | Action: states:StartExecution 146 | Resource: !Ref NukeStepFunction 147 | 148 | NukeCodeBuildProjectRole: 149 | Type: AWS::IAM::Role 150 | Properties: 151 | RoleName: !Sub NukeCodeBuildProject-${AWS::StackName} 152 | AssumeRolePolicyDocument: 153 | Version: 2012-10-17 154 | Statement: 155 | - Effect: Allow 156 | Principal: 157 | Service: codebuild.amazonaws.com 158 | Action: sts:AssumeRole 159 | Tags: 160 | - Key: DoNotNuke 161 | Value: 'True' 162 | - Key: owner 163 | Value: !Ref Owner 164 | Policies: 165 | - 166 | PolicyName: NukeCodeBuildLogsPolicy 167 | PolicyDocument: 168 | Version: 2012-10-17 169 | Statement: 170 | - 171 | Effect: Allow 172 | Action: 173 | - logs:CreateLogGroup 174 | - logs:CreateLogStream 175 | - logs:PutLogEvents 176 | - logs:DescribeLogStreams 177 | - logs:FilterLogEvents 178 | Resource: 179 | - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:AccountNuker-${AWS::StackName} 180 | - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:AccountNuker-${AWS::StackName}:* 181 | - 182 | PolicyName: AssumeNukePolicy 183 | PolicyDocument: 184 | Version: 2012-10-17 185 | Statement: 186 | - 187 | Effect: Allow 188 | Action: sts:AssumeRole 189 | Resource: !Sub arn:aws:iam::*:role/${NukeCleanserRoleName} 190 | - 191 | PolicyName: NukeListOUAccounts 192 | PolicyDocument: 193 | Version: 2012-10-17 194 | Statement: 195 | - 196 | Effect: Allow 197 | Action: organizations:ListAccountsForParent 198 | Resource: "*" 199 | - 200 | PolicyName: S3BucketReadOnly 201 | PolicyDocument: 202 | Version: 2012-10-17 203 | Statement: 204 | - 205 | Effect: Allow 206 | Action: 207 | - s3:Get* 208 | - s3:List* 209 | Resource: 210 | - !Sub arn:aws:s3:::${NukeS3Bucket} 211 | - !Sub arn:aws:s3:::${NukeS3Bucket}/* 212 | - 213 | PolicyName: SNSPublishPolicy 214 | PolicyDocument: 215 | Version: 2012-10-17 216 | Statement: 217 | - 218 | Effect: Allow 219 | Action: 220 | - sns:ListTagsForResource 221 | - sns:ListSubscriptionsByTopic 222 | - sns:GetTopicAttributes 223 | - sns:Publish 224 | Resource: 225 | - !Ref NukeEmailTopic 226 | 227 | NukeCodeBuildProject: 228 | Type: AWS::CodeBuild::Project 229 | Properties: 230 | Artifacts: 231 | Type: NO_ARTIFACTS 232 | BadgeEnabled: false 233 | Description: Builds a container to run AWS-Nuke for all accounts within the specified account/regions 234 | Tags: 235 | - Key: DoNotNuke 236 | Value: 'True' 237 | - Key: owner 238 | Value: !Ref Owner 239 | Environment: 240 | ComputeType: BUILD_GENERAL1_SMALL 241 | Image: aws/codebuild/docker:18.09.0 242 | ImagePullCredentialsType: CODEBUILD 243 | PrivilegedMode: true 244 | Type: LINUX_CONTAINER 245 | EnvironmentVariables: 246 | - Name: AWS_NukeDryRun 247 | Type: PLAINTEXT 248 | Value: !Ref AWSNukeDryRunFlag 249 | - Name: AWS_NukeVersion 250 | Type: PLAINTEXT 251 | Value: !Ref AWSNukeVersion 252 | - Name: Publish_TopicArn 253 | Type: PLAINTEXT 254 | Value: !Ref NukeEmailTopic 255 | - Name: NukeS3Bucket 256 | Type: PLAINTEXT 257 | Value: !Ref 'NukeS3Bucket' 258 | - Name: NukeAssumeRoleArn 259 | Type: PLAINTEXT 260 | Value: !GetAtt NukeAccountCleanserRole.Arn 261 | - Name: NukeCodeBuildProjectName 262 | Type: PLAINTEXT 263 | Value: !Sub "AccountNuker-${AWS::StackName}" 264 | LogsConfig: 265 | CloudWatchLogs: 266 | GroupName: !Sub "AccountNuker-${AWS::StackName}" 267 | Status: ENABLED 268 | Name: !Sub "AccountNuker-${AWS::StackName}" 269 | ServiceRole: !GetAtt NukeCodeBuildProjectRole.Arn 270 | TimeoutInMinutes: 120 271 | Source: 272 | BuildSpec: | 273 | version: 0.2 274 | phases: 275 | install: 276 | on-failure: ABORT 277 | commands: 278 | - export AWS_NUKE_VERSION=$AWS_NukeVersion 279 | - apt-get install -y wget 280 | - apt-get install jq 281 | - wget https://github.com/rebuy-de/aws-nuke/releases/download/v$AWS_NUKE_VERSION/aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz --no-check-certificate 282 | - tar xvf aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz 283 | - chmod +x aws-nuke-v$AWS_NUKE_VERSION-linux-amd64 284 | - mv aws-nuke-v$AWS_NUKE_VERSION-linux-amd64 /usr/local/bin/aws-nuke 285 | - aws-nuke version 286 | - echo "Setting aws cli profile with config file for role assumption using metadata" 287 | - aws configure set profile.nuke.role_arn ${NukeAssumeRoleArn} 288 | - aws configure set profile.nuke.credential_source "EcsContainer" 289 | - export AWS_PROFILE=nuke 290 | - export AWS_DEFAULT_PROFILE=nuke 291 | - export AWS_SDK_LOAD_CONFIG=1 292 | - echo "Getting 12-digit ID of this account" 293 | - account_id=$(aws sts get-caller-identity |jq -r ".Account"); 294 | build: 295 | on-failure: CONTINUE 296 | commands: 297 | - echo " ------------------------------------------------ " >> error_log.txt 298 | - echo "Getting nuke generic config file from S3"; 299 | - aws s3 cp s3://$NukeS3Bucket/nuke_generic_config.yaml . 300 | - echo "Updating the TARGET_REGION in the generic config from the parameter" 301 | - sed -i "s/TARGET_REGION/$NukeTargetRegion/g" nuke_generic_config.yaml 302 | - echo "Getting filter/exclusion python script from S3"; 303 | - aws s3 cp s3://$NukeS3Bucket/nuke_config_update.py . 304 | - echo "Getting 12-digit ID of this account" 305 | - account_id=$(aws sts get-caller-identity |jq -r ".Account"); 306 | - echo "Running Config filter/update script"; 307 | - python3 nuke_config_update.py --account $account_id --region "$NukeTargetRegion"; 308 | - echo "Configured nuke_config.yaml"; 309 | - echo "Running Nuke on Account"; 310 | - | 311 | if [ "$AWS_NukeDryRun" = "true" ]; then 312 | for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --profile nuke 2>&1 |tee -a aws-nuke.log; done 313 | elif [ "$AWS_NukeDryRun" = "false" ]; then 314 | for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --no-dry-run --profile nuke 2>&1 |tee -a aws-nuke.log; done 315 | else 316 | echo "Couldn't determine Dryrun flag...exiting" 317 | exit 1 318 | fi 319 | - nuke_pid=$!; 320 | - wait $nuke_pid; 321 | - echo "Checking if Nuke Process completed for account" 322 | - | 323 | if cat aws-nuke.log | grep -F "Error: The specified account doesn"; then 324 | echo "Nuke errored due to no AWS account alias set up - exiting" 325 | cat aws-nuke.log >> error_log.txt 326 | exit 1 327 | else 328 | echo "Nuke completed Successfully - Continuing" 329 | fi 330 | 331 | post_build: 332 | commands: 333 | - echo $CODEBUILD_BUILD_SUCCEEDING 334 | - echo "Get current timestamp for naming reports" 335 | - BLD_START_TIME=$(date -d @$(($CODEBUILD_START_TIME/1000))) 336 | - CURR_TIME_UTC=$(date -u) 337 | - | 338 | { 339 | echo " Account Cleansing Process Failed;" 340 | echo "" 341 | 342 | echo " ----------------------------------------------------------------" 343 | echo " Summary of the process:" 344 | echo " ----------------------------------------------------------------" 345 | echo " DryRunMode : $AWS_NukeDryRun" 346 | echo " Account ID : $account_id" 347 | echo " Target Region : $NukeTargetRegion" 348 | echo " Build State : $([ "${CODEBUILD_BUILD_SUCCEEDING}" = "1" ] && echo "JOB SUCCEEDED" || echo "JOB FAILED")" 349 | echo " Build ID : ${CODEBUILD_BUILD_ID}" 350 | echo " CodeBuild Project Name : $NukeCodeBuildProjectName" 351 | echo " Process Start Time : ${BLD_START_TIME}" 352 | echo " Process End Time : ${CURR_TIME_UTC}" 353 | echo " Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}" 354 | echo " ----------------------------------------------------------------" 355 | echo " ################# Failed Nuke Process - Exiting ###################" 356 | echo "" 357 | } >> fail_email_template.txt 358 | - | 359 | if [ "$CODEBUILD_BUILD_SUCCEEDING" = "0" ]; then 360 | echo " Couldn't process Nuke Cleanser - Exiting " >> fail_email_template.txt 361 | cat error_log.txt >> fail_email_template.txt 362 | aws sns publish --topic-arn $Publish_TopicArn --message file://fail_email_template.txt --subject "Nuke Account Cleanser Failed in account $account_id and region $NukeTargetRegion" 363 | exit 1; 364 | fi 365 | - sleep 120 366 | - LOG_STREAM_NAME=$CODEBUILD_LOG_PATH; 367 | - CURR_TIME_UTC=$(date -u) 368 | - | 369 | if [ -z "${LOG_STREAM_NAME}" ]; then 370 | echo "Couldn't find the log stream for log events"; 371 | exit 0; 372 | else 373 | aws logs filter-log-events --log-group-name $NukeCodeBuildProjectName --log-stream-names $LOG_STREAM_NAME --filter-pattern "removed" --no-interleaved | jq -r .events[].message > log_output.txt; 374 | awk '/There are resources in failed state/,/Error: failed/' aws-nuke.log > failure_email_output.txt 375 | awk '/Error: failed/,/\n/' failure_email_output.txt > failed_log_output.txt 376 | fi 377 | - | 378 | if [ -r log_output.txt ]; then 379 | content=$(cat log_output.txt) 380 | echo $content 381 | elif [ -f "log_output.txt" ]; then 382 | echo "The file log_output.txt exists but is not readable to the script." 383 | else 384 | echo "The file log_output.txt does not exist." 385 | fi 386 | - echo "Publishing Log Ouput to SNS:" 387 | - sub="Nuke Account Cleanser Succeeded in account "$account_id" and region "$NukeTargetRegion"" 388 | - | 389 | { 390 | echo " Account Cleansing Process Completed;" 391 | echo "" 392 | 393 | echo " ------------------------------------------------------------------" 394 | echo " Summary of the process:" 395 | echo " ------------------------------------------------------------------" 396 | echo " DryRunMode : $AWS_NukeDryRun" 397 | echo " Account ID : $account_id" 398 | echo " Target Region : $NukeTargetRegion" 399 | echo " Build State : $([ "${CODEBUILD_BUILD_SUCCEEDING}" = "1" ] && echo "JOB SUCCEEDED" || echo "JOB FAILED")" 400 | echo " Build ID : ${CODEBUILD_BUILD_ID}" 401 | echo " CodeBuild Project Name : $NukeCodeBuildProjectName" 402 | echo " Process Start Time : ${BLD_START_TIME}" 403 | echo " Process End Time : ${CURR_TIME_UTC}" 404 | echo " Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}" 405 | echo " ------------------------------------------------------------------" 406 | echo " ################### Nuke Cleanser Logs ####################" 407 | echo "" 408 | } >> email_template.txt 409 | 410 | - cat aws-nuke.log | grep -F "Scan complete:" || echo "No Resources scanned and nukeable yet" 411 | - echo "Number of Resources that is filtered by config:" >> email_template.txt 412 | - cat aws-nuke.log | grep -c " - filtered by config" || echo 0 >> email_template.txt 413 | - echo " ------------------------------------------ " >> email_template.txt 414 | - | 415 | if [ "$AWS_NukeDryRun" = "true" ]; then 416 | echo "RESOURCES THAT WOULD BE REMOVED:" >> email_template.txt 417 | echo " ----------------------------------------- " >> email_template.txt 418 | cat aws-nuke.log | grep -c " - would remove" || echo 0 >> email_template.txt 419 | cat aws-nuke.log | grep -F " - would remove" >> email_template.txt || echo "No resources to be removed" >> email_template.txt 420 | else 421 | echo " FAILED RESOURCES " >> email_template.txt 422 | echo " ------------------------------- " >> email_template.txt 423 | cat failed_log_output.txt >> email_template.txt 424 | echo " SUCCESSFULLY NUKED RESOURCES " >> email_template.txt 425 | echo " ------------------------------- " >> email_template.txt 426 | cat log_output.txt >> email_template.txt 427 | fi 428 | - aws sns publish --topic-arn $Publish_TopicArn --message file://email_template.txt --subject "$sub" 429 | - echo "Resources Nukeable:" 430 | - cat aws-nuke.log | grep -F "Scan complete:" || echo "Nothing Nukeable yet" 431 | - echo "Total number of Resources that would be removed:" 432 | - cat aws-nuke.log | grep -c " - would remove" || echo "Nothing would be removed yet" 433 | - echo "Total number of Resources Deleted:" 434 | - cat aws-nuke.log | grep -c " - removed" || echo "Nothing deleted yet" 435 | - echo "List of Resources Deleted today:" 436 | - cat aws-nuke.log | grep -F " - removed" || echo "Nothing deleted yet" 437 | 438 | Type: NO_SOURCE 439 | 440 | EventBridgeNukeSchedule: 441 | Type: AWS::Events::Rule 442 | Properties: 443 | Name: !Sub EventBridgeNukeSchedule-${AWS::StackName} 444 | Description: Scheduled Event for running AWS Nuke on the target accounts within the specified regions 445 | ScheduleExpression: cron(0 7 ? * * *) 446 | State: ENABLED 447 | RoleArn: !GetAtt EventBridgeNukeScheduleRole.Arn 448 | # Update the tags for this manually as the property is not available for all targets 449 | # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-tag.html 450 | # Tag: 451 | #- Key: DoNotNuke 452 | # Value: 'True' 453 | #- Key: owner 454 | # Value: !Ref Owner 455 | Targets: 456 | - 457 | Arn: !Ref NukeStepFunction 458 | RoleArn: !GetAtt EventBridgeNukeScheduleRole.Arn 459 | Id: !GetAtt NukeStepFunction.Name 460 | Input: 461 | !Sub |- 462 | { 463 | "InputPayLoad": { 464 | "nuke_dry_run": "${AWSNukeDryRunFlag}", 465 | "nuke_version": "${AWSNukeVersion}", 466 | "region_list": [ 467 | "us-west-1", 468 | "us-east-1" 469 | ] 470 | } 471 | } 472 | 473 | # S3 Bucket for storing nuke config yaml 474 | NukeS3Bucket: 475 | Type: AWS::S3::Bucket 476 | DeletionPolicy: Retain # should empty the S3 bucket contents if you want to delete the S3 when the stack is deleted 477 | UpdateReplacePolicy: Retain 478 | Properties: 479 | BucketName: !Join 480 | - '-' 481 | - - !Ref BucketName 482 | - !Ref AWS::AccountId 483 | - !Ref AWS::Region 484 | - !Select 485 | - 0 486 | - !Split 487 | - '-' 488 | - !Select 489 | - 2 490 | - !Split 491 | - / 492 | - !Ref AWS::StackId 493 | PublicAccessBlockConfiguration: 494 | BlockPublicAcls: true 495 | IgnorePublicAcls: true 496 | BlockPublicPolicy: true 497 | RestrictPublicBuckets: true 498 | Tags: 499 | - Key: DoNotNuke 500 | Value: 'True' 501 | - Key: owner 502 | Value: !Ref Owner 503 | 504 | # S3 Bucket Policy with https secure deny 505 | NukeS3BucketPolicy: 506 | Type: AWS::S3::BucketPolicy 507 | Properties: 508 | Bucket: !Ref NukeS3Bucket 509 | PolicyDocument: 510 | Version: '2012-10-17' 511 | Statement: 512 | - Sid: ForceSSLOnlyAccess 513 | Effect: Deny 514 | Principal: "*" 515 | Action: s3:* 516 | Resource: 517 | - !Sub arn:aws:s3:::${NukeS3Bucket} 518 | - !Sub arn:aws:s3:::${NukeS3Bucket}/* 519 | Condition: 520 | Bool: 521 | aws:SecureTransport: 'false' 522 | 523 | NukeEmailTopic: 524 | Type: AWS::SNS::Topic 525 | Properties: 526 | DisplayName: NukeTopic 527 | FifoTopic: false 528 | KmsMasterKeyId: "alias/aws/sns" 529 | Subscription: 530 | - Endpoint: "test@test.com" # Provide your valid email address for receiving notifications 531 | Protocol: "email" 532 | TopicName: !Ref NukeTopicName 533 | Tags: 534 | - Key: DoNotNuke 535 | Value: 'True' 536 | - Key: owner 537 | Value: !Ref Owner 538 | 539 | ## Role for sample step function 540 | NukeStepFunctionRole: 541 | Type: AWS::IAM::Role 542 | Properties: 543 | RoleName: nuke-account-cleanser-codebuild-state-machine-role 544 | AssumeRolePolicyDocument: 545 | Version: "2012-10-17" 546 | Statement: 547 | - Effect: "Allow" 548 | Principal: 549 | Service: 550 | - !Sub "states.${AWS::Region}.amazonaws.com" 551 | Action: 552 | - "sts:AssumeRole" 553 | Tags: 554 | - Key: DoNotNuke 555 | Value: 'True' 556 | - Key: owner 557 | Value: !Ref Owner 558 | Path: "/" 559 | Policies: 560 | - PolicyName: nuke-account-cleanser-codebuild-state-machine-policy 561 | PolicyDocument: 562 | Version: "2012-10-17" 563 | Statement: 564 | - Effect: "Allow" 565 | Action: 566 | - codebuild:StartBuild 567 | - codebuild:StartBuild 568 | - codebuild:StopBuild 569 | - codebuild:StartBuildBatch 570 | - codebuild:StopBuildBatch 571 | - codebuild:RetryBuild 572 | - codebuild:RetryBuildBatch 573 | - codebuild:BatchGet* 574 | - codebuild:GetResourcePolicy 575 | - codebuild:DescribeTestCases 576 | - codebuild:DescribeCodeCoverages 577 | - codebuild:List* 578 | Resource: 579 | - !GetAtt 'NukeCodeBuildProject.Arn' 580 | - Effect: Allow 581 | Action: 582 | - events:PutTargets 583 | - events:PutRule 584 | - events:DescribeRule 585 | Resource: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventForCodeBuildStartBuildRule 586 | - Effect: Allow 587 | Action: 588 | - sns:Publish 589 | Resource: 590 | - !Ref NukeEmailTopic 591 | - Effect: "Allow" 592 | Action: 593 | - states:DescribeStateMachine 594 | - states:ListExecutions 595 | - states:StartExecution 596 | - states:StopExecution 597 | - states:DescribeExecution 598 | Resource: 599 | - !Sub "arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:nuke-account-cleanser-codebuild-state-machine" 600 | 601 | NukeStepFunction: 602 | Type: 'AWS::StepFunctions::StateMachine' 603 | Properties: 604 | StateMachineName: nuke-account-cleanser-codebuild-state-machine 605 | RoleArn: !GetAtt 'NukeStepFunctionRole.Arn' 606 | DefinitionString: 607 | Fn::Sub: |- 608 | { 609 | "Comment": "AWS Nuke Account Cleanser for multi-region single account clean up using SFN Map state parallel invocation of CodeBuild project.", 610 | "StartAt": "StartNukeCodeBuildForEachRegion", 611 | "States": { 612 | "StartNukeCodeBuildForEachRegion": { 613 | "Type": "Map", 614 | "ItemsPath": "$.InputPayLoad.region_list", 615 | "Parameters": { 616 | "region_id.$": "$$.Map.Item.Value", 617 | "nuke_dry_run.$": "$.InputPayLoad.nuke_dry_run", 618 | "nuke_version.$": "$.InputPayLoad.nuke_version" 619 | }, 620 | "Next": "Clean Output and Notify", 621 | "MaxConcurrency": 0, 622 | "Iterator": { 623 | "StartAt": "Trigger Nuke CodeBuild Job", 624 | "States": { 625 | "Trigger Nuke CodeBuild Job": { 626 | "Type": "Task", 627 | "Resource": "arn:aws:states:::codebuild:startBuild.sync", 628 | "Parameters": { 629 | "ProjectName": "${NukeCodeBuildProject.Arn}", 630 | "EnvironmentVariablesOverride": [ 631 | { 632 | "Name": "NukeTargetRegion", 633 | "Type": "PLAINTEXT", 634 | "Value.$": "$.region_id" 635 | }, 636 | { 637 | "Name": "AWS_NukeDryRun", 638 | "Type": "PLAINTEXT", 639 | "Value.$": "$.nuke_dry_run" 640 | }, 641 | { 642 | "Name": "AWS_NukeVersion", 643 | "Type": "PLAINTEXT", 644 | "Value.$": "$.nuke_version" 645 | } 646 | ] 647 | }, 648 | "Next": "Check Nuke CodeBuild Job Status", 649 | "ResultSelector": { 650 | "NukeBuildOutput.$": "$.Build" 651 | }, 652 | "ResultPath": "$.AccountCleanserRegionOutput", 653 | "Retry": [ 654 | { 655 | "ErrorEquals": [ 656 | "States.TaskFailed" 657 | ], 658 | "BackoffRate": 1, 659 | "IntervalSeconds": 1, 660 | "MaxAttempts": 1 661 | } 662 | ], 663 | "Catch": [ 664 | { 665 | "ErrorEquals": [ 666 | "States.ALL" 667 | ], 668 | "Next": "Nuke Failed", 669 | "ResultPath": "$.AccountCleanserRegionOutput" 670 | } 671 | ] 672 | }, 673 | "Check Nuke CodeBuild Job Status": { 674 | "Type": "Choice", 675 | "Choices": [ 676 | { 677 | "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus", 678 | "StringEquals": "SUCCEEDED", 679 | "Next": "Nuke Success" 680 | }, 681 | { 682 | "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus", 683 | "StringEquals": "FAILED", 684 | "Next": "Nuke Failed" 685 | } 686 | ], 687 | "Default": "Nuke Success" 688 | }, 689 | "Nuke Success": { 690 | "Type": "Pass", 691 | "Parameters": { 692 | "Status": "Succeeded", 693 | "Region.$": "$.region_id", 694 | "CodeBuild Status.$": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus" 695 | }, 696 | "ResultPath": "$.result", 697 | "End": true 698 | }, 699 | "Nuke Failed": { 700 | "Type": "Pass", 701 | "Parameters": { 702 | "Status": "Failed", 703 | "Region.$": "$.region_id", 704 | "CodeBuild Status.$": "States.Format('Nuke Account Cleanser failed with error {}. Check CodeBuild execution for input region {} to investigate', $.AccountCleanserRegionOutput.Error, $.region_id)" 705 | }, 706 | "ResultPath": "$.result", 707 | "End": true 708 | } 709 | } 710 | }, 711 | "ResultSelector": { 712 | "filteredResult.$": "$..result" 713 | }, 714 | "ResultPath": "$.NukeFinalMapAllRegionsOutput" 715 | }, 716 | "Clean Output and Notify": { 717 | "Type": "Task", 718 | "Resource": "arn:aws:states:::sns:publish", 719 | "Parameters": { 720 | "Subject": "State Machine for Nuke Account Cleanser completed", 721 | "Message.$": "States.Format('Nuke Account Cleanser completed for input payload: \n {}. \n ----------------------------------------- \n Check the summmary of execution below: \n {}', $.InputPayLoad, $.NukeFinalMapAllRegionsOutput.filteredResult)", 722 | "TopicArn": "${NukeEmailTopic}" 723 | }, 724 | "End": true 725 | } 726 | } 727 | } 728 | Tags: 729 | - Key: DoNotNuke 730 | Value: 'True' 731 | - Key: owner 732 | Value: !Ref Owner 733 | 734 | 735 | Outputs: 736 | NukeTopicArn: 737 | Description: Arn of SNS Topic used for notifying nuke results in email 738 | Value: !Ref NukeEmailTopic 739 | NukeS3BucketValue: 740 | Description: S3 bucket created with the random generated name 741 | Value: !Ref NukeS3Bucket 742 | --------------------------------------------------------------------------------