Blog Post

Azure Networking Blog
6 MIN READ

Automated Deployment of Cross-Tenant Azure Virtual Hubs Virtual Networks Connection

alishamb's avatar
alishamb
Icon for Microsoft rankMicrosoft
Mar 19, 2025

In modern cloud infrastructure, interconnecting resources across different Azure tenants is essential for various business scenarios, such as mergers, acquisitions, or multi-departmental collaborations. This article will walk you through creating a Virtual Hub in a Virtual WAN and connecting to a Virtual Network in a different tenant from the hub using Bicep and Azure Pipelines.

The pipeline will be using a Service Principal to create and access resources in the two Tenants. The client ID and secret need to be stored in a Key Vault, the details of which would be passed as parameters to the pipeline.

 

Mutli-Tenant SPN Setup and Access

As mentioned in the Azure Documentation, we would be using the below command in the pipeline to setup the connection between the hub and virtual network:

az network vhub connection create --resource-group "[resource_group_name]" --name "[connection_name]" --vhub-name "[virtual_hub_name]" --remote-vnet "/subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e/resourceGroups/rgName/providers/Microsoft.Network/virtualNetworks/vnetName"

 

Since a service principal is being used to run the pipelines, the SPN would need contributor access on the appropriate subscriptions on both the main tenant and the remote tenant. This can be achieved using the following steps:

1. Create Service Principal and Assign Roles

We will use a single SPN for the creation of VWAN,  Vhub and the connection to the remote virtual network. Create the service principal in the Tenant that will contain Virtual WAN and the hub and provide contributor access on the subscription level.

2. Setting Up Multi-Tenant SPN
  • Assign the SPN Contributor access on the subscription where the Vnet resides. 

Note: The contributor access on the Subscription is the lowest possible access required for multi-tenant setup. Contributor access on the Resource Group level would not work.

Code Explanation

The code and parameter files can be found here.

There are 3 main Azure Bicep files that manages the creation of the resources:

Network.bicep
  • The file retrieves the required modules to create a single VWAN and multiple hubs in the VWAN, along with a virtual network and resource group(s). The naming convention is fixed based on environment (TST, PROD etc.) and region of resource (west us, east us etc.)
  • It also contains the following code to create a connection between the virtual network (either provide ID of the VNET or parameters for the creation of the VNET) in the same tenant as the hub. 
  • The parameter file used is ‘network.main.parameters.json’ 
  • The below code would only run if createHubVirtualNetworkConnection is set to True. By default, the parameter is set to False.
module connectVnetHub '../Modules/VirtualHub/connectVnet.bicep' = if (createHubVirtualNetworkConnection){
  name: 'connect-vnet-to-hub'
  scope: resourceGroup(vwan.subscriptionID, vwan.resourceGroupName)
  params: {
    vhubName: 'vwanhub-${env}-${stage}-${vnet.location}-001'
    virtualNetworkID: (createVnet) ? virtualnetwork.outputs.resourceId : vnet.id
    connectionName: '${purpose}-vnet-${env}'
  }
  dependsOn: [
    vWan
    vwanHub
    virtualnetwork
  ]
}

 

The above module calls the below bicep file to create the connection:

param vhubName string
param virtualNetworkID string
param connectionName string

resource vhub 'Microsoft.Network/virtualHubs@2023-04-01' existing = {
  name: vhubName
}

resource connectVnet 'Microsoft.Network/virtualHubs/hubVirtualNetworkConnections@2023-04-01' = {
  name: connectionName
  parent: vhub
  properties: {
    remoteVirtualNetwork: {
      id: virtualNetworkID
    }
  }
}

 

RemoteVnetSubnet.bicep
  • This Bicep file creates multiple Resource Groups, VNETs and Subnets as mentioned in the parameter file.
  • Parameter file name is network.remote.parameters.json.
VhubRemoteVnetMapping.bicep
  • This file calls the module to connect the remote VNET(s) to the Vhub. The appropriate Virtual Hub is selected based on the region. For example, a VNET created in east us will be connected to the hub is east us.
  • The multi-tenant SPN needs to be passed as a parameter which is required to run the PowerShell script in the module. 
  • The SPN details is stored in a Key Vault and the below code is used to retrieve the Client ID and secret:
param keyVaultName string
param keyVaultResourceGroup string
param keyVaultSubscription string

resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing =  {
  name: keyVaultName
  scope: resourceGroup(keyVaultSubscription, keyVaultResourceGroup)
}
  • The following code can connect multiple VNETs to the right virtual hub:
