Securing Azure Services with Private Endpoints

Akhil Mittal's avatarPosted by

Introduction

As organizations increasingly move to the cloud, securing connectivity between resources becomes critical. Traditional models of accessing Azure services over public endpoints can expose organizations to various security threats, including data exfiltration and unauthorized access.

Enter Azure Private Endpoints—a powerful feature that allows secure and private access to Azure services over a private IP address within your Virtual Network (VNet), eliminating exposure to the public internet.


What Are Azure Private Endpoints?

An Azure Private Endpoint is a network interface that connects you privately and securely to a service powered by Azure Private Link. It uses a private IP address from your VNet, effectively bringing the Azure service into your private network.

Key Points:

  • It maps a private IP from your VNet to an Azure resource (e.g., Blob Storage, SQL Database).
  • All traffic to the service traverses the Microsoft backbone network, not the public internet.
  • You can apply NSGs (Network Security Groups) and firewall rules to control access.

Why Do We Need Private Endpoints?

Here’s why organizations adopt Private Endpoints:

1. Enhanced Security

  • Removes exposure of Azure PaaS services (like Azure Storage, Azure SQL, etc.) to the public internet.
  • Protects against data exfiltration and man-in-the-middle attacks.

2. Access Control

  • You can control access via NSGs, Azure Firewall, and custom routing.
  • Enables segmentation within your environment.

3. Compliance

  • Helps meet regulatory and compliance needs by ensuring traffic stays within private IP spaces.

4. No Internet Dependency

  • Even if internet access is cut off, your private endpoint traffic continues to work as it’s within the Azure backbone.

Services Supported by Private Endpoints

Some commonly used Azure services that support Private Endpoints:

  • Azure Storage (Blob/File/Table/Queue)
  • Azure SQL Database
  • Azure Cosmos DB
  • Azure Web Apps (App Services)
  • Azure Key Vault
  • Azure Container Registry
  • Azure Backup, and more…

Architecture Diagram

Here’s a visual representation of how Private Endpoints work:

Explanation:

  • A Private Endpoint in the VNet gets a private IP.
  • It connects to the Azure service over the Microsoft backbone.
  • The traffic stays within the private Azure network, not routed via the public internet.

How to Implement a Private Endpoint

Let’s walk through how to implement a Private Endpoint for Azure Blob Storage.

Prerequisites:

  • An existing Virtual Network and Subnet.
  • A Storage Account created in Azure.
  • Necessary permissions (Owner or Network Contributor roles).

Step-by-Step: Create a Private Endpoint

Step 1: Open the Storage Account

  • Go to Azure Portal > Your Storage Account
  • Under Networking, select Private endpoint connections

Step 2: Create a New Private Endpoint

  • Click + Private endpoint
  • Provide:
    • Name
    • Region (should match your VNet region)

Step 3: Resource Details

  • Choose the resource type (Microsoft.Storage/storageAccounts)
  • Select your Storage Account

Step 4: Configure Virtual Network

  • Choose your VNet and subnet
  • Optionally configure Private DNS integration

Step 5: DNS Settings

  • Integrate with Private DNS Zone (e.g., privatelink.blob.core.windows.net)
  • Azure will automatically create DNS records for name resolution.

Step 6: Review + Create

  • Review configuration and click Create

Done! You now have a Private Endpoint for your Azure Blob Storage.


Testing the Private Endpoint

You can test access using a VM in the same VNet:

nslookup yourstorageaccount.blob.core.windows.net

You should see something like:

Name:    yourstorageaccount.privatelink.blob.core.windows.net
Address: 10.x.x.x

This confirms traffic is resolving to a private IP, not the public one.

Using Private DNS Zones (Optional but Recommended)

Azure automatically integrates Private Endpoints with Private DNS Zones (e.g., privatelink.database.windows.net) to ensure name resolution happens within your VNet.

Benefits:

  • No need to manually update DNS.
  • Automatically works for other resources in the same VNet or peered VNets.

Controlling Access with NSGs and Firewalls

Although Private Endpoints offer private connectivity, you still need to restrict access further using:

  • NSGs on the subnet where the private endpoint is connected.
  • Firewall rules on the service itself to allow only private endpoint access.

Example:

  • Deny all public access to the Storage Account.
  • Allow only traffic via the Private Endpoint.

Common Pitfalls to Avoid

  • DNS misconfiguration: If your DNS isn’t set up properly, the service might still resolve to its public IP.
  • Service firewall not updated: If the target Azure service firewall blocks private endpoint traffic, connectivity fails.
  • Wrong subnet: Make sure the subnet is not delegated to conflicting services.

Real-World Use Case

Scenario: A healthcare company wants to securely access Azure SQL Database from their Azure VNet without exposing the database to the internet.

Solution:

  • Create a Private Endpoint for the SQL DB.
  • Disable public access on the SQL firewall.
  • Use Private DNS to ensure correct name resolution.
  • Control access using NSGs.

