From 21acc33c8c1e4c7991f357f4ad02de205532c9c1 Mon Sep 17 00:00:00 2001 From: James Kwon Date: Tue, 23 Apr 2024 22:14:33 -0400 Subject: [PATCH] [WIP] Feature - Network Firewall resources --- README.md | 8 + aws/resource_registry.go | 4 + aws/resources/network_firewall.go | 162 +++++++++++++++++ aws/resources/network_firewall_policy.go | 146 +++++++++++++++ aws/resources/network_firewall_policy_test.go | 158 ++++++++++++++++ .../network_firewall_policy_types.go | 59 ++++++ aws/resources/network_firewall_rule_group.go | 163 +++++++++++++++++ .../network_firewall_rule_group_test.go | 172 ++++++++++++++++++ .../network_firewall_rule_group_types.go | 67 +++++++ aws/resources/network_firewall_test.go | 158 ++++++++++++++++ aws/resources/network_firewall_tls_config.go | 141 ++++++++++++++ .../network_firewall_tls_config_test.go | 161 ++++++++++++++++ .../network_firewall_tls_config_types.go | 58 ++++++ aws/resources/network_firewall_types.go | 59 ++++++ config/config.go | 4 + config/config_test.go | 4 + util/error.go | 2 + util/tag.go | 10 + 18 files changed, 1536 insertions(+) create mode 100644 aws/resources/network_firewall.go create mode 100644 aws/resources/network_firewall_policy.go create mode 100644 aws/resources/network_firewall_policy_test.go create mode 100644 aws/resources/network_firewall_policy_types.go create mode 100644 aws/resources/network_firewall_rule_group.go create mode 100644 aws/resources/network_firewall_rule_group_test.go create mode 100644 aws/resources/network_firewall_rule_group_types.go create mode 100644 aws/resources/network_firewall_test.go create mode 100644 aws/resources/network_firewall_tls_config.go create mode 100644 aws/resources/network_firewall_tls_config_test.go create mode 100644 aws/resources/network_firewall_tls_config_types.go create mode 100644 aws/resources/network_firewall_types.go diff --git a/README.md b/README.md index bcc60965..55e044e1 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,10 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res | Route53 | Hosted Zones | | Route53 | CIDR collections | | Route53 | Traffic Policies | +| NetworkFirewall | Network Firewall | +| NetworkFirewall | Network Firewall Policy | +| NetworkFirewall | Network Firewall Rule Group | +| NetworkFirewall | Network Firewall TLS inspection configuration | > **WARNING:** The RDS APIs also interact with neptune and document db resources. > Running `cloud-nuke aws --resource-type rds` without a config file will remove any neptune and document db resources @@ -617,6 +621,10 @@ of the file that are supported are listed here. | route53-hosted-zone | Route53HostedZone | ✅ (Hosted zone name) | ❌ | ❌ | ❌ | | route53-cidr-collection | Route53CIDRCollection | ✅ (Cidr collection name) | ❌ | ❌ | ❌ | | route53-traffic-policy | Route53TrafficPolicy | ✅ (Traffic policy name) | ❌ | ❌ | ❌ | +| network-firewall | NetworkFirewall | ✅ (Firewall name) | ✅ (First Seen Tag Time) | ✅ | ❌ | +| network-firewall-policy | NetworkFirewallPolicy | ✅ (Firewall Policy name) | ✅ (First Seen Tag Time) | ✅ | ❌ | +| network-firewall-rule-group | NetworkFirewallRuleGroup | ✅ (Firewall Rule group name) | ✅ (First Seen Tag Time) | ✅ | ❌ | +| network-firewall-tls-config | NetworkFirewallTLSConfig | ✅ (Firewall TLS config name) | ✅ (First Seen Tag Time) | ✅ | ❌ | ### How to Use diff --git a/aws/resource_registry.go b/aws/resource_registry.go index 91e28112..1410d517 100644 --- a/aws/resource_registry.go +++ b/aws/resource_registry.go @@ -134,6 +134,10 @@ func getRegisteredRegionalResources() []AwsResource { &resources.NetworkInterface{}, &resources.SecurityGroup{}, &resources.NetworkACL{}, + &resources.NetworkFirewall{}, + &resources.NetworkFirewallPolicy{}, + &resources.NetworkFirewallRuleGroup{}, + &resources.NetworkFirewallTLSConfig{}, } } diff --git a/aws/resources/network_firewall.go b/aws/resources/network_firewall.go new file mode 100644 index 00000000..0556096e --- /dev/null +++ b/aws/resources/network_firewall.go @@ -0,0 +1,162 @@ +package resources + +import ( + "context" + "slices" + "time" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "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 (nfw *NetworkFirewall) setFirstSeenTag(resource *networkfirewall.Firewall, value time.Time) error { + _, err := nfw.Client.TagResource(&networkfirewall.TagResourceInput{ + ResourceArn: resource.FirewallArn, + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(value)), + }, + }, + }) + return errors.WithStackTrace(err) +} + +func (nfw *NetworkFirewall) getFirstSeenTag(resource *networkfirewall.Firewall) (*time.Time, error) { + for _, tag := range resource.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 shouldIncludeNetworkFirewall(firewall *networkfirewall.Firewall, firstSeenTime *time.Time, configObj config.Config) bool { + var identifierName string + tags := util.ConvertNetworkFirewallTagsToMap(firewall.Tags) + + identifierName = awsgo.StringValue(firewall.FirewallName) // set the default + if v, ok := tags["Name"]; ok { + identifierName = v + } + + return configObj.NetworkFirewall.ShouldInclude(config.ResourceValue{ + Name: &identifierName, + Tags: tags, + Time: firstSeenTime, + }) +} + +func (nfw *NetworkFirewall) getAll(_ context.Context, configObj config.Config) ([]*string, error) { + var identifiers []*string + + metaOutput, err := nfw.Client.ListFirewalls(nil) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + var deleteprotected []string + // describe the firewalls to get more info + for _, firewall := range metaOutput.Firewalls { + output, err := nfw.Client.DescribeFirewall(&networkfirewall.DescribeFirewallInput{ + FirewallArn: firewall.FirewallArn, + }) + if err != nil { + logging.Errorf("[Failed] to describe the firewall %s", awsgo.StringValue(firewall.FirewallArn)) + return nil, errors.WithStackTrace(err) + } + + if output.Firewall == nil { + logging.Errorf("[Failed] no firewall information found for %s", awsgo.StringValue(firewall.FirewallArn)) + continue + } + + // check first seen tag + firstSeenTime, err := nfw.getFirstSeenTag(output.Firewall) + if err != nil { + logging.Errorf( + "Unable to retrieve tags for Firewall: %s, with error: %s", awsgo.StringValue(firewall.FirewallName), err) + continue + } + + // if the first seen tag is not there, then create one + if firstSeenTime == nil { + now := time.Now().UTC() + firstSeenTime = &now + if err := nfw.setFirstSeenTag(output.Firewall, time.Now().UTC()); err != nil { + logging.Errorf( + "Unable to apply first seen tag Firewall: %s, with error: %s", awsgo.StringValue(firewall.FirewallName), err) + continue + } + } + + // check the resource is delete protected + if awsgo.BoolValue(output.Firewall.DeleteProtection) { + deleteprotected = append(deleteprotected, awsgo.StringValue(firewall.FirewallName)) + } + + if shouldIncludeNetworkFirewall(output.Firewall, firstSeenTime, configObj) { + identifiers = append(identifiers, firewall.FirewallName) + } + } + + nfw.VerifyNukablePermissions(identifiers, func(id *string) error { + // check the resource is enabled delete protection + if slices.Contains(deleteprotected, awsgo.StringValue(id)) { + return util.ErrDeleteProtectionEnabled + } + return nil + }) + + return identifiers, nil +} + +func (nfw *NetworkFirewall) nukeAll(identifiers []*string) error { + if len(identifiers) == 0 { + logging.Debugf("No Network Firewalls to nuke in region %s", nfw.Region) + return nil + } + + logging.Debugf("Deleting Network firewalls in region %s", nfw.Region) + var deleted []*string + + for _, id := range identifiers { + if nukable, err := nfw.IsNukable(*id); !nukable { + logging.Debugf("[Skipping] %s nuke because %v", *id, err) + continue + } + + _, err := nfw.Client.DeleteFirewall(&networkfirewall.DeleteFirewallInput{ + FirewallName: id, + }) + + // Record status of this resource + e := report.Entry{ + Identifier: awsgo.StringValue(id), + ResourceType: "Network Firewall", + Error: err, + } + report.Record(e) + + if err != nil { + logging.Debugf("[Failed] %s", err) + } else { + deleted = append(deleted, id) + } + } + + logging.Debugf("[OK] %d Network Firewall(s) deleted in %s", len(deleted), nfw.Region) + + return nil +} diff --git a/aws/resources/network_firewall_policy.go b/aws/resources/network_firewall_policy.go new file mode 100644 index 00000000..4ed3938d --- /dev/null +++ b/aws/resources/network_firewall_policy.go @@ -0,0 +1,146 @@ +package resources + +import ( + "context" + "time" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "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 (nfw *NetworkFirewallPolicy) setFirstSeenTag(resource *networkfirewall.FirewallPolicyResponse, value time.Time) error { + _, err := nfw.Client.TagResource(&networkfirewall.TagResourceInput{ + ResourceArn: resource.FirewallPolicyArn, + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(value)), + }, + }, + }) + return errors.WithStackTrace(err) +} + +func (nfw *NetworkFirewallPolicy) getFirstSeenTag(resource *networkfirewall.FirewallPolicyResponse) (*time.Time, error) { + for _, tag := range resource.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 shouldIncludeNetworkFirewallPolicy(firewall *networkfirewall.FirewallPolicyResponse, firstSeenTime *time.Time, configObj config.Config) bool { + // if the firewall policy has any attachments, then we can't remove that policy + if awsgo.Int64Value(firewall.NumberOfAssociations) > 0 { + logging.Debugf("[Skipping] the policy %s is still in use", awsgo.StringValue(firewall.FirewallPolicyName)) + return false + } + + var identifierName string + tags := util.ConvertNetworkFirewallTagsToMap(firewall.Tags) + + if v, ok := tags["Name"]; ok { + identifierName = v + } + return configObj.NetworkFirewallPolicy.ShouldInclude(config.ResourceValue{ + Name: &identifierName, + Tags: tags, + Time: firstSeenTime, + }) +} + +func (nfw *NetworkFirewallPolicy) getAll(_ context.Context, configObj config.Config) ([]*string, error) { + var identifiers []*string + + metaOutput, err := nfw.Client.ListFirewallPolicies(nil) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + for _, policy := range metaOutput.FirewallPolicies { + + output, err := nfw.Client.DescribeFirewallPolicy(&networkfirewall.DescribeFirewallPolicyInput{ + FirewallPolicyArn: policy.Arn, + }) + if err != nil { + logging.Errorf("[Failed] to describe the firewall policy %s", awsgo.StringValue(policy.Name)) + return nil, errors.WithStackTrace(err) + } + + if output.FirewallPolicyResponse == nil { + logging.Errorf("[Failed] no firewall policy information found for %s", awsgo.StringValue(policy.Name)) + continue + } + + // check first seen tag + firstSeenTime, err := nfw.getFirstSeenTag(output.FirewallPolicyResponse) + if err != nil { + logging.Errorf( + "Unable to retrieve tags for Rule group: %s, with error: %s", awsgo.StringValue(policy.Name), err) + continue + } + + // if the first seen tag is not there, then create one + if firstSeenTime == nil { + now := time.Now().UTC() + firstSeenTime = &now + if err := nfw.setFirstSeenTag(output.FirewallPolicyResponse, time.Now().UTC()); err != nil { + logging.Errorf( + "Unable to apply first seen tag Rule group: %s, with error: %s", awsgo.StringValue(policy.Name), err) + continue + } + } + + if shouldIncludeNetworkFirewallPolicy(output.FirewallPolicyResponse, firstSeenTime, configObj) { + identifiers = append(identifiers, policy.Name) + } + } + + return identifiers, nil +} + +func (nfw *NetworkFirewallPolicy) nukeAll(identifiers []*string) error { + if len(identifiers) == 0 { + logging.Debugf("No Network Firewall policy to nuke in region %s", nfw.Region) + return nil + } + + logging.Debugf("Deleting Network firewall policy in region %s", nfw.Region) + var deleted []*string + + for _, id := range identifiers { + _, err := nfw.Client.DeleteFirewallPolicy(&networkfirewall.DeleteFirewallPolicyInput{ + FirewallPolicyName: id, + }) + + // Record status of this resource + e := report.Entry{ + Identifier: awsgo.StringValue(id), + ResourceType: "Network Firewall policy", + Error: err, + } + report.Record(e) + + if err != nil { + logging.Debugf("[Failed] %s", err) + } else { + deleted = append(deleted, id) + } + } + + logging.Debugf("[OK] %d Network Policy(s) deleted in %s", len(deleted), nfw.Region) + + return nil +} diff --git a/aws/resources/network_firewall_policy_test.go b/aws/resources/network_firewall_policy_test.go new file mode 100644 index 00000000..8858760c --- /dev/null +++ b/aws/resources/network_firewall_policy_test.go @@ -0,0 +1,158 @@ +package resources + +import ( + "context" + "fmt" + "regexp" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkfirewall/networkfirewalliface" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/cloud-nuke/util" + "github.com/stretchr/testify/require" +) + +type mockedNetworkFirewallPolicy struct { + networkfirewalliface.NetworkFirewallAPI + DeleteFirewallPolicyOutput networkfirewall.DeleteFirewallPolicyOutput + ListFirewallPoliciesOutput networkfirewall.ListFirewallPoliciesOutput + TagResourceOutput networkfirewall.TagResourceOutput + DescribeFirewallPolicyOutput map[string]networkfirewall.DescribeFirewallPolicyOutput +} + +func (m mockedNetworkFirewallPolicy) TagResource(*networkfirewall.TagResourceInput) (*networkfirewall.TagResourceOutput, error) { + return &m.TagResourceOutput, nil +} + +func (m mockedNetworkFirewallPolicy) DeleteFirewallPolicy(*networkfirewall.DeleteFirewallPolicyInput) (*networkfirewall.DeleteFirewallPolicyOutput, error) { + return &m.DeleteFirewallPolicyOutput, nil +} + +func (m mockedNetworkFirewallPolicy) ListFirewallPolicies(*networkfirewall.ListFirewallPoliciesInput) (*networkfirewall.ListFirewallPoliciesOutput, error) { + return &m.ListFirewallPoliciesOutput, nil +} + +func (m mockedNetworkFirewallPolicy) DescribeFirewallPolicy(req *networkfirewall.DescribeFirewallPolicyInput) (*networkfirewall.DescribeFirewallPolicyOutput, error) { + raw := awsgo.StringValue(req.FirewallPolicyArn) + v, ok := m.DescribeFirewallPolicyOutput[raw] + if !ok { + return nil, fmt.Errorf("unable to describe the %s", raw) + } + return &v, nil +} + +func TestNetworkFirewallPolicy_GetAll(t *testing.T) { + + t.Parallel() + + var ( + now = time.Now() + testId1 = "test-network-firewall-id1" + testId2 = "test-network-firewall-id2" + testName1 = "test-network-firewall-1" + testName2 = "test-network-firewall-2" + ) + + nfw := NetworkFirewallPolicy{ + Client: mockedNetworkFirewallPolicy{ + ListFirewallPoliciesOutput: networkfirewall.ListFirewallPoliciesOutput{ + FirewallPolicies: []*networkfirewall.FirewallPolicyMetadata{ + { + Arn: awsgo.String(testId1), + Name: awsgo.String(testName1), + }, + { + Arn: awsgo.String(testId2), + Name: awsgo.String(testName2), + }, + }, + }, + DescribeFirewallPolicyOutput: map[string]networkfirewall.DescribeFirewallPolicyOutput{ + testId1: { + FirewallPolicyResponse: &networkfirewall.FirewallPolicyResponse{ + FirewallPolicyName: awsgo.String(testName1), + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(testName1), + }, { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(now)), + }, + }, + }, + }, + testId2: { + FirewallPolicyResponse: &networkfirewall.FirewallPolicyResponse{ + FirewallPolicyName: awsgo.String(testName2), + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(testName2), + }, { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(now.Add(1 * time.Hour))), + }, + }, + }, + }, + }, + }, + } + + nfw.BaseAwsResource.Init(nil) + + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{testName1, testName2}, + }, + "nameExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile(testName1), + }}}, + }, + expected: []string{testName2}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: awsgo.Time(now), + }}, + expected: []string{testName1}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := nfw.getAll(context.Background(), config.Config{ + NetworkFirewallPolicy: tc.configObj, + }) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) + } +} + +func TestNetworkFirewallPolicy_NukeAll(t *testing.T) { + + t.Parallel() + + ngw := NetworkFirewallPolicy{ + Client: mockedNetworkFirewallPolicy{ + DeleteFirewallPolicyOutput: networkfirewall.DeleteFirewallPolicyOutput{}, + }, + } + + err := ngw.nukeAll([]*string{aws.String("test")}) + require.NoError(t, err) +} diff --git a/aws/resources/network_firewall_policy_types.go b/aws/resources/network_firewall_policy_types.go new file mode 100644 index 00000000..93c9a1d5 --- /dev/null +++ b/aws/resources/network_firewall_policy_types.go @@ -0,0 +1,59 @@ +package resources + +import ( + "context" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkfirewall/networkfirewalliface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/go-commons/errors" +) + +type NetworkFirewallPolicy struct { + BaseAwsResource + Client networkfirewalliface.NetworkFirewallAPI + Region string + Identifiers []string +} + +func (nfw *NetworkFirewallPolicy) Init(session *session.Session) { + nfw.BaseAwsResource.Init(session) + nfw.Client = networkfirewall.New(session) +} + +// ResourceName - the simple name of the aws resource +func (nfw *NetworkFirewallPolicy) ResourceName() string { + return "network-firewall-policy" +} + +func (nfw *NetworkFirewallPolicy) ResourceIdentifiers() []string { + return nfw.Identifiers +} + +func (nfw *NetworkFirewallPolicy) MaxBatchSize() int { + // Tentative batch size to ensure AWS doesn't throttle. Note that nat gateway does not support bulk delete, so + // we will be deleting this many in parallel using go routines. We conservatively pick 10 here, both to limit + // overloading the runtime and to avoid AWS throttling with many API calls. + return 10 +} + +func (nfw *NetworkFirewallPolicy) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { + identifiers, err := nfw.getAll(c, configObj) + if err != nil { + return nil, err + } + + nfw.Identifiers = awsgo.StringValueSlice(identifiers) + return nfw.Identifiers, nil +} + +// Nuke - nuke 'em all!!! +func (nfw *NetworkFirewallPolicy) Nuke(identifiers []string) error { + if err := nfw.nukeAll(awsgo.StringSlice(identifiers)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} diff --git a/aws/resources/network_firewall_rule_group.go b/aws/resources/network_firewall_rule_group.go new file mode 100644 index 00000000..670fb560 --- /dev/null +++ b/aws/resources/network_firewall_rule_group.go @@ -0,0 +1,163 @@ +package resources + +import ( + "context" + "fmt" + "time" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "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 (nfw *NetworkFirewallRuleGroup) setFirstSeenTag(resource *networkfirewall.RuleGroupResponse, value time.Time) error { + _, err := nfw.Client.TagResource(&networkfirewall.TagResourceInput{ + ResourceArn: resource.RuleGroupArn, + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(value)), + }, + }, + }) + return errors.WithStackTrace(err) +} + +func (nfw *NetworkFirewallRuleGroup) getFirstSeenTag(resource *networkfirewall.RuleGroupResponse) (*time.Time, error) { + for _, tag := range resource.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 shouldIncludeNetworkFirewallRuleGroup(group *networkfirewall.RuleGroupResponse, firstSeenTime *time.Time, configObj config.Config) bool { + // if the firewall policy has any attachments, then we can't remove that policy + if awsgo.Int64Value(group.NumberOfAssociations) > 0 { + logging.Debugf("[Skipping] the rule group %s is still in use", awsgo.StringValue(group.RuleGroupName)) + return false + } + + var identifierName string + tags := util.ConvertNetworkFirewallTagsToMap(group.Tags) + + identifierName = awsgo.StringValue(group.RuleGroupName) // set the default + if v, ok := tags["Name"]; ok { + identifierName = v + } + + return configObj.NetworkFirewallRuleGroup.ShouldInclude(config.ResourceValue{ + Name: &identifierName, + Tags: tags, + Time: firstSeenTime, + }) +} + +func (nfw *NetworkFirewallRuleGroup) getAll(_ context.Context, configObj config.Config) ([]*string, error) { + var identifiers []*string + + meta, err := nfw.Client.ListRuleGroups(nil) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + for _, group := range meta.RuleGroups { + output, err := nfw.Client.DescribeRuleGroup(&networkfirewall.DescribeRuleGroupInput{ + RuleGroupArn: group.Arn, + }) + if err != nil { + logging.Errorf("[Failed] to describe the firewall rule group %s", awsgo.StringValue(group.Name)) + return nil, errors.WithStackTrace(err) + } + + if output.RuleGroupResponse == nil { + logging.Errorf("[Failed] no firewall rule group information found for %s", awsgo.StringValue(group.Name)) + continue + } + + // check first seen tag + firstSeenTime, err := nfw.getFirstSeenTag(output.RuleGroupResponse) + if err != nil { + logging.Errorf( + "Unable to retrieve tags for Rule group: %s, with error: %s", awsgo.StringValue(group.Name), err) + continue + } + + // if the first seen tag is not there, then create one + if firstSeenTime == nil { + now := time.Now().UTC() + firstSeenTime = &now + if err := nfw.setFirstSeenTag(output.RuleGroupResponse, time.Now().UTC()); err != nil { + logging.Errorf( + "Unable to apply first seen tag Rule group: %s, with error: %s", awsgo.StringValue(group.Name), err) + continue + } + } + + if shouldIncludeNetworkFirewallRuleGroup(output.RuleGroupResponse, firstSeenTime, configObj) { + identifiers = append(identifiers, group.Name) + + raw := awsgo.StringValue(group.Name) + nfw.RuleGroups[raw] = RuleGroup{ + Name: output.RuleGroupResponse.RuleGroupName, + Type: output.RuleGroupResponse.Type, + } + } + } + + return identifiers, nil +} + +func (nfw *NetworkFirewallRuleGroup) nukeAll(identifiers []*string) error { + if len(identifiers) == 0 { + logging.Debugf("No Network Firewall rule group to nuke in region %s", nfw.Region) + return nil + } + + logging.Debugf("Deleting Network firewall rule group in region %s", nfw.Region) + var deleted []*string + + for _, id := range identifiers { + // check and get the type for this identifier + group, ok := nfw.RuleGroups[awsgo.StringValue(id)] + if !ok { + logging.Errorf("couldn't find the rule group type for %s", awsgo.StringValue(id)) + return fmt.Errorf("couldn't find the rule group type for %s", awsgo.StringValue(id)) + } + + // delete the rule group + _, err := nfw.Client.DeleteRuleGroup(&networkfirewall.DeleteRuleGroupInput{ + RuleGroupName: id, + Type: group.Type, + }) + + // Record status of this resource + e := report.Entry{ + Identifier: awsgo.StringValue(id), + ResourceType: "Network Firewall Rule group", + Error: err, + } + report.Record(e) + + if err != nil { + logging.Debugf("[Failed] %s", err) + } else { + deleted = append(deleted, id) + } + } + + logging.Debugf("[OK] %d Network Firewall Rule group(s) deleted in %s", len(deleted), nfw.Region) + + return nil +} diff --git a/aws/resources/network_firewall_rule_group_test.go b/aws/resources/network_firewall_rule_group_test.go new file mode 100644 index 00000000..3c46ac2b --- /dev/null +++ b/aws/resources/network_firewall_rule_group_test.go @@ -0,0 +1,172 @@ +package resources + +import ( + "context" + "fmt" + "regexp" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkfirewall/networkfirewalliface" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/cloud-nuke/util" + "github.com/stretchr/testify/require" +) + +type mockedNetworkFirewallRuleGroup struct { + networkfirewalliface.NetworkFirewallAPI + DescribeRuleGroupOutput map[string]networkfirewall.DescribeRuleGroupOutput + TagResourceOutput networkfirewall.TagResourceOutput + DeleteRuleGroupOutput networkfirewall.DeleteRuleGroupOutput + ListRuleGroupsOutput networkfirewall.ListRuleGroupsOutput +} + +func (m mockedNetworkFirewallRuleGroup) TagResource(*networkfirewall.TagResourceInput) (*networkfirewall.TagResourceOutput, error) { + return &m.TagResourceOutput, nil +} + +func (m mockedNetworkFirewallRuleGroup) DescribeRuleGroup(req *networkfirewall.DescribeRuleGroupInput) (*networkfirewall.DescribeRuleGroupOutput, error) { + raw := awsgo.StringValue(req.RuleGroupArn) + v, ok := m.DescribeRuleGroupOutput[raw] + if !ok { + return nil, fmt.Errorf("unable to describe the %s", raw) + } + return &v, nil +} + +func (m mockedNetworkFirewallRuleGroup) DeleteRuleGroup(*networkfirewall.DeleteRuleGroupInput) (*networkfirewall.DeleteRuleGroupOutput, error) { + return &m.DeleteRuleGroupOutput, nil +} + +func (m mockedNetworkFirewallRuleGroup) ListRuleGroups(*networkfirewall.ListRuleGroupsInput) (*networkfirewall.ListRuleGroupsOutput, error) { + return &m.ListRuleGroupsOutput, nil +} + +func TestNetworkFirewallRuleGroup_GetAll(t *testing.T) { + + t.Parallel() + + var ( + now = time.Now() + testId1 = "test-network-rule-group-id1" + testId2 = "test-network-firewall-id2" + testName1 = "test-network-firewall-1" + testName2 = "test-network-firewall-2" + ) + + nfw := NetworkFirewallRuleGroup{ + RuleGroups: make(map[string]RuleGroup), + Client: mockedNetworkFirewallRuleGroup{ + ListRuleGroupsOutput: networkfirewall.ListRuleGroupsOutput{ + RuleGroups: []*networkfirewall.RuleGroupMetadata{ + { + Arn: awsgo.String(testId1), + Name: awsgo.String(testName1), + }, + { + Arn: awsgo.String(testId2), + Name: awsgo.String(testName2), + }, + }, + }, + DescribeRuleGroupOutput: map[string]networkfirewall.DescribeRuleGroupOutput{ + testId1: { + RuleGroupResponse: &networkfirewall.RuleGroupResponse{ + RuleGroupName: awsgo.String(testName1), + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(testName1), + }, { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(now)), + }, + }, + }, + }, + testId2: { + RuleGroupResponse: &networkfirewall.RuleGroupResponse{ + RuleGroupName: awsgo.String(testName2), + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(testName2), + }, { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(now.Add(1 * time.Hour))), + }, + }, + }, + }, + }, + }, + } + + nfw.BaseAwsResource.Init(nil) + + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{testName1, testName2}, + }, + "nameExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile(testName1), + }}}, + }, + expected: []string{testName2}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: awsgo.Time(now), + }}, + expected: []string{testName1}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := nfw.getAll(context.Background(), config.Config{ + NetworkFirewallRuleGroup: tc.configObj, + }) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) + } +} + +func TestNetworkFirewallRuleGroup_NukeAll(t *testing.T) { + + t.Parallel() + + ngw := NetworkFirewallRuleGroup{ + Client: mockedNetworkFirewallRuleGroup{ + DeleteRuleGroupOutput: networkfirewall.DeleteRuleGroupOutput{}, + }, + RuleGroups: map[string]RuleGroup{ + "test-001": { + Name: awsgo.String("test-001"), + Type: awsgo.String("stateless"), + }, + "test-002": { + Name: awsgo.String("test-002"), + Type: awsgo.String("stateless"), + }, + }, + } + + err := ngw.nukeAll([]*string{ + aws.String("test-001"), + aws.String("test-002"), + }) + require.NoError(t, err) +} diff --git a/aws/resources/network_firewall_rule_group_types.go b/aws/resources/network_firewall_rule_group_types.go new file mode 100644 index 00000000..693e945c --- /dev/null +++ b/aws/resources/network_firewall_rule_group_types.go @@ -0,0 +1,67 @@ +package resources + +import ( + "context" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkfirewall/networkfirewalliface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/go-commons/errors" +) + +type RuleGroup struct { + Name, Type *string +} + +type NetworkFirewallRuleGroup struct { + BaseAwsResource + Client networkfirewalliface.NetworkFirewallAPI + Region string + Identifiers []string + // Note: It is mandatory to pass the rule type while nuking it. + // This map can be used to store information about a rule group name as key and value. + // When invoking the nuke method, information about a rule can be easily retrieved without making another API request. + RuleGroups map[string]RuleGroup +} + +func (nfrg *NetworkFirewallRuleGroup) Init(session *session.Session) { + nfrg.Client = networkfirewall.New(session) + nfrg.RuleGroups = make(map[string]RuleGroup, 0) +} + +// ResourceName - the simple name of the aws resource +func (nfrg *NetworkFirewallRuleGroup) ResourceName() string { + return "network-firewall-rule-group" +} + +func (nfrg *NetworkFirewallRuleGroup) ResourceIdentifiers() []string { + return nfrg.Identifiers +} + +func (nfrg *NetworkFirewallRuleGroup) MaxBatchSize() int { + // Tentative batch size to ensure AWS doesn't throttle. Note that nat gateway does not support bulk delete, so + // we will be deleting this many in parallel using go routines. We conservatively pick 10 here, both to limit + // overloading the runtime and to avoid AWS throttling with many API calls. + return 10 +} + +func (nfrg *NetworkFirewallRuleGroup) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { + identifiers, err := nfrg.getAll(c, configObj) + if err != nil { + return nil, err + } + + nfrg.Identifiers = awsgo.StringValueSlice(identifiers) + return nfrg.Identifiers, nil +} + +// Nuke - nuke 'em all!!! +func (nfrg *NetworkFirewallRuleGroup) Nuke(identifiers []string) error { + if err := nfrg.nukeAll(awsgo.StringSlice(identifiers)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} diff --git a/aws/resources/network_firewall_test.go b/aws/resources/network_firewall_test.go new file mode 100644 index 00000000..015c9b83 --- /dev/null +++ b/aws/resources/network_firewall_test.go @@ -0,0 +1,158 @@ +package resources + +import ( + "context" + "fmt" + "regexp" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkfirewall/networkfirewalliface" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/cloud-nuke/util" + "github.com/stretchr/testify/require" +) + +type mockedNetworkFirewall struct { + networkfirewalliface.NetworkFirewallAPI + DeleteFirewallOutput networkfirewall.DeleteFirewallOutput + ListFirewallsOutput networkfirewall.ListFirewallsOutput + TagResourceOutput networkfirewall.TagResourceOutput + DescribeFirewallOutput map[string]networkfirewall.DescribeFirewallOutput +} + +func (m mockedNetworkFirewall) TagResource(*networkfirewall.TagResourceInput) (*networkfirewall.TagResourceOutput, error) { + return &m.TagResourceOutput, nil +} + +func (m mockedNetworkFirewall) DeleteFirewall(*networkfirewall.DeleteFirewallInput) (*networkfirewall.DeleteFirewallOutput, error) { + return &m.DeleteFirewallOutput, nil +} + +func (m mockedNetworkFirewall) ListFirewalls(*networkfirewall.ListFirewallsInput) (*networkfirewall.ListFirewallsOutput, error) { + return &m.ListFirewallsOutput, nil +} + +func (m mockedNetworkFirewall) DescribeFirewall(req *networkfirewall.DescribeFirewallInput) (*networkfirewall.DescribeFirewallOutput, error) { + raw := awsgo.StringValue(req.FirewallArn) + v, ok := m.DescribeFirewallOutput[raw] + if !ok { + return nil, fmt.Errorf("unable to describe the %s", raw) + } + return &v, nil +} + +func TestNetworkFirewall_GetAll(t *testing.T) { + + t.Parallel() + + var ( + now = time.Now() + testId1 = "test-network-firewall-id1" + testId2 = "test-network-firewall-id2" + testName1 = "test-network-firewall-1" + testName2 = "test-network-firewall-2" + ) + + nfw := NetworkFirewall{ + Client: mockedNetworkFirewall{ + ListFirewallsOutput: networkfirewall.ListFirewallsOutput{ + Firewalls: []*networkfirewall.FirewallMetadata{ + { + FirewallArn: awsgo.String(testId1), + FirewallName: awsgo.String(testName1), + }, + { + FirewallArn: awsgo.String(testId2), + FirewallName: awsgo.String(testName2), + }, + }, + }, + DescribeFirewallOutput: map[string]networkfirewall.DescribeFirewallOutput{ + testId1: { + Firewall: &networkfirewall.Firewall{ + FirewallName: awsgo.String(testName1), + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(testName1), + }, { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(now)), + }, + }, + }, + }, + testId2: { + Firewall: &networkfirewall.Firewall{ + FirewallName: awsgo.String(testName2), + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(testName2), + }, { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(now.Add(1 * time.Hour))), + }, + }, + }, + }, + }, + }, + } + + nfw.BaseAwsResource.Init(nil) + + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{testName1, testName2}, + }, + "nameExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile(testName1), + }}}, + }, + expected: []string{testName2}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: awsgo.Time(now), + }}, + expected: []string{testName1}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := nfw.getAll(context.Background(), config.Config{ + NetworkFirewall: tc.configObj, + }) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) + } +} + +func TestNetworkFirewall_NukeAll(t *testing.T) { + + t.Parallel() + + ngw := NetworkFirewall{ + Client: mockedNetworkFirewall{ + DeleteFirewallOutput: networkfirewall.DeleteFirewallOutput{}, + }, + } + + err := ngw.nukeAll([]*string{aws.String("test")}) + require.NoError(t, err) +} diff --git a/aws/resources/network_firewall_tls_config.go b/aws/resources/network_firewall_tls_config.go new file mode 100644 index 00000000..cc3059d9 --- /dev/null +++ b/aws/resources/network_firewall_tls_config.go @@ -0,0 +1,141 @@ +package resources + +import ( + "context" + "time" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "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 (nfw *NetworkFirewallTLSConfig) setFirstSeenTag(resource *networkfirewall.TLSInspectionConfigurationResponse, value time.Time) error { + _, err := nfw.Client.TagResource(&networkfirewall.TagResourceInput{ + ResourceArn: resource.TLSInspectionConfigurationArn, + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(value)), + }, + }, + }) + return errors.WithStackTrace(err) +} + +func (nfw *NetworkFirewallTLSConfig) getFirstSeenTag(resource *networkfirewall.TLSInspectionConfigurationResponse) (*time.Time, error) { + for _, tag := range resource.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 shouldIncludeNetworkFirewallTLSConfig(tlsconfig *networkfirewall.TLSInspectionConfigurationResponse, firstSeenTime *time.Time, configObj config.Config) bool { + + var identifierName string + tags := util.ConvertNetworkFirewallTagsToMap(tlsconfig.Tags) + + identifierName = awsgo.StringValue(tlsconfig.TLSInspectionConfigurationName) // set the default + if v, ok := tags["Name"]; ok { + identifierName = v + } + + return configObj.NetworkFirewallTLSConfig.ShouldInclude(config.ResourceValue{ + Name: &identifierName, + Tags: tags, + Time: firstSeenTime, + }) +} + +func (nftc *NetworkFirewallTLSConfig) getAll(_ context.Context, configObj config.Config) ([]*string, error) { + var identifiers []*string + meta, err := nftc.Client.ListTLSInspectionConfigurations(nil) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + for _, tlsconfig := range meta.TLSInspectionConfigurations { + output, err := nftc.Client.DescribeTLSInspectionConfiguration(&networkfirewall.DescribeTLSInspectionConfigurationInput{ + TLSInspectionConfigurationArn: tlsconfig.Arn, + }) + if err != nil { + logging.Errorf("[Failed] to describe the firewall TLS inspection configuation %s", awsgo.StringValue(tlsconfig.Name)) + return nil, errors.WithStackTrace(err) + } + + if output.TLSInspectionConfigurationResponse == nil { + logging.Errorf("[Failed] no firewall TLS inspection configuation information found for %s", awsgo.StringValue(tlsconfig.Name)) + continue + } + + // check first seen tag + firstSeenTime, err := nftc.getFirstSeenTag(output.TLSInspectionConfigurationResponse) + if err != nil { + logging.Errorf( + "Unable to retrieve tags for TLS inspection configurations: %s, with error: %s", awsgo.StringValue(tlsconfig.Name), err) + continue + } + + // if the first seen tag is not there, then create one + if firstSeenTime == nil { + now := time.Now().UTC() + firstSeenTime = &now + if err := nftc.setFirstSeenTag(output.TLSInspectionConfigurationResponse, time.Now().UTC()); err != nil { + logging.Errorf( + "Unable to apply first seen tag TLS inspection configurations: %s, with error: %s", awsgo.StringValue(tlsconfig.Name), err) + continue + } + } + + if shouldIncludeNetworkFirewallTLSConfig(output.TLSInspectionConfigurationResponse, firstSeenTime, configObj) { + identifiers = append(identifiers, tlsconfig.Name) + } + } + + return identifiers, nil +} + +func (nftc *NetworkFirewallTLSConfig) nukeAll(identifiers []*string) error { + if len(identifiers) == 0 { + logging.Debugf("No Network Firewall TLS inspection configurations to nuke in region %s", nftc.Region) + return nil + } + + logging.Debugf("Deleting Network firewall TLS inspection configurations in region %s", nftc.Region) + var deleted []*string + + for _, id := range identifiers { + _, err := nftc.Client.DeleteTLSInspectionConfiguration(&networkfirewall.DeleteTLSInspectionConfigurationInput{ + TLSInspectionConfigurationName: id, + }) + + // Record status of this resource + e := report.Entry{ + Identifier: awsgo.StringValue(id), + ResourceType: "Network Firewall TLS inspection configurations", + Error: err, + } + report.Record(e) + + if err != nil { + logging.Debugf("[Failed] %s", err) + } else { + deleted = append(deleted, id) + } + } + + logging.Debugf("[OK] %d Network Firewall TLS inspection configurations(s) deleted in %s", len(deleted), nftc.Region) + + return nil +} diff --git a/aws/resources/network_firewall_tls_config_test.go b/aws/resources/network_firewall_tls_config_test.go new file mode 100644 index 00000000..c9619980 --- /dev/null +++ b/aws/resources/network_firewall_tls_config_test.go @@ -0,0 +1,161 @@ +package resources + +import ( + "context" + "fmt" + "regexp" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkfirewall/networkfirewalliface" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/cloud-nuke/util" + "github.com/stretchr/testify/require" +) + +type mockedNetworkFirewallTLSConfig struct { + networkfirewalliface.NetworkFirewallAPI + DescribeTLSInspectionConfigurationOutput map[string]networkfirewall.DescribeTLSInspectionConfigurationOutput + TagResourceOutput networkfirewall.TagResourceOutput + DeleteTLSInspectionConfigurationOutput networkfirewall.DeleteTLSInspectionConfigurationOutput + ListTLSInspectionConfigurationsOutput networkfirewall.ListTLSInspectionConfigurationsOutput +} + +func (m mockedNetworkFirewallTLSConfig) TagResource(*networkfirewall.TagResourceInput) (*networkfirewall.TagResourceOutput, error) { + return &m.TagResourceOutput, nil +} + +func (m mockedNetworkFirewallTLSConfig) DescribeTLSInspectionConfiguration(req *networkfirewall.DescribeTLSInspectionConfigurationInput) (*networkfirewall.DescribeTLSInspectionConfigurationOutput, error) { + raw := awsgo.StringValue(req.TLSInspectionConfigurationArn) + v, ok := m.DescribeTLSInspectionConfigurationOutput[raw] + if !ok { + return nil, fmt.Errorf("unable to describe the %s", raw) + } + return &v, nil +} + +func (m mockedNetworkFirewallTLSConfig) DeleteTLSInspectionConfiguration(*networkfirewall.DeleteTLSInspectionConfigurationInput) (*networkfirewall.DeleteTLSInspectionConfigurationOutput, error) { + return &m.DeleteTLSInspectionConfigurationOutput, nil +} + +func (m mockedNetworkFirewallTLSConfig) ListTLSInspectionConfigurations(*networkfirewall.ListTLSInspectionConfigurationsInput) (*networkfirewall.ListTLSInspectionConfigurationsOutput, error) { + return &m.ListTLSInspectionConfigurationsOutput, nil +} + +func TestNetworkFirewallTLSConfig_GetAll(t *testing.T) { + + t.Parallel() + + var ( + now = time.Now() + testId1 = "test-network-firewall-id1" + testId2 = "test-network-firewall-id2" + testName1 = "test-network-firewall-1" + testName2 = "test-network-firewall-2" + ) + + nfw := NetworkFirewallTLSConfig{ + Client: mockedNetworkFirewallTLSConfig{ + ListTLSInspectionConfigurationsOutput: networkfirewall.ListTLSInspectionConfigurationsOutput{ + TLSInspectionConfigurations: []*networkfirewall.TLSInspectionConfigurationMetadata{ + { + Arn: awsgo.String(testId1), + Name: awsgo.String(testName1), + }, + { + Arn: awsgo.String(testId2), + Name: awsgo.String(testName2), + }, + }, + }, + DescribeTLSInspectionConfigurationOutput: map[string]networkfirewall.DescribeTLSInspectionConfigurationOutput{ + testId1: { + TLSInspectionConfigurationResponse: &networkfirewall.TLSInspectionConfigurationResponse{ + TLSInspectionConfigurationName: awsgo.String(testName1), + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(testName1), + }, { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(now)), + }, + }, + }, + }, + testId2: { + TLSInspectionConfigurationResponse: &networkfirewall.TLSInspectionConfigurationResponse{ + TLSInspectionConfigurationName: awsgo.String(testName2), + Tags: []*networkfirewall.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(testName2), + }, { + Key: awsgo.String(util.FirstSeenTagKey), + Value: awsgo.String(util.FormatTimestamp(now.Add(1 * time.Hour))), + }, + }, + }, + }, + }, + }, + } + + nfw.BaseAwsResource.Init(nil) + + tests := map[string]struct { + configObj config.ResourceType + expected []string + }{ + "emptyFilter": { + configObj: config.ResourceType{}, + expected: []string{testName1, testName2}, + }, + "nameExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + NamesRegExp: []config.Expression{{ + RE: *regexp.MustCompile(testName1), + }}}, + }, + expected: []string{testName2}, + }, + "timeAfterExclusionFilter": { + configObj: config.ResourceType{ + ExcludeRule: config.FilterRule{ + TimeAfter: awsgo.Time(now), + }}, + expected: []string{testName1}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + names, err := nfw.getAll(context.Background(), config.Config{ + NetworkFirewallTLSConfig: tc.configObj, + }) + require.NoError(t, err) + require.Equal(t, tc.expected, aws.StringValueSlice(names)) + }) + } +} + +func TestNetworkFirewallTLSConfig_NukeAll(t *testing.T) { + + t.Parallel() + + ngw := NetworkFirewallTLSConfig{ + Client: mockedNetworkFirewallTLSConfig{ + DeleteTLSInspectionConfigurationOutput: networkfirewall.DeleteTLSInspectionConfigurationOutput{}, + }, + } + + err := ngw.nukeAll([]*string{ + aws.String("test-001"), + aws.String("test-002"), + }) + require.NoError(t, err) +} diff --git a/aws/resources/network_firewall_tls_config_types.go b/aws/resources/network_firewall_tls_config_types.go new file mode 100644 index 00000000..921194bd --- /dev/null +++ b/aws/resources/network_firewall_tls_config_types.go @@ -0,0 +1,58 @@ +package resources + +import ( + "context" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkfirewall/networkfirewalliface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/go-commons/errors" +) + +type NetworkFirewallTLSConfig struct { + BaseAwsResource + Client networkfirewalliface.NetworkFirewallAPI + Region string + Identifiers []string +} + +func (nftc *NetworkFirewallTLSConfig) Init(session *session.Session) { + nftc.Client = networkfirewall.New(session) +} + +// ResourceName - the simple name of the aws resource +func (nftc *NetworkFirewallTLSConfig) ResourceName() string { + return "network-firewall-tls-config" +} + +func (nftc *NetworkFirewallTLSConfig) ResourceIdentifiers() []string { + return nftc.Identifiers +} + +func (nftc *NetworkFirewallTLSConfig) MaxBatchSize() int { + // Tentative batch size to ensure AWS doesn't throttle. Note that nat gateway does not support bulk delete, so + // we will be deleting this many in parallel using go routines. We conservatively pick 10 here, both to limit + // overloading the runtime and to avoid AWS throttling with many API calls. + return 10 +} + +func (nftc *NetworkFirewallTLSConfig) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { + identifiers, err := nftc.getAll(c, configObj) + if err != nil { + return nil, err + } + + nftc.Identifiers = awsgo.StringValueSlice(identifiers) + return nftc.Identifiers, nil +} + +// Nuke - nuke 'em all!!! +func (nftc *NetworkFirewallTLSConfig) Nuke(identifiers []string) error { + if err := nftc.nukeAll(awsgo.StringSlice(identifiers)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} diff --git a/aws/resources/network_firewall_types.go b/aws/resources/network_firewall_types.go new file mode 100644 index 00000000..d6f3afb8 --- /dev/null +++ b/aws/resources/network_firewall_types.go @@ -0,0 +1,59 @@ +package resources + +import ( + "context" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkfirewall/networkfirewalliface" + "github.com/gruntwork-io/cloud-nuke/config" + "github.com/gruntwork-io/go-commons/errors" +) + +type NetworkFirewall struct { + BaseAwsResource + Client networkfirewalliface.NetworkFirewallAPI + Region string + Identifiers []string +} + +func (nfw *NetworkFirewall) Init(session *session.Session) { + nfw.BaseAwsResource.Init(session) + nfw.Client = networkfirewall.New(session) +} + +// ResourceName - the simple name of the aws resource +func (nfw *NetworkFirewall) ResourceName() string { + return "network-firewall" +} + +func (nfw *NetworkFirewall) ResourceIdentifiers() []string { + return nfw.Identifiers +} + +func (nfw *NetworkFirewall) MaxBatchSize() int { + // Tentative batch size to ensure AWS doesn't throttle. Note that nat gateway does not support bulk delete, so + // we will be deleting this many in parallel using go routines. We conservatively pick 10 here, both to limit + // overloading the runtime and to avoid AWS throttling with many API calls. + return 10 +} + +func (nfw *NetworkFirewall) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) { + identifiers, err := nfw.getAll(c, configObj) + if err != nil { + return nil, err + } + + nfw.Identifiers = awsgo.StringValueSlice(identifiers) + return nfw.Identifiers, nil +} + +// Nuke - nuke 'em all!!! +func (nfw *NetworkFirewall) Nuke(identifiers []string) error { + if err := nfw.nukeAll(awsgo.StringSlice(identifiers)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} diff --git a/config/config.go b/config/config.go index b8fc3c55..6c4f44d0 100644 --- a/config/config.go +++ b/config/config.go @@ -105,6 +105,10 @@ type Config struct { NetworkACL ResourceType `yaml:"NetworkACL"` NetworkInterface ResourceType `yaml:"NetworkInterface"` SecurityGroup EC2ResourceType `yaml:"SecurityGroup"` + NetworkFirewall ResourceType `yaml:"NetworkFirewall"` + NetworkFirewallPolicy ResourceType `yaml:"NetworkFirewallPolicy"` + NetworkFirewallRuleGroup ResourceType `yaml:"NetworkFirewallRuleGroup"` + NetworkFirewallTLSConfig ResourceType `yaml:"NetworkFirewallTLSConfig"` } func (c *Config) addTimeAfterFilter(timeFilter *time.Time, fieldName string) { diff --git a/config/config_test.go b/config/config_test.go index 2b57bdb4..a26ccaa6 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -103,6 +103,10 @@ func emptyConfig() *Config { ResourceType{FilterRule{}, FilterRule{}, ""}, ResourceType{FilterRule{}, FilterRule{}, ""}, EC2ResourceType{false, ResourceType{FilterRule{}, FilterRule{}, ""}}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, + ResourceType{FilterRule{}, FilterRule{}, ""}, } } diff --git a/util/error.go b/util/error.go index d3b26a4e..735550e2 100644 --- a/util/error.go +++ b/util/error.go @@ -13,6 +13,7 @@ var ErrDifferentOwner = errors.New("error:DIFFERENT_OWNER") var ErrContextExecutionTimeout = errors.New("error:EXECUTION_TIMEOUT") var ErrInterfaceIDNotFound = errors.New("error:InterfaceIdNotFound") var ErrInvalidPermisionNotFound = errors.New("error:InvalidPermission.NotFound") +var ErrDeleteProtectionEnabled = errors.New("error:DeleteProtectionEnabled") const AWsUnauthorizedError string = "UnauthorizedOperation" const AwsDryRunSuccess string = "Request would have succeeded, but DryRun flag is set." @@ -40,6 +41,7 @@ func TransformAWSError(err error) error { if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "InvalidPermission.NotFound" { return ErrInvalidPermisionNotFound } + return err } diff --git a/util/tag.go b/util/tag.go index 3ab99608..0e4c4dd1 100644 --- a/util/tag.go +++ b/util/tag.go @@ -4,6 +4,7 @@ import ( "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/iam" + "github.com/aws/aws-sdk-go/service/networkfirewall" "github.com/aws/aws-sdk-go/service/rds" "github.com/aws/aws-sdk-go/service/s3" ) @@ -70,3 +71,12 @@ func GetEC2ResourceNameTagValue(tags []*ec2.Tag) *string { } return nil } + +func ConvertNetworkFirewallTagsToMap(tags []*networkfirewall.Tag) map[string]string { + tagMap := make(map[string]string) + for _, tag := range tags { + tagMap[*tag.Key] = *tag.Value + } + + return tagMap +}