Repository: gruntwork-io/cloud-nuke
Pull request #691 (2024-04-25 23:43:41)

Handle Global Cluster Nuke Failure
<!-- Prepend '[WIP]' to the title if this PR is still a work-in-progress. Remove it when it is ready for review! -->

## Description

Fixes https://github.com/gruntwork-io/cloud-nuke/issues/597.

<!-- Description of the changes introduced by this PR. -->

## TODOs

Read the [Gruntwork contribution guidelines](https://gruntwork.notion.site/Gruntwork-Coding-Methodology-02fdcd6e4b004e818553684760bf691e).

- [ ] Update the docs.
- [ ] Run the relevant tests successfully, including pre-commit checks.
- [ ] Ensure any 3rd party code adheres with our [license policy](https://www.notion.so/gruntwork/Gruntwork-licenses-and-open-source-usage-policy-f7dece1f780341c7b69c1763f22b1378) or delete this line if its not applicable.
- [ ] Include release notes. If this PR is backward incompatible, include a migration guide.
- [ ] Attention Grunts - if this PR adds support for a new resource, ensure the `nuke_sandbox` and `nuke_phxdevops` jobs in `.circleci/config.yml` have been updated with appropriate exclusions (either directly in the job or via the `.circleci/nuke_config.yml` file) to prevent nuking IAM roles, groups, resources, etc that are important for the test accounts.


## Release Notes (draft)

<!-- One-line description of the PR that can be included in the final release notes. -->
Added / Removed / Updated [X].

### Migration Guide

<!-- Important: If you made any backward incompatible changes, then you must write a migration guide! -->




Patch:
diff --git a/README.md b/README.md
index 55e044e..c260b0b 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,7 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res
 | NetworkFirewall         | Network Firewall Policy                                  |
 | NetworkFirewall         | Network Firewall Rule Group                              |
 | NetworkFirewall         | Network Firewall TLS inspection configuration            |
+| NetworkFirewall         | Network Firewall Resource Policy 						             |
 
 > **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
@@ -625,6 +626,7 @@ of the file that are supported are listed here.
 | 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)             | ✅    |    ❌   |
+| network-firewall-resource-policy | NetworkFirewallResourcePolicy     | ✅ (Firewall Resource Policy ARN)   			  |  ❌             |  ❌    |    ❌   |
 
 
 ### How to Use
diff --git a/aws/resource_registry.go b/aws/resource_registry.go
index 1410d51..0d25ae6 100644
--- a/aws/resource_registry.go
+++ b/aws/resource_registry.go
@@ -33,6 +33,7 @@ func GetAndInitRegisteredResources(session *session.Session, region string) []*A
 // GetRegisteredGlobalResources - returns a list of registered global resources.
 func getRegisteredGlobalResources() []AwsResource {
 	return []AwsResource{
+		&resources.DBGlobalClusters{},
 		&resources.IAMUsers{},
 		&resources.IAMGroups{},
 		&resources.IAMPolicies{},
@@ -99,6 +100,7 @@ func getRegisteredRegionalResources() []AwsResource {
 		&resources.MSKCluster{},
 		&resources.NatGateways{},
 		&resources.OpenSearchDomains{},
+		&resources.DBGlobalClusterMemberships{},
 		&resources.DBInstances{},
 		&resources.DBSubnetGroups{},
 		&resources.DBClusters{},
@@ -138,6 +140,7 @@ func getRegisteredRegionalResources() []AwsResource {
 		&resources.NetworkFirewallPolicy{},
 		&resources.NetworkFirewallRuleGroup{},
 		&resources.NetworkFirewallTLSConfig{},
+		&resources.NetworkFirewallResourcePolicy{},
 	}
 }
 
diff --git a/aws/resources/ec2_vpc.go b/aws/resources/ec2_vpc.go
index a1ddaff..43278ba 100644
--- a/aws/resources/ec2_vpc.go
+++ b/aws/resources/ec2_vpc.go
@@ -4,16 +4,19 @@ import (
 	"context"
 	cerrors "errors"
 	"fmt"
+	"slices"
 	"strconv"
 	"strings"
 	"time"
 
-	"github.com/aws/aws-sdk-go-v2/aws"
-
 	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
+	"github.com/aws/aws-sdk-go/service/elbv2"
+	"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
 	"github.com/gruntwork-io/cloud-nuke/util"
 	"github.com/pterm/pterm"
 
+	"github.com/gruntwork-io/go-commons/retry"
+
 	awsgo "github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/ec2"
 	"github.com/gruntwork-io/cloud-nuke/config"
@@ -64,7 +67,7 @@ func (v *EC2VPCs) getAll(_ context.Context, configObj config.Config) ([]*string,
 			{
 				Name: awsgo.String("is-default"),
 				Values: []*string{
-					aws.String(strconv.FormatBool(configObj.VPC.DefaultOnly)), // convert the bool status into string
+					awsgo.String(strconv.FormatBool(configObj.VPC.DefaultOnly)), // convert the bool status into string
 				},
 			},
 		},
@@ -113,7 +116,7 @@ func (v *EC2VPCs) nukeAll(vpcIds []string) error {
 
 	for _, id := range vpcIds {
 		var err error
-		err = nuke(v.Client, id)
+		err = nuke(v.Client, v.ELBClient, id)
 
 		// Record status of this resource
 		e := report.Entry{
@@ -136,10 +139,30 @@ func (v *EC2VPCs) nukeAll(vpcIds []string) error {
 	return nil
 }
 
-func nuke(client ec2iface.EC2API, vpcID string) error {
+func nuke(client ec2iface.EC2API, elbClient elbv2iface.ELBV2API, vpcID string) error {
+	var err error
 	// Note: order is quite important, otherwise you will encounter dependency violation errors.
+
+	err = nukeAttachedLB(client, elbClient, vpcID)
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Error nuking loadbalancer for VPC %s: %s", vpcID, err.Error()))
+		return err
+	}
+
+	err = nukeTargetGroups(elbClient, vpcID)
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Error nuking target group for VPC %s: %s", vpcID, err.Error()))
+		return err
+	}
+
+	err = nukeEc2Instances(client, vpcID)
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Error nuking instances for VPC %s: %s", vpcID, err.Error()))
+		return err
+	}
+
 	logging.Debug(fmt.Sprintf("Start nuking VPC %s", vpcID))
-	err := nukeDhcpOptions(client, vpcID)
+	err = nukeDhcpOptions(client, vpcID)
 	if err != nil {
 		logging.Debug(fmt.Sprintf("Error cleaning up DHCP Options for VPC %s: %s", vpcID, err.Error()))
 		return err
@@ -169,6 +192,44 @@ func nuke(client ec2iface.EC2API, vpcID string) error {
 		return err
 	}
 
+	// NOTE: Since the network interfaces attached to the load balancer may not be removed immediately after removing the load balancers,
+	// and attempting to remove the internet gateway without waiting for these network interfaces removal will result in an error.
+	// The actual error message states: 'has some mapped public address(es). Please unmap those public address(es) before detaching the gateway.'
+	// Therefore, it is recommended to wait until all the load balancer-related network interfaces are detached and deleted before proceeding.
+	//
+	// Important : The waiting should be happen before nuking the internet gateway
+	err = retry.DoWithRetry(
+		logging.Logger.WithTime(time.Now()),
+		"Waiting for all Network interfaces to be deleted.",
+		// Wait a maximum of 5 minutes: 10 seconds in between, up to 30 times
+		30, 10*time.Second,
+		func() error {
+			interfaces, err := client.DescribeNetworkInterfaces(
+				&ec2.DescribeNetworkInterfacesInput{
+					Filters: []*ec2.Filter{
+						{
+							Name:   awsgo.String("vpc-id"),
+							Values: []*string{awsgo.String(vpcID)},
+						},
+					},
+				},
+			)
+			if err != nil {
+				return errors.WithStackTrace(err)
+			}
+
+			if len(interfaces.NetworkInterfaces) == 0 {
+				return nil
+			}
+
+			return fmt.Errorf("Not all Network interfaces are deleted.")
+		},
+	)
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Error waiting up Network interfaces deletion for VPC %s: %s", vpcID, err.Error()))
+		return errors.WithStackTrace(err)
+	}
+
 	err = nukeInternetGateways(client, vpcID)
 	if err != nil {
 		logging.Debug(fmt.Sprintf("Error cleaning up Internet Gateway for VPC %s: %s", vpcID, err.Error()))
@@ -199,6 +260,12 @@ func nuke(client ec2iface.EC2API, vpcID string) error {
 		return err
 	}
 
+	err = nukePeeringConnections(client, vpcID)
+	if err != nil {
+		pterm.Error.Println(fmt.Sprintf("Error deleting VPC Peer connection %s: %s ", vpcID, err))
+		return err
+	}
+
 	err = nukeVpc(client, vpcID)
 	if err != nil {
 		pterm.Error.Println(fmt.Sprintf("Error deleting VPC %s: %s ", vpcID, err))
@@ -210,6 +277,79 @@ func nuke(client ec2iface.EC2API, vpcID string) error {
 	return nil
 }
 
+func nukePeeringConnections(client ec2iface.EC2API, vpcID string) error {
+	logging.Debug(fmt.Sprintf("Finding VPC peering connections to nuke for: %s", vpcID))
+
+	peerConnections := []*string{}
+	vpcIds := []string{vpcID}
+	requesterFilters := []*ec2.Filter{
+		{
+			Name:   awsgo.String("requester-vpc-info.vpc-id"),
+			Values: awsgo.StringSlice(vpcIds),
+		}, {
+			Name:   awsgo.String("status-code"),
+			Values: awsgo.StringSlice([]string{"active"}),
+		},
+	}
+	accepterFilters := []*ec2.Filter{
+		{
+			Name:   awsgo.String("accepter-vpc-info.vpc-id"),
+			Values: awsgo.StringSlice(vpcIds),
+		}, {
+			Name:   awsgo.String("status-code"),
+			Values: awsgo.StringSlice([]string{"active"}),
+		},
+	}
+
+	// check the peering connection as requester
+	err := client.DescribeVpcPeeringConnectionsPages(
+		&ec2.DescribeVpcPeeringConnectionsInput{
+			Filters: requesterFilters,
+		},
+		func(page *ec2.DescribeVpcPeeringConnectionsOutput, lastPage bool) bool {
+			for _, connection := range page.VpcPeeringConnections {
+				peerConnections = append(peerConnections, connection.VpcPeeringConnectionId)
+			}
+			return !lastPage
+		},
+	)
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Failed to describe vpc peering connections for vpc as requester: %s", vpcID))
+		return errors.WithStackTrace(err)
+	}
+
+	// check the peering connection as accepter
+	err = client.DescribeVpcPeeringConnectionsPages(
+		&ec2.DescribeVpcPeeringConnectionsInput{
+			Filters: accepterFilters,
+		},
+		func(page *ec2.DescribeVpcPeeringConnectionsOutput, lastPage bool) bool {
+			for _, connection := range page.VpcPeeringConnections {
+				peerConnections = append(peerConnections, connection.VpcPeeringConnectionId)
+			}
+			return !lastPage
+		},
+	)
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Failed to describe vpc peering connections for vpc as accepter: %s", vpcID))
+		return errors.WithStackTrace(err)
+	}
+
+	logging.Debug(fmt.Sprintf("Found %d VPC Peering connections to Nuke.", len(peerConnections)))
+
+	for _, connection := range peerConnections {
+		_, err := client.DeleteVpcPeeringConnection(&ec2.DeleteVpcPeeringConnectionInput{
+			VpcPeeringConnectionId: connection,
+		})
+		if err != nil {
+			logging.Debug(fmt.Sprintf("Failed to delete peering connection for vpc: %s", vpcID))
+			return errors.WithStackTrace(err)
+		}
+	}
+
+	return nil
+}
+
 // nukeVpcInternetGateways
 // This function is specifically for VPCs. It retrieves all the internet gateways attached to the given VPC ID and nuke them
 func nukeInternetGateways(client ec2iface.EC2API, vpcID string) error {
@@ -678,3 +818,156 @@ func nukeVpc(client ec2iface.EC2API, vpcID string) error {
 	logging.Debug(fmt.Sprintf("Successfully deleted VPC %s", vpcID))
 	return nil
 }
+
+func nukeAttachedLB(client ec2iface.EC2API, elbclient elbv2iface.ELBV2API, vpcID string) error {
+	logging.Debug(fmt.Sprintf("Describing load balancers for %s", vpcID))
+
+	// get all loadbalancers
+	output, err := elbclient.DescribeLoadBalancers(nil)
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Failed to describe loadbalancer for %s", vpcID))
+		return errors.WithStackTrace(err)
+	}
+	var attachedLoadBalancers []string
+
+	// get a list of load balancers which was attached on the vpc
+	for _, lb := range output.LoadBalancers {
+		if awsgo.StringValue(lb.VpcId) != vpcID {
+			continue
+		}
+
+		attachedLoadBalancers = append(attachedLoadBalancers, awsgo.StringValue(lb.LoadBalancerArn))
+	}
+
+	// check the load-balancers are attached with any vpc-endpoint-service, then nuke them first
+	esoutput, err := client.DescribeVpcEndpointServiceConfigurations(nil)
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Failed to describe vpc endpoint services for %s", vpcID))
+		return errors.WithStackTrace(err)
+	}
+
+	// since we don't want duplicating the endpoint service ids, using a map here
+	var nukableEndpointServices = make(map[*string]struct{})
+	for _, config := range esoutput.ServiceConfigurations {
+		// check through gateway load balancer attachments and select the service for nuking
+		for _, gwlb := range config.GatewayLoadBalancerArns {
+			if slices.Contains(attachedLoadBalancers, awsgo.StringValue(gwlb)) {
+				nukableEndpointServices[config.ServiceId] = struct{}{}
+			}
+		}
+
+		// check through network load balancer attachments and select the service for nuking
+		for _, nwlb := range config.NetworkLoadBalancerArns {
+			if slices.Contains(attachedLoadBalancers, awsgo.StringValue(nwlb)) {
+				nukableEndpointServices[config.ServiceId] = struct{}{}
+			}
+		}
+	}
+
+	logging.Debug(fmt.Sprintf("Found %d Endpoint services attached with the load balancers to nuke.", len(nukableEndpointServices)))
+
+	// nuke the endpoint services
+	for endpointService := range nukableEndpointServices {
+		_, err := client.DeleteVpcEndpointServiceConfigurations(&ec2.DeleteVpcEndpointServiceConfigurationsInput{
+			ServiceIds: []*string{endpointService},
+		})
+		if err != nil {
+			logging.Debug(fmt.Sprintf("Failed to delete endpoint service %v for %s", awsgo.StringValue(endpointService), vpcID))
+			return errors.WithStackTrace(err)
+		}
+	}
+	logging.Debug(fmt.Sprintf("Successfully deleted he endpoints attached with load balancers for %s", vpcID))
+
+	// nuke the load-balancers
+	for _, lb := range attachedLoadBalancers {
+		_, err := elbclient.DeleteLoadBalancer(&elbv2.DeleteLoadBalancerInput{
+			LoadBalancerArn: awsgo.String(lb),
+		})
+		if err != nil {
+			logging.Debug(fmt.Sprintf("Failed to delete loadbalancer %v for %s", lb, vpcID))
+			return errors.WithStackTrace(err)
+		}
+	}
+
+	logging.Debug(fmt.Sprintf("Successfully deleted loadbalancers attached %s", vpcID))
+	return nil
+}
+
+func nukeTargetGroups(client elbv2iface.ELBV2API, vpcID string) error {
+	logging.Debug(fmt.Sprintf("Describing target groups for %s", vpcID))
+
+	output, err := client.DescribeTargetGroups(&elbv2.DescribeTargetGroupsInput{})
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Failed to describe target groups for %s", vpcID))
+		return errors.WithStackTrace(err)
+	}
+
+	for _, tg := range output.TargetGroups {
+		// if the target group is not for this vpc, then skip
+		if tg.VpcId != nil && awsgo.StringValue(tg.VpcId) == vpcID {
+			_, err := client.DeleteTargetGroup(&elbv2.DeleteTargetGroupInput{
+				TargetGroupArn: tg.TargetGroupArn,
+			})
+			if err != nil {
+				logging.Debug(fmt.Sprintf("Failed to delete target group %v for %s", *tg.TargetGroupArn, vpcID))
+				return errors.WithStackTrace(err)
+			}
+		}
+
+	}
+
+	logging.Debug(fmt.Sprintf("Successfully deleted target group attached %s", vpcID))
+
+	return nil
+}
+
+func nukeEc2Instances(client ec2iface.EC2API, vpcID string) error {
+	logging.Debug(fmt.Sprintf("Describing instances for %s", vpcID))
+	output, err := client.DescribeInstances(&ec2.DescribeInstancesInput{
+		Filters: []*ec2.Filter{
+			{
+				Name: awsgo.String("network-interface.vpc-id"),
+				Values: awsgo.StringSlice([]string{
+					vpcID,
+				}),
+			},
+		},
+	})
+
+	if err != nil {
+		logging.Debug(fmt.Sprintf("Failed to describe instances for %s", vpcID))
+		return errors.WithStackTrace(err)
+	}
+
+	var terminateInstances []*string
+	for _, instance := range output.Reservations {
+		for _, i := range instance.Instances {
+			terminateInstances = append(terminateInstances, i.InstanceId)
+		}
+	}
+
+	if len(terminateInstances) > 0 {
+		logging.Debug(fmt.Sprintf("Found %d VPC attached instances to Nuke.", len(terminateInstances)))
+
+		_, err := client.TerminateInstances(&ec2.TerminateInstancesInput{
+			InstanceIds: terminateInstances,
+		})
+		if err != nil {
+			logging.Debug(fmt.Sprintf("Failed to terminate instances for %s", vpcID))
+			return errors.WithStackTrace(err)
+		}
+
+		// weight for terminate the instances
+		logging.Debug(fmt.Sprintf("waiting for the instance to be terminated for %s", vpcID))
+		err = client.WaitUntilInstanceTerminated(&ec2.DescribeInstancesInput{
+			InstanceIds: terminateInstances,
+		})
+		if err != nil {
+			logging.Debug(fmt.Sprintf("Failed to wait instance termination for %s", vpcID))
+			return errors.WithStackTrace(err)
+		}
+	}
+
+	logging.Debug(fmt.Sprintf("Successfully deleted instances for %s", vpcID))
+	return nil
+}
diff --git a/aws/resources/ec2_vpc_types.go b/aws/resources/ec2_vpc_types.go
index 4f3105e..d934e40 100644
--- a/aws/resources/ec2_vpc_types.go
+++ b/aws/resources/ec2_vpc_types.go
@@ -7,19 +7,23 @@ import (
 	"github.com/aws/aws-sdk-go/aws/session"
 	"github.com/aws/aws-sdk-go/service/ec2"
 	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
+	"github.com/aws/aws-sdk-go/service/elbv2"
+	"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
 	"github.com/gruntwork-io/cloud-nuke/config"
 	"github.com/gruntwork-io/go-commons/errors"
 )
 
 type EC2VPCs struct {
 	BaseAwsResource
-	Client ec2iface.EC2API
-	Region string
-	VPCIds []string
+	Client    ec2iface.EC2API
+	ELBClient elbv2iface.ELBV2API
+	Region    string
+	VPCIds    []string
 }
 
 func (v *EC2VPCs) Init(session *session.Session) {
 	v.Client = ec2.New(session)
+	v.ELBClient = elbv2.New(session)
 }
 
 // ResourceName - the simple name of the aws resource
diff --git a/aws/resources/network_firewall_resource_policy.go b/aws/resources/network_firewall_resource_policy.go
new file mode 100644
index 0000000..57f28af
--- /dev/null
+++ b/aws/resources/network_firewall_resource_policy.go
@@ -0,0 +1,97 @@
+package resources
+
+import (
+	"context"
+
+	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"
+)
+
+// The Network Firewall service supports only one type of resource-based policy called a resource policy, which is attached to a shared firewall policy or rule group
+// References :
+// - https://docs.aws.amazon.com/network-firewall/latest/developerguide/security_iam_resource-based-policy-examples.html
+// - https://docs.aws.amazon.com/network-firewall/latest/developerguide/sharing.html
+// - https://docs.aws.amazon.com/ram/latest/userguide/what-is.html
+func (nfw *NetworkFirewallResourcePolicy) getAll(_ context.Context, configObj config.Config) ([]*string, error) {
+	var identifiers []*string
+
+	var resourceArns []*string
+	// list the firewall policies and rule group
+
+	{
+		policyMeta, err := nfw.Client.ListFirewallPolicies(nil)
+		if err != nil {
+			return nil, errors.WithStackTrace(err)
+		}
+
+		for _, policy := range policyMeta.FirewallPolicies {
+			resourceArns = append(resourceArns, policy.Arn)
+		}
+		groupMeta, err := nfw.Client.ListRuleGroups(nil)
+		if err != nil {
+			return nil, errors.WithStackTrace(err)
+		}
+		for _, group := range groupMeta.RuleGroups {
+			resourceArns = append(resourceArns, group.Arn)
+		}
+	}
+
+	// get the resource policies attached on these arns
+	for _, arn := range resourceArns {
+		output, err := nfw.Client.DescribeResourcePolicy(&networkfirewall.DescribeResourcePolicyInput{
+			ResourceArn: arn,
+		})
+		if err != nil && util.TransformAWSError(err) != util.ErrResourceNotFoundException {
+			return nil, errors.WithStackTrace(err)
+		}
+
+		// if the policy exists for a resource
+		if output.Policy != nil {
+			if configObj.NetworkFirewallResourcePolicy.ShouldInclude(config.ResourceValue{
+				Name: arn,
+			}) {
+				identifiers = append(identifiers, arn)
+			}
+		}
+	}
+	return identifiers, nil
+}
+
+func (nfw *NetworkFirewallResourcePolicy) nukeAll(identifiers []*string) error {
+	if len(identifiers) == 0 {
+		logging.Debugf("No Network Firewall resource policy to nuke in region %s", nfw.Region)
+		return nil
+	}
+
+	logging.Debugf("Deleting Network firewall resource policy in region %s", nfw.Region)
+	var deleted []*string
+
+	for _, id := range identifiers {
+		_, err := nfw.Client.DeleteResourcePolicy(&networkfirewall.DeleteResourcePolicyInput{
+			ResourceArn: id,
+		})
+
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   awsgo.StringValue(id),
+			ResourceType: "Network Firewall Resource policy",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err != nil {
+			logging.Debugf("[Failed] %s", err)
+		} else {
+			deleted = append(deleted, id)
+		}
+	}
+
+	logging.Debugf("[OK] %d Network Resource Policy(s) deleted in %s", len(deleted), nfw.Region)
+
+	return nil
+}
diff --git a/aws/resources/network_firewall_resource_policy_types.go b/aws/resources/network_firewall_resource_policy_types.go
new file mode 100644
index 0000000..557beac
--- /dev/null
+++ b/aws/resources/network_firewall_resource_policy_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 NetworkFirewallResourcePolicy struct {
+	BaseAwsResource
+	Client      networkfirewalliface.NetworkFirewallAPI
+	Region      string
+	Identifiers []string
+}
+
+func (nfrp *NetworkFirewallResourcePolicy) Init(session *session.Session) {
+	nfrp.Client = networkfirewall.New(session)
+}
+
+// ResourceName - the simple name of the aws resource
+func (nfrp *NetworkFirewallResourcePolicy) ResourceName() string {
+	return "network-firewall-resource-policy"
+}
+
+func (nfrp *NetworkFirewallResourcePolicy) ResourceIdentifiers() []string {
+	return nfrp.Identifiers
+}
+
+func (nfrp *NetworkFirewallResourcePolicy) 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 (nfrp *NetworkFirewallResourcePolicy) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := nfrp.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	nfrp.Identifiers = awsgo.StringValueSlice(identifiers)
+	return nfrp.Identifiers, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (nfrp *NetworkFirewallResourcePolicy) Nuke(identifiers []string) error {
+	if err := nfrp.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/aws/resources/rds_cluster.go b/aws/resources/rds_cluster.go
index 6e0cd22..a1826c5 100644
--- a/aws/resources/rds_cluster.go
+++ b/aws/resources/rds_cluster.go
@@ -2,9 +2,10 @@ package resources
 
 import (
 	"context"
-	"github.com/gruntwork-io/cloud-nuke/util"
 	"time"
 
+	"github.com/gruntwork-io/cloud-nuke/util"
+
 	awsgo "github.com/aws/aws-sdk-go/aws"
 
 	"github.com/aws/aws-sdk-go/aws"
@@ -90,7 +91,6 @@ func (instance *DBClusters) nukeAll(names []*string) error {
 
 	if len(deletedNames) > 0 {
 		for _, name := range deletedNames {
-
 			err := instance.waitUntilRdsClusterDeleted(&rds.DescribeDBClustersInput{
 				DBClusterIdentifier: name,
 			})
diff --git a/aws/resources/rds_global_cluster.go b/aws/resources/rds_global_cluster.go
new file mode 100644
index 0000000..a3e264f
--- /dev/null
+++ b/aws/resources/rds_global_cluster.go
@@ -0,0 +1,106 @@
+package resources
+
+import (
+	"context"
+	"time"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/awserr"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/rds"
+	"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/go-commons/errors"
+)
+
+// wait up to 15 minutes
+const (
+	dbGlobalClusterDeletionRetryDelay = 10 * time.Second
+	dbGlobalClusterDeletionRetryCount = 90
+)
+
+func (instance *DBGlobalClusters) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+	result, err := instance.Client.DescribeGlobalClustersWithContext(c, &rds.DescribeGlobalClustersInput{})
+	if err != nil {
+		return nil, errors.WithStackTrace(err)
+	}
+
+	var names []*string
+	for _, cluster := range result.GlobalClusters {
+		if !configObj.DBGlobalClusters.ShouldInclude(config.ResourceValue{
+			Name: cluster.GlobalClusterIdentifier,
+		}) {
+			continue
+		}
+
+		names = append(names, cluster.GlobalClusterIdentifier)
+	}
+
+	return names, nil
+}
+
+func (instance *DBGlobalClusters) nukeAll(names []*string) error {
+	if len(names) == 0 {
+		logging.Debugf("No RDS DB Global Cluster Membership to nuke")
+		return nil
+	}
+
+	logging.Debugf("Deleting Global Cluster (members)")
+	deletedNames := []*string{}
+
+	for _, name := range names {
+		_, err := instance.Client.DeleteGlobalCluster(&rds.DeleteGlobalClusterInput{
+			GlobalClusterIdentifier: name,
+		})
+
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(name),
+			ResourceType: "RDS Global Cluster Membership",
+			Error:        err,
+		}
+		report.Record(e)
+
+		switch {
+		case err != nil:
+			logging.Debugf("[Failed] %s: %s", *name, err)
+
+		default:
+			deletedNames = append(deletedNames, name)
+			logging.Debugf("Deleted RDS DB Global Cluster Membership: %s", awsgo.StringValue(name))
+		}
+	}
+
+	for _, name := range deletedNames {
+		err := instance.waitUntilRDSGlobalClusterDeleted(*name)
+		if err != nil {
+			logging.Errorf("[Failed] %s", err)
+			return errors.WithStackTrace(err)
+		}
+	}
+
+	logging.Debugf("[OK] %d RDS Global DB Cluster(s) Membership nuked in %s", len(deletedNames), instance.Region)
+	return nil
+}
+
+func (instance *DBGlobalClusters) waitUntilRDSGlobalClusterDeleted(name string) error {
+	for i := 0; i < dbGlobalClusterDeletionRetryCount; i++ {
+		_, err := instance.Client.DescribeGlobalClusters(&rds.DescribeGlobalClustersInput{
+			GlobalClusterIdentifier: &name,
+		})
+		if err != nil {
+			if awsErr, isAwsErr := err.(awserr.Error); isAwsErr && awsErr.Code() == rds.ErrCodeGlobalClusterNotFoundFault {
+				return nil
+			}
+
+			return errors.WithStackTrace(err)
+		}
+
+		time.Sleep(dbGlobalClusterDeletionRetryDelay)
+		logging.Debug("Waiting for RDS Global Cluster to be deleted")
+	}
+
+	return RdsDeleteError{name: name}
+}
diff --git a/aws/resources/rds_global_cluster_membership.go b/aws/resources/rds_global_cluster_membership.go
new file mode 100644
index 0000000..2863103
--- /dev/null
+++ b/aws/resources/rds_global_cluster_membership.go
@@ -0,0 +1,151 @@
+package resources
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"time"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/rds"
+	"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/go-commons/errors"
+)
+
+// wait up to 15 minutes
+const (
+	dbGlobalClusterMembershipsRemovalRetryDelay = 10 * time.Second
+	dbGlobalClusterMembershipsRemovalRetryCount = 90
+)
+
+func (instance *DBGlobalClusterMemberships) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+	result, err := instance.Client.DescribeGlobalClustersWithContext(c, &rds.DescribeGlobalClustersInput{})
+	if err != nil {
+		return nil, errors.WithStackTrace(err)
+	}
+
+	var names []*string
+	for _, cluster := range result.GlobalClusters {
+		if !configObj.DBGlobalClusterMemberships.ShouldInclude(config.ResourceValue{
+			Name: cluster.GlobalClusterIdentifier,
+		}) {
+			continue
+		}
+
+		names = append(names, cluster.GlobalClusterIdentifier)
+	}
+
+	return names, nil
+}
+
+func (instance *DBGlobalClusterMemberships) nukeAll(names []*string) error {
+	if len(names) == 0 {
+		logging.Debugf("No RDS DB Global Cluster Membership to nuke")
+		return nil
+	}
+
+	logging.Debugf("Deleting Global Cluster (members)")
+	deletedNames := []*string{}
+
+	for _, name := range names {
+		deleted, err := instance.removeGlobalClusterMembership(*name)
+
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(name),
+			ResourceType: "RDS Global Cluster Membership",
+			Error:        err,
+		}
+		report.Record(e)
+
+		switch {
+		case err != nil:
+			logging.Debugf("[Failed] %s: %s", *name, err)
+
+		case !deleted:
+			logging.Debugf("No RDS Global Cluster Membership was deleted on %s", *name)
+
+		default:
+			deletedNames = append(deletedNames, name)
+			logging.Debugf("Deleted RDS DB Global Cluster Membership: %s", awsgo.StringValue(name))
+		}
+	}
+
+	logging.Debugf("[OK] %d RDS Global DB Cluster(s) Membership nuked in %s", len(deletedNames), instance.Region)
+	return nil
+}
+
+func (instance *DBGlobalClusterMemberships) removeGlobalClusterMembership(name string) (deleted bool, err error) {
+	gdbcs, err := instance.Client.DescribeGlobalClusters(&rds.DescribeGlobalClustersInput{
+		GlobalClusterIdentifier: &name,
+	})
+	if err != nil {
+		return deleted, fmt.Errorf("fail to describe global cluster: %w", err)
+	}
+	if len(gdbcs.GlobalClusters) != 1 || *gdbcs.GlobalClusters[0].GlobalClusterIdentifier != name {
+		return deleted, fmt.Errorf("unexpected describe result global cluster")
+	}
+	gdbc := gdbcs.GlobalClusters[0]
+
+	deletedNames := []string{}
+	for _, member := range gdbc.GlobalClusterMembers {
+		region := strings.Split(*member.DBClusterArn, ":")[3]
+		if instance.Region != "" && instance.Region != region {
+			logging.Debugf("Skip removing cluster '%s' from global cluster since it is in different region", *member.DBClusterArn)
+			continue
+		}
+
+		logging.Debugf("Removing cluster '%s' from global cluster", *member.DBClusterArn)
+		_, err := instance.Client.RemoveFromGlobalCluster(&rds.RemoveFromGlobalClusterInput{
+			GlobalClusterIdentifier: gdbc.GlobalClusterIdentifier,
+			DbClusterIdentifier:     member.DBClusterArn,
+		})
+		if err != nil {
+			return deleted, fmt.Errorf("fail to remove cluster '%s' from global cluster :%w", *member, err)
+		}
+		deletedNames = append(deletedNames, *member.DBClusterArn)
+	}
+	for _, name := range deletedNames {
+		err = instance.waitUntilRdsClusterRemovedFromGlobalCluster(*gdbc.GlobalClusterIdentifier, name)
+		if err != nil {
+			return deleted, fmt.Errorf("fail to remove cluster '%s' from global cluster :%w", name, err)
+		}
+	}
+
+	return len(deletedNames) > 0, nil
+}
+
+func (instance *DBGlobalClusterMemberships) waitUntilRdsClusterRemovedFromGlobalCluster(arnGlobalCluster string, arnCluster string) error {
+	for i := 0; i < dbGlobalClusterMembershipsRemovalRetryCount; i++ {
+		gcs, err := instance.Client.DescribeGlobalClusters(&rds.DescribeGlobalClustersInput{
+			GlobalClusterIdentifier: &arnGlobalCluster,
+		})
+		if err != nil {
+			return errors.WithStackTrace(err)
+		}
+
+		found := false
+		for _, gc := range gcs.GlobalClusters {
+			for _, m := range gc.GlobalClusterMembers {
+				if *m.DBClusterArn != arnCluster {
+					continue
+				}
+
+				found = true
+				break
+			}
+		}
+		if !found {
+			return nil
+		}
+
+		time.Sleep(dbGlobalClusterMembershipsRemovalRetryDelay)
+		logging.Debug("Waiting for RDS Cluster to be removed from RDS Global Cluster")
+	}
+
+	return RdsDeleteError{name: arnCluster}
+}
diff --git a/aws/resources/rds_global_cluster_membership_types.go b/aws/resources/rds_global_cluster_membership_types.go
new file mode 100644
index 0000000..8ee4998
--- /dev/null
+++ b/aws/resources/rds_global_cluster_membership_types.go
@@ -0,0 +1,56 @@
+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/rds"
+	"github.com/aws/aws-sdk-go/service/rds/rdsiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+type DBGlobalClusterMemberships struct {
+	BaseAwsResource
+	Client        rdsiface.RDSAPI
+	Region        string
+	InstanceNames []string
+}
+
+func (instance *DBGlobalClusterMemberships) Init(session *session.Session) {
+	instance.Client = rds.New(session)
+}
+
+func (instance *DBGlobalClusterMemberships) ResourceName() string {
+	return "rds-global-cluster-membership"
+}
+
+// ResourceIdentifiers - The instance names of the rds db instances
+func (instance *DBGlobalClusterMemberships) ResourceIdentifiers() []string {
+	return instance.InstanceNames
+}
+
+func (instance *DBGlobalClusterMemberships) MaxBatchSize() int {
+	// Tentative batch size to ensure AWS doesn't throttle
+	return 49
+}
+
+func (instance *DBGlobalClusterMemberships) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := instance.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	instance.InstanceNames = awsgo.StringValueSlice(identifiers)
+	return instance.InstanceNames, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (instance *DBGlobalClusterMemberships) Nuke(identifiers []string) error {
+	if err := instance.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/aws/resources/rds_global_cluster_types_.go b/aws/resources/rds_global_cluster_types_.go
new file mode 100644
index 0000000..b7f2fe6
--- /dev/null
+++ b/aws/resources/rds_global_cluster_types_.go
@@ -0,0 +1,56 @@
+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/rds"
+	"github.com/aws/aws-sdk-go/service/rds/rdsiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+type DBGlobalClusters struct {
+	BaseAwsResource
+	Client        rdsiface.RDSAPI
+	Region        string
+	InstanceNames []string
+}
+
+func (instance *DBGlobalClusters) Init(session *session.Session) {
+	instance.Client = rds.New(session)
+}
+
+func (instance *DBGlobalClusters) ResourceName() string {
+	return "rds-global-cluster"
+}
+
+// ResourceIdentifiers - The instance names of the rds db instances
+func (instance *DBGlobalClusters) ResourceIdentifiers() []string {
+	return instance.InstanceNames
+}
+
+func (instance *DBGlobalClusters) MaxBatchSize() int {
+	// Tentative batch size to ensure AWS doesn't throttle
+	return 49
+}
+
+func (instance *DBGlobalClusters) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := instance.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	instance.InstanceNames = awsgo.StringValueSlice(identifiers)
+	return instance.InstanceNames, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (instance *DBGlobalClusters) Nuke(identifiers []string) error {
+	if err := instance.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/config/config.go b/config/config.go
index 6c4f44d..b1f7a88 100644
--- a/config/config.go
+++ b/config/config.go
@@ -30,8 +30,10 @@ type Config struct {
 	CodeDeployApplications          ResourceType               `yaml:"CodeDeployApplications"`
 	ConfigServiceRecorder           ResourceType               `yaml:"ConfigServiceRecorder"`
 	ConfigServiceRule               ResourceType               `yaml:"ConfigServiceRule"`
+	DBGlobalClusters                ResourceType               `yaml:"DBGlobalClusters"`
 	DBClusters                      ResourceType               `yaml:"DBClusters"`
 	DBInstances                     ResourceType               `yaml:"DBInstances"`
+	DBGlobalClusterMemberships      ResourceType               `yaml:"DBGlobalClusterMemberships"`
 	DBSubnetGroups                  ResourceType               `yaml:"DBSubnetGroups"`
 	DynamoDB                        ResourceType               `yaml:"DynamoDB"`
 	EBSVolume                       ResourceType               `yaml:"EBSVolume"`
@@ -109,6 +111,7 @@ type Config struct {
 	NetworkFirewallPolicy           ResourceType               `yaml:"NetworkFirewallPolicy"`
 	NetworkFirewallRuleGroup        ResourceType               `yaml:"NetworkFirewallRuleGroup"`
 	NetworkFirewallTLSConfig        ResourceType               `yaml:"NetworkFirewallTLSConfig"`
+	NetworkFirewallResourcePolicy   ResourceType               `yaml:"NetworkFirewallResourcePolicy"`
 }
 
 func (c *Config) addTimeAfterFilter(timeFilter *time.Time, fieldName string) {
diff --git a/util/error.go b/util/error.go
index 735550e..ec13d5d 100644
--- a/util/error.go
+++ b/util/error.go
@@ -14,6 +14,7 @@ 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")
+var ErrResourceNotFoundException = errors.New("error:ErrResourceNotFoundException")
 
 const AWsUnauthorizedError string = "UnauthorizedOperation"
 const AwsDryRunSuccess string = "Request would have succeeded, but DryRun flag is set."
@@ -41,6 +42,9 @@ func TransformAWSError(err error) error {
 	if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "InvalidPermission.NotFound" {
 		return ErrInvalidPermisionNotFound
 	}
+	if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" {
+		return ErrResourceNotFoundException
+	}
 
 	return err
 


Test patch:
diff --git a/aws/resources/ec2_vpc_test.go b/aws/resources/ec2_vpc_test.go
index cbee4bf..9e1e2c7 100644
--- a/aws/resources/ec2_vpc_test.go
+++ b/aws/resources/ec2_vpc_test.go
@@ -6,10 +6,12 @@ import (
 	"testing"
 	"time"
 
-	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go/aws"
 	awsgo "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/aws/aws-sdk-go/service/elbv2"
+	"github.com/aws/aws-sdk-go/service/elbv2/elbv2iface"
 	"github.com/gruntwork-io/cloud-nuke/config"
 	"github.com/gruntwork-io/cloud-nuke/util"
 	"github.com/stretchr/testify/require"
@@ -17,8 +19,13 @@ import (
 
 type mockedEC2VPCs struct {
 	ec2iface.EC2API
-	DescribeVpcsOutput ec2.DescribeVpcsOutput
-	DeleteVpcOutput    ec2.DeleteVpcOutput
+	DescribeVpcsOutput                             ec2.DescribeVpcsOutput
+	DeleteVpcOutput                                ec2.DeleteVpcOutput
+	DescribeVpcPeeringConnectionsOutput            ec2.DescribeVpcPeeringConnectionsOutput
+	DescribeInstancesOutput                        ec2.DescribeInstancesOutput
+	TerminateInstancesOutput                       ec2.TerminateInstancesOutput
+	DescribeVpcEndpointServiceConfigurationsOutput ec2.DescribeVpcEndpointServiceConfigurationsOutput
+	DeleteVpcEndpointServiceConfigurationsOutput   ec2.DeleteVpcEndpointServiceConfigurationsOutput
 }
 
 func (m mockedEC2VPCs) DescribeVpcs(input *ec2.DescribeVpcsInput) (*ec2.DescribeVpcsOutput, error) {
@@ -28,7 +35,27 @@ func (m mockedEC2VPCs) DescribeVpcs(input *ec2.DescribeVpcsInput) (*ec2.Describe
 func (m mockedEC2VPCs) DeleteVpc(input *ec2.DeleteVpcInput) (*ec2.DeleteVpcOutput, error) {
 	return &m.DeleteVpcOutput, nil
 }
+func (m mockedEC2VPCs) DescribeVpcPeeringConnectionsPages(input *ec2.DescribeVpcPeeringConnectionsInput, callback func(page *ec2.DescribeVpcPeeringConnectionsOutput, lastPage bool) bool) error {
+	callback(&m.DescribeVpcPeeringConnectionsOutput, true)
+	return nil
+}
+func (m mockedEC2VPCs) DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
+	return &m.DescribeInstancesOutput, nil
+}
+
+func (m mockedEC2VPCs) TerminateInstances(*ec2.TerminateInstancesInput) (*ec2.TerminateInstancesOutput, error) {
+	return &m.TerminateInstancesOutput, nil
+}
+func (m mockedEC2VPCs) WaitUntilInstanceTerminated(*ec2.DescribeInstancesInput) error {
+	return nil
+}
 
+func (m mockedEC2VPCs) DescribeVpcEndpointServiceConfigurations(*ec2.DescribeVpcEndpointServiceConfigurationsInput) (*ec2.DescribeVpcEndpointServiceConfigurationsOutput, error) {
+	return &m.DescribeVpcEndpointServiceConfigurationsOutput, nil
+}
+func (m mockedEC2VPCs) DeleteVpcEndpointServiceConfigurations(*ec2.DeleteVpcEndpointServiceConfigurationsInput) (*ec2.DeleteVpcEndpointServiceConfigurationsOutput, error) {
+	return &m.DeleteVpcEndpointServiceConfigurationsOutput, nil
+}
 func TestEC2VPC_GetAll(t *testing.T) {
 
 	t.Parallel()
@@ -128,3 +155,115 @@ func TestEC2VPC_GetAll(t *testing.T) {
 //	err := vpc.nukeAll([]string{"test-vpc-id1", "test-vpc-id2"})
 //	require.NoError(t, err)
 //}
+
+func TestEC2VPCPeeringConnections_NukeAll(t *testing.T) {
+	t.Parallel()
+	vpc := EC2VPCs{
+		Client: mockedEC2VPCs{
+			DescribeVpcPeeringConnectionsOutput: ec2.DescribeVpcPeeringConnectionsOutput{},
+		},
+	}
+
+	err := nukePeeringConnections(vpc.Client, "vpc-test-00001")
+	require.NoError(t, err)
+}
+
+type mockedEC2ELB struct {
+	elbv2iface.ELBV2API
+	DescribeLoadBalancersOutput elbv2.DescribeLoadBalancersOutput
+	DeleteLoadBalancerOutput    elbv2.DeleteLoadBalancerOutput
+
+	DescribeTargetGroupsOutput elbv2.DescribeTargetGroupsOutput
+	DeleteTargetGroupOutput    elbv2.DeleteTargetGroupOutput
+}
+
+func (m mockedEC2ELB) DescribeLoadBalancers(*elbv2.DescribeLoadBalancersInput) (*elbv2.DescribeLoadBalancersOutput, error) {
+	return &m.DescribeLoadBalancersOutput, nil
+}
+func (m mockedEC2ELB) DescribeTargetGroups(*elbv2.DescribeTargetGroupsInput) (*elbv2.DescribeTargetGroupsOutput, error) {
+	return &m.DescribeTargetGroupsOutput, nil
+}
+
+func (m mockedEC2ELB) DeleteLoadBalancer(*elbv2.DeleteLoadBalancerInput) (*elbv2.DeleteLoadBalancerOutput, error) {
+	return &m.DeleteLoadBalancerOutput, nil
+}
+func (m mockedEC2ELB) DeleteTargetGroup(*elbv2.DeleteTargetGroupInput) (*elbv2.DeleteTargetGroupOutput, error) {
+	return &m.DeleteTargetGroupOutput, nil
+}
+
+func TestAttachedLB_Nuke(t *testing.T) {
+	t.Parallel()
+	vpcID := "vpc-0e9a3e7c72d9f3a0f"
+
+	vpc := EC2VPCs{
+		Client: mockedEC2VPCs{
+			DescribeVpcEndpointServiceConfigurationsOutput: ec2.DescribeVpcEndpointServiceConfigurationsOutput{
+				ServiceConfigurations: []*ec2.ServiceConfiguration{
+					{
+						GatewayLoadBalancerArns: awsgo.StringSlice([]string{
+							"load-balancer-arn-00012",
+						}),
+					},
+				},
+			},
+		},
+		ELBClient: mockedEC2ELB{
+			DescribeLoadBalancersOutput: elbv2.DescribeLoadBalancersOutput{
+				LoadBalancers: []*elbv2.LoadBalancer{
+					{
+						VpcId:           awsgo.String(vpcID),
+						LoadBalancerArn: awsgo.String("load-balancer-arn-00012"),
+					},
+				},
+			},
+		},
+	}
+
+	err := nukeAttachedLB(vpc.Client, vpc.ELBClient, vpcID)
+	require.NoError(t, err)
+}
+
+func TestTargetGroup_Nuke(t *testing.T) {
+	t.Parallel()
+	vpcID := "vpc-0e9a3e7c72d9f3a0f"
+
+	vpc := EC2VPCs{
+		ELBClient: mockedEC2ELB{
+			DescribeTargetGroupsOutput: elbv2.DescribeTargetGroupsOutput{
+				TargetGroups: []*elbv2.TargetGroup{
+					{
+						VpcId:          awsgo.String(vpcID),
+						TargetGroupArn: awsgo.String("arn:aws:elasticloadbalancing:us-east-1:tg-001"),
+					},
+				},
+			},
+		},
+	}
+
+	err := nukeTargetGroups(vpc.ELBClient, vpcID)
+	require.NoError(t, err)
+}
+
+func TestEc2Instance_Nuke(t *testing.T) {
+	t.Parallel()
+	vpcID := "vpc-0e9a3e7c72d9f3a0f"
+
+	vpc := EC2VPCs{
+		Client: mockedEC2VPCs{
+			DescribeInstancesOutput: ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							{
+								InstanceId: awsgo.String("instance-001"),
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	err := nukeEc2Instances(vpc.Client, vpcID)
+	require.NoError(t, err)
+}
diff --git a/aws/resources/network_firewall_resource_policy_test.go b/aws/resources/network_firewall_resource_policy_test.go
new file mode 100644
index 0000000..6d20658
--- /dev/null
+++ b/aws/resources/network_firewall_resource_policy_test.go
@@ -0,0 +1,122 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+
+	"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/stretchr/testify/require"
+)
+
+type mockedNetworkFirewallResourcePolicy struct {
+	networkfirewalliface.NetworkFirewallAPI
+	ListFirewallPoliciesOutput   networkfirewall.ListFirewallPoliciesOutput
+	ListRuleGroupsOutput         networkfirewall.ListRuleGroupsOutput
+	DeleteResourcePolicyOutput   networkfirewall.DeleteResourcePolicyOutput
+	DescribeResourcePolicyOutput networkfirewall.DescribeResourcePolicyOutput
+}
+
+func (m mockedNetworkFirewallResourcePolicy) ListFirewallPolicies(*networkfirewall.ListFirewallPoliciesInput) (*networkfirewall.ListFirewallPoliciesOutput, error) {
+	return &m.ListFirewallPoliciesOutput, nil
+}
+
+func (m mockedNetworkFirewallResourcePolicy) ListRuleGroups(*networkfirewall.ListRuleGroupsInput) (*networkfirewall.ListRuleGroupsOutput, error) {
+	return &m.ListRuleGroupsOutput, nil
+}
+func (m mockedNetworkFirewallResourcePolicy) DeleteResourcePolicy(*networkfirewall.DeleteResourcePolicyInput) (*networkfirewall.DeleteResourcePolicyOutput, error) {
+	return &m.DeleteResourcePolicyOutput, nil
+}
+
+func (m mockedNetworkFirewallResourcePolicy) DescribeResourcePolicy(*networkfirewall.DescribeResourcePolicyInput) (*networkfirewall.DescribeResourcePolicyOutput, error) {
+	return &m.DescribeResourcePolicyOutput, nil
+}
+
+func TestNetworkFirewallResourcePolicy_GetAll(t *testing.T) {
+
+	t.Parallel()
+
+	var (
+		policy1 = "test-network-firewall-policy-1"
+		policy2 = "test-network-firewall-policy-2"
+		group1  = "test-network-firewall-group-1"
+		group2  = "test-network-firewall-group-2"
+	)
+
+	nfw := NetworkFirewallResourcePolicy{
+		Client: mockedNetworkFirewallResourcePolicy{
+			ListFirewallPoliciesOutput: networkfirewall.ListFirewallPoliciesOutput{
+				FirewallPolicies: []*networkfirewall.FirewallPolicyMetadata{
+					{
+						Arn: awsgo.String(policy1),
+					},
+					{
+						Arn: awsgo.String(policy2),
+					},
+				},
+			},
+			ListRuleGroupsOutput: networkfirewall.ListRuleGroupsOutput{
+				RuleGroups: []*networkfirewall.RuleGroupMetadata{
+					{
+						Arn: awsgo.String(group1),
+					},
+					{
+						Arn: awsgo.String(group2),
+					},
+				},
+			},
+			DescribeResourcePolicyOutput: networkfirewall.DescribeResourcePolicyOutput{
+				Policy: aws.String("policy-statements"),
+			},
+		},
+	}
+
+	nfw.BaseAwsResource.Init(nil)
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{policy1, policy2, group1, group2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(policy1),
+					}}},
+			},
+			expected: []string{policy2, group1, group2},
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			names, err := nfw.getAll(context.Background(), config.Config{
+				NetworkFirewallResourcePolicy: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, aws.StringValueSlice(names))
+		})
+	}
+}
+
+func TestNetworkFirewallResourcePolicy_NukeAll(t *testing.T) {
+
+	t.Parallel()
+
+	ngw := NetworkFirewallResourcePolicy{
+		Client: mockedNetworkFirewallResourcePolicy{
+			DeleteResourcePolicyOutput: networkfirewall.DeleteResourcePolicyOutput{},
+		},
+	}
+
+	err := ngw.nukeAll([]*string{aws.String("test")})
+	require.NoError(t, err)
+}
diff --git a/aws/resources/rds_global_cluster_membership_test.go b/aws/resources/rds_global_cluster_membership_test.go
new file mode 100644
index 0000000..165fe29
--- /dev/null
+++ b/aws/resources/rds_global_cluster_membership_test.go
@@ -0,0 +1,92 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"strings"
+	"testing"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/request"
+	"github.com/aws/aws-sdk-go/service/rds"
+	"github.com/aws/aws-sdk-go/service/rds/rdsiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/stretchr/testify/assert"
+)
+
+type mockedDBGlobalClusterMemberships struct {
+	rdsiface.RDSAPI
+	DescribeGlobalClustersOutput  rds.DescribeGlobalClustersOutput
+	DescribeGlobalClustersError   error
+	RemoveFromGlobalClusterOutput rds.RemoveFromGlobalClusterOutput
+}
+
+func (m mockedDBGlobalClusterMemberships) RemoveFromGlobalCluster(input *rds.RemoveFromGlobalClusterInput) (*rds.RemoveFromGlobalClusterOutput, error) {
+	return &m.RemoveFromGlobalClusterOutput, nil
+}
+
+func (m mockedDBGlobalClusterMemberships) DescribeGlobalClusters(input *rds.DescribeGlobalClustersInput) (*rds.DescribeGlobalClustersOutput, error) {
+	return &m.DescribeGlobalClustersOutput, m.DescribeGlobalClustersError
+}
+
+func (m mockedDBGlobalClusterMemberships) DescribeGlobalClustersWithContext(ctx context.Context, input *rds.DescribeGlobalClustersInput, _ ...request.Option) (*rds.DescribeGlobalClustersOutput, error) {
+	return &m.DescribeGlobalClustersOutput, m.DescribeGlobalClustersError
+}
+
+func TestRDSGlobalClusterMembershipGetAll(t *testing.T) {
+	t.Parallel()
+
+	testName := "test-db-global-cluster"
+	dbCluster := DBGlobalClusterMemberships{
+		Client: mockedDBGlobalClusterMemberships{
+			DescribeGlobalClustersOutput: rds.DescribeGlobalClustersOutput{
+				GlobalClusters: []*rds.GlobalCluster{
+					{
+						GlobalClusterIdentifier: &testName,
+					},
+				},
+			},
+		},
+	}
+
+	// Testing empty config
+	clusters, err := dbCluster.getAll(context.Background(), config.Config{DBGlobalClusterMemberships: config.ResourceType{}})
+	assert.NoError(t, err)
+	assert.Contains(t, awsgo.StringValueSlice(clusters), strings.ToLower(testName))
+
+	// Testing db cluster exclusion
+	clusters, err = dbCluster.getAll(context.Background(), config.Config{
+		DBGlobalClusterMemberships: config.ResourceType{
+			ExcludeRule: config.FilterRule{
+				NamesRegExp: []config.Expression{{
+					RE: *regexp.MustCompile(testName),
+				}},
+			},
+		},
+	})
+	assert.NoError(t, err)
+	assert.NotContains(t, awsgo.StringValueSlice(clusters), strings.ToLower(testName))
+}
+
+func TestRDSGlobalClusterMembershipNukeAll(t *testing.T) {
+
+	t.Parallel()
+
+	testName := "test-db-global-cluster"
+	dbCluster := DBGlobalClusterMemberships{
+		Client: mockedDBGlobalClusterMemberships{
+			DescribeGlobalClustersOutput: rds.DescribeGlobalClustersOutput{
+				GlobalClusters: []*rds.GlobalCluster{
+					{
+						GlobalClusterIdentifier: &testName,
+						GlobalClusterMembers:    []*rds.GlobalClusterMember{},
+					},
+				},
+			},
+			RemoveFromGlobalClusterOutput: rds.RemoveFromGlobalClusterOutput{},
+		},
+	}
+
+	err := dbCluster.nukeAll([]*string{&testName})
+	assert.NoError(t, err)
+}
diff --git a/aws/resources/rds_global_cluster_test.go b/aws/resources/rds_global_cluster_test.go
new file mode 100644
index 0000000..4b154c7
--- /dev/null
+++ b/aws/resources/rds_global_cluster_test.go
@@ -0,0 +1,87 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"strings"
+	"testing"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/awserr"
+	"github.com/aws/aws-sdk-go/aws/request"
+	"github.com/aws/aws-sdk-go/service/rds"
+	"github.com/aws/aws-sdk-go/service/rds/rdsiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/stretchr/testify/assert"
+)
+
+type mockedDBGlobalClusters struct {
+	rdsiface.RDSAPI
+	DescribeGlobalClustersOutput rds.DescribeGlobalClustersOutput
+	DescribeGlobalClustersError  error
+	DeleteGlobalClusterOutput    rds.DeleteGlobalClusterOutput
+}
+
+func (m mockedDBGlobalClusters) DeleteGlobalCluster(input *rds.DeleteGlobalClusterInput) (*rds.DeleteGlobalClusterOutput, error) {
+	return &m.DeleteGlobalClusterOutput, nil
+}
+
+func (m mockedDBGlobalClusters) DescribeGlobalClusters(input *rds.DescribeGlobalClustersInput) (*rds.DescribeGlobalClustersOutput, error) {
+	return &m.DescribeGlobalClustersOutput, m.DescribeGlobalClustersError
+}
+
+func (m mockedDBGlobalClusters) DescribeGlobalClustersWithContext(ctx context.Context, input *rds.DescribeGlobalClustersInput, _ ...request.Option) (*rds.DescribeGlobalClustersOutput, error) {
+	return &m.DescribeGlobalClustersOutput, m.DescribeGlobalClustersError
+}
+
+func TestRDSGlobalClusterGetAll(t *testing.T) {
+	t.Parallel()
+
+	testName := "test-db-global-cluster"
+	dbCluster := DBGlobalClusters{
+		Client: mockedDBGlobalClusters{
+			DescribeGlobalClustersOutput: rds.DescribeGlobalClustersOutput{
+				GlobalClusters: []*rds.GlobalCluster{
+					{
+						GlobalClusterIdentifier: &testName,
+					},
+				},
+			},
+		},
+	}
+
+	// Testing empty config
+	clusters, err := dbCluster.getAll(context.Background(), config.Config{DBGlobalClusters: config.ResourceType{}})
+	assert.NoError(t, err)
+	assert.Contains(t, awsgo.StringValueSlice(clusters), strings.ToLower(testName))
+
+	// Testing db cluster exclusion
+	clusters, err = dbCluster.getAll(context.Background(), config.Config{
+		DBGlobalClusters: config.ResourceType{
+			ExcludeRule: config.FilterRule{
+				NamesRegExp: []config.Expression{{
+					RE: *regexp.MustCompile(testName),
+				}},
+			},
+		},
+	})
+	assert.NoError(t, err)
+	assert.NotContains(t, awsgo.StringValueSlice(clusters), strings.ToLower(testName))
+}
+
+func TestRDSGlobalClusterNukeAll(t *testing.T) {
+
+	t.Parallel()
+
+	testName := "test-db-global-cluster"
+	dbCluster := DBGlobalClusters{
+		Client: mockedDBGlobalClusters{
+			DescribeGlobalClustersOutput: rds.DescribeGlobalClustersOutput{},
+			DescribeGlobalClustersError:  awserr.New(rds.ErrCodeGlobalClusterNotFoundFault, "", nil),
+			DeleteGlobalClusterOutput:    rds.DeleteGlobalClusterOutput{},
+		},
+	}
+
+	err := dbCluster.nukeAll([]*string{&testName})
+	assert.NoError(t, err)
+}
diff --git a/config/config_test.go b/config/config_test.go
index a26ccaa..bf25053 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -43,6 +43,8 @@ func emptyConfig() *Config {
 		ResourceType{FilterRule{}, FilterRule{}, ""},
 		ResourceType{FilterRule{}, FilterRule{}, ""},
 		ResourceType{FilterRule{}, FilterRule{}, ""},
+		ResourceType{FilterRule{}, FilterRule{}, ""},
+		ResourceType{FilterRule{}, FilterRule{}, ""},
 		EC2ResourceType{false, ResourceType{FilterRule{}, FilterRule{}, ""}},
 		ResourceType{FilterRule{}, FilterRule{}, ""},
 		ResourceType{FilterRule{}, FilterRule{}, ""},
@@ -107,6 +109,7 @@ func emptyConfig() *Config {
 		ResourceType{FilterRule{}, FilterRule{}, ""},
 		ResourceType{FilterRule{}, FilterRule{}, ""},
 		ResourceType{FilterRule{}, FilterRule{}, ""},
+		ResourceType{FilterRule{}, FilterRule{}, ""},
 	}
 }