How secure are your web applications? Deploy an Azure Application Gateway with Web Application Firewall (WAF). This is a powerful tool to protect your applications while ensuring seamless performance. Let’s dive in and explore how you can get started!

Why deploy an Application Gateway?

Have you ever heard about the OWASP Top 10? It is a list of the most common web application security risks. Could you even name all ten risks? If not, you are not alone. But you want to protect against these risks, right? An Application Gateway with Web Application Firewall (WAF) can help you with that. It is a managed service that provides a web application firewall to protect your web applications from common threats and vulnerabilities.

Alternatives

An alternative is using the Azure FrontDoor service. Or look outside of Azure, but I will not discuss those options in this blog. Frontdoor has some additional features like CDN and global load balancing.

How to migrate to an Application Gateway?

Let’s say you have a web application running on Azure App Service. You want to migrate this application to use an Application Gateway with WAF. Here are the steps you need to take:

  1. Create a Key Vault to store the SSL/TLS certificates.
  2. Create a Vnet and subnet for the Application Gateway.
  3. Create a Public IP Address for the Application Gateway.
  4. Create a new Application Gateway with WAF enabled.
  5. Configure the backend pool to point to your Azure App Service.
  6. Create a new routing rule to forward traffic to the backend pool.
  7. Update your DNS settings to point to the Application Gateway.

I assume you have a Key Vault and know how to provide it with an SSL/TLS certificate. Let’s combine the 2,3, 4, and 5 steps in a Bicep deployment.

Deploy Application gateway using bicep

Let’s deploy an Application Gateway with a backend pool pointing to an Azure App Service.

main.bicep
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
param location string = resourceGroup().location
param region string
param environment string
param tags object

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-09-01' = {
  name: 'vnet-${region}-${environment}'
  location: location
  tags: tags
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'snet-agw'
        properties: {
          addressPrefix: '10.0.1.0/24'
          privateEndpointNetworkPolicies: 'Disabled'
          privateLinkServiceNetworkPolicies: 'Disabled'
        }
      }
      { // Sample for if your backends are in Azure Container apps
        name: 'snet-containerapp'
        properties: {
          addressPrefix: '10.0.2.0/23'
          delegations: [
            {
              name: 'Microsoft.App/environments'
              properties: {
                serviceName: 'Microsoft.App/environments'
              }
            }
          ]
        }
      }
    ]
  }
}

module keyvault 'br/public:avm/res/key-vault/vault:0.13.3' = {
  name: '${deployment().name}-kv'
  scope: resourceGroup()
  params: {
    name: 'kv-${region}-${environment}'
    // Other parameters...
  }
}

module wafPolicy 'br/public:avm/res/network/application-gateway-web-application-firewall-policy:0.2.0' = {
  name: 'applicationGatewayWebApplicationFirewallPolicyDeployment'
  params: {
    // Required parameters
    managedRules: {
      managedRuleSets: [
        {
          ruleGroupOverrides: []
          ruleSetType: 'OWASP'
          ruleSetVersion: '3.2'
        }
        {
          ruleSetType: 'Microsoft_BotManagerRuleSet'
          ruleSetVersion: '0.1'
        }
      ]
    }
    name: 'mycoolwaf'
    // Non-required parameters
    location: location
    policySettings: {
      fileUploadLimitInMb: 10
      jsChallengeCookieExpirationInMins: 60
      mode: 'Detection' // 'Detection' or 'Prevention'
      state: 'Enabled'
    }
  }
}

module publicIp 'br/public:avm/res/network/public-ip-address:0.6.0' = {
  name: '${deployment().name}-pip'
  scope: resourceGroup()
  params: {
    name: 'pip-ag-${region}-${environment}'
    // Other parameters...
  }
}

var gatewayId = resourceId('Microsoft.Network/applicationGateways/probes', 'ag-${region}-${environment}')
module appGateway 'br/public:avm/res/network/application-gateway:0.7.1' = {
  name: '${deployment().name}-agw'
  scope: resourceGroup()
  params: {
    name: 'ag-${region}-${environment}'
    sku: 'WAF_v2'
    gatewayIPConfigurations: [
      {
        name: 'appGatewayIpConfig'
        properties: {
          subnet: {
            id: virtualNetwork.properties.subnets[0].id
          }
        }
      }
    ]
    frontendIPConfigurations: [
        {
            name: 'appGwFrontend'
            properties: {
                publicIPAddress: {
                    id: publicIp.outputs.resourceId
                }
                privateAllocationMethod: 'Dynamic'
            }
        }
        // Add IPv6 frontend configuration if needed, see my other blog post
    ]
    firewallPolicyResourceId: wafPolicy.outputs.resourceId
    backendAddressPools: [
        {
            name: 'appGwBackendPool'
            properties: {
                backendAddresses: [
                    {
                        fqdn: 'your-app-service.azurewebsites.net'
                    }
                ]
            }
        }
    ]
    httpListeners: [
        {
            name: 'appGwHttpListener'
            properties: {
                frontendIpConfiguration: {
                    id: '${gatewayId}/frontendIpConfigurations/appGwFrontend'
                }
                frontendPort: {
                    id: '${gatewayId}/frontendPorts/appGwFrontendPort'
                }
                protocol: 'Http'
            }
        }
    ]
    requestRoutingRules: [
        {
            name: 'appGwRoutingRule'
            properties: {
                httpListener: {
                    id: '${gatewayId}/httpListeners/appGwHttpListener'
                }
                backendAddressPool: {
                    id: '${gatewayId}/backendAddressPools/appGwBackendPool'
                }
                backendHttpSettings: {
                    id: '${gatewayId}/backendHttpSettings/appGwBackendHttpSettings'
                }
            }
        }
    ]
  }
}

Configure DNS for Application Gateway

After deploying the Application Gateway, you need to update your DNS settings to point to the Application Gateway’s public IP address. This ensures that all incoming traffic to your web application is routed through the Application Gateway, allowing it to provide the necessary security and performance enhancements.

📓 See also my other blog post on Dual stack support with Application Gateway and IPv6 for more details on updating DNS for dual stack support.

Detection vs Prevention mode

When configuring the WAF policy, you have the option to set the mode to either “Detection” or “Prevention”. In Detection mode, the WAF will monitor and log potential threats without blocking any traffic. This is useful for testing and tuning the WAF rules before enforcing them. In Prevention mode, the WAF will actively block traffic that matches the defined rules, providing a higher level of security for your web applications.

I recommend starting with Detection mode to understand the traffic patterns and potential threats to your application. Once you are confident that the WAF rules are properly configured, you can switch to Prevention mode for enhanced protection.

See my next blog post on how to find false positives and tune your WAF rules.

Next steps

We now got an Application gateway before your application. Your next steps could be: