Connect to private endpoints with Azure Functions
Published Jun 16 2020 07:04 PM 45.8K Views
Microsoft

As enterprises continue to adopt serverless (and Platform-as-a-Service, or PaaS) solutions, they often need a way to integrate with existing resources on a virtual network. These existing resources could be databases, file storage, message queues or event streams, or REST APIs. In doing so, those interactions need to take place within the virtual network. Until relatively recently, combining serverless/PaaS offerings with traditional network access restrictions was complex, if not nearly impossible.

 

With the introduction of Azure Virtual Network service endpoints and private endpoints, it’s becoming easier for enterprises to realize the benefits of serverless, while also complying with necessary virtual network access controls.

 

This post will detail how to configure an Azure Function to work with Azure resources using private endpoints. Private endpoints ensure that the designated resources are accessible only via the specified virtual network. The Azure Function app will communicate with designated resources using a resource-specific private IP address (e.g. 10.100.0/24 address space). This provides an additional level of network-based security and control.

 

The sample shown in this post discusses the following key concepts necessary to use private endpoints with Azure Functions:

  • Azure Function with blob trigger and CosmosDB output binding
  • Azure Function Premium plan with Virtual Network Integration enabled
  • Virtual network
  • Configuring private endpoints for Azure resources
    • Azure Storage private endpoints
    • Azure Cosmos DB private endpoint
  • Using private Azure DNS zones

Additionally, the sample uses an Azure VM and Azure Bastion in order to access Azure resources within the virtual network. The VM and Azure Bastion setup is not discussed in this post. If you want to learn more about using Azure Bastion, please refer to https://docs.microsoft.com/azure/bastion/bastion-overview

 

Please view the full sample and related artifacts on the Microsoft Code Samples site.

 

Architecture Overview

 

The following diagram shows the high-level architecture of the solution to be created:

high-level-architecture.jpg

Deployment

In order to get started with this sample, you’ll need an Azure subscription. If you don’t have one, you can get a free Azure account at https://azure.microsoft.com/free/.

 

Prerequisites

 

Deploy the Resource Manager template

An Azure Resource Manager (ARM) template is available to provision the necessary Azure resources. The template will also create the application settings needed by the Azure Function sample code. The Azure CLI can be used to deploy the template:

resourceGroupName="functions-private-endpoints"
location="eastus"
now=`date +%Y%m%d-%H%M%S`
deploymentName="azuredeploy-$now"

echo "Creating resource group '$resourceGroupName' in region '$location' . . ."
az group create --name $resourceGroupName --location $location

echo "Deploying main template . . ."
az deployment group create -g $resourceGroupName --template-file azuredeploy.json --parameters azuredeploy.parameters.json --name $deploymentName

 

Deploy the code

The function can be published manually by using the Azure Function Core Tools:

 

 

 

 

 

func azure functionapp publish <function-app-name>

 

 

 

 

 

Virtual Network

One of the first components to set up is the virtual network. Nearly all other Azure services in this sample are either provisioned into, or integrated with, the virtual network. After all, this sample is about using private endpoints, and private endpoints go along with a virtual network (can’t have one without the other).

 

The sample uses four subnets:

  1. Subnet for Azure Function virtual network integration. This subnet is delegated to the function.
  2. Subnet for private endpoints. Private IP addresses are allocated from this subnet.
  3. Subnet for the virtual machine. The template creates a VM which is placed within this subnet.
  4. Subnet for the Azure Bastion host.

 

Virtual Network (VNet) Integration

 

In order for the function to access resources within the virtual network, VNet Integration is needed. The matrix of Azure Functions networking features shows that there are currently three options for VNet Integration:

  1. Azure Functions Premium plan
  2. Azure App Service Plan
  3. Azure App Service Environment

An Azure Function Premium plan is used in this sample. By using an Azure Function Premium plan with VNet Integration enabled, the function is able to access Azure Storage and CosmosDB via the configured private endpoints.

 

Please refer to the official documentation for more information on using Azure Functions with virtual network integration.

 

Azure Function

 

