Azure Functions — VNet Integration & Private Endpoints

Outbound via VNet, Inbound via Private Endpoint


Introduction

Securing Azure Functions is critical for enterprise applications. Two primary mechanisms provide network isolation:

  1. VNet Integration — Enables your function app to access resources in your virtual network (outbound traffic)
  2. Private Endpoints — Allows other resources to access your function app through a private IP address (inbound traffic)

Together, these provide a comprehensive security model where your functions can securely communicate with on-premises resources, other Azure services, and be accessed only from within your network.


Understanding VNet Integration

How VNet Integration Works

When you enable VNet integration:

  • Your function app gets a subnet delegated to it
  • Outbound traffic from your function can be routed through the VNet
  • You can access on-premises resources via VPN or ExpressRoute
  • You can access other Azure services using private IPs
┌─────────────────────────────────────────────────────────┐
│                    Your Virtual Network                 │
│                                                         │
│  ┌──────────────┐    ┌──────────────┐    ┌───────────┐  │
│  │ On-Premises  │    │   Azure VM   │    │   SQL DB  │  │
│  │   Database   │◀───│  (Private)   │◀───│ (Private) │  │
│  └──────────────┘    └──────────────┘    └───────────┘  │
│          ▲                  ▲                  ▲        │
│          │                  │                  │        │
│  ┌───────┴──────────────────┴──────────────────┴──────┐ │
│  │              Function App with VNet                │ │
│  │              Integration Enabled                   │ │
│  └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

Configuring VNet Integration

Premium Plan (Elastic Premium)

// host.json
{
  "version": "2.0",
  "extensions": {
    "http": {
      "routePrefix": "api"
    }
  }
}

Azure Portal Configuration

  1. Go to your Function App
  2. Select "Networking" under Settings
  3. Under "VNet Integration", click "Add VNet Integration"
  4. Select your subscription, VNet, and subnet
  5. The subnet must be /27 or larger and dedicated to Functions

Using ARM/Bicep

{
  "type": "Microsoft.Web/sites/config",
  "name": "[concat(variables('functionAppName'), '/web')]",
  "apiVersion": "2021-02-01",
  "properties": {
    "vnetName": "[parameters('vnetName')]",
    "vnetResourceGroupName": "[parameters('vnetResourceGroupName')]",
    "subnetResourceId": "[resourceId(parameters('vnetResourceGroupName'), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('subnetName'))]"
  }
}

Using Azure CLI

# Create a dedicated subnet for Functions
az network vnet subnet create \
  --name function-subnet \
  --resource-group my-rg \
  --vnet-name my-vnet \
  --address-prefixes 10.0.1.0/24

# Enable VNet integration
az functionapp vnet-integration add \
  --name my-function-app \
  --resource-group my-rg \
  --vnet-name my-vnet \
  --subnet-name function-subnet

Routing Traffic Through VNet

Configure Route All

By default, only traffic to private addresses goes through the VNet. To route ALL traffic through the VNet:

{
  "name": "WEBSITE_VNET_ROUTE_ALL",
  "value": "1",
  "slotSetting": false
}

This is useful when:

  • You need to inspect all outbound traffic
  • Using network virtual appliances (NVAs)
  • Accessing on-premises resources

Configuring Specific Routes

{
  "name": "WEBSITE_ADD_SITENAME_BINDINGS_IN_APPHOST",
  "value": "1"
}

Sticky Connections

{
  "name": "WEBSITE_VNET_STICKY_DIAGNOSTICS",
  "value": "true"
}

Private Endpoints for Inbound Access

Private endpoints allow your function to be accessed from within your VNet without exposing it to the public internet.

Architecture

┌────────────────────────────────────────────────────────┐
│                    Your Virtual Network                │
│                                                        │
│   ┌─────────────┐                                      │
│   │   Client    │                                      │
│   │     VM      │──────┐                               │
│   └─────────────┘      │                               │
│                        ▼                               │
│   ┌─────────────────────────────────────────┐          │
│   │        Private Endpoint                 │          │
│   │   (10.0.1.20 - my-function-app.azurewebsites.net)  │
│   └─────────────────────────────────────────┘          │
└────────────────────────────────────────────────────────┘

