diff --git a/.hashibot.hcl b/.hashibot.hcl
index 8e10d40b912..2e4d6b828b2 100644
--- a/.hashibot.hcl
+++ b/.hashibot.hcl
@@ -432,6 +432,9 @@ behavior "regexp_issue_labeler_v2" "service_labels" {
     "service/neptune" = [
       "aws_neptune_",
     ],
+    "service/networkfirewall" = [
+      "aws_networkfirewall_",
+    ],
     "service/networkmanager" = [
       "aws_networkmanager_",
     ],
@@ -1242,6 +1245,11 @@ behavior "pull_request_path_labeler" "service_labels" {
       "**/*_neptune_*",
       "**/neptune_*"
     ]
+    "service/networkfirewall" = [
+      "aws/internal/service/networkfirewall/**/*",
+      "**/*_networkfirewall_*",
+      "**/networkfirewall_*",
+    ]
     "service/networkmanager" = [
       "aws/internal/service/networkmanager/**/*",
       "**/*_networkmanager_*",
diff --git a/aws/config.go b/aws/config.go
index 793d4a3a28e..d91cd2b94b8 100644
--- a/aws/config.go
+++ b/aws/config.go
@@ -109,6 +109,7 @@ import (
 	"github.com/aws/aws-sdk-go/service/mediastoredata"
 	"github.com/aws/aws-sdk-go/service/mq"
 	"github.com/aws/aws-sdk-go/service/neptune"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
 	"github.com/aws/aws-sdk-go/service/networkmanager"
 	"github.com/aws/aws-sdk-go/service/opsworks"
 	"github.com/aws/aws-sdk-go/service/organizations"
@@ -302,6 +303,7 @@ type AWSClient struct {
 	mediastoredataconn                  *mediastoredata.MediaStoreData
 	mqconn                              *mq.MQ
 	neptuneconn                         *neptune.Neptune
+	networkfirewallconn                 *networkfirewall.NetworkFirewall
 	networkmanagerconn                  *networkmanager.NetworkManager
 	opsworksconn                        *opsworks.OpsWorks
 	organizationsconn                   *organizations.Organizations
@@ -535,6 +537,7 @@ func (c *Config) Client() (interface{}, error) {
 		mediastoredataconn:                  mediastoredata.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["mediastoredata"])})),
 		mqconn:                              mq.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["mq"])})),
 		neptuneconn:                         neptune.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["neptune"])})),
+		networkfirewallconn:                 networkfirewall.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["networkfirewall"])})),
 		networkmanagerconn:                  networkmanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["networkmanager"])})),
 		opsworksconn:                        opsworks.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["opsworks"])})),
 		organizationsconn:                   organizations.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["organizations"])})),
diff --git a/aws/internal/keyvaluetags/generators/listtags/main.go b/aws/internal/keyvaluetags/generators/listtags/main.go
index ece382df3c9..19793063874 100644
--- a/aws/internal/keyvaluetags/generators/listtags/main.go
+++ b/aws/internal/keyvaluetags/generators/listtags/main.go
@@ -92,6 +92,7 @@ var serviceNames = []string{
 	"mediastore",
 	"mq",
 	"neptune",
+	"networkfirewall",
 	"networkmanager",
 	"opsworks",
 	"organizations",
diff --git a/aws/internal/keyvaluetags/generators/servicetags/main.go b/aws/internal/keyvaluetags/generators/servicetags/main.go
index fde36cda2dc..e5f2e37c7fd 100644
--- a/aws/internal/keyvaluetags/generators/servicetags/main.go
+++ b/aws/internal/keyvaluetags/generators/servicetags/main.go
@@ -72,6 +72,7 @@ var sliceServiceNames = []string{
 	"lightsail",
 	"mediastore",
 	"neptune",
+	"networkfirewall",
 	"networkmanager",
 	"organizations",
 	"quicksight",
diff --git a/aws/internal/keyvaluetags/generators/updatetags/main.go b/aws/internal/keyvaluetags/generators/updatetags/main.go
index 452628f437b..e398ced459c 100644
--- a/aws/internal/keyvaluetags/generators/updatetags/main.go
+++ b/aws/internal/keyvaluetags/generators/updatetags/main.go
@@ -95,6 +95,7 @@ var serviceNames = []string{
 	"mediastore",
 	"mq",
 	"neptune",
+	"networkfirewall",
 	"networkmanager",
 	"opsworks",
 	"organizations",
diff --git a/aws/internal/keyvaluetags/list_tags_gen.go b/aws/internal/keyvaluetags/list_tags_gen.go
index 359cd2f9e69..8cf4316958a 100644
--- a/aws/internal/keyvaluetags/list_tags_gen.go
+++ b/aws/internal/keyvaluetags/list_tags_gen.go
@@ -79,6 +79,7 @@ import (
 	"github.com/aws/aws-sdk-go/service/mediastore"
 	"github.com/aws/aws-sdk-go/service/mq"
 	"github.com/aws/aws-sdk-go/service/neptune"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
 	"github.com/aws/aws-sdk-go/service/networkmanager"
 	"github.com/aws/aws-sdk-go/service/opsworks"
 	"github.com/aws/aws-sdk-go/service/organizations"
@@ -1392,6 +1393,23 @@ func NeptuneListTags(conn *neptune.Neptune, identifier string) (KeyValueTags, er
 	return NeptuneKeyValueTags(output.TagList), nil
 }
 
+// NetworkfirewallListTags lists networkfirewall service tags.
+// The identifier is typically the Amazon Resource Name (ARN), although
+// it may also be a different identifier depending on the service.
+func NetworkfirewallListTags(conn *networkfirewall.NetworkFirewall, identifier string) (KeyValueTags, error) {
+	input := &networkfirewall.ListTagsForResourceInput{
+		ResourceArn: aws.String(identifier),
+	}
+
+	output, err := conn.ListTagsForResource(input)
+
+	if err != nil {
+		return New(nil), err
+	}
+
+	return NetworkfirewallKeyValueTags(output.Tags), nil
+}
+
 // NetworkmanagerListTags lists networkmanager service tags.
 // The identifier is typically the Amazon Resource Name (ARN), although
 // it may also be a different identifier depending on the service.
diff --git a/aws/internal/keyvaluetags/service_generation_customizations.go b/aws/internal/keyvaluetags/service_generation_customizations.go
index 98a5b9b75ba..5ba667bc3b5 100644
--- a/aws/internal/keyvaluetags/service_generation_customizations.go
+++ b/aws/internal/keyvaluetags/service_generation_customizations.go
@@ -85,6 +85,7 @@ import (
 	"github.com/aws/aws-sdk-go/service/mediastore"
 	"github.com/aws/aws-sdk-go/service/mq"
 	"github.com/aws/aws-sdk-go/service/neptune"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
 	"github.com/aws/aws-sdk-go/service/networkmanager"
 	"github.com/aws/aws-sdk-go/service/opsworks"
 	"github.com/aws/aws-sdk-go/service/organizations"
@@ -283,6 +284,8 @@ func ServiceClientType(serviceName string) string {
 		funcType = reflect.TypeOf(mq.New)
 	case "neptune":
 		funcType = reflect.TypeOf(neptune.New)
+	case "networkfirewall":
+		funcType = reflect.TypeOf(networkfirewall.New)
 	case "networkmanager":
 		funcType = reflect.TypeOf(networkmanager.New)
 	case "opsworks":
diff --git a/aws/internal/keyvaluetags/service_tags_gen.go b/aws/internal/keyvaluetags/service_tags_gen.go
index c6c0cadaddb..4dc5e666ba4 100644
--- a/aws/internal/keyvaluetags/service_tags_gen.go
+++ b/aws/internal/keyvaluetags/service_tags_gen.go
@@ -60,6 +60,7 @@ import (
 	"github.com/aws/aws-sdk-go/service/lightsail"
 	"github.com/aws/aws-sdk-go/service/mediastore"
 	"github.com/aws/aws-sdk-go/service/neptune"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
 	"github.com/aws/aws-sdk-go/service/networkmanager"
 	"github.com/aws/aws-sdk-go/service/organizations"
 	"github.com/aws/aws-sdk-go/service/quicksight"
@@ -2081,6 +2082,33 @@ func NeptuneKeyValueTags(tags []*neptune.Tag) KeyValueTags {
 	return New(m)
 }
 
+// NetworkfirewallTags returns networkfirewall service tags.
+func (tags KeyValueTags) NetworkfirewallTags() []*networkfirewall.Tag {
+	result := make([]*networkfirewall.Tag, 0, len(tags))
+
+	for k, v := range tags.Map() {
+		tag := &networkfirewall.Tag{
+			Key:   aws.String(k),
+			Value: aws.String(v),
+		}
+
+		result = append(result, tag)
+	}
+
+	return result
+}
+
+// NetworkfirewallKeyValueTags creates KeyValueTags from networkfirewall service tags.
+func NetworkfirewallKeyValueTags(tags []*networkfirewall.Tag) KeyValueTags {
+	m := make(map[string]*string, len(tags))
+
+	for _, tag := range tags {
+		m[aws.StringValue(tag.Key)] = tag.Value
+	}
+
+	return New(m)
+}
+
 // NetworkmanagerTags returns networkmanager service tags.
 func (tags KeyValueTags) NetworkmanagerTags() []*networkmanager.Tag {
 	result := make([]*networkmanager.Tag, 0, len(tags))
diff --git a/aws/internal/keyvaluetags/update_tags_gen.go b/aws/internal/keyvaluetags/update_tags_gen.go
index 8846f8770ca..872a1c36f37 100644
--- a/aws/internal/keyvaluetags/update_tags_gen.go
+++ b/aws/internal/keyvaluetags/update_tags_gen.go
@@ -84,6 +84,7 @@ import (
 	"github.com/aws/aws-sdk-go/service/mediastore"
 	"github.com/aws/aws-sdk-go/service/mq"
 	"github.com/aws/aws-sdk-go/service/neptune"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
 	"github.com/aws/aws-sdk-go/service/networkmanager"
 	"github.com/aws/aws-sdk-go/service/opsworks"
 	"github.com/aws/aws-sdk-go/service/organizations"
@@ -2925,6 +2926,42 @@ func NeptuneUpdateTags(conn *neptune.Neptune, identifier string, oldTagsMap inte
 	return nil
 }
 
+// NetworkfirewallUpdateTags updates networkfirewall service tags.
+// The identifier is typically the Amazon Resource Name (ARN), although
+// it may also be a different identifier depending on the service.
+func NetworkfirewallUpdateTags(conn *networkfirewall.NetworkFirewall, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
+	oldTags := New(oldTagsMap)
+	newTags := New(newTagsMap)
+
+	if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 {
+		input := &networkfirewall.UntagResourceInput{
+			ResourceArn: aws.String(identifier),
+			TagKeys:     aws.StringSlice(removedTags.IgnoreAws().Keys()),
+		}
+
+		_, err := conn.UntagResource(input)
+
+		if err != nil {
+			return fmt.Errorf("error untagging resource (%s): %w", identifier, err)
+		}
+	}
+
+	if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 {
+		input := &networkfirewall.TagResourceInput{
+			ResourceArn: aws.String(identifier),
+			Tags:        updatedTags.IgnoreAws().NetworkfirewallTags(),
+		}
+
+		_, err := conn.TagResource(input)
+
+		if err != nil {
+			return fmt.Errorf("error tagging resource (%s): %w", identifier, err)
+		}
+	}
+
+	return nil
+}
+
 // NetworkmanagerUpdateTags updates networkmanager service tags.
 // The identifier is typically the Amazon Resource Name (ARN), although
 // it may also be a different identifier depending on the service.
diff --git a/aws/internal/service/networkfirewall/finder/finder.go b/aws/internal/service/networkfirewall/finder/finder.go
new file mode 100644
index 00000000000..fcf027b7087
--- /dev/null
+++ b/aws/internal/service/networkfirewall/finder/finder.go
@@ -0,0 +1,81 @@
+package finder
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+)
+
+// LoggingConfiguration returns the LoggingConfigurationOutput from a call to DescribeLoggingConfigurationWithContext
+// given the context and Firewall ARN.
+// Returns nil if the LoggingConfiguration is not found.
+func LoggingConfiguration(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*networkfirewall.DescribeLoggingConfigurationOutput, error) {
+	input := &networkfirewall.DescribeLoggingConfigurationInput{
+		FirewallArn: aws.String(arn),
+	}
+	output, err := conn.DescribeLoggingConfigurationWithContext(ctx, input)
+	if err != nil {
+		return nil, err
+	}
+	return output, nil
+}
+
+// Firewall returns the FirewallOutput from a call to DescribeFirewallWithContext
+// given the context and Firewall ARN.
+// Returns nil if the Firewall is not found.
+func Firewall(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*networkfirewall.DescribeFirewallOutput, error) {
+	input := &networkfirewall.DescribeFirewallInput{
+		FirewallArn: aws.String(arn),
+	}
+	output, err := conn.DescribeFirewallWithContext(ctx, input)
+	if err != nil {
+		return nil, err
+	}
+	return output, nil
+}
+
+// FirewallPolicy returns the FirewallPolicyOutput from a call to DescribeFirewallPolicyWithContext
+// given the context and FirewallPolicy ARN.
+// Returns nil if the FirewallPolicy is not found.
+func FirewallPolicy(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*networkfirewall.DescribeFirewallPolicyOutput, error) {
+	input := &networkfirewall.DescribeFirewallPolicyInput{
+		FirewallPolicyArn: aws.String(arn),
+	}
+	output, err := conn.DescribeFirewallPolicyWithContext(ctx, input)
+	if err != nil {
+		return nil, err
+	}
+	return output, nil
+}
+
+// ResourcePolicy returns the Policy string from a call to DescribeResourcePolicyWithContext
+// given the context and resource ARN.
+// Returns nil if the ResourcePolicy is not found.
+func ResourcePolicy(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*string, error) {
+	input := &networkfirewall.DescribeResourcePolicyInput{
+		ResourceArn: aws.String(arn),
+	}
+	output, err := conn.DescribeResourcePolicyWithContext(ctx, input)
+	if err != nil {
+		return nil, err
+	}
+	if output == nil {
+		return nil, nil
+	}
+	return output.Policy, nil
+}
+
+// RuleGroup returns the RuleGroupOutput from a call to DescribeRuleGroupWithContext
+// given the context and RuleGroup ARN.
+// Returns nil if the RuleGroup is not found.
+func RuleGroup(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*networkfirewall.DescribeRuleGroupOutput, error) {
+	input := &networkfirewall.DescribeRuleGroupInput{
+		RuleGroupArn: aws.String(arn),
+	}
+	output, err := conn.DescribeRuleGroupWithContext(ctx, input)
+	if err != nil {
+		return nil, err
+	}
+	return output, nil
+}
diff --git a/aws/internal/service/networkfirewall/waiter/status.go b/aws/internal/service/networkfirewall/waiter/status.go
new file mode 100644
index 00000000000..e6261a52b50
--- /dev/null
+++ b/aws/internal/service/networkfirewall/waiter/status.go
@@ -0,0 +1,136 @@
+package waiter
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/aws-sdk-go-base/tfawserr"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+)
+
+const (
+	ResourceStatusFailed  = "Failed"
+	ResourceStatusUnknown = "Unknown"
+	ResourceStatusDeleted = "Deleted"
+)
+
+// FirewallCreatedStatus fetches the Firewall and its Status.
+// A Firewall is READY only when the ConfigurationSyncStateSummary value
+// is IN_SYNC and the Attachment Status values for ALL of the configured
+// subnets are READY.
+func FirewallCreatedStatus(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) resource.StateRefreshFunc {
+	return func() (interface{}, string, error) {
+		input := &networkfirewall.DescribeFirewallInput{
+			FirewallArn: aws.String(arn),
+		}
+
+		output, err := conn.DescribeFirewallWithContext(ctx, input)
+
+		if err != nil {
+			return output, ResourceStatusFailed, err
+		}
+
+		if output == nil || output.FirewallStatus == nil {
+			return output, ResourceStatusUnknown, nil
+		}
+
+		return output.Firewall, aws.StringValue(output.FirewallStatus.Status), nil
+	}
+}
+
+// FirewallUpdatedStatus fetches the Firewall and its Status and UpdateToken.
+func FirewallUpdatedStatus(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) resource.StateRefreshFunc {
+	return func() (interface{}, string, error) {
+		input := &networkfirewall.DescribeFirewallInput{
+			FirewallArn: aws.String(arn),
+		}
+
+		output, err := conn.DescribeFirewallWithContext(ctx, input)
+
+		if err != nil {
+			return output, ResourceStatusFailed, err
+		}
+
+		if output == nil || output.FirewallStatus == nil {
+			return output, ResourceStatusUnknown, nil
+		}
+
+		return output.UpdateToken, aws.StringValue(output.FirewallStatus.Status), nil
+	}
+}
+
+// FirewallDeletedStatus fetches the Firewall and its Status
+func FirewallDeletedStatus(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) resource.StateRefreshFunc {
+	return func() (interface{}, string, error) {
+		input := &networkfirewall.DescribeFirewallInput{
+			FirewallArn: aws.String(arn),
+		}
+
+		output, err := conn.DescribeFirewallWithContext(ctx, input)
+
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return output, ResourceStatusDeleted, nil
+		}
+
+		if err != nil {
+			return output, ResourceStatusUnknown, err
+		}
+
+		if output == nil || output.FirewallStatus == nil {
+			return output, ResourceStatusUnknown, nil
+		}
+
+		return output.Firewall, aws.StringValue(output.FirewallStatus.Status), nil
+	}
+}
+
+// FirewallPolicyStatus fetches the Firewall Policy and its Status
+func FirewallPolicyStatus(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) resource.StateRefreshFunc {
+	return func() (interface{}, string, error) {
+		input := &networkfirewall.DescribeFirewallPolicyInput{
+			FirewallPolicyArn: aws.String(arn),
+		}
+
+		output, err := conn.DescribeFirewallPolicyWithContext(ctx, input)
+
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return output, ResourceStatusDeleted, nil
+		}
+
+		if err != nil {
+			return nil, ResourceStatusUnknown, err
+		}
+
+		if output == nil || output.FirewallPolicyResponse == nil {
+			return nil, ResourceStatusUnknown, nil
+		}
+
+		return output.FirewallPolicy, aws.StringValue(output.FirewallPolicyResponse.FirewallPolicyStatus), nil
+	}
+}
+
+// RuleGroupStatus fetches the Rule Group and its Status
+func RuleGroupStatus(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) resource.StateRefreshFunc {
+	return func() (interface{}, string, error) {
+		input := &networkfirewall.DescribeRuleGroupInput{
+			RuleGroupArn: aws.String(arn),
+		}
+
+		output, err := conn.DescribeRuleGroupWithContext(ctx, input)
+
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return output, ResourceStatusDeleted, nil
+		}
+
+		if err != nil {
+			return nil, ResourceStatusUnknown, err
+		}
+
+		if output == nil || output.RuleGroupResponse == nil {
+			return nil, ResourceStatusUnknown, nil
+		}
+
+		return output.RuleGroup, aws.StringValue(output.RuleGroupResponse.RuleGroupStatus), nil
+	}
+}
diff --git a/aws/internal/service/networkfirewall/waiter/waiter.go b/aws/internal/service/networkfirewall/waiter/waiter.go
new file mode 100644
index 00000000000..903fb38bcba
--- /dev/null
+++ b/aws/internal/service/networkfirewall/waiter/waiter.go
@@ -0,0 +1,106 @@
+package waiter
+
+import (
+	"context"
+	"time"
+
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+)
+
+const (
+	// Maximum amount of time to wait for a Firewall to be created, updated, or deleted
+	FirewallTimeout = 20 * time.Minute
+	// Maximum amount of time to wait for a Firewall Policy to be deleted
+	FirewallPolicyTimeout = 10 * time.Minute
+	// Maximum amount of time to wait for a Rule Group to be deleted
+	RuleGroupDeleteTimeout = 10 * time.Minute
+)
+
+func FirewallCreated(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*networkfirewall.Firewall, error) {
+	stateConf := &resource.StateChangeConf{
+		Pending: []string{networkfirewall.FirewallStatusValueProvisioning},
+		Target:  []string{networkfirewall.FirewallStatusValueReady},
+		Refresh: FirewallCreatedStatus(ctx, conn, arn),
+		Timeout: FirewallTimeout,
+	}
+
+	outputRaw, err := stateConf.WaitForState()
+
+	if v, ok := outputRaw.(*networkfirewall.Firewall); ok {
+		return v, err
+	}
+
+	return nil, err
+}
+
+func FirewallUpdated(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*string, error) {
+	stateConf := &resource.StateChangeConf{
+		Pending: []string{networkfirewall.FirewallStatusValueProvisioning},
+		Target:  []string{networkfirewall.FirewallStatusValueReady},
+		Refresh: FirewallUpdatedStatus(ctx, conn, arn),
+		Timeout: FirewallTimeout,
+	}
+
+	outputRaw, err := stateConf.WaitForState()
+
+	if v, ok := outputRaw.(*string); ok {
+		return v, err
+	}
+
+	return nil, err
+}
+
+// FirewallDeleted waits for a Firewall to return "Deleted"
+func FirewallDeleted(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*networkfirewall.Firewall, error) {
+	stateConf := &resource.StateChangeConf{
+		Pending: []string{networkfirewall.FirewallStatusValueDeleting},
+		Target:  []string{ResourceStatusDeleted},
+		Refresh: FirewallDeletedStatus(ctx, conn, arn),
+		Timeout: FirewallTimeout,
+	}
+
+	outputRaw, err := stateConf.WaitForState()
+
+	if v, ok := outputRaw.(*networkfirewall.Firewall); ok {
+		return v, err
+	}
+
+	return nil, err
+}
+
+// FirewallPolicyDeleted waits for a Firewall Policy to return "Deleted"
+func FirewallPolicyDeleted(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*networkfirewall.FirewallPolicy, error) {
+	stateConf := &resource.StateChangeConf{
+		Pending: []string{networkfirewall.ResourceStatusDeleting},
+		Target:  []string{ResourceStatusDeleted},
+		Refresh: FirewallPolicyStatus(ctx, conn, arn),
+		Timeout: FirewallPolicyTimeout,
+	}
+
+	outputRaw, err := stateConf.WaitForState()
+
+	if v, ok := outputRaw.(*networkfirewall.FirewallPolicy); ok {
+		return v, err
+	}
+
+	return nil, err
+}
+
+// RuleGroupDeleted waits for a Rule Group to return "Deleted"
+func RuleGroupDeleted(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string) (*networkfirewall.RuleGroup, error) {
+	stateConf := &resource.StateChangeConf{
+		Pending: []string{networkfirewall.ResourceStatusDeleting},
+		Target:  []string{ResourceStatusDeleted},
+		Refresh: RuleGroupStatus(ctx, conn, arn),
+		Timeout: RuleGroupDeleteTimeout,
+	}
+
+	outputRaw, err := stateConf.WaitForState()
+
+	if v, ok := outputRaw.(*networkfirewall.RuleGroup); ok {
+		return v, err
+	}
+
+	return nil, err
+}
diff --git a/aws/networkfirewall_helpers.go b/aws/networkfirewall_helpers.go
new file mode 100644
index 00000000000..9e397d2265b
--- /dev/null
+++ b/aws/networkfirewall_helpers.go
@@ -0,0 +1,177 @@
+package aws
+
+import (
+	"regexp"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+)
+
+func customActionSchema() *schema.Schema {
+	return &schema.Schema{
+		Type:     schema.TypeSet,
+		Optional: true,
+		Elem: &schema.Resource{
+			Schema: map[string]*schema.Schema{
+				"action_definition": {
+					Type:     schema.TypeList,
+					Required: true,
+					MaxItems: 1,
+					Elem: &schema.Resource{
+						Schema: map[string]*schema.Schema{
+							"publish_metric_action": {
+								Type:     schema.TypeList,
+								Required: true,
+								MaxItems: 1,
+								Elem: &schema.Resource{
+									Schema: map[string]*schema.Schema{
+										"dimension": {
+											Type:     schema.TypeSet,
+											Required: true,
+											Elem: &schema.Resource{
+												Schema: map[string]*schema.Schema{
+													"value": {
+														Type:     schema.TypeString,
+														Required: true,
+													},
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				"action_name": {
+					Type:         schema.TypeString,
+					Required:     true,
+					ForceNew:     true,
+					ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9]+$`), "must contain only alphanumeric characters"),
+				},
+			},
+		},
+	}
+}
+
+func expandNetworkFirewallCustomActions(l []interface{}) []*networkfirewall.CustomAction {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+
+	customActions := make([]*networkfirewall.CustomAction, 0, len(l))
+	for _, tfMapRaw := range l {
+		customAction := &networkfirewall.CustomAction{}
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		if v, ok := tfMap["action_definition"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
+			customAction.ActionDefinition = expandNetworkFirewallActionDefinition(v)
+		}
+		if v, ok := tfMap["action_name"].(string); ok && v != "" {
+			customAction.ActionName = aws.String(v)
+		}
+		customActions = append(customActions, customAction)
+	}
+
+	return customActions
+}
+
+func expandNetworkFirewallActionDefinition(l []interface{}) *networkfirewall.ActionDefinition {
+	if l == nil || l[0] == nil {
+		return nil
+	}
+
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+	customAction := &networkfirewall.ActionDefinition{}
+
+	if v, ok := tfMap["publish_metric_action"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
+		customAction.PublishMetricAction = expandNetworkFirewallCustomActionPublishMetricAction(v)
+	}
+
+	return customAction
+}
+
+func expandNetworkFirewallCustomActionPublishMetricAction(l []interface{}) *networkfirewall.PublishMetricAction {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+	action := &networkfirewall.PublishMetricAction{}
+	if tfSet, ok := tfMap["dimension"].(*schema.Set); ok && tfSet.Len() > 0 {
+		tfList := tfSet.List()
+		dimensions := make([]*networkfirewall.Dimension, 0, len(tfList))
+		for _, tfMapRaw := range tfList {
+			tfMap, ok := tfMapRaw.(map[string]interface{})
+			if !ok {
+				continue
+			}
+			dimension := &networkfirewall.Dimension{
+				Value: aws.String(tfMap["value"].(string)),
+			}
+			dimensions = append(dimensions, dimension)
+		}
+		action.Dimensions = dimensions
+	}
+	return action
+}
+
+func flattenNetworkFirewallCustomActions(c []*networkfirewall.CustomAction) []interface{} {
+	if c == nil {
+		return []interface{}{}
+	}
+
+	customActions := make([]interface{}, 0, len(c))
+	for _, elem := range c {
+		m := map[string]interface{}{
+			"action_definition": flattenNetworkFirewallActionDefinition(elem.ActionDefinition),
+			"action_name":       aws.StringValue(elem.ActionName),
+		}
+		customActions = append(customActions, m)
+	}
+
+	return customActions
+}
+
+func flattenNetworkFirewallActionDefinition(v *networkfirewall.ActionDefinition) []interface{} {
+	if v == nil {
+		return []interface{}{}
+	}
+	m := map[string]interface{}{
+		"publish_metric_action": flattenNetworkFirewallPublishMetricAction(v.PublishMetricAction),
+	}
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallPublishMetricAction(m *networkfirewall.PublishMetricAction) []interface{} {
+	if m == nil {
+		return []interface{}{}
+	}
+
+	metrics := map[string]interface{}{
+		"dimension": flattenNetworkFirewallDimensions(m.Dimensions),
+	}
+
+	return []interface{}{metrics}
+}
+
+func flattenNetworkFirewallDimensions(d []*networkfirewall.Dimension) []interface{} {
+	dimensions := make([]interface{}, 0, len(d))
+	for _, v := range d {
+		dimension := map[string]interface{}{
+			"value": aws.StringValue(v.Value),
+		}
+		dimensions = append(dimensions, dimension)
+	}
+
+	return dimensions
+}
diff --git a/aws/provider.go b/aws/provider.go
index 45a40bb6e9a..977e63f7cbe 100644
--- a/aws/provider.go
+++ b/aws/provider.go
@@ -780,6 +780,10 @@ func Provider() *schema.Provider {
 			"aws_network_acl_rule":                                    resourceAwsNetworkAclRule(),
 			"aws_network_interface":                                   resourceAwsNetworkInterface(),
 			"aws_network_interface_attachment":                        resourceAwsNetworkInterfaceAttachment(),
+			"aws_networkfirewall_firewall":                            resourceAwsNetworkFirewallFirewall(),
+			"aws_networkfirewall_firewall_policy":                     resourceAwsNetworkFirewallFirewallPolicy(),
+			"aws_networkfirewall_logging_configuration":               resourceAwsNetworkFirewallLoggingConfiguration(),
+			"aws_networkfirewall_rule_group":                          resourceAwsNetworkFirewallRuleGroup(),
 			"aws_opsworks_application":                                resourceAwsOpsworksApplication(),
 			"aws_opsworks_stack":                                      resourceAwsOpsworksStack(),
 			"aws_opsworks_java_app_layer":                             resourceAwsOpsworksJavaAppLayer(),
@@ -1192,6 +1196,7 @@ func init() {
 		"mediastoredata",
 		"mq",
 		"neptune",
+		"networkfirewall",
 		"networkmanager",
 		"opsworks",
 		"organizations",
diff --git a/aws/resource_aws_networkfirewall_firewall.go b/aws/resource_aws_networkfirewall_firewall.go
new file mode 100644
index 00000000000..4b6d8f45722
--- /dev/null
+++ b/aws/resource_aws_networkfirewall_firewall.go
@@ -0,0 +1,433 @@
+package aws
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"log"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/aws-sdk-go-base/tfawserr"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/waiter"
+)
+
+func resourceAwsNetworkFirewallFirewall() *schema.Resource {
+	return &schema.Resource{
+		CreateContext: resourceAwsNetworkFirewallFirewallCreate,
+		ReadContext:   resourceAwsNetworkFirewallFirewallRead,
+		UpdateContext: resourceAwsNetworkFirewallFirewallUpdate,
+		DeleteContext: resourceAwsNetworkFirewallFirewallDelete,
+
+		Importer: &schema.ResourceImporter{
+			StateContext: schema.ImportStatePassthroughContext,
+		},
+
+		Schema: map[string]*schema.Schema{
+			"arn": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
+			"delete_protection": {
+				Type:     schema.TypeBool,
+				Optional: true,
+				Default:  false,
+			},
+			"description": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"firewall_policy_arn": {
+				Type:         schema.TypeString,
+				Required:     true,
+				ValidateFunc: validateArn,
+			},
+			"firewall_policy_change_protection": {
+				Type:     schema.TypeBool,
+				Optional: true,
+			},
+			"name": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"subnet_change_protection": {
+				Type:     schema.TypeBool,
+				Optional: true,
+			},
+			"subnet_mapping": {
+				Type:     schema.TypeSet,
+				Required: true,
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"subnet_id": {
+							Type:     schema.TypeString,
+							Required: true,
+						},
+					},
+				},
+			},
+			"tags": tagsSchema(),
+			"update_token": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
+			"vpc_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+		},
+	}
+}
+
+func resourceAwsNetworkFirewallFirewallCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	name := d.Get("name").(string)
+	input := &networkfirewall.CreateFirewallInput{
+		FirewallName:      aws.String(name),
+		FirewallPolicyArn: aws.String(d.Get("firewall_policy_arn").(string)),
+		SubnetMappings:    expandNetworkFirewallSubnetMappings(d.Get("subnet_mapping").(*schema.Set).List()),
+		VpcId:             aws.String(d.Get("vpc_id").(string)),
+	}
+
+	if v, ok := d.GetOk("delete_protection"); ok {
+		input.DeleteProtection = aws.Bool(v.(bool))
+	}
+
+	if v, ok := d.GetOk("description"); ok {
+		input.Description = aws.String(v.(string))
+	}
+
+	if v, ok := d.GetOk("firewall_policy_change_protection"); ok {
+		input.FirewallPolicyChangeProtection = aws.Bool(v.(bool))
+	}
+
+	if v, ok := d.GetOk("subnet_change_protection"); ok {
+		input.SubnetChangeProtection = aws.Bool(v.(bool))
+	}
+	if v, ok := d.GetOk("tags"); ok {
+		input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().NetworkfirewallTags()
+	}
+
+	log.Printf("[DEBUG] Creating NetworkFirewall Firewall %s", name)
+
+	output, err := conn.CreateFirewallWithContext(ctx, input)
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error creating NetworkFirewall Firewall (%s): %w", name, err))
+	}
+
+	d.SetId(aws.StringValue(output.Firewall.FirewallArn))
+
+	if _, err := waiter.FirewallCreated(ctx, conn, d.Id()); err != nil {
+		return diag.FromErr(fmt.Errorf("error waiting for NetworkFirewall Firewall (%s) to be created: %w", d.Id(), err))
+	}
+
+	return resourceAwsNetworkFirewallFirewallRead(ctx, d, meta)
+}
+
+func resourceAwsNetworkFirewallFirewallRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig
+
+	log.Printf("[DEBUG] Reading NetworkFirewall Firewall %s", d.Id())
+
+	input := &networkfirewall.DescribeFirewallInput{
+		FirewallArn: aws.String(d.Id()),
+	}
+	output, err := conn.DescribeFirewallWithContext(ctx, input)
+	if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+		log.Printf("[WARN] NetworkFirewall Firewall (%s) not found, removing from state", d.Id())
+		d.SetId("")
+		return nil
+	}
+
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error reading NetworkFirewall Firewall (%s): %w", d.Id(), err))
+	}
+
+	if output == nil || output.Firewall == nil {
+		return diag.FromErr(fmt.Errorf("error reading NetworkFirewall Firewall (%s): empty output", d.Id()))
+	}
+
+	firewall := output.Firewall
+	arn := aws.StringValue(firewall.FirewallArn)
+
+	d.Set("arn", arn)
+	d.Set("delete_protection", firewall.DeleteProtection)
+	d.Set("description", firewall.Description)
+	d.Set("name", firewall.FirewallName)
+	d.Set("firewall_policy_arn", firewall.FirewallPolicyArn)
+	d.Set("firewall_policy_change_protection", firewall.FirewallPolicyChangeProtection)
+	d.Set("subnet_change_protection", firewall.SubnetChangeProtection)
+	d.Set("update_token", output.UpdateToken)
+	d.Set("vpc_id", firewall.VpcId)
+
+	if err := d.Set("subnet_mapping", flattenNetworkFirewallSubnetMappings(firewall.SubnetMappings)); err != nil {
+		return diag.FromErr(fmt.Errorf("error setting subnet_mappings: %w", err))
+	}
+
+	tags, err := keyvaluetags.NetworkfirewallListTags(conn, arn)
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error listing tags for NetworkFirewall Firewall (%s): %w", arn, err))
+	}
+
+	if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
+		return diag.FromErr(fmt.Errorf("error setting tags: %w", err))
+	}
+
+	return nil
+}
+
+func resourceAwsNetworkFirewallFirewallUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	arn := d.Id()
+	updateToken := aws.String(d.Get("update_token").(string))
+
+	if d.HasChange("description") {
+		input := &networkfirewall.UpdateFirewallDescriptionInput{
+			Description: aws.String(d.Get("description").(string)),
+			FirewallArn: aws.String(arn),
+			UpdateToken: updateToken,
+		}
+		resp, err := conn.UpdateFirewallDescriptionWithContext(ctx, input)
+		if err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) description: %w", d.Id(), err))
+		}
+		if resp == nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) description: empty update_token", arn))
+		}
+		updateToken = resp.UpdateToken
+	}
+
+	if d.HasChange("delete_protection") {
+		input := &networkfirewall.UpdateFirewallDeleteProtectionInput{
+			DeleteProtection: aws.Bool(d.Get("delete_protection").(bool)),
+			FirewallArn:      aws.String(arn),
+			UpdateToken:      updateToken,
+		}
+		resp, err := conn.UpdateFirewallDeleteProtectionWithContext(ctx, input)
+		if err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) delete_protection: %w", arn, err))
+		}
+		if resp == nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) delete_protection: empty update_token", arn))
+		}
+		updateToken = resp.UpdateToken
+	}
+
+	// Note: The *_change_protection fields below are handled before their respective fields
+	// to account for disabling and subsequent changes
+
+	if d.HasChange("firewall_policy_change_protection") {
+		input := &networkfirewall.UpdateFirewallPolicyChangeProtectionInput{
+			FirewallArn:                    aws.String(arn),
+			FirewallPolicyChangeProtection: aws.Bool(d.Get("firewall_policy_change_protection").(bool)),
+			UpdateToken:                    updateToken,
+		}
+		resp, err := conn.UpdateFirewallPolicyChangeProtection(input)
+		if err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) firewall_policy_change_protection: %w", arn, err))
+		}
+		if resp == nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) firewall_policy_change_protection: empty update_token", arn))
+		}
+		updateToken = resp.UpdateToken
+	}
+
+	if d.HasChange("firewall_policy_arn") {
+		input := &networkfirewall.AssociateFirewallPolicyInput{
+			FirewallArn:       aws.String(arn),
+			FirewallPolicyArn: aws.String(d.Get("firewall_policy_arn").(string)),
+			UpdateToken:       updateToken,
+		}
+		resp, err := conn.AssociateFirewallPolicy(input)
+		if err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) firewall_policy_arn: %w", arn, err))
+		}
+		if resp == nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) firewall_policy_arn: empty update_token", arn))
+		}
+		updateToken = resp.UpdateToken
+	}
+
+	if d.HasChange("subnet_change_protection") {
+		input := &networkfirewall.UpdateSubnetChangeProtectionInput{
+			FirewallArn:            aws.String(arn),
+			SubnetChangeProtection: aws.Bool(d.Get("subnet_change_protection").(bool)),
+			UpdateToken:            updateToken,
+		}
+		resp, err := conn.UpdateSubnetChangeProtection(input)
+		if err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) subnet_change_protection: %w", arn, err))
+		}
+		if resp == nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) subnet_change_protection: empty update_token", arn))
+		}
+		updateToken = resp.UpdateToken
+	}
+
+	if d.HasChange("subnet_mapping") {
+		o, n := d.GetChange("subnet_mapping")
+		subnetsToRemove, subnetsToAdd := networkFirewallSubnetMappingsDiff(o.(*schema.Set), n.(*schema.Set))
+		// Ensure we add before removing a SubnetMapping if there is only 1
+		if len(subnetsToAdd) > 0 {
+			input := &networkfirewall.AssociateSubnetsInput{
+				FirewallArn:    aws.String(arn),
+				SubnetMappings: subnetsToAdd,
+				UpdateToken:    updateToken,
+			}
+
+			_, err := conn.AssociateSubnetsWithContext(ctx, input)
+			if err != nil {
+				return diag.FromErr(fmt.Errorf("error associating NetworkFirewall Firewall (%s) subnet: %w", arn, err))
+			}
+
+			respToken, err := waiter.FirewallUpdated(ctx, conn, arn)
+			if err != nil {
+				return diag.FromErr(fmt.Errorf("error waiting for NetworkFirewall Firewall (%s) to be updated: %w", d.Id(), err))
+
+			}
+			if respToken == nil {
+				return diag.FromErr(fmt.Errorf("error associating NetworkFirewall Firewall (%s) subnet: empty update_token", arn))
+			}
+
+			updateToken = respToken
+		}
+		if len(subnetsToRemove) > 0 {
+			input := &networkfirewall.DisassociateSubnetsInput{
+				FirewallArn: aws.String(arn),
+				SubnetIds:   aws.StringSlice(subnetsToRemove),
+				UpdateToken: updateToken,
+			}
+
+			_, err := conn.DisassociateSubnetsWithContext(ctx, input)
+			if err != nil && !tfawserr.ErrMessageContains(err, networkfirewall.ErrCodeInvalidRequestException, "inaccessible") {
+				return diag.FromErr(fmt.Errorf("error disassociating NetworkFirewall Firewall (%s) subnet: %w", arn, err))
+			}
+
+			_, err = waiter.FirewallUpdated(ctx, conn, arn)
+			if err != nil {
+				return diag.FromErr(fmt.Errorf("error waiting for NetworkFirewall Firewall (%s) to be updated: %w", d.Id(), err))
+
+			}
+		}
+	}
+
+	if d.HasChange("tags") {
+		o, n := d.GetChange("tags")
+		if err := keyvaluetags.NetworkfirewallUpdateTags(conn, arn, o, n); err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall (%s) tags: %w", arn, err))
+		}
+	}
+
+	return resourceAwsNetworkFirewallFirewallRead(ctx, d, meta)
+}
+
+func resourceAwsNetworkFirewallFirewallDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+
+	log.Printf("[DEBUG] Deleting NetworkFirewall Firewall %s", d.Id())
+
+	input := &networkfirewall.DeleteFirewallInput{
+		FirewallArn: aws.String(d.Id()),
+	}
+
+	_, err := conn.DeleteFirewallWithContext(ctx, input)
+	if err != nil {
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return nil
+		}
+		return diag.FromErr(fmt.Errorf("error deleting NetworkFirewall Firewall (%s): %w", d.Id(), err))
+	}
+
+	if _, err := waiter.FirewallDeleted(ctx, conn, d.Id()); err != nil {
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return nil
+		}
+		return diag.FromErr(fmt.Errorf("error waiting for NetworkFirewall Firewall (%s) to delete: %w", d.Id(), err))
+	}
+
+	return nil
+}
+
+func expandNetworkFirewallSubnetMappings(l []interface{}) []*networkfirewall.SubnetMapping {
+	mappings := make([]*networkfirewall.SubnetMapping, 0, len(l))
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		mapping := &networkfirewall.SubnetMapping{
+			SubnetId: aws.String(tfMap["subnet_id"].(string)),
+		}
+		mappings = append(mappings, mapping)
+	}
+
+	return mappings
+}
+
+func expandNetworkFirewallSubnetMappingIds(l []interface{}) []string {
+	var ids []string
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		if id, ok := tfMap["subnet_id"].(string); ok && id != "" {
+			ids = append(ids, id)
+		}
+	}
+
+	return ids
+}
+
+func flattenNetworkFirewallSubnetMappings(sm []*networkfirewall.SubnetMapping) []interface{} {
+	mappings := make([]interface{}, 0, len(sm))
+	for _, s := range sm {
+		m := map[string]interface{}{
+			"subnet_id": aws.StringValue(s.SubnetId),
+		}
+		mappings = append(mappings, m)
+	}
+
+	return mappings
+}
+
+func networkFirewallSubnetMappingsHash(v interface{}) int {
+	var buf bytes.Buffer
+
+	tfMap, ok := v.(map[string]interface{})
+	if !ok {
+		return 0
+	}
+	if id, ok := tfMap["subnet_id"].(string); ok {
+		buf.WriteString(fmt.Sprintf("%s-", id))
+	}
+
+	return hashcode.String(buf.String())
+}
+
+func networkFirewallSubnetMappingsDiff(old, new *schema.Set) ([]string, []*networkfirewall.SubnetMapping) {
+	if old.Len() == 0 {
+		return nil, expandNetworkFirewallSubnetMappings(new.List())
+	}
+	if new.Len() == 0 {
+		return expandNetworkFirewallSubnetMappingIds(old.List()), nil
+	}
+
+	oldHashedSet := schema.NewSet(networkFirewallSubnetMappingsHash, old.List())
+	newHashedSet := schema.NewSet(networkFirewallSubnetMappingsHash, new.List())
+
+	toRemove := oldHashedSet.Difference(newHashedSet)
+	toAdd := new.Difference(old)
+
+	subnetsToRemove := expandNetworkFirewallSubnetMappingIds(toRemove.List())
+	subnetsToAdd := expandNetworkFirewallSubnetMappings(toAdd.List())
+
+	return subnetsToRemove, subnetsToAdd
+}
diff --git a/aws/resource_aws_networkfirewall_firewall_policy.go b/aws/resource_aws_networkfirewall_firewall_policy.go
new file mode 100644
index 00000000000..50dad7b4f33
--- /dev/null
+++ b/aws/resource_aws_networkfirewall_firewall_policy.go
@@ -0,0 +1,371 @@
+package aws
+
+import (
+	"context"
+	"fmt"
+	"log"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/aws-sdk-go-base/tfawserr"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/finder"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/waiter"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
+)
+
+func resourceAwsNetworkFirewallFirewallPolicy() *schema.Resource {
+	return &schema.Resource{
+		CreateContext: resourceAwsNetworkFirewallFirewallPolicyCreate,
+		ReadContext:   resourceAwsNetworkFirewallFirewallPolicyRead,
+		UpdateContext: resourceAwsNetworkFirewallFirewallPolicyUpdate,
+		DeleteContext: resourceAwsNetworkFirewallFirewallPolicyDelete,
+
+		Importer: &schema.ResourceImporter{
+			StateContext: schema.ImportStatePassthroughContext,
+		},
+
+		Schema: map[string]*schema.Schema{
+			"arn": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
+			"description": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"firewall_policy": {
+				Type:     schema.TypeList,
+				Required: true,
+				MaxItems: 1,
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"stateful_rule_group_reference": {
+							Type:     schema.TypeSet,
+							Optional: true,
+							Elem: &schema.Resource{
+								Schema: map[string]*schema.Schema{
+									"resource_arn": {
+										Type:         schema.TypeString,
+										Required:     true,
+										ValidateFunc: validateArn,
+									},
+								},
+							},
+						},
+						"stateless_custom_action": customActionSchema(),
+						"stateless_default_actions": {
+							Type:     schema.TypeSet,
+							Required: true,
+							Elem:     &schema.Schema{Type: schema.TypeString},
+						},
+						"stateless_fragment_default_actions": {
+							Type:     schema.TypeSet,
+							Required: true,
+							Elem:     &schema.Schema{Type: schema.TypeString},
+						},
+						"stateless_rule_group_reference": {
+							Type:     schema.TypeSet,
+							Optional: true,
+							Elem: &schema.Resource{
+								Schema: map[string]*schema.Schema{
+									"priority": {
+										Type:         schema.TypeInt,
+										Required:     true,
+										ValidateFunc: validation.IntAtLeast(1),
+									},
+									"resource_arn": {
+										Type:         schema.TypeString,
+										Required:     true,
+										ValidateFunc: validateArn,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			"name": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"tags": tagsSchema(),
+			"update_token": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
+		},
+	}
+}
+
+func resourceAwsNetworkFirewallFirewallPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	name := d.Get("name").(string)
+	input := &networkfirewall.CreateFirewallPolicyInput{
+		FirewallPolicy:     expandNetworkFirewallFirewallPolicy(d.Get("firewall_policy").([]interface{})),
+		FirewallPolicyName: aws.String(d.Get("name").(string)),
+	}
+
+	if v, ok := d.GetOk("description"); ok {
+		input.Description = aws.String(v.(string))
+	}
+
+	if v, ok := d.GetOk("tags"); ok {
+		input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().NetworkfirewallTags()
+	}
+
+	log.Printf("[DEBUG] Creating NetworkFirewall Firewall Policy %s", name)
+
+	output, err := conn.CreateFirewallPolicyWithContext(ctx, input)
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error creating NetworkFirewall Firewall Policy (%s): %w", name, err))
+	}
+	if output == nil || output.FirewallPolicyResponse == nil {
+		return diag.FromErr(fmt.Errorf("error creating NetworkFirewall Firewall Policy (%s): empty output", name))
+	}
+
+	d.SetId(aws.StringValue(output.FirewallPolicyResponse.FirewallPolicyArn))
+
+	return resourceAwsNetworkFirewallFirewallPolicyRead(ctx, d, meta)
+}
+
+func resourceAwsNetworkFirewallFirewallPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig
+
+	log.Printf("[DEBUG] Reading NetworkFirewall Firewall Policy %s", d.Id())
+
+	output, err := finder.FirewallPolicy(ctx, conn, d.Id())
+	if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+		log.Printf("[WARN] NetworkFirewall Firewall Policy (%s) not found, removing from state", d.Id())
+		d.SetId("")
+		return nil
+	}
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error reading NetworkFirewall Firewall Policy (%s): %w", d.Id(), err))
+	}
+
+	if output == nil {
+		return diag.FromErr(fmt.Errorf("error reading NetworkFirewall Firewall Policy (%s): empty output", d.Id()))
+	}
+	if output.FirewallPolicyResponse == nil {
+		return diag.FromErr(fmt.Errorf("error reading NetworkFirewall Firewall Policy (%s): empty output.FirewallPolicyResponse", d.Id()))
+	}
+
+	resp := output.FirewallPolicyResponse
+	policy := output.FirewallPolicy
+	arn := aws.StringValue(resp.FirewallPolicyArn)
+
+	d.Set("arn", resp.FirewallPolicyArn)
+	d.Set("description", resp.Description)
+	d.Set("name", resp.FirewallPolicyName)
+	d.Set("update_token", output.UpdateToken)
+
+	if err := d.Set("firewall_policy", flattenNetworkFirewallFirewallPolicy(policy)); err != nil {
+		return diag.FromErr(fmt.Errorf("error setting firewall_policy: %w", err))
+	}
+
+	tags, err := keyvaluetags.NetworkfirewallListTags(conn, arn)
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error listing tags for NetworkFirewall Firewall Policy (%s): %w", arn, err))
+	}
+
+	if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
+		return diag.FromErr(fmt.Errorf("error setting tags: %w", err))
+	}
+
+	return nil
+}
+
+func resourceAwsNetworkFirewallFirewallPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	arn := d.Id()
+
+	log.Printf("[DEBUG] Updating NetworkFirewall Firewall Policy %s", arn)
+
+	if d.HasChanges("description", "firewall_policy") {
+		input := &networkfirewall.UpdateFirewallPolicyInput{
+			FirewallPolicy:    expandNetworkFirewallFirewallPolicy(d.Get("firewall_policy").([]interface{})),
+			FirewallPolicyArn: aws.String(arn),
+			UpdateToken:       aws.String(d.Get("update_token").(string)),
+		}
+		// Only pass non-empty description values, else API request returns an InternalServiceError
+		if v, ok := d.GetOk("description"); ok {
+			input.Description = aws.String(v.(string))
+		}
+		_, err := conn.UpdateFirewallPolicyWithContext(ctx, input)
+		if err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall Policy (%s) firewall_policy: %w", arn, err))
+		}
+	}
+
+	if d.HasChange("tags") {
+		o, n := d.GetChange("tags")
+		if err := keyvaluetags.NetworkfirewallUpdateTags(conn, arn, o, n); err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Firewall Policy (%s) tags: %w", arn, err))
+		}
+	}
+
+	return resourceAwsNetworkFirewallFirewallPolicyRead(ctx, d, meta)
+}
+
+func resourceAwsNetworkFirewallFirewallPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+
+	log.Printf("[DEBUG] Deleting NetworkFirewall Firewall Policy %s", d.Id())
+
+	input := &networkfirewall.DeleteFirewallPolicyInput{
+		FirewallPolicyArn: aws.String(d.Id()),
+	}
+
+	err := resource.Retry(waiter.FirewallPolicyTimeout, func() *resource.RetryError {
+		var err error
+		_, err = conn.DeleteFirewallPolicyWithContext(ctx, input)
+		if err != nil {
+			if tfawserr.ErrMessageContains(err, networkfirewall.ErrCodeInvalidOperationException, "Unable to delete the object because it is still in use") {
+				return resource.RetryableError(err)
+			}
+			return resource.NonRetryableError(err)
+		}
+		return nil
+	})
+
+	if tfresource.TimedOut(err) {
+		_, err = conn.DeleteFirewallPolicyWithContext(ctx, input)
+	}
+
+	if err != nil {
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return nil
+		}
+		return diag.FromErr(fmt.Errorf("error deleting NetworkFirewall Firewall Policy (%s): %w", d.Id(), err))
+	}
+
+	if _, err := waiter.FirewallPolicyDeleted(ctx, conn, d.Id()); err != nil {
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return nil
+		}
+		return diag.FromErr(fmt.Errorf("error waiting for NetworkFirewall Firewall Policy (%s) to delete: %w", d.Id(), err))
+	}
+
+	return nil
+}
+
+func expandNetworkFirewallStatefulRuleGroupReferences(l []interface{}) []*networkfirewall.StatefulRuleGroupReference {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	references := make([]*networkfirewall.StatefulRuleGroupReference, 0, len(l))
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		reference := &networkfirewall.StatefulRuleGroupReference{}
+		if v, ok := tfMap["resource_arn"].(string); ok && v != "" {
+			reference.ResourceArn = aws.String(v)
+		}
+		references = append(references, reference)
+	}
+	return references
+}
+
+func expandNetworkFirewallStatelessRuleGroupReferences(l []interface{}) []*networkfirewall.StatelessRuleGroupReference {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	references := make([]*networkfirewall.StatelessRuleGroupReference, 0, len(l))
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		reference := &networkfirewall.StatelessRuleGroupReference{}
+		if v, ok := tfMap["priority"].(int); ok && v > 0 {
+			reference.Priority = aws.Int64(int64(v))
+		}
+		if v, ok := tfMap["resource_arn"].(string); ok && v != "" {
+			reference.ResourceArn = aws.String(v)
+		}
+		references = append(references, reference)
+	}
+	return references
+}
+
+func expandNetworkFirewallFirewallPolicy(l []interface{}) *networkfirewall.FirewallPolicy {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	lRaw := l[0].(map[string]interface{})
+	policy := &networkfirewall.FirewallPolicy{
+		StatelessDefaultActions:         expandStringSet(lRaw["stateless_default_actions"].(*schema.Set)),
+		StatelessFragmentDefaultActions: expandStringSet(lRaw["stateless_fragment_default_actions"].(*schema.Set)),
+	}
+
+	if v, ok := lRaw["stateful_rule_group_reference"].(*schema.Set); ok && v.Len() > 0 {
+		policy.StatefulRuleGroupReferences = expandNetworkFirewallStatefulRuleGroupReferences(v.List())
+	}
+
+	if v, ok := lRaw["stateless_custom_action"].(*schema.Set); ok && v.Len() > 0 {
+		policy.StatelessCustomActions = expandNetworkFirewallCustomActions(v.List())
+	}
+
+	if v, ok := lRaw["stateless_rule_group_reference"].(*schema.Set); ok && v.Len() > 0 {
+		policy.StatelessRuleGroupReferences = expandNetworkFirewallStatelessRuleGroupReferences(v.List())
+	}
+
+	return policy
+}
+
+func flattenNetworkFirewallFirewallPolicy(policy *networkfirewall.FirewallPolicy) []interface{} {
+	if policy == nil {
+		return []interface{}{}
+	}
+	p := map[string]interface{}{}
+	if policy.StatefulRuleGroupReferences != nil {
+		p["stateful_rule_group_reference"] = flattenNetworkFirewallPolicyStatefulRuleGroupReference(policy.StatefulRuleGroupReferences)
+	}
+	if policy.StatelessCustomActions != nil {
+		p["stateless_custom_action"] = flattenNetworkFirewallCustomActions(policy.StatelessCustomActions)
+	}
+	if policy.StatelessDefaultActions != nil {
+		p["stateless_default_actions"] = flattenStringSet(policy.StatelessDefaultActions)
+	}
+	if policy.StatelessFragmentDefaultActions != nil {
+		p["stateless_fragment_default_actions"] = flattenStringSet(policy.StatelessFragmentDefaultActions)
+	}
+	if policy.StatelessRuleGroupReferences != nil {
+		p["stateless_rule_group_reference"] = flattenNetworkFirewallPolicyStatelessRuleGroupReference(policy.StatelessRuleGroupReferences)
+	}
+
+	return []interface{}{p}
+}
+
+func flattenNetworkFirewallPolicyStatefulRuleGroupReference(l []*networkfirewall.StatefulRuleGroupReference) []interface{} {
+	references := make([]interface{}, 0, len(l))
+	for _, ref := range l {
+		reference := map[string]interface{}{
+			"resource_arn": aws.StringValue(ref.ResourceArn),
+		}
+		references = append(references, reference)
+	}
+
+	return references
+}
+
+func flattenNetworkFirewallPolicyStatelessRuleGroupReference(l []*networkfirewall.StatelessRuleGroupReference) []interface{} {
+	references := make([]interface{}, 0, len(l))
+	for _, ref := range l {
+		reference := map[string]interface{}{
+			"priority":     int(aws.Int64Value(ref.Priority)),
+			"resource_arn": aws.StringValue(ref.ResourceArn),
+		}
+		references = append(references, reference)
+	}
+	return references
+}
diff --git a/aws/resource_aws_networkfirewall_firewall_policy_test.go b/aws/resource_aws_networkfirewall_firewall_policy_test.go
new file mode 100644
index 00000000000..458c85951c5
--- /dev/null
+++ b/aws/resource_aws_networkfirewall_firewall_policy_test.go
@@ -0,0 +1,964 @@
+package aws
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/aws-sdk-go-base/tfawserr"
+	"github.com/hashicorp/go-multierror"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/finder"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource"
+)
+
+func init() {
+	resource.AddTestSweepers("aws_networkfirewall_firewall_policy", &resource.Sweeper{
+		Name: "aws_networkfirewall_firewall_policy",
+		F:    testSweepNetworkFirewallFirewallPolicies,
+		Dependencies: []string{
+			"aws_networkfirewall_firewall",
+		},
+	})
+}
+
+func testSweepNetworkFirewallFirewallPolicies(region string) error {
+	client, err := sharedClientForRegion(region)
+	if err != nil {
+		return fmt.Errorf("error getting client: %w", err)
+	}
+	conn := client.(*AWSClient).networkfirewallconn
+	ctx := context.Background()
+	input := &networkfirewall.ListFirewallPoliciesInput{MaxResults: aws.Int64(100)}
+	var sweeperErrs *multierror.Error
+
+	for {
+		resp, err := conn.ListFirewallPoliciesWithContext(ctx, input)
+		if testSweepSkipSweepError(err) {
+			log.Printf("[WARN] Skipping NetworkFirewall Firewall Policy sweep for %s: %s", region, err)
+			return nil
+		}
+		if err != nil {
+			return fmt.Errorf("error retrieving NetworkFirewall Firewall Policies: %w", err)
+		}
+
+		for _, fp := range resp.FirewallPolicies {
+			if fp == nil {
+				continue
+			}
+
+			arn := aws.StringValue(fp.Arn)
+			log.Printf("[INFO] Deleting NetworkFirewall Firewall Policy: %s", arn)
+
+			r := resourceAwsNetworkFirewallFirewallPolicy()
+			d := r.Data(nil)
+			d.SetId(arn)
+			diags := r.DeleteContext(ctx, d, client)
+			for i := range diags {
+				if diags[i].Severity == diag.Error {
+					log.Printf("[ERROR] %s", diags[i].Summary)
+					sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf(diags[i].Summary))
+					continue
+				}
+			}
+		}
+
+		if aws.StringValue(resp.NextToken) == "" {
+			break
+		}
+		input.NextToken = resp.NextToken
+	}
+
+	return sweeperErrs.ErrorOrNil()
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_basic(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					testAccCheckResourceAttrRegionalARN(resourceName, "arn", "network-firewall", fmt.Sprintf("firewall-policy/%s", rName)),
+					resource.TestCheckResourceAttr(resourceName, "description", ""),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_fragment_default_actions.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "firewall_policy.0.stateless_fragment_default_actions.*", "aws:drop"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_default_actions.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "firewall_policy.0.stateless_default_actions.*", "aws:pass"),
+					resource.TestCheckResourceAttr(resourceName, "name", rName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_statefulRuleGroupReference(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+	ruleGroupResourceName := "aws_networkfirewall_rule_group.test.0"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statefulRuleGroupReference(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateful_rule_group_reference.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateful_rule_group_reference.*.resource_arn", ruleGroupResourceName, "arn"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_updateStatefulRuleGroupReference(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+	ruleGroupResourceName := "aws_networkfirewall_rule_group.test.0"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statefulRuleGroupReference(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateful_rule_group_reference.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateful_rule_group_reference.*.resource_arn", ruleGroupResourceName, "arn"),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_multipleStatefulRuleGroupReferences(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+	ruleGroupResourceName1 := "aws_networkfirewall_rule_group.test.0"
+	ruleGroupResourceName2 := "aws_networkfirewall_rule_group.test.1"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_multipleStatefulRuleGroupReferences(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateful_rule_group_reference.#", "2"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateful_rule_group_reference.*.resource_arn", ruleGroupResourceName1, "arn"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateful_rule_group_reference.*.resource_arn", ruleGroupResourceName2, "arn"),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_singleStatefulRuleGroupReference(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateful_rule_group_reference.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateful_rule_group_reference.*.resource_arn", ruleGroupResourceName1, "arn"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_statelessRuleGroupReference(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+	ruleGroupResourceName := "aws_networkfirewall_rule_group.test.0"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statelessRuleGroupReference(rName, 20),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_rule_group_reference.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateless_rule_group_reference.*.resource_arn", ruleGroupResourceName, "arn"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_rule_group_reference.*", map[string]string{
+						"priority": "20",
+					}),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statelessRuleGroupReference(rName, 1),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_rule_group_reference.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_rule_group_reference.*", map[string]string{
+						"priority": "1",
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_updateStatelessRuleGroupReference(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+	ruleGroupResourceName := "aws_networkfirewall_rule_group.test.0"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statelessRuleGroupReference(rName, 20),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateless_rule_group_reference.*.resource_arn", ruleGroupResourceName, "arn"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_rule_group_reference.*", map[string]string{
+						"priority": "20",
+					}),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_rule_group_reference.#", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_multipleStatelessRuleGroupReferences(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+	ruleGroupResourceName1 := "aws_networkfirewall_rule_group.test.0"
+	ruleGroupResourceName2 := "aws_networkfirewall_rule_group.test.1"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_multipleStatelessRuleGroupReferences(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_rule_group_reference.#", "2"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateless_rule_group_reference.*.resource_arn", ruleGroupResourceName1, "arn"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_rule_group_reference.*", map[string]string{
+						"priority": "1",
+					}),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateless_rule_group_reference.*.resource_arn", ruleGroupResourceName2, "arn"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_rule_group_reference.*", map[string]string{
+						"priority": "2",
+					}),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_singleStatelessRuleGroupReference(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_rule_group_reference.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_rule_group_reference.*", map[string]string{
+						"priority": "1",
+					}),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateless_rule_group_reference.*.resource_arn", ruleGroupResourceName1, "arn"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_statelessCustomAction(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statelessCustomAction(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_custom_action.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_custom_action.*", map[string]string{
+						"action_definition.#":                                     "1",
+						"action_definition.0.publish_metric_action.#":             "1",
+						"action_definition.0.publish_metric_action.0.dimension.#": "1",
+						"action_name": "CustomAction",
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_updateStatelessCustomAction(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statelessCustomAction(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_custom_action.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_custom_action.*", map[string]string{
+						"action_name":         "CustomAction",
+						"action_definition.#": "1",
+						"action_definition.0.publish_metric_action.#":             "1",
+						"action_definition.0.publish_metric_action.0.dimension.#": "1",
+					}),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_updateStatelessCustomAction(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_custom_action.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_custom_action.*", map[string]string{
+						"action_name":         "updated",
+						"action_definition.#": "1",
+						"action_definition.0.publish_metric_action.#":             "1",
+						"action_definition.0.publish_metric_action.0.dimension.#": "1",
+					}),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_custom_action.#", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_multipleStatelessCustomActions(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_multipleStatelessCustomActions(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_custom_action.#", "2"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_custom_action.*", map[string]string{
+						"action_name":         "CustomAction",
+						"action_definition.#": "1",
+						"action_definition.0.publish_metric_action.#":             "1",
+						"action_definition.0.publish_metric_action.0.dimension.#": "1",
+					}),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_custom_action.*", map[string]string{
+						"action_name":         "CustomAction2",
+						"action_definition.#": "1",
+						"action_definition.0.publish_metric_action.#":             "1",
+						"action_definition.0.publish_metric_action.0.dimension.#": "1",
+					}),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statelessCustomAction(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateless_custom_action.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_custom_action.*", map[string]string{
+						"action_name":         "CustomAction",
+						"action_definition.#": "1",
+						"action_definition.0.publish_metric_action.#":             "1",
+						"action_definition.0.publish_metric_action.0.dimension.#": "1",
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_statefulRuleGroupReferenceAndCustomAction(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+	ruleGroupResourceName := "aws_networkfirewall_rule_group.test.0"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statefulRuleGroupReferenceAndStatelessCustomAction(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateful_rule_group_reference.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateful_rule_group_reference.*.resource_arn", ruleGroupResourceName, "arn"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "firewall_policy.0.stateless_custom_action.*", map[string]string{
+						"action_name":         "CustomAction",
+						"action_definition.#": "1",
+						"action_definition.0.publish_metric_action.#":             "1",
+						"action_definition.0.publish_metric_action.0.dimension.#": "1",
+					}),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_statefulRuleGroupReference(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "firewall_policy.0.stateful_rule_group_reference.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_policy.0.stateful_rule_group_reference.*.resource_arn", ruleGroupResourceName, "arn"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_tags(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_oneTag(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
+					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_twoTags(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
+					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
+					resource.TestCheckResourceAttr(resourceName, "tags.Description", "updated"),
+				),
+			},
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewallPolicy_disappears(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall_policy.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallPolicyDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccAwsNetworkFirewallFirewallPolicy_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallPolicyExists(resourceName),
+					testAccCheckResourceDisappears(testAccProvider, resourceAwsNetworkFirewallFirewallPolicy(), resourceName),
+				),
+				ExpectNonEmptyPlan: true,
+			},
+		},
+	})
+}
+
+func testAccCheckAwsNetworkFirewallFirewallPolicyDestroy(s *terraform.State) error {
+	for _, rs := range s.RootModule().Resources {
+		if rs.Type != "aws_networkfirewall_firewall_policy" {
+			continue
+		}
+
+		conn := testAccProvider.Meta().(*AWSClient).networkfirewallconn
+		output, err := finder.FirewallPolicy(context.Background(), conn, rs.Primary.ID)
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			continue
+		}
+		if err != nil {
+			return err
+		}
+		if output != nil {
+			return fmt.Errorf("NetworkFirewall Firewall Policy still exists: %s", rs.Primary.ID)
+		}
+	}
+
+	return nil
+}
+
+func testAccCheckAwsNetworkFirewallFirewallPolicyExists(n string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		rs, ok := s.RootModule().Resources[n]
+		if !ok {
+			return fmt.Errorf("Not found: %s", n)
+		}
+
+		if rs.Primary.ID == "" {
+			return fmt.Errorf("No NetworkFirewall Firewall Policy ID is set")
+		}
+
+		conn := testAccProvider.Meta().(*AWSClient).networkfirewallconn
+		output, err := finder.FirewallPolicy(context.Background(), conn, rs.Primary.ID)
+		if err != nil {
+			return err
+		}
+
+		if output == nil {
+			return fmt.Errorf("NetworkFirewall Firewall Policy (%s) not found", rs.Primary.ID)
+		}
+
+		return nil
+	}
+}
+
+func testAccAwsNetworkFirewallFirewallPolicyStatelessRuleGroupDependencies(rName string, count int) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  count    = %d
+  capacity = 100
+  name     = "%s-${count.index}"
+  type     = "STATELESS"
+  rule_group {
+    rules_source {
+      stateless_rules_and_custom_actions {
+        stateless_rule {
+          priority = 1
+          rule_definition {
+            actions = ["aws:drop"]
+            match_attributes {
+              destination {
+                address_definition = "1.2.3.4/32"
+              }
+              source {
+                address_definition = "124.1.1.5/32"
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  lifecycle {
+    create_before_destroy = true
+  }
+}
+`, count, rName)
+}
+
+func testAccAwsNetworkFirewallFirewallPolicyStatefulRuleGroupDependencies(rName string, count int) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  count    = %d
+  capacity = 100
+  name     = "%s-${count.index}"
+  type     = "STATEFUL"
+  rule_group {
+    rules_source {
+      rules_source_list {
+        generated_rules_type = "ALLOWLIST"
+        target_types         = ["HTTP_HOST"]
+        targets              = ["test.example.com"]
+      }
+    }
+  }
+  lifecycle {
+    create_before_destroy = true
+  }
+}
+`, count, rName)
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_basic(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+  }
+}
+`, rName)
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_oneTag(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %[1]q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+  }
+  tags = {
+    Name = %[1]q
+  }
+}
+`, rName)
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_twoTags(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %[1]q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+  }
+  tags = {
+    Name        = %[1]q
+    Description = "updated"
+  }
+}
+`, rName)
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_statefulRuleGroupReference(rName string) string {
+	return composeConfig(
+		testAccAwsNetworkFirewallFirewallPolicyStatefulRuleGroupDependencies(rName, 1),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateful_rule_group_reference {
+      resource_arn = aws_networkfirewall_rule_group.test[0].arn
+    }
+  }
+}
+`, rName))
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_multipleStatefulRuleGroupReferences(rName string) string {
+	return composeConfig(
+		testAccAwsNetworkFirewallFirewallPolicyStatefulRuleGroupDependencies(rName, 2),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateful_rule_group_reference {
+      resource_arn = aws_networkfirewall_rule_group.test[0].arn
+    }
+    stateful_rule_group_reference {
+      resource_arn = aws_networkfirewall_rule_group.test[1].arn
+    }
+  }
+}
+`, rName))
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_singleStatefulRuleGroupReference(rName string) string {
+	return composeConfig(
+		testAccAwsNetworkFirewallFirewallPolicyStatefulRuleGroupDependencies(rName, 2),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateful_rule_group_reference {
+      resource_arn = aws_networkfirewall_rule_group.test[0].arn
+    }
+  }
+}
+`, rName))
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_statelessRuleGroupReference(rName string, priority int) string {
+	return composeConfig(
+		testAccAwsNetworkFirewallFirewallPolicyStatelessRuleGroupDependencies(rName, 1),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateless_rule_group_reference {
+      priority     = %d
+      resource_arn = aws_networkfirewall_rule_group.test[0].arn
+    }
+  }
+}
+`, rName, priority))
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_multipleStatelessRuleGroupReferences(rName string) string {
+	return composeConfig(
+		testAccAwsNetworkFirewallFirewallPolicyStatelessRuleGroupDependencies(rName, 2),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateless_rule_group_reference {
+      priority     = 1
+      resource_arn = aws_networkfirewall_rule_group.test[0].arn
+    }
+    stateless_rule_group_reference {
+      priority     = 2
+      resource_arn = aws_networkfirewall_rule_group.test[1].arn
+    }
+  }
+}
+`, rName))
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_singleStatelessRuleGroupReference(rName string) string {
+	return composeConfig(
+		testAccAwsNetworkFirewallFirewallPolicyStatelessRuleGroupDependencies(rName, 2),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateless_rule_group_reference {
+      priority     = 1
+      resource_arn = aws_networkfirewall_rule_group.test[0].arn
+    }
+  }
+}
+`, rName))
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_statelessCustomAction(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %[1]q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateless_custom_action {
+      action_name = "CustomAction"
+      action_definition {
+        publish_metric_action {
+          dimension {
+            value = "example"
+          }
+        }
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_updateStatelessCustomAction(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %[1]q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateless_custom_action {
+      action_name = "updated"
+      action_definition {
+        publish_metric_action {
+          dimension {
+            value = "example-update"
+          }
+        }
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_multipleStatelessCustomActions(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %[1]q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateless_custom_action {
+      action_definition {
+        publish_metric_action {
+          dimension {
+            value = "example"
+          }
+        }
+      }
+      action_name = "CustomAction"
+    }
+    stateless_custom_action {
+      action_definition {
+        publish_metric_action {
+          dimension {
+            value = "example-custom-action"
+          }
+        }
+      }
+      action_name = "CustomAction2"
+    }
+  }
+}
+`, rName)
+}
+
+func testAccAwsNetworkFirewallFirewallPolicy_statefulRuleGroupReferenceAndStatelessCustomAction(rName string) string {
+	return composeConfig(
+		testAccAwsNetworkFirewallFirewallPolicyStatefulRuleGroupDependencies(rName, 1),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %[1]q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+    stateful_rule_group_reference {
+      resource_arn = aws_networkfirewall_rule_group.test[0].arn
+    }
+    stateless_custom_action {
+      action_definition {
+        publish_metric_action {
+          dimension {
+            value = "example"
+          }
+        }
+      }
+      action_name = "CustomAction"
+    }
+  }
+}
+`, rName))
+}
diff --git a/aws/resource_aws_networkfirewall_firewall_test.go b/aws/resource_aws_networkfirewall_firewall_test.go
new file mode 100644
index 00000000000..9f5366702f8
--- /dev/null
+++ b/aws/resource_aws_networkfirewall_firewall_test.go
@@ -0,0 +1,569 @@
+package aws
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/aws-sdk-go-base/tfawserr"
+	"github.com/hashicorp/go-multierror"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/finder"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource"
+)
+
+func init() {
+	resource.AddTestSweepers("aws_networkfirewall_firewall", &resource.Sweeper{
+		Name:         "aws_networkfirewall_firewall",
+		F:            testSweepNetworkFirewallFirewalls,
+		Dependencies: []string{"aws_networkfirewall_logging_configuration"},
+	})
+}
+
+func testSweepNetworkFirewallFirewalls(region string) error {
+	client, err := sharedClientForRegion(region)
+	if err != nil {
+		return fmt.Errorf("error getting client: %s", err)
+	}
+	conn := client.(*AWSClient).networkfirewallconn
+	ctx := context.TODO()
+	input := &networkfirewall.ListFirewallsInput{MaxResults: aws.Int64(100)}
+	var sweeperErrs *multierror.Error
+
+	for {
+		resp, err := conn.ListFirewallsWithContext(ctx, input)
+		if testSweepSkipSweepError(err) {
+			log.Printf("[WARN] Skipping NetworkFirewall Firewall sweep for %s: %s", region, err)
+			return nil
+		}
+		if err != nil {
+			return fmt.Errorf("error retrieving NetworkFirewall firewalls: %s", err)
+		}
+
+		for _, f := range resp.Firewalls {
+			if f == nil {
+				continue
+			}
+
+			arn := aws.StringValue(f.FirewallArn)
+
+			log.Printf("[INFO] Deleting NetworkFirewall Firewall: %s", arn)
+
+			r := resourceAwsNetworkFirewallFirewall()
+			d := r.Data(nil)
+			d.SetId(arn)
+			diags := r.DeleteContext(ctx, d, client)
+			for i := range diags {
+				if diags[i].Severity == diag.Error {
+					log.Printf("[ERROR] %s", diags[i].Summary)
+					sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf(diags[i].Summary))
+					continue
+				}
+			}
+		}
+
+		if aws.StringValue(resp.NextToken) == "" {
+			break
+		}
+		input.NextToken = resp.NextToken
+	}
+
+	return sweeperErrs.ErrorOrNil()
+}
+
+func TestAccAwsNetworkFirewallFirewall_basic(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall.test"
+	policyResourceName := "aws_networkfirewall_firewall_policy.test"
+	subnetResourceName := "aws_subnet.test"
+	vpcResourceName := "aws_vpc.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallFirewall_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					testAccCheckResourceAttrRegionalARN(resourceName, "arn", "network-firewall", fmt.Sprintf("firewall/%s", rName)),
+					resource.TestCheckResourceAttr(resourceName, "delete_protection", "false"),
+					resource.TestCheckResourceAttr(resourceName, "description", ""),
+					resource.TestCheckResourceAttrPair(resourceName, "firewall_policy_arn", policyResourceName, "arn"),
+					resource.TestCheckResourceAttr(resourceName, "name", rName),
+					resource.TestCheckResourceAttrPair(resourceName, "vpc_id", vpcResourceName, "id"),
+					resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", subnetResourceName, "id"),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewall_description(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallFirewall_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "description", ""),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallFirewall_updateDescription(rName, "updated"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "description", "updated"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallFirewall_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "description", ""),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewall_deleteProtection(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallDestroy,
+		Steps: []resource.TestStep{
+
+			{
+				Config: testAccNetworkFirewallFirewall_deleteProtection(rName, false),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "delete_protection", "false"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallFirewall_deleteProtection(rName, true),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "delete_protection", "true"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallFirewall_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "delete_protection", "false"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewall_subnetMappings_updateSubnet(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall.test"
+	subnetResourceName := "aws_subnet.test"
+	updateSubnetResourceName := "aws_subnet.example"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallDestroy,
+		Steps: []resource.TestStep{
+
+			{
+				Config: testAccNetworkFirewallFirewall_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", subnetResourceName, "id"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallFirewall_updateSubnet(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", updateSubnetResourceName, "id"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewall_subnetMappings_updateMultipleSubnets(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall.test"
+	subnetResourceName := "aws_subnet.test"
+	updateSubnetResourceName := "aws_subnet.example"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallFirewall_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", subnetResourceName, "id"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallFirewall_updateMultipleSubnets(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "2"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", subnetResourceName, "id"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", updateSubnetResourceName, "id"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallFirewall_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", subnetResourceName, "id"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewall_tags(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall.test"
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallFirewall_oneTag(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
+					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallFirewall_twoTags(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
+					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
+					resource.TestCheckResourceAttr(resourceName, "tags.Description", "updated"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallFirewall_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallFirewall_disappears(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_firewall.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallFirewallDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallFirewall_basic(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallFirewallExists(resourceName),
+					testAccCheckResourceDisappears(testAccProvider, resourceAwsNetworkFirewallFirewall(), resourceName),
+				),
+				ExpectNonEmptyPlan: true,
+			},
+		},
+	})
+}
+
+func testAccCheckAwsNetworkFirewallFirewallDestroy(s *terraform.State) error {
+	for _, rs := range s.RootModule().Resources {
+		if rs.Type != "aws_networkfirewall_firewall" {
+			continue
+		}
+
+		conn := testAccProvider.Meta().(*AWSClient).networkfirewallconn
+		output, err := finder.Firewall(context.Background(), conn, rs.Primary.ID)
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			continue
+		}
+		if err != nil {
+			return err
+		}
+		if output != nil {
+			return fmt.Errorf("NetworkFirewall Firewall still exists: %s", rs.Primary.ID)
+		}
+	}
+
+	return nil
+}
+
+func testAccCheckAwsNetworkFirewallFirewallExists(n string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		rs, ok := s.RootModule().Resources[n]
+		if !ok {
+			return fmt.Errorf("Not found: %s", n)
+		}
+
+		if rs.Primary.ID == "" {
+			return fmt.Errorf("No NetworkFirewall Firewall ID is set")
+		}
+
+		conn := testAccProvider.Meta().(*AWSClient).networkfirewallconn
+		output, err := finder.Firewall(context.Background(), conn, rs.Primary.ID)
+		if err != nil {
+			return err
+		}
+
+		if output == nil {
+			return fmt.Errorf("NetworkFirewall Firewall (%s) not found", rs.Primary.ID)
+		}
+
+		return nil
+	}
+}
+
+func testAccNetworkFirewallFirewallDependenciesConfig(rName string) string {
+	return fmt.Sprintf(`
+data "aws_availability_zones" "available" {
+  state = "available"
+
+  filter {
+    name   = "opt-in-status"
+    values = ["opt-in-not-required"]
+  }
+}
+
+resource "aws_vpc" "test" {
+  cidr_block = "192.168.0.0/16"
+
+  tags = {
+    Name = %[1]q
+  }
+}
+
+resource "aws_subnet" "test" {
+  availability_zone = data.aws_availability_zones.available.names[0]
+  cidr_block        = cidrsubnet(aws_vpc.test.cidr_block, 8, 0)
+  vpc_id            = aws_vpc.test.id
+
+  tags = {
+    Name = %[1]q
+  }
+}
+
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %[1]q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallFirewall_basic(rName string) string {
+	return composeConfig(
+		testAccNetworkFirewallFirewallDependenciesConfig(rName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall" "test" {
+  name                = %[1]q
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.test.arn
+  vpc_id              = aws_vpc.test.id
+
+  subnet_mapping {
+    subnet_id = aws_subnet.test.id
+  }
+}
+`, rName))
+}
+
+func testAccNetworkFirewallFirewall_deleteProtection(rName string, deleteProtection bool) string {
+	return composeConfig(
+		testAccNetworkFirewallFirewallDependenciesConfig(rName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall" "test" {
+  delete_protection   = %t
+  name                = %q
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.test.arn
+  vpc_id              = aws_vpc.test.id
+
+  subnet_mapping {
+    subnet_id = aws_subnet.test.id
+  }
+}
+`, deleteProtection, rName))
+}
+
+func testAccNetworkFirewallFirewall_oneTag(rName string) string {
+	return composeConfig(
+		testAccNetworkFirewallFirewallDependenciesConfig(rName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall" "test" {
+  name                = %[1]q
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.test.arn
+  vpc_id              = aws_vpc.test.id
+  subnet_mapping {
+    subnet_id = aws_subnet.test.id
+  }
+  tags = {
+    Name = %[1]q
+  }
+}
+`, rName))
+}
+
+func testAccNetworkFirewallFirewall_twoTags(rName string) string {
+	return composeConfig(
+		testAccNetworkFirewallFirewallDependenciesConfig(rName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall" "test" {
+  name                = %[1]q
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.test.arn
+  vpc_id              = aws_vpc.test.id
+  subnet_mapping {
+    subnet_id = aws_subnet.test.id
+  }
+  tags = {
+    Name        = %[1]q
+    Description = "updated"
+  }
+}
+`, rName))
+}
+
+func testAccNetworkFirewallFirewall_updateDescription(rName, description string) string {
+	return composeConfig(
+		testAccNetworkFirewallFirewallDependenciesConfig(rName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_firewall" "test" {
+  name                = %q
+  description         = %q
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.test.arn
+  vpc_id              = aws_vpc.test.id
+  subnet_mapping {
+    subnet_id = aws_subnet.test.id
+  }
+}
+`, rName, description))
+}
+
+func testAccNetworkFirewallFirewall_updateSubnet(rName string) string {
+	return composeConfig(
+		testAccNetworkFirewallFirewallDependenciesConfig(rName),
+		fmt.Sprintf(`
+resource "aws_subnet" "example" {
+  availability_zone = data.aws_availability_zones.available.names[1]
+  cidr_block        = cidrsubnet(aws_vpc.test.cidr_block, 8, 1)
+  vpc_id            = aws_vpc.test.id
+
+  tags = {
+    Name = %[1]q
+  }
+}
+
+resource "aws_networkfirewall_firewall" "test" {
+  name                = %[1]q
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.test.arn
+  vpc_id              = aws_vpc.test.id
+
+  subnet_mapping {
+    subnet_id = aws_subnet.example.id
+  }
+}
+`, rName))
+}
+
+func testAccNetworkFirewallFirewall_updateMultipleSubnets(rName string) string {
+	return composeConfig(
+		testAccNetworkFirewallFirewallDependenciesConfig(rName),
+		fmt.Sprintf(`
+resource "aws_subnet" "example" {
+  availability_zone = data.aws_availability_zones.available.names[1]
+  cidr_block        = cidrsubnet(aws_vpc.test.cidr_block, 8, 1)
+  vpc_id            = aws_vpc.test.id
+
+  tags = {
+    Name = %[1]q
+  }
+
+  lifecycle {
+    create_before_destroy = true
+  }
+}
+
+resource "aws_networkfirewall_firewall" "test" {
+  name                = %[1]q
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.test.arn
+  vpc_id              = aws_vpc.test.id
+
+  subnet_mapping {
+    subnet_id = aws_subnet.test.id
+  }
+
+  subnet_mapping {
+    subnet_id = aws_subnet.example.id
+  }
+}
+`, rName))
+}
diff --git a/aws/resource_aws_networkfirewall_logging_configuration.go b/aws/resource_aws_networkfirewall_logging_configuration.go
new file mode 100644
index 00000000000..a59f9abb391
--- /dev/null
+++ b/aws/resource_aws_networkfirewall_logging_configuration.go
@@ -0,0 +1,326 @@
+package aws
+
+import (
+	"context"
+	"fmt"
+	"log"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/aws-sdk-go-base/tfawserr"
+	"github.com/hashicorp/go-multierror"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/finder"
+)
+
+func resourceAwsNetworkFirewallLoggingConfiguration() *schema.Resource {
+	return &schema.Resource{
+		CreateContext: resourceAwsNetworkFirewallLoggingConfigurationCreate,
+		ReadContext:   resourceAwsNetworkFirewallLoggingConfigurationRead,
+		UpdateContext: resourceAwsNetworkFirewallLoggingConfigurationUpdate,
+		DeleteContext: resourceAwsNetworkFirewallLoggingConfigurationDelete,
+
+		Importer: &schema.ResourceImporter{
+			StateContext: schema.ImportStatePassthroughContext,
+		},
+
+		Schema: map[string]*schema.Schema{
+			"firewall_arn": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"logging_configuration": {
+				Type:     schema.TypeList,
+				Required: true,
+				MaxItems: 1,
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"log_destination_config": {
+							// At most 2 configurations can exist,
+							// with 1 destination for FLOW logs and 1 for ALERT logs
+							Type:     schema.TypeSet,
+							Required: true,
+							MaxItems: 2,
+							Elem: &schema.Resource{
+								Schema: map[string]*schema.Schema{
+									"log_destination": {
+										Type:     schema.TypeMap,
+										Required: true,
+										Elem:     &schema.Schema{Type: schema.TypeString},
+									},
+									"log_destination_type": {
+										Type:         schema.TypeString,
+										Required:     true,
+										ValidateFunc: validation.StringInSlice(networkfirewall.LogDestinationType_Values(), false),
+									},
+									"log_type": {
+										Type:         schema.TypeString,
+										Required:     true,
+										ValidateFunc: validation.StringInSlice(networkfirewall.LogType_Values(), false),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func resourceAwsNetworkFirewallLoggingConfigurationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	firewallArn := d.Get("firewall_arn").(string)
+
+	log.Printf("[DEBUG] Adding Logging Configuration to NetworkFirewall Firewall: %s", firewallArn)
+
+	loggingConfigs := expandNetworkFirewallLoggingConfiguration(d.Get("logging_configuration").([]interface{}))
+	// cumulatively add the configured "log_destination_config" in "logging_configuration"
+	err := putNetworkFirewallLoggingConfiguration(ctx, conn, firewallArn, loggingConfigs)
+	if err != nil {
+		return diag.FromErr(err)
+	}
+
+	d.SetId(firewallArn)
+
+	return resourceAwsNetworkFirewallLoggingConfigurationRead(ctx, d, meta)
+}
+
+func resourceAwsNetworkFirewallLoggingConfigurationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+
+	log.Printf("[DEBUG] Reading Logging Configuration for NetworkFirewall Firewall: %s", d.Id())
+
+	output, err := finder.LoggingConfiguration(ctx, conn, d.Id())
+	if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+		log.Printf("[WARN] Logging Configuration for NetworkFirewall Firewall (%s) not found, removing from state", d.Id())
+		d.SetId("")
+		return nil
+	}
+
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error reading Logging Configuration for NetworkFirewall Firewall: %s: %w", d.Id(), err))
+	}
+
+	if output == nil {
+		return diag.FromErr(fmt.Errorf("error reading Logging Configuration for NetworkFirewall Firewall: %s: empty output", d.Id()))
+	}
+
+	d.Set("firewall_arn", output.FirewallArn)
+
+	if err := d.Set("logging_configuration", flattenNetworkFirewallLoggingConfiguration(output.LoggingConfiguration)); err != nil {
+		return diag.FromErr(fmt.Errorf("error setting logging_configuration: %w", err))
+	}
+
+	return nil
+}
+
+func resourceAwsNetworkFirewallLoggingConfigurationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+
+	log.Printf("[DEBUG] Updating Logging Configuration for NetworkFirewall Firewall: %s", d.Id())
+
+	o, n := d.GetChange("logging_configuration")
+	// Remove destination configs one by one, if any
+	if oldConfig := o.([]interface{}); len(oldConfig) != 0 && oldConfig[0] != nil {
+		loggingConfig := expandNetworkFirewallLoggingConfigurationOnUpdate(oldConfig)
+		if loggingConfig != nil {
+			err := removeNetworkFirewallLoggingConfiguration(ctx, conn, d.Id(), loggingConfig)
+			if err != nil {
+				return diag.FromErr(err)
+			}
+		}
+	}
+	// Only send new LoggingConfiguration with content
+	if newConfig := n.([]interface{}); len(newConfig) != 0 && newConfig[0] != nil {
+		loggingConfigs := expandNetworkFirewallLoggingConfiguration(d.Get("logging_configuration").([]interface{}))
+		// cumulatively add the configured "log_destination_config" in "logging_configuration"
+		err := putNetworkFirewallLoggingConfiguration(ctx, conn, d.Id(), loggingConfigs)
+		if err != nil {
+			return diag.FromErr(err)
+		}
+	}
+
+	return resourceAwsNetworkFirewallLoggingConfigurationRead(ctx, d, meta)
+}
+
+func resourceAwsNetworkFirewallLoggingConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+
+	log.Printf("[DEBUG] Deleting Logging Configuration for NetworkFirewall Firewall: %s", d.Id())
+
+	output, err := finder.LoggingConfiguration(ctx, conn, d.Id())
+	if err != nil {
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return nil
+		}
+		return diag.FromErr(fmt.Errorf("error deleting Logging Configuration for NetworkFirewall Firewall: %s: %w", d.Id(), err))
+	}
+
+	if output != nil && output.LoggingConfiguration != nil {
+		err := removeNetworkFirewallLoggingConfiguration(ctx, conn, aws.StringValue(output.FirewallArn), output.LoggingConfiguration)
+		if err != nil {
+			return diag.FromErr(err)
+		}
+	}
+
+	return nil
+}
+
+func putNetworkFirewallLoggingConfiguration(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string, l []*networkfirewall.LoggingConfiguration) error {
+	var errors *multierror.Error
+	for _, config := range l {
+		input := &networkfirewall.UpdateLoggingConfigurationInput{
+			FirewallArn:          aws.String(arn),
+			LoggingConfiguration: config,
+		}
+		_, err := conn.UpdateLoggingConfigurationWithContext(ctx, input)
+		if err != nil {
+			errors = multierror.Append(errors, fmt.Errorf("error adding Logging Configuration to NetworkFirewall Firewall (%s): %w", arn, err))
+		}
+	}
+	return errors.ErrorOrNil()
+}
+
+func removeNetworkFirewallLoggingConfiguration(ctx context.Context, conn *networkfirewall.NetworkFirewall, arn string, l *networkfirewall.LoggingConfiguration) error {
+	if l == nil {
+		return nil
+	}
+	var errors *multierror.Error
+	// Must delete destination configs one at a time
+	for i, config := range l.LogDestinationConfigs {
+		input := &networkfirewall.UpdateLoggingConfigurationInput{
+			FirewallArn: aws.String(arn),
+		}
+		if i == 0 && len(l.LogDestinationConfigs) == 2 {
+			loggingConfig := &networkfirewall.LoggingConfiguration{
+				LogDestinationConfigs: []*networkfirewall.LogDestinationConfig{config},
+			}
+			input.LoggingConfiguration = loggingConfig
+		}
+		_, err := conn.UpdateLoggingConfigurationWithContext(ctx, input)
+		if err != nil {
+			errors = multierror.Append(errors, fmt.Errorf("error removing Logging Configuration LogDestinationConfig (%v) from NetworkFirewall Firewall: %s: %w", config, arn, err))
+		}
+	}
+
+	return errors.ErrorOrNil()
+}
+
+func expandNetworkFirewallLoggingConfiguration(l []interface{}) []*networkfirewall.LoggingConfiguration {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+
+	loggingConfigs := make([]*networkfirewall.LoggingConfiguration, 0)
+	if tfSet, ok := tfMap["log_destination_config"].(*schema.Set); ok && tfSet.Len() > 0 {
+		tfList := tfSet.List()
+		for _, tfMapRaw := range tfList {
+			tfMap, ok := tfMapRaw.(map[string]interface{})
+			if !ok {
+				continue
+			}
+			config := &networkfirewall.LogDestinationConfig{}
+			if v, ok := tfMap["log_destination"].(map[string]interface{}); ok && len(v) > 0 {
+				config.LogDestination = aws.StringMap(expandNetworkFirewallLogDestinationConfigLogDestination(v))
+			}
+			if v, ok := tfMap["log_destination_type"].(string); ok && v != "" {
+				config.LogDestinationType = aws.String(v)
+			}
+			if v, ok := tfMap["log_type"].(string); ok && v != "" {
+				config.LogType = aws.String(v)
+			}
+			// exclude empty LogDestinationConfig due to TypeMap in TypeSet behavior
+			// Related: https://github.com/hashicorp/terraform-plugin-sdk/issues/588
+			if config.LogDestination == nil && config.LogDestinationType == nil && config.LogType == nil {
+				continue
+			}
+			loggingConfig := &networkfirewall.LoggingConfiguration{}
+			// include all (max 2) "log_destination_config" i.e. prepend the already-expanded loggingConfig
+			if len(loggingConfigs) == 1 && len(loggingConfigs[0].LogDestinationConfigs) == 1 {
+				loggingConfig.LogDestinationConfigs = append(loggingConfig.LogDestinationConfigs, loggingConfigs[0].LogDestinationConfigs[0])
+			}
+			loggingConfig.LogDestinationConfigs = append(loggingConfig.LogDestinationConfigs, config)
+			loggingConfigs = append(loggingConfigs, loggingConfig)
+		}
+	}
+	return loggingConfigs
+}
+
+func expandNetworkFirewallLoggingConfigurationOnUpdate(l []interface{}) *networkfirewall.LoggingConfiguration {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+
+	loggingConfig := &networkfirewall.LoggingConfiguration{}
+	if tfSet, ok := tfMap["log_destination_config"].(*schema.Set); ok && tfSet.Len() > 0 {
+		tfList := tfSet.List()
+		destConfigs := make([]*networkfirewall.LogDestinationConfig, 0, len(tfList))
+		for _, tfMapRaw := range tfList {
+			tfMap, ok := tfMapRaw.(map[string]interface{})
+			if !ok {
+				continue
+			}
+			config := &networkfirewall.LogDestinationConfig{}
+			if v, ok := tfMap["log_destination"].(map[string]interface{}); ok && len(v) > 0 {
+				config.LogDestination = aws.StringMap(expandNetworkFirewallLogDestinationConfigLogDestination(v))
+			}
+			if v, ok := tfMap["log_destination_type"].(string); ok && v != "" {
+				config.LogDestinationType = aws.String(v)
+			}
+			if v, ok := tfMap["log_type"].(string); ok && v != "" {
+				config.LogType = aws.String(v)
+			}
+			// exclude empty LogDestinationConfig due to TypeMap in TypeSet behavior
+			// Related: https://github.com/hashicorp/terraform-plugin-sdk/issues/588
+			if config.LogDestination == nil && config.LogDestinationType == nil && config.LogType == nil {
+				continue
+			}
+			destConfigs = append(destConfigs, config)
+		}
+		loggingConfig.LogDestinationConfigs = destConfigs
+	}
+	return loggingConfig
+}
+
+func expandNetworkFirewallLogDestinationConfigLogDestination(dst map[string]interface{}) map[string]string {
+	m := map[string]string{}
+	for k, v := range dst {
+		m[k] = v.(string)
+	}
+	return m
+}
+
+func flattenNetworkFirewallLoggingConfiguration(lc *networkfirewall.LoggingConfiguration) []interface{} {
+	if lc == nil || lc.LogDestinationConfigs == nil {
+		return []interface{}{}
+	}
+	m := map[string]interface{}{
+		"log_destination_config": flattenNetworkFirewallLoggingConfigurationLogDestinationConfigs(lc.LogDestinationConfigs),
+	}
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallLoggingConfigurationLogDestinationConfigs(configs []*networkfirewall.LogDestinationConfig) []interface{} {
+	l := make([]interface{}, 0, len(configs))
+	for _, config := range configs {
+		m := map[string]interface{}{
+			"log_destination":      aws.StringValueMap(config.LogDestination),
+			"log_destination_type": aws.StringValue(config.LogDestinationType),
+			"log_type":             aws.StringValue(config.LogType),
+		}
+		l = append(l, m)
+	}
+	return l
+}
diff --git a/aws/resource_aws_networkfirewall_logging_configuration_test.go b/aws/resource_aws_networkfirewall_logging_configuration_test.go
new file mode 100644
index 00000000000..43e07db60c9
--- /dev/null
+++ b/aws/resource_aws_networkfirewall_logging_configuration_test.go
@@ -0,0 +1,1083 @@
+package aws
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/aws-sdk-go-base/tfawserr"
+	"github.com/hashicorp/go-multierror"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/finder"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource"
+)
+
+func init() {
+	resource.AddTestSweepers("aws_networkfirewall_logging_configuration", &resource.Sweeper{
+		Name: "aws_networkfirewall_logging_configuration",
+		F:    testSweepNetworkFirewallLoggingConfigurations,
+	})
+}
+
+func testSweepNetworkFirewallLoggingConfigurations(region string) error {
+	client, err := sharedClientForRegion(region)
+	if err != nil {
+		return fmt.Errorf("error getting client: %s", err)
+	}
+	conn := client.(*AWSClient).networkfirewallconn
+	ctx := context.TODO()
+	input := &networkfirewall.ListFirewallsInput{MaxResults: aws.Int64(100)}
+	var sweeperErrs *multierror.Error
+
+	for {
+		resp, err := conn.ListFirewallsWithContext(ctx, input)
+		if testSweepSkipSweepError(err) {
+			log.Printf("[WARN] Skipping NetworkFirewall Logging Configuration sweep for %s: %s", region, err)
+			return nil
+		}
+		if err != nil {
+			return fmt.Errorf("error retrieving NetworkFirewall firewalls: %s", err)
+		}
+
+		for _, f := range resp.Firewalls {
+			if f == nil {
+				continue
+			}
+
+			arn := aws.StringValue(f.FirewallArn)
+
+			log.Printf("[INFO] Deleting NetworkFirewall Logging Configuration for firewall: %s", arn)
+
+			r := resourceAwsNetworkFirewallLoggingConfiguration()
+			d := r.Data(nil)
+			d.SetId(arn)
+			diags := r.DeleteContext(ctx, d, client)
+			for i := range diags {
+				if diags[i].Severity == diag.Error {
+					log.Printf("[ERROR] %s", diags[i].Summary)
+					sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf(diags[i].Summary))
+					continue
+				}
+			}
+		}
+
+		if aws.StringValue(resp.NextToken) == "" {
+			break
+		}
+		input.NextToken = resp.NextToken
+	}
+
+	return sweeperErrs.ErrorOrNil()
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_cloudwatchLogDestination_logGroup(t *testing.T) {
+	logGroupName := acctest.RandomWithPrefix("tf-acc-test")
+	updatedLogGroupName := fmt.Sprintf("%s-updated", logGroupName)
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_cloudwatch(logGroupName, rName, networkfirewall.LogDestinationTypeCloudWatchLogs, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":        "1",
+						"log_destination.logGroup": logGroupName,
+						"log_destination_type":     networkfirewall.LogDestinationTypeCloudWatchLogs,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_cloudwatch(updatedLogGroupName, rName, networkfirewall.LogDestinationTypeCloudWatchLogs, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":        "1",
+						"log_destination.logGroup": updatedLogGroupName,
+						"log_destination_type":     networkfirewall.LogDestinationTypeCloudWatchLogs,
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_cloudwatchLogDestination_logType(t *testing.T) {
+	logGroupName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_cloudwatch(logGroupName, rName, networkfirewall.LogDestinationTypeCloudWatchLogs, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_type": networkfirewall.LogTypeFlow,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_cloudwatch(logGroupName, rName, networkfirewall.LogDestinationTypeCloudWatchLogs, networkfirewall.LogTypeAlert),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_type": networkfirewall.LogTypeAlert,
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_kinesisLogDestination_deliveryStream(t *testing.T) {
+	streamName := acctest.RandomWithPrefix("tf-acc-test")
+	updatedStreamName := fmt.Sprintf("%s-updated", streamName)
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_kinesis(streamName, rName, networkfirewall.LogDestinationTypeKinesisDataFirehose, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":              "1",
+						"log_destination.deliveryStream": streamName,
+						"log_destination_type":           networkfirewall.LogDestinationTypeKinesisDataFirehose,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_kinesis(updatedStreamName, rName, networkfirewall.LogDestinationTypeKinesisDataFirehose, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":              "1",
+						"log_destination.deliveryStream": updatedStreamName,
+						"log_destination_type":           networkfirewall.LogDestinationTypeKinesisDataFirehose,
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_kinesisLogDestination_logType(t *testing.T) {
+	streamName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_kinesis(streamName, rName, networkfirewall.LogDestinationTypeKinesisDataFirehose, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_type": networkfirewall.LogTypeFlow,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_kinesis(streamName, rName, networkfirewall.LogDestinationTypeKinesisDataFirehose, networkfirewall.LogTypeAlert),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_type": networkfirewall.LogTypeAlert,
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_s3LogDestination_bucketName(t *testing.T) {
+	bucketName := acctest.RandomWithPrefix("tf-acc-test")
+	updatedBucketName := fmt.Sprintf("%s-updated", bucketName)
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":          "1",
+						"log_destination.bucketName": bucketName,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(updatedBucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":          "1",
+						"log_destination.bucketName": updatedBucketName,
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_s3LogDestination_logType(t *testing.T) {
+	bucketName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_type": networkfirewall.LogTypeFlow,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeAlert),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_type": networkfirewall.LogTypeAlert,
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_s3LogDestination_prefix(t *testing.T) {
+	bucketName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":          "1",
+						"log_destination.bucketName": bucketName,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3_updatePrefix(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":          "2",
+						"log_destination.bucketName": bucketName,
+						"log_destination.prefix":     "update-prefix",
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_updateFirewallArn(t *testing.T) {
+	bucketName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+	firewallResourceName := "aws_networkfirewall_firewall.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttrPair(resourceName, "firewall_arn", firewallResourceName, "arn"),
+				),
+			},
+			{
+				// ForceNew Firewall i.e. LoggingConfiguration Resource
+				Config: testAccNetworkFirewallLoggingConfiguration_s3_updateFirewallArn(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttrPair(resourceName, "firewall_arn", firewallResourceName, "arn"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_updateLogDestinationType(t *testing.T) {
+	bucketName := acctest.RandomWithPrefix("tf-acc-test")
+	logGroupName := acctest.RandomWithPrefix("tf-acc-test")
+	streamName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_cloudwatch(logGroupName, rName, networkfirewall.LogDestinationTypeCloudWatchLogs, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":        "1",
+						"log_destination.logGroup": logGroupName,
+						"log_destination_type":     networkfirewall.LogDestinationTypeCloudWatchLogs,
+						"log_type":                 networkfirewall.LogTypeFlow,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_kinesis(streamName, rName, networkfirewall.LogDestinationTypeKinesisDataFirehose, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":              "1",
+						"log_destination.deliveryStream": streamName,
+						"log_destination_type":           networkfirewall.LogDestinationTypeKinesisDataFirehose,
+						"log_type":                       networkfirewall.LogTypeFlow,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination_type": networkfirewall.LogDestinationTypeS3,
+						"log_type":             networkfirewall.LogTypeFlow,
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_updateToMultipleLogDestinationConfigs(t *testing.T) {
+	bucketName := acctest.RandomWithPrefix("tf-acc-test")
+	streamName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeAlert),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3AndKinesis(bucketName, streamName, rName, networkfirewall.LogTypeAlert, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.log_destination_config.#", "2"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":              "1",
+						"log_destination.deliveryStream": streamName,
+						"log_destination_type":           networkfirewall.LogDestinationTypeKinesisDataFirehose,
+						"log_type":                       networkfirewall.LogTypeFlow,
+					}),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":          "1",
+						"log_destination.bucketName": bucketName,
+						"log_destination_type":       networkfirewall.LogDestinationTypeS3,
+						"log_type":                   networkfirewall.LogTypeAlert,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeAlert),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.log_destination_config.#", "1"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_updateToSingleAlertTypeLogDestinationConfig(t *testing.T) {
+	bucketName := acctest.RandomWithPrefix("tf-acc-test")
+	logGroupName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3AndCloudWatch(bucketName, logGroupName, rName, networkfirewall.LogTypeAlert, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.log_destination_config.#", "2"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":        "1",
+						"log_destination.logGroup": logGroupName,
+						"log_destination_type":     networkfirewall.LogDestinationTypeCloudWatchLogs,
+						"log_type":                 networkfirewall.LogTypeFlow,
+					}),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":          "1",
+						"log_destination.bucketName": bucketName,
+						"log_destination_type":       networkfirewall.LogDestinationTypeS3,
+						"log_type":                   networkfirewall.LogTypeAlert,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeAlert),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.log_destination_config.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":          "1",
+						"log_destination.bucketName": bucketName,
+						"log_destination_type":       networkfirewall.LogDestinationTypeS3,
+						"log_type":                   networkfirewall.LogTypeAlert,
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_updateToSingleFlowTypeLogDestinationConfig(t *testing.T) {
+	bucketName := acctest.RandomWithPrefix("tf-acc-test")
+	logGroupName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3AndCloudWatch(bucketName, logGroupName, rName, networkfirewall.LogTypeAlert, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.log_destination_config.#", "2"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":        "1",
+						"log_destination.logGroup": logGroupName,
+						"log_destination_type":     networkfirewall.LogDestinationTypeCloudWatchLogs,
+						"log_type":                 networkfirewall.LogTypeFlow,
+					}),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":          "1",
+						"log_destination.bucketName": bucketName,
+						"log_destination_type":       networkfirewall.LogDestinationTypeS3,
+						"log_type":                   networkfirewall.LogTypeAlert,
+					}),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_cloudwatch(logGroupName, rName, networkfirewall.LogDestinationTypeCloudWatchLogs, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "logging_configuration.0.log_destination_config.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "logging_configuration.0.log_destination_config.*", map[string]string{
+						"log_destination.%":        "1",
+						"log_destination.logGroup": logGroupName,
+						"log_destination_type":     networkfirewall.LogDestinationTypeCloudWatchLogs,
+						"log_type":                 networkfirewall.LogTypeFlow,
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallLoggingConfiguration_disappears(t *testing.T) {
+	bucketName := acctest.RandomWithPrefix("tf-acc-test")
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_logging_configuration.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, networkfirewall.LogDestinationTypeS3, networkfirewall.LogTypeFlow),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallLoggingConfigurationExists(resourceName),
+					testAccCheckResourceDisappears(testAccProvider, resourceAwsNetworkFirewallLoggingConfiguration(), resourceName),
+				),
+				ExpectNonEmptyPlan: true,
+			},
+		},
+	})
+}
+
+func testAccCheckAwsNetworkFirewallLoggingConfigurationDestroy(s *terraform.State) error {
+	for _, rs := range s.RootModule().Resources {
+		if rs.Type != "aws_networkfirewall_logging_configuration" {
+			continue
+		}
+
+		conn := testAccProvider.Meta().(*AWSClient).networkfirewallconn
+		output, err := finder.LoggingConfiguration(context.Background(), conn, rs.Primary.ID)
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			continue
+		}
+		if err != nil {
+			return err
+		}
+		if output != nil {
+			return fmt.Errorf("NetworkFirewall Logging Configuration for firewall (%s) still exists", rs.Primary.ID)
+		}
+	}
+
+	return nil
+}
+
+func testAccCheckAwsNetworkFirewallLoggingConfigurationExists(n string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		rs, ok := s.RootModule().Resources[n]
+		if !ok {
+			return fmt.Errorf("Not found: %s", n)
+		}
+
+		if rs.Primary.ID == "" {
+			return fmt.Errorf("No NetworkFirewall Logging Configuration ID is set")
+		}
+
+		conn := testAccProvider.Meta().(*AWSClient).networkfirewallconn
+		output, err := finder.LoggingConfiguration(context.Background(), conn, rs.Primary.ID)
+		if err != nil {
+			return err
+		}
+		if output == nil && output.LoggingConfiguration == nil {
+			return fmt.Errorf("NetworkFirewall Logging Configuration for firewall (%s) not found", rs.Primary.ID)
+		}
+
+		return nil
+	}
+}
+
+func testAccNetworkFirewallLoggingConfigurationBaseConfig(rName string) string {
+	return fmt.Sprintf(`
+data "aws_availability_zones" "available" {
+  state = "available"
+
+  filter {
+    name   = "opt-in-status"
+    values = ["opt-in-not-required"]
+  }
+}
+
+resource "aws_vpc" "test" {
+  cidr_block = "192.168.0.0/16"
+
+  tags = {
+    Name = %[1]q
+  }
+}
+
+resource "aws_subnet" "test" {
+  availability_zone = data.aws_availability_zones.available.names[0]
+  cidr_block        = cidrsubnet(aws_vpc.test.cidr_block, 8, 0)
+  vpc_id            = aws_vpc.test.id
+
+  tags = {
+    Name = %[1]q
+  }
+}
+
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %[1]q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+  }
+}
+
+resource "aws_networkfirewall_firewall" "test" {
+  name                = %[1]q
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.test.arn
+  vpc_id              = aws_vpc.test.id
+
+  subnet_mapping {
+    subnet_id = aws_subnet.test.id
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallLoggingConfigurationBaseConfig_updateFirewall(rName string) string {
+	return fmt.Sprintf(`
+data "aws_availability_zones" "available" {
+  state = "available"
+
+  filter {
+    name   = "opt-in-status"
+    values = ["opt-in-not-required"]
+  }
+}
+
+resource "aws_vpc" "test" {
+  cidr_block = "192.168.0.0/16"
+
+  tags = {
+    Name = %[1]q
+  }
+}
+
+resource "aws_subnet" "test" {
+  availability_zone = data.aws_availability_zones.available.names[0]
+  cidr_block        = cidrsubnet(aws_vpc.test.cidr_block, 8, 0)
+  vpc_id            = aws_vpc.test.id
+
+  tags = {
+    Name = %[1]q
+  }
+}
+
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = %[1]q
+  firewall_policy {
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_default_actions          = ["aws:pass"]
+  }
+}
+
+resource "aws_networkfirewall_firewall" "test" {
+  name                = "%[1]s-updated"
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.test.arn
+  vpc_id              = aws_vpc.test.id
+
+  subnet_mapping {
+    subnet_id = aws_subnet.test.id
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallLoggingConfigurationS3BucketDependencyConfig(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_s3_bucket" "test" {
+  bucket        = %q
+  acl           = "private"
+  force_destroy = true
+
+  lifecycle {
+    create_before_destroy = true
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallLoggingConfigurationCloudWatchDependencyConfig(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_cloudwatch_log_group" "test" {
+  name = %q
+
+  lifecycle {
+    create_before_destroy = true
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallLoggingConfiguration_kinesisDependenciesConfig(rName, streamName string) string {
+	return fmt.Sprintf(`
+data "aws_caller_identity" "current" {}
+
+data "aws_partition" "current" {}
+
+resource "aws_iam_role" "test" {
+  name = %[1]q
+
+  assume_role_policy = <<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "",
+      "Effect": "Allow",
+      "Principal": {
+        "Service": "firehose.${data.aws_partition.current.dns_suffix}"
+      },
+      "Action": "sts:AssumeRole",
+      "Condition": {
+        "StringEquals": {
+          "sts:ExternalId": "${data.aws_caller_identity.current.account_id}"
+        }
+      }
+    }
+  ]
+}
+EOF
+}
+
+resource "aws_iam_role_policy" "test" {
+  name = %[1]q
+  role = aws_iam_role.test.id
+
+  policy = <<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "",
+      "Effect": "Allow",
+      "Action": [
+        "s3:AbortMultipartUpload",
+        "s3:GetBucketLocation",
+        "s3:GetObject",
+        "s3:ListBucket",
+        "s3:ListBucketMultipartUploads",
+        "s3:PutObject"
+      ],
+      "Resource": [
+        "${aws_s3_bucket.logs.arn}",
+        "${aws_s3_bucket.logs.arn}/*"
+      ]
+    },
+    {
+      "Sid": "GlueAccess",
+      "Effect": "Allow",
+      "Action": [
+        "glue:GetTableVersions"
+      ],
+      "Resource": [
+        "*"
+      ]
+    }
+  ]
+}
+EOF
+}
+
+resource "aws_s3_bucket" "logs" {
+  bucket        = %[1]q
+  acl           = "private"
+  force_destroy = true
+}
+
+resource "aws_kinesis_firehose_delivery_stream" "test" {
+  depends_on  = [aws_iam_role_policy.test]
+  name        = %[2]q
+  destination = "s3"
+
+  s3_configuration {
+    role_arn   = aws_iam_role.test.arn
+    bucket_arn = aws_s3_bucket.logs.arn
+  }
+
+  tags = {
+    LogDeliveryEnabled = "placeholder"
+  }
+
+  lifecycle {
+    create_before_destroy = true
+    ignore_changes = [
+      # Ignore changes to LogDeliveryEnabled tag as API adds this tag when broker log delivery is enabled
+      tags["LogDeliveryEnabled"],
+    ]
+  }
+}
+`, rName, streamName)
+}
+
+func testAccNetworkFirewallLoggingConfiguration_s3(bucketName, rName, destinationType, logType string) string {
+	return composeConfig(
+		testAccNetworkFirewallLoggingConfigurationBaseConfig(rName),
+		testAccNetworkFirewallLoggingConfigurationS3BucketDependencyConfig(bucketName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_logging_configuration" "test" {
+  firewall_arn = aws_networkfirewall_firewall.test.arn
+
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        bucketName = aws_s3_bucket.test.bucket
+      }
+      log_destination_type = %[2]q
+      log_type             = %[3]q
+    }
+  }
+}
+`, rName, destinationType, logType))
+}
+
+func testAccNetworkFirewallLoggingConfiguration_s3_updateFirewallArn(bucketName, rName, destinationType, logType string) string {
+	return composeConfig(
+		testAccNetworkFirewallLoggingConfigurationBaseConfig_updateFirewall(rName),
+		testAccNetworkFirewallLoggingConfigurationS3BucketDependencyConfig(bucketName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_logging_configuration" "test" {
+  firewall_arn = aws_networkfirewall_firewall.test.arn
+
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        bucketName = aws_s3_bucket.test.bucket
+      }
+      log_destination_type = %[2]q
+      log_type             = %[3]q
+    }
+  }
+}
+`, rName, destinationType, logType))
+}
+
+func testAccNetworkFirewallLoggingConfiguration_s3_updatePrefix(bucketName, rName, destinationType, logType string) string {
+	return composeConfig(
+		testAccNetworkFirewallLoggingConfigurationBaseConfig(rName),
+		testAccNetworkFirewallLoggingConfigurationS3BucketDependencyConfig(bucketName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_logging_configuration" "test" {
+  firewall_arn = aws_networkfirewall_firewall.test.arn
+
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        bucketName = aws_s3_bucket.test.bucket
+        prefix     = "update-prefix"
+      }
+      log_destination_type = %[2]q
+      log_type             = %[3]q
+    }
+  }
+}
+`, rName, destinationType, logType))
+}
+
+func testAccNetworkFirewallLoggingConfiguration_kinesis(streamName, rName, destinationType, logType string) string {
+	return composeConfig(
+		testAccNetworkFirewallLoggingConfigurationBaseConfig(rName),
+		testAccNetworkFirewallLoggingConfiguration_kinesisDependenciesConfig(rName, streamName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_logging_configuration" "test" {
+  firewall_arn = aws_networkfirewall_firewall.test.arn
+
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        deliveryStream = aws_kinesis_firehose_delivery_stream.test.name
+      }
+      log_destination_type = %[2]q
+      log_type             = %[3]q
+    }
+  }
+}
+`, rName, destinationType, logType))
+}
+
+func testAccNetworkFirewallLoggingConfiguration_cloudwatch(logGroupName, rName, destinationType, logType string) string {
+	return composeConfig(
+		testAccNetworkFirewallLoggingConfigurationBaseConfig(rName),
+		testAccNetworkFirewallLoggingConfigurationCloudWatchDependencyConfig(logGroupName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_logging_configuration" "test" {
+  firewall_arn = aws_networkfirewall_firewall.test.arn
+
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        logGroup = aws_cloudwatch_log_group.test.name
+      }
+      log_destination_type = %[2]q
+      log_type             = %[3]q
+    }
+  }
+}
+`, rName, destinationType, logType))
+}
+
+func testAccNetworkFirewallLoggingConfiguration_s3AndKinesis(bucketName, streamName, rName, logTypeS3, logTypeKinesis string) string {
+	return composeConfig(
+		testAccNetworkFirewallLoggingConfigurationS3BucketDependencyConfig(bucketName),
+		testAccNetworkFirewallLoggingConfiguration_kinesisDependenciesConfig(rName, streamName),
+		testAccNetworkFirewallLoggingConfigurationBaseConfig(rName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_logging_configuration" "test" {
+  firewall_arn = aws_networkfirewall_firewall.test.arn
+
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        bucketName = aws_s3_bucket.test.bucket
+      }
+      log_destination_type = "S3"
+      log_type             = %[2]q
+    }
+
+    log_destination_config {
+      log_destination = {
+        deliveryStream = aws_kinesis_firehose_delivery_stream.test.name
+      }
+      log_destination_type = "KinesisDataFirehose"
+      log_type             = %[3]q
+    }
+  }
+}
+`, rName, logTypeS3, logTypeKinesis))
+}
+
+func testAccNetworkFirewallLoggingConfiguration_s3AndCloudWatch(bucketName, logGroupName, rName, logTypeS3, logTypeCloudWatch string) string {
+	return composeConfig(
+		testAccNetworkFirewallLoggingConfigurationBaseConfig(rName),
+		testAccNetworkFirewallLoggingConfigurationS3BucketDependencyConfig(bucketName),
+		testAccNetworkFirewallLoggingConfigurationCloudWatchDependencyConfig(logGroupName),
+		fmt.Sprintf(`
+resource "aws_networkfirewall_logging_configuration" "test" {
+  firewall_arn = aws_networkfirewall_firewall.test.arn
+
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        bucketName = aws_s3_bucket.test.bucket
+      }
+      log_destination_type = "S3"
+      log_type             = %[2]q
+    }
+
+    log_destination_config {
+      log_destination = {
+        logGroup = aws_cloudwatch_log_group.test.name
+      }
+      log_destination_type = "CloudWatchLogs"
+      log_type             = %[3]q
+    }
+  }
+}
+`, rName, logTypeS3, logTypeCloudWatch))
+}
diff --git a/aws/resource_aws_networkfirewall_rule_group.go b/aws/resource_aws_networkfirewall_rule_group.go
new file mode 100644
index 00000000000..416b1ba366e
--- /dev/null
+++ b/aws/resource_aws_networkfirewall_rule_group.go
@@ -0,0 +1,1205 @@
+package aws
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"regexp"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/aws-sdk-go-base/tfawserr"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/finder"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/waiter"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
+)
+
+func resourceAwsNetworkFirewallRuleGroup() *schema.Resource {
+	return &schema.Resource{
+		CreateContext: resourceAwsNetworkFirewallRuleGroupCreate,
+		ReadContext:   resourceAwsNetworkFirewallRuleGroupRead,
+		UpdateContext: resourceAwsNetworkFirewallRuleGroupUpdate,
+		DeleteContext: resourceAwsNetworkFirewallRuleGroupDelete,
+
+		Importer: &schema.ResourceImporter{
+			StateContext: schema.ImportStatePassthroughContext,
+		},
+
+		Schema: map[string]*schema.Schema{
+			"arn": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
+			"capacity": {
+				Type:     schema.TypeInt,
+				Required: true,
+				ForceNew: true,
+			},
+			"description": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"name": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"rule_group": {
+				Type:     schema.TypeList,
+				MaxItems: 1,
+				Optional: true,
+				Computed: true,
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"rule_variables": {
+							Type:     schema.TypeList,
+							Optional: true,
+							MaxItems: 1,
+							Elem: &schema.Resource{
+								Schema: map[string]*schema.Schema{
+									"ip_sets": {
+										Type:     schema.TypeSet,
+										Optional: true,
+										Elem: &schema.Resource{
+											Schema: map[string]*schema.Schema{
+												"key": {
+													Type:     schema.TypeString,
+													Required: true,
+													ValidateFunc: validation.All(
+														validation.StringLenBetween(1, 32),
+														validation.StringMatch(regexp.MustCompile(`^[A-Za-z]`), "must begin with alphabetic character"),
+														validation.StringMatch(regexp.MustCompile(`^[A-Za-z0-9_]+$`), "must contain only alphanumeric and underscore characters"),
+													),
+												},
+												"ip_set": {
+													Type:     schema.TypeList,
+													Required: true,
+													MaxItems: 1,
+													Elem: &schema.Resource{
+														Schema: map[string]*schema.Schema{
+															"definition": {
+																Type:     schema.TypeSet,
+																Required: true,
+																Elem: &schema.Schema{
+																	Type:         schema.TypeString,
+																	ValidateFunc: validateIpv4CIDRNetworkAddress,
+																},
+															},
+														},
+													},
+												},
+											},
+										},
+									},
+									"port_sets": {
+										Type:     schema.TypeSet,
+										Optional: true,
+										Elem: &schema.Resource{
+											Schema: map[string]*schema.Schema{
+												"key": {
+													Type:     schema.TypeString,
+													Required: true,
+													ValidateFunc: validation.All(
+														validation.StringLenBetween(1, 32),
+														validation.StringMatch(regexp.MustCompile(`^[A-Za-z]`), "must begin with alphabetic character"),
+														validation.StringMatch(regexp.MustCompile(`^[A-Za-z0-9_]+$`), "must contain only alphanumeric and underscore characters"),
+													),
+												},
+												"port_set": {
+													Type:     schema.TypeList,
+													Required: true,
+													MaxItems: 1,
+													Elem: &schema.Resource{
+														Schema: map[string]*schema.Schema{
+															"definition": {
+																Type:     schema.TypeSet,
+																Required: true,
+																Elem:     &schema.Schema{Type: schema.TypeString},
+															},
+														},
+													},
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+						"rules_source": {
+							Type:     schema.TypeList,
+							Required: true,
+							MaxItems: 1,
+							Elem: &schema.Resource{
+								Schema: map[string]*schema.Schema{
+									"rules_source_list": {
+										Type:     schema.TypeList,
+										Optional: true,
+										MaxItems: 1,
+										Elem: &schema.Resource{
+											Schema: map[string]*schema.Schema{
+												"generated_rules_type": {
+													Type:         schema.TypeString,
+													Required:     true,
+													ValidateFunc: validation.StringInSlice(networkfirewall.GeneratedRulesType_Values(), false),
+												},
+												"target_types": {
+													Type:     schema.TypeSet,
+													Required: true,
+													Elem: &schema.Schema{
+														Type:         schema.TypeString,
+														ValidateFunc: validation.StringInSlice(networkfirewall.TargetType_Values(), false),
+													},
+												},
+												"targets": {
+													Type:     schema.TypeSet,
+													Required: true,
+													Elem:     &schema.Schema{Type: schema.TypeString},
+												},
+											},
+										},
+									},
+									"rules_string": {
+										Type:     schema.TypeString,
+										Optional: true,
+									},
+									"stateful_rule": {
+										Type:     schema.TypeSet,
+										Optional: true,
+										ForceNew: true,
+										Elem: &schema.Resource{
+											Schema: map[string]*schema.Schema{
+												"action": {
+													Type:         schema.TypeString,
+													Required:     true,
+													ForceNew:     true,
+													ValidateFunc: validation.StringInSlice(networkfirewall.StatefulAction_Values(), false),
+												},
+												"header": {
+													Type:     schema.TypeList,
+													Required: true,
+													MaxItems: 1,
+													ForceNew: true,
+													Elem: &schema.Resource{
+														Schema: map[string]*schema.Schema{
+															"destination": {
+																Type:         schema.TypeString,
+																Optional:     true,
+																ForceNew:     true,
+																ValidateFunc: validateIpv4CIDRNetworkAddress,
+															},
+															"destination_port": {
+																Type:     schema.TypeString,
+																Optional: true,
+																ForceNew: true,
+															},
+															"direction": {
+																Type:         schema.TypeString,
+																Required:     true,
+																ForceNew:     true,
+																ValidateFunc: validation.StringInSlice(networkfirewall.StatefulRuleDirection_Values(), false),
+															},
+															"protocol": {
+																Type:         schema.TypeString,
+																Optional:     true,
+																ForceNew:     true,
+																ValidateFunc: validation.StringInSlice(networkfirewall.StatefulRuleProtocol_Values(), false),
+															},
+															"source": {
+																Type:         schema.TypeString,
+																Optional:     true,
+																ForceNew:     true,
+																ValidateFunc: validateIpv4CIDRNetworkAddress,
+															},
+															"source_port": {
+																Type:     schema.TypeString,
+																Optional: true,
+																ForceNew: true,
+															},
+														},
+													},
+												},
+												"rule_option": {
+													Type:     schema.TypeSet,
+													Required: true,
+													Elem: &schema.Resource{
+														Schema: map[string]*schema.Schema{
+															"keyword": {
+																Type:     schema.TypeString,
+																Required: true,
+															},
+															"settings": {
+																Type:     schema.TypeSet,
+																Optional: true,
+																Elem:     &schema.Schema{Type: schema.TypeString},
+															},
+														},
+													},
+												},
+											},
+										},
+									},
+									"stateless_rules_and_custom_actions": {
+										Type:     schema.TypeList,
+										MaxItems: 1,
+										Optional: true,
+										Elem: &schema.Resource{
+											Schema: map[string]*schema.Schema{
+												"custom_action": customActionSchema(),
+												"stateless_rule": {
+													Type:     schema.TypeSet,
+													Required: true,
+													Elem: &schema.Resource{
+														Schema: map[string]*schema.Schema{
+															"priority": {
+																Type:     schema.TypeInt,
+																Required: true,
+															},
+															"rule_definition": {
+																Type:     schema.TypeList,
+																MaxItems: 1,
+																Required: true,
+																Elem: &schema.Resource{
+																	Schema: map[string]*schema.Schema{
+																		"actions": {
+																			Type:     schema.TypeSet,
+																			Required: true,
+																			Elem:     &schema.Schema{Type: schema.TypeString},
+																		},
+																		"match_attributes": {
+																			Type:     schema.TypeList,
+																			MaxItems: 1,
+																			Required: true,
+																			Elem: &schema.Resource{
+																				Schema: map[string]*schema.Schema{
+																					"destination": {
+																						Type:     schema.TypeSet,
+																						Optional: true,
+																						Elem: &schema.Resource{
+																							Schema: map[string]*schema.Schema{
+																								"address_definition": {
+																									Type:         schema.TypeString,
+																									Optional:     true,
+																									ValidateFunc: validateIpv4CIDRNetworkAddress,
+																								},
+																							},
+																						},
+																					},
+																					"destination_port": {
+																						Type:     schema.TypeSet,
+																						Optional: true,
+																						Elem: &schema.Resource{
+																							Schema: map[string]*schema.Schema{
+																								"from_port": {
+																									Type:     schema.TypeInt,
+																									Required: true,
+																								},
+																								"to_port": {
+																									Type:     schema.TypeInt,
+																									Optional: true,
+																								},
+																							},
+																						},
+																					},
+																					"protocols": {
+																						Type:     schema.TypeSet,
+																						Optional: true,
+																						Elem:     &schema.Schema{Type: schema.TypeInt},
+																					},
+																					"source": {
+																						Type:     schema.TypeSet,
+																						Optional: true,
+																						Elem: &schema.Resource{
+																							Schema: map[string]*schema.Schema{
+																								"address_definition": {
+																									Type:         schema.TypeString,
+																									Optional:     true,
+																									ValidateFunc: validateIpv4CIDRNetworkAddress,
+																								},
+																							},
+																						},
+																					},
+																					"source_port": {
+																						Type:     schema.TypeSet,
+																						Optional: true,
+																						Elem: &schema.Resource{
+																							Schema: map[string]*schema.Schema{
+																								"from_port": {
+																									Type:     schema.TypeInt,
+																									Required: true,
+																								},
+																								"to_port": {
+																									Type:     schema.TypeInt,
+																									Optional: true,
+																								},
+																							},
+																						},
+																					},
+																					"tcp_flag": {
+																						Type:     schema.TypeSet,
+																						Optional: true,
+																						Elem: &schema.Resource{
+																							Schema: map[string]*schema.Schema{
+																								"flags": {
+																									Type:     schema.TypeSet,
+																									Required: true,
+																									Elem: &schema.Schema{
+																										Type:         schema.TypeString,
+																										ValidateFunc: validation.StringInSlice(networkfirewall.TCPFlag_Values(), false),
+																									},
+																								},
+																								"masks": {
+																									Type:     schema.TypeSet,
+																									Optional: true,
+																									Elem: &schema.Schema{
+																										Type:         schema.TypeString,
+																										ValidateFunc: validation.StringInSlice(networkfirewall.TCPFlag_Values(), false),
+																									},
+																								},
+																							},
+																						},
+																					},
+																				},
+																			},
+																		},
+																	},
+																},
+															},
+														},
+													},
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			"rules": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"tags": tagsSchema(),
+			"type": {
+				Type:         schema.TypeString,
+				Required:     true,
+				ValidateFunc: validation.StringInSlice(networkfirewall.RuleGroupType_Values(), false),
+			},
+			"update_token": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
+		},
+	}
+}
+
+func resourceAwsNetworkFirewallRuleGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	name := d.Get("name").(string)
+
+	input := &networkfirewall.CreateRuleGroupInput{
+		Capacity:      aws.Int64(int64(d.Get("capacity").(int))),
+		RuleGroupName: aws.String(name),
+		Type:          aws.String(d.Get("type").(string)),
+	}
+	if v, ok := d.GetOk("description"); ok {
+		input.Description = aws.String(v.(string))
+	}
+	if v, ok := d.GetOk("rule_group"); ok {
+		if vRaw := v.([]interface{}); len(vRaw) > 0 && vRaw[0] != nil {
+			input.RuleGroup = expandNetworkFirewallRuleGroup(vRaw)
+		}
+	}
+	if v, ok := d.GetOk("rules"); ok {
+		input.Rules = aws.String(v.(string))
+	}
+	if v, ok := d.GetOk("tags"); ok {
+		input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().NetworkfirewallTags()
+	}
+
+	log.Printf("[DEBUG] Creating NetworkFirewall Rule Group %s", name)
+
+	output, err := conn.CreateRuleGroupWithContext(ctx, input)
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error creating NetworkFirewall Rule Group %s: %w", name, err))
+	}
+	if output == nil || output.RuleGroupResponse == nil {
+		return diag.FromErr(fmt.Errorf("error creating NetworkFirewall Rule Group (%s): empty output", name))
+	}
+
+	d.SetId(aws.StringValue(output.RuleGroupResponse.RuleGroupArn))
+
+	return resourceAwsNetworkFirewallRuleGroupRead(ctx, d, meta)
+}
+
+func resourceAwsNetworkFirewallRuleGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig
+
+	log.Printf("[DEBUG] Reading NetworkFirewall Rule Group %s", d.Id())
+
+	output, err := finder.RuleGroup(ctx, conn, d.Id())
+	if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+		log.Printf("[WARN] NetworkFirewall Rule Group (%s) not found, removing from state", d.Id())
+		d.SetId("")
+		return nil
+	}
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error reading NetworkFirewall Rule Group (%s): %w", d.Id(), err))
+	}
+
+	if output == nil {
+		return diag.FromErr(fmt.Errorf("error reading NetworkFirewall Rule Group (%s): empty output", d.Id()))
+	}
+	if output.RuleGroupResponse == nil {
+		return diag.FromErr(fmt.Errorf("error reading NetworkFirewall Rule Group (%s): empty output.RuleGroupResponse", d.Id()))
+	}
+	if output.RuleGroup == nil {
+		return diag.FromErr(fmt.Errorf("error reading NetworkFirewall Rule Group (%s): empty output.RuleGroup", d.Id()))
+	}
+
+	resp := output.RuleGroupResponse
+	ruleGroup := output.RuleGroup
+	arn := aws.StringValue(resp.RuleGroupArn)
+
+	d.Set("arn", arn)
+	d.Set("capacity", resp.Capacity)
+	d.Set("description", resp.Description)
+	d.Set("name", resp.RuleGroupName)
+	d.Set("type", resp.Type)
+	d.Set("update_token", output.UpdateToken)
+
+	if err := d.Set("rule_group", flattenNetworkFirewallRuleGroup(ruleGroup)); err != nil {
+		return diag.FromErr(fmt.Errorf("error setting rule_group: %w", err))
+	}
+
+	tags, err := keyvaluetags.NetworkfirewallListTags(conn, arn)
+	if err != nil {
+		return diag.FromErr(fmt.Errorf("error listing tags for NetworkFirewall Rule Group (%s): %w", arn, err))
+	}
+
+	if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
+		return diag.FromErr(fmt.Errorf("error setting tags: %w", err))
+	}
+
+	return nil
+}
+
+func resourceAwsNetworkFirewallRuleGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+	arn := d.Id()
+
+	log.Printf("[DEBUG] Updating NetworkFirewall Rule Group %s", arn)
+
+	if d.HasChanges("description", "rule_group", "rules", "type") {
+		input := &networkfirewall.UpdateRuleGroupInput{
+			RuleGroupArn: aws.String(arn),
+			Type:         aws.String(d.Get("type").(string)),
+			UpdateToken:  aws.String(d.Get("update_token").(string)),
+		}
+		if d.HasChange("description") {
+			input.Description = aws.String(d.Get("description").(string))
+		}
+		if d.HasChange("rule_group") {
+			input.RuleGroup = expandNetworkFirewallRuleGroup(d.Get("rule_group").([]interface{}))
+		}
+		if d.HasChange("rules") {
+			input.Rules = aws.String(d.Get("rules").(string))
+		}
+
+		_, err := conn.UpdateRuleGroupWithContext(ctx, input)
+		if err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Rule Group (%s): %w", arn, err))
+		}
+	}
+
+	if d.HasChange("tags") {
+		o, n := d.GetChange("tags")
+		if err := keyvaluetags.NetworkfirewallUpdateTags(conn, arn, o, n); err != nil {
+			return diag.FromErr(fmt.Errorf("error updating NetworkFirewall Rule Group (%s) tags: %w", arn, err))
+		}
+	}
+
+	return resourceAwsNetworkFirewallRuleGroupRead(ctx, d, meta)
+}
+
+func resourceAwsNetworkFirewallRuleGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	conn := meta.(*AWSClient).networkfirewallconn
+
+	log.Printf("[DEBUG] Deleting NetworkFirewall Rule Group %s", d.Id())
+
+	input := &networkfirewall.DeleteRuleGroupInput{
+		RuleGroupArn: aws.String(d.Id()),
+	}
+	err := resource.Retry(waiter.RuleGroupDeleteTimeout, func() *resource.RetryError {
+		var err error
+		_, err = conn.DeleteRuleGroupWithContext(ctx, input)
+		if err != nil {
+			if tfawserr.ErrMessageContains(err, networkfirewall.ErrCodeInvalidOperationException, "Unable to delete the object because it is still in use") {
+				return resource.RetryableError(err)
+			}
+			return resource.NonRetryableError(err)
+		}
+		return nil
+	})
+
+	if tfresource.TimedOut(err) {
+		_, err = conn.DeleteRuleGroupWithContext(ctx, input)
+	}
+
+	if err != nil {
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return nil
+		}
+		return diag.FromErr(fmt.Errorf("error deleting NetworkFirewall Rule Group (%s): %w", d.Id(), err))
+	}
+
+	if _, err := waiter.RuleGroupDeleted(ctx, conn, d.Id()); err != nil {
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			return nil
+		}
+		return diag.FromErr(fmt.Errorf("error waiting for NetworkFirewall Rule Group (%s) to delete: %w", d.Id(), err))
+	}
+
+	return nil
+}
+
+func expandNetworkFirewallStatefulRuleHeader(l []interface{}) *networkfirewall.Header {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+	header := &networkfirewall.Header{}
+	if v, ok := tfMap["destination"].(string); ok && v != "" {
+		header.Destination = aws.String(v)
+	}
+	if v, ok := tfMap["destination_port"].(string); ok && v != "" {
+		header.DestinationPort = aws.String(v)
+	}
+	if v, ok := tfMap["direction"].(string); ok && v != "" {
+		header.Direction = aws.String(v)
+	}
+	if v, ok := tfMap["protocol"].(string); ok && v != "" {
+		header.Protocol = aws.String(v)
+	}
+	if v, ok := tfMap["source"].(string); ok && v != "" {
+		header.Source = aws.String(v)
+	}
+	if v, ok := tfMap["source_port"].(string); ok && v != "" {
+		header.SourcePort = aws.String(v)
+	}
+
+	return header
+}
+
+func expandNetworkFirewallStatefulRuleOptions(l []interface{}) []*networkfirewall.RuleOption {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+
+	ruleOptions := make([]*networkfirewall.RuleOption, 0, len(l))
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		keyword := tfMap["keyword"].(string)
+		option := &networkfirewall.RuleOption{
+			Keyword: aws.String(keyword),
+		}
+		if v, ok := tfMap["settings"].(*schema.Set); ok && v.Len() > 0 {
+			option.Settings = expandStringSet(v)
+		}
+		ruleOptions = append(ruleOptions, option)
+	}
+
+	return ruleOptions
+}
+
+func expandNetworkFirewallRulesSourceList(l []interface{}) *networkfirewall.RulesSourceList {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+	rulesSourceList := &networkfirewall.RulesSourceList{}
+	if v, ok := tfMap["generated_rules_type"].(string); ok && v != "" {
+		rulesSourceList.GeneratedRulesType = aws.String(v)
+	}
+	if v, ok := tfMap["target_types"].(*schema.Set); ok && v.Len() > 0 {
+		rulesSourceList.TargetTypes = expandStringSet(v)
+	}
+	if v, ok := tfMap["targets"].(*schema.Set); ok && v.Len() > 0 {
+		rulesSourceList.Targets = expandStringSet(v)
+	}
+
+	return rulesSourceList
+}
+
+func expandNetworkFirewallStatefulRules(l []interface{}) []*networkfirewall.StatefulRule {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+
+	rules := make([]*networkfirewall.StatefulRule, 0, len(l))
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		rule := &networkfirewall.StatefulRule{}
+		if v, ok := tfMap["action"].(string); ok && v != "" {
+			rule.Action = aws.String(v)
+		}
+		if v, ok := tfMap["header"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
+			rule.Header = expandNetworkFirewallStatefulRuleHeader(v)
+		}
+		if v, ok := tfMap["rule_option"].(*schema.Set); ok && v.Len() > 0 {
+			rule.RuleOptions = expandNetworkFirewallStatefulRuleOptions(v.List())
+		}
+		rules = append(rules, rule)
+	}
+
+	return rules
+}
+
+func expandNetworkFirewallRuleGroup(l []interface{}) *networkfirewall.RuleGroup {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+	ruleGroup := &networkfirewall.RuleGroup{}
+	if tfList, ok := tfMap["rule_variables"].([]interface{}); ok && len(tfList) > 0 && tfList[0] != nil {
+		ruleVariables := &networkfirewall.RuleVariables{}
+		rvMap, ok := tfList[0].(map[string]interface{})
+		if ok {
+			if v, ok := rvMap["ip_sets"].(*schema.Set); ok && v.Len() > 0 {
+				ruleVariables.IPSets = expandNetworkFirewallIPSets(v.List())
+			}
+			if v, ok := rvMap["port_sets"].(*schema.Set); ok && v.Len() > 0 {
+				ruleVariables.PortSets = expandNetworkFirewallPortSets(v.List())
+			}
+			ruleGroup.RuleVariables = ruleVariables
+		}
+	}
+	if tfList, ok := tfMap["rules_source"].([]interface{}); ok && len(tfList) > 0 && tfList[0] != nil {
+		rulesSource := &networkfirewall.RulesSource{}
+		rsMap, ok := tfList[0].(map[string]interface{})
+		if ok {
+			if v, ok := rsMap["rules_source_list"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
+				rulesSource.RulesSourceList = expandNetworkFirewallRulesSourceList(v)
+			}
+			if v, ok := rsMap["rules_string"].(string); ok && v != "" {
+				rulesSource.RulesString = aws.String(v)
+			}
+			if v, ok := rsMap["stateful_rule"].(*schema.Set); ok && v.Len() > 0 {
+				rulesSource.StatefulRules = expandNetworkFirewallStatefulRules(v.List())
+			}
+			if v, ok := rsMap["stateless_rules_and_custom_actions"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
+				rulesSource.StatelessRulesAndCustomActions = expandNetworkFirewallStatelessRulesAndCustomActions(v)
+			}
+			ruleGroup.RulesSource = rulesSource
+		}
+	}
+
+	return ruleGroup
+}
+
+func expandNetworkFirewallIPSets(l []interface{}) map[string]*networkfirewall.IPSet {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+
+	m := make(map[string]*networkfirewall.IPSet)
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+
+		if key, ok := tfMap["key"].(string); ok && key != "" {
+			if tfList, ok := tfMap["ip_set"].([]interface{}); ok && len(tfList) > 0 && tfList[0] != nil {
+				tfMap, ok := tfList[0].(map[string]interface{})
+				if ok {
+					if tfSet, ok := tfMap["definition"].(*schema.Set); ok && tfSet.Len() > 0 {
+						ipSet := &networkfirewall.IPSet{
+							Definition: expandStringSet(tfSet),
+						}
+						m[key] = ipSet
+					}
+				}
+			}
+		}
+	}
+
+	return m
+}
+
+func expandNetworkFirewallPortSets(l []interface{}) map[string]*networkfirewall.PortSet {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+
+	m := make(map[string]*networkfirewall.PortSet)
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+
+		if key, ok := tfMap["key"].(string); ok && key != "" {
+			if tfList, ok := tfMap["port_set"].([]interface{}); ok && len(tfList) > 0 && tfList[0] != nil {
+				tfMap, ok := tfList[0].(map[string]interface{})
+				if ok {
+					if tfSet, ok := tfMap["definition"].(*schema.Set); ok && tfSet.Len() > 0 {
+						ipSet := &networkfirewall.PortSet{
+							Definition: expandStringSet(tfSet),
+						}
+						m[key] = ipSet
+					}
+				}
+			}
+		}
+	}
+
+	return m
+}
+
+func expandNetworkFirewallAddresses(l []interface{}) []*networkfirewall.Address {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	destinations := make([]*networkfirewall.Address, 0, len(l))
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		destination := &networkfirewall.Address{}
+		if v, ok := tfMap["address_definition"].(string); ok && v != "" {
+			destination.AddressDefinition = aws.String(v)
+		}
+		destinations = append(destinations, destination)
+	}
+	return destinations
+}
+
+func expandNetworkFirewallPortRanges(l []interface{}) []*networkfirewall.PortRange {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	ports := make([]*networkfirewall.PortRange, 0, len(l))
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		port := &networkfirewall.PortRange{}
+		if v, ok := tfMap["from_port"].(int); ok {
+			port.FromPort = aws.Int64(int64(v))
+		}
+		if v, ok := tfMap["to_port"].(int); ok {
+			port.ToPort = aws.Int64(int64(v))
+		}
+		ports = append(ports, port)
+	}
+	return ports
+}
+
+func expandNetworkFirewallTCPFlags(l []interface{}) []*networkfirewall.TCPFlagField {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	tcpFlags := make([]*networkfirewall.TCPFlagField, 0, len(l))
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		tcpFlag := &networkfirewall.TCPFlagField{}
+		if v, ok := tfMap["flags"].(*schema.Set); ok && v.Len() > 0 {
+			tcpFlag.Flags = expandStringSet(v)
+		}
+		if v, ok := tfMap["masks"].(*schema.Set); ok && v.Len() > 0 {
+			tcpFlag.Masks = expandStringSet(v)
+		}
+		tcpFlags = append(tcpFlags, tcpFlag)
+	}
+	return tcpFlags
+}
+
+func expandNetworkFirewallMatchAttributes(l []interface{}) *networkfirewall.MatchAttributes {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+	matchAttributes := &networkfirewall.MatchAttributes{}
+	if v, ok := tfMap["destination"].(*schema.Set); ok && v.Len() > 0 {
+		matchAttributes.Destinations = expandNetworkFirewallAddresses(v.List())
+	}
+	if v, ok := tfMap["destination_port"].(*schema.Set); ok && v.Len() > 0 {
+		matchAttributes.DestinationPorts = expandNetworkFirewallPortRanges(v.List())
+	}
+	if v, ok := tfMap["protocols"].(*schema.Set); ok && v.Len() > 0 {
+		matchAttributes.Protocols = expandInt64Set(v)
+	}
+	if v, ok := tfMap["source"].(*schema.Set); ok && v.Len() > 0 {
+		matchAttributes.Sources = expandNetworkFirewallAddresses(v.List())
+	}
+	if v, ok := tfMap["source_port"].(*schema.Set); ok && v.Len() > 0 {
+		matchAttributes.SourcePorts = expandNetworkFirewallPortRanges(v.List())
+	}
+	if v, ok := tfMap["tcp_flag"].(*schema.Set); ok && v.Len() > 0 {
+		matchAttributes.TCPFlags = expandNetworkFirewallTCPFlags(v.List())
+	}
+
+	return matchAttributes
+}
+
+func expandNetworkFirewallRuleDefinition(l []interface{}) *networkfirewall.RuleDefinition {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+	rd := &networkfirewall.RuleDefinition{}
+	if v, ok := tfMap["actions"].(*schema.Set); ok && v.Len() > 0 {
+		rd.Actions = expandStringSet(v)
+	}
+	if v, ok := tfMap["match_attributes"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
+		rd.MatchAttributes = expandNetworkFirewallMatchAttributes(v)
+	}
+	return rd
+}
+
+func expandNetworkFirewallStatelessRules(l []interface{}) []*networkfirewall.StatelessRule {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+	statelessRules := make([]*networkfirewall.StatelessRule, 0, len(l))
+	for _, tfMapRaw := range l {
+		tfMap, ok := tfMapRaw.(map[string]interface{})
+		if !ok {
+			continue
+		}
+		statelessRule := &networkfirewall.StatelessRule{}
+		if v, ok := tfMap["priority"].(int); ok && v > 0 {
+			statelessRule.Priority = aws.Int64(int64(v))
+		}
+		if v, ok := tfMap["rule_definition"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
+			statelessRule.RuleDefinition = expandNetworkFirewallRuleDefinition(v)
+		}
+		statelessRules = append(statelessRules, statelessRule)
+	}
+
+	return statelessRules
+}
+
+func expandNetworkFirewallStatelessRulesAndCustomActions(l []interface{}) *networkfirewall.StatelessRulesAndCustomActions {
+	if len(l) == 0 || l[0] == nil {
+		return nil
+	}
+
+	s := &networkfirewall.StatelessRulesAndCustomActions{}
+	tfMap, ok := l[0].(map[string]interface{})
+	if !ok {
+		return nil
+	}
+	if v, ok := tfMap["custom_action"].(*schema.Set); ok && v.Len() > 0 {
+		s.CustomActions = expandNetworkFirewallCustomActions(v.List())
+	}
+	if v, ok := tfMap["stateless_rule"].(*schema.Set); ok && v.Len() > 0 {
+		s.StatelessRules = expandNetworkFirewallStatelessRules(v.List())
+	}
+
+	return s
+}
+
+func flattenNetworkFirewallRuleGroup(r *networkfirewall.RuleGroup) []interface{} {
+	if r == nil {
+		return []interface{}{}
+	}
+
+	m := map[string]interface{}{
+		"rule_variables": flattenNetworkFirewallRuleVariables(r.RuleVariables),
+		"rules_source":   flattenNetworkFirewallRulesSource(r.RulesSource),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallRuleVariables(rv *networkfirewall.RuleVariables) []interface{} {
+	if rv == nil {
+		return []interface{}{}
+	}
+	m := map[string]interface{}{
+		"ip_sets":   flattenNetworkFirewallIPSets(rv.IPSets),
+		"port_sets": flattenNetworkFirewallPortSets(rv.PortSets),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallIPSets(m map[string]*networkfirewall.IPSet) []interface{} {
+	if m == nil {
+		return []interface{}{}
+	}
+	sets := make([]interface{}, 0, len(m))
+	for k, v := range m {
+		tfMap := map[string]interface{}{
+			"key":    k,
+			"ip_set": flattenNetworkFirewallIPSet(v),
+		}
+		sets = append(sets, tfMap)
+	}
+
+	return sets
+}
+
+func flattenNetworkFirewallPortSets(m map[string]*networkfirewall.PortSet) []interface{} {
+	if m == nil {
+		return []interface{}{}
+	}
+	sets := make([]interface{}, 0, len(m))
+	for k, v := range m {
+		tfMap := map[string]interface{}{
+			"key":      k,
+			"port_set": flattenNetworkFirewallPortSet(v),
+		}
+		sets = append(sets, tfMap)
+	}
+
+	return sets
+}
+
+func flattenNetworkFirewallIPSet(i *networkfirewall.IPSet) []interface{} {
+	if i == nil {
+		return []interface{}{}
+	}
+	m := map[string]interface{}{
+		"definition": flattenStringSet(i.Definition),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallPortSet(p *networkfirewall.PortSet) []interface{} {
+	if p == nil {
+		return []interface{}{}
+	}
+	m := map[string]interface{}{
+		"definition": flattenStringSet(p.Definition),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallRulesSource(rs *networkfirewall.RulesSource) []interface{} {
+	if rs == nil {
+		return []interface{}{}
+	}
+
+	m := map[string]interface{}{
+		"rules_source_list":                  flattenNetworkFirewallRulesSourceList(rs.RulesSourceList),
+		"rules_string":                       aws.StringValue(rs.RulesString),
+		"stateful_rule":                      flattenNetworkFirewallStatefulRules(rs.StatefulRules),
+		"stateless_rules_and_custom_actions": flattenNetworkFirewallStatelessRulesAndCustomActions(rs.StatelessRulesAndCustomActions),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallRulesSourceList(r *networkfirewall.RulesSourceList) []interface{} {
+	if r == nil {
+		return []interface{}{}
+	}
+
+	m := map[string]interface{}{
+		"generated_rules_type": aws.StringValue(r.GeneratedRulesType),
+		"target_types":         flattenStringSet(r.TargetTypes),
+		"targets":              flattenStringSet(r.Targets),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallStatefulRules(sr []*networkfirewall.StatefulRule) []interface{} {
+	if sr == nil {
+		return []interface{}{}
+	}
+	rules := make([]interface{}, 0, len(sr))
+	for _, s := range sr {
+		m := map[string]interface{}{
+			"action":      aws.StringValue(s.Action),
+			"header":      flattenNetworkFirewallHeader(s.Header),
+			"rule_option": flattenNetworkFirewallRuleOptions(s.RuleOptions),
+		}
+		rules = append(rules, m)
+	}
+	return rules
+}
+
+func flattenNetworkFirewallHeader(h *networkfirewall.Header) []interface{} {
+	if h == nil {
+		return []interface{}{}
+	}
+
+	m := map[string]interface{}{
+		"destination":      aws.StringValue(h.Destination),
+		"destination_port": aws.StringValue(h.DestinationPort),
+		"direction":        aws.StringValue(h.Direction),
+		"protocol":         aws.StringValue(h.Protocol),
+		"source":           aws.StringValue(h.Source),
+		"source_port":      aws.StringValue(h.SourcePort),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallRuleOptions(o []*networkfirewall.RuleOption) []interface{} {
+	if o == nil {
+		return []interface{}{}
+	}
+
+	options := make([]interface{}, 0, len(o))
+	for _, option := range o {
+		m := map[string]interface{}{
+			"keyword":  aws.StringValue(option.Keyword),
+			"settings": aws.StringValueSlice(option.Settings),
+		}
+		options = append(options, m)
+	}
+
+	return options
+}
+
+func flattenNetworkFirewallStatelessRulesAndCustomActions(sr *networkfirewall.StatelessRulesAndCustomActions) []interface{} {
+	if sr == nil {
+		return []interface{}{}
+	}
+
+	m := map[string]interface{}{
+		"custom_action":  flattenNetworkFirewallCustomActions(sr.CustomActions),
+		"stateless_rule": flattenNetworkFirewallStatelessRules(sr.StatelessRules),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallStatelessRules(sr []*networkfirewall.StatelessRule) []interface{} {
+	if sr == nil {
+		return []interface{}{}
+	}
+
+	rules := make([]interface{}, 0, len(sr))
+	for _, s := range sr {
+		rule := map[string]interface{}{
+			"priority":        int(aws.Int64Value(s.Priority)),
+			"rule_definition": flattenNetworkFirewallRuleDefinition(s.RuleDefinition),
+		}
+		rules = append(rules, rule)
+	}
+
+	return rules
+}
+
+func flattenNetworkFirewallRuleDefinition(r *networkfirewall.RuleDefinition) []interface{} {
+	if r == nil {
+		return []interface{}{}
+	}
+
+	m := map[string]interface{}{
+		"actions":          flattenStringSet(r.Actions),
+		"match_attributes": flattenNetworkFirewallMatchAttributes(r.MatchAttributes),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallMatchAttributes(ma *networkfirewall.MatchAttributes) []interface{} {
+	if ma == nil {
+		return []interface{}{}
+	}
+
+	m := map[string]interface{}{
+		"destination":      flattenNetworkFirewallAddresses(ma.Destinations),
+		"destination_port": flattenNetworkFirewallPortRanges(ma.DestinationPorts),
+		"protocols":        flattenInt64Set(ma.Protocols),
+		"source":           flattenNetworkFirewallAddresses(ma.Sources),
+		"source_port":      flattenNetworkFirewallPortRanges(ma.SourcePorts),
+		"tcp_flag":         flattenNetworkFirewallTCPFlags(ma.TCPFlags),
+	}
+
+	return []interface{}{m}
+}
+
+func flattenNetworkFirewallAddresses(d []*networkfirewall.Address) []interface{} {
+	if d == nil {
+		return []interface{}{}
+	}
+
+	destinations := make([]interface{}, 0, len(d))
+	for _, addr := range d {
+		m := map[string]interface{}{
+			"address_definition": aws.StringValue(addr.AddressDefinition),
+		}
+		destinations = append(destinations, m)
+	}
+
+	return destinations
+}
+
+func flattenNetworkFirewallPortRanges(pr []*networkfirewall.PortRange) []interface{} {
+	if pr == nil {
+		return []interface{}{}
+	}
+
+	portRanges := make([]interface{}, 0, len(pr))
+	for _, r := range pr {
+		m := map[string]interface{}{
+			"from_port": int(aws.Int64Value(r.FromPort)),
+			"to_port":   int(aws.Int64Value(r.ToPort)),
+		}
+		portRanges = append(portRanges, m)
+	}
+
+	return portRanges
+}
+
+func flattenNetworkFirewallTCPFlags(t []*networkfirewall.TCPFlagField) []interface{} {
+	if t == nil {
+		return []interface{}{}
+	}
+	flagFields := make([]interface{}, 0, len(t))
+	for _, v := range t {
+		m := map[string]interface{}{
+			"flags": flattenStringSet(v.Flags),
+			"masks": flattenStringSet(v.Masks),
+		}
+		flagFields = append(flagFields, m)
+	}
+
+	return flagFields
+}
diff --git a/aws/resource_aws_networkfirewall_rule_group_test.go b/aws/resource_aws_networkfirewall_rule_group_test.go
new file mode 100644
index 00000000000..300bc6ce00b
--- /dev/null
+++ b/aws/resource_aws_networkfirewall_rule_group_test.go
@@ -0,0 +1,933 @@
+package aws
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/networkfirewall"
+	"github.com/hashicorp/aws-sdk-go-base/tfawserr"
+	"github.com/hashicorp/go-multierror"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/networkfirewall/finder"
+	"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource"
+)
+
+func init() {
+	resource.AddTestSweepers("aws_networkfirewall_rule_group", &resource.Sweeper{
+		Name: "aws_networkfirewall_rule_group",
+		F:    testSweepNetworkFirewallRuleGroups,
+		Dependencies: []string{
+			"aws_networkfirewall_firewall_policy",
+		},
+	})
+}
+
+func testSweepNetworkFirewallRuleGroups(region string) error {
+	client, err := sharedClientForRegion(region)
+	if err != nil {
+		return fmt.Errorf("error getting client: %w", err)
+	}
+	conn := client.(*AWSClient).networkfirewallconn
+	ctx := context.Background()
+	input := &networkfirewall.ListRuleGroupsInput{MaxResults: aws.Int64(100)}
+	var sweeperErrs *multierror.Error
+
+	for {
+		resp, err := conn.ListRuleGroupsWithContext(ctx, input)
+		if testSweepSkipSweepError(err) {
+			log.Printf("[WARN] Skipping NetworkFirewall Rule Group sweep for %s: %s", region, err)
+			return nil
+		}
+		if err != nil {
+			return fmt.Errorf("error retrieving NetworkFirewall Rule Groups: %w", err)
+		}
+
+		for _, r := range resp.RuleGroups {
+			if r == nil {
+				continue
+			}
+
+			arn := aws.StringValue(r.Arn)
+			log.Printf("[INFO] Deleting NetworkFirewall Rule Group: %s", arn)
+
+			r := resourceAwsNetworkFirewallRuleGroup()
+			d := r.Data(nil)
+			d.SetId(arn)
+			diags := r.DeleteContext(ctx, d, client)
+			for i := range diags {
+				if diags[i].Severity == diag.Error {
+					log.Printf("[ERROR] %s", diags[i].Summary)
+					sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf(diags[i].Summary))
+					continue
+				}
+			}
+		}
+
+		if aws.StringValue(resp.NextToken) == "" {
+			break
+		}
+		input.NextToken = resp.NextToken
+	}
+
+	return sweeperErrs.ErrorOrNil()
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_basic_rulesSourceList(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_rulesSourceList(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					testAccCheckResourceAttrRegionalARN(resourceName, "arn", "network-firewall", fmt.Sprintf("stateful-rulegroup/%s", rName)),
+					resource.TestCheckResourceAttr(resourceName, "capacity", "100"),
+					resource.TestCheckResourceAttr(resourceName, "name", rName),
+					resource.TestCheckResourceAttr(resourceName, "type", networkfirewall.RuleGroupTypeStateful),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.generated_rules_type", networkfirewall.GeneratedRulesTypeAllowlist),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.target_types.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.target_types.*", networkfirewall.TargetTypeHttpHost),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.targets.#", "1"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.targets.*", "test.example.com"),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_basic_statefulRule(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_statefulRule(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					testAccCheckResourceAttrRegionalARN(resourceName, "arn", "network-firewall", fmt.Sprintf("stateful-rulegroup/%s", rName)),
+					resource.TestCheckResourceAttr(resourceName, "capacity", "100"),
+					resource.TestCheckResourceAttr(resourceName, "name", rName),
+					resource.TestCheckResourceAttr(resourceName, "type", networkfirewall.RuleGroupTypeStateful),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateful_rule.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateful_rule.*", map[string]string{
+						"action":                    networkfirewall.StatefulActionPass,
+						"header.#":                  "1",
+						"header.0.destination":      "124.1.1.24/32",
+						"header.0.destination_port": "53",
+						"header.0.direction":        networkfirewall.StatefulRuleDirectionAny,
+						"header.0.protocol":         networkfirewall.StatefulRuleProtocolTcp,
+						"header.0.source":           "1.2.3.4/32",
+						"header.0.source_port":      "53",
+						"rule_option.#":             "1",
+					}),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_basic_statelessRule(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_statelessRule(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					testAccCheckResourceAttrRegionalARN(resourceName, "arn", "network-firewall", fmt.Sprintf("stateless-rulegroup/%s", rName)),
+					resource.TestCheckResourceAttr(resourceName, "capacity", "100"),
+					resource.TestCheckResourceAttr(resourceName, "name", rName),
+					resource.TestCheckResourceAttr(resourceName, "type", networkfirewall.RuleGroupTypeStateless),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*", map[string]string{
+						"priority":                                           "1",
+						"rule_definition.#":                                  "1",
+						"rule_definition.0.actions.#":                        "1",
+						"rule_definition.0.match_attributes.#":               "1",
+						"rule_definition.0.match_attributes.0.destination.#": "1",
+						"rule_definition.0.match_attributes.0.source.#":      "1",
+					}),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*.rule_definition.0.actions.*", "aws:drop"),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_basic_rules(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+	rules := `alert http any any -> any any (http_response_line; content:"403 Forbidden"; sid:1;)`
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_rules(rName, rules),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					testAccCheckResourceAttrRegionalARN(resourceName, "arn", "network-firewall", fmt.Sprintf("stateful-rulegroup/%s", rName)),
+					resource.TestCheckResourceAttr(resourceName, "capacity", "100"),
+					resource.TestCheckResourceAttr(resourceName, "name", rName),
+					resource.TestCheckResourceAttr(resourceName, "type", networkfirewall.RuleGroupTypeStateful),
+					resource.TestCheckResourceAttr(resourceName, "rules", rules),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.rules_string", rules),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateful_rule.#", "0"),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:            resourceName,
+				ImportState:             true,
+				ImportStateVerify:       true,
+				ImportStateVerifyIgnore: []string{"rules"}, // argument not returned in RuleGroup API response
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_statelessRuleWithCustomAction(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_statelessRuleWithCustomAction(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					testAccCheckResourceAttrRegionalARN(resourceName, "arn", "network-firewall", fmt.Sprintf("stateless-rulegroup/%s", rName)),
+					resource.TestCheckResourceAttr(resourceName, "capacity", "100"),
+					resource.TestCheckResourceAttr(resourceName, "name", rName),
+					resource.TestCheckResourceAttr(resourceName, "type", networkfirewall.RuleGroupTypeStateless),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*", map[string]string{
+						"priority":                                           "1",
+						"rule_definition.#":                                  "1",
+						"rule_definition.0.actions.#":                        "2",
+						"rule_definition.0.match_attributes.#":               "1",
+						"rule_definition.0.match_attributes.0.destination.#": "1",
+						"rule_definition.0.match_attributes.0.source.#":      "1",
+					}),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*.rule_definition.0.actions.*", "aws:pass"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*.rule_definition.0.actions.*", "example"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.custom_action.*", map[string]string{
+						"action_name":         "example",
+						"action_definition.#": "1",
+						"action_definition.0.publish_metric_action.#":             "1",
+						"action_definition.0.publish_metric_action.0.dimension.#": "1",
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_updateRulesSourceList(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_rulesSourceList(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallRuleGroup_updateRulesSourceList(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					testAccCheckResourceAttrRegionalARN(resourceName, "arn", "network-firewall", fmt.Sprintf("stateful-rulegroup/%s", rName)),
+					resource.TestCheckResourceAttr(resourceName, "capacity", "100"),
+					resource.TestCheckResourceAttr(resourceName, "name", rName),
+					resource.TestCheckResourceAttr(resourceName, "type", networkfirewall.RuleGroupTypeStateful),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.generated_rules_type", networkfirewall.GeneratedRulesTypeDenylist),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.target_types.#", "2"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.target_types.*", networkfirewall.TargetTypeHttpHost),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.target_types.*", networkfirewall.TargetTypeTlsSni),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.targets.#", "2"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.targets.*", "test.example.com"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.rules_source_list.0.targets.*", "test2.example.com"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_rulesSourceAndRuleVariables(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_rulesSourceList_ruleVariables(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rule_variables.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rule_variables.0.ip_sets.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rule_variables.0.ip_sets.*", map[string]string{
+						"key":                   "example",
+						"ip_set.#":              "1",
+						"ip_set.0.definition.#": "2",
+					}),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rule_variables.0.ip_sets.*.ip_set.0.definition.*", "10.0.0.0/16"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rule_variables.0.ip_sets.*.ip_set.0.definition.*", "10.0.1.0/24"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallRuleGroup_rulesSourceList_updateRuleVariables(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rule_variables.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rule_variables.0.ip_sets.#", "2"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rule_variables.0.port_sets.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rule_variables.0.ip_sets.*", map[string]string{
+						"key":                   "example",
+						"ip_set.#":              "1",
+						"ip_set.0.definition.#": "3",
+					}),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rule_variables.0.ip_sets.*.ip_set.0.definition.*", "10.0.0.0/16"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rule_variables.0.ip_sets.*.ip_set.0.definition.*", "10.0.1.0/24"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rule_variables.0.ip_sets.*.ip_set.0.definition.*", "192.168.0.0/16"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rule_variables.0.ip_sets.*", map[string]string{
+						"key":                   "example2",
+						"ip_set.#":              "1",
+						"ip_set.0.definition.#": "1",
+					}),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rule_variables.0.ip_sets.*.ip_set.0.definition.*", "1.2.3.4/32"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rule_variables.0.port_sets.*", map[string]string{
+						"key":                     "example",
+						"port_set.#":              "1",
+						"port_set.0.definition.#": "2",
+					}),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rule_variables.0.port_sets.*.port_set.0.definition.*", "443"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rule_variables.0.port_sets.*.port_set.0.definition.*", "80"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_rulesSourceList(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rule_variables.#", "0"),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.#", "1"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_updateStatefulRule(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_statefulRule(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallRuleGroup_updateStatefulRule(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateful_rule.#", "1"),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateful_rule.*", map[string]string{
+						"action":                    networkfirewall.StatefulActionDrop,
+						"header.#":                  "1",
+						"header.0.destination":      "1.2.3.4/32",
+						"header.0.destination_port": "1001",
+						"header.0.direction":        networkfirewall.StatefulRuleDirectionForward,
+						"header.0.protocol":         networkfirewall.StatefulRuleProtocolIp,
+						"header.0.source":           "124.1.1.24/32",
+						"header.0.source_port":      "1001",
+						"rule_option.#":             "1",
+					}),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_updateStatelessRule(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_statelessRule(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallRuleGroup_updateStatelessRule(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*", map[string]string{
+						"priority":                                                "10",
+						"rule_definition.#":                                       "1",
+						"rule_definition.0.actions.#":                             "1",
+						"rule_definition.0.match_attributes.#":                    "1",
+						"rule_definition.0.match_attributes.0.destination.#":      "1",
+						"rule_definition.0.match_attributes.0.destination_port.#": "1",
+						"rule_definition.0.match_attributes.0.protocols.#":        "1",
+						"rule_definition.0.match_attributes.0.source.#":           "1",
+						"rule_definition.0.match_attributes.0.source_port.#":      "1",
+						"rule_definition.0.match_attributes.0.tcp_flag.#":         "1",
+					}),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*.rule_definition.0.actions.*", "aws:pass"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*.rule_definition.0.match_attributes.0.protocols.*", "6"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*.rule_definition.0.match_attributes.0.tcp_flag.*.flags.*", "SYN"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*.rule_definition.0.match_attributes.0.tcp_flag.*.masks.*", "SYN"),
+					tfawsresource.TestCheckTypeSetElemAttr(resourceName, "rule_group.0.rules_source.0.stateless_rules_and_custom_actions.0.stateless_rule.*.rule_definition.0.match_attributes.0.tcp_flag.*.masks.*", "ACK"),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_tags(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_oneTag(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
+					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallRuleGroup_twoTags(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
+					resource.TestCheckResourceAttr(resourceName, "tags.Name", rName),
+					resource.TestCheckResourceAttr(resourceName, "tags.Description", "updated"),
+				),
+			},
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_rulesSourceList(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
+				),
+			},
+			{
+				ResourceName:      resourceName,
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
+
+func TestAccAwsNetworkFirewallRuleGroup_disappears(t *testing.T) {
+	rName := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "aws_networkfirewall_rule_group.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccNetworkFirewallRuleGroup_basic_rulesSourceList(rName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName),
+					testAccCheckResourceDisappears(testAccProvider, resourceAwsNetworkFirewallRuleGroup(), resourceName),
+				),
+				ExpectNonEmptyPlan: true,
+			},
+		},
+	})
+}
+
+func testAccCheckAwsNetworkFirewallRuleGroupDestroy(s *terraform.State) error {
+	for _, rs := range s.RootModule().Resources {
+		if rs.Type != "aws_networkfirewall_rule_group" {
+			continue
+		}
+
+		conn := testAccProvider.Meta().(*AWSClient).networkfirewallconn
+		output, err := finder.RuleGroup(context.Background(), conn, rs.Primary.ID)
+		if tfawserr.ErrCodeEquals(err, networkfirewall.ErrCodeResourceNotFoundException) {
+			continue
+		}
+		if err != nil {
+			return err
+		}
+		if output != nil {
+			return fmt.Errorf("NetworkFirewall Rule Group still exists: %s", rs.Primary.ID)
+		}
+	}
+
+	return nil
+}
+
+func testAccCheckAwsNetworkFirewallRuleGroupExists(n string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		rs, ok := s.RootModule().Resources[n]
+		if !ok {
+			return fmt.Errorf("Not found: %s", n)
+		}
+
+		if rs.Primary.ID == "" {
+			return fmt.Errorf("No NetworkFirewall Rule Group ID is set")
+		}
+
+		conn := testAccProvider.Meta().(*AWSClient).networkfirewallconn
+		output, err := finder.RuleGroup(context.Background(), conn, rs.Primary.ID)
+		if err != nil {
+			return err
+		}
+
+		if output == nil {
+			return fmt.Errorf("NetworkFirewall Rule Group (%s) not found", rs.Primary.ID)
+		}
+
+		return nil
+	}
+}
+
+func testAccNetworkFirewallRuleGroup_basic_rulesSourceList(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %q
+  type     = "STATEFUL"
+  rule_group {
+    rules_source {
+      rules_source_list {
+        generated_rules_type = "ALLOWLIST"
+        target_types         = ["HTTP_HOST"]
+        targets              = ["test.example.com"]
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_rulesSourceList_ruleVariables(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %[1]q
+  type     = "STATEFUL"
+  rule_group {
+    rule_variables {
+      ip_sets {
+        key = "example"
+        ip_set {
+          definition = ["10.0.0.0/16", "10.0.1.0/24"]
+        }
+      }
+    }
+    rules_source {
+      rules_source_list {
+        generated_rules_type = "ALLOWLIST"
+        target_types         = ["HTTP_HOST"]
+        targets              = ["test.example.com"]
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_rulesSourceList_updateRuleVariables(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %[1]q
+  type     = "STATEFUL"
+  rule_group {
+    rule_variables {
+      ip_sets {
+        key = "example"
+        ip_set {
+          definition = ["10.0.0.0/16", "10.0.1.0/24", "192.168.0.0/16"]
+        }
+      }
+      ip_sets {
+        key = "example2"
+        ip_set {
+          definition = ["1.2.3.4/32"]
+        }
+      }
+      port_sets {
+        key = "example"
+        port_set {
+          definition = ["443", "80"]
+        }
+      }
+    }
+    rules_source {
+      rules_source_list {
+        generated_rules_type = "ALLOWLIST"
+        target_types         = ["HTTP_HOST"]
+        targets              = ["test.example.com"]
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_updateRulesSourceList(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %q
+  type     = "STATEFUL"
+  rule_group {
+    rules_source {
+      rules_source_list {
+        generated_rules_type = "DENYLIST"
+        target_types         = ["HTTP_HOST", "TLS_SNI"]
+        targets              = ["test.example.com", "test2.example.com"]
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_basic_statefulRule(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity    = 100
+  name        = %[1]q
+  description = %[1]q
+  type        = "STATEFUL"
+  rule_group {
+    rules_source {
+      stateful_rule {
+        action = "PASS"
+        header {
+          destination      = "124.1.1.24/32"
+          destination_port = 53
+          direction        = "ANY"
+          protocol         = "TCP"
+          source           = "1.2.3.4/32"
+          source_port      = 53
+        }
+        rule_option {
+          keyword = "sid:1"
+        }
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_updateStatefulRule(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %[1]q
+  type     = "STATEFUL"
+  rule_group {
+    rules_source {
+      stateful_rule {
+        action = "DROP"
+        header {
+          destination      = "1.2.3.4/32"
+          destination_port = 1001
+          direction        = "FORWARD"
+          protocol         = "IP"
+          source           = "124.1.1.24/32"
+          source_port      = 1001
+        }
+        rule_option {
+          keyword = "sid:1"
+        }
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_basic_statelessRule(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %[1]q
+  type     = "STATELESS"
+  rule_group {
+    rules_source {
+      stateless_rules_and_custom_actions {
+        stateless_rule {
+          priority = 1
+          rule_definition {
+            actions = ["aws:drop"]
+            match_attributes {
+              destination {
+                address_definition = "1.2.3.4/32"
+              }
+              source {
+                address_definition = "124.1.1.5/32"
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_updateStatelessRule(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %[1]q
+  type     = "STATELESS"
+  rule_group {
+    rules_source {
+      stateless_rules_and_custom_actions {
+        stateless_rule {
+          priority = 10
+          rule_definition {
+            actions = ["aws:pass"]
+            match_attributes {
+              destination {
+                address_definition = "1.2.3.4/32"
+              }
+              destination_port {
+                from_port = 53
+                to_port   = 53
+              }
+              protocols = [6]
+              source {
+                address_definition = "124.1.1.5/32"
+              }
+              source_port {
+                from_port = 53
+                to_port   = 53
+              }
+              tcp_flag {
+                flags = ["SYN"]
+                masks = ["SYN", "ACK"]
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_basic_rules(rName, rules string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %q
+  type     = "STATEFUL"
+  rules    = %q
+}
+`, rName, rules)
+}
+
+func testAccNetworkFirewallRuleGroup_statelessRuleWithCustomAction(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %[1]q
+  type     = "STATELESS"
+  rule_group {
+    rules_source {
+      stateless_rules_and_custom_actions {
+        custom_action {
+          action_name = "example"
+          action_definition {
+            publish_metric_action {
+              dimension {
+                value = "2"
+              }
+            }
+          }
+        }
+        stateless_rule {
+          priority = 1
+          rule_definition {
+            actions = ["aws:pass", "example"]
+            match_attributes {
+              destination {
+                address_definition = "1.2.3.4/32"
+              }
+              source {
+                address_definition = "124.1.1.5/32"
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_oneTag(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %q
+  type     = "STATEFUL"
+  rule_group {
+    rules_source {
+      rules_source_list {
+        generated_rules_type = "ALLOWLIST"
+        target_types         = ["HTTP_HOST"]
+        targets              = ["test.example.com"]
+      }
+    }
+  }
+  tags = {
+    Name = %[1]q
+  }
+}
+`, rName)
+}
+
+func testAccNetworkFirewallRuleGroup_twoTags(rName string) string {
+	return fmt.Sprintf(`
+resource "aws_networkfirewall_rule_group" "test" {
+  capacity = 100
+  name     = %q
+  type     = "STATEFUL"
+  rule_group {
+    rules_source {
+      rules_source_list {
+        generated_rules_type = "ALLOWLIST"
+        target_types         = ["HTTP_HOST"]
+        targets              = ["test.example.com"]
+      }
+    }
+  }
+  tags = {
+    Name        = %[1]q
+    Description = "updated"
+  }
+}
+`, rName)
+}
diff --git a/infrastructure/repository/labels-service.tf b/infrastructure/repository/labels-service.tf
index 2b76e122171..8f2b8721f0d 100644
--- a/infrastructure/repository/labels-service.tf
+++ b/infrastructure/repository/labels-service.tf
@@ -137,6 +137,7 @@ variable "service_labels" {
     "mobile",
     "mq",
     "neptune",
+    "networkfirewall",
     "networkmanager",
     "opsworks",
     "organizations",
diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt
index 88fa369eeeb..cadb59cf71b 100644
--- a/website/allowed-subcategories.txt
+++ b/website/allowed-subcategories.txt
@@ -82,6 +82,7 @@ MediaConvert
 MediaPackage
 MediaStore
 Neptune
+Network Firewall
 OpsWorks
 Organizations
 Outposts
diff --git a/website/docs/guides/custom-service-endpoints.html.md b/website/docs/guides/custom-service-endpoints.html.md
index ef3996b94e8..3dfa44d24fd 100644
--- a/website/docs/guides/custom-service-endpoints.html.md
+++ b/website/docs/guides/custom-service-endpoints.html.md
@@ -152,6 +152,7 @@ The Terraform AWS Provider allows the following endpoints to be customized:
   <li><code>mediastoredata</code></li>
   <li><code>mq</code></li>
   <li><code>neptune</code></li>
+  <li><code>networkfirewall</code></li>
   <li><code>networkmanager</code></li>
   <li><code>opsworks</code></li>
   <li><code>organizations</code></li>
diff --git a/website/docs/r/networkfirewall_firewall.html.markdown b/website/docs/r/networkfirewall_firewall.html.markdown
new file mode 100644
index 00000000000..caa5196277a
--- /dev/null
+++ b/website/docs/r/networkfirewall_firewall.html.markdown
@@ -0,0 +1,75 @@
+---
+subcategory: "Network Firewall"
+layout: "aws"
+page_title: "AWS: aws_networkfirewall_firewall"
+description: |-
+  Provides an AWS Network Firewall Firewall resource.
+---
+
+# Resource: aws_networkfirewall_firewall
+
+Provides an AWS Network Firewall Firewall Resource
+
+## Example Usage
+
+```hcl
+resource "aws_networkfirewall_firewall" "example" {
+  name                = "example"
+  firewall_policy_arn = aws_networkfirewall_firewall_policy.example.arn
+  vpc_id              = aws_vpc.example.id
+  subnet_mapping {
+    subnet_id = aws_subnet.example.id
+  }
+
+  tags = {
+    Tag1 = "Value1"
+    Tag2 = "Value2"
+  }
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `delete_protection` - (Optional) A boolean flag indicating whether it is possible to delete the firewall. Defaults to `false`.
+
+* `description` - (Optional) A friendly description of the firewall.
+
+* `firewall_policy_arn` - (Required) The Amazon Resource Name (ARN) of the VPC Firewall policy.
+
+* `firewall_policy_change_protection` - (Option) A boolean flag indicating whether it is possible to change the associated firewall policy. Defaults to `false`.
+
+* `name` - (Required, Forces new resource) A friendly name of the firewall.
+
+* `subnet_change_protection` - (Optional) A boolean flag indicating whether it is possible to change the associated subnet(s). Defaults to `false`.
+
+* `subnet_mapping` - (Required) Set of configuration blocks describing the public subnets. Each subnet must belong to a different Availability Zone in the VPC. AWS Network Firewall creates a firewall endpoint in each subnet. See [Subnet Mapping](#subnet-mapping) below for details.
+
+* `tags` - (Optional) The key:value pairs to associate with the resource.
+
+* `vpc_id` - (Required, Forces new resource) The unique identifier of the VPC where AWS Network Firewall should create the firewall.
+
+### Subnet Mapping
+
+The `subnet_mapping` block supports the following arguments:
+
+* `subnet_id` - (Required) The unique identifier for the subnet.
+
+## Attributes Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - The Amazon Resource Name (ARN) that identifies the firewall.
+
+* `arn` - The Amazon Resource Name (ARN) that identifies the firewall.
+
+* `update_token` - A string token used when updating a firewall.
+
+## Import
+
+Network Firewall Firewalls can be imported using their `ARN`.
+
+```
+$ terraform import aws_networkfirewall_firewall.example arn:aws:network-firewall:us-west-1:123456789012:firewall/example
+```
diff --git a/website/docs/r/networkfirewall_firewall_policy.html.markdown b/website/docs/r/networkfirewall_firewall_policy.html.markdown
new file mode 100644
index 00000000000..ce8bb98fa3d
--- /dev/null
+++ b/website/docs/r/networkfirewall_firewall_policy.html.markdown
@@ -0,0 +1,143 @@
+---
+subcategory: "Network Firewall"
+layout: "aws"
+page_title: "AWS: aws_networkfirewall_firewall_policy"
+description: |-
+  Provides an AWS Network Firewall Policy resource.
+---
+
+# Resource: aws_networkfirewall_firewall_policy
+
+Provides an AWS Network Firewall Firewall Policy Resource
+
+## Example Usage
+
+```hcl
+resource "aws_networkfirewall_firewall_policy" "example" {
+  name = "example"
+
+  firewall_policy {
+    stateless_default_actions          = ["aws:pass"]
+    stateless_fragment_default_actions = ["aws:drop"]
+    stateless_rule_group_reference {
+      priority     = 1
+      resource_arn = aws_networkfirewall_rule_group.example.arn
+    }
+  }
+
+  tags = {
+    Tag1 = "Value1"
+    Tag2 = "Value2"
+  }
+}
+```
+
+## Policy with a Custom Action for Stateless Inspection
+
+```hcl
+resource "aws_networkfirewall_firewall_policy" "test" {
+  name = "example"
+
+  firewall_policy {
+    stateless_default_actions          = ["aws:pass", "ExampleCustomAction"]
+    stateless_fragment_default_actions = ["aws:drop"]
+
+    stateless_custom_action {
+      action_definition {
+        publish_metric_action {
+          dimension {
+            value = "1"
+          }
+        }
+      }
+      action_name = "ExampleCustomAction"
+    }
+  }
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `description` - (Optional) A friendly description of the firewall policy.
+
+* `firewall_policy` - (Required) A configuration block describing the rule groups and policy actions to use in the firewall policy. See [Firewall Policy](#firewall-policy) below for details.
+
+* `name` - (Required, Forces new resource) A friendly name of the firewall policy.
+
+* `tags` - (Optional) An array of key:value pairs to associate with the resource.
+
+### Firewall Policy
+
+The `firewall_policy` block supports the following arguments:
+
+* `stateful_rule_group_reference` - (Optional) Set of configuration blocks containing references to the stateful rule groups that are used in the policy. See [Stateful Rule Group Reference](#stateful-rule-group-reference) below for details.
+
+* `stateless_custom_action` - (Optional) Set of configuration blocks describing the custom action definitions that are available for use in the firewall policy's `stateless_default_actions`. See [Stateless Custom Action](#stateless-custom-action) below for details.
+
+* `stateless_default_actions` - (Required) Set of actions to take on a packet if it does not match any of the stateless rules in the policy. You must specify one of the standard actions including: `aws:drop`, `aws:pass`, or `aws:forward_to_sfe`.
+In addition, you can specify custom actions that are compatible with your standard action choice. If you want non-matching packets to be forwarded for stateful inspection, specify `aws:forward_to_sfe`.
+
+* `stateless_fragment_default_actions` - (Required) Set of actions to take on a fragmented packet if it does not match any of the stateless rules in the policy. You must specify one of the standard actions including: `aws:drop`, `aws:pass`, or `aws:forward_to_sfe`.
+In addition, you can specify custom actions that are compatible with your standard action choice. If you want non-matching packets to be forwarded for stateful inspection, specify `aws:forward_to_sfe`.
+
+* `stateless_rule_group_reference` - (Optional) Set of configuration blocks containing references to the stateless rule groups that are used in the policy. See [Stateless Rule Group Reference](#stateless-rule-group-reference) below for details.
+
+### Stateful Rule Group Reference
+
+The `stateful_rule_group_reference` block supports the following argument:
+
+* `resource_arn` - (Required) The Amazon Resource Name (ARN) of the stateful rule group.
+
+### Stateless Custom Action
+
+The `stateless_custom_action` block supports the following arguments:
+
+* `action_definition` - (Required) A configuration block describing the custom action associated with the `action_name`. See [Action Definition](#action-definition) below for details.
+
+* `action_name` - (Required, Forces new resource) A friendly name of the custom action.
+
+### Stateless Rule Group Reference
+
+The `stateless_rule_group_reference` block supports the following arguments:
+
+* `priority` - (Required) An integer setting that indicates the order in which to run the stateless rule groups in a single policy. AWS Network Firewall applies each stateless rule group to a packet starting with the group that has the lowest priority setting.
+
+* `resource_arn` - (Required) The Amazon Resource Name (ARN) of the stateless rule group.
+
+### Action Definition
+
+The `action_definition` block supports the following argument:
+
+* `publish_metric_action` - (Required) A configuration block describing the stateless inspection criteria that publishes the specified metrics to Amazon CloudWatch for the matching packet. You can pair this custom action with any of the standard stateless rule actions. See [Publish Metric Action](#publish-metric-action) below for details.
+
+### Publish Metric Action
+
+The `publish_metric_action` block supports the following argument:
+
+* `dimension` - (Required) Set of configuration blocks describing dimension settings to use for Amazon CloudWatch custom metrics. See [Dimension](#dimension) below for more details.
+
+### Dimension
+
+The `dimension` block supports the following argument:
+
+* `value` - (Required) The string value to use in the custom metric dimension.
+
+## Attributes Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - The Amazon Resource Name (ARN) that identifies the firewall policy.
+
+* `arn` - The Amazon Resource Name (ARN) that identifies the firewall policy.
+
+* `update_token` - A string token used when updating a firewall policy.
+
+## Import
+
+Network Firewall Policies can be imported using their `ARN`.
+
+```
+$ terraform import aws_networkfirewall_firewall_policy.example arn:aws:network-firewall:us-west-1:123456789012:firewall-policy/example
+```
diff --git a/website/docs/r/networkfirewall_logging_configuration.html.markdown b/website/docs/r/networkfirewall_logging_configuration.html.markdown
new file mode 100644
index 00000000000..be5a06e0a53
--- /dev/null
+++ b/website/docs/r/networkfirewall_logging_configuration.html.markdown
@@ -0,0 +1,106 @@
+---
+subcategory: "Network Firewall"
+layout: "aws"
+page_title: "AWS: aws_networkfirewall_logging_configuration"
+description: |-
+  Provides an AWS Network Firewall Logging Configuration resource.
+---
+
+# Resource: aws_networkfirewall_logging_configuration
+
+Provides an AWS Network Firewall Logging Configuration Resource
+
+## Example Usage
+
+### Logging to S3
+
+```hcl
+resource "aws_networkfirewall_logging_configuration" "example" {
+  firewall_arn = aws_networkfirewall_firewall.example.arn
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        bucketName = aws_s3_bucket.example.bucket
+        prefix     = "/example"
+      }
+      log_destination_type = "S3"
+      log_type             = "FLOW"
+    }
+  }
+}
+```
+
+### Logging to CloudWatch
+
+```hcl
+resource "aws_networkfirewall_logging_configuration" "example" {
+  firewall_arn = aws_networkfirewall_firewall.example.arn
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        logGroup = aws_cloudwatch_log_group.example.name
+      }
+      log_destination_type = "CloudWatchLogs"
+      log_type             = "ALERT"
+    }
+  }
+}
+```
+
+### Logging to Kinesis Data Firehose
+
+```hcl
+resource "aws_networkfirewall_logging_configuration" "example" {
+  firewall_arn = aws_networkfirewall_firewall.example.arn
+  logging_configuration {
+    log_destination_config {
+      log_destination = {
+        deliveryStream = aws_kinesis_firehose_delivery_stream.example.name
+      }
+      log_destination_type = "KinesisDataFirehose"
+      log_type             = "ALERT"
+    }
+  }
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `firewall_arn` - (Required, Forces new resource) The Amazon Resource Name (ARN) of the Network Firewall firewall.
+
+* `logging_configuration` - (Required) A configuration block describing how AWS Network Firewall performs logging for a firewall. See [Logging Configuration](#logging-configuration) below for details.
+
+### Logging Configuration
+
+The `logging_configuration` block supports the following arguments:
+
+* `log_destination_config` - (Required) Set of configuration blocks describing the logging details for a firewall. See [Log Destination Config](#log-destination-config) below for details. At most, only two blocks can be specified; one for `FLOW` logs and one for `ALERT` logs.
+
+### Log Destination Config
+
+The `log_destination_config` block supports the following arguments:
+
+* `log_destination` - (Required) A map describing the logging destination for the chosen `log_destination_type`.
+    * For an Amazon S3 bucket, specify the key `bucketName` with the URL of the bucket and optionally specify the key `prefix` with a path.
+    * For a CloudWatch log group, specify the key `logGroup` with the Amazon Resource Name (ARN) of the CloudWatch log group.
+    * For a Kinesis Data Firehose delivery stream, specify the key `deliveryStream` with the Amazon Resource Name (ARN) of the delivery stream.
+
+* `log_destination_type` - (Required) The location to send logs to. Valid values: `S3`, `CloudWatchLogs`, `KinesisDataFirehose`.
+
+* `log_type` - (Required) The type of log to send. Valid values: `ALERT` or `FLOW`. Alert logs report traffic that matches a `StatefulRule` with an action setting that sends a log message. Flow logs are standard network traffic flow logs.
+
+## Attributes Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - The Amazon Resource Name (ARN) of the associated firewall.
+
+## Import
+
+Network Firewall Logging Configurations can be imported using the `firewall_arn` e.g
+
+```
+$ terraform import aws_networkfirewall_logging_configuration.example arn:aws:network-firewall:us-west-1:123456789012:firewall/example
+```
diff --git a/website/docs/r/networkfirewall_rule_group.html.markdown b/website/docs/r/networkfirewall_rule_group.html.markdown
new file mode 100644
index 00000000000..1e0d4f02b2f
--- /dev/null
+++ b/website/docs/r/networkfirewall_rule_group.html.markdown
@@ -0,0 +1,389 @@
+---
+subcategory: "Network Firewall"
+layout: "aws"
+page_title: "AWS: aws_networkfirewall_rule_group"
+description: |-
+  Provides an AWS Network Firewall Rule Group resource.
+---
+
+# Resource: aws_networkfirewall_rule_group
+
+Provides an AWS Network Firewall Rule Group Resource
+
+## Example Usage
+
+### Stateful Inspection
+
+```hcl
+resource "aws_networkfirewall_rule_group" "example" {
+  capacity = 100
+  name     = "example"
+  type     = "STATEFUL"
+  rule_group {
+    rules_source {
+      rules_source_list {
+        generated_rules_type = "DENYLIST"
+        target_types         = ["HTTP_HOST"]
+        targets              = ["test.example.com"]
+      }
+    }
+  }
+
+  tags = {
+    Tag1 = "Value1"
+    Tag2 = "Value2"
+  }
+}
+```
+
+### Stateful Inspection compatible with intrusion detection systems like Snort or Suricata
+
+```hcl
+resource "aws_networkfirewall_rule_group" "example" {
+  capacity = 100
+  name     = "example"
+  type     = "STATEFUL"
+  rule_group {
+    rules_source {
+      stateful_rule {
+        action = "DROP"
+        header {
+          destination      = "124.1.1.24/32"
+          destination_port = 53
+          direction        = "ANY"
+          protocol         = "TCP"
+          source           = "1.2.3.4/32"
+          source_port      = 53
+        }
+        rule_option {
+          keyword = "sid:1"
+        }
+      }
+    }
+  }
+
+  tags = {
+    Tag1 = "Value1"
+    Tag2 = "Value2"
+  }
+}
+```
+
+### Stateful Inspection from rules specifications defined in Suricata flat format
+
+```hcl
+resource "aws_networkfirewall_rule_group" "example" {
+  capacity = 100
+  name     = "example"
+  type     = "STATEFUL"
+  rules    = file("example.rules")
+
+  tags = {
+    Tag1 = "Value1"
+    Tag2 = "Value2"
+  }
+}
+```
+
+### Stateless Inspection with a Custom Action
+
+```hcl
+resource "aws_networkfirewall_rule_group" "example" {
+  description = "Stateless Rate Limiting Rule"
+  capacity    = 100
+  name        = "example"
+  type        = "STATELESS"
+  rule_group {
+    rules_source {
+      stateless_rules_and_custom_actions {
+        custom_action {
+          action_definition {
+            publish_metric_action {
+              dimension {
+                value = "2"
+              }
+            }
+          }
+          action_name = "ExampleMetricsAction"
+        }
+        stateless_rule {
+          priority = 1
+          rule_definition {
+            actions = ["aws:pass", "ExampleMetricsAction"]
+            match_attributes {
+              source {
+                address_definition = "1.2.3.4/32"
+              }
+              source_port {
+                from_port = 443
+                to_port   = 443
+              }
+              destination {
+                address_definition = "124.1.1.5/32"
+              }
+              destination_port {
+                from_port = 443
+                to_port   = 443
+              }
+              protocols = [6]
+              tcp_flag {
+                flags = ["SYN"]
+                masks = ["SYN", "ACK"]
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  tags = {
+    Tag1 = "Value1"
+    Tag2 = "Value2"
+  }
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `capacity` - (Required, Forces new resource) The maximum number of operating resources that this rule group can use. For a stateless rule group, the capacity required is the sum of the capacity requirements of the individual rules. For a stateful rule group, the minimum capacity required is the number of individual rules.
+
+* `description` - (Optional) A friendly description of the rule group.
+
+* `name` - (Required, Forces new resource) A friendly name of the rule group.
+
+* `rule_group` - (Optional) A configuration block that defines the rule group rules. Required unless `rules` is specified. See [Rule Group](#rule-group) below for details.
+
+* `rules` - (Optional) The stateful rule group rules specifications in Suricata file format, with one rule per line. Use this to import your existing Suricata compatible rule groups. Required unless `rule_group` is specified.
+
+* `tags` - (Optional) A map of key:value pairs to associate with the resource.
+
+* `type` - (Required) Whether the rule group is stateless (containing stateless rules) or stateful (containing stateful rules). Valid values include: `STATEFUL` or `STATELESS`.
+
+### Rule Group
+
+The `rule_group` block supports the following argument:
+
+* `rule_variables` - (Optional) A configuration block that defines additional settings available to use in the rules defined in the rule group. See [Rule Variables](#rule-variables) below for details.
+
+* `rules_source` - (Required) A configuration block that defines the stateful or stateless rules for the rule group. See [Rules Source](#rules-source) below for details.
+
+### Rule Variables
+
+The `rule_variables` block supports the following arguments:
+
+* `ip_sets` - (Optional) Set of configuration blocks that define IP address information. See [IP Sets](#ip-sets) below for details.
+
+* `port_sets` - (Optional) Set of configuration blocks that define port range information. See [Port Sets](#port-sets) below for details.
+
+### IP Sets
+
+The `ip_sets` block supports the following arguments:
+
+* `key` - (Required) A unique alphanumeric string to identify the `ip_set`.
+
+* `ip_set` - (Required) A configuration block that defines a set of IP addresses. See [IP Set](#ip-set) below for details.
+
+### IP Set
+
+The `ip_set` configuration block supports the following argument:
+
+* `definition` - (Required) Set of IP addresses and address ranges, in CIDR notation.
+
+### Port Sets
+
+The `port_sets` block supports the following arguments:
+
+* `key` - (Required) An unique alphanumeric string to identify the `port_set`.
+
+* `port_set` - (Required) A configuration block that defines a set of port ranges. See [Port Set](#port-set) below for details.
+
+### Port Set
+
+The `port_set` configuration block suppports the following argument:
+
+* `definition` - (Required) Set of port ranges.
+
+### Rules Source
+
+The `rules_source` block supports the following arguments:
+
+~> **NOTE:** Only one of `rules_source_list`, `rules_string`, `stateful_rule`, or `stateless_rules_and_custom_actions` must be specified.
+
+* `rules_source_list` - (Optional) A configuration block containing **stateful** inspection criteria for a domain list rule group. See [Rules Source List](#rules-source-list) below for details.
+
+* `rules_string` - (Optional) The fully qualified name of a file in an S3 bucket that contains Suricata compatible intrusion preventions system (IPS) rules or the Suricata rules as a string. These rules contain **stateful** inspection criteria and the action to take for traffic that matches the criteria.
+
+* `stateful_rule` - (Optional) Set of configuration blocks containing **stateful** inspection criteria for 5-tuple rules to be used together in a rule group. See [Stateful Rule](#stateful-rule) below for details.
+
+* `stateless_rules_and_custom_actions` - (Optional) A configuration block containing **stateless** inspection criteria for a stateless rule group. See [Stateless Rules and Custom Actions](#stateless-rules-and-custom-actions) below for details.
+
+### Rules Source List
+
+The `rules_source_list` block supports the following arguments:
+
+* `generated_rules_type` - (Required) String value to specify whether domains in the target list are allowed or denied access. Valid values: `ALLOWLIST`, `DENYLIST`.
+
+* `target_types` - (Required) Set of types of domain specifications that are provided in the `targets` argument. Valid values: `HTTP_HOST`, `TLS_SNI`.
+
+* `targets` - (Required) Set of domains that you want to inspect for in your traffic flows.
+
+### Stateful Rule
+
+The `stateful_rule` block supports the following arguments:
+
+* `action` - (Required) Action to take with packets in a traffic flow when the flow matches the stateful rule criteria. For all actions, AWS Network Firewall performs the specified action and discontinues stateful inspection of the traffic flow. Valid values: `ALERT`, `DROP` or `PASS`.
+
+* `header` - (Required) A configuration block containing the stateful 5-tuple inspection criteria for the rule, used to inspect traffic flows. See [Header](#header) below for details.
+
+* `rule_option` - (Required) Set of configuration blocks containing additional settings for a stateful rule. See [Rule Option](#rule-option) below for details.
+
+### Stateless Rules and Custom Actions
+
+The `stateless_rules_and_custom_actions` block supports the following arguments:
+
+* `custom_action` - (Optional) Set of configuration blocks containing custom action definitions that are available for use by the set of `stateless rule`. See [Custom Action](#custom-action) below for details.
+
+* `stateless_rule` - (Required) Set of configuration blocks containing the stateless rules for use in the stateless rule group. See [Stateless Rule](#stateless-rule) below for details.
+
+### Header
+
+The `header` block supports the following arguments:
+
+* `destination` - (Optional) The destination IP address or address range to inspect for, in CIDR notation. If left empty, this matches with any destination address.
+
+* `destination_port` - (Optional) The destination port to inspect for. If left empty, this matches with any port.
+
+* `direction` - (Required) The direction of traffic flow to inspect. Valid values: `ANY` or `FORWARD`.
+
+* `protocol` - (Optional) The protocol to inspect. If not specified, this matches with any protocol.
+Valid values: `IP`, `TCP`, `UDP`, `ICMP`, `HTTP`, `FTP`, `TLS`, `SMB`, `DNS`, `DCERPC`, `SSH`, `SMTP`, `IMAP`, `MSN`, `KRB5`, `IKEV2`, `TFTP`, `NTP`, `DHCP`.
+
+* `source` - (Optional) The source IP address or address range for, in CIDR notation. If left empty, this matches with any source address.
+
+* `source_port` - (Optional) The source port to inspect for. If left empty, this matches with any port.
+
+### Rule Option
+
+The `rule_option` block supports the following arguments:
+
+* `keyword` - (Required) Keyword defined by open source detection systems like Snort or Suricata for stateful rule inspection.
+See [Snort General Rule Options](http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node31.html) or [Suricata Rule Options](https://suricata.readthedocs.io/en/suricata-5.0.1/rules/intro.html#rule-options) for more details.
+
+* `settings` - (Optional) Set of strings for additional settings to use in stateful rule inspection.
+
+### Custom Action
+
+The `custom_action` block supports the following arguments:
+
+* `action_definition` - (Required) A configuration block describing the custom action associated with the `action_name`. See [Action Definition](#action-definition) below for details.
+
+* `action_name` - (Required, Forces new resource) A friendly name of the custom action.
+
+### Stateless Rule
+
+The `stateless_rule` block supports the following arguments:
+
+* `priority` - (Required) A setting that indicates the order in which to run this rule relative to all of the rules that are defined for a stateless rule group. AWS Network Firewall evaluates the rules in a rule group starting with the lowest priority setting.
+
+* `rule_definition` - (Required) A configuration block defining the stateless 5-tuple packet inspection criteria and the action to take on a packet that matches the criteria. See [Rule Definition](#rule-definition) below for details.
+
+### Rule Definition
+
+The `rule_definition` block supports the following arguments:
+
+* `actions` - (Required) Set of actions to take on a packet that matches one of the stateless rule definition's `match_attributes`. For every rule you must specify 1 standard action, and you can add custom actions. Standard actions include: `aws:pass`, `aws:drop`, `aws:forward_to_sfe`.
+
+* `match_attributes` - (Required) A configuration block containing criteria for AWS Network Firewall to use to inspect an individual packet in stateless rule inspection. See [Match Attributes](#match-attributes) below for details.
+
+### Match Attributes
+
+The `match_attributes` block supports the following arguments:
+
+* `destination` - (Optional) Set of configuration blocks describing the destination IP address and address ranges to inspect for, in CIDR notation. If not specified, this matches with any destination address. See [Destination](#destination) below for details.
+
+* `destination_port` - (Optional) Set of configuration blocks describing the destination ports to inspect for. If not specified, this matches with any destination port. See [Destination Port](#destination-port) below for details.
+
+* `protocols` - (Optional) Set of protocols to inspect for, specified using the protocol's assigned internet protocol number (IANA).
+
+* `source` - (Optional) Set of configuration blocks describing the source IP address and address ranges to inspect for, in CIDR notation. If not specified, this matches with any source address. See [Source](#source) below for details.
+
+* `source_port` - (Optional) Set of configuration blocks describing the source ports to inspect for. If not specified, this matches with any source port. See [Source Port](#source-port) below for details.
+
+* `tcp_flag` - (Optional) Set of configuration blocks containing the TCP flags and masks to inspect for. If not specified, this matches with any settings.
+
+### Action Definition
+
+The `action_definition` block supports the following argument:
+
+* `publish_metric_action` - (Required) A configuration block describing the stateless inspection criteria that publishes the specified metrics to Amazon CloudWatch for the matching packet. You can pair this custom action with any of the standard stateless rule actions. See [Publish Metric Action](#publish-metric-action) below for details.
+
+### Publish Metric Action
+
+The `publish_metric_action` block supports the following argument:
+
+* `dimension` - (Required) Set of configuration blocks containing the dimension settings to use for Amazon CloudWatch custom metrics. See [Dimension](#dimension) below for details.
+
+### Dimension
+
+The `dimension` block supports the following argument:
+
+* `value` - (Required) The value to use in the custom metric dimension.
+
+### Destination
+
+The `destination` block supports the following argument:
+
+* `address_definition` - (Optional)  An IP address or a block of IP addresses in CIDR notation. AWS Network Firewall supports all address ranges for IPv4.
+
+### Destination Port
+
+The `destination_port` block supports the following arguments:
+
+* `from_port` - (Required) The lower limit of the port range. This must be less than or equal to the `to_port`.
+
+* `to_port` - (Optional) The upper limit of the port range. This must be greater than or equal to the `from_port`.
+
+### Source
+
+The `source` block supports the following argument:
+
+* `address_definition` - (Optional)  An IP address or a block of IP addresses in CIDR notation. AWS Network Firewall supports all address ranges for IPv4.
+
+### Source Port
+
+The `source_port` block supports the following arguments:
+
+* `from_port` - (Required) The lower limit of the port range. This must be less than or equal to the `to_port`.
+
+* `to_port` - (Optional) The upper limit of the port range. This must be greater than or equal to the `from_port`.
+
+### TCP Flag
+
+The `tcp_flag` block supports the following arguments:
+
+* `flags` - (Required) Set of flags to look for in a packet. AWS Network Firewall checks only the part of the packet specified in `masks`.
+Valid values: `FIN`, `SYN`, `RST`, `PSH`, `ACK`, `URG`, `ECE`, `CWR`.
+
+* `masks` - (Optional) Set of values describing the part of the packet that you want to check for the flags. To inspect the entire packet, leave this empty.
+Valid values: `FIN`, `SYN`, `RST`, `PSH`, `ACK`, `URG`, `ECE`, `CWR`.
+
+## Attributes Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - The Amazon Resource Name (ARN) that identifies the rule group.
+
+* `arn` - The Amazon Resource Name (ARN) that identifies the rule group.
+
+* `update_token` - A string token used when updating the rule group.
+
+## Import
+
+Network Firewall Rule Groups can be imported using their `ARN`.
+
+```
+$ terraform import aws_networkfirewall_rule_group.example arn:aws:network-firewall:us-west-1:123456789012:stateful-rulegroup/example
+```