diff --git a/aws/resource_aws_wafv2_web_acl_logging_configuration.go b/aws/resource_aws_wafv2_web_acl_logging_configuration.go index 03d229372bf..d460f7fb562 100644 --- a/aws/resource_aws_wafv2_web_acl_logging_configuration.go +++ b/aws/resource_aws_wafv2_web_acl_logging_configuration.go @@ -93,7 +93,8 @@ func resourceAwsWafv2WebACLLoggingConfiguration() *schema.Resource { "uri_path": wafv2EmptySchema(), }, }, - Description: "Parts of the request to exclude from logs", + Description: "Parts of the request to exclude from logs", + DiffSuppressFunc: suppressEquivalentRedactedFields, }, "resource_arn": { Type: schema.TypeString, @@ -106,6 +107,62 @@ func resourceAwsWafv2WebACLLoggingConfiguration() *schema.Resource { } } +// suppressEquivalentRedactedFields is required to +// handle shifts in List ordering returned from the API +func suppressEquivalentRedactedFields(k, old, new string, d *schema.ResourceData) bool { + o, n := d.GetChange("redacted_fields") + if o != nil && n != nil { + oldFields := o.([]interface{}) + newFields := n.([]interface{}) + if len(oldFields) != len(newFields) { + // account for case where the empty block {} is used as input + return !d.IsNewResource() && len(oldFields) == 0 && len(newFields) == 1 && newFields[0] == nil + } + + for _, oldField := range oldFields { + om := oldField.(map[string]interface{}) + found := false + for _, newField := range newFields { + nm := newField.(map[string]interface{}) + if len(om) != len(nm) { + continue + } + for k, newVal := range nm { + if oldVal, ok := om[k]; ok { + if k == "method" || k == "query_string" || k == "uri_path" { + if len(oldVal.([]interface{})) == len(newVal.([]interface{})) { + found = true + break + } + } else if k == "single_header" { + oldHeader := oldVal.([]interface{}) + newHeader := newVal.([]interface{}) + if len(oldHeader) > 0 && oldHeader[0] != nil { + if len(newHeader) > 0 && newHeader[0] != nil { + oldName := oldVal.([]interface{})[0].(map[string]interface{})["name"].(string) + newName := newVal.([]interface{})[0].(map[string]interface{})["name"].(string) + if oldName == newName { + found = true + break + } + } + } + } + } + } + if found { + break + } + } + if !found { + return false + } + } + return true + } + return false +} + func resourceAwsWafv2WebACLLoggingConfigurationPut(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).wafv2conn diff --git a/aws/resource_aws_wafv2_web_acl_logging_configuration_test.go b/aws/resource_aws_wafv2_web_acl_logging_configuration_test.go index 423847c0753..00bcf512af0 100644 --- a/aws/resource_aws_wafv2_web_acl_logging_configuration_test.go +++ b/aws/resource_aws_wafv2_web_acl_logging_configuration_test.go @@ -223,6 +223,72 @@ func TestAccAwsWafv2WebACLLoggingConfiguration_updateUriPathRedactedField(t *tes }) } +// Reference: https://github.com/terraform-providers/terraform-provider-aws/issues/14248 +func TestAccAwsWafv2WebACLLoggingConfiguration_updateMultipleRedactedFields(t *testing.T) { + var v wafv2.LoggingConfiguration + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_wafv2_web_acl_logging_configuration.test" + webACLResourceName := "aws_wafv2_web_acl.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsWafv2WebACLLoggingConfigurationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsWafv2WebACLLoggingConfiguration_updateRedactedField(rName, "uri_path"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsWafv2WebACLLoggingConfigurationExists(resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, "resource_arn", webACLResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "log_destination_configs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "redacted_fields.#", "1"), + tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "redacted_fields.*", map[string]string{ + "uri_path.#": "1", + }), + ), + }, + { + Config: testAccAwsWafv2WebACLLoggingConfiguration_updateTwoRedactedFields(rName, "uri_path", "method"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsWafv2WebACLLoggingConfigurationExists(resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, "resource_arn", webACLResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "log_destination_configs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "redacted_fields.#", "2"), + tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "redacted_fields.*", map[string]string{ + "uri_path.#": "1", + }), + tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "redacted_fields.*", map[string]string{ + "method.#": "1", + }), + ), + }, + { + Config: testAccAwsWafv2WebACLLoggingConfiguration_updateThreeRedactedFields(rName, "uri_path", "query_string"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsWafv2WebACLLoggingConfigurationExists(resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, "resource_arn", webACLResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "log_destination_configs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "redacted_fields.#", "3"), + tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "redacted_fields.*", map[string]string{ + "uri_path.#": "1", + }), + tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "redacted_fields.*", map[string]string{ + "query_string.#": "1", + }), + tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "redacted_fields.*", map[string]string{ + "single_header.0.name": "user-agent", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAwsWafv2WebACLLoggingConfiguration_changeResourceARNForceNew(t *testing.T) { var before, after wafv2.LoggingConfiguration rName := acctest.RandomWithPrefix("tf-acc-test") @@ -357,6 +423,47 @@ func TestAccAwsWafv2WebACLLoggingConfiguration_emptyRedactedFields(t *testing.T) }) } +func TestAccAwsWafv2WebACLLoggingConfiguration_updateEmptyRedactedFields(t *testing.T) { + var v wafv2.LoggingConfiguration + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_wafv2_web_acl_logging_configuration.test" + webACLResourceName := "aws_wafv2_web_acl.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsWafv2WebACLLoggingConfigurationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsWafv2WebACLLoggingConfiguration_emptyRedactedField(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsWafv2WebACLLoggingConfigurationExists(resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, "resource_arn", webACLResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "log_destination_configs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "redacted_fields.#", "0"), + ), + }, + { + Config: testAccAwsWafv2WebACLLoggingConfiguration_updateRedactedField(rName, "uri_path"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsWafv2WebACLLoggingConfigurationExists(resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, "resource_arn", webACLResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "log_destination_configs.#", "1"), + resource.TestCheckResourceAttr(resourceName, "redacted_fields.#", "1"), + tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "redacted_fields.*", map[string]string{ + "uri_path.#": "1", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAwsWafv2WebACLLoggingConfiguration_webACLDisappears(t *testing.T) { var v wafv2.LoggingConfiguration rName := acctest.RandomWithPrefix("tf-acc-test") @@ -557,7 +664,7 @@ const testAccWebACLLoggingConfigurationResource_emptyRedactedFieldsConfig = ` resource "aws_wafv2_web_acl_logging_configuration" "test" { resource_arn = aws_wafv2_web_acl.test.arn log_destination_configs = [aws_kinesis_firehose_delivery_stream.test.arn] - redacted_fields = {} + redacted_fields {} } ` @@ -606,6 +713,46 @@ resource "aws_wafv2_web_acl_logging_configuration" "test" { `, field) } +func testAccWebACLLoggingConfigurationResource_updateTwoRedactedFieldsConfig(field1, field2 string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl_logging_configuration" "test" { + resource_arn = aws_wafv2_web_acl.test.arn + log_destination_configs = [aws_kinesis_firehose_delivery_stream.test.arn] + + redacted_fields { + %s {} + } + + redacted_fields { + %s {} + } +} +`, field1, field2) +} + +func testAccWebACLLoggingConfigurationResource_updateThreeRedactedFieldsConfig(field1, field2 string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl_logging_configuration" "test" { + resource_arn = aws_wafv2_web_acl.test.arn + log_destination_configs = [aws_kinesis_firehose_delivery_stream.test.arn] + + redacted_fields { + %s {} + } + + redacted_fields { + %s {} + } + + redacted_fields { + single_header { + name = "user-agent" + } + } +} +`, field1, field2) +} + func testAccAwsWafv2WebACLLoggingConfiguration_basic(rName string) string { return composeConfig( testAccWebACLLoggingConfigurationDependenciesConfig(rName), @@ -641,6 +788,20 @@ func testAccAwsWafv2WebACLLoggingConfiguration_updateRedactedField(rName, field testAccWebACLLoggingConfigurationResource_updateRedactedFieldConfig(field)) } +func testAccAwsWafv2WebACLLoggingConfiguration_updateTwoRedactedFields(rName, field1, field2 string) string { + return composeConfig( + testAccWebACLLoggingConfigurationDependenciesConfig(rName), + testAccWebACLLoggingConfigurationKinesisDependencyConfig(rName), + testAccWebACLLoggingConfigurationResource_updateTwoRedactedFieldsConfig(field1, field2)) +} + +func testAccAwsWafv2WebACLLoggingConfiguration_updateThreeRedactedFields(rName, field1, field2 string) string { + return composeConfig( + testAccWebACLLoggingConfigurationDependenciesConfig(rName), + testAccWebACLLoggingConfigurationKinesisDependencyConfig(rName), + testAccWebACLLoggingConfigurationResource_updateThreeRedactedFieldsConfig(field1, field2)) +} + func testAccAwsWafv2WebACLLoggingConfiguration_emptyRedactedField(rName string) string { return composeConfig( testAccWebACLLoggingConfigurationDependenciesConfig(rName),