Updates on IP Restrictions for Azure App Services

Two weeks ago, I wrote about the new VNet Integration feature on Azure App Services. This has everything to do with being able to lock down downstream systems to only accept traffic coming from a specific VNet under your control, as opposed to a set of public IP addresses that are managed by Microsoft, shared with other tenants, and prone to change.

Today it’s time to follow up on that. Because we may not only want to protect downstream systems, but also the web apps themselves. For public web apps, that protection typically does not exist at the network level, but wouldn’t it be nice if there was some network-level protection option available for private apps? Until now, the only real way to do that was by employing a very pricey App Service Environment. An ASE sits in your own VNet with all the security and flexibility this brings, but it is exceedingly expensive.

But the need to actually deploy an ASE has now disappeared if all you want to do is lock down access to a web app so that it’s only available from your network: the Microsoft.Web service endpoint has just become available!

That’s right: you can now configure a service endpoint for Azure App Services on one or more of your subnets:


Then, on the Web App, you can configure IP restrictions to only allow your subnet to access the Service Endpoint:


So, there you have it: not only can you rigorously protect access to downstream systems while allowing traffic originating from Azure App Services; you can now also protect Azure App Services itself to only allow internal traffic!

Enabling Azure App Service VNet Integration ‘v2’ from CI/CD

If you’re anything like me, you want to automate everything from deploying your basic Azure infrastructure all the way to the application code. And the bar is set exceedingly high for deviations to this rule.

Sometimes – and especially with new and/or preview features – that requires some extra work, because support for such features in your deployment technology of choice may not yet be available.

Take the case of the new VNet Integration feature for Azure Web Apps. All very cool that it can be set through the portal, but that’s about the last thing I want to do when creating robust deployments. For me, ARM templates are the technology of choice when it comes to deploying Azure resources, even though a case can be made for alternatives such as Azure CLI. But in the case of VNet integration, both ARM and the Azure CLI are not an option yet at the time of writing. There aren’t even proper Azure Powershell commands to get this done.

In situations like this, I head over to the Azure Resource Explorer, and see if I can reverse-engineer what happens in the Azure Resource Manager API when I change a setting through the portal. Armed with that knowledge, I’m able to craft an API call that gets the job done. Wrapped in a fairly simple Powershell script, it may end up like this:

[string]$subnetResourceId, ## Something along the lines of '/subscriptions/[subscriptionid]/resourceGroups/[resourceGroupName]/providers/Microsoft.Network/virtualNetworks/[vnetname]/subnets/[subnetname]'
function Get-AccessToken {
$context = Get-AzureRmContext
$tokenCache = $context.TokenCache
$cachedTokens = $tokenCache.ReadItems() `
| Sort-Object Property ExpiresOn Descending
$accessToken = $cachedTokens[0].AccessToken
function Set-VNetIntegration {
$app = Get-AzureRMWebApp Name $webappName
$resourceGroup = $app.ResourceGroup
$location = $app.Location
$body = "{
""location"": ""$location"",
""properties"": {
""subnetResourceId"": ""$subnetResourceId""
$url = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.Web/sites/$webappName/config/virtualNetwork?api-version=2018-02-01"
$accessToken = Get-AccessToken
Invoke-RestMethod Uri $url Method PUT Headers @{Authorization = "Bearer $accessToken"} ContentType 'application/json' Body $body
$subscriptionId = (Get-AzureRMContext).Subscription.Id
if ($subscriptionId -eq $null) {
throw "Not logged in. Please login using Connect-AzureRmAccount and select the correct subscription using Select-AzureRmSubscription; then try again"

Some remarks here are in order, the most important of which is that the subnet with which the app is integrated, must be preconfigured with a delegation to Microsoft.Web/serverFarms. This is done automatically when you enable VNet Integration through the portal, but not when making the API call yourself like in the script above. Fortunately, this actually is settable through ARM, so no need to include it in the script.

Second, the script works with the ‘old’ AzureRM modules for all Azure interactions apart from the actual API call. For me, this works best for interoperability with Azure DevOps hosted agents, which didn’t support the new Az modules yet at the time I created this script. But of course it can quite easily be adapted to the new modules.

And lastly, this is not really pretty and production-ready code (but then again, is Powershell ever pretty?). It can certainly be improved upon, but for me this is a piece of dispensable code in the sense that I take it out of my pipeline and discard it, the minute ARM or Azure CLI support becomes available. It’s just that I don’t feel like waiting for that to happen before I can automate deployments that use this feature.

Hope this helps anyone looking for a way to automate the new VNet Integration feature, and just maybe it can also serve as a bit of inspiration as to how you could create your own temporary solutions to features not available in your primary deployment technology of choice.