Azure Application Gateway (AAG) is one of the most interesting components in Azure. Although it seems simple enough, it might get very tricky to get it working. This components isn’t that well documented and interacting with it for the first time can be challenging. So, after spending the last 3 to 4 weeks wotking with it, I thought of writing this guide on how each of the UI (or ARM) sections relate to the functionality of the component.

I would like to thanks Vincent-Philippe Lauzon for this great post which was a great source and inspiration for this article: Azure Application Gateway Anatomy

Before getting started

A couple of things to keep in mind when starting to work with AAG:

  • It does NOT support certificates with more than 1 key on them. If you have one of this, make sure you remove the extra keys before using it.
  • It’s better to deploy AAG through ARM Template with all its configurations in place than deploying a based one and manually modify it. Otherwise, arm yourself with patience since every click on the UI might take up to 15 minutes to reflect.
  • AAG can only be deployed in an AAG dedicated subnet, this means in the subnet where you deploy AAG, there can only be AAG deployed to it. No other Azure resource is allowed.
  • AAG is constrained to a Virtual Network (VNET) make sure Network Security Group (NSG) allows it to reach whatever it needs to.
  • If you configure an HTTPS listener, it won’t redirect or do anything with HTTP traffic (you will get a 404 error, period). In order to improve user experience, I recommend creating a redirect in AAG for HTTP traffic to HTTPS.

What is it?

Well, if you read Microsoft documentation, it will tell you it’s a web traffic load balancer (basically an OSI Layer 7 load balancer) with Web Application Firewall (WAF) capabilities. In other words, it’s an Internet Information Services (IIS) instance working a reverse-proxy with WAF capabilities which can balance HTTP and HTTPS traffic. It also has capabilities for SSL-termination (HTTPS in front-end and HTTP in back-end).

How does it work?

In order to understand AAG, you need to understand how each of its different piece relate to each other. Although it’s a single components, configurations are spread all over the place which makes hard to understand what each piece does. To me, it’s easier to think of the AAG as a three layer component:

  • FrontEnd which is exposed to client reaching to it (Orange)
  • Internals which is what it does to process the request (Blue)
  • BackEnd which where the request should be sent to (Green)

This diagram aims to give you a high-level overview on how each AAG configuration relates to each other:

aag-high-level-diagram

The request gets to AAG through its Public/Private IP Address/Port where you have a listener configured. If this is HTTPS traffic and WAF feature is enabled, AAG will decrypt (using the provided SSL Certificate), open the package and analyze it according to the defined WAF rules. If everything is OK (and you have SSL end-to-end), it will re-encrypt it (using the provided Authentication Certificate) and, based on the listener, a rule will get applied to it in order to define to which backend service should be fordward to and using which HTTP settings. In order to verify if the backend is available for sending the request, a probe is configured (just like a regular Load Balancer does).

Internals

Going a little bit deeper on AAG, this diagram shows each one of the UI/ARM components and how they relate to each other. I’m keeping the same color coding as before in order to keep the big picture.

aag-internals

In the following sections, I’m including some ARM code snippets on how each of those pieces gets configured

Application Gateway

This is the main component where you configure which ciphers suite you want to use, if you want WAF feature enabled or not. In ARM Template, this is where you define/upload the SSLCertificates (for use in the FrontEnd, they must be PFX files) and the AuthenticationCertificates (for us in the BackEnd, they must be CER files). Although not in the diagram, RedirectRules get defined in there too.

Gateway IP Configuration

This defines in the ARM template which subnet the AAG should be deployed to. If using UI, this gets prompted during the initial deployment and it cannot be changed later.

Frontend IP Configuration

This defines which IP Address will be configured in front of the AAG

Frontend Ports

Only visible through ARM Template, this specify which Ports AAG should listen to and it’s used in conjunction with “Frontend IP Configuration”

HTTP Listeners

This is the key component of the Frontend; it defines which traffic AAG should listen from, it can be HTTP or HTTPS traffic (in spite of its name). This is very similar to an IIS Binding where you define, if needed for multi-site configuration, the host header, certificate, IP Address, Port, etc. to use. This is just for the incoming traffic and there can be multiple listeners configured to the same Frontend IP/Port.

Request Routing Rules

This is what “glues” the frontend with the backend. Basically, this tell AAG “the traffic coming through this listener should be sent to this backend service with this backend HTTP Settings and you will know the backend service is healthy based on this probes.

Backend Address Pool

This specifies which components are in the AAG backend, just that. It doesn’t specify how the request should be sent or protocol or anything. It’s just what it’s in the backend, nothing else. If the backend address pool is an IP Address (such as an App Service Environment (ASE) with an Internal Load Balancer (ILB)), the “host header” used will be the same one as the defined in the HTTP Listeners.

