diff --git a/aws/resource_aws_networkfirewall_rule_group.go b/aws/resource_aws_networkfirewall_rule_group.go index beb601fbf302..5e78d010a61d 100644 --- a/aws/resource_aws_networkfirewall_rule_group.go +++ b/aws/resource_aws_networkfirewall_rule_group.go @@ -170,26 +170,22 @@ func resourceAwsNetworkFirewallRuleGroup() *schema.Resource { "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, Required: true, - ForceNew: true, ValidateFunc: validation.Any( validateIpv4CIDRNetworkAddress, validation.StringInSlice([]string{networkfirewall.StatefulRuleDirectionAny}, false), @@ -198,24 +194,20 @@ func resourceAwsNetworkFirewallRuleGroup() *schema.Resource { "destination_port": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "direction": { Type: schema.TypeString, Required: true, - ForceNew: true, ValidateFunc: validation.StringInSlice(networkfirewall.StatefulRuleDirection_Values(), false), }, "protocol": { Type: schema.TypeString, Required: true, - ForceNew: true, ValidateFunc: validation.StringInSlice(networkfirewall.StatefulRuleProtocol_Values(), false), }, "source": { Type: schema.TypeString, Required: true, - ForceNew: true, ValidateFunc: validation.Any( validateIpv4CIDRNetworkAddress, validation.StringInSlice([]string{networkfirewall.StatefulRuleDirectionAny}, false), @@ -224,7 +216,6 @@ func resourceAwsNetworkFirewallRuleGroup() *schema.Resource { "source_port": { Type: schema.TypeString, Required: true, - ForceNew: true, }, }, }, @@ -498,19 +489,20 @@ func resourceAwsNetworkFirewallRuleGroupUpdate(ctx context.Context, d *schema.Re log.Printf("[DEBUG] Updating NetworkFirewall Rule Group %s", arn) if d.HasChanges("description", "rule_group", "rules", "type") { + // Provide updated object with the currently configured fields 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 v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) } - if d.HasChange("rule_group") { - input.RuleGroup = expandNetworkFirewallRuleGroup(d.Get("rule_group").([]interface{})) + if v, ok := d.GetOk("rule_group"); ok { + input.RuleGroup = expandNetworkFirewallRuleGroup(v.([]interface{})) } - if d.HasChange("rules") { - input.Rules = aws.String(d.Get("rules").(string)) + if v, ok := d.GetOk("rules"); ok { + input.Rules = aws.String(v.(string)) } _, err := conn.UpdateRuleGroupWithContext(ctx, input) diff --git a/aws/resource_aws_networkfirewall_rule_group_test.go b/aws/resource_aws_networkfirewall_rule_group_test.go index cee003bf18db..5a03f00387e6 100644 --- a/aws/resource_aws_networkfirewall_rule_group_test.go +++ b/aws/resource_aws_networkfirewall_rule_group_test.go @@ -145,6 +145,9 @@ func TestAccAwsNetworkFirewallRuleGroup_basic_statefulRule(t *testing.T) { "header.0.source_port": "53", "rule_option.#": "1", }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateful_rule.*.rule_option.*", map[string]string{ + "keyword": "sid:1", + }), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -401,6 +404,8 @@ func TestAccAwsNetworkFirewallRuleGroup_rulesSourceAndRuleVariables(t *testing.T }) } +// TestAccAwsNetworkFirewallRuleGroup_updateStatefulRule validates +// in-place updates to a single stateful_rule configuration block func TestAccAwsNetworkFirewallRuleGroup_updateStatefulRule(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_networkfirewall_rule_group.test" @@ -443,6 +448,147 @@ func TestAccAwsNetworkFirewallRuleGroup_updateStatefulRule(t *testing.T) { }) } +// TestAccAwsNetworkFirewallRuleGroup_updateMultipleStatefulRules validates +// in-place updates to stateful_rule configuration blocks +// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/16868 +func TestAccAwsNetworkFirewallRuleGroup_updateMultipleStatefulRules(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_networkfirewall_rule_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAwsNetworkFirewall(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNetworkFirewallRuleGroup_basic_statefulRule(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateful_rule.#", "1"), + ), + }, + { + Config: testAccNetworkFirewallRuleGroup_multipleStatefulRules(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateful_rule.#", "2"), + resource.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.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateful_rule.*", map[string]string{ + "action": networkfirewall.StatefulActionAlert, + "header.#": "1", + "header.0.destination": networkfirewall.StatefulRuleDirectionAny, + "header.0.destination_port": networkfirewall.StatefulRuleDirectionAny, + "header.0.direction": networkfirewall.StatefulRuleDirectionAny, + "header.0.protocol": networkfirewall.StatefulRuleProtocolIp, + "header.0.source": networkfirewall.StatefulRuleDirectionAny, + "header.0.source_port": networkfirewall.StatefulRuleDirectionAny, + "rule_option.#": "1", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccNetworkFirewallRuleGroup_updateStatefulRule(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateful_rule.#", "1"), + resource.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, + }, + }, + }) +} + +// TestAccAwsNetworkFirewallRuleGroup_statefulRule_action validates in-place +// updates to the "action" argument within 1 stateful_rule configuration block +// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/16868 +func TestAccAwsNetworkFirewallRuleGroup_statefulRule_action(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_networkfirewall_rule_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAwsNetworkFirewall(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsNetworkFirewallRuleGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNetworkFirewallRuleGroup_statefulRule_action(rName, networkfirewall.StatefulActionAlert), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateful_rule.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateful_rule.*", map[string]string{ + "action": networkfirewall.StatefulActionAlert, + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccNetworkFirewallRuleGroup_statefulRule_action(rName, networkfirewall.StatefulActionPass), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateful_rule.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateful_rule.*", map[string]string{ + "action": networkfirewall.StatefulActionPass, + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccNetworkFirewallRuleGroup_statefulRule_action(rName, networkfirewall.StatefulActionDrop), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsNetworkFirewallRuleGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "rule_group.0.rules_source.0.stateful_rule.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule_group.0.rules_source.0.stateful_rule.*", map[string]string{ + "action": networkfirewall.StatefulActionDrop, + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/16470 func TestAccAwsNetworkFirewallRuleGroup_statefulRule_header(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") @@ -794,6 +940,35 @@ resource "aws_networkfirewall_rule_group" "test" { `, rName) } +func testAccNetworkFirewallRuleGroup_statefulRule_action(rName, action 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 = %q + 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, action) +} + func testAccNetworkFirewallRuleGroup_statefulRule_header(rName, dstPort, srcPort string) string { return fmt.Sprintf(` resource "aws_networkfirewall_rule_group" "test" { @@ -841,10 +1016,52 @@ resource "aws_networkfirewall_rule_group" "test" { source = "124.1.1.24/32" source_port = 1001 } + rule_option { + keyword = "sid:1;rev:2" + } + } + } + } +} +`, rName) +} + +func testAccNetworkFirewallRuleGroup_multipleStatefulRules(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 = "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" } } + stateful_rule { + action = "ALERT" + header { + destination = "ANY" + destination_port = "ANY" + direction = "ANY" + protocol = "IP" + source = "ANY" + source_port = "ANY" + } + rule_option { + keyword = "sid:2" + } + } } } }