Como encontrar grupos de segurança não utilizados do Amazon EC2

93

Estou tentando encontrar uma maneira de determinar os grupos de segurança órfãos para poder limpá-los e me livrar deles. Alguém conhece uma maneira de descobrir grupos de segurança não utilizados.

Tanto através do console quanto com as ferramentas de linha de comando funcionarão (rodando ferramentas de linha de comando em máquinas Linux e OSX).

Raio
fonte
3
Meu Reino para uma resposta que responda totalmente a esta pergunta, sem exceções para objetos de longa duração não-Instância (RDS, ELBs, ALBs) que podem ter SGs atribuídos a eles e não envolvem 'selecionar todos e excluir' fim de semana assustador -abordagem destruidora. :)
Jesse Adelman

Respostas:

77

Observação: isso considera apenas o uso de segurança no EC2, não em outros serviços como RDS. Você precisará trabalhar mais para incluir grupos de segurança usados ​​fora do EC2. O bom é que você não pode facilmente (pode não ser possível) excluir grupos de segurança ativos se perder um associado com outro serviço.

Usando a ferramenta AWS CLI mais recente, descobri uma maneira fácil de conseguir o que preciso:

Primeiro, obtenha uma lista de todos os grupos de segurança

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

Em seguida, obter todos os grupos de segurança ligados a uma instância, então canalizada para sort, em seguida uniq:

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

Em seguida, reúna e compare as 2 listas e veja o que não está sendo usado na lista principal:

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)
Raio
fonte
1
@Erik Sim, tenho apenas uma única região e os scripts da AWS têm sua região inicial definida por meio de variáveis ​​ambientais. Eu estaria interessado em ver uma versão multirregional deste script.
Ray
1
você pode querer adicionar um --filter para seu vpc para que você não precise ver outro vpc sg
shadowbq
2
Um grupo de segurança também pode estar em uso por um ELB. Este comando listará o conjunto uniq de IDs de grupo de segurança referenciados por ELBs na região padrão:aws elb describe-load-balancers --query 'LoadBalancerDescriptions[*].SecurityGroups[*]' --output text | tr '\t' '\n' | sort | uniq
astletron
2
Um grupo de segurança EC2 também pode estar em uso por uma instância RDS. Este comando listará os IDs do grupo de segurança usados ​​por instâncias RDS na região padrão:aws rds describe-db-security-groups --query 'DBSecurityGroups[*].EC2SecurityGroups[*].EC2SecurityGroupId' --output text | tr '\t' '\n' | sort | uniq
aharden
2
Você também pode usar aws ec2 describe-network-interfaces --query 'NetworkInterfaces[*].Groups[*].GroupId' --output text| tr '\t' '\n' | sort | uniqapenas para descrever as interfaces de rede.
Jonathan
61

Se você selecionar todos os seus grupos de segurança no console EC2, pressione ações -> Excluir grupos de segurança, um pop-up aparecerá informando que você não pode excluir grupos de segurança que estão anexados a instâncias, outros grupos de segurança ou interfaces de rede, e irá listar os grupos de segurança que você pode excluir; ou seja, os grupos de segurança não utilizados :)

NLail
fonte
15
Embora eu deva concordar, usar "selecionar tudo + excluir" geralmente não é um bom hábito.
Balmipour de
3
Se você não tiver certeza se funcionará, pode simplesmente criar um grupo de segurança fictício e anexar algo a ele, tente excluí-lo e veja se isso não vai permitir.
NLail
2
Você não precisa realmente confirmar a exclusão, no pop-up ele mostrará uma lista de quais podem ser excluídos (órfãos) e quais não podem. Você pode clicar em cancelar e excluir os órfãos.
rjarmstrong
4
O que não entendo é o seguinte: se o console da AWS pode oferecer essas informações quando você faz essa manobra assustadora, por que eles não compartilham como fazer a mesma coisa por meio da API? Não é assim, não é algo que provavelmente seja necessário em ambientes de campo marrom ...
Jesse Adelman
1
seja corajoso :: faça isso
zanuka
29

Este é o código de amostra escrito em boto (Python SDK para AWS) para listar o Grupo de Segurança em relação ao número de instâncias ao qual está associado.

Você pode usar esta lógica para obter o mesmo na linha de comando também

Boto Code

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

Resultado

Security-Group-1 0
Security-Group-2 1
Security-Group-3 0
Security-Group-4 3
Naveen Vijay
fonte
Legal e fácil! Obrigado
Chris Koston de
6
bem, sim, mas e os cotovelos?
Ilja
Observe também que isso inclui apenas instâncias em execução. Você também não pode excluir um SG que esteja vinculado a uma instância interrompida.
AgDude
6
Isso ignora interfaces de serviços como RDS. RDS possui a instância, mas você possui o ENI. Acho que ElasticSearch e ELB funcionam de forma semelhante e não apareceriam com este script
rajat banerjee
6

Após cerca de um ano de uso não auditado, achei necessário auditar meus grupos de segurança do AWS EC2 e limpar os grupos legados não usados.