Result: Completely secure, private access to SQL DB—no public exposure.


Scenario: Create a Private Endpoint for Azure Storage Account

Resources Included:

  • Virtual Network
  • Subnet
  • Storage Account
  • Private Endpoint
  • Private DNS Zone
  • Private DNS Zone Group (to link the DNS zone to the private endpoint)

Bicep Template

param location string = resourceGroup().location
param vnetName string = 'myVnet'
param subnetName string = 'mySubnet'
param storageAccountName string = 'mystorageacct12345'
param privateEndpointName string = 'pe-storage'
param dnsZoneName string = 'privatelink.blob.core.windows.net'

resource vnet 'Microsoft.Network/virtualNetworks@2023-02-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: '10.0.0.0/24'
        }
      }
    ]
  }
}

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    accessTier: 'Hot'
  }
}

resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: dnsZoneName
  location: 'global'
}

resource vnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  name: 'dnsLink-${vnet.name}'
  parent: privateDnsZone
  location: 'global'
  properties: {
    virtualNetwork: {
      id: vnet.id
    }
    registrationEnabled: false
  }
}

resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-02-01' = {
  name: privateEndpointName
  location: location
  properties: {
    subnet: {
      id: vnet.properties.subnets[0].id
    }
    privateLinkServiceConnections: [
      {
        name: '${privateEndpointName}-link'
        properties: {
          privateLinkServiceId: storageAccount.id
          groupIds: [
            'blob'
          ]
        }
      }
    ]
  }
}

resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-09-01' = {
  name: 'default'
  parent: privateEndpoint
  properties: {
    privateDnsZoneConfigs: [
      {
        name: 'config'
        properties: {
          privateDnsZoneId: privateDnsZone.id
        }
      }
    ]
  }
}

ARM Template (JSON)

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": { "type": "string", "defaultValue": "[resourceGroup().location]" },
    "vnetName": { "type": "string", "defaultValue": "myVnet" },
    "subnetName": { "type": "string", "defaultValue": "mySubnet" },
    "storageAccountName": { "type": "string" },
    "privateEndpointName": { "type": "string", "defaultValue": "pe-storage" }
  },
  "resources": [
    {
      "type": "Microsoft.Network/virtualNetworks",
      "apiVersion": "2023-02-01",
      "name": "[parameters('vnetName')]",
      "location": "[parameters('location')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": ["10.0.0.0/16"]
        },
        "subnets": [
          {
            "name": "[parameters('subnetName')]",
            "properties": {
              "addressPrefix": "10.0.0.0/24"
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-01-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": { "name": "Standard_LRS" },
      "kind": "StorageV2",
      "properties": {
        "accessTier": "Hot"
      }
    },
    {
      "type": "Microsoft.Network/privateDnsZones",
      "apiVersion": "2020-06-01",
      "name": "privatelink.blob.core.windows.net",
      "location": "global",
      "properties": {}
    },
    {
      "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
      "apiVersion": "2020-06-01",
      "name": "[concat('dnsLink-', parameters('vnetName'))]",
      "dependsOn": ["Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net"],
      "location": "global",
      "properties": {
        "virtualNetwork": {
          "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]"
        },
        "registrationEnabled": false
      }
    },
    {
      "type": "Microsoft.Network/privateEndpoints",
      "apiVersion": "2023-02-01",
      "name": "[parameters('privateEndpointName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
        "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]"
      ],
      "properties": {
        "subnet": {
          "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('subnetName'))]"
        },
        "privateLinkServiceConnections": [
          {
            "name": "[concat(parameters('privateEndpointName'), '-link')]",
            "properties": {
              "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
              "groupIds": ["blob"]
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
      "apiVersion": "2022-09-01",
      "name": "default",
      "dependsOn": ["Microsoft.Network/privateEndpoints/[parameters('privateEndpointName')]"],
      "properties": {
        "privateDnsZoneConfigs": [
          {
            "name": "config",
            "properties": {
              "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.blob.core.windows.net')]"
            }
          }
        ]
      }
    }
  ]
}

Deployment Instructions

Deploy Bicep via Azure CLI:

az deployment group create \
  --resource-group myResourceGroup \
  --template-file private-endpoint.bicep \
  --parameters storageAccountName=mystorageacct12345

Deploy ARM via Azure CLI:

az deployment group create \
  --resource-group myResourceGroup \
  --template-file private-endpoint-arm.json \
  --parameters storageAccountName=mystorageacct12345

Conclusion

Azure Private Endpoints are a critical security measure that allows you to access Azure services privately and securely. By bringing services into your VNet via private IPs, you eliminate the risks associated with public endpoints and gain granular control over your traffic.

Use them to:

  • Secure your Azure services
  • Eliminate internet exposure
  • Comply with internal and external security policies

References & Learn More


Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.