Managing Azure Application Gateway WAF with Terraform
Learn how to set up and manage Azure Application Gateway WAF using Terraform, including security rules and protection policies
Managing Azure Application Gateway WAF with Terraform
Azure Application Gateway WAF (Web Application Firewall) provides centralized protection for your web applications. This guide demonstrates how to set up and manage WAF using Terraform.
Video Tutorial
Learn more about managing Azure WAF with Terraform in this comprehensive video tutorial:
Prerequisites
- Azure CLI configured with appropriate permissions
- Terraform installed (version 1.0.0 or later)
- Resource group created
- Understanding of web security concepts
Project Structure
terraform-azure-waf/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│ └── waf/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── policies/
└── security.json
WAF Configuration
Create modules/waf/main.tf
:
# WAF Policy
resource "azurerm_web_application_firewall_policy" "main" {
name = "${var.project_name}-waf-policy"
resource_group_name = var.resource_group_name
location = var.location
custom_rules {
name = "BlockCountries"
priority = 1
rule_type = "MatchRule"
action = "Block"
match_conditions {
match_variables {
variable_name = "RemoteAddr"
}
operator = "GeoMatch"
negation_condition = false
match_values = ["CN", "RU"]
}
}
custom_rules {
name = "BlockIPRange"
priority = 2
rule_type = "MatchRule"
action = "Block"
match_conditions {
match_variables {
variable_name = "RemoteAddr"
}
operator = "IPMatch"
negation_condition = false
match_values = ["192.168.1.0/24", "10.0.0.0/24"]
}
}
custom_rules {
name = "BlockUserAgent"
priority = 3
rule_type = "MatchRule"
action = "Block"
match_conditions {
match_variables {
variable_name = "RequestHeaders"
selector = "User-Agent"
}
operator = "Contains"
negation_condition = false
match_values = ["bad-bot", "malicious-scanner"]
}
}
policy_settings {
enabled = true
mode = "Prevention"
request_body_check = true
file_upload_limit_in_mb = 100
max_request_body_size_in_kb = 128
}
managed_rules {
managed_rule_set {
type = "OWASP"
version = "3.2"
rule_group_override {
rule_group_name = "REQUEST-920-PROTOCOL-ENFORCEMENT"
rule {
id = "920300"
enabled = true
action = "Block"
}
rule {
id = "920330"
enabled = true
action = "Block"
}
}
rule_group_override {
rule_group_name = "REQUEST-930-APPLICATION-ATTACK-LFI"
rule {
id = "930100"
enabled = true
action = "Block"
}
}
rule_group_override {
rule_group_name = "REQUEST-942-APPLICATION-ATTACK-SQLI"
disabled_rules = ["942100", "942110", "942120"]
}
}
managed_rule_set {
type = "Microsoft_BotManagerRuleSet"
version = "1.0"
}
}
tags = var.tags
}
# Application Gateway
resource "azurerm_application_gateway" "main" {
name = "${var.project_name}-appgw"
resource_group_name = var.resource_group_name
location = var.location
sku {
name = "WAF_v2"
tier = "WAF_v2"
capacity = 2
}
gateway_ip_configuration {
name = "gateway-ip-configuration"
subnet_id = azurerm_subnet.frontend.id
}
frontend_port {
name = "http"
port = 80
}
frontend_port {
name = "https"
port = 443
}
frontend_ip_configuration {
name = "frontend"
public_ip_address_id = azurerm_public_ip.main.id
}
backend_address_pool {
name = "backend"
}
backend_http_settings {
name = "http"
cookie_based_affinity = "Disabled"
port = 80
protocol = "Http"
request_timeout = 60
probe_name = "probe"
}
probe {
name = "probe"
host = "example.com"
interval = 30
timeout = 30
unhealthy_threshold = 3
protocol = "Http"
port = 80
path = "/health"
match {
status_code = ["200-399"]
}
}
http_listener {
name = "http"
frontend_ip_configuration_name = "frontend"
frontend_port_name = "http"
protocol = "Http"
}
ssl_certificate {
name = "ssl-cert"
data = filebase64("${path.module}/cert.pfx")
password = var.cert_password
}
ssl_policy {
policy_type = "Predefined"
policy_name = "AppGwSslPolicy20170401S"
}
request_routing_rule {
name = "rule1"
rule_type = "Basic"
http_listener_name = "http"
backend_address_pool_name = "backend"
backend_http_settings_name = "http"
priority = 1
}
waf_configuration {
enabled = true
firewall_mode = "Prevention"
rule_set_type = "OWASP"
rule_set_version = "3.2"
file_upload_limit_mb = 100
max_request_body_size_kb = 128
}
firewall_policy_id = azurerm_web_application_firewall_policy.main.id
zones = ["1", "2", "3"]
tags = var.tags
}
# SSL Certificate
resource "azurerm_key_vault_certificate" "main" {
name = "${var.project_name}-cert"
key_vault_id = var.key_vault_id
certificate_policy {
issuer_parameters {
name = "Self"
}
key_properties {
exportable = true
key_size = 2048
key_type = "RSA"
reuse_key = true
}
lifetime_action {
action {
action_type = "AutoRenew"
}
trigger {
days_before_expiry = 30
}
}
secret_properties {
content_type = "application/x-pkcs12"
}
x509_certificate_properties {
key_usage = [
"cRLSign",
"dataEncipherment",
"digitalSignature",
"keyAgreement",
"keyCertSign",
"keyEncipherment",
]
subject = "CN=example.com"
validity_in_months = 12
subject_alternative_names {
dns_names = ["example.com", "www.example.com"]
}
}
}
}
Monitoring Configuration
- Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "waf" {
name = "${var.project_name}-diag"
target_resource_id = azurerm_application_gateway.main.id
log_analytics_workspace_id = var.log_analytics_workspace_id
log {
category = "ApplicationGatewayAccessLog"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
log {
category = "ApplicationGatewayFirewallLog"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
metric {
category = "AllMetrics"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
}
# WAF Alerts
resource "azurerm_monitor_metric_alert" "waf_blocked" {
name = "${var.project_name}-waf-blocked-alert"
resource_group_name = var.resource_group_name
scopes = [azurerm_application_gateway.main.id]
description = "Alert when WAF blocks requests"
criteria {
metric_namespace = "Microsoft.Network/applicationGateways"
metric_name = "BlockedReqCount"
aggregation = "Total"
operator = "GreaterThan"
threshold = 100
}
action {
action_group_id = var.action_group_id
}
}
resource "azurerm_monitor_metric_alert" "waf_latency" {
name = "${var.project_name}-waf-latency-alert"
resource_group_name = var.resource_group_name
scopes = [azurerm_application_gateway.main.id]
description = "Alert when WAF latency is high"
criteria {
metric_namespace = "Microsoft.Network/applicationGateways"
metric_name = "ApplicationGatewayTotalTime"
aggregation = "Average"
operator = "GreaterThan"
threshold = 1000
}
action {
action_group_id = var.action_group_id
}
}
Best Practices
-
Security
- Enable Prevention mode
- Configure custom rules
- Monitor attacks
- Update policies
-
Performance
- Use v2 SKU
- Configure caching
- Monitor latency
- Scale appropriately
-
High Availability
- Use multiple zones
- Configure health probes
- Monitor status
- Implement backup
-
Cost Optimization
- Choose right SKU
- Monitor usage
- Optimize rules
- Review metrics
Advanced Features
- Custom Error Pages
resource "azurerm_storage_account" "error_pages" {
name = "${var.project_name}errorpages"
resource_group_name = var.resource_group_name
location = var.location
account_tier = "Standard"
account_replication_type = "LRS"
static_website {
index_document = "index.html"
error_404_document = "404.html"
}
tags = var.tags
}
Conclusion
You’ve learned how to set up and manage Azure Application Gateway WAF using Terraform. This setup provides:
- Application protection
- Custom rules
- Traffic monitoring
- Security policies
Remember to:
- Monitor attacks
- Update rules
- Review configurations
- Maintain security