Use Private Link to access apps on networks with overlapping address spaces.

Leverage Bicep Language to build a solution that uses Private Link to access Apps on networks with overlapping address spaces.

Use Private Link to access apps on networks with overlapping address spaces.

Overlapping IP address spaces often happens when networks from different customers or companies are connected, especially when there’s no centralized IP address management. Azure offers connection methods like Azure VPN Gateway and Azure ExpressRoute. However, they cannot connect networks with overlapping IP addresses.

This article provides step-by-step instructions on using Bicep Language to build a solution for managing overlapping IP address spaces with Azure Private Link.

It guides how to make applications running in one Azure virtual network accessible to users in another virtual network with an overlapping IP address space. We will deploy this solution using Azure Bicep Language.

Azure Bicep Language is a domain-specific language (DSL) that uses a declarative syntax to deploy Azure resources.

Azure Bicep Language is an abstraction on top of Azure Resource Manager (ARM) templates to define Azure resources using declarative Infrastructure as Code.

Prerequisites

Source Code.

You can find the code for this solution in the following URL, so feel free to contribute!

👉 https://github.com/daveRendon/azinsider/tree/staging/application-workloads/utilize-private-link-for-accessing-apps-on-networks-with-overlapping-addresses

Solution Overview

We will author a Bicep template that creates the below architecture reference:

Use Private Link to access apps on networks with overlapping addresses.

The solution will include the following files:

  • 📄 main.bicep: This is the Bicep template
  • 📄 azuredeploy.parameters.json: This parameter file contains the values for deploying your Bicep template.

2. Azure Bicep Template — parameters

Create a new file in your working directory and name it main.bicep . We will define the following parameters:

@description('Username for the Virtual Machine.')
param vmAdminUsername string

@description('Password for the Virtual Machine. The password must be at least 12 characters long and have lower case, upper characters, digit and a special character (Regex match)')
@secure()
param vmAdminPassword string

@description('The size of the VM')
param vmSize string = 'Standard_D2_v3'

@description('Location for all resources.')
param location string = resourceGroup().location
@description('Number of VMs')
param vmCount int = 2

3. Azure Bicep Template — variables

We will define the following variables:

var prefix = 'azinsider'
var vnetName = 'NetworkB-PrivateLink'
var vnetConsumerName = 'NetworkA-PrivateEndpoint'
var vnetAddressPrefix = '10.0.0.0/16'
var frontendSubnetPrefix = '10.0.2.0/24'
var frontendSubnetName = 'frontendSubnet'
var backendSubnetPrefix = '10.0.1.0/24'
var backendSubnetName = 'backendSubnet'
var consumerSubnetPrefix = '10.0.1.0/24'
var consumerSubnetName = 'myPESubnet'
var loadbalancerName = 'myILB'
var backendPoolName = 'myBackEndPool'
var loadBalancerFrontEndIpConfigurationName = 'myFrontEnd'
var healthProbeName = 'myHealthProbe'
var privateEndpointName = 'myPrivateEndpoint'
var vmName = 'myVM'
var networkInterfaceName = '${vmName}-nic'
var vmConsumerName = 'VMConsumer'
var publicIpAddressConsumerName = '${vmConsumerName}PublicIP'
var networkInterfaceConsumerName = '${vmConsumerName}-nic'
var osDiskType = 'StandardSSD_LRS'
var privatelinkServiceName = 'myPLS'
var loadbalancerId = loadbalancer.id

4. Azure Bicep Template — resources

We will define the following resources:

resource vnet 'Microsoft.Network/virtualNetworks@2021-05-01' = {
name: vnetName
location: location
properties: {
addressSpace: {
addressPrefixes: [
vnetAddressPrefix
]
}
subnets: [
{
name: frontendSubnetName
properties: {
addressPrefix: frontendSubnetPrefix
privateLinkServiceNetworkPolicies: 'Disabled'
}
}
{
name: backendSubnetName
properties: {
addressPrefix: backendSubnetPrefix
}
}
]
}
}

resource loadbalancer 'Microsoft.Network/loadBalancers@2021-05-01' = {
name: loadbalancerName
location: location
sku: {
name: 'Standard'
}
properties: {
frontendIPConfigurations: [
{
name: loadBalancerFrontEndIpConfigurationName
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, frontendSubnetName)
}
}
}
]
backendAddressPools: [
{
name: backendPoolName
}
]
inboundNatRules: [

]
loadBalancingRules: [
{
name: 'myHTTPRule'
properties: {
frontendIPConfiguration: {
id: resourceId('Microsoft.Network/loadBalancers/frontendIpConfigurations', loadbalancerName, loadBalancerFrontEndIpConfigurationName)
}
backendAddressPool: {
id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', loadbalancerName, backendPoolName)
}
probe: {
id: resourceId('Microsoft.Network/loadBalancers/probes', loadbalancerName, healthProbeName)
}
protocol: 'Tcp'
frontendPort: 80
backendPort: 80
idleTimeoutInMinutes: 15
}
}
]
probes: [
{
properties: {
protocol: 'Tcp'
port: 80
intervalInSeconds: 15
numberOfProbes: 2
}
name: healthProbeName
}
]
}
dependsOn: [
vnet
]
}

resource networkInterface 'Microsoft.Network/networkInterfaces@2021-05-01' = [for i in range(0, vmCount): {
name: '${networkInterfaceName}${i}'
location: location
properties: {
ipConfigurations: [
{
name: 'ipConfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, backendSubnetName)
}
loadBalancerBackendAddressPools: [
{
id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', loadbalancerName, backendPoolName)
}
]

}
}
]
}
dependsOn: [
loadbalancer
]
}]

