In Part 1, we covered the benefits, considerations, and real-world use cases for Azure VNet Service Endpoints. Now, let's take a closer look at how to track and manage service endpoint usage in your network, focusing on writing complex KQL queries to accurately identify service endpoint configurations. Once identified, you can start assessing your environment to understand the impact and optimize your network setup.
⚡ Querying Service Endpoint Usage with KQL
KQL is one of the most effective tools in Azure for querying large datasets, especially within Azure Resource Graph. It allows you to monitor and identify where service endpoints are being used in your environment and provides visibility into traffic patterns. I have tons of examples in my blog about KQL and Azure Resource Graph. You can check them out for more details.
The first step is to identify which subnets in your tenant are using service endpoints, specifically for Microsoft.Storage or Microsoft.StorageGlobal
resources
| where type =~ "Microsoft.Network/virtualNetworks"
| mv-expand subnets = properties.subnets
| extend subnetName = tostring(subnets.name),
serviceEndpoints = subnets.properties.serviceEndpoints
| mv-expand serviceEndpoint = serviceEndpoints
| where isnotempty(serviceEndpoint) and serviceEndpoint.service in~ ("Microsoft.Storage", "Microsoft.Storage.Global")
| join kind=leftouter (
resourcecontainers
| where type == "microsoft.resources/subscriptions"
| project subscriptionId, subscriptionName = name
)
on subscriptionId
| project subscriptionName, subscriptionId, resourceGroup, virtualNetworkName = name, subnetName, serviceEndpoint = serviceEndpoint.service
Here is an example output for the KQL query you provided, which retrieves VNets with Microsoft.Storage service endpoints along with their subscription details.
Example Output
Subscription Name | Subscription ID | Resource Group | Virtual Network Name | Subnet Name | Service Endpoint |
---|---|---|---|---|---|
Azure-Prod | 12345678-aaaa-bbbb-cccc-1234567890ab | RG-Network | VNet-East | Subnet-App1 | Microsoft.Storage |
Azure-Prod | 12345678-aaaa-bbbb-cccc-1234567890ab | RG-Network | VNet-East | Subnet-App2 | Microsoft.Storage |
Azure-Dev | 98765432-dddd-eeee-ffff-0987654321ab | RG-Storage | VNet-West | Subnet-DB1 | Microsoft.Storage.Global |
Azure-Test | 11223344-zzzz-yyyy-xxxx-112233445566 | RG-Test | VNet-Test | Subnet-Test | Microsoft.Storage |
What This Query Does
- ✔ Retrieves all Virtual Networks (VNets) in your Azure environment
- ✔ Extracts subnet names and their associated service endpoints
- ✔ Filters results to include only Microsoft.Storage and Microsoft.Storage.Global service endpoints
- ✔ Provides a structured list of VNet, Subnet, Service Endpoint, Subscription, and Resource Group
💡 Use Case: You can use this query in Azure Resource Graph Explorer to quickly audit other service endpoints usage across multiple subscriptions and resource groups.
The second step is to identify when a subnet is whitelisted to a storage account, it means that resources within that subnet can communicate directly with the storage account, bypassing User-Defined Routes (UDRs) that might otherwise redirect traffic.
resources
| where type == "microsoft.network/virtualnetworks"
| mv-expand subnets = properties.subnets
| extend subnetName = tostring(subnets.name),
vnetName = name,
serviceEndpoints = subnets.properties.serviceEndpoints
| mv-expand serviceEndpoint = serviceEndpoints
| where isnotempty(serviceEndpoint) and serviceEndpoint.service in~ ("Microsoft.Storage", "Microsoft.Storage.Global")
| project subscriptionId, resourceGroup, vnetName, subnetName
| join kind=inner (
resources
| where type == "microsoft.storage/storageaccounts"
| where isnotempty(properties.networkAcls.virtualNetworkRules) // Ensure storage accounts have network rules
| extend storageAccountName = name,
networkRules = properties.networkAcls.virtualNetworkRules
| mv-expand networkRule = networkRules
| extend subnetId = tostring(networkRule.id)
| parse subnetId with "/subscriptions/" subscriptionId "/resourceGroups/" resourceGroup "/providers/Microsoft.Network/virtualNetworks/" vnetName "/subnets/" subnetName
| project subscriptionId, resourceGroup, vnetName, subnetName, storageAccountName
)
on subscriptionId, resourceGroup, vnetName, subnetName
| summarize subnets = make_list(subnetName), storageAccounts = make_list(storageAccountName) by subscriptionId, resourceGroup, vnetName
| project subscriptionId, resourceGroup, vnetName, subnets, storageAccounts
What This Query Does
- ✔ Finds all subnets with Microsoft.Storage service endpoints enabled
- ✔ Finds all storage accounts that have been explicitly whitelisted to those subnets
- ✔ Filters out storage accounts that do not have virtual network rules applied
- ✔ Summarizes results per VNet, listing:
- Subnets that have Microsoft.Storage service endpoints (
subnets
) - Storage accounts that whitelist these subnets (
storageAccounts
)
- Subnets that have Microsoft.Storage service endpoints (
Example Output
SubscriptionId | ResourceGroup | VNetName | Subnets | StorageAccounts |
---|---|---|---|---|
sub-12345 | RG-Network | VNet-East | ["Subnet1", "Subnet2"] | ["StorageAccount1", "StorageAccount2"] |
sub-67890 | RG-Storage | VNet-West | ["Subnet3"] | ["StorageAccount3"] |
Why This Matters:
- If a subnet is whitelisted, any VMs or other resources in that subnet can access the storage account even if a UDR exists to redirect traffic elsewhere.
💻 Automating Service Endpoint Cleanup Using PowerShell Script
Now that we have identified which subnets are whitelisted to storage accounts, the next step is to automate the removal of these service endpoints using PowerShell with the Az module. This script will help you clean up service endpoints from subnets that no longer require direct access to storage accounts.Trafic will be redirected to the firewall or NVA for inspection if there is any.
# Login to Azure - Assuming you have already logged in to Azure using Connect-AzAccount
$subscriptionId = "12345678-aaaa-bbbb-cccc-1234567890ab"
Select-AzSubscription -SubscriptionId $subscriptionId
# Query Azure Resource Graph for Subnets with Microsoft.Storage Service Endpoints
$kqlQuery = @"
resources
| where type == "microsoft.network/virtualnetworks"
| mv-expand subnets = properties.subnets
| extend subnetName = tostring(subnets.name),
vnetName = name,
serviceEndpoints = subnets.properties.serviceEndpoints
| mv-expand serviceEndpoint = serviceEndpoints
| where isnotempty(serviceEndpoint) and serviceEndpoint.service in~ ("Microsoft.Storage", "Microsoft.Storage.Global")
| project subscriptionId, resourceGroup, vnetName, subnetName
"@
$subnetResults = Search-AzGraph -Query $kqlQuery
if ($subnetResults.Count -eq 0) {
Write-Host "No subnets with Microsoft.Storage service endpoints found."
exit
}
ForEach ($subnet in $subnetResults) {
$resourceGroup = $subnet.resourceGroup
$vnetName = $subnet.vnetName
$subnetName = $subnet.subnetName
Write-Output "Processing: $vnetName / $subnetName in $resourceGroup"
$vnet = Get-AzVirtualNetwork -ResourceGroupName $resourceGroup -Name $vnetName -ErrorAction SilentlyContinue
if (-not $vnet) {
Write-Host "Could not find VNet: $vnetName in $resourceGroup. Skipping..." -ForegroundColor Red
continue
}
# Get the current subnet configuration
$currentSubnet = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $vnet -Name $subnetName
# Remove the Microsoft.Storage Service Endpoint
if ($currentSubnet.ServiceEndpoints) {
$updatedEndpoints = $currentSubnet.ServiceEndpoints `
| Where-Object { $_.Service -ne "Microsoft.Storage" -and $_.Service -ne "Microsoft.Storage.Global" }
# Apply the updated subnet configuration
Set-AzVirtualNetworkSubnetConfig -VirtualNetwork $vnet `
-Name $subnetName `
-AddressPrefix $currentSubnet.AddressPrefix `
-ServiceEndpoint $updatedEndpoints
# Apply changes to VNet
Set-AzVirtualNetwork -VirtualNetwork $vnet
Write-Output "Removed Microsoft.Storage service endpoint from: $vnetName / $subnetName"
}
else {
Write-Output "No Microsoft.Storage service endpoints found for: $vnetName / $subnetName"
}
}
Write-Host "Service endpoint cleanup completed!"
🚀 Execute the Script for Service Endpoint Cleanup
Run the PowerShell script to clean up whitelisted subnets.
- Caution: Removing service endpoints from a subnet can disrupt existing services that rely on direct access to Azure Storage or other services. Plan carefully before making changes, and consider running this in a non-production environment first to assess the impact.
Monitor effective routes to validate security compliance.
- Use Azure Resource Graph and Azure Route Tables to check how traffic is being routed before and after changes.
Use Azure Policy to deny the usage of service endpoints unless explicitly approved.
- Use case-by-case exemptions where service endpoints are strictly necessary for workloads.