Secure Azure Storage Access with Private Link, Private DNS, and Managed Identity
Secure Azure Storage access with Private Link, Private DNS, and a system‑assigned managed identity—no public endpoints, no secrets, minimal operational overhead. Includes step‑by‑step CLI, checks, and failure‑mode mitigation.
24 Dec 2025, 10:29 UTC

Problem Statement
Serverless functions, container apps, or VMs that need to read or write blobs often default to using a storage account’s public endpoint. This exposes traffic to the public internet and requires storing connection strings or keys. The goal is to keep all traffic within Azure’s backbone, avoid secrets, and keep the design minimal.
Requirements
- All blob traffic must stay inside Azure (no public IPs).
- Authentication must not rely on connection strings or keys.
- Access should be auditable through Azure Activity Log.
- Operational overhead should be low – no custom NAT gateways or proxy services.
- The design must work for single‑region serverless workloads (Azure Functions, Logic Apps, etc.).
Minimal Suitable Design
The smallest design that satisfies the above uses three Azure components:
- Disable public network access on the storage account.
- Create a Private Endpoint in the workload subnet and link the storage account’s private DNS zone to the VNet.
- Grant the workload a system‑assigned managed identity with the
Storage Blob Data Readerrole.
Below is a step‑by‑step example using Azure CLI. Replace placeholders with your actual names.
# Variables
RESOURCE_GROUP="rg-storage-demo"
STORAGE_ACCOUNT="stgdemoacct"
VNET_NAME="vnet-demo"
SUBNET_NAME="subnet-app"
LOCATION="eastus"
# 1. Create the storage account with public access disabled
az storage account create \
--name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--sku Standard_LRS \
--kind StorageV2 \
--allow-blob-public-access false \
--default-action Deny
# 2. Create a private endpoint in the workload subnet
az network private-endpoint create \
--name ${STORAGE_ACCOUNT}-pe \
--resource-group $RESOURCE_GROUP \
--vnet-name $VNET_NAME \
--subnet $SUBNET_NAME \
--private-connection-resource-id \
$(az storage account show \
--name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--query id -o tsv) \
--group-id blob \
--connection-name ${STORAGE_ACCOUNT}-blob
# 3. Link the storage account’s private DNS zone to the VNet
PRIVATE_DNS_ZONE=$(az network private-dns zone list \
--resource-group $RESOURCE_GROUP \
--query "[?name=='privatelink.blob.core.windows.net'].id" -o tsv)
az network private-dns link vnet create \
--name ${STORAGE_ACCOUNT}-dnslink \
--resource-group $RESOURCE_GROUP \
--zone-name privatelink.blob.core.windows.net \
--vnet-name $VNET_NAME \
--registration-enabled false
# 4. Assign a system‑assigned managed identity to an Azure Function
# (Assuming the function already exists)
FUNCTION_NAME="func-demo"
az functionapp identity assign \
--name $FUNCTION_NAME \
--resource-group $RESOURCE_GROUP
# 5. Grant the identity Storage Blob Data Reader role on the storage account
IDENTITY_PRINCIPAL_ID=$(az functionapp identity show \
--name $FUNCTION_NAME \
--resource-group $RESOURCE_GROUP \
--query principalId -o tsv)
az role assignment create \
--assignee $IDENTITY_PRINCIPAL_ID \
--role "Storage Blob Data Reader" \
--scope $(az storage account show \
--name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--query id -o tsv)
Key Points in the Example
- The storage account is created with
--allow-blob-public-access falseand--default-action Denyto block all public traffic. - The private endpoint is created against the
blobgroup, which maps to the blob service. - Linking the private DNS zone ensures that DNS queries for
*.blob.core.windows.netresolve to the private IP assigned to the endpoint. - The function’s system‑assigned identity is granted the minimal role needed for read‑only access.
Trust & Data Boundaries
Three layers enforce isolation:
- Network boundary – Private Link and the VNet keep traffic on Azure’s backbone.
- Name‑resolution boundary – Azure Private DNS guarantees that the storage endpoint resolves to the private IP, not the public one.
- Identity boundary – Managed identity and RBAC prevent credential leakage; only the workload’s identity can access the blobs.
Operational Checks
- DNS resolution
From a VM or function in the same subnet run:nslookup $STORAGE_ACCOUNT.blob.core.windows.net # The A record should show a 10.x.x.x private IP - Private Endpoint state
Verify the connection is approved:az network private-endpoint show \ --name ${STORAGE_ACCOUNT}-pe \ --resource-group $RESOURCE_GROUP \ --query privateEndpointConnections[0].privateLinkServiceConnectionState.status -o tsv # Expected output: Approved - Blob access test
Using the managed identity:
This command should return a list of blobs without requiring a key.az storage blob list \ --account-name $STORAGE_ACCOUNT \ --auth-mode login \ --container-name testcontainer - Monitoring
Check Storage metrics forPrivateEndpointConnectionandAuthenticationFailuresin Azure Monitor. Also audit the Activity Log for anyPrivateEndpointConnectionStateChangedevents.
Failure Modes & Mitigations
- Private DNS mislink – If the VNet isn’t linked, DNS resolves to the public IP, causing timeouts. Mitigation: Use
az network private-dns link vnet showto confirm the link exists. - Provisioning delay – Private Endpoint creation can take a few minutes. During this window, requests may fail with 403 or name‑resolution errors. Mitigation: Implement retry logic with exponential back‑off in your application.
- Identity token latency – Cold starts of serverless workloads can delay token acquisition, leading to 401 errors. Mitigation: Warm‑up functions or use
--auth-mode loginearly in the execution flow. - Subnet IP exhaustion – Adding many private endpoints can fill the subnet’s address space. Mitigation: Use a larger subnet or split endpoints across multiple subnets.
- Policy drift – Disabling public network access can be overridden by an Azure Policy that allows public endpoints. Mitigation: Enforce a policy that requires
publicNetworkAccess=Disabledon storage accounts.
When to Re‑evaluate the Design
Consider changing the architecture if:
- Workloads span multiple regions and require cross‑region replication. You’ll need a zone‑redundant storage account and additional private endpoints per region.
- Cross‑tenant access is required. In that case a Private Link service should be exposed instead of a direct private endpoint.
- Public anonymous read access becomes a requirement (e.g., public static website). You would need to enable
allowBlobPublicAccessor use a separate storage account with public endpoints. - Your workload needs write permissions. You’d assign
Storage Blob Data ContributororStorage Blob Data Ownerroles. - You introduce a proxy or NAT gateway for outbound traffic (e.g., to monitor logs). The design may shift to a hybrid approach with both private and public endpoints.
Conclusion
By disabling public network access, creating a private endpoint, linking the private DNS zone, and using a managed identity with the minimal RBAC role, you achieve a clean, auditable, and low‑maintenance path for Azure Functions or VMs to access blob storage entirely within Azure. Operational checks and awareness of failure modes help maintain reliability, while clear triggers for redesign keep the solution future‑proof.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.