Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing nuking for Network Firewall resources #688

Merged
merged 1 commit into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions aws/resource_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ func getRegisteredRegionalResources() []AwsResource {
&resources.NetworkInterface{},
&resources.SecurityGroup{},
&resources.NetworkACL{},
&resources.NetworkFirewall{},
&resources.NetworkFirewallPolicy{},
&resources.NetworkFirewallRuleGroup{},
&resources.NetworkFirewallTLSConfig{},
}
}

Expand Down
162 changes: 162 additions & 0 deletions aws/resources/network_firewall.go
Original file line number Diff line number Diff line change
@@ -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
}
146 changes: 146 additions & 0 deletions aws/resources/network_firewall_policy.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading