Archive for November, 2016
The myth of Azure Application Gateway – Part 2
In part 1 of this article I have gone through creating Azure Applications Gateways (AGW) using Powershell which is a powerful way of deploying resources on Azure, using recursive functions and methods you could build a complex solution in few lines. Unlike Powershell, JSON is a static solution. It gives you a way of creating a baseline deployment, I still haven’t found a way of controlling sub-resources (which isn’t governed by the JSON Copy() function).
In this article I will go over the same deployment using a JSON template, this only serve as a reference point for your deployment. I will use the same Azure AGW configuration I used in part 1 to keep both deployments consistent.
When building Azure AGW, same prerequisites apply to the JSON method. There is a subtle difference which I will go over when I get to that point.
So this time I have prepared a Visio diagram to simplify our deployment and understand what we are trying to achieve – see below.
The diagram shows a Web server hosting four websites running a combination of HTTP HTTPS with authentication (using Basic authentication) or Anonymous access.
The only thing different in comparison to previous Powershell deployment is the steps we take to process SSL certificates and passing them over to the JSON template.
$pfxCert = Get-Content "pathToYourSSL.pfx" -Encoding Byte $pfxCert = [System.Convert]::ToBase64String($pfxCert)
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "applicationGatewayName": { "type": "string", "minLength": 1 }, "location": { "type": "string", "minLength": 1, "metadata": { "description": "Region" } }, "virtualNetworkName": { "type": "string", "minLength": 1, "metadata": { "description": "Application Gateway vnet name" } }, "virtualNetworkResourceGroup": { "type": "string", "minLength": 1, "metadata": { "description": "Application Gateway vnet resource group name" } }, "subnetName": { "type": "string", "minLength": 1, "metadata": { "description": "Application Gateway subnet name" } }, "publicIPAddressName": { "type": "string", "minLength": 1, "metadata": { "description": "PIP resource name" } }, "certData": { "type": "string", "minLength": 1, "metadata": { "description": "SSL data in binary Base64 format" } }, "certPassword": { "type": "securestring", "minLength": 1, "metadata": { "description": "SSL certificate password" } }, "authCert": { "type": "string", "minLength": 1, "metadata": { "description": "Public authentication certificate - in binary Base64 format" } }, "Authhostname": { "type": "string", "minLength": 1, "metadata": { "description": "Backend hostheader/Host SNI names used as part of your HTTPS request" } }, "Noauthhostname": { "type": "string", "minLength": 1, "metadata": { "description": "Backend hostheader/Host SNI names used as part of your HTTPS request" } }, "backendIPAddress": { "type": "string", "minLength": 1, "metadata": { "description": "Backend web server IP address" } } }, "variables": { "applicationGatewayID": "[resourceId('Microsoft.Network/applicationGateways',parameters('applicationGatewayName'))]", "capacity": 2, "skuName": "Standard_Medium", "subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('subnetName'))]", "vnetID": "[resourceId(parameters('virtualNetworkResourceGroup'),'Microsoft.Network/virtualNetworks',parameters('virtualNetworkName'))]" }, "resources": [ { "apiVersion": "2015-06-15", "type": "Microsoft.Network/publicIPAddresses", "name": "[parameters('publicIPAddressName')]", "location": "[parameters('location')]", "properties": { "publicIPAllocationMethod": "Dynamic" }, "tags": { "displayName": "[parameters('publicIPAddressName')]" } }, { "apiVersion": "2016-06-01", "name": "[parameters('applicationGatewayName')]", "type": "Microsoft.Network/applicationGateways", "location": "[resourceGroup().location]", "dependsOn": [ "[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]" ], "properties": { "sku": { "name": "[variables('skuName')]", "tier": "Standard", "capacity": "[variables('capacity')]" }, "sslCertificates": [ { "name": "appGatewaySslCert", "properties": { "data": "[parameters('certData')]", "password": "[parameters('certPassword')]", "publicCertData": "[parameters('authCert')]" } } ], "authenticationCertificates": [ { "name": "PublicCert", "properties": { "data": "[parameters('authCert')]" } } ], "gatewayIPConfigurations": [ { "name": "appGatewayIpConfig", "properties": { "subnet": { "id": "[variables('subnetRef')]" } } } ], "frontendIPConfigurations": [ { "name": "appGatewayFrontendIP", "properties": { "PublicIPAddress": { "id": "[resourceId('Microsoft.Network/publicIPAddresses',parameters('publicIPAddressName'))]" } } } ], "frontendPorts": [ { "name": "appGWFEHttps", "properties": { "Port": 443 } }, { "name": "appGWFEHttp", "properties": { "Port": 80 } } ], "probes": [ { "name": "CustomProbe", "properties": { "protocol": "Https", "host": "auth.simplesite.com:443", "path": "/noauth/iisstart.htm", "interval": 5, "timeout": 120, "unhealthyThreshold": 2 } } ], "backendAddressPools": [ { "name": "appGatewayBackendPool", "properties": { "BackendAddresses": [ { "IpAddress": "[parameters('backendIPAddress')]" } ] } } ], "backendHttpSettingsCollection": [ { "name": "appGWBEHttpSettings", "properties": { "Port": 80, "Protocol": "Http", "CookieBasedAffinity": "Disabled" } }, { "name": "appGWBEHttpsSettings", "properties": { "Port": 443, "Protocol": "Https", "CookieBasedAffinity": "Disabled", "probe": { "id": "[concat(variables('applicationGatewayID'), '/probes/CustomProbe')]" }, "authenticationCertificates": [ { "id": "[concat(variables('applicationGatewayID'), '/authenticationCertificates/PublicCert')]" } ] } } ], "httpListeners": [ { "name": "appGatewayHttpListener", "properties": { "FrontendIPConfiguration": { "Id": "[concat(variables('applicationGatewayID'), '/frontendIPConfigurations/appGatewayFrontendIP')]" }, "FrontendPort": { "Id": "[concat(variables('applicationGatewayID'), '/frontendPorts/appGWFEHttp')]" }, "Protocol": "Http", "SslCertificate": null } }, { "name": "appGatewayHttpsListenerNoAuth", "properties": { "FrontendIPConfiguration": { "Id": "[concat(variables('applicationGatewayID'), '/frontendIPConfigurations/appGatewayFrontendIP')]" }, "FrontendPort": { "Id": "[concat(variables('applicationGatewayID'), '/frontendPorts/appGWFEHttps')]" }, "Protocol": "Https", "hostName": "[parameters('Noauthhostname')]", "SslCertificate": { "Id": "[concat(variables('applicationGatewayID'), '/sslCertificates/appGatewaySslCert')]" }, "RequireServerNameIndication": "true" } }, { "name": "appGatewayHttpsListenerAuth", "properties": { "FrontendIPConfiguration": { "Id": "[concat(variables('applicationGatewayID'), '/frontendIPConfigurations/appGatewayFrontendIP')]" }, "FrontendPort": { "Id": "[concat(variables('applicationGatewayID'), '/frontendPorts/appGWFEHttps')]" }, "Protocol": "Https", "hostName": "[parameters('Authhostname')]", "SslCertificate": { "Id": "[concat(variables('applicationGatewayID'), '/sslCertificates/appGatewaySslCert')]" }, "RequireServerNameIndication": "true" } } ], "requestRoutingRules": [ { "Name": "HTTPrule", "properties": { "RuleType": "Basic", "httpListener": { "id": "[concat(variables('applicationGatewayID'), '/httpListeners/appGatewayHttpListener')]" }, "backendAddressPool": { "id": "[concat(variables('applicationGatewayID'), '/backendAddressPools/appGatewayBackendPool')]" }, "backendHttpSettings": { "id": "[concat(variables('applicationGatewayID'), '/backendHttpSettingsCollection/appGWBEHttpSettings')]" } } }, { "Name": "HTTPSruleNoAuth", "properties": { "RuleType": "Basic", "httpListener": { "id": "[concat(variables('applicationGatewayID'), '/httpListeners/appGatewayHttpsListenerNoAuth')]" }, "backendAddressPool": { "id": "[concat(variables('applicationGatewayID'), '/backendAddressPools/appGatewayBackendPool')]" }, "backendHttpSettings": { "id": "[concat(variables('applicationGatewayID'), '/backendHttpSettingsCollection/appGWBEHttpsSettings')]" } } }, { "Name": "HTTPSruleAuth", "properties": { "RuleType": "Basic", "httpListener": { "id": "[concat(variables('applicationGatewayID'), '/httpListeners/appGatewayHttpsListenerAuth')]" }, "backendAddressPool": { "id": "[concat(variables('applicationGatewayID'), '/backendAddressPools/appGatewayBackendPool')]" }, "backendHttpSettings": { "id": "[concat(variables('applicationGatewayID'), '/backendHttpSettingsCollection/appGWBEHttpsSettings')]" } } } ] } } ] }
The myth of Azure Application Gateways – Part 1
Azure Application Gateways is a layer 7 reverse proxy service offered as a PaaS to general public. It supports SSL offloading, which means you can terminate your SSL connection at the Application Gateway and connect to the backend server using HTTP traffic or initiate a new SSL connection to your backend service.
This is all well and good, simple and painless if you have a single backend server with a single website. The complexity of the solution increases as the backend start leveraging more of the IIS functionalities such as Windows/NTLM authentication, SNI and host headers or various SSL certificates used for each sub-site (if you have multiple sites running on the same IIS server).
Before even starting to look at designing your Azure Application Gateway, there are few guidelines you will need to follow:
- You should have an empty default site.
- If using both HTTP/HTTPS protocols on any of the sub-sites, the default website should be listening on both 80 and 443.
- In the case of HTTPS the default site will need to be loaded with a single SSL certificate that will primarily be used by the Application Gateway to authenticate against the server.
- Not running SNI on default website.
- If you are running NTLM or Windows authentication on any of the sites (except form based authentication) then you will need a site/page that allow anonymous authentication to be used for Application Gateway custom probe.
- Use IP address for the backend pool rather than FQDN.
The above will save you a lot of hassle while implementing and configuring your Application Gateway to work with your backend web server.
Microsoft have fixed few issues we were experiencing recently with Application Gateways around SSL and custom probes.
There are two ways available to deploy an Application Gateway, Powershell or JSON template. The latter is preferable to ensure consistency at each deployment. This article is in two parts, in this article I will be using Powershell to deploy an Application Gateway.
Prerequisites:
- SSL private key in PFX format for all sites using SSL
- SSL public key in CER format for default site
- IP address of the backend web server
- Front and backend listening port
- Site/page with anonymous access if requiring authentication
Powershell code below would deploy an Application Gateway listening on two ports (80,443). The backend consists of four sites with SNI and host headers enabled, two sites run under port 80, one of them require basic authentication. Another two sites run under port 443 bound with self-signed SSL cert for testing, one of the sites has basic authentication turned on. This would test the four common scenarios of a typical deployment.
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 |
Login-AzureRmAccount Get-AzureRmSubscription | Select Subscription* | out-gridview -Title "Select your Azure Subscription" -OutputMode Single | Select-AzureRmSubscription $ApplicationGWName = "ApplicationGatewayName" $AppGatwayPIPName = "ApplicationGatewayPublicIPName" $vnetName = 'vNETName' $VNetResourceGroup = 'vNETResourceGroup' $AppGWSubnetName = 'ApplicationGatewaySubnetName' $ResouceGroupName = "ApplicationGatewayResourceGroupName" $Location = "UK South" $BackendIPaddresses = "BackendServerIP" New-AzureRmResourceGroup -Name $ResouceGroupName -Location $Location #Prompt for Certificate password $SecurePassword = Read-Host -AsSecureString "Enter Certificate password" $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword) $UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) #Retrive Virtual Network and Application Gateway information $vnet = Get-AzureRmvirtualNetwork -Name $VnetName -ResourceGroupName $VNetResourceGroup $gwSubnet = Get-AzureRmVirtualNetworkSubnetConfig -Name $AppGWSubnetName -VirtualNetwork $vnet #Create Public IP address for Application Gateway $publicip = New-AzureRmPublicIpAddress -ResourceGroupName $ResouceGroupName -Name $AppGatwayPIPName -Location $Location -AllocationMethod Dynamic #$publicip = Get-AzureRmPublicIpAddress -Name $AppGatwayPIPName -ResourceGroupName $ResouceGroupName #Create an application gateway IP configuration, this setting configures what subnet the application gateway uses $gipconfig = New-AzureRmApplicationGatewayIPConfiguration -Name 'appGatewayIpConfig' -SubnetId $gwSubnet.Id -Verbose #Create a front-end IP configuration, this setting maps the public ip address to the front-end of the application gateway $fipconfig = New-AzureRmApplicationGatewayFrontendIPConfig -Name 'appGatewayFrontendIP' -PublicIPAddress $publicip #Configure the back-end IP address pool with the IP addresses of the backend web servers $pool = New-AzureRmApplicationGatewayBackendAddressPool -Name 'appGatewayBackendPool' -BackendIPAddresses $BackendIPaddresses #Configure the front-end IP port for the public IP endpoint $fp = New-AzureRmApplicationGatewayFrontendPort -Name 'appGatewayFrontendPorthttps' -Port 443 $fp1 = New-AzureRmApplicationGatewayFrontendPort -Name 'appGatewayFrontendPorthttp' -Port 80 #Configure the certificate for the application gateway $cert = New-AzureRmApplicationGatewaySslCertificate -Name wildcard -CertificateFile '.\selfsignedcert.pfx' #Create the HTTPS listener for the application gateway. Assign the front-end ip configuration, port, and ssl certificate to use $listener1 = New-AzureRmApplicationGatewayHttpListener -Name authHTTPS -Protocol Https -FrontendIPConfiguration $fipconfig -FrontendPort $fp ` -HostName "auth.simplesite.com" -RequireServerNameIndication true -SslCertificate $cert $listener2 = New-AzureRmApplicationGatewayHttpListener -Name noauthHTTPS -Protocol Https -FrontendIPConfiguration $fipconfig -FrontendPort $fp ` -HostName "noauth.simplesite.com" -RequireServerNameIndication true -SslCertificate $cert $listener3 = New-AzureRmApplicationGatewayHttpListener -Name authHTTP -Protocol Http -FrontendIPConfiguration $fipconfig ` -FrontendPort $fp1 -HostName "auth.simplesite.com" $listener4 = New-AzureRmApplicationGatewayHttpListener -Name noauthHTTP -Protocol Http -FrontendIPConfiguration $fipconfig ` -FrontendPort $fp1 -HostName "noauth.simplesite.com" #Upload the certificate to be used on the ssl enabled backend pool resources - authentication certificate $authcert = New-AzureRmApplicationGatewayAuthenticationCertificate -Name 'sanauthcert' -CertificateFile '.\publickey.cer' #Configure the application gateway back-end http settings. Assign the certificate uploaded in the preceding command to the http settings $poolSetting1 = New-AzureRmApplicationGatewayBackendHttpSettings -Name 'wildcard' -Port 443 -Protocol Https ` -CookieBasedAffinity Enabled -AuthenticationCertificates $authcert $poolSetting2 = New-AzureRmApplicationGatewayBackendHttpSettings -Name 'unsecured' -Port 80 -Protocol Http -CookieBasedAffinity Enabled #Create a load balancer routing rule, Basic Round Robin $rule1 = New-AzureRmApplicationGatewayRequestRoutingRule -Name 'authHTTPrule' -RuleType basic -BackendHttpSettings $poolSetting2 ` -HttpListener $listener3 -BackendAddressPool $pool $rule2 = New-AzureRmApplicationGatewayRequestRoutingRule -Name 'noauthHTTPrule' -RuleType basic -BackendHttpSettings $poolSetting2 ` -HttpListener $listener4 -BackendAddressPool $pool $rule3 = New-AzureRmApplicationGatewayRequestRoutingRule -Name 'authHTTPSrule' -RuleType basic -BackendHttpSettings $poolSetting1 ` -HttpListener $listener1 -BackendAddressPool $pool $rule4 = New-AzureRmApplicationGatewayRequestRoutingRule -Name 'noauthHTTPSrule' -RuleType basic -BackendHttpSettings $poolSetting1 ` -HttpListener $listener2 -BackendAddressPool $pool #Configure the instance size of the application gateway $sku = New-AzureRmApplicationGatewaySku -Name Standard_Small -Tier Standard -Capacity 1 #Create the Application Gateway $appgw = New-AzureRmApplicationGateway ` -Name $ApplicationGWName ` -SslCertificates $cert ` -ResourceGroupName $ResouceGroupName ` -Location $Location ` -BackendAddressPools $pool ` -BackendHttpSettingsCollection $poolSetting1,$poolSetting2 ` -FrontendIpConfigurations $fipconfig ` -GatewayIpConfigurations $gipconfig ` -FrontendPorts $fp,$fp1 ` -HttpListeners $listener1,$listener2,$listener3,$listener4 ` -RequestRoutingRules $rule1,$rule2,$rule3,$rule4 ` -Sku $sku ` -AuthenticationCertificates $authcert ` -Verbose |
To be continued …..