diff --git a/.gitignore b/.gitignore index a7c7693..71655d0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.tfstate.* crash.log *.tfvars* +!terraform.tfvars override.tf override.tf.json *_override.tf diff --git a/README.md b/README.md index e66d32e..a89196b 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,10 @@ This project creates and manages the RSD FrontDoor CDN. | [key\_vault\_access\_ipv4](#input\_key\_vault\_access\_ipv4) | List of IPv4 Addresses that are permitted to access the Key Vault | `list(string)` | n/a | yes | | [tags](#input\_tags) | Tags to be applied to all resources | `map(string)` | `{}` | no | | [tfvars\_filename](#input\_tfvars\_filename) | tfvars filename. This file is uploaded and stored encrypted within Key Vault, to ensure that the latest tfvars are stored in a shared place. | `string` | n/a | yes | +| [waf\_custom\_rules](#input\_waf\_custom\_rules) | Map of all Custom rules you want to apply to the WAF |
map(object({
priority : number,
action : string
match_conditions : map(object({
match_variable : string,
match_values : optional(list(string), []),
operator : optional(string, "Any"),
selector : optional(string, null),
negation_condition : optional(bool, false),
}))
})) | `{}` | no |
| [waf\_enable\_bot\_protection](#input\_waf\_enable\_bot\_protection) | Enable Bot Protection rules in the WAF Policy | `bool` | `true` | no |
| [waf\_enable\_rate\_limiting](#input\_waf\_enable\_rate\_limiting) | Enable a Rate Limit rule in the WAF Policy | `bool` | `true` | no |
+| [waf\_managed\_rulesets](#input\_waf\_managed\_rulesets) | A map of managed rule sets and their group-level overrides. The key is the rule set type and version, e.g., 'Microsoft\_DefaultRuleSet\_2.1'. | map(object({
action = optional(string, "Block")
overrides = optional(map(object({
disabled_rules = optional(list(string), [])
exclusions = optional(list(object({
match_variable = string
operator = string
selector = string
})), [])
})), {})
})) | `{}` | no |
| [waf\_mode](#input\_waf\_mode) | Set whether the WAF is in Detection or Prevention mode | `string` | `"Detection"` | no |
| [waf\_rate\_limiting\_bypass\_ip\_list](#input\_waf\_rate\_limiting\_bypass\_ip\_list) | List of IPv4 Addresses that should bypass the Rate Limit rules | `list(string)` | `[]` | no |
| [waf\_rate\_limiting\_duration\_in\_minutes](#input\_waf\_rate\_limiting\_duration\_in\_minutes) | Number of minutes to block an IP address when it exceeds rate limit | `number` | `5` | no |
diff --git a/locals.tf b/locals.tf
index a4e1b3b..aaef360 100644
--- a/locals.tf
+++ b/locals.tf
@@ -56,7 +56,8 @@ locals {
waf_rate_limiting_duration_in_minutes = var.waf_rate_limiting_duration_in_minutes
waf_rate_limiting_threshold = var.waf_rate_limiting_threshold
waf_rate_limiting_bypass_ip_list = var.waf_rate_limiting_bypass_ip_list
-
+ waf_custom_rules = var.waf_custom_rules
+ waf_managed_rulesets = var.waf_managed_rulesets
enable_custom_reroute_ruleset = var.enable_custom_reroute_ruleset
complete_dotnet_ruby_migration_paths_development = {
diff --git a/terraform.tfvars b/terraform.tfvars
new file mode 100644
index 0000000..980e53d
--- /dev/null
+++ b/terraform.tfvars
@@ -0,0 +1,41 @@
+# Disable rules at the Edge, these will be handled by the App Gateway WAF as
+# AFD has a 100-exclusion limit due to Global Propagation
+waf_managed_rulesets = {
+ "Microsoft_DefaultRuleSet_2.1" = {
+ action = "Block"
+ overrides = {
+ "SQLI" = {
+ disabled_rules = [
+ "942100", "942110", "942150", "942180", "942200", "942210",
+ "942230", "942260", "942310", "942340", "942360", "942370", "942380",
+ "942390", "942400", "942410", "942430", "942440", "942450", "942480"
+ ]
+ },
+ "XSS" = {
+ disabled_rules = ["941100", "941120", "941150", "941330"]
+ },
+ "RFI" = {
+ disabled_rules = ["931130"]
+ },
+ "MS-ThreatIntel-SQLI" = {
+ disabled_rules = ["99031001", "99031002", "99031003", "99031004"]
+ },
+ "PHP" = {
+ disabled_rules = ["933160", "933210"]
+ },
+ "PROTOCOL-ENFORCEMENT" = {
+ disabled_rules = ["920230", "920300", "920320", "920440"]
+ },
+ "PROTOCOL-ATTACK" = {
+ disabled_rules = ["921110"]
+ },
+ "RCE" = {
+ disabled_rules = ["932100", "932105", "932110", "932115", "932150"]
+ }
+ }
+ },
+ "Microsoft_BotManagerRuleSet_1.1" = {
+ action = "Block"
+ overrides = {}
+ }
+}
diff --git a/variables.tf b/variables.tf
index 6158d82..b698c58 100644
--- a/variables.tf
+++ b/variables.tf
@@ -190,6 +190,38 @@ variable "waf_rate_limiting_bypass_ip_list" {
default = []
}
+variable "waf_custom_rules" {
+ description = "Map of all Custom rules you want to apply to the WAF"
+ type = map(object({
+ priority : number,
+ action : string
+ match_conditions : map(object({
+ match_variable : string,
+ match_values : optional(list(string), []),
+ operator : optional(string, "Any"),
+ selector : optional(string, null),
+ negation_condition : optional(bool, false),
+ }))
+ }))
+ default = {}
+}
+
+variable "waf_managed_rulesets" {
+ description = "A map of managed rule sets and their group-level overrides. The key is the rule set type and version, e.g., 'Microsoft_DefaultRuleSet_2.1'."
+ type = map(object({
+ action = optional(string, "Block")
+ overrides = optional(map(object({
+ disabled_rules = optional(list(string), [])
+ exclusions = optional(list(object({
+ match_variable = string
+ operator = string
+ selector = string
+ })), [])
+ })), {})
+ }))
+ default = {}
+}
+
variable "enable_custom_reroute_ruleset" {
description = "Toggle on/off the re-routing of traffic accessing the Ruby Complete app under certain request paths which should route to the .NET Complete app backend origin"
type = bool
diff --git a/waf.tf b/waf.tf
index 8f582a0..fa4d1f1 100644
--- a/waf.tf
+++ b/waf.tf
@@ -21,7 +21,7 @@ resource "azurerm_cdn_frontdoor_firewall_policy" "waf" {
content {
name = "RateLimiting"
enabled = true
- priority = 1000
+ priority = 2000
rate_limit_duration_in_minutes = local.waf_rate_limiting_duration_in_minutes
rate_limit_threshold = local.waf_rate_limiting_threshold
type = "RateLimitRule"
@@ -48,7 +48,7 @@ resource "azurerm_cdn_frontdoor_firewall_policy" "waf" {
}
dynamic "managed_rule" {
- for_each = local.waf_enable_bot_protection ? [1] : []
+ for_each = contains(keys(local.waf_managed_rulesets), "Microsoft_BotManagerRuleSet_1.1") == false && local.waf_enable_bot_protection && azurerm_cdn_frontdoor_profile.rsd[0].sku_name == "Premium_AzureFrontDoor" ? [1] : []
content {
type = "Microsoft_BotManagerRuleSet"
@@ -57,13 +57,74 @@ resource "azurerm_cdn_frontdoor_firewall_policy" "waf" {
}
}
+ dynamic "managed_rule" {
+ for_each = azurerm_cdn_frontdoor_profile.rsd[0].sku_name == "Premium_AzureFrontDoor" ? local.waf_managed_rulesets : {}
+
+ content {
+ type = replace(managed_rule.key, "_${element(split("_", managed_rule.key), -1)}", "")
+ version = element(split("_", managed_rule.key), -1)
+ action = managed_rule.value.action
+
+ dynamic "override" {
+ for_each = managed_rule.value.overrides
+
+ content {
+ rule_group_name = override.key
+
+ dynamic "exclusion" {
+ for_each = lookup(override.value, "exclusions", [])
+
+ content {
+ match_variable = exclusion.value.match_variable
+ operator = exclusion.value.operator
+ selector = exclusion.value.selector
+ }
+ }
+
+ dynamic "rule" {
+ for_each = toset(lookup(override.value, "disabled_rules", []))
+
+ content {
+ rule_id = rule.value
+ enabled = false
+ action = "Log"
+ }
+ }
+ }
+ }
+ }
+ }
+
+ dynamic "custom_rule" {
+ for_each = local.waf_custom_rules
+
+ content {
+ name = custom_rule.key
+ enabled = true
+ priority = custom_rule.value["priority"]
+ type = "MatchRule"
+ action = custom_rule.value["action"]
+
+ dynamic "match_condition" {
+ for_each = custom_rule.value["match_conditions"]
+
+ content {
+ match_variable = match_condition.value["match_variable"]
+ match_values = match_condition.value["match_values"]
+ operator = match_condition.value["operator"]
+ selector = match_condition.value["selector"]
+ }
+ }
+ }
+ }
+
dynamic "custom_rule" {
for_each = local.per_origin_custom_rules
content {
name = replace(custom_rule.key, "/[^[:alnum:]]/", "")
enabled = true
- priority = index(tolist(keys(local.per_origin_custom_rules)), custom_rule.key) + 1
+ priority = (index(tolist(keys(local.per_origin_custom_rules)), custom_rule.key) * 10) + (length(var.waf_custom_rules) > 0 ? sum([for rule in values(var.waf_custom_rules) : rule.priority]) : 10)
type = "MatchRule"
action = custom_rule.value["action"]
@@ -94,7 +155,6 @@ resource "azurerm_cdn_frontdoor_firewall_policy" "waf" {
tags = local.tags
}
-
resource "azurerm_cdn_frontdoor_security_policy" "waf" {
count = local.enable_frontdoor && length(azurerm_cdn_frontdoor_endpoint.rsd) > 0 ? 1 : 0