-
-
Notifications
You must be signed in to change notification settings - Fork 356
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement egress only internet gateway nuke (#657)
- Loading branch information
1 parent
2e34b83
commit 3c3d3fb
Showing
7 changed files
with
385 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package resources | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/service/ec2" | ||
"github.com/gruntwork-io/cloud-nuke/config" | ||
"github.com/gruntwork-io/cloud-nuke/logging" | ||
"github.com/gruntwork-io/cloud-nuke/report" | ||
"github.com/gruntwork-io/cloud-nuke/util" | ||
"github.com/gruntwork-io/go-commons/errors" | ||
) | ||
|
||
func (egigw *EgressOnlyInternetGateway) setFirstSeenTag(eoig ec2.EgressOnlyInternetGateway, value time.Time) error { | ||
_, err := egigw.Client.CreateTags(&ec2.CreateTagsInput{ | ||
Resources: []*string{eoig.EgressOnlyInternetGatewayId}, | ||
Tags: []*ec2.Tag{ | ||
{ | ||
Key: aws.String(util.FirstSeenTagKey), | ||
Value: aws.String(util.FormatTimestamp(value)), | ||
}, | ||
}, | ||
}) | ||
if err != nil { | ||
return errors.WithStackTrace(err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (egigw *EgressOnlyInternetGateway) getFirstSeenTag(eoig ec2.EgressOnlyInternetGateway) (*time.Time, error) { | ||
tags := eoig.Tags | ||
for _, tag := range tags { | ||
if util.IsFirstSeenTag(tag.Key) { | ||
firstSeenTime, err := util.ParseTimestamp(tag.Value) | ||
if err != nil { | ||
return nil, errors.WithStackTrace(err) | ||
} | ||
|
||
return firstSeenTime, nil | ||
} | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
func shouldIncludeEgressOnlyInternetGateway(gateway *ec2.EgressOnlyInternetGateway, firstSeenTime *time.Time, configObj config.Config) bool { | ||
var gatewayName string | ||
// get the tags as map | ||
tagMap := util.ConvertEC2TagsToMap(gateway.Tags) | ||
if name, ok := tagMap["Name"]; ok { | ||
gatewayName = name | ||
} | ||
return configObj.EgressOnlyInternetGateway.ShouldInclude(config.ResourceValue{ | ||
Name: &gatewayName, | ||
Tags: tagMap, | ||
Time: firstSeenTime, | ||
}) | ||
} | ||
|
||
func (egigw *EgressOnlyInternetGateway) getAll(_ context.Context, configObj config.Config) ([]*string, error) { | ||
var result []*string | ||
|
||
output, err := egigw.Client.DescribeEgressOnlyInternetGateways(&ec2.DescribeEgressOnlyInternetGatewaysInput{}) | ||
if err != nil { | ||
return nil, errors.WithStackTrace(err) | ||
} | ||
|
||
for _, igw := range output.EgressOnlyInternetGateways { | ||
// check first seen tag | ||
firstSeenTime, err := egigw.getFirstSeenTag(*igw) | ||
if err != nil { | ||
logging.Errorf( | ||
"Unable to retrieve tags for Egress IGW: %s, with error: %s", *igw.EgressOnlyInternetGatewayId, err) | ||
continue | ||
} | ||
|
||
// if the first seen tag is not there, then create one | ||
if firstSeenTime == nil { | ||
now := time.Now().UTC() | ||
firstSeenTime = &now | ||
if err := egigw.setFirstSeenTag(*igw, time.Now().UTC()); err != nil { | ||
logging.Errorf( | ||
"Unable to apply first seen tag Egress IGW: %s, with error: %s", *igw.EgressOnlyInternetGatewayId, err) | ||
continue | ||
} | ||
} | ||
|
||
if shouldIncludeEgressOnlyInternetGateway(igw, firstSeenTime, configObj) { | ||
result = append(result, igw.EgressOnlyInternetGatewayId) | ||
} | ||
} | ||
|
||
// checking the nukable permissions | ||
egigw.VerifyNukablePermissions(result, func(id *string) error { | ||
_, err := egigw.Client.DeleteEgressOnlyInternetGateway(&ec2.DeleteEgressOnlyInternetGatewayInput{ | ||
EgressOnlyInternetGatewayId: id, | ||
DryRun: aws.Bool(true), | ||
}) | ||
return err | ||
}) | ||
|
||
return result, nil | ||
} | ||
|
||
func (egigw *EgressOnlyInternetGateway) nukeAll(ids []*string) error { | ||
if len(ids) == 0 { | ||
logging.Debugf("No Egress only internet gateway ID's to nuke in region %s", egigw.Region) | ||
return nil | ||
} | ||
|
||
logging.Debugf("Deleting all Egress only internet gateway in region %s", egigw.Region) | ||
var deletedList []*string | ||
|
||
for _, id := range ids { | ||
// NOTE : We can skip the error checking and return it here, since it is already being checked while displaying the identifiers with the Nukable field. | ||
// Here, `err` refers to the error indicating whether the identifier is eligible for nuke or not (an error which we got from aws when tried to delete the resource with dryRun), | ||
// and it is not a programming error. (edited) | ||
if nukable, err := egigw.IsNukable(*id); !nukable { | ||
logging.Debugf("[Skipping] %s nuke because %v", *id, err) | ||
continue | ||
} | ||
|
||
_, err := egigw.Client.DeleteEgressOnlyInternetGateway(&ec2.DeleteEgressOnlyInternetGatewayInput{ | ||
EgressOnlyInternetGatewayId: id, | ||
}) | ||
|
||
// Record status of this resource | ||
e := report.Entry{ | ||
Identifier: aws.StringValue(id), | ||
ResourceType: "Egress Only Internet Gateway", | ||
Error: err, | ||
} | ||
report.Record(e) | ||
|
||
if err != nil { | ||
logging.Debugf("[Failed] %s", err) | ||
} else { | ||
deletedList = append(deletedList, id) | ||
logging.Debugf("Deleted egress only internet gateway: %s", *id) | ||
} | ||
} | ||
|
||
logging.Debugf("[OK] %d Egress only internet gateway(s) deleted in %s", len(deletedList), egigw.Region) | ||
|
||
return nil | ||
|
||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
package resources | ||
|
||
import ( | ||
"context" | ||
"regexp" | ||
"testing" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/service/ec2" | ||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface" | ||
"github.com/gruntwork-io/cloud-nuke/config" | ||
"github.com/gruntwork-io/cloud-nuke/telemetry" | ||
"github.com/gruntwork-io/cloud-nuke/util" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type mockedEgressOnlyIgw struct { | ||
BaseAwsResource | ||
ec2iface.EC2API | ||
DescribeEgressOnlyInternetGatewaysOutput ec2.DescribeEgressOnlyInternetGatewaysOutput | ||
DeleteEgressOnlyInternetGatewayOutput ec2.DeleteEgressOnlyInternetGatewayOutput | ||
} | ||
|
||
func (m mockedEgressOnlyIgw) DescribeEgressOnlyInternetGateways(_ *ec2.DescribeEgressOnlyInternetGatewaysInput) (*ec2.DescribeEgressOnlyInternetGatewaysOutput, error) { | ||
return &m.DescribeEgressOnlyInternetGatewaysOutput, nil | ||
} | ||
|
||
func (m mockedEgressOnlyIgw) DeleteEgressOnlyInternetGateway(_ *ec2.DeleteEgressOnlyInternetGatewayInput) (*ec2.DeleteEgressOnlyInternetGatewayOutput, error) { | ||
return &m.DeleteEgressOnlyInternetGatewayOutput, nil | ||
} | ||
|
||
func TestEgressOnlyInternetGateway_GetAll(t *testing.T) { | ||
telemetry.InitTelemetry("cloud-nuke", "") | ||
t.Parallel() | ||
|
||
var ( | ||
now = time.Now() | ||
gateway1 = "igw-0b44cfa6103932e1d001" | ||
gateway2 = "igw-0b44cfa6103932e1d002" | ||
|
||
testName1 = "cloud-nuke-igw-001" | ||
testName2 = "cloud-nuke-igw-002" | ||
) | ||
object := EgressOnlyInternetGateway{ | ||
Client: mockedEgressOnlyIgw{ | ||
DescribeEgressOnlyInternetGatewaysOutput: ec2.DescribeEgressOnlyInternetGatewaysOutput{ | ||
EgressOnlyInternetGateways: []*ec2.EgressOnlyInternetGateway{ | ||
{ | ||
EgressOnlyInternetGatewayId: aws.String(gateway1), | ||
Tags: []*ec2.Tag{ | ||
{ | ||
Key: aws.String("Name"), | ||
Value: aws.String(testName1), | ||
}, { | ||
Key: aws.String(util.FirstSeenTagKey), | ||
Value: aws.String(util.FormatTimestamp(now)), | ||
}, | ||
}, | ||
}, | ||
{ | ||
EgressOnlyInternetGatewayId: aws.String(gateway2), | ||
Tags: []*ec2.Tag{ | ||
{ | ||
Key: aws.String("Name"), | ||
Value: aws.String(testName2), | ||
}, { | ||
Key: aws.String(util.FirstSeenTagKey), | ||
Value: aws.String(util.FormatTimestamp(now.Add(1 * time.Hour))), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
object.BaseAwsResource.Init(nil) | ||
|
||
tests := map[string]struct { | ||
configObj config.ResourceType | ||
expected []string | ||
}{ | ||
"emptyFilter": { | ||
configObj: config.ResourceType{}, | ||
expected: []string{gateway1, gateway2}, | ||
}, | ||
"nameExclusionFilter": { | ||
configObj: config.ResourceType{ | ||
ExcludeRule: config.FilterRule{ | ||
NamesRegExp: []config.Expression{{ | ||
RE: *regexp.MustCompile(testName1), | ||
}}}, | ||
}, | ||
expected: []string{gateway2}, | ||
}, | ||
"timeAfterExclusionFilter": { | ||
configObj: config.ResourceType{ | ||
ExcludeRule: config.FilterRule{ | ||
TimeAfter: aws.Time(now), | ||
}}, | ||
expected: []string{gateway1}, | ||
}, | ||
"timeBeforeExclusionFilter": { | ||
configObj: config.ResourceType{ | ||
ExcludeRule: config.FilterRule{ | ||
TimeBefore: aws.Time(now.Add(1)), | ||
}}, | ||
expected: []string{gateway2}, | ||
}, | ||
} | ||
for name, tc := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
t.Parallel() | ||
names, err := object.getAll(context.Background(), config.Config{ | ||
EgressOnlyInternetGateway: tc.configObj, | ||
}) | ||
require.NoError(t, err) | ||
require.Equal(t, tc.expected, aws.StringValueSlice(names)) | ||
}) | ||
} | ||
|
||
} | ||
|
||
func TestEc2EgressOnlyInternetGateway_NukeAll(t *testing.T) { | ||
telemetry.InitTelemetry("cloud-nuke", "") | ||
t.Parallel() | ||
|
||
var ( | ||
gateway1 = "igw-0b44cfa6103932e1d001" | ||
gateway2 = "igw-0b44cfa6103932e1d002" | ||
) | ||
|
||
igw := EgressOnlyInternetGateway{ | ||
BaseAwsResource: BaseAwsResource{ | ||
Nukables: map[string]error{ | ||
gateway1: nil, | ||
}, | ||
}, | ||
Client: mockedEgressOnlyIgw{ | ||
DescribeEgressOnlyInternetGatewaysOutput: ec2.DescribeEgressOnlyInternetGatewaysOutput{ | ||
EgressOnlyInternetGateways: []*ec2.EgressOnlyInternetGateway{ | ||
{ | ||
EgressOnlyInternetGatewayId: aws.String(gateway1), | ||
Attachments: []*ec2.InternetGatewayAttachment{ | ||
{ | ||
State: aws.String("testing-state"), | ||
VpcId: aws.String("test-gateway-vpc"), | ||
}, | ||
}, | ||
}, | ||
{ | ||
EgressOnlyInternetGatewayId: aws.String(gateway2), | ||
Attachments: []*ec2.InternetGatewayAttachment{ | ||
{ | ||
State: aws.String("testing-state"), | ||
VpcId: aws.String("test-gateway-vpc"), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
DeleteEgressOnlyInternetGatewayOutput: ec2.DeleteEgressOnlyInternetGatewayOutput{}, | ||
}, | ||
} | ||
|
||
err := igw.nukeAll([]*string{ | ||
aws.String(gateway1), | ||
aws.String(gateway2), | ||
}) | ||
require.NoError(t, err) | ||
} |
Oops, something went wrong.