Azure Application Gateway with WAF: Architecture and Tuning

Application Gateway is the layer 7 workhorse for internal and regional web traffic on Azure: TLS termination, path based routing, and a Web Application Firewall in one managed resource. The deployment is straightforward. The part that takes actual engineering is WAF tuning, because a WAF in Detection mode forever protects nothing, and a WAF flipped straight to Prevention breaks legitimate traffic on day one.

Core Architecture

Always v2 SKU: autoscaling, zone redundancy, and header rewrites do not exist on v1. The gateway lives in its own dedicated subnet, backends are IPs, VMSS, App Service, or AKS pods (via the AGIC or, better, Application Gateway for Containers for new builds). A standard production listener chain: HTTP listener that redirects to HTTPS, HTTPS listener with the certificate pulled from Key Vault through a managed identity, routing rule to a backend pool with a custom health probe.

resource "azurerm_application_gateway" "this" {
  name                = "agw-web-prod"
  resource_group_name = azurerm_resource_group.web.name
  location            = "westeurope"
  firewall_policy_id  = azurerm_web_application_firewall_policy.this.id
  zones               = ["1", "2", "3"]

  sku {
    name = "WAF_v2"
    tier = "WAF_v2"
  }

  autoscale_configuration {
    min_capacity = 2
    max_capacity = 10
  }

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

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

  # gateway_ip_configuration, frontend, listeners, pools,
  # probes and rules omitted for space
}

Two details worth calling out. Minimum capacity of two across zones, because scale out takes minutes and a traffic spike will not wait. And end to end TLS: re-encrypt to the backend with a trusted root certificate on the HTTP settings rather than terminating and going plaintext internally.

WAF Policy and the Tuning Loop

Use a standalone WAF policy resource, not legacy inline config, and run the newest ruleset (DRS 2.1, which is anomaly scoring based: rules add points and the request is blocked when the total crosses the threshold).

resource "azurerm_web_application_firewall_policy" "this" {
  name                = "wafp-web-prod"
  resource_group_name = azurerm_resource_group.web.name
  location            = "westeurope"

  policy_settings {
    enabled                     = true
    mode                        = "Prevention"
    request_body_check          = true
    max_request_body_size_in_kb = 128
    file_upload_limit_in_mb     = 100
  }

  managed_rules {
    managed_rule_set {
      type    = "Microsoft_DefaultRuleSet"
      version = "2.1"

      rule_group_override {
        rule_group_name = "SQLI"
        rule {
          id      = "942440"
          enabled = true
          action  = "Log"
        }
      }
    }

    managed_rule_set {
      type    = "Microsoft_BotManagerRuleSet"
      version = "1.1"
    }

    exclusion {
      match_variable          = "RequestCookieNames"
      selector                = "session_token"
      selector_match_operator = "Equals"
      excluded_rule_set {
        type    = "Microsoft_DefaultRuleSet"
        version = "2.1"
        rule_group {
          rule_group_name = "SQLI"
        }
      }
    }
  }
}

The tuning loop: run Detection for two to four weeks of real traffic, query the firewall logs grouped by rule ID and URI, and for each frequent hit decide whether it is an attack, a false positive to solve with a scoped exclusion (a specific cookie or parameter, never a whole rule group), or an application bug to fix properly. Then switch to Prevention and keep watching the same query. Rich session cookies, JSON heavy APIs, and CMS admin paths generate most false positives, so tune those first.

Operations

Send access and firewall logs to Log Analytics and alert on three things: backend health dropping below 100 percent, a spike in blocked requests (either an attack or a bad deploy tripping the WAF), and response time percentiles from the gateway view versus the backend view, which tells you instantly whether latency lives in the gateway, network, or application. Health probes should hit a real dependency checking endpoint, not just a 200 from a static page, because the gateway only routes around failures it can see.

Cheers
Osama

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.