How to find Unused Amazon EC2 Security groups

我正在想办法确定孤儿保护组织这样我就能清理干净,摆脱他们。有人知道如何发现未使用的安全组吗。

通过控制台或使用命令行工具都可以工作(在 linux 和 OSX 机器上运行命令行工具)。

58135 次浏览

这是用 boto (Python SDK for aWS)编写的示例代码,用于列出与其关联的安全组(Security Group)的数量。

您也可以使用这种逻辑在命令行中获得相同的结果

Boto Code

import boto
ec2 = boto.connect_ec2()
sgs = ec2.get_all_security_groups()
for sg in sgs:
print sg.name, len(sg.instances())

输出

Security-Group-1 0
Security-Group-2 1
Security-Group-3 0
Security-Group-4 3

注意: 这只考虑 EC2中的安全性使用,而不考虑 RDS 等其他服务。您需要做更多的工作来包括 EC2之外使用的安全组。好的一面是,如果错过了一个相关的 w/另一个服务,就不能轻易(甚至不可能)删除活动的安全组。

通过使用新的 AWS CLI 工具,我找到了一个获得我所需要的东西的简单方法:

首先,获取所有安全组的列表

aws ec2 describe-security-groups --query 'SecurityGroups[*].GroupId'  --output text | tr '\t' '\n'

然后将所有安全组绑定到一个实例,然后通过管道连接到 sort,然后通过管道连接到 uniq:

aws ec2 describe-instances --query 'Reservations[*].Instances[*].SecurityGroups[*].GroupId' --output text | tr '\t' '\n' | sort | uniq

然后把它们放在一起,比较两个列表,看看主列表中没有使用什么:

comm -23  <(aws ec2 describe-security-groups --query 'SecurityGroups[*].GroupId'  --output text | tr '\t' '\n'| sort) <(aws ec2 describe-instances --query 'Reservations[*].Instances[*].SecurityGroups[*].GroupId' --output text | tr '\t' '\n' | sort | uniq)

经过大约一年未经审计的使用,我发现有必要审计我的 AWS EC2安全组并清理遗留的、未使用的组。

通过 web GUI 执行这个任务是一个令人畏缩的任务,所以我指望使用 AWS CLI 使这个任务变得更容易。我在 StackOverflow 找到了一个如何做到这一点的开始,但它还远远没有完成。所以我决定自己写剧本。我使用了 AWS CLI、 MySQL 和一些“ Bash-foo”来执行以下操作:

  1. 获取所有 EC2安全组的列表。 I store the group-id, group-name and description in a tabled called “groups” in a MySQL database called aws_security_groups on the localhost. The total number of groups found is reported to the user.

  2. 获取与下列每个服务关联的所有安全组的列表,并将它们从表中排除: EC2 Istances EC2弹性负载均衡器 AWS RDS 实例 AWS OpsWorks (不应该被亚马逊删除) 默认安全组(无法删除) ElastiCache

对于每个服务,我报告排除完成后表中剩余的组数量的计数。

  1. 最后,我显示了剩下的组的 group-id、 group-name 和描述。这些是需要审计和/或删除的“未使用”组。我发现实例和弹性负载平衡器(ELB)之间的 SG 经常相互引用。最佳实践是在删除交叉引用和删除安全组之前进行一些手动调查,以确保它们确实没有被使用。但是我的剧本至少把这个缩小到更容易处理的程度。

备注: 1. You will want to create a file to store your MySQL host, username and password and point the $DBCONFIG variable to it. It should be structured like this:

[mysql]
host=your-mysql-server-host.com
user=your-mysql-user
password=your-mysql-user-password
  1. 如果愿意,您可以更改数据库的名称-确保更改脚本中的 $DB 变量

如果您觉得这很有用,或者有任何评论、修复或增强,请告诉我。

这是剧本。

#!/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/'

这是创建数据库的 sql。

-- 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


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;


--
-- Table structure for table `groups`
--