targetScope='subscription'

param vnets object [] = []
param vwan object
param env string = 'main'
param stage string = 'prod'

module connectRemoteVnetVhub '../Modules/VirtualHub/connectRemoteVnet.bicep' = [for i in range(0, length(vnets)): {
  name: 'connect-${vnets[i].vnetName}-to-vhub'
  scope: resourceGroup(vwan.subscriptionID, vwan.resourceGroupName)
  params: {
    remoteResourceGroup: vnets[i].resourceGroupName
    remoteSubscriptionID: vnets[i].subscriptionID
    remoteTenant: vnets[i].tenantID
    remoteVnetName: vnets[i].vnetName
    vhubName: 'vwanhub-${env}-${stage}-${vnets[i].location}-001'
    subscriptionID: vwan.SubscriptionID
    tenantID: vwan.tenantID
    clientID: keyVault.getSecret('clientid-Main')
    clientSecret: keyVault.getSecret('clientsecret-Main')
    connectionName: '${vnets[i].vnetName}-dns-connection' 
    location: vnets[i].location
  }
}]

 

The module basically contains a deployment script resource that runs a bash script to connect the VNET and the hub. 

resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
  name: 'connectRemote${remoteVnetName}toVhub'
  location: location
  kind: 'AzureCLI'
  properties: {
    arguments: '${tenantID} ${remoteTenant} ${remoteSubscriptionID} ${subscriptionID} ${remoteResourceGroup} ${remoteVnetName} ${connectionName} ${vhubName}'
    environmentVariables: [
      {
        name: 'parentResourceGroupName'
        value: resourceGroup().name
      }
      {
        name: 'clientID'
        value: clientID
      }
      {
        name: 'clientSecret'
        value: clientSecret
      }
    ]
    azCliVersion: '2.54.0'
    scriptContent: '''
    az login --service-principal -u $clientID -p $clientSecret --tenant $2
    az account set --subscription $3
    az login --service-principal -u $clientID -p $clientSecret --tenant $1
    az account set --subscription $4
    az extension add --name virtual-wan -y --version 0.3.0
    az network vhub connection create --resource-group $parentResourceGroupName --name $7 --vhub-name $8 --remote-vnet "/subscriptions/${3}/resourceGroups/${5}/providers/Microsoft.Network/virtualNetworks/${6}"
    
    '''
    retentionInterval: 'P1D'
  }
}

 

Azure Pipeline Explanation

Even though the SPN created earlier has access to both the main and remote Tenant, it would be a much more secure option to minimize the use of the multi-tenant SPN to just create the VHUB VNET connection due to its elevated access.

Hence, we need to create an additional SPN that has the required access only on the remote Tenant. This new SPN then should be used to deploy only the remote VNET(s) and subnets and use the Multi-tenant SPN to create the main tenant resources and the connection.

The pipeline will retrieve the SPN Client ID and secret from the Key Vault, run a validation on the Bicep code and deploy the resources. Appropriate parameters need to be passed to the pipeline to run the appropriate bicep file. It has to be run 3 times by selecting the following values:

Iteration 1 (To deploy VWAN, VHUB in Main Tenant):

  • Tenant Name: Main
  • Purpose of Deployment: Main Network Deployment
  • Subscription ID: Details of the subscription where the VWAN, VHUB and VNET will reside in the Main Tenant.

Iteration 2 (To deploy Remote VNET):

  • Tenant Name: Remote
  • Purpose of Deployment: Remote Network Deployment
  • Subscription ID: Details of the subscription where the remote VNET will reside in the remote Tenant.

Iteration 3 (To create the cross-tenant VHUB VNET Connection):

  • Tenant Name: Main
  • Purpose of Deployment: Vnet-Hub-Connection
  • Subscription ID: Details of the subscription where the VWAN, VHUB and VNET will reside in the Main Tenant.

Note: Ensure that the Tenant ID for the main and remote Tenant, Client ID and Client Secret of the SPNs created are stored in the Key Vault before running the pipeline.

The below format should be followed while creating the secrets in the key vault:

$clientIDSecretName = "clientid-${{ parameters.tenantName }}"
$clientSecretSecretName = "clientsecret-${{ parameters.tenantName }}"
$tenantIDSecretName = "tenantid-${{ parameters.tenantName }}"   

 where the Parameter 'tenantName' would be either 'Main' or 'Remote'.

 

All the above-mentioned code and parameter files can be found here.

Azure Documentation References 

Updated Mar 19, 2025
Version 1.0
No CommentsBe the first to comment