/terraform-azurerm-front-door-app-gateway-waf

Deploy a WAF Policy against Azure Front Door or Application Gateway

Primary LanguageHCLMIT LicenseMIT

Azure WAF terraform module

Terraform CI

This module creates and manages an Azure Front Door/Application gateway, and associated WAF policy.

Usage

Example module usage:

module "azurerm_waf" {
  source  = "github.com/dfe-digital/terraform-azurerm-front-door-app-gateway-waf?ref=v1.3.0"

  ## General configuration
  environment    = "dev"
  project_name   = "waf"
  azure_location = "uksouth"
  existing_resource_group = "my-existing-rg"
  # existing_virtual_network = ""
  virtual_network_address_space = "172.16.0.0/12"
  # enable_latency_monitor = false
  # latency_monitor_threshold = 5000 # ms
  # response_request_timeout = 60 # seconds

  ## Web Application Firewall configuration
  enable_waf               = true
  waf_mode                 = "Prevention" # or "Detection"
  ## Choose whether to deploy an Azure Front Door, or an App Gateway
  waf_application  = "CDN" # or "AppGatewayV2"

  waf_targets = {
    "my-origin-name" = {
      domain = "my-fqdn.example.tld",
      # create_custom_domain = true,
      # enable_health_probe = true,
      # health_probe_interval = 60,
      # health_probe_request_type = "HEAD", # or "GET"
      # health_probe_path = "/",
      # cdn_add_response_headers = [
      #   {
      #     name = "X-Custom-Http-Header",
      #     value = "My-Favourite-Value"
      #   }
      # ],
      # cdn_add_request_headers = [
      #   {
      #     name = "X-Request-Header",
      #     value = "My-Favourite-Value"
      #   }
      # ],
      # cdn_remove_response_headers = [
      #   "X-Remove-This-Header"
      # ]
      # cdn_remove_request_headers = [
      #   "X-Remove-Me"
      # ],
      # custom_errors = {
      #   error_page_directory = "${path.root}/my-error-pages"
      #   error_pages = {
      #     "HttpStatus403" = "403.html",
      #     "HttpStatus502" = "502.html"
      #   }
      # }
    }
  }

  waf_custom_rules = {
    "PostParamValuesfoo" = {
      priority = 10,
      action = "Allow",
      match_conditions = {
        "allow-any-foo" = {
          match_variable = "PostArgs",
          match_values = []
          operator = "Any",
          selector = "foo"
        }
      }
    }
  }

  ## Azure Front Door specific configuration
  cdn_sku = "Premium_AzureFrontDoor" # or "Standard_AzureFrontDoor"
  # cdn_host_redirects = [
  #   "my-site-redirect" = {
  #     from = "example.com",
  #     to = "my.example.com"
  #   }
  # ]
  # cdn_url_path_redirects = [
  #   {
  #     "redirect_type" : "Moved",
  #     "redirect_protocol" : "Https"
  #     "destination_path" : "/example",
  #     "destination_hostname" : "www.example.com",
  #     "operator" : "Equal",
  #     "match_values" : ["/example"],
  #   }
  # ]
  ## Add custom HTTP Response Headers to all origins
  # cdn_add_response_headers = [
  #   {
  #     name = "X-Apply-To-All-Origins",
  #     value = "MyValue"
  #   }
  # ]
  ## Remove HTTP Response Headers from all origins
  # cdn_remove_response_headers = [
  #   "X-Remove-Me"
  # ]
  # cdn_waf_enable_rate_limiting              = false
  # cdn_waf_rate_limiting_duration_in_minutes = 5
  # cdn_waf_rate_limiting_threshold           = 1000
  # cdn_waf_rate_limiting_bypass_ip_list      = [ 1.1.1.1, 8.8.8.8 ]
  # cdn_waf_rate_limiting_action              = "Block" # one of "Allow", "Block", "Log"
  cdn_waf_managed_rulesets = {
    "BotProtection" = {
      version = "preview-0.1",
      action  = "Block"
    },
    "DefaultRuleSet" = {
      version = "1.0",
      action  = "Block"
      # overrides = {
      #   "SQLI" = {
      #     "942440" = { # SQL Comment Sequence Detected
      #       action = "Block"
      #       enabled = false # Optional - true by default
      #       exclusions = {
      #         "rcn-sw-cookies" = {
      #           match_variable = "RequestCookieNames"
      #           operator       = "StartsWith"
      #           selector       = ".MyCustom.Cookies" # .NET
      #         },
      #       }
      #     }
      #   }
      # }
    }
  }
  cdn_waf_custom_block_response_status_code = 503
  cdn_waf_custom_block_response_body        = "<h1>Service unavailable</h1>"

