Skip to content

Instantly share code, notes, and snippets.

@TomRyan-321
Forked from lingmann/security-group-cleanup.py
Last active October 29, 2024 02:54
Show Gist options
  • Save TomRyan-321/0cf6e48937cbe9513afc50117d6ffd6f to your computer and use it in GitHub Desktop.
Save TomRyan-321/0cf6e48937cbe9513afc50117d6ffd6f to your computer and use it in GitHub Desktop.
Security Group Cleanup using boto3 with RDS check fixed
#!/usr/bin/env python
import boto3
import argparse
def lookup_by_id(sgid):
sg = ec2.get_all_security_groups(group_ids=sgid)
return sg[0].name
# get a full list of the available regions
client = boto3.client('ec2')
regions_dict = client.describe_regions()
region_list = [region['RegionName'] for region in regions_dict['Regions']]
# parse arguments
parser = argparse.ArgumentParser(description="Show unused security groups")
parser.add_argument("-r", "--region", type=str, default="us-east-1",
help="The default region is us-east-1. The list of available regions are as follows: %s" % sorted(
region_list))
parser.add_argument("-d", "--delete", help="delete security groups from AWS", action="store_true")
args = parser.parse_args()
client = boto3.client('ec2', region_name=args.region)
ec2 = boto3.resource('ec2', region_name=args.region)
all_groups = []
security_groups_in_use = []
# Get ALL security groups names
security_groups_dict = client.describe_security_groups()
security_groups = security_groups_dict['SecurityGroups']
for groupobj in security_groups:
if groupobj['GroupName'] == 'default' or groupobj['GroupName'].startswith('d-') or groupobj['GroupName'].startswith('AWS-OpsWorks-'):
security_groups_in_use.append(groupobj['GroupId'])
all_groups.append(groupobj['GroupId'])
# Get all security groups used by instances
instances_dict = client.describe_instances()
reservations = instances_dict['Reservations']
network_interface_count = 0
for i in reservations:
for j in i['Instances']:
for k in j['SecurityGroups']:
if k['GroupId'] not in security_groups_in_use:
security_groups_in_use.append(k['GroupId'])
# Security Groups in use by Network Interfaces
eni_client = boto3.client('ec2', region_name=args.region)
eni_dict = eni_client.describe_network_interfaces()
for i in eni_dict['NetworkInterfaces']:
for j in i['Groups']:
if j['GroupId'] not in security_groups_in_use:
security_groups_in_use.append(j['GroupId'])
# Security groups used by classic ELBs
elb_client = boto3.client('elb', region_name=args.region)
elb_dict = elb_client.describe_load_balancers()
for i in elb_dict['LoadBalancerDescriptions']:
for j in i['SecurityGroups']:
if j not in security_groups_in_use:
security_groups_in_use.append(j)
# Security groups used by ALBs
elb2_client = boto3.client('elbv2', region_name=args.region)
elb2_dict = elb2_client.describe_load_balancers()
for i in elb2_dict['LoadBalancers']:
for j in i['SecurityGroups']:
if j not in security_groups_in_use:
security_groups_in_use.append(j)
# Security groups used by RDS
rds_client = boto3.client('rds', region_name=args.region)
rds_dict = rds_client.describe_db_instances()
for i in rds_dict['DBInstances']:
for j in i['VpcSecurityGroups']:
if j['VpcSecurityGroupId'] not in security_groups_in_use:
security_groups_in_use.append(j['VpcSecurityGroupId'])
delete_candidates = []
for group in all_groups:
if group not in security_groups_in_use:
delete_candidates.append(group)
if args.delete:
print("We will now delete security groups identified to not be in use.")
for group in delete_candidates:
security_group = ec2.SecurityGroup(group)
try:
security_group.delete()
except Exception as e:
print(e)
print("{0} requires manual remediation.".format(security_group.group_name))
else:
print("The list of security groups to be removed is below.")
print("Run this again with `-d` to remove them")
for group in sorted(delete_candidates):
print(" " + group)
print("---------------")
print("Activity Report")
print("---------------")
print(u"Total number of Security Groups evaluated: {0:d}".format(len(all_groups)))
print(u"Total number of EC2 Instances evaluated: {0:d}".format(len(reservations)))
print(u"Total number of Load Balancers evaluated: {0:d}".format(len(elb_dict['LoadBalancerDescriptions']) +
len(elb2_dict['LoadBalancers'])))
print(u"Total number of RDS Instances evaluated: {0:d}".format(len(rds_dict['DBInstances'])))
print(u"Total number of Network Interfaces evaluated: {0:d}".format(len(eni_dict['NetworkInterfaces'])))
print(u"Total number of Security Groups in-use evaluated: {0:d}".format(len(security_groups_in_use)))
if args.delete:
print(u"Total number of Unused Security Groups deleted: {0:d}".format(len(delete_candidates)))
else:
print(u"Total number of Unused Security Groups targeted for removal: {0:d}".format(len(delete_candidates)))
# For each security group in the total list, if not in the "used" list, flag for deletion
# If running with a "--delete" flag, delete the ones flagged.
@philipbjorge
Copy link