Backend HTTP Settings

Specifies how a request should be sent to a backend address pool; this is what protocol to use, to which port and encrypting it with what public key (certificate. CER file).

Probes

This is just any regular Load Balancer how to verify the backend is healthy and ready to get requests.

Putting All Together

As an example, this is a full ARM Template which deploys an AAG in front of an ASE with ILB; it implements SSL End-To-End. It hosts two websites (FrontEnd and BackEnd) out of the same ASE but with different URL. It also configures an HTTP to HTTPS redirect for the Frontend only.

Pre-Requirements

  • ASE should be already deployed
  • Subnet for AAG should already exists
  • A CNAME should already exists from the custom domain to the Public IP Address URI

The ARM Template

{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"hostingEnvironmentResourceGroupName": {
"type": "string"
},
"applicationGatewayVirtualNetworkName": {
"type": "string"
},
"applicationGatewaySubnetName": {
"type": "string"
},
"resourceNamePrefix": {
"type": "string"
},
"frontEndUri": {
"type": "string"
},
"backEndUri": {
"type": "string"
},
"frontend_CertPFXData": {
"type": "string"
},
"frontend_CertPFXPassword": {
"type": "securestring"
},
"backend_CertPFXData": {
"type": "string"
},
"backend_CertPFXPassword": {
"type": "securestring"
},
"ase_PublicCertData": {
"type": "string"
},
"ase_ILBIpAddress": {
"type":"string"
}
},
"variables": {
"vnet_Name": "[parameters('applicationGatewayVirtualNetworkName')]",
"aag_Name": "[concat(parameters('resourceNamePrefix'),'-CEA-GBL-AAG-001')]",
"aag_PublicIpName": "[concat(variables('aag_Name'),'-PIP001')]",
"aag_SubnetName": "[parameters('applicationGatewaySubnetName')]",
"frontend_CertName": "frontend-web",
"backend_CertName": "backend-app"
},
"resources": [
{
"name": "[variables('aag_PublicIpName')]",
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2018-06-01",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "Application Gateway - Public IP Address"
},
"sku": {
"name": "Basic"
},
"properties": {
"publicIPAllocationMethod": "Dynamic",
"idleTimeoutInMinutes": 4,
"dnsSettings": {
"domainNameLabel": "[toLower(variables('aag_PublicIpName'))]"
}
}
},
{
"name": "[variables('aag_Name')]",
"type": "Microsoft.Network/applicationGateways",
"apiVersion": "2017-10-01",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "Application Gateway"
},
"dependsOn": [
"[variables('aag_PublicIpName')]"
],
"properties": {
"sku": {
"name": "WAF_Medium",
"tier": "WAF",
"capacity": 2
},
"gatewayIPConfigurations": [
{
"name": "appGatewayIpConfig",
"properties": {
"subnet": {
"id": "[concat(resourceId(parameters('hostingEnvironmentResourceGroupName'),'Microsoft.Network/virtualNetworks', variables('vnet_Name')), '/subnets/', variables('aag_SubnetName'))]"
}
}
}
],
"frontendIPConfigurations": [
{
"name": "appGatewayFrontEndHttpsPort",
"properties": {
"PublicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses/', variables('aag_PublicIpName'))]"
}
}
}
],
"frontendPorts": [
{
"name": "appGatewayFrontendHttpPort",
"properties": {
"port": 80
}
},
{
"name": "appGatewayFrontendHttpsPort",
"properties": {
"port": 443
}
}
],
"probes":[
{
"name": "FrontEnd-Https-Probe",
"properties":{
"protocol":"Https",
"path": "/hostingstart.html",
"interval":30,
"timeout": 120,
"unhealthyThreshold": 3,
"pickHostNameFromBackendHttpSettings":false,
"minServers":0,
"match":{
"statusCodes":[
"200-399"
]
},
"host": "[parameters('frontEndUri')]"
}
},
{
"name": "BackEnd-Https-Probe",
"properties":{
"protocol":"Https",
"path": "/hostingstart.html",
"interval":30,
"timeout": 120,
"unhealthyThreshold": 3,
"pickHostNameFromBackendHttpSettings":false,
"minServers":0,
"match":{
"statusCodes":[
"200-399"
]
},
"host": "[parameters('backEndUri')]"
}
}
],
"backendAddressPools": [
{
"name": "AppServiceEnvironment",
"properties": {
"backendAddresses":[
{
"ipAddress": "[parameters('ase_ILBIpAddress')]"
}
]
}
}
],
"backendHttpSettingsCollection": [
{
"name": "FrontEnd-BackendHttpsSettings",
"properties":{
"port": 443,
"protocol": "Https",
"cookieBasedAffinity":"Disabled",
"requestTimeout": 30,
"probe":{
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/probes/FrontEnd-Https-Probe')]"
},
"hostName": "[parameters('frontEndUri')]",
"authenticationCertificates":[
{
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/authenticationCertificates/', 'ASE')]"
}
]
}
},
{
"name": "BackEnd-BackendHttpsSettings",
"properties":{
"port": 443,
"protocol": "Https",
"cookieBasedAffinity":"Disabled",
"requestTimeout": 30,
"probe":{
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/probes/BackEnd-Https-Probe')]"
},
"hostName": "[parameters('frontEndUri')]",
"authenticationCertificates":[
{
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/authenticationCertificates/', 'ASE')]"
}
]
}
}
],
"sslPolicy":{
"policyType":"Predefined",
"minProtocolVersion":"TLSv1_2"
},
"httpListeners": [
{
"name": "FrontEnd-HTTP-Listener",
"properties": {
"frontendIPConfiguration": {
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/frontendIPConfigurations/appGatewayFrontEndHttpsPort')]"
},
"frontendPort": {
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/frontendPorts/appGatewayFrontendHttpPort')]"
},
"protocol": "Http",
"hostName": "[parameters('frontEndUri')]"
}
},
{
"name": "FrontEnd-HTTPS-Listener",
"properties": {
"frontendIPConfiguration": {
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/frontendIPConfigurations/appGatewayFrontEndHttpsPort')]"
},
"frontendPort": {
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/frontendPorts/appGatewayFrontendHttpsPort')]"
},
"sslCertificate": {
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/sslCertificates/', variables('frontend_CertName'))]"
},
"protocol": "Https",
"hostName": "[parameters('frontEndUri')]",
"requireServerNameIndication": true
}
},
{
"name": "BackEnd-HTTPS-Listener",
"properties": {
"frontendIPConfiguration": {
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/frontendIPConfigurations/appGatewayFrontEndHttpsPort')]"
},
"frontendPort": {
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/frontendPorts/appGatewayFrontendHttpsPort')]"
},
"sslCertificate": {
"Id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/sslCertificates/', variables('api_CertName'))]"
},
"protocol": "Https",
"hostName": "[parameters('backEndUri')]",
"requireServerNameIndication": true
}
}
],
"redirectConfigurations": [
{
"name": "FrontEnd-HTTP2HTTPS",
"properties": {
"redirectType": "Permanent",
"targetListener": {
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/httpListeners/FrontEnd-HTTPS-Listener')]"
},
"includePath": true,
"includeQueryString": true
}
}
],
"requestRoutingRules": [
{
"Name": "FrontEnd-Redirect-HTTP-to-HTTPS",
"properties": {
"ruleType":"Basic",
"httpListener": {
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/httpListeners/FrontEnd-HTTP-Listener')]"
},
"redirectConfiguration": {
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/redirectConfigurations/FrontEnd-HTTP2HTTPS')]"
}
}
},
{
"Name": "FrontEnd-Routing",
"properties": {
"ruleType":"Basic",
"httpListener": {
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/httpListeners/FrontEnd-HTTPS-Listener')]"
},
"backendAddressPool": {
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/backendAddressPools/AppServiceEnvironment')]"
},
"backendHttpSettings": {
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/backendHttpSettingsCollection/FrontEnd-BackendHttpsSettings')]"
}
}
},
{
"Name": "BackEnd-Routing",
"properties": {
"ruleType":"Basic",
"httpListener": {
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/httpListeners/BackEnd-HTTPS-Listener')]"
},
"backendAddressPool": {
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/backendAddressPools/AppServiceEnvironment')]"
},
"backendHttpSettings": {
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', variables('aag_Name')), '/backendHttpSettingsCollection/BackEnd-BackendHttpsSettings')]"
}
}
}
],
"enableHttp2": false,
"sslCertificates": [
{
"name": "[variables('frontend_CertName')]",
"properties":{
"data": "[parameters('frontend_CertPFXData')]",
"password": "[parameters('frontend_CertPFXPassword')]"
}
},
{
"name": "[variables('backend_CertName')]",
"properties":{
"data": "[parameters('backend_CertPFXData')]",
"password": "[parameters('backend_CertPFXPassword')]"
}
}
],
"webApplicationFirewallConfiguration": {
"enabled": true,
"firewallMode": "Detection",
"ruleSetType": "OWASP",
"ruleSetVersion": "3.0"
},
"authenticationCertificates":[
{
"name": "ASE",
"properties":{
"data": "[parameters('ase_PublicCertData')]"
}
}
]
}
}
]
}