Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.tfstate.*
crash.log
*.tfvars*
!terraform.tfvars
override.tf
override.tf.json
*_override.tf
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ This project creates and manages the RSD FrontDoor CDN.
| <a name="input_key_vault_access_ipv4"></a> [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 |
| <a name="input_tags"></a> [tags](#input\_tags) | Tags to be applied to all resources | `map(string)` | `{}` | no |
| <a name="input_tfvars_filename"></a> [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 |
| <a name="input_waf_custom_rules"></a> [waf\_custom\_rules](#input\_waf\_custom\_rules) | Map of all Custom rules you want to apply to the WAF | <pre>map(object({<br/> priority : number,<br/> action : string<br/> match_conditions : map(object({<br/> match_variable : string,<br/> match_values : optional(list(string), []),<br/> operator : optional(string, "Any"),<br/> selector : optional(string, null),<br/> negation_condition : optional(bool, false),<br/> }))<br/> }))</pre> | `{}` | no |
| <a name="input_waf_enable_bot_protection"></a> [waf\_enable\_bot\_protection](#input\_waf\_enable\_bot\_protection) | Enable Bot Protection rules in the WAF Policy | `bool` | `true` | no |
| <a name="input_waf_enable_rate_limiting"></a> [waf\_enable\_rate\_limiting](#input\_waf\_enable\_rate\_limiting) | Enable a Rate Limit rule in the WAF Policy | `bool` | `true` | no |
| <a name="input_waf_managed_rulesets"></a> [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'. | <pre>map(object({<br/> action = optional(string, "Block")<br/> overrides = optional(map(object({<br/> disabled_rules = optional(list(string), [])<br/> exclusions = optional(list(object({<br/> match_variable = string<br/> operator = string<br/> selector = string<br/> })), [])<br/> })), {})<br/> }))</pre> | `{}` | no |
| <a name="input_waf_mode"></a> [waf\_mode](#input\_waf\_mode) | Set whether the WAF is in Detection or Prevention mode | `string` | `"Detection"` | no |
| <a name="input_waf_rate_limiting_bypass_ip_list"></a> [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 |
| <a name="input_waf_rate_limiting_duration_in_minutes"></a> [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 |
Expand Down
3 changes: 2 additions & 1 deletion locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
41 changes: 41 additions & 0 deletions terraform.tfvars
Original file line number Diff line number Diff line change
@@ -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 = {}
}
}
32 changes: 32 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 64 additions & 4 deletions waf.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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"]

Expand Down Expand Up @@ -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

Expand Down