Creating a Private Endpoint

# Get the resource ID of your function app
FUNCTION_APP_ID=$(az functionapp show \
  --name my-function-app \
  --resource-group my-rg \
  --query id -o tsv)

# Create private endpoint
az network private-endpoint create \
  --name function-private-endpoint \
  --resource-group my-rg \
  --subnet-name private-subnet \
  --connection-name function-connection \
  --target-resource $FUNCTION_APP_ID \
  --group-id sites \
  --private-link-service-connection-type manual

DNS Configuration

Option 1: Private DNS Zone

# Create private DNS zone
az network private-dns zone create \
  --name "privatelink.azurewebsites.net" \
  --resource-group my-rg

# Link to VNet
az network private-dns vnet-link create \
  --name my-dns-link \
  --resource-group my-rg \
  --zone-name "privatelink.azurewebsites.net" \
  --virtual-network my-vnet

# Create DNS record
az network private-dns record-set a create \
  --name my-function-app \
  --zone-name "privatelink.azurewebsites.net" \
  --resource-group my-rg

az network private-dns record-set a add-record \
  --record-set-name my-function-app \
  --zone-name "privatelink.azurewebsites.net" \
  --resource-group my-rg \
  --ipv4-address 10.0.1.20

Option 2: Custom DNS Forwarder

If you use your own DNS server:

# Add A record to your DNS
my-function-app.azurewebsites.net -> 10.0.1.20

Hybrid Connections for On-Premises Access

Azure Functions Hybrid Connections are a simpler alternative for accessing on-premises resources without full VNet integration.

When to Use Hybrid Connections

  • Access a single on-premises resource
  • Don't need full VNet integration
  • Quick setup for development/testing

Setting Up Hybrid Connections

# Create Hybrid Connection
az functionapp hybrid-connection add \
  --name my-function-app \
  --resource-group my-rg \
  --namespace-name my-hybrid-namespace \
  --hybrid-connection-name onprem-db-connection

Configuration in host.json

