Skip to content

Commit 9fc99b0

Browse files
committed
updates
1 parent 341b7b5 commit 9fc99b0

File tree

11 files changed

+5126
-0
lines changed

11 files changed

+5126
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
2+
# aws-nuke for Weathertop
3+
4+
AWS Nuke is an open source tool created by [ekristen](https://github.com/ekristen/aws-nuke).
5+
6+
It searches for deleteable resources in the provided AWS account and deletes those which are not considered "Default" or "AWS-Managed", taking your account back to Day 1 with few exceptions.
7+
8+
9+
## ⚠ Important
10+
This is a very destructive tool! It should not be deployed without fully understanding the impact it will have on your AWS accounts.
11+
Please use caution and configure this tool to delete unused resources only in your lower test/sandbox environment accounts.
12+
13+
## Overview
14+
15+
The code in this repository helps you set up the following architecture:
16+
17+
![infrastructure-overview](architecture-overview.png)
18+
19+
## Feature Outline
20+
21+
1. **Scheduled Trigger**: Amazon EventBridge invokes AWS Step Functions daily.
22+
2. **Regional Scalability**: Runs AWS CodeBuild projects per region.
23+
4. **Custom Config**: Pulls resource filters and region targets in [nuke_generic_config.yaml](nuke_generic_config.yaml).
24+
25+
## Prerequisites
26+
27+
1. **AWS-Nuke Binary**: Open-source library from [ekristen](https://github.com/ekristen/aws-nuke) staged in S3.
28+
2. **AWS Account Alias**: Must exist in the IAM Dashboard for the target account.
29+
8. **Network Connectivity**: Ensure VPC allows downloads from GitHub or stage the binary in S3/artifactory for restricted environments.
30+
31+
## Setup and Installation
32+
33+
* Clone the [repo](https://github.com/ekristen/aws-nuke).
34+
* Determine the `id` of the account to be deployed for nuking.
35+
* Narrow [filters](nuke_generic_config.yaml) for the resources/accounts you wish to nuke.
36+
* Deploy the stack using the below command. You can run it in any desired region.
37+
```sh
38+
cdk bootstrap && cdk deploy
39+
```
40+
41+
Note a successful stack creation, e.g.:
42+
43+
```bash
44+
✅ NukeCleanser
45+
46+
✨ Deployment time: 172.66s
47+
48+
Outputs:
49+
NukeCleanser.NukeS3BucketValue = nuke-account-cleanser-config-616362312345-us-east-1-c043b470
50+
Stack ARN:
51+
arn:aws:cloudformation:us-east-1:123456788985:stack/NukeCleanser/cfhdkiott-acec-11ef-ba2e-4555c1356d07
52+
```
53+
54+
Next, run `python upload_job_files.py` to upload two files in this directory: `nuke_config_update.py` and `nuke_generic_config.yaml`.
55+
56+
## When it runs
57+
* 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
58+
59+
* 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.
60+
61+
* If the workflow is successful, the stack will send out
62+
- 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.
63+
- The StepFunctions workflow also sends out another email when the whole Map state process completes successfully. Sample email template given below.
64+
65+
```sh
66+
Account Cleansing Process Completed;
67+
68+
------------------------------------------------------------------
69+
Summary of the process:
70+
------------------------------------------------------------------
71+
DryRunMode : true
72+
Account ID : 123456789012
73+
Target Region : us-west-1
74+
Build State : JOB SUCCEEDED
75+
Build ID : AccountNuker-NukeCleanser:4509a9b5
76+
CodeBuild Project Name : AccountNuker-NukeCleanser
77+
Process Start Time : Thu Feb 23 04:05:21 UTC 2023
78+
Process End Time : Thu Feb 23 04:05:54 UTC 2023
79+
Log Stream Path : AccountNuker-NukeCleanser/logPath
80+
------------------------------------------------------------------
81+
################ Nuke Cleanser Logs #################
82+
83+
FAILED RESOURCES
84+
-------------------------------
85+
Total number of Resources that would be removed:
86+
3
87+
us-west-1 - SQSQueue - https://sqs.us-east-1.amazonaws.com/123456789012/test-nuke-queue - would remove
88+
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
89+
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
90+
91+
```
92+
93+
## Monitoring queries
94+
95+
Use the `aws-cli` to get CloudWatch logs associated with your most recent nuclear activity.
96+
97+
```sh
98+
# Get the current Unix timestamp
99+
CURRENT_TIME=$(date +%s000)
100+
101+
# Get the timestamp from 5 minutes ago
102+
FIVE_MINUTES_AGO=$(($(date +%s) - 300))000
103+
104+
# Filter log events using above date range
105+
aws logs filter-log-events \
106+
--log-group-name AccountNuker-nuke-auto-account-cleanser \
107+
--start-time $FIVE_MINUTES_AGO --end-time $CURRENT_TIME \
108+
--log-stream-names "10409c89-a90f-4af7-9642-0df9bc9f0855" \
109+
--filter-pattern removed \
110+
--no-interleaved \
111+
--output text \
112+
--limit 5
113+
```
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import * as cdk from 'aws-cdk-lib';
2+
import * as events from 'aws-cdk-lib/aws-events';
3+
import * as targets from 'aws-cdk-lib/aws-events-targets';
4+
import * as iam from 'aws-cdk-lib/aws-iam';
5+
import * as s3 from 'aws-cdk-lib/aws-s3';
6+
import * as s3assets from 'aws-cdk-lib/aws-s3-assets';
7+
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'
8+
import * as path from 'path';
9+
import * as lambda from "aws-cdk-lib/aws-lambda";
10+
import * as python from "@aws-cdk/aws-lambda-python-alpha";
11+
import * as fs from 'fs';
12+
13+
export interface NukeStackProps extends cdk.StackProps {
14+
awsNukeDryRunFlag?: string;
15+
awsNukeVersion?: string;
16+
owner?: string;
17+
}
18+
19+
class NukeStack extends cdk.Stack {
20+
constructor(scope: cdk.App, id: string, props: NukeStackProps) {
21+
super(scope, id, props);
22+
23+
// Applying default props
24+
props = {
25+
...props,
26+
awsNukeDryRunFlag: props.awsNukeDryRunFlag ?? 'true',
27+
awsNukeVersion: props.awsNukeVersion ?? '2.21.2',
28+
owner: props.owner ?? 'OpsAdmin',
29+
};
30+
31+
// S3 Bucket for storing AWS Nuke binary and configuration
32+
const nukeS3Bucket = new s3.Bucket(this, 'NukeS3Bucket', {
33+
bucketName: `nuke-config-bucket-${this.account}-${this.region}`,
34+
removalPolicy: cdk.RemovalPolicy.DESTROY,
35+
autoDeleteObjects: true,
36+
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
37+
});
38+
39+
const awsNukeConfig = new s3deploy.BucketDeployment(this, 'DeployFile', {
40+
sources: [s3deploy.Source.data('nuke_generic_config.yaml', fs.readFileSync('nuke_generic_config.yaml', 'utf-8'))],
41+
destinationBucket: nukeS3Bucket,
42+
destinationKeyPrefix: 'nuke_generic_config.yaml'
43+
})
44+
45+
// AWS Lambda Function
46+
const nukeLambdaRole = new iam.Role(this, 'NukeLambdaRole', {
47+
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
48+
managedPolicies: [
49+
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
50+
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess'),
51+
],
52+
});
53+
54+
const nukeLambda = new lambda.Function(this, 'NukeLambda', {
55+
runtime: lambda.Runtime.PYTHON_3_9,
56+
handler: 'index.handler',
57+
code: lambda.Code.fromAsset(path.join(__dirname, 'lambda')),
58+
role: nukeLambdaRole,
59+
timeout: cdk.Duration.minutes(15),
60+
environment: {
61+
AWS_NUKE_DRY_RUN: props.awsNukeDryRunFlag ?? 'true',
62+
AWS_NUKE_VERSION: props.awsNukeVersion ?? '2.21.2',
63+
NUKE_S3_BUCKET: nukeS3Bucket.bucketName,
64+
NUKE_CONFIG_KEY: cdk.Fn.select(0, awsNukeConfig.objectKeys),
65+
},
66+
});
67+
68+
// Grant permissions for the Lambda function to access the S3 bucket
69+
nukeS3Bucket.grantRead(nukeLambda);
70+
71+
// EventBridge Rule
72+
const eventBridgeRule = new events.Rule(this, 'NukeScheduleRule', {
73+
schedule: events.Schedule.cron({ minute: '0', hour: '7', // Change the schedule as needed
74+
}),
75+
});
76+
77+
eventBridgeRule.addTarget(new targets.LambdaFunction(nukeLambda));
78+
}
79+
}
80+
81+
const app = new cdk.App();
82+
new NukeStack(app, 'NukeStack', {
83+
env: {
84+
account: process.env.CDK_DEFAULT_ACCOUNT,
85+
region: process.env.CDK_DEFAULT_REGION,
86+
},
87+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts account_nuker.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21+
"@aws-cdk/core:checkSecretUsage": true,
22+
"@aws-cdk/core:target-partitions": [
23+
"aws",
24+
"aws-cn"
25+
],
26+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29+
"@aws-cdk/aws-iam:minimizePolicies": true,
30+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35+
"@aws-cdk/core:enablePartitionLiterals": true,
36+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
38+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
39+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
40+
"@aws-cdk/aws-route53-patters:useCertificate": true,
41+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
42+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
43+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
44+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
45+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
46+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
47+
"@aws-cdk/aws-redshift:columnId": true,
48+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
50+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
51+
"@aws-cdk/aws-kms:aliasNameRef": true,
52+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
53+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
54+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
55+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
56+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
57+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
58+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
59+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
60+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
61+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
62+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
63+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
64+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
65+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
66+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
67+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
68+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
69+
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
70+
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
71+
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
72+
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
73+
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
74+
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
75+
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
76+
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
77+
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
78+
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
79+
"cdk-migrate": true
80+
}
81+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
This module is used to create an AWS account alias, which is required by the deploy.py script.
5+
6+
It provides a function to create an account alias using the AWS CLI, as this specific
7+
operation is not supported by the AWS CDK.
8+
"""
9+
10+
import logging
11+
import re
12+
import subprocess
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
def _is_valid_alias(alias_name: str) -> bool:
18+
"""
19+
Check if the provided alias name is valid according to AWS rules.
20+
21+
AWS account alias must be unique and must be between 3 and 63 characters long.
22+
Valid characters are a-z, 0-9 and '-'.
23+
24+
Args:
25+
alias_name (str): The alias name to validate.
26+
27+
Returns:
28+
bool: True if the alias is valid, False otherwise.
29+
"""
30+
pattern = r"^[a-z0-9](([a-z0-9]|-){0,61}[a-z0-9])?$"
31+
return bool(re.match(pattern, alias_name)) and 3 <= len(alias_name) <= 63
32+
33+
34+
def _log_aws_cli_version() -> None:
35+
"""
36+
Log the version of the AWS CLI installed on the system.
37+
"""
38+
try:
39+
result = subprocess.run(["aws", "--version"], capture_output=True, text=True)
40+
logger.info(f"AWS CLI version: {result.stderr.strip()}")
41+
except Exception as e:
42+
logger.warning(f"Unable to determine AWS CLI version: {str(e)}")
43+
44+
45+
def create_account_alias(alias_name: str) -> None:
46+
"""
47+
Create a new account alias with the given name.
48+
49+
This function exists because the CDK does not support the specific
50+
CreateAccountAliases API call. It attempts to create an account alias
51+
using the AWS CLI and logs the result.
52+
53+
If the account alias is created successfully, it logs a success message.
54+
If the account alias already exists, it logs a message indicating that.
55+
If there is any other error, it logs the error message.
56+
57+
Args:
58+
alias_name (str): The desired name for the account alias.
59+
"""
60+
# Log AWS CLI version when the function is called
61+
_log_aws_cli_version()
62+
63+
if not _is_valid_alias(alias_name):
64+
logger.error(
65+
f"Invalid alias name '{alias_name}'. It must be between 3 and 63 characters long and contain only lowercase letters, numbers, and hyphens."
66+
)
67+
return
68+
69+
command = ["aws", "iam", "create-account-alias", "--account-alias", alias_name]
70+
71+
try:
72+
subprocess.run(
73+
command,
74+
stdout=subprocess.PIPE,
75+
stderr=subprocess.PIPE,
76+
text=True,
77+
check=True,
78+
)
79+
logger.info(f"Account alias '{alias_name}' created successfully.")
80+
except subprocess.CalledProcessError as e:
81+
if "EntityAlreadyExists" in e.stderr:
82+
logger.info(f"Account alias '{alias_name}' already exists.")
83+
elif "AccessDenied" in e.stderr:
84+
logger.error(
85+
f"Access denied when creating account alias '{alias_name}'. Check your AWS credentials and permissions."
86+
)
87+
elif "ValidationError" in e.stderr:
88+
logger.error(
89+
f"Validation error when creating account alias '{alias_name}'. The alias might not meet AWS requirements."
90+
)
91+
else:
92+
logger.error(f"Error creating account alias '{alias_name}': {e.stderr}")
93+
except Exception as e:
94+
logger.error(
95+
f"Unexpected error occurred while creating account alias '{alias_name}': {str(e)}"
96+
)

0 commit comments

Comments
 (0)