  ## Azure App Gateway specific configuration
  # app_gateway_v2_capacity_units               = 1
  # app_gateway_v2_frontend_port                = 443
  # app_gateway_v2_cookie_based_affinity        = "Disabled" # or "Enabled"
  # app_gateway_v2_tls_disabled_protocols       = ["TLSv1_0", "TLSv1_1"]
  # app_gateway_v2_identity_ids                 = []
  app_gateway_v2_waf_managed_rulesets           = {
    "OWASP" = {
      version = "3.2",
      # overrides = {
      #   "0000-RULE-GROUP-NAME-0000" = {
      #     rules = {
      #       "000001" = {
      #         enabled = false
      #       },
      #       "000002" = {
      #         enabled = true,
      #         action  = "Log"
      #       },
      #     }
      #   }
      # }
    },
    "Microsoft_BotManagerRuleSet" = {
      version = "1.0"
    }
  }
  # app_gateway_v2_waf_managed_rulesets_exclusions = {
  #   "rcn-sw-cookies" : {
  #     match_variable = "RequestCookieNames",
  #     selector = ".MyCustom.Cookies",
  #     selector_match_operator = "StartsWith"
  #     excluded_rule_set = {
  #       "OWASP" = {
  #         version         = "3.2",
  #         rule_group_name = "0000-RULE-GROUP-NAME-0000",
  #         excluded_rules  = [
  #           "12345",
  #           "67890"
  #         ]
  #       }
  #       "Microsoft_BotManagerRuleSet" = {
  #         version         = "1.0",
  #         rule_group_name = "1111-RULE-GROUP-NAME-1111",
  #         excluded_rules  = [
  #           "12345",
  #         ]
  #       }
  #     }
  #   },
  # }
  ## If your App Gateway is expected to sit behind Azure Front Door, then set this to True to only permit inbound traffic from that source
  # restrict_app_gateway_v2_to_front_door_inbound_only = true
  # restrict_app_gateway_v2_to_front_door_inbound_only_destination_prefixes = [ "*" ]

  tags = {
    "Environment"      = "Dev"
  }
}

Requirements

Name Version
terraform >= 1.6.1
azapi >= 1.9.0
azuread >= 2.39.0
azurerm >= 3.51.0

Providers

Name Version
azapi 1.15.0
azuread 3.0.1
azurerm 4.3.0

Resources

Name Type
azapi_update_resource.container_app_storage_key_rotation_reminder resource
azurerm_application_gateway.waf resource
azurerm_cdn_frontdoor_custom_domain.waf resource
azurerm_cdn_frontdoor_custom_domain_association.waf resource
azurerm_cdn_frontdoor_endpoint.waf resource
azurerm_cdn_frontdoor_firewall_policy.waf resource
azurerm_cdn_frontdoor_origin.waf resource
azurerm_cdn_frontdoor_origin_group.waf resource
azurerm_cdn_frontdoor_profile.waf resource
azurerm_cdn_frontdoor_route.waf resource
azurerm_cdn_frontdoor_rule.add_origin_request_headers resource
azurerm_cdn_frontdoor_rule.add_origin_response_headers resource
azurerm_cdn_frontdoor_rule.add_response_headers resource
azurerm_cdn_frontdoor_rule.redirect resource
azurerm_cdn_frontdoor_rule.remove_origin_request_headers resource
azurerm_cdn_frontdoor_rule.remove_origin_response_headers resource
azurerm_cdn_frontdoor_rule.remove_response_header resource
azurerm_cdn_frontdoor_rule.url_path_redirect resource
azurerm_cdn_frontdoor_rule_set.global_headers resource
azurerm_cdn_frontdoor_rule_set.origin_headers resource
azurerm_cdn_frontdoor_rule_set.redirects resource
azurerm_cdn_frontdoor_rule_set.url_path_redirects resource
azurerm_cdn_frontdoor_security_policy.waf resource
azurerm_key_vault.app_gateway_certificates resource
azurerm_log_analytics_workspace.waf resource
azurerm_monitor_action_group.main resource
azurerm_monitor_diagnostic_setting.waf resource
azurerm_monitor_metric_alert.app_gateway_v2 resource
azurerm_monitor_metric_alert.cdn resource
azurerm_monitor_scheduled_query_rules_alert_v2.appgateway resource
azurerm_monitor_scheduled_query_rules_alert_v2.frontdoor resource
azurerm_network_security_group.app_gateway_v2_allow_frontdoor_inbound_only resource
azurerm_public_ip.app_gateway_v2 resource
azurerm_resource_group.default resource
azurerm_role_assignment.app_gateway_certificates resource
azurerm_route_table.default resource
azurerm_storage_account.custom_error resource
azurerm_storage_blob.custom_error_web_pages resource
azurerm_subnet.app_gateway_v2_subnet resource
azurerm_subnet_network_security_group_association.app_gateway_v2_allow_frontdoor_inbound_only resource
azurerm_subnet_route_table_association.app_gateway_v2_subnet resource
azurerm_user_assigned_identity.app_gateway resource
azurerm_virtual_network.default resource
azurerm_web_application_firewall_policy.waf resource
azapi_resource_action.existing_logic_app_workflow_callback_url data source
azuread_user.key_vault_app_gateway_certificates_access data source
azurerm_client_config.current data source
azurerm_logic_app_workflow.existing_logic_app_workflow data source
azurerm_resource_group.existing_resource_group data source
azurerm_user_assigned_identity.app_gateway_v2 data source
azurerm_virtual_network.existing_virtual_network data source

Inputs

Name Description Type Default Required
app_gateway_v2_capacity_units App Gateway V2 capacity units number 1 no
app_gateway_v2_cookie_based_affinity App Gateway V2 Cookie Based Affinity. Sets an affinity cookie in the response with a hash value which contains the session details, so that the subsequent requests carrying the affinity cookie will be routed to the same backend server for maintaining stickiness. string "Disabled" no
app_gateway_v2_custom_error_configuration A map of Status Codes to HTML URLs map(string) {} no
app_gateway_v2_enable_http2 App Gateway V2 enable HTTP2 bool true no
app_gateway_v2_frontend_port App Gateway V2 frontend port number 80 no
app_gateway_v2_identity_ids App Gateway V2 User Assigned identity ids. If empty, one will be created. list(any) [] no
app_gateway_v2_waf_file_upload_limit_in_mb Maximum file size permitted in MB number 100 no
app_gateway_v2_waf_managed_rulesets Map of all Managed rules you want to apply to the App Gateway WAF, including any overrides
map(object({
version : string,
overrides : optional(map(object({
rules : map(object({
enabled : bool,
action : optional(string, "Block")
}))
})), {})
}))
{
"Microsoft_BotManagerRuleSet": {
"version": "1.0"
},
"OWASP": {
"version": "3.2"
}
}
no
app_gateway_v2_waf_managed_rulesets_exclusions Map of all exlusions and the assoicated Managed rules to apply to the App Gateway WAF
map(object({
match_variable : string,
selector : string,
selector_match_operator : string,
excluded_rule_set : map(object({
version : string,
rule_group_name : string,
excluded_rules : list(string)
}))
}))
{} no
app_gateway_v2_waf_max_request_body_size_in_kb Maximum request size for a single request in KB. Has no effect if 'app_gateway_v2_waf_request_body_enforcement' is set to 'false' number 128 no
app_gateway_v2_waf_request_body_enforcement Should the firewall block a request with a body size greater than 'app_gateway_v2_waf_max_request_body_size_in_kb' bool true no
azure_location Azure location in which to launch resources. string n/a yes
cdn_add_response_headers List of response headers to add at the CDN Front Door for all endpoints [{ "Name" = "Strict-Transport-Security", "value" = "max-age=31536000" }] list(map(string)) [] no
cdn_host_redirects CDN FrontDoor host redirects [{ "from" = "example.com", "to" = "www.example.com" }] list(map(string)) [] no
cdn_remove_response_headers List of response headers to remove at the CDN Front Door for all endpoints list(string) [] no
cdn_sku Azure CDN Front Door SKU string "Standard_AzureFrontDoor" no
cdn_url_path_redirects CDN FrontDoor url path redirects [{ "redirect_type": "PermanentRedirect", "destination_path": "/example", "destination_hostname": "www.example.uk", "operator": "Equals", "match_values": ["/example"] }]
list(object({
redirect_type = string
redirect_protocol = optional(string, null)
destination_path = optional(string, null)
destination_hostname = optional(string, null)
destination_fragment = optional(string, null)
query_string = optional(string, null)
operator = string
match_values = optional(list(string), [])
transforms = optional(list(string), [])
}))
[] no
cdn_waf_custom_block_response_body Base64 encoded custom response body when the WAF blocks a request string "" no
cdn_waf_custom_block_response_status_code Custom response status code when the WAF blocks a request. number 0 no
cdn_waf_enable_rate_limiting Deploy a Rate Limiting Policy on the Front Door WAF bool false no
cdn_waf_managed_rulesets Map of all Managed rules you want to apply to the CDN WAF, including any overrides, or exclusions
map(object({
version : string,
action : optional(string, "Block"),
exclusions : optional(map(object({
match_variable : string,
operator : string,
selector : string
})), {})
overrides : optional(map(map(object({
action : string,
enabled : optional(bool, true),
exclusions : optional(map(object({
match_variable : string,
operator : string,
selector : string
})), {})
}))), {})
}))
{
"BotProtection": {
"version": "preview-0.1"
},
"DefaultRuleSet": {
"version": "1.0"
}
}
no
cdn_waf_rate_limiting_action Action to take when rate limiting (Block/Log) string "Block" no
cdn_waf_rate_limiting_bypass_ip_list List if IP CIDRs to bypass the Rate Limit Policy list(string) [] no
cdn_waf_rate_limiting_duration_in_minutes Number of minutes to BLOCK requests that hit the Rate Limit threshold number 1 no
cdn_waf_rate_limiting_threshold Maximum number of concurrent requests before Rate Limiting policy is applied number 300 no
enable_key_vault_app_gateway_certificates Deploy a Key Vault to hold TLS Certificates for use by App Gateway bool true no
enable_latency_monitor Enable CDN latency monitor bool false no
enable_waf Enable WAF bool false no
enable_waf_alert Toggle to enable or disable the WAF logs alert bool true no
environment Environment name. Will be used along with project_name as a prefix for all resources. string n/a yes
existing_logic_app_workflow Name, Resource Group and HTTP Trigger URL of an existing Logic App Workflow
object({
name : string
resource_group_name : string
})
{
"name": "",
"resource_group_name": ""
}
no
existing_monitor_action_group_id ID of an existing monitor action group string "" no
existing_resource_group Conditionally launch resources into an existing resource group. Specifying this will NOT create a resource group. string "" no
existing_virtual_network Conditionally use an existing virtual network. The virtual_network_address_space must match an existing address space in the VNet. This also requires the resource group name. string "" no
key_vault_app_gateway_certificates_access_ipv4 List of IPv4 Addresses that are permitted to access the App Gateway Certificates Key Vault list(string) [] no
key_vault_app_gateway_certificates_access_subnet_ids List of Azure Subnet IDs that are permitted to access the App Gateway Certificates Key Vault list(string) [] no
key_vault_app_gateway_certificates_access_users List of users that require access to the App Gateway Certificates Key Vault. This should be a list of User Principle Names (Found in Active Directory) that need to run terraform list(string) [] no
key_vault_app_gateway_enable_rbac Use RBAC authorisation on the App Gateway Certificates Key Vault. Has no effect if key_vault_app_gateway_certificates_access_users is defined. bool false no
latency_monitor_threshold CDN latency monitor threshold in milliseconds number 5000 no
monitor_email_receivers A list of email addresses that should be notified by monitoring alerts list(string) [] no
project_name Project name. Will be used along with environment as a prefix for all resources. string n/a yes
response_request_timeout Azure CDN Front Door response timeout, or app gateway v2 request timeout in seconds number 120 no
restrict_app_gateway_v2_to_front_door_inbound_only Restricts access to the App Gateway V2 by creating a network security group that only allows 'AzureFrontDoor.Backend' inbound, and attaches it to the subnet of the application gateway. bool false no
restrict_app_gateway_v2_to_front_door_inbound_only_destination_prefix If app gateway v2 has access restricted to front door only (by enabling restrict_app_gateway_v2_to_front_door_inbound_only), use this to set the destination prefix for the security group rule. string "*" no
restrict_app_gateway_v2_to_front_door_inbound_only_destination_prefixes If app gateway v2 has access restricted to front door only (by enabling restrict_app_gateway_v2_to_front_door_inbound_only), use this to set the destination prefixes for the security group rule. list(string) [] no
tags Tags to be applied to all resources map(string) {} no
virtual_network_address_space Virtual Network address space CIDR string "172.16.0.0/12" no
waf_application Which product to apply the WAF to. Must be either CDN or AppGatewayV2 string "CDN" no
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_mode WAF mode string "Prevention" no
waf_targets Target endpoints to configure the WAF to point towards
map(
object({
domain : string,
cdn_create_custom_domain : optional(bool, false),
custom_fqdn : optional(string, "")
app_gateway_v2_ssl_certificate_key_vault_id : optional(string, "")
enable_health_probe : optional(bool, true),
health_probe_interval : optional(number, 60),
health_probe_request_type : optional(string, "HEAD"),
health_probe_path : optional(string, "/"),
cdn_add_response_headers : optional(list(object({
name : string,
value : string
})
), [])
cdn_add_request_headers : optional(list(object({
name : string,
value : string
})
), [])
cdn_remove_response_headers : optional(list(string), [])
cdn_remove_request_headers : optional(list(string), [])
custom_errors : optional(object({
error_page_directory : string,
error_pages : map(string)
}), null)
})
)
{} no

Outputs

Name Description
custom_error_web_page_storage_accounts Storage Accounts used for holding custom error pages
environment n/a