Quickstart: Create an Azure WAF v2 on Application Gateway using Bicep
In this quickstart, you use Bicep to create an Azure Web Application Firewall v2 on Application Gateway.
Bicep is a domain-specific language (DSL) that uses declarative syntax to deploy Azure resources. It provides concise syntax, reliable type safety, and support for code reuse. Bicep offers the best authoring experience for your infrastructure-as-code solutions in Azure.
Note
We recommend that you use the Azure Az PowerShell module to interact with Azure. To get started, see Install Azure PowerShell. To learn how to migrate to the Az PowerShell module, see Migrate Azure PowerShell from AzureRM to Az.
Prerequisites
- An Azure account with an active subscription. Create an account for free.
Review the Bicep file
This Bicep file creates a simple Web Application Firewall v2 on Azure Application Gateway. This includes a public IP frontend IP address, HTTP settings, a rule with a basic listener on port 80, and a backend pool. The file also creates a WAF policy with a custom rule to block traffic to the backend pool based on an IP address match type.
The Bicep file used in this quickstart is from Azure Quickstart Templates.
@description('Admin username for the backend servers')
param adminUsername string
@description('Password for the admin account on the backend servers')
@secure()
param adminPassword string
@description('Location for all resources.')
param location string = resourceGroup().location
@description('Size of the virtual machine.')
param vmSize string = 'Standard_B2ms'
var virtualMachines_myVM_name = 'myVM'
var virtualNetworks_myVNet_name = 'myVNet'
var myNic_name = 'net-int'
var ipconfig_name = 'ipconfig'
var publicIPAddress_name = 'public_ip'
var nsg_name = 'vm-nsg'
var applicationGateways_myAppGateway_name = 'myAppGateway'
var vnet_prefix = '10.0.0.0/16'
var ag_subnet_prefix = '10.0.0.0/24'
var backend_subnet_prefix = '10.0.1.0/24'
var AppGW_AppFW_Pol_name = 'WafPol01'
resource nsg 'Microsoft.Network/networkSecurityGroups@2021-08-01' = [for i in range(0, 2): {
name: '${nsg_name}${(i + 1)}'
location: location
properties: {
securityRules: [
{
name: 'RDP'
properties: {
protocol: 'Tcp'
sourcePortRange: '*'
destinationPortRange: '3389'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
access: 'Allow'
priority: 300
direction: 'Inbound'
}
}
]
}
}]
resource publicIPAddress 'Microsoft.Network/publicIPAddresses@2021-08-01' = [for i in range(0, 3): {
name: '${publicIPAddress_name}${i}'
location: location
sku: {
name: 'Standard'
}
properties: {
publicIPAddressVersion: 'IPv4'
publicIPAllocationMethod: 'Static'
idleTimeoutInMinutes: 4
}
}]
resource myVNet 'Microsoft.Network/virtualNetworks@2021-08-01' = {
name: virtualNetworks_myVNet_name
location: location
properties: {
addressSpace: {
addressPrefixes: [
vnet_prefix
]
}
subnets: [
{
name: 'myAGSubnet'
properties: {
addressPrefix: ag_subnet_prefix
privateEndpointNetworkPolicies: 'Enabled'
privateLinkServiceNetworkPolicies: 'Enabled'
}
}
{
name: 'myBackendSubnet'
properties: {
addressPrefix: backend_subnet_prefix
privateEndpointNetworkPolicies: 'Enabled'
privateLinkServiceNetworkPolicies: 'Enabled'
}
}
]
enableDdosProtection: false
enableVmProtection: false
}
}
resource myVM 'Microsoft.Compute/virtualMachines@2021-11-01' = [for i in range(0, 2): {
name: '${virtualMachines_myVM_name}${(i + 1)}'
location: location
properties: {
hardwareProfile: {
vmSize: vmSize
}
storageProfile: {
imageReference: {
publisher: 'MicrosoftWindowsServer'
offer: 'WindowsServer'
sku: '2019-Datacenter'
version: 'latest'
}
osDisk: {
osType: 'Windows'
createOption: 'FromImage'
caching: 'ReadWrite'
managedDisk: {
storageAccountType: 'StandardSSD_LRS'
}
diskSizeGB: 127
}
}
osProfile: {
computerName: '${virtualMachines_myVM_name}${(i + 1)}'
adminUsername: adminUsername
adminPassword: adminPassword
windowsConfiguration: {
provisionVMAgent: true
enableAutomaticUpdates: true
}
allowExtensionOperations: true
}
networkProfile: {
networkInterfaces: [
{
id: resourceId('Microsoft.Network/networkInterfaces', '${myNic_name}${(i + 1)}')
}
]
}
}
dependsOn: [
myNic
]
}]
resource myVM_IIS 'Microsoft.Compute/virtualMachines/extensions@2021-11-01' = [for i in range(0, 2): {
name: '${virtualMachines_myVM_name}${(i + 1)}/IIS'
location: location
properties: {
autoUpgradeMinorVersion: true
publisher: 'Microsoft.Compute'
type: 'CustomScriptExtension'
typeHandlerVersion: '1.4'
settings: {
commandToExecute: 'powershell Add-WindowsFeature Web-Server; powershell Add-Content -Path "C:\\inetpub\\wwwroot\\Default.htm" -Value $($env:computername)'
}
}
dependsOn: [
myVM
]
}]
resource myAppGateway 'Microsoft.Network/applicationGateways@2021-08-01' = {
name: applicationGateways_myAppGateway_name
location: location
properties: {
sku: {
name: 'WAF_v2'
tier: 'WAF_v2'
capacity: 2
}
gatewayIPConfigurations: [
{
name: 'appGatewayIpConfig'
properties: {
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', virtualNetworks_myVNet_name, 'myAGSubnet')
}
}
}
]
frontendIPConfigurations: [
{
name: 'appGwPublicFrontendIp'
properties: {
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: resourceId('Microsoft.Network/publicIPAddresses', '${publicIPAddress_name}0')
}
}
}
]
frontendPorts: [
{
name: 'port_80'
properties: {
port: 80
}
}
]
backendAddressPools: [
{
name: 'myBackendPool'
properties: {}
}
]
backendHttpSettingsCollection: [
{
name: 'myHTTPSetting'
properties: {
port: 80
protocol: 'Http'
cookieBasedAffinity: 'Disabled'
pickHostNameFromBackendAddress: false
requestTimeout: 20
}
}
]
httpListeners: [
{
name: 'myListener'
properties: {
firewallPolicy: {
id: AppGW_AppFW_Pol.id
}
frontendIPConfiguration: {
id: resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', applicationGateways_myAppGateway_name, 'appGwPublicFrontendIp')
}
frontendPort: {
id: resourceId('Microsoft.Network/applicationGateways/frontendPorts', applicationGateways_myAppGateway_name, 'port_80')
}
protocol: 'Http'
requireServerNameIndication: false
}
}
]
requestRoutingRules: [
{
name: 'myRoutingRule'
properties: {
ruleType: 'Basic'
priority: 10
httpListener: {
id: resourceId('Microsoft.Network/applicationGateways/httpListeners', applicationGateways_myAppGateway_name, 'myListener')
}
backendAddressPool: {
id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', applicationGateways_myAppGateway_name, 'myBackendPool')
}
backendHttpSettings: {
id: resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', applicationGateways_myAppGateway_name, 'myHTTPSetting')
}
}
}
]
enableHttp2: false
firewallPolicy: {
id: AppGW_AppFW_Pol.id
}
}
dependsOn: [
myVNet
publicIPAddress
]
}
resource AppGW_AppFW_Pol 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2021-08-01' = {
name: AppGW_AppFW_Pol_name
location: location
properties: {
customRules: [
{
name: 'CustRule01'
priority: 100
ruleType: 'MatchRule'
action: 'Block'
matchConditions: [
{
matchVariables: [
{
variableName: 'RemoteAddr'
}
]
operator: 'IPMatch'
negationConditon: true
matchValues: [
'10.10.10.0/24'
]
}
]
}
]
policySettings: {
requestBodyCheck: true
maxRequestBodySizeInKb: 128
fileUploadLimitInMb: 100
state: 'Enabled'
mode: 'Prevention'
}
managedRules: {
managedRuleSets: [
{
ruleSetType: 'OWASP'
ruleSetVersion: '3.1'
}
]
}
}
}
resource myNic 'Microsoft.Network/networkInterfaces@2021-08-01' = [for i in range(0, 2): {
name: '${myNic_name}${(i + 1)}'
location: location
properties: {
ipConfigurations: [
{
name: '${ipconfig_name}${(i + 1)}'
properties: {
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: resourceId('Microsoft.Network/publicIPAddresses', '${publicIPAddress_name}${(i + 1)}')
}
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', virtualNetworks_myVNet_name, 'myBackendSubnet')
}
primary: true
privateIPAddressVersion: 'IPv4'
applicationGatewayBackendAddressPools: [
{
id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', applicationGateways_myAppGateway_name, 'myBackendPool')
}
]
}
}
]
enableAcceleratedNetworking: false
enableIPForwarding: false
networkSecurityGroup: {
id: resourceId('Microsoft.Network/networkSecurityGroups', '${nsg_name}${(i + 1)}')
}
}
dependsOn: [
publicIPAddress
myVNet
myAppGateway
nsg
]
}]
Multiple Azure resources are defined in the Bicep file:
- Microsoft.Network/applicationgateways
- Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies
- Microsoft.Network/publicIPAddresses : one for the application gateway, and two for the virtual machines.
- Microsoft.Network/networkSecurityGroups
- Microsoft.Network/virtualNetworks
- Microsoft.Compute/virtualMachines : two virtual machines
- Microsoft.Network/networkInterfaces : two for the virtual machines
- Microsoft.Compute/virtualMachine/extensions : to configure IIS and the web pages
Deploy the Bicep file
Save the Bicep file as main.bicep to your local computer.
Deploy the Bicep file using either Azure CLI or Azure PowerShell.
az group create --name exampleRG --location eastus az deployment group create --resource-group exampleRG --template-file main.bicep --parameters adminUsername=<admin-user>
Note
You'll be prompted to enter adminPassword, which is the password for the admin account on the backend servers. The password must be between 8-123 characters long and must contain at least three of the following: an uppercase character, a lowercase character, a numeric digit, or a special character.
When the deployment finishes, you should see a message indicating the deployment succeeded. The deployment can take 10 minutes or longer to complete.
Validate the deployment
Although IIS isn't required to create the application gateway, it's installed on the backend servers to verify if Azure successfully created a WAF v2 on the application gateway.
Use IIS to test the application gateway:
Find the public IP address for the application gateway on its Overview page.
Copy the public IP address, and then paste it into the address bar of your browser to browse that IP address.
Check the response. A 403 Forbidden response verifies that the WAF was successfully created and is blocking connections to the backend pool.
Change the custom rule to Allow traffic using Azure PowerShell.
$rgName = "exampleRG" $appGWName = "myAppGateway" $fwPolicyName = "WafPol01" # Pull the existing Azure resources $appGW = Get-AzApplicationGateway -Name $appGWName -ResourceGroupName $rgName $pol = Get-AzApplicationGatewayFirewallPolicy -Name $fwPolicyName -ResourceGroupName $rgName # Update the resources $pol[0].CustomRules[0].Action = "allow" $appGW.FirewallPolicy = $pol # Push your changes to Azure Set-AzApplicationGatewayFirewallPolicy -Name $fwPolicyName -ResourceGroupName $rgName -CustomRule $pol.CustomRules Set-AzApplicationGateway -ApplicationGateway $appGW
Refresh your browser multiple times and you should see connections to both myVM1 and myVM2.
Clean up resources
When you no longer need the resources that you created with the application gateway, use the Azure portal, Azure CLI, or Azure PowerShell to delete the resource group. This removes the application gateway and all the related resources.
az group delete --name exampleRG