diff --git a/.changelog/31111.txt b/.changelog/31111.txt new file mode 100644 index 00000000000..d49ae66cf16 --- /dev/null +++ b/.changelog/31111.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_wafv2_web_acl: Fixed error handling `response_inspection` parameters +``` diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 7b673d038ff..b0e3e602e2d 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -411,6 +411,27 @@ func MatchResourceAttrRegionalARN(resourceName, attributeName, arnService string } } +// MatchResourceAttrRegionalARNRegion ensures the Terraform state regexp matches a formatted ARN with the specified region +func MatchResourceAttrRegionalARNRegion(resourceName, attributeName, arnService, region string, arnResourceRegexp *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + arnRegexp := arn.ARN{ + AccountID: AccountID(), + Partition: Partition(), + Region: region, + Resource: arnResourceRegexp.String(), + Service: arnService, + }.String() + + attributeMatch, err := regexp.Compile(arnRegexp) + + if err != nil { + return fmt.Errorf("unable to compile ARN regexp (%s): %w", arnRegexp, err) + } + + return resource.TestMatchResourceAttr(resourceName, attributeName, attributeMatch)(s) + } +} + // MatchResourceAttrRegionalARNNoAccount ensures the Terraform state regexp matches a formatted ARN with region but without account ID func MatchResourceAttrRegionalARNNoAccount(resourceName, attributeName, arnService string, arnResourceRegexp *regexp.Regexp) resource.TestCheckFunc { return func(s *terraform.State) error { diff --git a/internal/service/wafv2/flex.go b/internal/service/wafv2/flex.go index c27fbd4d704..5a6b5a428a2 100644 --- a/internal/service/wafv2/flex.go +++ b/internal/service/wafv2/flex.go @@ -2309,7 +2309,7 @@ func flattenBodyContains(apiObject *wafv2.ResponseInspectionBodyContains) []inte m := map[string]interface{}{ "failure_strings": flex.FlattenStringSet(apiObject.FailureStrings), - "succeed_strings": flex.FlattenStringSet(apiObject.SuccessStrings), + "success_strings": flex.FlattenStringSet(apiObject.SuccessStrings), } return []interface{}{m} @@ -2322,7 +2322,7 @@ func flattenHeader(apiObject *wafv2.ResponseInspectionHeader) []interface{} { m := map[string]interface{}{ "failure_values": flex.FlattenStringSet(apiObject.FailureValues), - "succeed_values": flex.FlattenStringSet(apiObject.SuccessValues), + "success_values": flex.FlattenStringSet(apiObject.SuccessValues), } return []interface{}{m} @@ -2336,7 +2336,7 @@ func flattenResponseInspectionJSON(apiObject *wafv2.ResponseInspectionJson) []in m := map[string]interface{}{ "failure_values": flex.FlattenStringSet(apiObject.FailureValues), "identifier": aws.StringValue(apiObject.Identifier), - "succeed_values": flex.FlattenStringSet(apiObject.SuccessValues), + "success_values": flex.FlattenStringSet(apiObject.SuccessValues), } return []interface{}{m} diff --git a/internal/service/wafv2/web_acl_test.go b/internal/service/wafv2/web_acl_test.go index 8afd137c4d8..d0485cd0fbf 100644 --- a/internal/service/wafv2/web_acl_test.go +++ b/internal/service/wafv2/web_acl_test.go @@ -11,7 +11,9 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/service/wafv2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -28,6 +30,7 @@ func init() { func testAccErrorCheckSkip(t *testing.T) resource.ErrorCheckFunc { return acctest.ErrorCheckSkipMessagesContaining(t, "Your request contains fields that belong to a feature you are not allowed to use", + "The scope is not valid., field: SCOPE_VALUE, parameter: CLOUDFRONT", ) } @@ -2388,15 +2391,74 @@ func TestAccWAFV2WebACL_tokenDomains(t *testing.T) { }) } +func TestAccWAFV2WebACL_CloudFrontScope(t *testing.T) { + ctx := acctest.Context(t) + var v wafv2.WebACL + webACLName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl.test" + + providers := make(map[string]*schema.Provider) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, wafv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesNamed(ctx, t, providers, acctest.ProviderName), + CheckDestroy: acctest.CheckWithNamedProviders(testAccCheckWebACLDestroyWithProvider(ctx), providers), + Steps: []resource.TestStep{ + { + Config: testAccWebACLConfig_CloudFrontScope(webACLName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckWebACLExistsWithProvider(ctx, resourceName, &v, acctest.NamedProviderFunc(acctest.ProviderName, providers)), + acctest.MatchResourceAttrRegionalARNRegion(resourceName, "arn", "wafv2", testAccCloudFrontScopeRegion(), regexp.MustCompile(`global/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, "name", webACLName), + resource.TestCheckResourceAttr(resourceName, "scope", wafv2.ScopeCloudfront), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "name": "rule-1", + "statement.#": "1", + "statement.0.managed_rule_group_statement.#": "1", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.#": "1", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.login_path": "/api/1/signin", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.request_inspection.#": "1", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.request_inspection.0.password_field.#": "1", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.request_inspection.0.password_field.0.identifier": "/password", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.request_inspection.0.payload_type": "JSON", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.request_inspection.0.username_field.#": "1", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.request_inspection.0.username_field.0.identifier": "/username", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.response_inspection.#": "1", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.response_inspection.0.body_contains.#": "1", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.response_inspection.0.body_contains.0.success_strings.#": "1", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.response_inspection.0.body_contains.0.success_strings.0": "Login successful", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.response_inspection.0.body_contains.0.failure_strings.#": "1", + "statement.0.managed_rule_group_statement.0.managed_rule_group_configs.0.aws_managed_rules_atp_rule_set.0.response_inspection.0.body_contains.0.failure_strings.0": "Login failed", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLImportStateIdFunc(resourceName), + }, + }, + }) +} + func testAccCheckWebACLDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { + return testAccCheckWebACLDestroyWithProvider(ctx)(s, acctest.Provider) + } +} + +func testAccCheckWebACLDestroyWithProvider(ctx context.Context) acctest.TestCheckWithProviderFunc { + return func(s *terraform.State, provider *schema.Provider) error { + conn := provider.Meta().(*conns.AWSClient).WAFV2Conn(ctx) + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_wafv2_web_acl" { continue } - conn := acctest.Provider.Meta().(*conns.AWSClient).WAFV2Conn(ctx) - _, err := tfwafv2.FindWebACLByThreePartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["name"], rs.Primary.Attributes["scope"]) if tfresource.NotFound(err) { @@ -2415,6 +2477,10 @@ func testAccCheckWebACLDestroy(ctx context.Context) resource.TestCheckFunc { } func testAccCheckWebACLExists(ctx context.Context, n string, v *wafv2.WebACL) resource.TestCheckFunc { + return testAccCheckWebACLExistsWithProvider(ctx, n, v, func() *schema.Provider { return acctest.Provider }) +} + +func testAccCheckWebACLExistsWithProvider(ctx context.Context, n string, v *wafv2.WebACL, providerF func() *schema.Provider) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -2425,7 +2491,7 @@ func testAccCheckWebACLExists(ctx context.Context, n string, v *wafv2.WebACL) re return fmt.Errorf("No WAFv2 WebACL ID is set") } - conn := acctest.Provider.Meta().(*conns.AWSClient).WAFV2Conn(ctx) + conn := providerF().Meta().(*conns.AWSClient).WAFV2Conn(ctx) output, err := tfwafv2.FindWebACLByThreePartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["name"], rs.Primary.Attributes["scope"]) @@ -4653,3 +4719,82 @@ resource "aws_wafv2_web_acl" "test" { } `, name, domain1, domain2) } + +func testAccCloudFrontScopeRegion() string { + switch acctest.Partition() { + case endpoints.AwsPartitionID: + return endpoints.UsEast1RegionID + case endpoints.AwsCnPartitionID: + return endpoints.CnNorthwest1RegionID + default: + return acctest.Region() + } +} + +func testAccCloudFrontScopeProviderConfig() string { + return acctest.ConfigRegionalProvider(testAccCloudFrontScopeRegion()) +} + +func testAccWebACLConfig_CloudFrontScope(name string) string { + return acctest.ConfigCompose( + testAccCloudFrontScopeProviderConfig(), + fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + description = %[1]q + scope = "CLOUDFRONT" + + default_action { + allow {} + } + + rule { + name = "rule-1" + priority = 1 + + override_action { + count {} + } + + statement { + managed_rule_group_statement { + name = "AWSManagedRulesATPRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_atp_rule_set { + login_path = "/api/1/signin" + request_inspection { + password_field { + identifier = "/password" + } + payload_type = "JSON" + username_field { + identifier = "/username" + } + } + response_inspection { + body_contains { + success_strings = ["Login successful"] + failure_strings = ["Login failed"] + } + } + } + } + } + } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesATPRuleSet_json" + sampled_requests_enabled = true + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "friendly-metric-name" + sampled_requests_enabled = false + } +} +`, name)) +}