Tutorial: Isolate back-end communication in Azure App Service with Virtual Network integration
In this article you will configure an App Service app with secure, network-isolated communication to backend services. The example scenario used is in Tutorial: Secure Cognitive Service connection from App Service using Key Vault. When you're finished, you have an App Service app that accesses both Key Vault and Cognitive Services through an Azure virtual network (VNet), and no other traffic is allowed to access those back-end resources. All traffic will be isolated within your VNet using VNet integration and private endpoints.
As a multi-tenanted service, outbound network traffic from your App Service app to other Azure services shares the same environment with other apps or even other subscriptions. While the traffic itself can be encrypted, certain scenarios may require an extra level of security by isolating back-end communication from other network traffic. These scenarios are typically accessible to large enterprises with a high level of expertise, but App Service puts it within reach with VNet integration.

With this architecture:
- Public traffic to the back-end services is blocked.
- Outbound traffic from App Service is routed to the VNet and can reach the back-end services.
- App Service is able to perform DNS resolution to the back-end services through the private DNS zones.
What you will learn:
- Create a VNet and subnets for App Service VNet integration
- Create private DNS zones
- Create private endpoints
- Configure VNet integration in App Service
Prerequisites
The tutorial assumes that you have followed the Tutorial: Secure Cognitive Service connection from App Service using Key Vault and created the language detector app.
The tutorial continues to use the following environment variables from the previous tutorial. Make sure you set them properly.
groupName=myKVResourceGroup
region=westeurope
csResourceName=<cs-resource-name>
appName=<app-name>
vaultName=<vault-name>
Create VNet and subnets
Create a VNet. Replace <virtual-network-name> with a unique name.
# Save vnet name as variable for convenience vnetName=<virtual-network-name> az network vnet create --resource-group $groupName --location $region --name $vnetName --address-prefixes 10.0.0.0/16Create a subnet for the App Service VNet integration.
az network vnet subnet create --resource-group $groupName --vnet-name $vnetName --name vnet-integration-subnet --address-prefixes 10.0.0.0/24 --delegations Microsoft.Web/serverfarmsFor App Service, the VNet integration subnet is recommended to have a CIDR block of
/26at a minimum (see VNet integration subnet requirements)./24is more than sufficient.--delegations Microsoft.Web/serverfarmsspecifies that the subnet is delegated for App Service VNet integration.Create another subnet for the private endpoints.
az network vnet subnet create --resource-group $groupName --vnet-name $vnetName --name private-endpoint-subnet --address-prefixes 10.0.1.0/24 --disable-private-endpoint-network-policiesFor private endpoint subnets, you must disable private endpoint network policies.
Create private DNS zones
Because your Key Vault and Cognitive Services resources will sit behind private endpoints, you need to define private DNS zones for them. These zones are used to host the DNS records for private endpoints and allow the clients to find the back-end services by name.
Create two private DNS zones, one for your Cognitive Services resource and one for your key vault.
az network private-dns zone create --resource-group $groupName --name privatelink.cognitiveservices.azure.com az network private-dns zone create --resource-group $groupName --name privatelink.vaultcore.azure.netFor more information on these settings, see Azure Private Endpoint DNS configuration
Link the private DNS zones to the VNet.
az network private-dns link vnet create --resource-group $groupName --name cognitiveservices-zonelink --zone-name privatelink.cognitiveservices.azure.com --virtual-network $vnetName --registration-enabled False az network private-dns link vnet create --resource-group $groupName --name vaultcore-zonelink --zone-name privatelink.vaultcore.azure.net --virtual-network $vnetName --registration-enabled False
Create private endpoints
In the private endpoint subnet of your VNet, create a private endpoint for your key vault.
# Get Cognitive Services resource ID csResourceId=$(az cognitiveservices account show --resource-group $groupName --name $csResourceName --query id --output tsv) az network private-endpoint create --resource-group $groupName --name securecstext-pe --location $region --connection-name securecstext-pc --private-connection-resource-id $csResourceId --group-id account --vnet-name $vnetName --subnet private-endpoint-subnetCreate a DNS zone group for the Cognitive Services private endpoint. DNS zone group is a link between the private DNS zone and the private endpoint. This link helps you to auto update the private DNS Zone when there is an update to the private endpoint.
az network private-endpoint dns-zone-group create --resource-group $groupName --endpoint-name securecstext-pe --name securecstext-zg --private-dns-zone privatelink.cognitiveservices.azure.com --zone-name privatelink.cognitiveservices.azure.comBlock public traffic to the Cognitive Services resource.
az rest --uri $csResourceId?api-version=2021-04-30 --method PATCH --body '{"properties":{"publicNetworkAccess":"Disabled"}}' --headers 'Content-Type=application/json' # Repeat following command until output is "Succeeded" az cognitiveservices account show --resource-group $groupName --name $csResourceName --query properties.provisioningStateNote
Make sure the provisioning state of your change is
"Succeeded". Then you can observe the behavior change in the sample app. You can still load the app, but if you try click the Detect button, you get anHTTP 500error. The app has lost its connectivity to the Cognitive Services resource through the shared networking.Repeat the steps above for the key vault.
# Create private endpoint for key vault vaultResourceId=$(az keyvault show --name $vaultName --query id --output tsv) az network private-endpoint create --resource-group $groupName --name securekeyvault-pe --location $region --connection-name securekeyvault-pc --private-connection-resource-id $vaultResourceId --group-id vault --vnet-name $vnetName --subnet private-endpoint-subnet # Create DNS zone group for the endpoint az network private-endpoint dns-zone-group create --resource-group $groupName --endpoint-name securekeyvault-pe --name securekeyvault-zg --private-dns-zone privatelink.vaultcore.azure.net --zone-name privatelink.vaultcore.azure.net # Block public traffic to key vault az keyvault update --name $vaultName --default-action DenyForce an immediate refetch of the key vault references in your app by resetting the app settings (for more information, see Rotation).
az webapp config appsettings set --resource-group $groupName --name $appName --settings CS_ACCOUNT_NAME="@Microsoft.KeyVault(SecretUri=$csResourceKVUri)" CS_ACCOUNT_KEY="@Microsoft.KeyVault(SecretUri=$csKeyKVUri)"Note
Again, you can observe the behavior change in the sample app. You can no longer load the app because it can no longer access the key vault references. The app has lost its connectivity to the key vault through the shared networking.
The two private endpoints are only accessible to clients inside the VNet you created. You can't even access the secrets in the key vault through Secrets page in the Azure portal, because the portal accesses them through the public internet (see Manage the locked down resources).
Configure VNet integration in your app
Scale the app up to Standard tier. VNet integration requires Standard tier or above (see Integrate your app with an Azure virtual network).
az appservice plan update --name $appName --resource-group $groupName --sku S1Unrelated to our scenario but also important, enforce HTTPS for inbound requests.
az webapp update --resource-group $groupName --name $appName --https-onlyEnable VNet integration on your app.
az webapp vnet-integration add --resource-group $groupName --name $appName --vnet $vnetName --subnet vnet-integration-subnetVNet integration allows outbound traffic to flow directly into the VNet. By default, only local IP traffic defined in RFC-1918 is routed to the VNet, which is what you need for the private endpoints. To route all your traffic to the VNet, see Manage virtual network integration routing. Routing all traffic can also be used if you want to route internet traffic through your VNet, such as through an Azure VNet NAT or an Azure Firewall.
In the browser, navigate to
<app-name>.azurewebsites.netagain and wait for the integration to take effect. If you get an HTTP 500 error, wait a few minutes and try again. If you can load the page and get detection results, then you're connecting to the Cognitive Services endpoint with key vault references.Note
If keep getting HTTP 500 errors after a long time, it may help to force a refetch of the key vault references again, like so:
az webapp config appsettings set --resource-group $groupName --name $appName --settings CS_ACCOUNT_NAME="@Microsoft.KeyVault(SecretUri=$csResourceKVUri)" CS_ACCOUNT_KEY="@Microsoft.KeyVault(SecretUri=$csKeyKVUri)"
Manage the locked down resources
Depending on your scenarios, you may not be able to manage the private endpoint protected resources through the Azure portal, Azure CLI, or Azure PowerShell (for example, Key Vault). These tools all make REST API calls to access the resources through the public internet, and are blocked by your configuration. Here are a few options for accessing the locked down resources:
- For Key Vault, add the public IP of your local machine to view or update the private endpoint protected secrets.
- If your on premises network is extended into the Azure VNet through a VPN gateway or ExpressRoute, you can manage the private endpoint protected resources directly from your on premises network.
- Manage the private endpoint protected resources from a jump server in the VNet.
- Deploy Cloud Shell into the VNet.
Clean up resources
In the preceding steps, you created Azure resources in a resource group. If you don't expect to need these resources in the future, delete the resource group by running the following command in the Cloud Shell:
az group delete --name $groupName
This command may take a minute to run.