From 7b4cc3b61a8b69fcb5446cc42c890bd11fe05a00 Mon Sep 17 00:00:00 2001 From: Gustavo Aguiar Date: Mon, 28 May 2018 12:53:00 -0400 Subject: [PATCH 1/4] Making account_id not required (account_aliases still required). --- clean.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/clean.py b/clean.py index 589d36f..aff9547 100755 --- a/clean.py +++ b/clean.py @@ -55,9 +55,10 @@ def _simple_delete(self, describe_function, delete_function, preserve_key, list_ self._delete_generic_resource(deletables, list_key, delete_function, item_key) def run_safety_checks(self, sts, iam, iam_resource): - # AWS Account ID in config.yml must match the account we are accessing using an API key + # AWS Account ID in config.yml must match the account we are accessing using an API key (if null then use only account_aliases) account_id = sts.get_caller_identity().get("Account") - assert account_id == self.config.get("assertions").get("account_id"), "Unexpected AWS Account ID, check configuration!" + if self.config.get("assertions").get("account_id"): + assert account_id == self.config.get("assertions").get("account_id"), "Unexpected AWS Account ID, check configuration!" # AWS Account alias in config.yml must match the account alias account_aliases = iam.list_account_aliases().get("AccountAliases") @@ -174,3 +175,5 @@ def get_boto_session(profile_name): cleaner.delete_securitygroups(ec2) cleaner.delete_key_pairs(ec2) cleaner.delete_buckets(s3, s3_resource) + + From a93782794d245a4d22e5dd860758bf26b300b648 Mon Sep 17 00:00:00 2001 From: Gustavo Aguiar Date: Mon, 28 May 2018 13:44:03 -0400 Subject: [PATCH 2/4] Adding support to EC2 instances. --- README.md | 1 + clean.py | 31 ++++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e059259..973a1a3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This utility tool will delete all resources from your AWS account. Whitelisted r - EC2 key pairs - EC2 AMI images - EC2 security groups +- EC2 instances - EBS snapshots - CloudWatch alarms - SNS topics diff --git a/clean.py b/clean.py index aff9547..248c491 100755 --- a/clean.py +++ b/clean.py @@ -55,10 +55,9 @@ def _simple_delete(self, describe_function, delete_function, preserve_key, list_ self._delete_generic_resource(deletables, list_key, delete_function, item_key) def run_safety_checks(self, sts, iam, iam_resource): - # AWS Account ID in config.yml must match the account we are accessing using an API key (if null then use only account_aliases) + # AWS Account ID in config.yml must match the account we are accessing using an API key account_id = sts.get_caller_identity().get("Account") - if self.config.get("assertions").get("account_id"): - assert account_id == self.config.get("assertions").get("account_id"), "Unexpected AWS Account ID, check configuration!" + assert account_id == self.config.get("assertions").get("account_id"), "Unexpected AWS Account ID, check configuration!" # AWS Account alias in config.yml must match the account alias account_aliases = iam.list_account_aliases().get("AccountAliases") @@ -88,6 +87,31 @@ def delete_cloudformation_stacks(self, cf): } self._simple_delete(cf.list_stacks, cf.delete_stack, "cloudformation", "StackSummaries", "StackName", args) + def delete_ec2_instances(self, ec2): + instances = ec2.describe_instances( + Filters=[{ + 'Name': 'instance-state-name', + 'Values': ['running', 'stopped', 'stopping'], + }] + ) + instance_list = [] + for reservation in instances["Reservations"]: + for instance in reservation["Instances"]: + instance_list.append(instance["InstanceId"]) + print(instance["InstanceId"] + ":") + print("\tInstanceType: " + instance["InstanceType"]) + if instance_list: + if self._ask("\nDelete EC2 Instances?", "no"): + response = ec2.terminate_instances( + InstanceIds= + instance_list + , + DryRun=False + ) + waiter = ec2.get_waiter('instance_terminated') + waiter.wait(InstanceIds=instance_list) + #print("Response was: ", response) + def delete_key_pairs(self, ec2): self._simple_delete(ec2.describe_key_pairs, ec2.delete_key_pair, "ec2_key_pairs", "KeyPairs", "KeyName") @@ -172,6 +196,7 @@ def get_boto_session(profile_name): cleaner.delete_sns_topics(sns) cleaner.delete_amis(sts, ec2) cleaner.delete_snapshots(sts, ec2) + cleaner.delete_ec2_instances(ec2) cleaner.delete_securitygroups(ec2) cleaner.delete_key_pairs(ec2) cleaner.delete_buckets(s3, s3_resource) From 4f97a052b9684b710b2e066a9ccb6e5da358e644 Mon Sep 17 00:00:00 2001 From: Gustavo Aguiar Date: Mon, 25 Jun 2018 14:58:28 -0400 Subject: [PATCH 3/4] Adding support for multiple regions --- .gitignore | 1 + clean.py | 74 ++++++++++++++++++++++++++++++------------------ requirements.txt | 1 + 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 1d3ed4c..c6c1e60 100755 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ config.yml +*.ignore diff --git a/clean.py b/clean.py index 248c491..7156359 100755 --- a/clean.py +++ b/clean.py @@ -3,6 +3,7 @@ from __future__ import print_function import sys import yaml +import pprint class Cleaner: @@ -54,10 +55,11 @@ def _simple_delete(self, describe_function, delete_function, preserve_key, list_ deletables = self._get_deletable_resources(describe_function, describe_args, preserve_key, list_key, item_key, filter_function) self._delete_generic_resource(deletables, list_key, delete_function, item_key) - def run_safety_checks(self, sts, iam, iam_resource): - # AWS Account ID in config.yml must match the account we are accessing using an API key + def run_safety_checks(self, sts, iam, iam_resource, aws_regions_list): + # AWS Account ID in config.yml must match the account we are accessing using an API key (if null then use only account_aliases) account_id = sts.get_caller_identity().get("Account") - assert account_id == self.config.get("assertions").get("account_id"), "Unexpected AWS Account ID, check configuration!" + if self.config.get("assertions").get("account_id"): + assert account_id == self.config.get("assertions").get("account_id"), "Unexpected AWS Account ID, check configuration!" # AWS Account alias in config.yml must match the account alias account_aliases = iam.list_account_aliases().get("AccountAliases") @@ -69,7 +71,7 @@ def run_safety_checks(self, sts, iam, iam_resource): current_user = iam_resource.CurrentUser().user_name assert current_user == self.config.get("assertions").get("iam_username"), "Unexpected IAM User name, check configuration!" - print("You are {} on account {} ({})".format(current_user, account_id, account_alias)) + print("You are {} on account {} ({}) and included regions are {}".format(current_user, account_id, account_alias, aws_regions_list)) if not self._ask("Proceed?", "no"): sys.exit() def delete_cloudformation_stacks(self, cf): @@ -95,11 +97,14 @@ def delete_ec2_instances(self, ec2): }] ) instance_list = [] + #pprint.pprint(instances) for reservation in instances["Reservations"]: for instance in reservation["Instances"]: instance_list.append(instance["InstanceId"]) print(instance["InstanceId"] + ":") print("\tInstanceType: " + instance["InstanceType"]) + print("\tAvailabilityZone: " + instance["Placement"]["AvailabilityZone"]) + #pprint.pprint(instance_list) if instance_list: if self._ask("\nDelete EC2 Instances?", "no"): response = ec2.terminate_instances( @@ -143,6 +148,7 @@ def delete_cloudwatch_alarms(self, cloudwatch): def delete_buckets(self, s3, s3_resource): def delete_bucket_and_its_objects(Name): bucket = s3_resource.Bucket(Name) + print("Bucket name: {}".format(bucket)) bucket.object_versions.delete() bucket.delete() self._simple_delete(s3.list_buckets, delete_bucket_and_its_objects, "s3_buckets", "Buckets", "Name") @@ -169,36 +175,50 @@ def _get_config_from_file(filename): config = yaml.load(stream) return config -def get_boto_session(profile_name): +def get_boto_session(profile_name, aws_region): import boto3 - return boto3.Session(profile_name=profile_name) + return boto3.Session(profile_name=profile_name, region_name=aws_region) if __name__ == "__main__": config = _get_config_from_file(sys.argv[1]) cleaner = Cleaner(config) print("Current configuration:\n", yaml.dump(config, default_flow_style=False)) - - boto_session = get_boto_session(config["profile_name"]) - - cf = boto_session.client("cloudformation") - cloudwatch = boto_session.client("cloudwatch") - ec2 = boto_session.client("ec2") - iam = boto_session.client("iam") + # Get all AWS regions + aws_regions_list = config.get("assertions").get("regions", []) + #pprint.pprint("Running for regions: %l", aws_regions_list) + #print("Running regions: {}".format(aws_regions_list)) + + # Query IAM and execute run_safety_checks + default_aws_region = "us-east-1" + boto_session = get_boto_session(config["profile_name"], default_aws_region) + iam = boto_session.client("iam", region_name=default_aws_region) iam_resource = boto_session.resource("iam") - s3 = boto_session.client("s3") - s3_resource = boto_session.resource("s3") - sts = boto_session.client("sts") - sns = boto_session.client("sns") - - cleaner.run_safety_checks(sts, iam, iam_resource) - cleaner.delete_cloudformation_stacks(cf) - cleaner.delete_cloudwatch_alarms(cloudwatch) - cleaner.delete_sns_topics(sns) - cleaner.delete_amis(sts, ec2) - cleaner.delete_snapshots(sts, ec2) - cleaner.delete_ec2_instances(ec2) - cleaner.delete_securitygroups(ec2) - cleaner.delete_key_pairs(ec2) + sts = boto_session.client("sts", region_name=default_aws_region) + cleaner.run_safety_checks(sts, iam, iam_resource, aws_regions_list) + + # Execute for each AWS region + for aws_region in aws_regions_list: + print("== Working region: " + aws_region) + #print("Default region: " + region) + boto_session = get_boto_session(config["profile_name"], aws_region) + cf = boto_session.client("cloudformation", region_name=aws_region) + cloudwatch = boto_session.client("cloudwatch", region_name=aws_region) + ec2 = boto_session.client("ec2", region_name=aws_region) + iam = boto_session.client("iam", region_name=aws_region) + s3_resource = boto_session.resource("s3", region_name=default_aws_region) + sts = boto_session.client("sts", region_name=aws_region) + sns = boto_session.client("sns", region_name=aws_region) + + cleaner.delete_cloudformation_stacks(cf) + cleaner.delete_cloudwatch_alarms(cloudwatch) + cleaner.delete_sns_topics(sns) + cleaner.delete_amis(sts, ec2) + cleaner.delete_snapshots(sts, ec2) + cleaner.delete_ec2_instances(ec2) + cleaner.delete_securitygroups(ec2) + cleaner.delete_key_pairs(ec2) + + s3 = boto_session.client("s3", region_name=default_aws_region) cleaner.delete_buckets(s3, s3_resource) diff --git a/requirements.txt b/requirements.txt index 5af1852..e0f9136 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ boto3==1.4.4 +pyyaml==3.12 From ccefb7c9ead0320512e054941b1befd53ea2bce9 Mon Sep 17 00:00:00 2001 From: Gustavo Aguiar Date: Tue, 26 Jun 2018 10:23:46 -0400 Subject: [PATCH 4/4] Adding support for multiple regions --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 973a1a3..2e630b7 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,16 @@ This utility tool will delete all resources from your AWS account. Whitelisted r - SNS topics - S3 buckets +### Region usage + # Assertions must pass before any resources are deleted + assertions: + account_id: "012345678901" + account_alias: your-account-iam-alias + iam_username: your-iam-username + regions: + - us-east-1 + - us-east-2 + ### Usage Create a config from the sample file and edit it: