Created
June 12, 2025 15:43
-
-
Save sebassdc/cf7ee579c1a30c1961d8286d49f4ee43 to your computer and use it in GitHub Desktop.
AWS VPC nuke script (nuke-vpc.sh)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Bash-3-friendly VPC-nuker with SG-reference discovery | |
set -euo pipefail | |
export AWS_PAGER="" | |
if [[ $# -ne 1 ]]; then | |
echo "Usage: $0 <vpc-id>" >&2 | |
exit 1 | |
fi | |
VPC_ID="$1" | |
echo "π¨ Attempting to delete VPC $VPC_ID β¦" | |
if aws ec2 delete-vpc --vpc-id "$VPC_ID" --no-cli-pager --output text 2>/dev/null; then | |
echo "β VPC $VPC_ID deleted successfully." | |
exit 0 | |
fi | |
echo "β οΈ delete-vpc failed β dependencies remain." | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
# Gather blockers | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
IGWS=$(aws ec2 describe-internet-gateways \ | |
--filters Name=attachment.vpc-id,Values="$VPC_ID" \ | |
--query 'InternetGateways[].InternetGatewayId' --output text) | |
NAT_GWS=$(aws ec2 describe-nat-gateways \ | |
--filter Name=vpc-id,Values="$VPC_ID" \ | |
--query 'NatGateways[].NatGatewayId' --output text) | |
ENDPOINTS=$(aws ec2 describe-vpc-endpoints \ | |
--filters Name=vpc-id,Values="$VPC_ID" \ | |
--query 'VpcEndpoints[].VpcEndpointId' --output text) | |
RTBLS=$(aws ec2 describe-route-tables \ | |
--filters Name=vpc-id,Values="$VPC_ID" \ | |
--query 'RouteTables[?Associations[?Main==`false`]].RouteTableId' \ | |
--output text) | |
SUBNETS=$(aws ec2 describe-subnets \ | |
--filters Name=vpc-id,Values="$VPC_ID" \ | |
--query 'Subnets[].SubnetId' --output text) | |
# All SGs in the VPC, *including* the default one (needed for reference checks) | |
ALL_SGS=$(aws ec2 describe-security-groups \ | |
--filters Name=vpc-id,Values="$VPC_ID" \ | |
--query 'SecurityGroups[].GroupId' --output text) | |
# Non-default SGs (the only ones you can delete) | |
NON_DEFAULT_SGS=$(aws ec2 describe-security-groups \ | |
--filters Name=vpc-id,Values="$VPC_ID" \ | |
--query 'SecurityGroups[?GroupName!=`default`].GroupId' \ | |
--output text) | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
# Print dependency tree | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
echo | |
echo "π§© Dependency tree for $VPC_ID" | |
echo "$VPC_ID" | |
# Internet Gateways | |
if [[ -n "$IGWS" ]]; then | |
echo "βββ¬ InternetGateways" | |
for igw in $IGWS; do echo " βββ $igw"; done | |
fi | |
# NAT Gateways | |
if [[ -n "$NAT_GWS" ]]; then | |
echo "βββ¬ NatGateways" | |
for nat in $NAT_GWS; do echo " βββ $nat"; done | |
fi | |
# VPC Endpoints | |
if [[ -n "$ENDPOINTS" ]]; then | |
echo "βββ¬ VpcEndpoints" | |
for vpce in $ENDPOINTS; do echo " βββ $vpce"; done | |
fi | |
# Route Tables | |
if [[ -n "$RTBLS" ]]; then | |
echo "βββ¬ RouteTables (non-main)" | |
for rt in $RTBLS; do echo " βββ $rt"; done | |
fi | |
# Subnets β ENIs | |
if [[ -n "$SUBNETS" ]]; then | |
echo "βββ¬ Subnets" | |
for s in $SUBNETS; do | |
echo " βββ¬ $s" | |
ENIS=$(aws ec2 describe-network-interfaces \ | |
--filters Name=subnet-id,Values="$s" \ | |
--query 'NetworkInterfaces[].NetworkInterfaceId' --output text) | |
if [[ -n "$ENIS" ]]; then | |
for eni in $ENIS; do echo " βββ ENI: $eni"; done | |
else | |
echo " βββ (no ENIs)" | |
fi | |
done | |
fi | |
# Security Groups β ENIs + Referenced-By | |
if [[ -n "$NON_DEFAULT_SGS" ]]; then | |
echo "βββ¬ SecurityGroups (non-default)" | |
for sg in $NON_DEFAULT_SGS; do | |
echo " βββ¬ $sg" | |
# Attached ENIs | |
SG_ENIS=$(aws ec2 describe-network-interfaces \ | |
--filters Name=group-id,Values="$sg" \ | |
--query 'NetworkInterfaces[].NetworkInterfaceId' --output text) | |
if [[ -n "$SG_ENIS" ]]; then | |
for eni in $SG_ENIS; do echo " β βββ ENI: $eni"; done | |
else | |
echo " β βββ (no attached ENIs)" | |
fi | |
# Other SGs whose rules reference this SG (ingress + egress) | |
REF_ING=$(aws ec2 describe-security-groups \ | |
--filters Name=ip-permission.group-id,Values="$sg" \ | |
--query 'SecurityGroups[].GroupId' --output text) | |
REF_EGR=$(aws ec2 describe-security-groups \ | |
--filters Name=egress.ip-permission.group-id,Values="$sg" \ | |
--query 'SecurityGroups[].GroupId' --output text) | |
REFS=$(echo "$REF_ING $REF_EGR" | tr ' ' '\n' | grep -v "^$" | sort -u) | |
if [[ -n "$REFS" ]]; then | |
echo " β βββ¬ ReferencedBy" | |
for ref in $REFS; do | |
# Show whether the reference is ingress/egress/both | |
ING=$(echo "$REF_ING" | grep -q "$ref" && echo "ingress") | |
EGR=$(echo "$REF_EGR" | grep -q "$ref" && echo "${ING:+,}egress") | |
[[ -z "$EGR" ]] && [[ -n "$ING" ]] && EGR="$ING" | |
echo " β βββ $ref ($EGR)" | |
done | |
fi | |
done | |
fi | |
echo | |
cat <<EOF | |
π‘ To fully delete the VPC: | |
β’ Detach / delete each resource above (including SG references). | |
β’ Then rerun: aws ec2 delete-vpc --vpc-id $VPC_ID --no-cli-pager | |
Tip for revoking SG references: | |
aws ec2 describe-security-group-rules \\ | |
--filters Name=group-id,Values=<referencing-sg> \\ | |
--query 'SecurityGroupRules[?ReferencedGroupInfo && ReferencedGroupInfo.GroupId==\`<blocked-sg>\`].SecurityGroupRuleId' \\ | |
--output text --no-cli-pager | \\ | |
xargs -I{} aws ec2 revoke-security-group-ingress \\ | |
--group-id <referencing-sg> --security-group-rule-ids {} --no-cli-pager | |
EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment