如何查找未使用的Amazon EC2安全组

123

我正在尝试找到一种方法来确定孤立的安全组,以便我可以清理并摆脱它们。有人知道发现未使用安全组的方法吗?

无论是通过控制台还是使用命令行工具都可以(在Linux和OSX机器上运行命令行工具)。


1
为了清理未使用的安全组,请前往EC2安全组控制台,选择所有安全组并执行删除操作,这样它将显示未使用的安全组并仅删除那些安全组。 - Akhil
14个回答

105
注意:这仅考虑EC2中的安全使用,不包括其他服务(如RDS)。如果您需要包含在EC2之外使用的安全组,则需要进行更多工作。好消息是,如果您错过了与另一个服务相关联的安全组,则很难(甚至可能不可能)删除活动安全组。
使用较新的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)

2
@Erik 是的,我只有一个区域,AWS脚本通过环境变量设置它们的主区域。我很想看到这个脚本的多区域版本。 - Ray
8
一些安全组也可能被负载均衡器使用。以下命令将列出默认区域内由ELBs引用的唯一安全组ID集合:aws elb describe-load-balancers --query 'LoadBalancerDescriptions[*].SecurityGroups[*]' --output text | tr '\t' '\n' | sort | uniq - astletron
7
EC2安全组也可以被RDS实例使用。该命令将列出默认区域中RDS实例所使用的安全组ID:aws rds describe-db-security-groups --query 'DBSecurityGroups[*].EC2SecurityGroups[*].EC2SecurityGroupId' --output text | tr '\t' '\n' | sort | uniq - aharden
2
您还可以使用 aws ec2 describe-network-interfaces --query 'NetworkInterfaces[*].Groups[*].GroupId' --output text| tr '\t' '\n' | sort | uniq 来仅描述网络接口。 - Jonathan
3
好的回答。翻译提示:将最后一个元素放在括号内,使用文本输出时每行得到一个值,无需使用tr命令。例如,--query 'SecurityGroups[].[GroupId]'--query 'SecurityGroups[].GroupId'相比。 - bimsapi
显示剩余5条评论

73
如果您在EC2控制台中选择所有安全组,然后按操作->删除安全组,则会弹出一个窗口告诉您不能删除附加到实例、其他安全组或网络接口的安全组,并且它将列出可以删除的安全组;即未使用的安全组。
注意:根据@andrewlorien的评论,这并不适用于所有类型的AWS服务。

33
我必须承认,“全选+删除”的使用通常不是一个好习惯。 - Balmipour
3
如果你不确定它是否有效,你可以创建一个虚拟安全组并将其附加到某个东西上,尝试删除它,看看它是否会阻止你这样做。 - NLail
2
您不需要实际确认删除,在弹出窗口中,它会向您显示哪些可以被删除(孤立)和哪些不能。然后您可以按取消,再删除孤立的那些。 - rjarmstrong
8
我不明白的是:如果使用AWS控制台可以在执行某个“可怕的”操作时提供这些信息,为什么他们不通过API分享如何执行相同的操作呢?这似乎是在既有环境中经常需要的事情。 - Jesse Adelman
4
另外,不是所有的AWS服务都提供此类警告。我不会告诉你哪些服务没有这个功能,但如果你假设每次单击删除按钮时都会收到警告,有一天你可能会让生产站点下线(就像我上个月所做的那样)。 - andrew lorien
显示剩余3条评论

34

这是使用boto(AWS的Python SDK)编写的示例代码,用于列出与之关联的实例数量的安全组。

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

Boto代码

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

6
好的,但是Elbs呢? - Ilja
请注意,这仅包括运行中的实例。您也无法删除与已停止实例相关联的安全组。 - AgDude
6
这忽略了来自像RDS这样的服务的接口。RDS拥有实例,但您拥有ENI。我认为ElasticSearch和ELB的工作方式也类似,并且不会在此脚本中显示。 - rajat banerjee
1
如果安全组被任何ECS服务使用,该怎么办? - Jananath Banuka

9

除其他功能外,ScoutSuiteProwler都会报告未使用的EC2安全组。这两者均为开源软件。


两者都似乎很难使用。Prowler为我生成了一个包含162个不同地区的安全组列表,但它说只有一个地区使用了6个安全组,这是不正确的。我无法让ScoutSuite运行单个规则,因为文档中说要克隆一个现有的规则集,但没有说明在哪里找到它。 - undefined

8

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

这是一个艰巨的任务,通过Web GUI执行,因此我寻找了AWS CLI来使任务更容易。我在StackOverflow上找到了如何做到这一点的起点,但还远远不够完整。所以我决定编写自己的脚本。我使用AWS CLI、MySQL和一些“Bash-foo”来执行以下操作:

  1. 获取所有EC2安全组的列表。 我将group-id、group-name和description存储在名为aws_security_groups的MySQL数据库中的名为“groups”的表中,该数据库位于localhost上。向用户报告找到的组的总数。

  2. 获取与以下每个服务相关联的所有安全组列表,并将其排除在表外: EC2 Istances EC2 Elastic Load Balancers AWS RDS Instances AWS OpsWorks(根据亚马逊的说法不应该被删除) 默认安全组(无法删除) ElastiCache

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

  1. 最后,我显示剩下的组的group-id、group-name和description。这些是需要进行审计和/或删除的“未使用”的组。我发现实例和弹性负载均衡器(ELB)之间的SG通常相互引用。最好在删除交叉引用和安全组之前进行一些手动调查,以确保它们真正没有使用。但是我的脚本至少将其缩小为更可管理的范围。

注: 1. 您需要创建一个文件来存储MySQL主机、用户名和密码,并将$DBCONFIG变量指向它。它应该像这样结构化:

[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

3

一个 Boto 示例,仅打印当前未有实例的安全组的组 ID 和名称。

它还展示了如何指定您所关注的区域。

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

3
使用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 }

2

这是一个非常老的问题,我相信有更多方法来解决AWS这个问题,但这里是我的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

请记得将REGION替换为您正在使用的区域。 第一步是获取安全组列表。 然后,我们检查每个安全组是否有与之关联的网络接口 - 这不仅限于EC2实例,还检查任何具有网络接口的内容(负载均衡器、RDS等)。 参考此处


2

对于附加到网络接口的SG:

按名称:

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

By id:

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

2
我正在寻找相同的信息。如何查找未附加到任何资源的所有安全组?我发现了这个:使用 AWS 配置规则“EC2_SECURITY_GROUP_ATTACHED_TO_ENI”,我得到了一个检查列表,其中非默认安全组附加到 Amazon Elastic Compute Cloud (EC2) 实例或弹性网络接口 (ENI)。如果安全组未与 EC2 实例或 ENI 关联,则规则返回 NON_COMPLIANT。

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接