After about a year of unaudited use, I found it necessary to audit my AWS EC2 security groups and clean out obsolete unused groups.
It was a difficult task to do through the web interface, so I turned to the AWS CLI to make the task easier. I found a start on how to do this in StackOverflow, but it was far from complete. So I decided to write my own script. I used AWS CLI, MySQL and some "bash-foo" to do the following:
Get a list of all EC2 security groups. I store the group id, group name and description in the declared "groups" in a MySQL database called aws_security_groups on the local host. The total number of found groups is reported.
List all security groups associated with each of the following services and exclude them from the table: EC2 Istances EC2 elastic balancing loads AWS RDS instances AWS OpsWorks (should not be deleted on Amazon) Default security groups (cannot be deleted) ElastiCache
For each service, I report the number of groups remaining in the table after the completion of the exception.
- Finally, I show the group id, group name and description for the remaining groups. These are βunused" groups that need to be checked and / or deleted. Ive found that SG between instances and Elastic Load Balancers (ELBs) often refer to each other. Itβs best to do a manual exploration to make sure that they arenβt actually used until you remove cross-references and remove security groups. But my script at least views this as something unacceptable.
NOTES: 1. You will need to create a file to store your host, MySQL username and password and specify the $ DBCONFIG variable for it. It should be structured as follows:
[mysql] host=your-mysql-server-host.com user=your-mysql-user password=your-mysql-user-password
- You can change the database name if you want - be sure to change the $ DB variable in the script
Let me know if you find this helpful or if you have comments, corrections or improvements.
Here is the script.
#!/bin/bash # Initialize Variables DBCONFIG="--defaults-file=mysql-defaults.cnf" DB="aws_security_groups" SGLOOP=0 EC2LOOP=0 ELBLOOP=0 RDSLOOP=0 DEFAULTLOOP=0 OPSLOOP=0 CACHELOOP=0 DEL_GROUP="" # Function to report back # of rows function Rows { ROWS=`echo "select count(*) from groups" | mysql $DBCONFIG --skip-column-names $DB` # echo -e "Excluding $1 Security Groups.\nGroups Left to audit: "$ROWS echo -e $ROWS" groups left after Excluding $1 Security Groups." } # Empty the table echo -e "delete from groups where groupid is not null" | mysql $DBCONFIG $DB # Get all Security Groups aws ec2 describe-security-groups --query "SecurityGroups[*].[GroupId,GroupName,Description]" --output text > /tmp/security_group_audit.txt while IFS=$'\t' read -r -a myArray do if [ $SGLOOP -eq 0 ]; then VALUES="(\""${myArray[0]}"\",\""${myArray[1]}"\",\""${myArray[2]}"\")" else VALUES=$VALUES",(\""${myArray[0]}"\",\""${myArray[1]}"\",\""${myArray[2]}"\")" fi let SGLOOP="$SGLOOP + 1" done < /tmp/security_group_audit.txt echo -e "insert into groups (groupid, groupname, description) values $VALUES" | mysql $DBCONFIG $DB echo -e $SGLOOP" security groups total." # Exclude Security Groups assigned to Instances for groupId in `aws ec2 describe-instances --output json | jq -r ".Reservations[].Instances[].SecurityGroups[].GroupId" | sort | uniq` do if [ $EC2LOOP -eq 0 ]; then DEL_GROUP="'$groupId'" else DEL_GROUP=$DEL_GROUP",'$groupId'" fi let EC2LOOP="$EC2LOOP + 1" done echo -e "delete from groups where groupid in ($DEL_GROUP)" | mysql $DBCONFIG $DB Rows "EC2 Instance" DEL_GROUP="" # Exclude groups assigned to Elastic Load Balancers for elbGroupId in `aws elb describe-load-balancers --output json | jq -c -r ".LoadBalancerDescriptions[].SecurityGroups" | tr -d "\"[]\"" | sort | uniq` do if [ $ELBLOOP -eq 0 ]; then DEL_GROUP="'$elbGroupId'" else DEL_GROUP=$DEL_GROUP",'$elbGroupId'" fi let ELBLOOP="$ELBLOOP + 1" done echo -e "delete from groups where groupid in ($DEL_GROUP)" | mysql $DBCONFIG $DB Rows "Elastic Load Balancer" DEL_GROUP="" # Exclude groups assigned to RDS for RdsGroupId in `aws rds describe-db-instances --output json | jq -c -r ".DBInstances[].VpcSecurityGroups[].VpcSecurityGroupId" | sort | uniq` do if [ $RDSLOOP -eq 0 ]; then DEL_GROUP="'$RdsGroupId'" else DEL_GROUP=$DEL_GROUP",'$RdsGroupId'" fi let RDSLOOP="$RDSLOOP + 1" done echo -e "delete from groups where groupid in ($DEL_GROUP)" | mysql $DBCONFIG $DB Rows "RDS Instances" DEL_GROUP="" # Exclude groups assigned to OpsWorks for OpsGroupId in `echo -e "select groupid from groups where groupname like \"AWS-OpsWorks%\"" | mysql $DBCONFIG $DB` do if [ $OPSLOOP -eq 0 ]; then DEL_GROUP="'$OpsGroupId'" else DEL_GROUP=$DEL_GROUP",'$OpsGroupId'" fi let OPSLOOP="$OPSLOOP + 1" done echo -e "delete from groups where groupid in ($DEL_GROUP)" | mysql $DBCONFIG $DB Rows "OpsWorks" DEL_GROUP="" # Exclude default groups (can't be deleted) for DefaultGroupId in `echo -e "select groupid from groups where groupname like \"default%\"" | mysql $DBCONFIG $DB` do if [ $DEFAULTLOOP -eq 0 ]; then DEL_GROUP="'$DefaultGroupId'" else DEL_GROUP=$DEL_GROUP",'$DefaultGroupId'" fi let DEFAULTLOOP="$DEFAULTLOOP + 1" done echo -e "delete from groups where groupid in ($DEL_GROUP)" | mysql $DBCONFIG $DB Rows "Default" DEL_GROUP="" # Exclude Elasticache groups for CacheGroupId in `aws elasticache describe-cache-clusters --output json | jq -r ".CacheClusters[].SecurityGroups[].SecurityGroupId" | sort | uniq` do if [ $CACHELOOP -eq 0 ]; then DEL_GROUP="'$CacheGroupId'" else DEL_GROUP=$DEL_GROUP",'$CacheGroupId'" fi let CACHELOOP="$CACHELOOP + 1" done echo -e "delete from groups where groupid in ($DEL_GROUP)" | mysql $DBCONFIG $DB Rows "ElastiCache" # Display Security Groups left to audit / delete echo "select * from groups order by groupid" | mysql $DBCONFIG $DB | sed 's/groupid\t/groupid\t\t/'
And here is sql to create the database.
-- MySQL dump 10.13 Distrib 5.5.41, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: aws_security_groups -- ------------------------------------------------------ -- Server version 5.5.40-log ; ; ; ; ; ; ; ; ; ; -- -- Table structure for table `groups` -- DROP TABLE IF EXISTS `groups`; ; ; CREATE TABLE `groups` ( `groupid` varchar(12) DEFAULT NULL, `groupname` varchar(200) DEFAULT NULL, `description` varchar(200) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; ; -- -- Dumping data for table `groups` -- LOCK TABLES `groups` WRITE; ; ; UNLOCK TABLES; ; ; ; ; ; ; ; ; -- Dump completed on 2015-01-27 16:07:44