diff --git a/action/action.go b/action/action.go index 4585ba2..fc6a0e7 100644 --- a/action/action.go +++ b/action/action.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/eks" "github.com/aws/aws-sdk-go/service/elb" ) @@ -32,10 +33,11 @@ func (a *action) Cleanup(ctx context.Context, input *Input) error { //NOTE: ordering matters here! cleanupFuncs := map[string]CleanupFunc{ - elb.ServiceName: a.cleanLoadBalancers, - cloudformation.ServiceName: a.cleanCfStacks, eks.ServiceName: a.cleanEKSClusters, autoscaling.ServiceName: a.cleanASGs, + elb.ServiceName: a.cleanLoadBalancers, + ec2.ServiceName: a.cleanSecurityGroups, + cloudformation.ServiceName: a.cleanCfStacks, } inputRegions := strings.Split(input.Regions, ",") diff --git a/action/cleanup_sgs.go b/action/cleanup_sgs.go new file mode 100644 index 0000000..81164b0 --- /dev/null +++ b/action/cleanup_sgs.go @@ -0,0 +1,129 @@ +package action + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" +) + +func (a *action) cleanSecurityGroups(ctx context.Context, input *CleanupScope) error { + client := ec2.New(input.Session) + + sgsToDelete := []*ec2.SecurityGroup{} + // NOTE: we delete security groups based on whether we're later deleting the vpc they belong to or not. + pageFunc := func(page *ec2.DescribeVpcsOutput, _ bool) bool { + sgPageFunc := func(sgPage *ec2.GetSecurityGroupsForVpcOutput, _ bool) bool { + for _, sg := range sgPage.SecurityGroupForVpcs { + var ignore bool + for _, tag := range sg.Tags { + if *tag.Key == input.IgnoreTag { + ignore = true + break + } + } + + if ignore || *sg.GroupName == "default" { + LogDebug("security group %s has ignore tag or is a default security group, skipping cleanup", *sg.GroupId) + continue + } + + securityGroups, err := client.DescribeSecurityGroupsWithContext(ctx, &ec2.DescribeSecurityGroupsInput{GroupIds: []*string{sg.GroupId}}) + if err != nil || len(securityGroups.SecurityGroups) != 1 { + LogError("failed to describe security group %s: %s", *sg.GroupId, err.Error()) + continue + } + + LogDebug("adding security group %s to delete list", *sg.GroupId) + sgsToDelete = append(sgsToDelete, securityGroups.SecurityGroups[0]) + } + + return true + } + + for _, vpc := range page.Vpcs { + var ignore bool + for _, tag := range vpc.Tags { + if *tag.Key == input.IgnoreTag { + ignore = true + break + } + } + + if ignore || aws.BoolValue(vpc.IsDefault) { + LogDebug("vpc %s has ignore tag or is a default vpc, won't delete security groups associated with it", *vpc.VpcId) + continue + } + + if err := client.GetSecurityGroupsForVpcPagesWithContext(ctx, &ec2.GetSecurityGroupsForVpcInput{}, sgPageFunc); err != nil { + LogError("failed getting list of security groups for vpc %s: %s", *vpc.VpcId, err.Error()) + continue + } + + } + + return true + } + + if err := client.DescribeVpcsPagesWithContext(ctx, &ec2.DescribeVpcsInput{}, pageFunc); err != nil { + return fmt.Errorf("failed getting list of vpcs: %w", err) + } + + if len(sgsToDelete) == 0 { + Log("no security groups to delete") + return nil + } + + // NOTE: some security groups may have rules that reference other security groups. + // deleting a security group that's referenced in another's rules will fail, + // so we need to delete the rules first. + for _, securityGroup := range sgsToDelete { + if !a.commit { + LogDebug("skipping deletion of security group %s as running in dry-mode", *securityGroup.GroupId) + continue + } + + if err := a.deleteSecurityGroupRules(ctx, *securityGroup.GroupId, securityGroup.IpPermissions, securityGroup.IpPermissionsEgress, client); err != nil { + LogError("failed to delete security group rules for %s: %s", *securityGroup.GroupId, err.Error()) + } + + } + + for _, securityGroup := range sgsToDelete { + if !a.commit { + LogDebug("skipping deletion of security group %s as running in dry-mode", *securityGroup.GroupId) + continue + } + + if err := a.deleteSecurityGroup(ctx, *securityGroup.GroupId, client); err != nil { + LogError("failed to delete security group %s: %s", *securityGroup.GroupId, err.Error()) + } + } + + return nil +} + +func (a *action) deleteSecurityGroupRules(ctx context.Context, sgId string, sgIngress, sgEgress []*ec2.IpPermission, client *ec2.EC2) error { + Log("Deleting Ingress/Egress Rules from security group %s", sgId) + + if _, err := client.RevokeSecurityGroupIngressWithContext(ctx, &ec2.RevokeSecurityGroupIngressInput{GroupId: &sgId, IpPermissions: sgIngress}); err != nil { + return fmt.Errorf("failed to revoke ingress rules from security group %s: %w", sgId, err) + } + + if _, err := client.RevokeSecurityGroupEgressWithContext(ctx, &ec2.RevokeSecurityGroupEgressInput{GroupId: &sgId, IpPermissions: sgEgress}); err != nil { + return fmt.Errorf("failed to revoke egress rules from security group %s: %w", sgId, err) + } + + return nil +} + +func (a *action) deleteSecurityGroup(ctx context.Context, sgId string, client *ec2.EC2) error { + Log("Deleting Security Group %s", sgId) + + if _, err := client.DeleteSecurityGroupWithContext(ctx, &ec2.DeleteSecurityGroupInput{GroupId: &sgId}); err != nil { + return fmt.Errorf("failed to delete security group %s: %w", sgId, err) + } + + return nil +}