diff --git a/aws/resource_aws_wafv2_web_acl.go b/aws/resource_aws_wafv2_web_acl.go index c22269683d79..5915bea9733a 100644 --- a/aws/resource_aws_wafv2_web_acl.go +++ b/aws/resource_aws_wafv2_web_acl.go @@ -338,14 +338,14 @@ func wafv2WebACLRootStatementSchema(level int) *schema.Schema { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "and_statement": wafv2StatementSchema(level - 1), + "and_statement": wafv2StatementSchema(level), "byte_match_statement": wafv2ByteMatchStatementSchema(), "geo_match_statement": wafv2GeoMatchStatementSchema(), "ip_set_reference_statement": wafv2IpSetReferenceStatementSchema(), "managed_rule_group_statement": wafv2ManagedRuleGroupStatementSchema(), - "not_statement": wafv2StatementSchema(level - 1), - "or_statement": wafv2StatementSchema(level - 1), - "rate_based_statement": wafv2RateBasedStatementSchema(level - 1), + "not_statement": wafv2StatementSchema(level), + "or_statement": wafv2StatementSchema(level), + "rate_based_statement": wafv2RateBasedStatementSchema(level), "regex_pattern_set_reference_statement": wafv2RegexPatternSetReferenceStatementSchema(), "rule_group_reference_statement": wafv2RuleGroupReferenceStatementSchema(), "size_constraint_statement": wafv2SizeConstraintSchema(), @@ -429,12 +429,12 @@ func wafv2ScopeDownStatementSchema(level int) *schema.Schema { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "and_statement": wafv2StatementSchema(level - 1), + "and_statement": wafv2StatementSchema(level), "byte_match_statement": wafv2ByteMatchStatementSchema(), "geo_match_statement": wafv2GeoMatchStatementSchema(), "ip_set_reference_statement": wafv2IpSetReferenceStatementSchema(), - "not_statement": wafv2StatementSchema(level - 1), - "or_statement": wafv2StatementSchema(level - 1), + "not_statement": wafv2StatementSchema(level), + "or_statement": wafv2StatementSchema(level), "regex_pattern_set_reference_statement": wafv2RegexPatternSetReferenceStatementSchema(), "size_constraint_statement": wafv2SizeConstraintSchema(), "sqli_match_statement": wafv2SqliMatchStatementSchema(), diff --git a/aws/resource_aws_wafv2_web_acl_test.go b/aws/resource_aws_wafv2_web_acl_test.go index b2f6a6a8b288..af99016fe7c1 100644 --- a/aws/resource_aws_wafv2_web_acl_test.go +++ b/aws/resource_aws_wafv2_web_acl_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfawsresource" "regexp" "testing" @@ -436,6 +437,91 @@ func TestAccAwsWafv2WebACL_Tags(t *testing.T) { }) } +// Reference: https://github.com/terraform-providers/terraform-provider-aws/issues/13862 +func TestAccAwsWafv2WebACL_MaxNestedRateBasedStatements(t *testing.T) { + var v wafv2.WebACL + webACLName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_wafv2_web_acl.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsWafv2WebACLDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsWafv2WebACLConfig_multipleNestedRateBasedStatements(webACLName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsWafv2WebACLExists(resourceName, &v), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, "name", webACLName), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "statement.#": "1", + "statement.0.rate_based_statement.#": "1", + "statement.0.rate_based_statement.0.limit": "300", + "statement.0.rate_based_statement.0.aggregate_key_type": "IP", + "statement.0.rate_based_statement.0.scope_down_statement.#": "1", + "statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.#": "1", + "statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.#": "1", + "statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.#": "1", + "statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.0.statement.#": "2", + "statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.0.statement.0.regex_pattern_set_reference_statement.#": "1", + "statement.0.rate_based_statement.0.scope_down_statement.0.not_statement.0.statement.0.or_statement.0.statement.1.ip_set_reference_statement.#": "1", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccAwsWafv2WebACLImportStateIdFunc(resourceName), + }, + }, + }) +} + +func TestAccAwsWafv2WebACL_MaxNestedOperatorStatements(t *testing.T) { + var v wafv2.WebACL + webACLName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_wafv2_web_acl.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsWafv2WebACLDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsWafv2WebACLConfig_multipleNestedOperatorStatements(webACLName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsWafv2WebACLExists(resourceName, &v), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/webacl/.+$`)), + resource.TestCheckResourceAttr(resourceName, "name", webACLName), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ + "statement.#": "1", + "statement.0.and_statement.#": "1", + "statement.0.and_statement.0.statement.#": "2", + "statement.0.and_statement.0.statement.0.not_statement.#": "1", + "statement.0.and_statement.0.statement.0.not_statement.0.statement.#": "1", + "statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.#": "1", + "statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.0.statement.#": "2", + "statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.0.statement.0.regex_pattern_set_reference_statement.#": "1", + "statement.0.and_statement.0.statement.0.not_statement.0.statement.0.or_statement.0.statement.1.ip_set_reference_statement.#": "1", + "statement.0.and_statement.0.statement.1.geo_match_statement.#": "1", + "statement.0.and_statement.0.statement.1.geo_match_statement.0.country_codes.0": "NL", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccAwsWafv2WebACLImportStateIdFunc(resourceName), + }, + }, + }) +} + // Calculates the index which isn't static because ARN is generated as part of the test func computeWafv2RuleGroupRefStatementIndex(r *wafv2.WebACL, idx *int, e []interface{}) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -1108,6 +1194,174 @@ resource "aws_wafv2_web_acl" "test" { `, name, tag1Key, tag1Value, tag2Key, tag2Value) } +func testAccAwsWafv2WebACLConfig_multipleNestedRateBasedStatements(name string) string { + return fmt.Sprintf(` +resource "aws_wafv2_regex_pattern_set" "test" { + name = "%[1]s" + scope = "REGIONAL" + regular_expression { + regex_string = "one" + } +} + +resource "aws_wafv2_ip_set" "test" { + name = "%[1]s" + scope = "REGIONAL" + ip_address_version = "IPV4" + addresses = ["1.2.3.4/32", "5.6.7.8/32"] +} + +resource "aws_wafv2_web_acl" "test" { + name = "%[1]s" + description = "%[1]s" + scope = "REGIONAL" + + default_action { + allow {} + } + + rule { + name = "rule" + priority = 0 + + action { + block {} + } + + statement { + rate_based_statement { + limit = 300 + aggregate_key_type = "IP" + + scope_down_statement { + not_statement { + statement { + or_statement { + statement { + regex_pattern_set_reference_statement { + arn = aws_wafv2_regex_pattern_set.test.arn + field_to_match { + uri_path {} + } + text_transformation { + type = "LOWERCASE" + priority = 1 + } + } + } + statement { + ip_set_reference_statement { + arn = aws_wafv2_ip_set.test.arn + } + } + } + } + } + } + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "rule" + sampled_requests_enabled = false + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "waf" + sampled_requests_enabled = false + } +} +`, name) +} + +func testAccAwsWafv2WebACLConfig_multipleNestedOperatorStatements(name string) string { + return fmt.Sprintf(` +resource "aws_wafv2_regex_pattern_set" "test" { + name = "%[1]s" + scope = "REGIONAL" + regular_expression { + regex_string = "one" + } +} + +resource "aws_wafv2_ip_set" "test" { + name = "%[1]s" + scope = "REGIONAL" + ip_address_version = "IPV4" + addresses = ["1.2.3.4/32", "5.6.7.8/32"] +} + +resource "aws_wafv2_web_acl" "test" { + name = "%[1]s" + description = "%[1]s" + scope = "REGIONAL" + + default_action { + allow {} + } + + rule { + name = "rule" + priority = 0 + + action { + block {} + } + + statement { + and_statement { + statement { + not_statement { + statement { + or_statement { + statement { + regex_pattern_set_reference_statement { + arn = aws_wafv2_regex_pattern_set.test.arn + field_to_match { + uri_path {} + } + text_transformation { + type = "LOWERCASE" + priority = 1 + } + } + } + statement { + ip_set_reference_statement { + arn = aws_wafv2_ip_set.test.arn + } + } + } + } + } + } + statement { + geo_match_statement { + country_codes = ["NL"] + } + } + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "rule" + sampled_requests_enabled = false + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "waf" + sampled_requests_enabled = false + } +} +`, name) +} + func testAccAwsWafv2WebACLImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { return func(s *terraform.State) (string, error) { rs, ok := s.RootModule().Resources[resourceName]