Private linking resources from inside an Azure VM to multiple regional networks

VM connected to Key Vault instances in multiple regions with Private Link

Setting the stage

When working with Private Linking and Private DNS the private DNS zone name and the subresource or groupId has to be used according to this table. Hence the script takes that groupId as input parameter as well as 2 RegEx patterns to identify resources and resource groups to be linked:

param (    [Parameter(Mandatory)]
[ValidateSet(
"blob",
"configurationStores",
"namespace",
"registry",
"sites",
"Sql",
"sqlServer",
"table",
"vault"
)]
[string]
$GroupId,
[Parameter(Mandatory)]
[string]
$ResourceNamePattern,
[Parameter(Mandatory)]
[string]
$ResourceGroupNamePattern,
[switch]
$SkipDeletes,
[switch]
$SkipCreate
)
# determine resource Idsswitch ($GroupId) {
"blob" {
$dnsZone = "privatelink.blob.core.windows.net"
$resourceType = "Microsoft.Storage/storageAccounts"
}
"configurationStores" {
$dnsZone = "privatelink.azconfig.io"
$resourceType = "Microsoft.AppConfiguration/configurationStores"
}
"namespace" {
$dnsZone = "privatelink.servicebus.windows.net"
$resourceType = "Microsoft.ServiceBus/namespaces"
}
"registry" {
$dnsZone = "privatelink.azurecr.io"
$resourceType = "Microsoft.ContainerRegistry/registries"
}
"sites" {
$dnsZone = "privatelink.azurewebsites.net"
$resourceType = "Microsoft.Web/sites"
}
"Sql" {
$dnsZone = "privatelink.documents.azure.com"
$resourceType = "Microsoft.AzureCosmosDB/databaseAccounts"
}
"sqlServer" {
$dnsZone = "privatelink.database.windows.net"
$resourceType = "Microsoft.Sql/servers"
}
"table" {
$dnsZone = "privatelink.table.core.windows.net"
$resourceType = "Microsoft.Storage/storageAccounts"
}
"vault" {
$dnsZone = "privatelink.vaultcore.azure.net"
$resourceType = "Microsoft.KeyVault/vaults"
}
}
$resources = @()az resource list --resource-type $resourceType -o json | ConvertFrom-Json | `
? { $_.name -match $ResourceNamePattern -and $_.resourceGroup -match $ResourceGroupNamePattern } | % {
$resources += @{id = $_.id; name = $_.name }
}
if ($resources.Count -eq 0) {
Write-Error "No resources found matching the pattern"
Exit
}

Extracting VM metadata

To retrieve information about the Azure VM one is currently working on, the metadata information endpoint can be used:

$vmInfo = Invoke-RestMethod -Headers @{"Metadata" = "true" } -Method GET -NoProxy -Uri "http://169.254.169.254/metadata/instance?api-version=2021-02-01"$nicId = az vm show --subscription $vmInfo.compute.subscriptionId --resource-group $vmInfo.compute.resourceGroupName --name $vmInfo.compute.name --query networkProfile.networkInterfaces[0].id --output tsv$subnetId = az network nic show --ids $nicId --query ipConfigurations[0].subnet.id --output tsv$vnetInfo = $subnetId.split('/')[0..8]
$vnetId = [string]::Join("/", $vnetInfo)

Wait a minute — why mixing PowerShell and Azure CLI?

I decided to use Azure CLI instead of Azure PowerShell as CLI allows working with resources in subscriptions outside the current context by simply specifying the remote --subscription and --resource-group. With Azure PowerShell I would be needing to switch back and forth - from/to subscription which holds the VM and to/from subscription which holds the regional networks and resources.

Private DNS zone

First we need a private DNS zone connected to the VMs virtual network which will receive the private IP address entries of the private link endpoints:

Write-Host "create/check private DNS zone" $dnsZone "for group" $groupIdif (!$(az network private-dns zone list -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId --query "[?name == '$dnsZone'].id" -o tsv)) {
az network private-dns zone create -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId `
-n $dnsZone
}
Write-Host "create private DNS link"az network private-dns link vnet create -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId `
-n $("vnet-" + $groupId + "-dns-link") `
-z $dnsZone -v $vnetId -e false

Private link + endpoints

The script then iterates through a the set of resources …

  • creates names unique within the Azure VMs subscription + resource group for private link, private endpoint and DNS zone group
  • creates a private endpoint and link from the target resource to VMs subnet (could also be another subnet in the VMs virtual network)
  • creates a private DNS zone group which automatically maintains the private DNS zone record for the private endpoint within the private DNS zone connected to the VMs virtual network
Write-Host "create/check private DNS zone" $dnsZone "for group" $groupIdif (!$(az network private-dns zone list -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId --query "[?name == '$dnsZone'].id" -o tsv)) {
az network private-dns zone create -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId `
-n $dnsZone
}
Write-Host "create private DNS link"az network private-dns link vnet create -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId `
-n $("vnet-" + $groupId + "-dns-link") `
-z $dnsZone -v $vnetId -e false
Write-Host "private link resources"foreach ($resource in $resources) {
Write-Host "link" $resource.name
$linkName = $GroupId + "-" + $resource.name + "-" + $vmInfo.compute.name + "-link"
$endpointName = $GroupId + "-" + $resource.name + "-" + $vmInfo.compute.name + "-pep"
$groupName = $GroupId + "-" + $resource.name + "-" + $vmInfo.compute.name + "-zonegroup"
az network private-endpoint create --connection-name $linkName `
--name $endpointName `
-g $vmInfo.compute.resourceGroupName `
--subscription $vmInfo.compute.subscriptionId `
--private-connection-resource-id $resource.id `
--group-id $GroupId `
--subnet $subnetId
az network private-endpoint dns-zone-group create `
-g $vmInfo.compute.resourceGroupName `
--subscription $vmInfo.compute.subscriptionId `
--endpoint-name $endpointName `
--name $groupName `
--private-dns-zone $dnsZone `
--zone-name $dnsZone
}
  • CosmosDB
  • API Management
  • Container Registry

Housekeeping

At beginning of the script existing virtual network peerings (which in my case could potentially cause conflicts with private links), existing private endpoints and private DNS links are deleted from the VMs virtual network to give the script a clean slate to work on:

Write-Host "delete existing (VM's) virtual network links"az network vnet peering list --vnet-name $vnetInfo[8] -g $vnetInfo[4] --subscription $vnetInfo[2] --output json | ConvertFrom-Json | % {
az network vnet peering delete --ids $_.Id
}
Write-Host "delete existing (VM's) private endpoint links"az network private-endpoint list -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId --output json | ConvertFrom-Json | % {
az network private-endpoint delete --ids $_.Id
}
Write-Host "delete existing (VM's) private DNS links"if ($(az network private-dns zone list -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId --query "[?name == '$dnsZone'].id" -o tsv)) {
az network private-dns link vnet list -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId --zone-name $dnsZone -o json | ConvertFrom-Json | % {
if ($_.virtualNetwork.id -eq $vnetId) {
Write-Host "deleting link: $($_.id)"
az network private-dns link vnet delete -g $vmInfo.compute.resourceGroupName --subscription $vmInfo.compute.subscriptionId --zone-name $dnsZone --name $_.name --yes
}
}
}

Conclusion

For me this is a very handy approach to switch with my Azure VM between various isolated environments.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Kai Walter

Kai Walter

35 years IT enterprise software development and project veteran.