{
  "version": "2.0",
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

Using Hybrid Connection in Code

[FunctionName("OnPremiseData")]
public static async Task<IActionResult> OnPremiseData(
    [HttpTrigger] HttpTriggerOptions options,
    ILogger log)
{
    // Connect to on-premises SQL Server via Hybrid Connection
    // Connection string uses the hybrid connection endpoint
    var connectionString = "Server=127.0.0.1,1433;Database=OnPremDB;...";
    
    using var connection = new SqlConnection(connectionString);
    await connection.OpenAsync();
    
    // Query data
    // ...
    
    return new OkObjectResult(data);
}

Secure Access to Azure Services

Accessing Key Vault with Private Endpoint

[FunctionName("GetSecret")]
public static async Task<IActionResult> GetSecret(
    [HttpTrigger] HttpTriggerOptions options,
    ILogger log)
{
    var keyVaultUrl = "https://my-keyvault.vault.azure.net/";
    
    // Using Managed Identity with Key Vault private endpoint
    var credential = new DefaultAzureCredential();
    var secretClient = new SecretClient(new Uri(keyVaultUrl), credential);
    
    var secret = await secretClient.GetSecretAsync("my-secret");
    
    return new OkObjectResult(secret.Value.Value);
}

Configuring Key Vault Access

{
  "name": "AZURE_CLIENT_ID",
  "value": "<managed-identity-client-id>"
}

Accessing SQL Database Privately

[FunctionName("QueryDatabase")]
public static async Task<IActionResult> QueryDatabase(
    [HttpTrigger] HttpTriggerOptions options,
    ILogger log)
{
    // Database with private endpoint
    var connectionString = "Server=my-sql-server.database.windows.net;Database=MyDB;...";
    
    using var connection = new SqlConnection(connectionString);
    await connection.OpenAsync();
    
    var result = await connection.QueryAsync("SELECT * FROM Customers");
    return new OkObjectResult(result);
}

Configuring Access Restrictions

Set IP Restrictions

# Allow only VNet traffic
az functionapp config access-restriction set \
  --name my-function-app \
  --resource-group my-rg \
  --action Allow \
  --rule-name "Allow VNet" \
  --ip-address "VirtualNetwork"

Using Azure Portal

  1. Navigate to Function App > Networking
  2. Under "Access Restrictions", add rules
  3. Configure:
    • Priority
    • Action (Allow/Deny)
    • Type (IP/Service Tag/VNet)
    • Value (IP range/Service Tag/Subnet)

Configure in ARM

{
  "type": "Microsoft.Web/sites/config",
  "name": "[concat(variables('functionAppName'), '/web')]",
  "apiVersion": "2021-02-01",
  "properties": {
    "ipSecurityRestrictions": [
      {
        "ipAddress": "VirtualNetwork",
        "action": "Allow",
        "priority": 100,
        "name": "Allow VNet"
      }
    ]
  }
}

Network Security Groups (NSGs)

Create NSG with Rules

# Create NSG
az network nsg create \
  --name function-nsg \
  --resource-group my-rg

# Allow outbound to VNet
az network nsg rule create \
  --name AllowVNetOutbound \
  --nsg-name function-nsg \
  --resource-group my-rg \
  --direction Outbound \
  --access Allow \
  --source-address-prefix "VirtualNetwork" \
  --destination-address-prefix "VirtualNetwork" \
  --destination-port-range "*"

# Deny public internet outbound (if needed)
az network nsg rule create \
  --name DenyInternetOutbound \
  --nsg-name function-nsg \
  --resource-group my-rg \
  --direction Outbound \
  --access Deny \
  --source-address-prefix "VirtualNetwork" \
  --destination-address-prefix "Internet"

Associate NSG with Subnet

az network vnet subnet update \
  --name function-subnet \
  --resource-group my-rg \
  --vnet-name my-vnet \
  --network-security-group function-nsg

Complete Security Architecture

┌────────────────────────────────────────────────────────────────┐
│                        Virtual Network                         │
│                                                                │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                  Private Endpoint                        │  │
│  │     (my-function-app.privatelink.azurewebsites.net)      │  │
│  └──────────────────────────────────────────────────────────┘  │
│                              │                                 │
│                              ▼                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                   Function App                           │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │  │
│  │  │   VNet      │  │  Managed    │  │   IP        │       │  │
│  │  │ Integration │  │  Identity   │  │  Restrict   │       │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘       │  │
│  └──────────────────────────────────────────────────────────┘  │
│                              │                                 │
│         ┌────────────────────┼────────────────────┐            │
│         ▼                    ▼                    ▼            │
│  ┌────────────┐      ┌────────────┐      ┌────────────┐        │
│  │  Key Vault │      │  SQL DB    │      │  Service   │        │
│  │ (Private)  │      │ (Private)  │      │   Bus      │        │
│  └────────────┘      └────────────┘      └────────────┘        │ 
│                                                                │
│  ┌────────────┐      ┌────────────┐                            │
│  │  On-Prem   │      │  Storage   │                            │
│  │  (VPN)     │      │ (Private)  │                            │
│  └────────────┘      └────────────┘                            │
└────────────────────────────────────────────────────────────────┘

Troubleshooting

Common Issues

IssueSolution
VNet integration not workingVerify subnet is delegated to Functions
Can't resolve DNSCheck private DNS zone configuration
Private endpoint not accessibleVerify NSG rules allow traffic
Functions can't reach on-premisesCheck VPN/ExpressRoute connectivity

Diagnostic Commands

# Check VNet integration status
az functionapp vnet-integration show \
  --name my-function-app \
  --resource-group my-rg

# Test connectivity
az functionapp show \
  --name my-function-app \
  --resource-group my-rg \
  --query "possibleOutboundIpAddresses"

Best Practices

PracticeDescription
Use dedicated subnetDon't share with other Azure resources
Plan IP address spaceReserve /24 or larger subnet
Use private endpointsFor all Azure service access
Enable Managed IdentityAvoid storing secrets in code
Configure NSGsAdd security layer at network level
Use Private DNS zonesProper DNS resolution for private endpoints

Azure Integration Hub - Advanced Level