Thanks a lot!

@smitty-exos
Copy link

smitty-exos commented Aug 2, 2018

Great script...I did find one issue around line 67 and dealing with elbv2 and security groups.
We have a number of 'network' load balancers, and these do not have any Security Groups associated with them, and thus would fail on your for loop. I added the following and all is well.

# Security groups used by ALBs
elb2_client = boto3.client('elbv2', region_name=args.region)
elb2_dict = elb2_client.describe_load_balancers()
for i in elb2_dict['LoadBalancers']:
    try:
        for j in i['SecurityGroups']:
            if j not in security_groups_in_use:
                security_groups_in_use.append(j)
    except KeyError:
        pass

@jeff-kilbride
Copy link

Nice script, works great! Thanks.

@HiroTamori
Copy link

Thanks for the code!
Small improvement. Most AWS users may set AWS_DEFALUT_REGION in the environmental variables.
So,

import os
# .... snip ....
try:
    default_region = os.environ["AWS_DEFAULT_REGION"]
except:
    default_region = "us-east-1"
# parse arguments
parser = argparse.ArgumentParser(description="Show unused security groups")
parser.add_argument("-r", "--region", type=str, default=default_region,
                    help="The default region is us-east-1. The list of available regions are as follows: %s" % sorted(
                        region_list))

may be good.

@sergeylanzman
Copy link

network loadbalancer not have SecurityGroups..

# Security groups used by ALBs
elb2_client = boto3.client('elbv2', region_name=args.region)
elb2_dict = elb2_client.describe_load_balancers()
for i in elb2_dict['LoadBalancers']:
  if i['Type']=='network':
    continue
  for j in i['SecurityGroups']:
    if j not in security_groups_in_use:
      security_groups_in_use.append(j)

@TitanTitane
Copy link

Thanks for ce code very best, but security group lamdba check :

Security groups used by lambda

lambda_client = boto3.client('lambda', region_name=args.region)
lambda_functions = lambda_client.list_functions()
while True:
    if "NextMarker" in lambda_functions:
        nextMarker = lambda_functions["NextMarker"]
    else:
        nextMarker = ""
    for function in lambda_functions["Functions"]:
        functionName = function["FunctionName"]
        print ("name"+functionName)
        functionVpcConfig=""
        functionSecurityGroupIds=""
        try:
            functionVpcConfig = function["VpcConfig"]
            functionSecurityGroupIds = functionVpcConfig["SecurityGroupIds"]
            for j in functionSecurityGroupIds:
                if j not in security_groups_in_use:
                    security_groups_in_use.append(j)
        except KeyError:
            continue
        finally:
            print (functionSecurityGroupIds)
    if nextMarker == "":
        break
    else:
        lambda_functions = lambda_client.list_functions(
            Marker= nextMarker
        )

@snixon
Copy link

snixon commented Jul 3, 2019

Hey thank you for this, I added a few more checks and options if you're interested:
https://gist.github.com/snixon/059b0a0edf87e9a34d020bb2c9546874

@rahulroycontractor
Copy link

rahulroycontractor commented Mar 22, 2022

Great script...I did find one issue around line 67 and dealing with elbv2 and security groups. We have a number of 'network' load balancers, and these do not have any Security Groups associated with them, and thus would fail on your for loop. I added the following and all is well.

# Security groups used by ALBs
elb2_client = boto3.client('elbv2', region_name=args.region)
elb2_dict = elb2_client.describe_load_balancers()
for i in elb2_dict['LoadBalancers']:
    try:
        for j in i['SecurityGroups']:
            if j not in security_groups_in_use:
                security_groups_in_use.append(j)
    except KeyError:
        pass

or we can do

        for i in elb2_dict['LoadBalancers']:
            if "SecurityGroups" in i:
                for j in i["SecurityGroups"]:
                    if j not in security_groups_in_use:
                        security_groups_in_use.append(j)
            else:
                print(f"ALB -> {i['LoadBalancerName']} didn't use any security group")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment