Managing Azure Application Gateway with Terraform

Learn how to set up and manage Azure Application Gateway using Terraform, including WAF, SSL, and routing configurations

Managing Azure Application Gateway with Terraform

Azure Application Gateway is a web traffic load balancer that enables you to manage traffic to your web applications. This guide demonstrates how to set up and manage Application Gateway using Terraform.

Video Tutorial

Learn more about managing Azure Application Gateway 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 networking concepts

Project Structure

terraform-azure-appgateway/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── appgateway/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── policies/
    └── waf.json

Application Gateway Configuration

Create modules/appgateway/main.tf:

# 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-config"
    subnet_id = azurerm_subnet.frontend.id
  }

  frontend_port {
    name = "https-port"
    port = 443
  }

  frontend_ip_configuration {
    name                          = "frontend-ip-config"
    public_ip_address_id          = azurerm_public_ip.appgw.id
    private_ip_address_allocation = "Dynamic"
  }

  backend_address_pool {
    name = "backend-pool"
    fqdns = var.backend_fqdns
  }

  backend_http_settings {
    name                                = "https-settings"
    cookie_based_affinity               = "Disabled"
    port                                = 443
    protocol                            = "Https"
    request_timeout                     = 60
    probe_name                          = "health-probe"
    pick_host_name_from_backend_address = true
    trusted_root_certificate_names      = ["root-cert"]
  }

  http_listener {
    name                           = "https-listener"
    frontend_ip_configuration_name = "frontend-ip-config"
    frontend_port_name             = "https-port"
    protocol                       = "Https"
    ssl_certificate_name           = "ssl-cert"
    host_names                     = var.host_names
    require_sni                    = true
  }

  ssl_certificate {
    name                = "ssl-cert"
    key_vault_secret_id = azurerm_key_vault_certificate.appgw.secret_id
  }

  trusted_root_certificate {
    name                = "root-cert"
    key_vault_secret_id = azurerm_key_vault_certificate.root.secret_id
  }

  ssl_policy {
    policy_type = "Predefined"
    policy_name = "AppGwSslPolicy20220101"
  }

  probe {
    name                                      = "health-probe"
    protocol                                  = "Https"
    path                                      = "/health"
    interval                                  = 30
    timeout                                   = 30
    unhealthy_threshold                       = 3
    pick_host_name_from_backend_http_settings = true
  }

  url_path_map {
    name                               = "path-map"
    default_backend_address_pool_name  = "backend-pool"
    default_backend_http_settings_name = "https-settings"

    path_rule {
      name                       = "api-rule"
      paths                      = ["/api/*"]
      backend_address_pool_name  = "api-pool"
      backend_http_settings_name = "api-settings"
    }
  }

  request_routing_rule {
    name                       = "routing-rule"
    rule_type                  = "PathBasedRouting"
    http_listener_name         = "https-listener"
    url_path_map_name         = "path-map"
    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

    disabled_rule_group {
      rule_group_name = "REQUEST-931-APPLICATION-ATTACK-RFI"
      rules           = [931130]
    }

    exclusion {
      match_variable          = "RequestHeaderNames"
      selector               = "x-company-secret-header"
      selector_match_operator = "Equals"
    }
  }

  autoscale_configuration {
    min_capacity = 2
    max_capacity = 10
  }

  zones = ["1", "2", "3"]

  identity {
    type = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.appgw.id]
  }

  tags = var.tags
}

# Public IP
resource "azurerm_public_ip" "appgw" {
  name                = "${var.project_name}-pip"
  resource_group_name = var.resource_group_name
  location            = var.location
  allocation_method   = "Static"
  sku                 = "Standard"
  zones               = ["1", "2", "3"]

  tags = var.tags
}

# User Assigned Identity
resource "azurerm_user_assigned_identity" "appgw" {
  name                = "${var.project_name}-identity"
  resource_group_name = var.resource_group_name
  location            = var.location

  tags = var.tags
}

Network Configuration

  1. Virtual Network Setup
resource "azurerm_virtual_network" "main" {
  name                = "${var.project_name}-vnet"
  resource_group_name = var.resource_group_name
  location            = var.location
  address_space       = ["10.0.0.0/16"]

  subnet {
    name           = "frontend"
    address_prefix = "10.0.1.0/24"
  }

  subnet {
    name           = "backend"
    address_prefix = "10.0.2.0/24"
  }

  tags = var.tags
}

resource "azurerm_subnet" "frontend" {
  name                 = "frontend"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.1.0/24"]
}

resource "azurerm_subnet" "backend" {
  name                 = "backend"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_network_security_group" "frontend" {
  name                = "${var.project_name}-frontend-nsg"
  location            = var.location
  resource_group_name = var.resource_group_name

  security_rule {
    name                       = "allow-gateway-manager"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "65200-65535"
    source_address_prefix      = "GatewayManager"
    destination_address_prefix = "*"
  }

  tags = var.tags
}

Security Configuration

  1. SSL Certificate Management
resource "azurerm_key_vault_certificate" "appgw" {
  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=${var.domain_name}"
      validity_in_months = 12

      subject_alternative_names {
        dns_names = var.dns_names
      }
    }
  }
}
  1. 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

  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"
    }
  }

  custom_rules {
    name      = "BlockCountry"
    priority  = 1
    rule_type = "MatchRule"
    action    = "Block"

    match_conditions {
      match_variables {
        variable_name = "RemoteAddr"
      }
      operator           = "GeoMatch"
      negation_condition = false
      match_values       = ["CN", "RU"]
    }
  }

  tags = var.tags
}

Monitoring Configuration

  1. Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "appgw" {
  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 = "ApplicationGatewayPerformanceLog"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }

  metric {
    category = "AllMetrics"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }
}

resource "azurerm_monitor_metric_alert" "appgw" {
  name                = "${var.project_name}-health-alert"
  resource_group_name = var.resource_group_name
  scopes              = [azurerm_application_gateway.main.id]
  description         = "Alert when unhealthy host count is high"

  criteria {
    metric_namespace = "Microsoft.Network/applicationGateways"
    metric_name      = "UnhealthyHostCount"
    aggregation      = "Average"
    operator         = "GreaterThan"
    threshold        = 0
  }

  action {
    action_group_id = var.action_group_id
  }
}

Best Practices

  1. Performance

    • Enable autoscaling
    • Configure zones
    • Use health probes
    • Optimize SSL policy
  2. Security

    • Enable WAF
    • Use SSL everywhere
    • Implement custom rules
    • Configure network isolation
  3. High Availability

    • Use multiple zones
    • Configure health probes
    • Implement backup pools
    • Monitor health metrics
  4. Cost Optimization

    • Monitor usage
    • Optimize rules

Conclusion

You’ve learned how to set up and manage Azure Application Gateway using Terraform. This setup provides:

  • Layer 7 load balancing
  • SSL termination
  • WAF protection
  • URL-based routing

Remember to:

  • Monitor performance
  • Review security rules
  • Update SSL certificates
  • Maintain health probes