DROP TABLE IF EXISTS `groups`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `groups` (
`groupid` varchar(12) DEFAULT NULL,
`groupname` varchar(200) DEFAULT NULL,
`description` varchar(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;


--
-- Dumping data for table `groups`
--


LOCK TABLES `groups` WRITE;
/*!40000 ALTER TABLE `groups` DISABLE KEYS */;
/*!40000 ALTER TABLE `groups` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;


/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;


-- Dump completed on 2015-01-27 16:07:44

A boto example printing the Group IDs and Names 只有 of the security groups that have no current instances.

它还显示了如何指定您所关心的区域。

import boto
import boto.ec2
EC2_REGION='ap-southeast-2'
ec2region = boto.ec2.get_region(EC2_REGION)
ec2 = boto.connect_ec2(region=ec2region)
sgs = ec2.get_all_security_groups()
for sg in sgs:
if len(sg.instances()) == 0:
print ("{0}\t{1}".format(sg.id, sg.name))

要确认哪些安全组 仍在使用,您应该反向或删除 if len(sg.instances()) == 0测试并打印出 len(sg.instances())值。

例如。

print ("{0}\t{1}\t{2} instances".format(sg.id, sg.name, len(sg.instances())))

如果你在 EC2控制台中选择了所有的安全组,然后按下操作-> 删除安全组,弹出窗口会告诉你不能删除附加到实例、其他安全组或网络接口的安全组,它会列出你可以删除的安全组; 即未使用的安全组。

注意: 根据@andrewlorien 的评论,这并不适用于所有类型的 AWS 服务。

使用 node.js AWS SDK,我可以确认 AWS 不允许删除正在使用的安全组。我编写了一个脚本,它只是尝试删除所有组,并优雅地处理错误。这适用于经典和现代 VPC。下面可以看到错误消息。

Err { [DependencyViolation: resource sg-12345678 has a dependent object]
message: 'resource sg-12345678 has a dependent object',
code: 'DependencyViolation',
time: Mon Dec 07 2015 12:12:43 GMT-0500 (EST),
statusCode: 400,
retryable: false,
retryDelay: 30 }

在 AWS 市场上有一个工具可以使这个过程更加容易。它向您显示哪些组被附加/分离以便于删除,但是它也将您的 VPC 流日志与安全组规则进行比较,并向您显示哪些 SG 规则正在使用或未使用。AWS 发布了一个 ELK 堆栈解决方案来实现这一点,但是它复杂得可笑。

下面是这个工具,还有一个免责声明,声明是我自己做的。但我希望你们都能发现它的相关性: Https://www.piasoftware.net/single-post/2018/04/24/video-watch-as-we-clean-up-ec2-security-groups-in-just-a-few-minutes

不幸的是,所选择的答案并不像我需要的那样准确(我试图调查原因,但我更愿意实现它)。
If I check ALL NetworkInterfaces, looking for attachments to any SecurityGroup, It gets me partial results. If I check only on EC2Instances, it gets me back partial results as well.

这就是我解决问题的方法:

  1. 我得到所有 EC2安全组-> all_secgrp
  2. 我得到所有 EC2实例-> all_instances
  3. 对于每个实例,我得到所有的 SecurityGroups 附加到它
    1. 我从 all _ secgrp 中删除了这些 SecurityGroup (因为附件)
  4. 对于每个 SecurityGroup,我检查与任何 NetworkInterfaces 的关联(使用 filter函数并使用该 security-group-id进行过滤)
    1. 如果没有找到关联,则从 all_secgrp中删除安全组

附件中你可以看到一个代码片段。不要抱怨效率,但是如果你想的话可以尝试优化它。

all_secgrp = list(ec2_connector.security_groups.all())
all_instances = ec2_connector.instances.all()


for single_instance in all_instances:
instance_secgrp = ec2_connector.Instance(single_instance.id).security_groups
for single_sec_grp in instance_secgrp:
if ec2.SecurityGroup(id=single_sec_grp['GroupId']) in all_secgrp:
all_secgrp.remove(ec2.SecurityGroup(id=single_sec_grp['GroupId']))


all_secgrp_detached_tmp = all_secgrp[:]
for single_secgrp in all_secgrp_detached_tmp:
try:
print(single_secgrp.id)
if len(list(ec2_connector.network_interfaces.filter(Filters=[{'Name': 'group-id', 'Values': [single_secgrp.id]}]))) > 0:
all_secgrp.remove(single_secgrp)
except Exception:
all_secgrp.remove(single_secgrp)


return all_secgrp_detached

如果您的安全组引用了规则中的其他安全组,那么这是一个困难的问题。如果是这样,那么必须解析 DependencyError,这可不是小事。

如果您只使用 IP 地址,那么在创建 boto3客户端之后,这个解决方案就可以工作了:

# pull all security groups from all vpcs in the given profile and region and save as a set
all_sgs = {sg['GroupId'] for sg in client.describe_security_groups()['SecurityGroups']}


# create a new set for all of the security groups that are currently in use
in_use = set()


# cycle through the ENIs and add all found security groups to the in_use set
for eni in client.describe_network_interfaces()['NetworkInterfaces']:
for group in eni['Groups']:
in_use.add(group['GroupId'])


unused_security_groups = all_sgs - in_use


for security_group in unused_security_groups:
try:
response = client.delete_security_group(GroupId=security_group)
except ClientError as e:
if e.response['Error']['Code'] == 'DependencyViolation':
print('EC2/Security Group Dependencies Exist')
else:
print('Unexpected error: {}'.format(e))

在其他功能中,童子军套房Prowler都报告未使用的 EC2安全组。

连接到网络接口的 SG:

姓名:

aws ec2 describe-network-interfaces --output text --query NetworkInterfaces[*].Groups[*].GroupName | tr -d '\r' | tr "\t" "\n" | sort | uniq

身份证:

aws ec2 describe-network-interfaces --output text --query NetworkInterfaces[*].Groups[*].GroupId | tr -d '\r' | tr "\t" "\n" | sort | uniq

我也在找同样的信息。 How to find all security groups that are not attached to any resource? And I found this: 使用 AWS 配置规则“ EC2 _ SECURITY _ GROUP _ ATTACHED _ TO _ ENI”,我得到了一个检查列表,检查非默认安全组是否附加到 Amazon EC2(EC2)实例或弹性网络接口(ENI)。如果安全组与 EC2实例或 ENI 没有关联,则该规则返回 NON _ COMPLIANT。

enter image description here

这是一个非常古老的问题,我相信还有更多的方法可以解决这个问题,但这里是我在 bash 中的解决方案(您需要 jq来实现这一点) :

REGION="eu-west-1"


SGLIST=$(aws ec2 describe-security-groups --query 'SecurityGroups[*].GroupId' | jq -r .[])


echo $SGLIST | xargs -n1 | while read SG; do  [ "$(aws ec2 describe-network-interfaces --filters Name=group-id,Values=$SG --region $REGION | jq .NetworkInterfaces)" != '[]' ] || echo $SG; done

请记住使用您正在使用的任何区域来替换 RegiION。 第一步是获取安全组列表。 然后我们检查每个安全组是否有一个与之关联的网络接口——这不限于 EC2实例,它检查任何具有网络接口的东西(LB、 RDS 等)。 参考资料见 here