Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
When you host a private Bicep module registry in one Azure tenant and want to consume those modules in deployments targeting a different tenant, you need a way for the deploying identity to authenticate to the Azure Container Registry (ACR) in the source tenant. Azure Lighthouse lets you grant a principal from one tenant access to resources in another, which solves this problem without maintaining separate credentials.
This article describes how to:
- Understand why cross-tenant registry access requires Lighthouse
- Create and deploy a Lighthouse delegation that grants AcrPull to the deploying identity
- Configure an Azure DevOps pipeline that deploys from a cross-tenant registry
Why Lighthouse solves cross-tenant registry access
When Bicep compiles a file that references a module stored in a registry (for example, br:myregistry.azurecr.io/modules/storage:v1), it restores the module before deployment. The restore step authenticates to the ACR using the credentials of the tool you're running, Azure CLI or Azure PowerShell, based on the credentialPrecedence setting in bicepconfig.json.
When the deployment target is in tenant B but the registry is in tenant A, the deploying identity needs:
- AcrPull on the ACR in tenant A (to restore modules during compilation)
- Deployment permissions in tenant B (to deploy the compiled resources)
A single service principal can't natively hold roles across two unrelated tenants. Azure Lighthouse solves this problem by projecting a principal from tenant B into tenant A, so you can assign roles in tenant A to that principal without managing it in tenant A's directory.
How Azure Lighthouse delegation works
A Lighthouse delegation consists of two resources:
| Resource | Scope | Purpose |
|---|---|---|
Microsoft.ManagedServices/registrationDefinitions |
Subscription | Declares the offer: which tenant manages what, and which roles to grant |
Microsoft.ManagedServices/registrationAssignments |
Resource group | Applies the delegation to a specific resource group |
You deploy both resources to tenant A, the tenant that owns the ACR. The delegation grants the principal in tenant B the AcrPull role on the resource group containing the ACR.
Prerequisites
- An ACR in tenant A that hosts your Bicep modules.
- A service principal or managed identity in tenant B that runs your deployments (for example, an Azure DevOps service connection).
- The object ID of the principal in tenant B's directory. Lighthouse takes the principal ID from the managing tenant (tenant B) and projects it into tenant A.
- Contributor or Owner permissions, or a custom role with
Microsoft.Authorization/roleAssignments/write,Microsoft.Authorization/roleAssignments/delete, andMicrosoft.Authorization/roleAssignments/read, on the subscription in tenant A where the ACR resides.
Step 1: Deploy the Lighthouse delegation to tenant A
The delegation needs two Bicep files. Deploy the main file at subscription scope in tenant A. The assignment module targets the resource group where your ACR resides.
Main delegation file
targetScope = 'subscription'
@description('A unique name for this Lighthouse offer.')
param mspOfferName string = 'bicepAcrDelegation'
@description('A description for this Lighthouse offer.')
param mspOfferDescription string = 'Grants AcrPull to the deploying tenant for Bicep module restore.'
@description('The tenant ID of the managing tenant (tenant B - the deploying tenant).')
param managedByTenantId string // Enter the tenant B GUID
@description('Principals in tenant B to receive AcrPull in tenant A.')
param authorizations array // Example: [{principalId:'<object-id-in-tenant-B>', roleDefinitionId:'7f951dda-4ed3-4680-a7ca-43fe172d538d', principalIdDisplayName:'Bicep registry deployer'}]
@description('The resource group in tenant A that contains the ACR.')
param rgName string = 'bicep-registry-rg'
var registrationName = guid(mspOfferName, 'definition')
var assignmentName = guid(mspOfferName, 'assignment')
resource acrDelegationDefinition 'Microsoft.ManagedServices/registrationDefinitions@2022-10-01' = {
name: registrationName
properties: {
registrationDefinitionName: mspOfferName
description: mspOfferDescription
managedByTenantId: managedByTenantId
authorizations: authorizations
}
}
module acrDelegationAssignment './acrDelegationAssignment.bicep' = {
name: assignmentName
scope: resourceGroup(rgName)
params: {
acrDefinitionResourceId: acrDelegationDefinition.id
assignmentName: assignmentName
}
}
output mspOfferName string = 'Managed by ${mspOfferName}'
output authorizations array = authorizations
Assignment module (acrDelegationAssignment.bicep)
Save this file alongside the main file as acrDelegationAssignment.bicep:
targetScope = 'resourceGroup'
param acrDefinitionResourceId string
param assignmentName string
resource acrDelegationAssignment 'Microsoft.ManagedServices/registrationAssignments@2022-10-01' = {
name: assignmentName
properties: {
registrationDefinitionId: acrDefinitionResourceId
}
}
Deploy to tenant A
The deploying account must have the appropriate permissions on the subscription in tenant A. Authenticate to tenant A by using the Azure CLI, and then deploy at subscription scope. The assignment module automatically scopes itself down to the resource group:
az cloud set -n AzureChinaCloud
az login --tenant <tenant-A-id>
az deployment sub create \
--location chinanorth3 \
--subscription <tenant-A-subscription-id> \
--template-file main.bicep \
--parameters managedByTenantId='<tenant-B-id>' \
authorizations='[{"principalId":"<object-id-from-tenant-B>","roleDefinitionId":"7f951dda-4ed3-4680-a7ca-43fe172d538d","principalIdDisplayName":"Bicep registry deployer"}]'
You only need to deploy this delegation once. After it's in place, you don't need to touch tenant A again for subsequent module deployments.
Step 2: Configure bicepconfig.json
In your Bicep project, configure bicepconfig.json to define the registry alias and set the credential provider. When you're using Azure CLI in a pipeline, set credentialPrecedence to ["AzureCLI"]:
{
"moduleAliases": {
"br": {
"RegistryAlias": {
"registry": "myregistry.azurecr.io",
"modulePath": "bicep/modules"
}
}
},
"cloud": {
"credentialPrecedence": [
"AzureCLI"
]
}
}
With this configuration, Bicep uses the Azure CLI token cache when restoring modules, the same token used by the az deployment command in your pipeline. Because the Lighthouse delegation grants AcrPull to the service principal in tenant B, that token now has access to the ACR in tenant A.
Step 3: Configure your Azure DevOps pipeline
Use the service connection that targets tenant B for both tasks. No separate login step for tenant A is needed, the Lighthouse delegation handles the cross-tenant access transparently.
trigger: none
pool:
vmImage: ubuntu-latest
jobs:
- job: Deploy
steps:
- task: AzureCLI@2
displayName: Upgrade Bicep CLI
inputs:
azureSubscription: 'my-tenant-b-service-connection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: az bicep upgrade
- task: AzureCLI@2
displayName: Deploy Bicep template
inputs:
azureSubscription: 'my-tenant-b-service-connection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group create \
--name $(Build.BuildNumber) \
--resource-group my-target-rg \
--template-file main.bicep
The az deployment group create command:
- Compiles
main.bicep, which triggers module restore from the ACR in tenant A using the Azure CLI token (permitted by the Lighthouse delegation) - Deploys the resulting ARM template to the resource group in tenant B
Verify the delegation is working
If the Lighthouse delegation isn't in place or is revoked, you might encounter a similar error:
ERROR: /home/runner/work/main.bicep(6,13) : Error BCP192: Unable to restore the module with reference
"br:myregistry.azurecr.io/bicep/modules/storage:v1": Unhandled exception:
Azure.RequestFailedException: Service request failed.
This error means the token used during compilation doesn't have AcrPull on the registry. Verify that:
- The Lighthouse registration definition and assignment were deployed successfully to tenant A.
- The
principalIdin the delegation matches the object ID of the service principal used by your pipeline's service connection. - The
credentialPrecedenceinbicepconfig.jsonincludes"AzureCLI"(or"AzurePowerShell"if you're using PowerShell).