The function used in this sample is based on a simplified concept of processing data from CSV files.  At a high level, the function logic is as follows:

 

  1. Trigger on a new CSV-formatted file being available in a specific Azure Storage blob container
  2. Convert the CSV file to JSON
  3. Save the JSON document to CosmosDB via an output binding

 

The function is invoked via an Azure Storage blob trigger. The storage account used by the blob trigger is configured with a private endpoint. The function assumes the file is in a CSV format, and then converts the CSV content to JSON. The resulting JSON document is saved to an Azure CosmosDB collection via an output binding.

 

 

 

 

 

[FunctionName("WidgetOrdersFunction")]
public static async Task ProcessOrderDataFiles(
    [BlobTrigger("%ContainerName%/{blobName}", Connection = "WidgetsAzureStorageConnection")] Stream myBlobStream,
    string blobName,
    [CosmosDB(
        databaseName: "%CosmosDbName%",
        collectionName: "%CosmosDbCollectionName%",
        ConnectionStringSetting = "CosmosDBConnection")] IAsyncCollector<string> items,
    ILogger logger)
{
    logger.LogInformation($"C# Blob trigger function processed blob of name '{blobName}' with size of {myBlobStream.Length} bytes");

    var jsonObject = await ConvertCsvToJsonAsync(myBlobStream);

    foreach (var item in jsonObject)
    {
        await items.AddAsync(JsonConvert.SerializeObject(item));
    }
}

 

 

 

 

 

Azure Function Configuration

There are a few important details about the configuration of the function.

 

Run from Package

 

The function is configured to run from a deployment package. As such, the package is persisted in an Azure File share referenced by the WEBSITE_CONTENTAZUREFILECONNECTIONSTRING application setting. Please review the section below on Azure Storage Private Endpoints for why this is important in this scenario.

 

Virtual Network Triggers

Virtual network trigger support must be enabled in order for the function to trigger against resources using a private endpoint. Virtual network trigger support can be enabled via the Azure portal, the Azure CLI, or via an ARM template (as done in this sample).

 

 

 

 

 

{
    "type": "config",
    "name": "web",
    "apiVersion": "2019-08-01",
    "dependsOn": [
        "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]",
        "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
    ],
    "properties": {
        "functionsRuntimeScaleMonitoringEnabled": true
    }
}

 

 

 

 

 

View the full ARM template here.

 

Azure DNS Private Zones

 

When using VNet Integration, the function app uses the same DNS server that is configured for the virtual network. To work with a private endpoint, the default configuration needs to be overridden. In order to make calls to a resource using a private endpoint, it is necessary to integrate with Azure DNS Private Zones.

 

Private endpoints automatically create Azure DNS Private Zones. The Azure DNS Private Zone contains the details on how to route requests to the private IP address for the designated Azure service. Therefore, it is necessary to configure the app to use a specific Azure DNS server, and also route all network traffic into the virtual network. This is accomplished by setting the following application settings:

 

Name Value
WEBSITE_DNS_SERVER 168.63.129.16
WEBSITE_VNET_ROUTE_ALL 1

 

Azure Storage Private Endpoints

 

Azure Functions requires an Azure Storage account for persisting runtime metadata and metadata related to various triggers. The official Microsoft documentation indicates that it is currently not possible to use Azure Functions with a storage account which uses virtual network restrictions. While that’s mostly true, there is a workaround.

 

The workaround being that it is possible to put virtual network restrictions on the Azure storage account referenced via the AzureWebJobsStorage application setting. However, if that is done, then a separate storage account - one without network restrictions - is needed.

 

The other (without network restrictions) storage account needs to be referenced via the WEBSITE_CONTENTAZUREFILECONNECTIONSTRING application setting. It is this storage account that will contain an Azure File share used to persist the function’s application code.

 

Furthermore, for this sample, a third storage account is used. This third storage account is used by the sample application code - it’s where the CSV file will be placed. The Function blob trigger will pick up this file and the function will do work against it. This storage account will also use a private endpoint.

 

The sample will use three Azure storage related application settings:

 

