diff --git a/internal/services/network/web_application_firewall_policy_resource.go b/internal/services/network/web_application_firewall_policy_resource.go index dc2f834dba84..16e2e0ce25b5 100644 --- a/internal/services/network/web_application_firewall_policy_resource.go +++ b/internal/services/network/web_application_firewall_policy_resource.go @@ -168,6 +168,7 @@ func resourceWebApplicationFirewallPolicy() *pluginsdk.Resource { Required: true, ValidateFunc: validation.StringInSlice([]string{ string(webapplicationfirewallpolicies.WebApplicationFirewallRuleTypeMatchRule), + string(webapplicationfirewallpolicies.WebApplicationFirewallRuleTypeRateLimitRule), string(webapplicationfirewallpolicies.WebApplicationFirewallRuleTypeInvalid), }, false), }, @@ -175,6 +176,22 @@ func resourceWebApplicationFirewallPolicy() *pluginsdk.Resource { Type: pluginsdk.TypeString, Optional: true, }, + "rate_limit_duration": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(webapplicationfirewallpolicies.PossibleValuesForApplicationGatewayFirewallRateLimitDuration(), false), + }, + "rate_limit_threshold": { + Type: pluginsdk.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "group_rate_limit_by": { + // group variables combination not supported yet, use a single variable name + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(webapplicationfirewallpolicies.PossibleValuesForApplicationGatewayFirewallUserSessionVariable(), false), + }, }, }, }, @@ -349,6 +366,7 @@ func resourceWebApplicationFirewallPolicy() *pluginsdk.Resource { Optional: true, Default: true, }, + "mode": { Type: pluginsdk.TypeString, Optional: true, @@ -358,23 +376,34 @@ func resourceWebApplicationFirewallPolicy() *pluginsdk.Resource { }, false), Default: string(webapplicationfirewallpolicies.WebApplicationFirewallModePrevention), }, + "request_body_check": { Type: pluginsdk.TypeBool, Optional: true, Default: true, }, + "file_upload_limit_in_mb": { Type: pluginsdk.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(1, 4000), Default: 100, }, + "max_request_body_size_in_kb": { Type: pluginsdk.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(8, 2000), Default: 128, }, + + "request_body_inspect_limit_in_kb": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 128, + ValidateFunc: validation.IntAtLeast(0), + }, + "log_scrubbing": { Type: pluginsdk.TypeList, MaxItems: 1, @@ -618,6 +647,27 @@ func expandWebApplicationFirewallPolicyWebApplicationFirewallCustomRule(input [] RuleType: webapplicationfirewallpolicies.WebApplicationFirewallRuleType(ruleType), } + if rateLimitDuration, ok := v["rate_limit_duration"]; ok && rateLimitDuration.(string) != "" { + result.RateLimitDuration = pointer.To(webapplicationfirewallpolicies.ApplicationGatewayFirewallRateLimitDuration(rateLimitDuration.(string))) + } + + if rateLimitThreshHold, ok := v["rate_limit_threshold"]; ok && rateLimitThreshHold.(int) > 0 { + result.RateLimitThreshold = pointer.To(int64(rateLimitThreshHold.(int))) + } + + if groupBy, ok := v["group_rate_limit_by"]; ok && groupBy.(string) != "" { + groups := []webapplicationfirewallpolicies.GroupByUserSession{ + { + GroupByVariables: []webapplicationfirewallpolicies.GroupByVariable{ + { + VariableName: webapplicationfirewallpolicies.ApplicationGatewayFirewallUserSessionVariable(groupBy.(string)), + }, + }, + }, + } + result.GroupByUserSession = &groups + } + results = append(results, result) } return &results @@ -639,13 +689,15 @@ func expandWebApplicationFirewallPolicyPolicySettings(input []interface{}) *weba fileUploadLimitInMb := v["file_upload_limit_in_mb"].(int) result := webapplicationfirewallpolicies.PolicySettings{ - State: pointer.To(enabled), - Mode: pointer.To(webapplicationfirewallpolicies.WebApplicationFirewallMode(mode)), - RequestBodyCheck: pointer.To(requestBodyCheck), - MaxRequestBodySizeInKb: pointer.To(int64(maxRequestBodySizeInKb)), - FileUploadLimitInMb: pointer.To(int64(fileUploadLimitInMb)), - LogScrubbing: expandWebApplicationFirewallPolicyLogScrubbing(v["log_scrubbing"].([]interface{})), + State: pointer.To(enabled), + Mode: pointer.To(webapplicationfirewallpolicies.WebApplicationFirewallMode(mode)), + RequestBodyCheck: pointer.To(requestBodyCheck), + MaxRequestBodySizeInKb: pointer.To(int64(maxRequestBodySizeInKb)), + FileUploadLimitInMb: pointer.To(int64(fileUploadLimitInMb)), + LogScrubbing: expandWebApplicationFirewallPolicyLogScrubbing(v["log_scrubbing"].([]interface{})), + RequestBodyInspectLimitInKB: pointer.To(int64(v["request_body_inspect_limit_in_kb"].(int))), } + return &result } @@ -961,6 +1013,14 @@ func flattenWebApplicationFirewallPolicyWebApplicationFirewallCustomRule(input * v["match_conditions"] = flattenWebApplicationFirewallPolicyMatchCondition(item.MatchConditions) v["priority"] = int(item.Priority) v["rule_type"] = string(item.RuleType) + v["rate_limit_duration"] = pointer.From(item.RateLimitDuration) + v["rate_limit_threshold"] = pointer.From(item.RateLimitThreshold) + + if item.GroupByUserSession != nil && len(*item.GroupByUserSession) > 0 { + if groupVariable := (*item.GroupByUserSession)[0].GroupByVariables; len(groupVariable) > 0 { + v["group_rate_limit_by"] = groupVariable[0].VariableName + } + } results = append(results, v) } @@ -981,6 +1041,7 @@ func flattenWebApplicationFirewallPolicyPolicySettings(input *webapplicationfire result["max_request_body_size_in_kb"] = int(pointer.From(input.MaxRequestBodySizeInKb)) result["file_upload_limit_in_mb"] = int(pointer.From(input.FileUploadLimitInMb)) result["log_scrubbing"] = flattenWebApplicationFirewallPolicyLogScrubbing(input.LogScrubbing) + result["request_body_inspect_limit_in_kb"] = pointer.From(input.RequestBodyInspectLimitInKB) return []interface{}{result} } diff --git a/internal/services/network/web_application_firewall_policy_resource_test.go b/internal/services/network/web_application_firewall_policy_resource_test.go index bd2231aa5e87..d93ccd47433d 100644 --- a/internal/services/network/web_application_firewall_policy_resource_test.go +++ b/internal/services/network/web_application_firewall_policy_resource_test.go @@ -226,6 +226,35 @@ func TestAccWebApplicationFirewallPolicy_updateOverrideRules(t *testing.T) { }) } +func TestAccWebApplicationFirewallPolicy_rateLimit(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_web_application_firewall_policy", "test") + r := WebApplicationFirewallResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.rateLimit(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.rateLimitUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.rateLimit(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccWebApplicationFirewallPolicy_knownCVEs(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_web_application_firewall_policy", "test") r := WebApplicationFirewallResource{} @@ -513,6 +542,174 @@ resource "azurerm_web_application_firewall_policy" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } +func (WebApplicationFirewallResource) rateLimit(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_web_application_firewall_policy" "test" { + name = "acctestwafpolicy-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + + tags = { + env = "test" + } + + custom_rules { + name = "Rule1" + priority = 1 + rule_type = "RateLimitRule" + rate_limit_duration = "FiveMins" + rate_limit_threshold = 100 + group_rate_limit_by = "ClientAddr" + + match_conditions { + match_variables { + variable_name = "RemoteAddr" + } + + operator = "IPMatch" + negation_condition = false + match_values = ["192.168.1.0/24", "10.0.0.0/24"] + } + + action = "Block" + } + + managed_rules { + exclusion { + match_variable = "RequestHeaderNames" + selector = "x-shared-secret" + selector_match_operator = "Equals" + } + + exclusion { + match_variable = "RequestCookieNames" + selector = "too-much-fun" + selector_match_operator = "EndsWith" + } + + managed_rule_set { + type = "OWASP" + version = "3.2" + + rule_group_override { + rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT" + rule { + id = "920300" + enabled = true + action = "Log" + } + + rule { + id = "920440" + enabled = true + action = "Block" + } + } + } + } + + policy_settings { + enabled = true + mode = "Prevention" + request_body_inspect_limit_in_kb = 1000 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func (WebApplicationFirewallResource) rateLimitUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_web_application_firewall_policy" "test" { + name = "acctestwafpolicy-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + + tags = { + env = "test" + } + + custom_rules { + name = "Rule1" + priority = 1 + rule_type = "RateLimitRule" + rate_limit_duration = "OneMin" + rate_limit_threshold = 123 + group_rate_limit_by = "GeoLocation" + + match_conditions { + match_variables { + variable_name = "RemoteAddr" + } + + operator = "IPMatch" + negation_condition = false + match_values = ["192.168.1.0/24", "10.0.0.0/24"] + } + + action = "Block" + } + + managed_rules { + exclusion { + match_variable = "RequestHeaderNames" + selector = "x-shared-secret" + selector_match_operator = "Equals" + } + + exclusion { + match_variable = "RequestCookieNames" + selector = "too-much-fun" + selector_match_operator = "EndsWith" + } + + managed_rule_set { + type = "OWASP" + version = "3.2" + + rule_group_override { + rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT" + rule { + id = "920300" + enabled = true + action = "Log" + } + + rule { + id = "920440" + enabled = true + action = "Block" + } + } + } + } + + policy_settings { + enabled = true + mode = "Prevention" + request_body_inspect_limit_in_kb = 1234 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + func (WebApplicationFirewallResource) updateOverrideRules(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/web_application_firewall_policy.html.markdown b/website/docs/r/web_application_firewall_policy.html.markdown index 303aeb6c6d27..6f84692021ff 100644 --- a/website/docs/r/web_application_firewall_policy.html.markdown +++ b/website/docs/r/web_application_firewall_policy.html.markdown @@ -140,12 +140,18 @@ The `custom_rules` block supports the following: * `priority` - (Required) Describes priority of the rule. Rules with a lower value will be evaluated before rules with a higher value. -* `rule_type` - (Required) Describes the type of rule. Possible values are `MatchRule` and `Invalid`. +* `rule_type` - (Required) Describes the type of rule. Possible values are `MatchRule`, `RateLimitRule` and `Invalid`. * `match_conditions` - (Required) One or more `match_conditions` blocks as defined below. * `action` - (Required) Type of action. Possible values are `Allow`, `Block` and `Log`. +* `rate_limit_duration` - (Optional) Specifies the duration at which the rate limit policy will be applied. Should be used with `RateLimitRule` rule type. Possible values are `FiveMins` and `OneMin`. + +* `rate_limit_threshold` - (Optional) Specifies the threshold value for the rate limit policy. Must be greater than or equal to 1 if provided. + +* `group_rate_limit_by` - (Optional) Specifies what grouping the rate limit will count requests by. Possible values are `GeoLocation`, `ClientAddr` and `None`. + --- The `match_conditions` block supports the following: @@ -184,6 +190,8 @@ The `policy_settings` block supports the following: * `log_scrubbing` - (Optional) One `log_scrubbing` block as defined below. +* `request_body_inspect_limit_in_kb` - (Optional) Specifies the maximum request body inspection limit in KB for the Web Application Firewall. Defaults to `128`. + --- The `managed_rules` block supports the following: