I'm trying to provision an App Service web app via PowerShell and an ARM template and connect the web app to a virtual network via a virtual network gateway (both also provisioned via ARM).
The template is reasonably straightforward. I have a single network security group to cut the vnet off from the public Internet.
{
"comments": "NSG - DENY Internet",
"type": "Microsoft.Network/networkSecurityGroups",
"name": "[parameters('private_nsg')]",
"apiVersion": "2016-03-30",
"location": "[parameters('location')]",
"properties": {
"securityRules": [
{
"name": "allow-ssh",
"properties": {
"protocol": "TCP",
"sourcePortRange": "*",
"destinationPortRange": "22",
"sourceAddressPrefix": "Internet",
"destinationAddressPrefix": "*",
"access": "Allow",
"priority": 100,
"direction": "Inbound"
}
},
{
"name": "deny-internet",
"properties": {
"protocol": "TCP",
"sourcePortRange": "*",
"destinationPortRange": "*",
"sourceAddressPrefix": "Internet",
"destinationAddressPrefix": "*",
"access": "Deny",
"priority": 200,
"direction": "Inbound"
}
}
]
},
"resources": [],
"dependsOn": []
},
and then the virtual network itself with a subnet belonging to the above nsg and also a GateWay subnet.
{
"comments": "VNet 1",
"type": "Microsoft.Network/virtualNetworks",
"name": "[parameters('vnet1')]",
"apiVersion": "2016-03-30",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"10.0.0.0/16"
]
},
"subnets": [
{
"name": "private1",
"properties": {
"addressPrefix": "10.0.1.0/24",
"networkSecurityGroup": {
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('private_nsg'))]"
}
}
},
{
"name": "GatewaySubnet",
"properties": {
"addressPrefix": "10.0.100.0/24"
}
}
]
},
"resources": [],
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('private_nsg'))]"
]
},
I then create an IP address for a virtual network gateway
{
"comments": "Gateway 1 IP",
"type": "Microsoft.Network/publicIPAddresses",
"name": "[parameters('gateway1_ip')]",
"apiVersion": "2016-03-30",
"location": "[parameters('location')]",
"properties": {
"publicIPAllocationMethod": "Dynamic",
"idleTimeoutInMinutes": 4
},
"resources": [],
"dependsOn": []
},
And the gateway itself
{
"comments": "VNet1 Gateway",
"apiVersion": "2015-05-01-preview",
"type": "Microsoft.Network/virtualNetworkGateways",
"name": "[parameters('gateway1')]",
"location": "[parameters('location')]",
"properties": {
"ipConfigurations": [
{
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": ""[concat(variables('vnet1_ref'),'/subnets/','GatewaySubnet')]""
},
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses',parameters('gateway1_ip'))]"
}
},
"name": "vnetGatewayConfig"
}
],
"gatewayType": "Vpn",
"vpnType": "RouteBased",
"vpnClientConfiguration": {
"vpnClientAddressPool": {
"addressPrefixes": [ "192.168.2.0/24"]
}
},
"enableBgp": false
},
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', parameters('gateway1_ip'))]",
"[concat('Microsoft.Network/virtualNetworks/', parameters('vnet1'))]"
]
},
I also create a Linux VM and put it inside the private subnet and then create an App Service web app (and Plan) that knows how to perform a GET request against the Linux VM's private (10...*) IP.
Once the template deploys, I run some PowerShell based on this other Stack Overflow answer.
$app1 = Get-AzureRmWebApp -ResourceGroupName $ResourceGroupName -Name $parameters["appsvc1_name"]
$app1Config = Get-AzureRmResource -ResourceName "$($app1.Name)/web" -ResourceType "Microsoft.Web/sites/config" -ResourceGroupName $resourceGroupName -ApiVersion 2015-08-01
$vnet = Get-AzureRmVirtualNetwork -ResourceGroupName $resourceGroupName -Name "$resourceGroupName-Net"
$gateway = (Get-AzureRmVirtualNetworkGateway -ResourceGroupName $ResourceGroupName)[0]
$networkProperties = @{ "vnetResourceId" = $vnet.Id }
$networkConnection = New-AzureRmResource -ResourceGroupName $resourceGroupName -Location $resourceGroupLocation -Properties $networkProperties -ResourceName "$($app1.Name)/$($vnet.Name)" -ResourceType "Microsoft.Web/sites/virtualNetworkConnections" -ApiVersion 2015-08-01 -Force
$null = Add-AzureRmVpnClientRootCertificate -ResourceGroupName $resourceGroupName -VpnClientRootCertificateName "AppServiceCertificate.cer" -PublicCertData $networkConnection.Properties.CertBlob -VirtualNetworkGatewayName $gateway.Name
At this point, if I look at my app in the portal, it looks like it's connected to the gateway (the portal says "Connected" in green and the "Certificate Status" is "Certificates are synced"). However, my app can't connect to the private VM.
If I remove the network connection in the portal and then reconnect it in the portal, my app can connect to the VM. This leads me to believe that the gateway is configured correctly and it's just the connection that's gone wrong.
The only thing I'm missing from the previous StackOverflow answer is:
$vpnPackageUri = Get-AzureRmVpnClientPackage -ResourceGroupName $resourceGroupName -VirtualNetworkGatewayName $gateway.Name -ProcessorArchitecture Amd64
$vpnPackageUri = $vpnPackageUri.Replace('"', "")
$vpnPackageProperties = @{ "vnetName" = $vnet.Name; "vpnPackageUri" = $vpnPackageUri }
$networkConnectionVPN = New-AzureRmResource -ResourceGroupName $resourceGroupName -Location $resourceGroupLocation -ResourceName "$($app1.Name)/$($vnet.Name)/primary" -ResourceType "Microsoft.Web/sites/virtualNetworkConnections/gateways" -ApiVersion 2015-08-01 -Force
Unfortunately, the last command errors out with a generic error message:
New-AzureRmResource : {"Message":"An error has occurred."}
At line:1 char:25
+ $networkConnectionVPN = New-AzureRmResource -ResourceGroupName $resourceGroupNam ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : CloseError: (:) [New-AzureRmResource], ErrorResponseMessageException
+ FullyQualifiedErrorId : InternalServerError,Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.NewAzureResourceCmdlet
So I can't tell what's actually gone wrong. Running this command doesn't allow my app to connect either; I have to manually remove the connection and re-add it in the portal.
What am I missing?