Name Description Uses a Private Endpoint?
WidgetsAzureStorageConnection The connection string for an Azure Storage account used by the function’s blob trigger. Yes
AzureWebJobsStorage The connection string for an Azure Storage account required by Azure Functions. Yes
WEBSITE_CONTENTAZUREFILECONNECTIONSTRING The connection string references an Azure Storage account which contains an Azure File share used to store the application content/code. No

 

When using private endpoints for Azure Storage, it is necessary to create a private endpoint for each Azure Storage service (table, blob, queue, or file). Therefore, this samples sets up 5 private endpoints related to Azure Storage.

 

  • Four private endpoints related to each of the services referenced by the AzureWebJobsStorage application setting.
  • One private endpoint for blob storage referenced by the WidgetsAzureStorageConnection application setting. This is the only private endpoint related to the WidgetsAzureStorageConnection application setting.

 

Azure Cosmos DB Private Endpoints

 

As mentioned previously, a CosmosDB output binding is used to save data to a CosmosDB collection. A CosmosDB private endpoint is created, and the function communicates with CosmosDB via the private endpoint.

 

CosmosDB supports different API (Sql, Cassandra, Mongo, Table, etc.) types, and a private endpoint is needed for each. Meaning, there is a private endpoint for the SQL protocol, and another private endpoint for the Mongo protocol, etc. This sample uses the Sql API type, and therefore it is only necessary to configure a private endpoint for the Sql API.

 

As mentioned previously, this sample uses an ARM template to provision the Azure resources. It is important to note the value for the groupIds in the ARM template below is case sensitive. In most cases, ARM templates are not case sensitive. In this case, since the CosmosDB SQL API is being used, the value must be “Sql”. 

 

 

 

 

 

{
    "type": "Microsoft.Network/privateEndpoints",
    "name": "[variables('privateEndpointCosmosDbName')]",
    "apiVersion": "2019-11-01",
    "location": "[parameters('location')]",
    "dependsOn": [
        "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('privateCosmosDbAccountName'))]",
        "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
    ],
    "properties": {
        "subnet": {
            "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), variables('privateEndpointSubnetName') )]"
        },
        "privateLinkServiceConnections": [
            {
                "name": "MyCosmosDbPrivateLinkConnection",
                "properties": {
                    "privateLinkServiceId": "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('privateCosmosDbAccountName'))]",
                    "groupIds": [
                        "Sql"
                    ]
                }
            }
        ]
    }
}

 

 

 

 

 

Private Azure DNS Zones

 

When working with private endpoints, it is necessary to make changes your DNS configuration. You can either use a host file on a VM within the virtual network, a private DNS zone, or your own DNS server hosted within the virtual network. 

 

More information on Private Endpoint DNS configuration can be found in the official documentation.

Azure services have DNS configuration to know how to connect to other Azure services over a public endpoint. However, when using a private endpoint, the connection isn’t made over the public endpoint. It’s made using a private IP address allocated specifically for that Azure resource. Therefore, the default DNS configuration will need to be overridden.

 

One of the nice things about working with private endpoints is that the connection string used by the calling service doesn’t need to change. In other words, you can use contoso.blob.core.windows.net to connect to either the public endpoint or the private endpoint (for blob storage in the Contoso storage account).

 

This is made possible by using private DNS zones. The private endpoint creates an alias in a subdomain prefixed with “privatelink”. For example, blobs in an Azure Storage account may have a public DNS name of contoso.blob.core.windows.net. A private DNS zone is created which corresponds to contoso.privatelink.blob.core.windows.net. A DNS A record is created for each private IP address associated with the private endpoint. Clients within the virtual network resolve the connection to the storage account as follows:

 

Name Type Value
contoso.blob.core.windows.net CNAME contoso.privatelink.blob.core.windows.net
contoso.privatelink.blob.core.windows.net A 10.100.1.6

 

Clients external to the virtual network continue to resolve to the public IP address of the service.

 

The Zones

 