Essa era uma tarefa difícil de realizar por meio da GUI da web, então olhei para o AWS CLI para tornar a tarefa mais fácil. Descobri como fazer isso no StackOverflow, mas estava longe de ser concluído. Então decidi escrever meu próprio roteiro. Usei o AWS CLI, MySQL e algum “Bash-foo” para realizar o seguinte:

  1. Obtenha uma lista de todos os grupos de segurança EC2. Eu armazeno a id do grupo, nome do grupo e descrição em uma tabela chamada “grupos” em um banco de dados MySQL chamado aws_security_groups no localhost. O número total de grupos encontrados é informado ao usuário.

  2. Obtenha uma lista de todos os grupos de segurança associados a cada um dos seguintes serviços e exclua-os da tabela: EC2 Istances EC2 Elastic Load Balancers AWS RDS Instances AWS OpsWorks (não deve ser removido por Amazon) Grupos de segurança padrão (não podem ser excluídos ) ElastiCache

Para cada serviço, relato uma contagem do número de grupos restantes na tabela após a conclusão da exclusão.

  1. Por fim, exibo o id do grupo, o nome do grupo e a descrição dos grupos restantes. Esses são os grupos “não usados” que precisam ser auditados e / ou excluídos. Descobri que SGs entre instâncias e Elastic Load Balancers (ELBs) geralmente se referem um ao outro. É uma prática recomendada fazer alguma investigação manual para garantir que eles realmente não estejam em uso antes de remover as referências cruzadas e excluir os grupos de segurança. Mas meu script pelo menos reduz isso a algo mais gerenciável.

NOTAS: 1. Você desejará criar um arquivo para armazenar seu host MySQL, nome de usuário e senha e apontar a variável $ DBCONFIG para ele. Deve ser estruturado assim:

[mysql]
host=your-mysql-server-host.com
user=your-mysql-user
password=your-mysql-user-password
  1. Você pode alterar o nome do banco de dados se desejar - certifique-se de alterar a variável $ DB no script

Deixe-me saber se você achar isso útil ou se tiver algum comentário, correção ou aprimoramento.

Aqui está o roteiro.

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

E aqui está o sql para criar o banco de dados.

-- 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
user2962402
fonte
3

Um exemplo de boto imprimindo os IDs e nomes dos grupos apenas dos grupos de segurança que não possuem instâncias atuais.

Também mostra como especificar com qual região você está preocupado.

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

Para confirmar quais grupos de segurança ainda estão sendo usados, você deve reverter ou remover o if len(sg.instances()) == 0teste e imprimir o len(sg.instances())valor.

Por exemplo

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

Usando o node.js AWS SDK, posso confirmar que a AWS não permite que você exclua grupos de segurança que estão em uso. Escrevi um script que simplesmente tenta excluir todos os grupos e trata os erros de maneira elegante. Isso funciona para o VPC clássico e moderno. A mensagem de erro pode ser vista abaixo.

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 }
Michael Connor
fonte
2

Entre outras funções, tanto ScoutSuite quanto Prowler relatam grupos de segurança EC2 não utilizados. Ambos são de código aberto.

Grande abóbora
fonte
1

Para os SGs anexados às interfaces de rede:

Por nome:

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

Por id:

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

Existe uma ferramenta no mercado da AWS que torna isso muito mais fácil. Ele mostra quais grupos estão anexados / desanexados para fácil exclusão, mas também compara seus registros de fluxo de VPC com as regras do grupo de segurança e mostra quais regras SG estão em uso ou não. A AWS postou uma solução ELK-stack para fazer isso, mas era ridiculamente complexa.

Aqui está a ferramenta e um aviso de que trabalhei nela. Mas espero que todos vocês achem pertinente: https://www.piasoftware.net/single-post/2018/04/24/VIDEO-Watch-as-we-clean-up-EC2-security-groups-in-just -alguns minutos

rajat banerjee
fonte
0

Infelizmente, a resposta escolhida não é tão precisa quanto preciso (tentei investigar o porquê, mas preferi implementá-la).
Se eu verificar TODOS NetworkInterfaces, procurando por anexos em algum SecurityGroup, obterei resultados parciais. Se eu verificar apenas em EC2Instances, também obterei resultados parciais.

Essa é a minha abordagem ao problema:

  1. Recebo TODOS os grupos de segurança EC2 -> all_secgrp
  2. Recebo TODAS as instâncias EC2 -> all_instances
  3. Para cada instância, recebo todos os SecurityGroups anexados a ela
    1. Eu removo de all_secgrp cada um desses SecurityGroup (porque anexado)
  4. Para cada SecurityGroup, eu verifico uma associação com qualquer NetworkInterfaces (usando a filterfunção e filtrando usando isso security-group-id)
    1. SE nenhuma associação for encontrada, eu removo o grupo de segurança de all_secgrp

Em anexo, você pode ver um trecho de código. Não reclame de eficiência, mas tente otimizá-la se quiser.

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  
Echoes_86
fonte
0

Este é um problema difícil, se você tiver grupos de segurança que fazem referência a outros grupos de segurança nas regras. Nesse caso, você terá que resolver DependencyErrors, o que não é trivial.

Se você estiver usando apenas endereços IP, esta solução funcionará depois de criar um cliente 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))
comer comida
fonte
Isso não cobrirá os SGs usados ​​pelo RDS
alexandernst