Skip to content

Commit 90b5d01

Browse files
authored
Merge pull request #27113 from hashicorp/f-wafv2_rule_group-add_rate_based_statement
aws_wafv2_rule_group: Add rate_based_statement
2 parents 459f11a + f878d7d commit 90b5d01

File tree

7 files changed

+249
-8
lines changed

7 files changed

+249
-8
lines changed

.changelog/27113.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
resource/aws_wafv2_rule_group: Add rate_based_statement
3+
```

internal/service/wafv2/consts.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package wafv2
22

33
const (
4-
rootStatementSchemaLevel = 3
5-
webACLRootStatementSchemaLevel = 3
4+
ruleGroupRootStatementSchemaLevel = 3
5+
webACLRootStatementSchemaLevel = 3
66
)

internal/service/wafv2/flex.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func expandRule(m map[string]interface{}) *wafv2.Rule {
3333
Name: aws.String(m["name"].(string)),
3434
Priority: aws.Int64(int64(m["priority"].(int))),
3535
Action: expandRuleAction(m["action"].([]interface{})),
36-
Statement: expandRootStatement(m["statement"].([]interface{})),
36+
Statement: expandRuleGroupRootStatement(m["statement"].([]interface{})),
3737
VisibilityConfig: expandVisibilityConfig(m["visibility_config"].([]interface{})),
3838
}
3939

@@ -271,7 +271,7 @@ func expandVisibilityConfig(l []interface{}) *wafv2.VisibilityConfig {
271271
return configuration
272272
}
273273

274-
func expandRootStatement(l []interface{}) *wafv2.Statement {
274+
func expandRuleGroupRootStatement(l []interface{}) *wafv2.Statement {
275275
if len(l) == 0 || l[0] == nil {
276276
return nil
277277
}
@@ -333,6 +333,10 @@ func expandStatement(m map[string]interface{}) *wafv2.Statement {
333333
statement.OrStatement = expandOrStatement(v.([]interface{}))
334334
}
335335

336+
if v, ok := m["rate_based_statement"]; ok {
337+
statement.RateBasedStatement = expandRateBasedStatement(v.([]interface{}))
338+
}
339+
336340
if v, ok := m["regex_match_statement"]; ok {
337341
statement.RegexMatchStatement = expandRegexMatchStatement(v.([]interface{}))
338342
}
@@ -1058,7 +1062,7 @@ func flattenRules(r []*wafv2.Rule) interface{} {
10581062
m["name"] = aws.StringValue(rule.Name)
10591063
m["priority"] = int(aws.Int64Value(rule.Priority))
10601064
m["rule_label"] = flattenRuleLabels(rule.RuleLabels)
1061-
m["statement"] = flattenRootStatement(rule.Statement)
1065+
m["statement"] = flattenRuleGroupRootStatement(rule.Statement)
10621066
m["visibility_config"] = flattenVisibilityConfig(rule.VisibilityConfig)
10631067
out[i] = m
10641068
}
@@ -1231,7 +1235,7 @@ func flattenRuleLabels(l []*wafv2.Label) []interface{} {
12311235
return out
12321236
}
12331237

1234-
func flattenRootStatement(s *wafv2.Statement) interface{} {
1238+
func flattenRuleGroupRootStatement(s *wafv2.Statement) interface{} {
12351239
if s == nil {
12361240
return []interface{}{}
12371241
}
@@ -1283,6 +1287,10 @@ func flattenStatement(s *wafv2.Statement) map[string]interface{} {
12831287
m["or_statement"] = flattenOrStatement(s.OrStatement)
12841288
}
12851289

1290+
if s.RateBasedStatement != nil {
1291+
m["rate_based_statement"] = flattenRateBasedStatement(s.RateBasedStatement)
1292+
}
1293+
12861294
if s.RegexMatchStatement != nil {
12871295
m["regex_match_statement"] = flattenRegexMatchStatement(s.RegexMatchStatement)
12881296
}

internal/service/wafv2/rule_group.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func ResourceRuleGroup() *schema.Resource {
107107
Required: true,
108108
},
109109
"rule_label": ruleLabelsSchema(),
110-
"statement": rootStatementSchema(rootStatementSchemaLevel),
110+
"statement": ruleGroupRootStatementSchema(ruleGroupRootStatementSchemaLevel),
111111
"visibility_config": visibilityConfigSchema(),
112112
},
113113
},

internal/service/wafv2/rule_group_test.go

+215
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,98 @@ func TestAccWAFV2RuleGroup_xssMatchStatement(t *testing.T) {
18681868
})
18691869
}
18701870

1871+
func TestAccWAFV2RuleGroup_rateBasedStatement(t *testing.T) {
1872+
var v wafv2.RuleGroup
1873+
ruleGroupName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
1874+
resourceName := "aws_wafv2_rule_group.test"
1875+
1876+
resource.ParallelTest(t, resource.TestCase{
1877+
PreCheck: func() { acctest.PreCheck(t); testAccPreCheckScopeRegional(t) },
1878+
ErrorCheck: acctest.ErrorCheck(t, wafv2.EndpointsID),
1879+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
1880+
CheckDestroy: testAccCheckRuleGroupDestroy,
1881+
Steps: []resource.TestStep{
1882+
{
1883+
Config: testAccRuleGroupConfig_rateBasedStatement(ruleGroupName),
1884+
Check: resource.ComposeTestCheckFunc(
1885+
testAccCheckRuleGroupExists(resourceName, &v),
1886+
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/rulegroup/.+$`)),
1887+
resource.TestCheckResourceAttr(resourceName, "rule.#", "1"),
1888+
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{
1889+
"statement.#": "1",
1890+
"statement.0.rate_based_statement.0.aggregate_key_type": "IP",
1891+
"statement.0.rate_based_statement.0.forwarded_ip_config.#": "0",
1892+
"statement.0.rate_based_statement.0.limit": "50000",
1893+
"statement.0.rate_based_statement.0.scope_down_statement.#": "0",
1894+
}),
1895+
),
1896+
},
1897+
{
1898+
Config: testAccRuleGroupConfig_rateBasedStatement_forwardedIPConfig(ruleGroupName, "MATCH", "X-Forwarded-For"),
1899+
Check: resource.ComposeTestCheckFunc(
1900+
testAccCheckRuleGroupExists(resourceName, &v),
1901+
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/rulegroup/.+$`)),
1902+
resource.TestCheckResourceAttr(resourceName, "rule.#", "1"),
1903+
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{
1904+
"statement.#": "1",
1905+
"statement.0.rate_based_statement.#": "1",
1906+
"statement.0.rate_based_statement.0.aggregate_key_type": "FORWARDED_IP",
1907+
"statement.0.rate_based_statement.0.forwarded_ip_config.#": "1",
1908+
"statement.0.rate_based_statement.0.forwarded_ip_config.0.fallback_behavior": "MATCH",
1909+
"statement.0.rate_based_statement.0.forwarded_ip_config.0.header_name": "X-Forwarded-For",
1910+
"statement.0.rate_based_statement.0.limit": "50000",
1911+
"statement.0.rate_based_statement.0.scope_down_statement.#": "0",
1912+
}),
1913+
),
1914+
},
1915+
{
1916+
Config: testAccRuleGroupConfig_rateBasedStatement_forwardedIPConfig(ruleGroupName, "NO_MATCH", "Updated"),
1917+
Check: resource.ComposeTestCheckFunc(
1918+
testAccCheckRuleGroupExists(resourceName, &v),
1919+
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/rulegroup/.+$`)),
1920+
resource.TestCheckResourceAttr(resourceName, "rule.#", "1"),
1921+
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{
1922+
"statement.#": "1",
1923+
"statement.0.rate_based_statement.#": "1",
1924+
"statement.0.rate_based_statement.0.aggregate_key_type": "FORWARDED_IP",
1925+
"statement.0.rate_based_statement.0.forwarded_ip_config.#": "1",
1926+
"statement.0.rate_based_statement.0.forwarded_ip_config.0.fallback_behavior": "NO_MATCH",
1927+
"statement.0.rate_based_statement.0.forwarded_ip_config.0.header_name": "Updated",
1928+
"statement.0.rate_based_statement.0.limit": "50000",
1929+
"statement.0.rate_based_statement.0.scope_down_statement.#": "0",
1930+
}),
1931+
),
1932+
},
1933+
{
1934+
Config: testAccRuleGroupConfig_rateBasedStatement_update(ruleGroupName),
1935+
Check: resource.ComposeTestCheckFunc(
1936+
testAccCheckRuleGroupExists(resourceName, &v),
1937+
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "wafv2", regexp.MustCompile(`regional/rulegroup/.+$`)),
1938+
resource.TestCheckResourceAttr(resourceName, "rule.#", "1"),
1939+
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{
1940+
"statement.#": "1",
1941+
"statement.0.rate_based_statement.#": "1",
1942+
"statement.0.rate_based_statement.0.aggregate_key_type": "IP",
1943+
"statement.0.rate_based_statement.0.forwarded_ip_config.#": "0",
1944+
"statement.0.rate_based_statement.0.limit": "10000",
1945+
"statement.0.rate_based_statement.0.scope_down_statement.#": "1",
1946+
"statement.0.rate_based_statement.0.scope_down_statement.0.geo_match_statement.#": "1",
1947+
"statement.0.rate_based_statement.0.scope_down_statement.0.geo_match_statement.0.country_codes.#": "2",
1948+
"statement.0.rate_based_statement.0.scope_down_statement.0.geo_match_statement.0.country_codes.0": "US",
1949+
"statement.0.rate_based_statement.0.scope_down_statement.0.geo_match_statement.0.country_codes.1": "NL",
1950+
}),
1951+
),
1952+
},
1953+
{
1954+
ResourceName: resourceName,
1955+
ImportState: true,
1956+
ImportStateVerify: true,
1957+
ImportStateIdFunc: testAccRuleGroupImportStateIdFunc(resourceName),
1958+
},
1959+
},
1960+
})
1961+
}
1962+
18711963
func testAccPreCheckScopeRegional(t *testing.T) {
18721964
conn := acctest.Provider.Meta().(*conns.AWSClient).WAFV2Conn
18731965

@@ -4031,6 +4123,129 @@ resource "aws_wafv2_rule_group" "test" {
40314123
`, name)
40324124
}
40334125

4126+
func testAccRuleGroupConfig_rateBasedStatement(name string) string {
4127+
return fmt.Sprintf(`
4128+
resource "aws_wafv2_rule_group" "test" {
4129+
capacity = 3
4130+
name = "%s"
4131+
scope = "REGIONAL"
4132+
4133+
rule {
4134+
name = "rule-1"
4135+
priority = 1
4136+
4137+
action {
4138+
count {}
4139+
}
4140+
4141+
statement {
4142+
rate_based_statement {
4143+
limit = 50000
4144+
}
4145+
}
4146+
4147+
visibility_config {
4148+
cloudwatch_metrics_enabled = false
4149+
metric_name = "friendly-rule-metric-name"
4150+
sampled_requests_enabled = false
4151+
}
4152+
}
4153+
4154+
visibility_config {
4155+
cloudwatch_metrics_enabled = false
4156+
metric_name = "friendly-metric-name"
4157+
sampled_requests_enabled = false
4158+
}
4159+
}
4160+
`, name)
4161+
}
4162+
4163+
func testAccRuleGroupConfig_rateBasedStatement_forwardedIPConfig(name, fallbackBehavior, headerName string) string {
4164+
return fmt.Sprintf(`
4165+
resource "aws_wafv2_rule_group" "test" {
4166+
capacity = 3
4167+
name = "%s"
4168+
scope = "REGIONAL"
4169+
4170+
rule {
4171+
name = "rule-1"
4172+
priority = 1
4173+
4174+
action {
4175+
count {}
4176+
}
4177+
4178+
statement {
4179+
rate_based_statement {
4180+
aggregate_key_type = "FORWARDED_IP"
4181+
forwarded_ip_config {
4182+
fallback_behavior = %[2]q
4183+
header_name = %[3]q
4184+
}
4185+
limit = 50000
4186+
}
4187+
}
4188+
4189+
visibility_config {
4190+
cloudwatch_metrics_enabled = false
4191+
metric_name = "friendly-rule-metric-name"
4192+
sampled_requests_enabled = false
4193+
}
4194+
}
4195+
4196+
visibility_config {
4197+
cloudwatch_metrics_enabled = false
4198+
metric_name = "friendly-metric-name"
4199+
sampled_requests_enabled = false
4200+
}
4201+
}
4202+
`, name, fallbackBehavior, headerName)
4203+
}
4204+
4205+
func testAccRuleGroupConfig_rateBasedStatement_update(name string) string {
4206+
return fmt.Sprintf(`
4207+
resource "aws_wafv2_rule_group" "test" {
4208+
capacity = 3
4209+
name = "%s"
4210+
scope = "REGIONAL"
4211+
4212+
rule {
4213+
name = "rule-1"
4214+
priority = 1
4215+
4216+
action {
4217+
count {}
4218+
}
4219+
4220+
statement {
4221+
rate_based_statement {
4222+
limit = 10000
4223+
aggregate_key_type = "IP"
4224+
4225+
scope_down_statement {
4226+
geo_match_statement {
4227+
country_codes = ["US", "NL"]
4228+
}
4229+
}
4230+
}
4231+
}
4232+
4233+
visibility_config {
4234+
cloudwatch_metrics_enabled = false
4235+
metric_name = "friendly-rule-metric-name"
4236+
sampled_requests_enabled = false
4237+
}
4238+
}
4239+
4240+
visibility_config {
4241+
cloudwatch_metrics_enabled = false
4242+
metric_name = "friendly-metric-name"
4243+
sampled_requests_enabled = false
4244+
}
4245+
}
4246+
`, name)
4247+
}
4248+
40344249
func testAccRuleGroupConfig_oneTag(name, tagKey, tagValue string) string {
40354250
return fmt.Sprintf(`
40364251
resource "aws_wafv2_rule_group" "test" {

internal/service/wafv2/schemas.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func ruleLabelsSchema() *schema.Schema {
5252
}
5353
}
5454

55-
func rootStatementSchema(level int) *schema.Schema {
55+
func ruleGroupRootStatementSchema(level int) *schema.Schema {
5656
return &schema.Schema{
5757
Type: schema.TypeList,
5858
Required: true,
@@ -66,6 +66,7 @@ func rootStatementSchema(level int) *schema.Schema {
6666
"label_match_statement": labelMatchStatementSchema(),
6767
"not_statement": statementSchema(level - 1),
6868
"or_statement": statementSchema(level - 1),
69+
"rate_based_statement": rateBasedStatementSchema(level - 1),
6970
"regex_match_statement": regexMatchStatementSchema(),
7071
"regex_pattern_set_reference_statement": regexPatternSetReferenceStatementSchema(),
7172
"size_constraint_statement": sizeConstraintSchema(),

website/docs/r/wafv2_rule_group.html.markdown

+14
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ The `statement` block supports the following arguments:
402402
* `ip_set_reference_statement` - (Optional) A rule statement used to detect web requests coming from particular IP addresses or address ranges. See [IP Set Reference Statement](#ip-set-reference-statement) below for details.
403403
* `not_statement` - (Optional) A logical rule statement used to negate the results of another rule statement. See [NOT Statement](#not-statement) below for details.
404404
* `or_statement` - (Optional) A logical rule statement used to combine other rule statements with OR logic. See [OR Statement](#or-statement) below for details.
405+
* `rate_based_statement` - (Optional) A rate-based rule tracks the rate of requests for each originating `IP address`, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any `5-minute` time span. This statement can not be nested. See [Rate Based Statement](#rate-based-statement) below for details.
405406
* `regex_match_statement` - (Optional) A rule statement used to search web request components for a match against a single regular expression. See [Regex Match Statement](#regex-match-statement) below for details.
406407
* `regex_pattern_set_reference_statement` - (Optional) A rule statement used to search web request components for matches with regular expressions. See [Regex Pattern Set Reference Statement](#regex-pattern-set-reference-statement) below for details.
407408
* `size_constraint_statement` - (Optional) A rule statement that compares a number of bytes against the size of a request component, using a comparison operator, such as greater than (>) or less than (<). See [Size Constraint Statement](#size-constraint-statement) below for more details.
@@ -468,6 +469,19 @@ The `or_statement` block supports the following arguments:
468469

469470
* `statement` - (Required) The statements to combine with `OR` logic. You can use any statements that can be nested. See [Statement](#statement) above for details.
470471

472+
### Rate Based Statement
473+
474+
A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action when the rate exceeds a limit that you specify on the number of requests in any 5-minute time span. You can use this to put a temporary block on requests from an IP address that is sending excessive requests. See the [documentation](https://docs.aws.amazon.com/waf/latest/APIReference/API_RateBasedStatement.html) for more information.
475+
476+
You can't nest a `rate_based_statement`, for example for use inside a `not_statement` or `or_statement`. It can only be referenced as a `top-level` statement within a `rule`.
477+
478+
The `rate_based_statement` block supports the following arguments:
479+
480+
* `aggregate_key_type` - (Optional) Setting that indicates how to aggregate the request counts. Valid values include: `FORWARDED_IP` or `IP`. Default: `IP`.
481+
* `forwarded_ip_config` - (Optional) The configuration for inspecting IP addresses in an HTTP header that you specify, instead of using the IP address that's reported by the web request origin. If `aggregate_key_type` is set to `FORWARDED_IP`, this block is required. See [Forwarded IP Config](#forwarded-ip-config) below for details.
482+
* `limit` - (Required) The limit on requests per 5-minute period for a single originating IP address.
483+
* `scope_down_statement` - (Optional) An optional nested statement that narrows the scope of the rate-based statement to matching web requests. This can be any nestable statement, and you can nest statements at any level below this scope-down statement. See [Statement](#statement) above for details.
484+
471485
### Regex Match Statement
472486

473487
A rule statement used to search web request components for a match against a single regular expression.

0 commit comments

Comments
 (0)