This sample uses private endpoints for Azure Storage and CosmosDB. As such, private DNS zones are needed for each Azure storage service, as well as the Sql API for CosmosDB. Meaning, five DNS zones are needed to support this sample:

  1. privatelink.queue.core.windows.net
  2. privatelink.blob.core.windows.net
  3. privatelink.table.core.windows.net
  4. privatelink.file.core.windows.net
  5. privatelink.documents.azure.com

When creating the zones, the recommended zone names where used. 

 

Setting it up

 

The ARM template uses the privateDnsZoneGroups sub-type to configure the DNS zone, obtaining the private IP address for the configured service, and setting up the corresponding DNS A record.

Below is a snippet from the ARM template which shows using the Microsoft.Network/privateEndpoints/privateDnsZoneGroups type.

 

 

 

 

 

{
    "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
    "apiVersion": "2020-03-01",
    "location": "[parameters('location')]",
    "name": "[concat(variables('privateEndpointCosmosDbName'), '/default')]",
    "dependsOn": [
        "[resourceId('Microsoft.Network/privateDnsZones', variables('privateCosmosDbDnsZoneName'))]",
        "[resourceId('Microsoft.Network/privateEndpoints', variables('privateEndpointCosmosDbName'))]"
    ],
    "properties": {
        "privateDnsZoneConfigs": [
            {
                "name": "config1",
                "properties": {
                    "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('privateCosmosDbDnsZoneName'))]"
                }
            }
        ]
    }
}

 

 

 

 

 

Summary

 

This post outline the key components that are necessary to connect to private endpoints with Azure Functions. Private endpoints allow for interaction with designated Azure resources via private IP address, thus keeping network traffic between the function and the resource confined to the virtual network.

 

Connecting to private endpoints with Azure Functions requires there to be a virtual network (with a few subnets), an Azure Functions Premium plan with VNet Integration enabled, Azure resources to connect to which support private endpoints, and modifications to DNS configuration. 

 

15 Comments

Hello @Michael Collier ,

 

Thanks for this interesting post. Is there any change (compared to what you wrote) regarding the private DNS zones? When enabling private link from the portal, a private DNS zone is indeed created but it doesn't create any ALIAS record. Trying to add one manually fails as the zone tells it already exists (but is not visible in any case) and indeed, resolving the public DNS from within the VNET works although no ALIAS record is visible. Any clue on that?

 

[edit]

Ok realized that the CNAME is automatically created and out of scope for the Cloud consumer.

[/edit]

Best Regards

 

Brass Contributor

Hi @Michael Collier,

 

Nice post! I managed to make the Azure Function work with Private Endpoints for the backing Storage Accounts with the following steps:

 

  • Created service endpoints for each service of the backing SA (blob, table, queue and files)
  • During Function deployment network restrictions are disabled for the backing SA
  • After successful deployment network restrictions are enabled.

 

I updated the post, I wrote back in May, with some notes and code here, where I'm using the same storage account for the backing services and also to hold 2 containers used by the Function to simply copy form one to another.

 

Best Regards

Brass Contributor

Hi ,

really interesting article , I previously deployed a function app with vnet integration enabled , trying to add the private endpoint on the subnet choose for the vnet integration using the portal but I get a message that says : "your subnet is a delegated subnet , private endpoint cannot be created" .

 

How I can handle with that ? I mean , private endpoint should be created before to enable  the vnet integration ? 

Brass Contributor

Hi @pioardi you'll need to add another subnet and enable the private endpoint there. The delegated one will only work for the VNET Integration.

 

Hope it helps.

Brass Contributor

Hi @cmendibl3  , thanks for your answer .

 

So the subnet with the private endpoint created must be another one , but in the same virtual network where the virtual network integration happen.

I tried and is working properly , thanks !!!

 

Great article !

Copper Contributor

Great article.

Whenever you use the WEBSITE_VNET_ROUTE_ALL setting in a function app, shouldn't the backing storage account for that function always be part of the same VNet (with a private endpoint), or else how could the function host otherwise ever reach the storage container? The MS documentation is a bit confusing on that, claiming the opposite.

Brass Contributor

@Crossbow the communication with the storage account depends also on the storage account firewall .

