Skip to content

Commit

Permalink
Bugfix/core 271 vpc with eni (#385)
Browse files Browse the repository at this point in the history
* wip-test vpc

* fix imports

* Delete ENI and Egress gateways before deleting VPCs

* tidy go mod

* dissociated dhcp options before deleting vpc

* fix deletion of test buckets
  • Loading branch information
Andrew Ellison authored Dec 14, 2022
1 parent 9694b5f commit a462d76
Show file tree
Hide file tree
Showing 7 changed files with 2,929 additions and 296 deletions.
2 changes: 1 addition & 1 deletion .circleci/nuke_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ s3:
- "^gruntwork-test-privs3bucket-[a-zA-Z0-9]{6}"
- "^gruntwork-terratest-[a-zA-Z0-9]{6}-logs"
- "^houston-static-[a-zA-Z0-9]{12}.*"
- "^.*-cloud-nuke-test-.*"
- "^.*cloud-nuke-test-.*"
- "^alb-alb-[a-zA-Z0-9]{6}-access-logs$"
- "^kafka-zk-standalone-[a-zA-Z0-9]{6}$"
- "^zookeeper-cluster-test-[a-zA-Z0-9]{6}$"
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The currently supported functionality includes:
- Inspecting and deleting all SQS queues in an AWS account
- Inspecting and deleting all S3 buckets in an AWS account - except for buckets tagged with Key=cloud-nuke-excluded Value=true
- Inspecting and deleting all default VPCs in an AWS account
- Deleting VPCs in an AWS Account (except for default VPCs which is handled by the dedicated `defaults-aws` subcommand)
- Deleting VPCs in an AWS Account (along with any dependency resources such as ENIs, Egress Only Gateways, and Security Groups. except for default VPCs which is handled by the dedicated `defaults-aws` subcommand)
- Inspecting and deleting all IAM users in an AWS account
- Inspecting and deleting all IAM roles (and any associated EC2 instance profiles) in an AWS account
- Inspecting and deleting all IAM groups in an AWS account
Expand Down Expand Up @@ -532,6 +532,25 @@ security group rules and not the default VPCs.
cloud-nuke defaults-aws --sg-only
```

## Note for nuking VPCs

When nuking VPCs cloud-nuke will attempt to remove dependency resources underneath the VPC

### Supported VPC sub-resources

- Internet Gateways
- Egress Only Internet Gateways
- Elastic Network Interfaces
- VPC Endpoints
- Subnets
- Route Tables
- Network ACLs
- Security Groups
- DHCP Option Sets (Will be dissociated from VPC, not deleted. Must be cleaned up separately)
- Elastic IPs (Supported as a separate resource that gets cleaned up first. If you are filtering what gets nuked, Elastic IPs may prevent VPCs from destroying.)

All other resources that get created within VPCs must be cleaned up prior to running cloud-nuke on VPC resources.

Happy Nuking!!!

## Credentials
Expand Down
86 changes: 86 additions & 0 deletions aws/ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package aws

import (
"fmt"
"github.com/hashicorp/go-multierror"
"time"

awsgo "github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -434,6 +435,73 @@ func (v Vpc) nukeEndpoints() error {
return nil
}

func (v Vpc) nukeEgressOnlyGateways() error {
allEgressGateways := []*string{}
logging.Logger.Debugf("Finding Egress Only Internet Gateways to Nuke")
err := v.svc.DescribeEgressOnlyInternetGatewaysPages(
&ec2.DescribeEgressOnlyInternetGatewaysInput{},
func(page *ec2.DescribeEgressOnlyInternetGatewaysOutput, lastPage bool) bool {
for _, gateway := range page.EgressOnlyInternetGateways {
for _, attachment := range gateway.Attachments {
if *attachment.VpcId == v.VpcId {
allEgressGateways = append(allEgressGateways, gateway.EgressOnlyInternetGatewayId)
break
}
}
}
return !lastPage
},
)
if err != nil {
return err
}
logging.Logger.Debugf("Found %d Egress Only Internet Gateways to Nuke.", len(allEgressGateways))

var allErrs *multierror.Error
for _, gateway := range allEgressGateways {
_, e := v.svc.DeleteEgressOnlyInternetGateway(&ec2.DeleteEgressOnlyInternetGatewayInput{EgressOnlyInternetGatewayId: gateway})
allErrs = multierror.Append(allErrs, e)
}
return errors.WithStackTrace(allErrs.ErrorOrNil())
}

func (v Vpc) nukeNetworkInterfaces() error {
allNetworkInterfaces := []*string{}
logging.Logger.Debugf("Finding Elastic Network Interfaces to Nuke")
vpcIds := []string{v.VpcId}
filters := []*ec2.Filter{{Name: awsgo.String("vpc-id"), Values: awsgo.StringSlice(vpcIds)}}
err := v.svc.DescribeNetworkInterfacesPages(
&ec2.DescribeNetworkInterfacesInput{
Filters: filters,
},
func(page *ec2.DescribeNetworkInterfacesOutput, lastPage bool) bool {
for _, netInterface := range page.NetworkInterfaces {
allNetworkInterfaces = append(allNetworkInterfaces, netInterface.NetworkInterfaceId)
}
return !lastPage
},
)
if err != nil {
return err
}
logging.Logger.Debugf("Found %d ELastic Network Interfaces to Nuke.", len(allNetworkInterfaces))

var allErrs *multierror.Error
for _, netInterface := range allNetworkInterfaces {
_, e := v.svc.DeleteNetworkInterface(&ec2.DeleteNetworkInterfaceInput{NetworkInterfaceId: netInterface})
allErrs = multierror.Append(allErrs, e)
}
return errors.WithStackTrace(allErrs.ErrorOrNil())
}

func (v Vpc) dissociateDhcpOptions() error {
_, err := v.svc.AssociateDhcpOptions(&ec2.AssociateDhcpOptionsInput{
DhcpOptionsId: awsgo.String("default"),
VpcId: awsgo.String(v.VpcId),
})
return err
}

func waitForVPCEndpointsToBeDeleted(v Vpc) error {
for i := 0; i < 30; i++ {
endpoints, err := v.svc.DescribeVpcEndpoints(
Expand Down Expand Up @@ -492,6 +560,18 @@ func (v Vpc) nuke() error {
return err
}

err = v.nukeEgressOnlyGateways()
if err != nil {
logging.Logger.Debugf("Error cleaning up Egress Only Internet Gateways for VPC %s: %s", v.VpcId, err.Error())
return err
}

err = v.nukeNetworkInterfaces()
if err != nil {
logging.Logger.Debugf("Error cleaning up Elastic Network Interfaces for VPC %s: %s", v.VpcId, err.Error())
return err
}

err = v.nukeEndpoints()
if err != nil {
logging.Logger.Debugf("Error cleaning up Endpoints for VPC %s: %s", v.VpcId, err.Error())
Expand Down Expand Up @@ -522,6 +602,12 @@ func (v Vpc) nuke() error {
return err
}

err = v.dissociateDhcpOptions()
if err != nil {
logging.Logger.Debugf("Error cleaning up DHCP Options for VPC %s: %s", v.VpcId, err.Error())
return err
}

err = v.nukeVpc()
if err != nil {
logging.Logger.Debugf("Error deleting VPC %s: %s ", v.VpcId, err)
Expand Down
111 changes: 107 additions & 4 deletions aws/ec2_vpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,58 @@ func createTestVpc(t *testing.T, session *session.Session) string {
return *vpc.Vpc.VpcId
}

func createTestVpcWithEgressGateway(t *testing.T, awsSession *session.Session) string {
testVpcId := createTestVpc(t, awsSession)
createTestEgressGateway(t, awsSession, testVpcId)
return testVpcId
}

func createTestEgressGateway(t *testing.T, awsSession *session.Session, vpcId string) string {
svc := ec2.New(awsSession)
egressGateway, err := svc.CreateEgressOnlyInternetGateway(&ec2.CreateEgressOnlyInternetGatewayInput{
VpcId: awsgo.String(vpcId),
})
require.NoError(t, err)
return *egressGateway.EgressOnlyInternetGateway.EgressOnlyInternetGatewayId
}

func createTestVpcWithNetworkInterface(t *testing.T, awsSession *session.Session) string {
testVpcId := createTestVpc(t, awsSession)
createTestNetworkInterface(t, awsSession, testVpcId)
return testVpcId
}

func createTestNetworkInterface(t *testing.T, awsSession *session.Session, vpcId string) string {
svc := ec2.New(awsSession)
subnet, err := svc.CreateSubnet(&ec2.CreateSubnetInput{
VpcId: awsgo.String(vpcId),
CidrBlock: awsgo.String("10.0.0.0/24"),
})
require.NoError(t, err)
netInterface, err := svc.CreateNetworkInterface(&ec2.CreateNetworkInterfaceInput{
SubnetId: subnet.Subnet.SubnetId,
})
require.NoError(t, err)
return *netInterface.NetworkInterface.NetworkInterfaceId
}

func TestCanTagVpc(t *testing.T) {
t.Parallel()

region, err := getRandomRegion()
require.NoError(t, err)

session, err := session.NewSession(&awsgo.Config{
awsSession, err := session.NewSession(&awsgo.Config{
Region: awsgo.String(region)},
)

require.NoError(t, err)

vpcId := createTestVpc(t, session)
svc := ec2.New(session)
vpcId := createTestVpc(t, awsSession)
svc := ec2.New(awsSession)

// clean up after this test
defer nukeAllVPCs(session, []string{vpcId}, []Vpc{{
defer nukeAllVPCs(awsSession, []string{vpcId}, []Vpc{{
Region: region,
VpcId: vpcId,
svc: svc,
Expand Down Expand Up @@ -150,6 +185,74 @@ func TestNukeVpcs(t *testing.T) {
assert.NotContains(t, awsgo.StringValueSlice(vpcIds), vpcId)
}

func TestNukeVpcsWithEgressGateway(t *testing.T) {
t.Parallel()

region, err := getRandomRegion()
require.NoError(t, err)

awsSession, err := session.NewSession(&awsgo.Config{
Region: awsgo.String(region)},
)

require.NoError(t, err)

vpcId := createTestVpcWithEgressGateway(t, awsSession)

// clean up after this test
err = nukeAllVPCs(awsSession, []string{vpcId}, []Vpc{{
Region: region,
VpcId: vpcId,
svc: ec2.New(awsSession),
}})

require.NoError(t, err)

// First run gives us a chance to tag the VPC
_, _, err = getAllVpcs(awsSession, region, time.Now().Add(1*time.Hour), config.Config{})
require.NoError(t, err)

// VPC should be tagged at this point
vpcIds, _, err := getAllVpcs(awsSession, region, time.Now().Add(1*time.Hour), config.Config{})
require.NoError(t, err)

assert.NotContains(t, awsgo.StringValueSlice(vpcIds), vpcId)
}

func TestNukeVpcsWithNetworkInterface(t *testing.T) {
t.Parallel()

region, err := getRandomRegion()
require.NoError(t, err)

awsSession, err := session.NewSession(&awsgo.Config{
Region: awsgo.String(region)},
)

require.NoError(t, err)

vpcId := createTestVpcWithNetworkInterface(t, awsSession)

// clean up after this test
err = nukeAllVPCs(awsSession, []string{vpcId}, []Vpc{{
Region: region,
VpcId: vpcId,
svc: ec2.New(awsSession),
}})

require.NoError(t, err)

// First run gives us a chance to tag the VPC
_, _, err = getAllVpcs(awsSession, region, time.Now().Add(1*time.Hour), config.Config{})
require.NoError(t, err)

// VPC should be tagged at this point
vpcIds, _, err := getAllVpcs(awsSession, region, time.Now().Add(1*time.Hour), config.Config{})
require.NoError(t, err)

assert.NotContains(t, awsgo.StringValueSlice(vpcIds), vpcId)
}

// Test config file filtering works as expected
func TestShouldIncludeVpc(t *testing.T) {

Expand Down
Loading

0 comments on commit a462d76

Please sign in to comment.