resource vm 'Microsoft.Compute/virtualMachines@2021-11-01' = [for i in range(0, vmCount): {
name: '${vmName}${i}'
location: location
properties: {
hardwareProfile: {
vmSize: vmSize
}
osProfile: {
computerName: '${vmName}${i}'
adminUsername: vmAdminUsername
adminPassword: vmAdminPassword
}
storageProfile: {
imageReference: {
publisher: 'MicrosoftWindowsServer'
offer: 'WindowsServer'
sku: '2019-Datacenter'
version: 'latest'
}
osDisk: {
name: '${vmName}${i}OsDisk'
caching: 'ReadWrite'
createOption: 'FromImage'
managedDisk: {
storageAccountType: osDiskType
}
diskSizeGB: 128
}
}
networkProfile: {
networkInterfaces: [
{
id: networkInterface[i].id
}
]
}
}
} ]

resource vmExtension 'Microsoft.Compute/virtualMachines/extensions@2021-11-01' = [for i in range(0, vmCount): {
name: '${vmName}${i}${'installcustomscript'}'
parent: vm[i]
location: location
properties: {
publisher: 'Microsoft.Compute'
type: 'CustomScriptExtension'
typeHandlerVersion: '1.9'
autoUpgradeMinorVersion: true
protectedSettings: {
commandToExecute: 'powershell.exe Install-WindowsFeature -name Web-Server -IncludeManagementTools && powershell.exe remove-item "C:\\inetpub\\wwwroot\\iisstart.htm" && powershell.exe Add-Content -Path "C:\\inetpub\\wwwroot\\iisstart.htm" -Value $($env:computername)'
}
}
}]

resource privatelinkService 'Microsoft.Network/privateLinkServices@2021-05-01' = {
name: privatelinkServiceName
location: location
properties: {
enableProxyProtocol: false
loadBalancerFrontendIpConfigurations: [
{
id: resourceId('Microsoft.Network/loadBalancers/frontendIpConfigurations', loadbalancerName, loadBalancerFrontEndIpConfigurationName)
}
]
ipConfigurations: [
{
name: 'snet-provider-default-1'
properties: {
privateIPAllocationMethod: 'Dynamic'
privateIPAddressVersion: 'IPv4'
subnet: {
id: reference(loadbalancerId, '2019-06-01').frontendIPConfigurations[0].properties.subnet.id
}
primary: false
}
}
]
}
}

resource vnetConsumer 'Microsoft.Network/virtualNetworks@2021-05-01' = {
name: vnetConsumerName
location: location
properties: {
addressSpace: {
addressPrefixes: [
vnetAddressPrefix
]
}
subnets: [
{
name: consumerSubnetName
properties: {
addressPrefix: consumerSubnetPrefix
privateEndpointNetworkPolicies: 'Disabled'
}
}
]
}
}

resource publicIpAddressConsumer 'Microsoft.Network/publicIPAddresses@2021-05-01' = {
name: publicIpAddressConsumerName
location: location
tags: {
displayName: publicIpAddressConsumerName
}
properties: {
publicIPAllocationMethod: 'Dynamic'
dnsSettings: {
domainNameLabel: toLower(vmConsumerName)
}
}
}

resource networkInterfaceConsumer 'Microsoft.Network/networkInterfaces@2021-05-01' = {
name: networkInterfaceConsumerName
location: location
tags: {
displayName: networkInterfaceConsumerName
}
properties: {
ipConfigurations: [
{
name: 'ipConfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: publicIpAddressConsumer.id
}
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetConsumerName, consumerSubnetName)
}
}
}
]
}
dependsOn: [
vnetConsumer
]
}

resource vmConsumer 'Microsoft.Compute/virtualMachines@2021-11-01' = {
name: vmConsumerName
location: location
tags: {
displayName: vmConsumerName
}
properties: {
hardwareProfile: {
vmSize: vmSize
}
osProfile: {
computerName: vmConsumerName
adminUsername: vmAdminUsername
adminPassword: vmAdminPassword
}
storageProfile: {
imageReference: {
publisher: 'MicrosoftWindowsServer'
offer: 'WindowsServer'
sku: '2019-Datacenter'
version: 'latest'
}
osDisk: {
name: '${vmConsumerName}OsDisk'
caching: 'ReadWrite'
createOption: 'FromImage'
managedDisk: {
storageAccountType: osDiskType
}
diskSizeGB: 128
}
}
networkProfile: {
networkInterfaces: [
{
id: networkInterfaceConsumer.id
}
]
}
}
}

resource privateEndpoint 'Microsoft.Network/privateEndpoints@2021-05-01' = {
name: privateEndpointName
location: location
properties: {
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetConsumerName, consumerSubnetName)
}
privateLinkServiceConnections: [
{
name: privateEndpointName
properties: {
privateLinkServiceId: privatelinkService.id
}
}
]
}
dependsOn: [
vnetConsumer
]
}

5. Parameters file

Create a new file named azuredeploy.parameters.json. The code below shows the definition of the parameters file:

{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"value": "East US"
},
"vmAdminUsername": {
"value": "YOUR-ADMIN-USER"
},
"vmAdminPassword": {
"value": "YOUR-PASSWORD"
}
}
}

6. Azure Bicep Template — Deployment

We will use the command below to deploy our Bicep template:

The image below shows the deployment output:

Deployment output

You can verify the deployment using the Azure Portal.

Deployment output -Azure Portal

Once you deploy the environment, you should be able to RDP to the VMConsumer virtual machine.

Then, open a browser in your VMConsumer machine and, using a browser, access the private IP address of the Private Endpoint, like 10.0.0.5. You should be able to access VM0 and VM1.

Sources:

👉 Join the AzInsider email list here.

-Dave R.

--

--