If you are using vnet integration and you choose a subnet X for the vnet integration , you should  configure the storage account firewall to allow transit from the subnet X .

 

Storage accounts , technically , are not part of any vnet or subnet , you just configure which network can access to it and how ( service endpoint vs private endpoint ) .

Copper Contributor

Unlucky phrasing yes.

I'm seeing weird behavior with my function app, which is linked to a VNet, and the WEBSITE_VNET_ROUTE_ALL flag is set.

When the app is started, the whole function-host errors out since it can not access its backing storage account ("connection timed out") even though that account is open to all networks (ie. the firewall/VNet integration is disabled).

Copper Contributor

Hi @Michael Collier,

 

Nice post!

 

It seems that you still need a dedicated storage account (without network restrictions) to the account referenced in WEBSITE_CONTENTAZUREFILECONNECTIONSTRING for the Azure Function to work properly.  When only using a backing storage account (with network restrictions) with private endpoints for (blob, table, queue and files) referenced in WEBSITE_CONTENTAZUREFILECONNECTIONSTRING the function runtime with not start (access denied).

 

Is there any plan to remove this restriction so we only need one storage account for the private endpoint (blob, table, queue and files) and same storage account referenced in WEBSITE_CONTENTAZUREFILECONNECTIONSTRING?

Brass Contributor

@Langer  not sure I am getting your point , but for what I tried and tested you can use a storage account with network restrictions , but to work properly you have to put the subnet that you choosed for vnet ingration ( or for hosting if you are using an isolated asp with an ASE ) on the target storage account firewall and allow it to access.

 

Microsoft

FYI - There is an update to the network restrictions possible for the storage account used by Azure Functions.  Please refer to https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#restrict-your-st....

 

Brass Contributor

Hi @Michael Collier thanks for the update. Is that the way this is going to be implemented? IMHO Azure should let us specify / configure one storage account and hide all those details.

 

The experience with IaC tools such as Terraform or ARM would require some sort orchestration or at least 2 trips (for instance to delete the first SA) and that is something I'd like to avoid.

Microsoft

Hello, @cmendibl3!  I am unsure of the product team's plans for feature enhancements.  The feature is currently in a preview state.  As such, I'd expect some changes before it is deemed "generally available".

 

FYI @Jeff Hollan 

Brass Contributor

Thanks for this really helpful article. Private endpoints is now GA. I like to check if the need for dual storage accounts still exists?

Copper Contributor

Hi! @Michael Collier Thank you for helpful tutorial!

 

I have one question regarding one error I'm getting when I try to query some Cosmos DB document. I have the next setup:

1) Azure Cosmos DB with disabled public access and Private endpoint configured. Connection from API's and Azure Functions is working through this private endpoint is working fine because they're places in the same VNet.

2) We have VPN configured to have an access to VNet that contains this Private endpoint and Cosmos DB Account. We use Private DNS Zone so for my local PC I've added DNS record to my local hosts file

 

And with this setup I'm unable to query any documents from Cosmos DB using the next code snippet:

 

 

string cosmosEndpoint = "https://<you-db-account>.documents.azure.com";
string cosmosKey = "<primary/secondary-key>";
var cosmosDbId = "<db-id>";
var cosmsoContainerId = "<container-id>";

CosmosClient client = new CosmosClient(cosmosEndpoint, cosmosKey);
var container = client.GetContainer(cosmosDbId, cosmsoContainerId);

ItemResponse<JObject> response = await container.ReadItemAsync<JObject>(
    id: "<document-id>",
    partitionKey: new PartitionKey("<partition-key>")
);

 

 

With this code on line 10 I'm getting the next error: "[CosmosException] Response status code does not indicate success: Forbidden (403); Reason: (Request originated from IP <my-ip> through public internet. This is blocked by your Cosmos DB account firewall settings."

 

But I'm able to query the same document using this ways:

  • Query document using HTTP request from Postman
  • Query document using HTTP request from Console App
  • Query document using CosmosClient with DefaultAzureCredential
Version history
Last update:
‎Aug 27 2020 02:49 PM
Updated by: