vpc-utils/awsutils.go (208 lines of code) (raw):
package vpcutils
import (
"context"
"fmt"
"log"
"os"
"text/tabwriter"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/securityhub"
"github.com/guardian/fsbp-tools/fsbp-fix/common"
)
type vpcDetails struct {
VpcName string
VpcId string
}
type securityGroupRule struct {
GroupRuleId string
FromPort int32
ToPort int32
IpProtocol string
Direction string // ingress or egress
}
type SecurityGroupRuleDetails struct {
SecurityGroup string
VpcDetails vpcDetails
Rule securityGroupRule
}
func getSecurityGroupRules(ctx context.Context, ec2Client *ec2.Client, groupId string) ([]securityGroupRule, error) {
fieldName := "group-id"
rules, err := ec2Client.DescribeSecurityGroupRules(ctx, &ec2.DescribeSecurityGroupRulesInput{
Filters: []types.Filter{
{
Name: &fieldName,
Values: []string{groupId},
},
},
})
if err != nil {
return nil, err
}
res := []securityGroupRule{}
for _, rule := range rules.SecurityGroupRules {
var direction string
if *rule.IsEgress {
direction = "egress"
} else {
direction = "ingress"
}
res = append(res, securityGroupRule{
GroupRuleId: *rule.SecurityGroupRuleId,
FromPort: *rule.FromPort,
ToPort: *rule.ToPort,
IpProtocol: *rule.IpProtocol,
Direction: direction,
})
}
return res, nil
}
func getVpcDetails(ctx context.Context, ec2Client *ec2.Client, groupId string) (vpcDetails, error) {
groupDescriptions, err := ec2Client.DescribeSecurityGroups(ctx, &ec2.DescribeSecurityGroupsInput{
GroupIds: []string{groupId},
})
if err != nil {
return vpcDetails{}, err
}
res := []vpcDetails{}
for _, group := range groupDescriptions.SecurityGroups {
vpcs, err := ec2Client.DescribeVpcs(ctx, &ec2.DescribeVpcsInput{
VpcIds: []string{*group.VpcId},
})
if err != nil {
return vpcDetails{}, err
}
for _, vpc := range vpcs.Vpcs {
name := FindTag(vpc.Tags, "Name", "unknown")
res = append(res, vpcDetails{
VpcName: name,
VpcId: *group.VpcId,
})
}
}
return res[0], nil // A security group cannot be associated with multiple VPCs.
}
func getSecurityGroupRuleDetails(ctx context.Context, ec2Client *ec2.Client, groupId string) ([]SecurityGroupRuleDetails, error) {
rules, err := getSecurityGroupRules(ctx, ec2Client, groupId)
if err != nil {
return nil, err
}
vpcDetails, err := getVpcDetails(ctx, ec2Client, groupId)
if err != nil {
return nil, err
}
res := []SecurityGroupRuleDetails{}
for _, rule := range rules {
res = append(res, SecurityGroupRuleDetails{
SecurityGroup: groupId,
VpcDetails: vpcDetails,
Rule: rule,
})
}
return res, nil
}
func findUnusedSecurityGroups(ctx context.Context, ec2Client *ec2.Client, sgIds []string) ([]string, error) {
allNetworkInterfaces := []types.NetworkInterface{}
securityGroupsInNetworkInterfaces := []string{}
maxInterfaceResults := int32(100)
firstNetworkInterfaces, err := ec2Client.DescribeNetworkInterfaces(ctx, &ec2.DescribeNetworkInterfacesInput{
MaxResults: &maxInterfaceResults,
})
if err != nil {
return nil, err
}
allNetworkInterfaces = append(allNetworkInterfaces, firstNetworkInterfaces.NetworkInterfaces...)
var nextToken = firstNetworkInterfaces.NextToken
for nextToken != nil {
networkInterfaces, err := ec2Client.DescribeNetworkInterfaces(ctx, &ec2.DescribeNetworkInterfacesInput{
MaxResults: &maxInterfaceResults,
NextToken: nextToken,
})
if err != nil {
return nil, err
}
allNetworkInterfaces = append(allNetworkInterfaces, networkInterfaces.NetworkInterfaces...)
nextToken = networkInterfaces.NextToken
}
for _, networkInterface := range allNetworkInterfaces {
for _, group := range networkInterface.Groups {
securityGroupsInNetworkInterfaces = append(securityGroupsInNetworkInterfaces, *group.GroupId)
}
}
return common.Complement(sgIds, securityGroupsInNetworkInterfaces), nil
}
func FindUnusedSecurityGroupRules(ctx context.Context, ec2Client *ec2.Client, securityHubClient *securityhub.Client) ([]SecurityGroupRuleDetails, error) {
findings, err := common.ReturnFindings(ctx, securityHubClient, "EC2.2", 100)
if err != nil {
return nil, err
}
securityGroups := []string{}
for _, finding := range findings.Findings {
for _, resource := range finding.Resources {
sgId := IdFromArn(*resource.Id)
securityGroups = append(securityGroups, sgId)
}
}
unusedSecurityGroups, err := findUnusedSecurityGroups(ctx, ec2Client, securityGroups)
if err != nil {
return nil, err
}
securityGroupRuleDetails := []SecurityGroupRuleDetails{}
for _, sg := range unusedSecurityGroups {
rules, err := getSecurityGroupRuleDetails(ctx, ec2Client, sg)
if err != nil {
return nil, err
}
securityGroupRuleDetails = append(securityGroupRuleDetails, rules...)
}
if len(securityGroupRuleDetails) > 0 {
fmt.Println("Ingress/egress rules on unused default security groups:")
// Print out results as a table
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.Debug)
fmt.Fprintln(w, "Security Group\tVPC Name\tVPC ID\tRule Id\tFrom Port\tTo Port\tIP Protocol\tDirection")
for _, sg := range securityGroupRuleDetails {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%d\t%s\t%s\n", sg.SecurityGroup, sg.VpcDetails.VpcName, sg.VpcDetails.VpcId, sg.Rule.GroupRuleId, sg.Rule.FromPort, sg.Rule.ToPort, sg.Rule.IpProtocol, sg.Rule.Direction)
}
err = w.Flush()
}
if err != nil {
return nil, err
}
return securityGroupRuleDetails, nil
}
func deleteSecurityGroupRule(ctx context.Context, ec2Client *ec2.Client, rule SecurityGroupRuleDetails) error {
if rule.Rule.Direction == "egress" {
_, err := ec2Client.RevokeSecurityGroupEgress(ctx, &ec2.RevokeSecurityGroupEgressInput{
GroupId: &rule.SecurityGroup,
SecurityGroupRuleIds: []string{rule.Rule.GroupRuleId},
})
if err != nil {
return err
}
} else {
_, err := ec2Client.RevokeSecurityGroupIngress(ctx, &ec2.RevokeSecurityGroupIngressInput{
GroupId: &rule.SecurityGroup,
SecurityGroupRuleIds: []string{rule.Rule.GroupRuleId},
})
if err != nil {
return err
}
}
fmt.Printf("Deleted rule %s from security group %s\n", rule.Rule.GroupRuleId, rule.SecurityGroup)
return nil
}
func DeleteSecurityGroupRules(ctx context.Context, ec2Client *ec2.Client, securityGroupRuleDetails []SecurityGroupRuleDetails) {
var failures int = 0
log.Println("Starting to delete rules...")
for _, sgr := range securityGroupRuleDetails {
err := deleteSecurityGroupRule(ctx, ec2Client, sgr)
if err != nil {
log.Printf("Error deleting rule: %v\n", sgr.Rule.GroupRuleId)
log.Printf("Error: %v\n", err)
failures++
}
}
log.Printf("Finished deleting rules.")
if failures > 0 {
log.Fatalf("Failed to delete %d rules", failures)
}
}