PaaS Access Should Stay on the Azure Backbone? Create a Service Endpoint

Published on:

Azure PaaS services normally use public endpoints. A virtual network service endpoint adds a direct route from a subnet to a supported Azure service over the Microsoft backbone and lets the service identify that subnet.

Configure snet-app for Azure Storage and allow a new Storage account only from that subnet:

WEB02 in snet-app
    |
    | Microsoft.Storage service-endpoint route
    v
Storage public endpoint over the Azure backbone
    |
    `-- Storage firewall allows snet-app

Other network ---> Storage public endpoint ---> Blocked

This trip builds on Storage Must Stay Private? Create a Private Endpoint. Keep vnet-cloudtrips-test-weu, snet-app, and WEB02. This lab uses a separate Storage account so the private-only account from the previous trip remains unchanged.

Compare Service and Private Endpoints

Both options keep VNet-to-service traffic on Microsoft’s network, but their network models differ:

Property Service endpoint Private endpoint
Destination Service’s public IP address Private IP address in the VNet
DNS change None Private DNS normally required
Configuration scope Subnet plus service firewall Private endpoint NIC plus service approval
Access from on-premises Not directly Through connected networks and DNS

A service endpoint does not deploy a NIC or private IP into the VNet. DNS still resolves <account>.blob.core.windows.net to a public Storage address, while Azure routes the traffic directly over its backbone and presents the subnet’s identity to the Storage firewall.

Enable Microsoft.Storage on the Subnet

In the Azure portal, open:

Virtual networks > vnet-cloudtrips-test-weu > Settings > Service endpoints

Select + Add and configure:

Service: Microsoft.Storage
Subnets: snet-app

Select Add and wait until Microsoft.Storage appears for snet-app with a successful provisioning state.

Microsoft.Storage service endpoint enabled on the CloudTrips application subnet

The equivalent CLI command is:

az network vnet subnet update \
  --resource-group rg-cloudtrips-network-test-weu \
  --vnet-name vnet-cloudtrips-test-weu \
  --name snet-app \
  --service-endpoints Microsoft.Storage

Use either the portal or CLI method. If the subnet already has other service endpoints, do not replace them with this command; add Microsoft.Storage while preserving the existing values.

Create a Storage Account for the Service Endpoint

In Cloud Shell, select Bash and generate a globally unique name:

SERVICE_STORAGE="stctse$(az account show --query id --output tsv | tr -d '-' | cut -c1-10)"
printf 'Service-endpoint storage account: %s\n' "$SERVICE_STORAGE"

In the Azure portal, search for Storage accounts, select + Create, and configure:

Subscription: CloudTrips TEST
Resource group: rg-cloudtrips-network-test-weu
Storage account name: the SERVICE_STORAGE value
Region: West Europe
Performance: Standard
Redundancy: Locally-redundant storage (LRS)

On Networking, configure:

Public network access: Enable
Public network access scope: Enable from selected virtual networks and IP addresses
Virtual network: vnet-cloudtrips-test-weu
Subnet: snet-app

Do not add your client IP and do not enable a trusted-services exception. Select Review + create > Create.

Storage account networking restricted to the snet-app virtual network rule

The equivalent CLI flow is:

az storage account create \
  --resource-group rg-cloudtrips-network-test-weu \
  --name "$SERVICE_STORAGE" \
  --location westeurope \
  --sku Standard_LRS \
  --kind StorageV2 \
  --https-only true \
  --min-tls-version TLS1_2 \
  --allow-blob-public-access false \
  --public-network-access Enabled \
  --default-action Deny \
  --bypass None

az storage account network-rule add \
  --resource-group rg-cloudtrips-network-test-weu \
  --account-name "$SERVICE_STORAGE" \
  --vnet-name vnet-cloudtrips-test-weu \
  --subnet snet-app

Verify DNS Has Not Become Private

Start WEB02 if necessary, then resolve the Blob hostname from the VM:

az vm start \
  --resource-group rg-cloudtrips-network-test-weu \
  --name vm-cloudtrips-web02-test-weu

az vm run-command invoke \
  --resource-group rg-cloudtrips-network-test-weu \
  --name vm-cloudtrips-web02-test-weu \
  --command-id RunShellScript \
  --scripts "getent ahostsv4 ${SERVICE_STORAGE}.blob.core.windows.net | head -1"

The answer should be a public Azure IP, not a 10.20.x.x address. This is expected: a service endpoint changes routing and service authorization, not the service’s DNS record.

WEB02 resolving the service-endpoint Storage hostname to a public Azure address

Verify the Backbone Route

Find the NIC attached to WEB02 and inspect its effective routes:

WEB02_NIC=$(az vm show \
  --resource-group rg-cloudtrips-network-test-weu \
  --name vm-cloudtrips-web02-test-weu \
  --query 'networkProfile.networkInterfaces[0].id' \
  --output tsv)

az network nic show-effective-route-table \
  --ids "$WEB02_NIC" \
  --query "value[?nextHopType=='VirtualNetworkServiceEndpoint'].{Prefix:addressPrefix[0],NextHop:nextHopType,State:state}" \
  --output table

Expect one or more active routes whose next hop is VirtualNetworkServiceEndpoint. Azure publishes service address prefixes, so the output can contain several rows.

WEB02 effective routes showing VirtualNetworkServiceEndpoint as the next hop

Compare Allowed and Blocked Requests

From WEB02, send an unauthenticated HTTPS request:

az vm run-command invoke \
  --resource-group rg-cloudtrips-network-test-weu \
  --name vm-cloudtrips-web02-test-weu \
  --command-id RunShellScript \
  --scripts "curl --silent --show-error --include https://${SERVICE_STORAGE}.blob.core.windows.net/?comp=list | head -20"

The request reaches the Blob service through the service endpoint. A 403 authentication response is expected because the request has no Storage credential; the important result is that the Storage firewall accepted the subnet path before normal data-plane authentication was evaluated.

WEB02 reaching Azure Storage through the allowed service-endpoint subnet

Run the same request from Cloud Shell, which is outside snet-app:

curl --silent --show-error --include \
  "https://${SERVICE_STORAGE}.blob.core.windows.net/?comp=list" \
  | head -20

Storage rejects this request because Cloud Shell is not part of the allowed subnet. The public endpoint still exists, but its network ACL permits only the configured VNet rule.

Cloud Shell request rejected because it is outside the allowed service-endpoint subnet

Confirm both sides of the configuration:

az network vnet subnet show \
  --resource-group rg-cloudtrips-network-test-weu \
  --vnet-name vnet-cloudtrips-test-weu \
  --name snet-app \
  --query 'serviceEndpoints[].service' \
  --output tsv

az storage account network-rule list \
  --resource-group rg-cloudtrips-network-test-weu \
  --account-name "$SERVICE_STORAGE" \
  --query '{DefaultAction:defaultAction,Bypass:bypass,VNetRules:virtualNetworkRules[].{State:state,Subnet:id}}' \
  --output yaml

Expect Microsoft.Storage, DefaultAction: Deny, Bypass: None, and an enabled rule for snet-app.

Keep or Remove the Service Endpoint Lab

Deallocate WEB02 when testing is complete. Keep the service endpoint and Storage account if a later workload will use them.

To remove this lab, delete the service-endpoint Storage account and remove the Microsoft.Storage service endpoint from snet-app only when no other Storage resource depends on it. Keep the VNet, subnet, VM, private endpoint, and private DNS resources from earlier networking trips.