借助 Azure Lighthouse 跨租户使用 Bicep 模块注册表

当你在一个 Azure 租户中托管私有 Bicep 模块注册表,并希望在面向另一租户的部署中使用这些模块时,就需要让执行部署的标识能够向源租户中的 Azure 容器注册表 (ACR) 进行身份验证。 Azure Lighthouse 允许你向来自一个租户的主体授予对另一个租户中资源的访问权限,从而无需维护单独的凭据即可解决此问题。

本文介绍如何执行以下操作:

  • 理解为何跨租户登记访问需要Lighthouse
  • 创建并部署一个Lighthouse委派,赋予部署身份AcrPull权限
  • 配置一个从 跨租户注册表部署的 Azure DevOps 管道

为什么Lighthouse解决了跨租户登记访问问题

当 Bicep 编译引用存储在注册表中的模块的文件(例如,br:myregistry.azurecr.io/modules/storage:v1),它会在部署前恢复该模块。 恢复步骤会根据 credentialPrecedence 中的 ,使用你正在运行的工具(Azure CLI 或 Azure PowerShell)的凭据向 ACR 进行身份验证。

当部署目标位于租户 B 中,而注册表位于租户 A 中时,用于部署的标识需要:

  • 租户 A 中 ACR 的 AcrPull 权限(用于在编译期间还原模块)
  • 租户B中的部署权限(用于部署编译后的资源)

单个服务主体不能在两个无关租户之间原生地担任角色。 Azure Lighthouse 通过将租户 B 中的主体映射到租户 A,解决了这一问题,因此你可以将租户 A 中的角色分配给该主体,而无需在租户 A 的目录中管理该主体。

Azure Lighthouse委托的工作原理

灯塔代表团由两种资源组成:

Resource Scope Purpose
Microsoft.ManagedServices/registrationDefinitions Subscription 定义服务提供内容:哪个租户负责管理哪些内容,以及应授予哪些角色
Microsoft.ManagedServices/registrationAssignments 资源组 将委派应用于特定的资源组

你把两个资源都部署到租 户A,也就是拥有ACR的租户。 该委托将 租户 B 中的主体授予对包含 ACR 的资源组的 AcrPull 角色。

先决条件

  • 位于租户 A中的一个 ACR,用于托管你的 Bicep 模块。
  • 租户 B中用于执行部署的服务主体或托管标识(例如 Azure DevOps 服务连接)。
  • 租户 B目录中主体的对象 ID。 Lighthouse 从管理租户(租户 B)获取主体 ID,并将其映射到租户 A。
  • 在租户 A 中 ACR 所在的订阅上具有 参与者所有者 权限,或者具有包含 Microsoft.Authorization/roleAssignments/writeMicrosoft.Authorization/roleAssignments/deleteMicrosoft.Authorization/roleAssignments/read 的自定义角色。

步骤1:将灯塔代表团部署到租户A处

代表团需要两份Bicep文件。 在租户 A 中于订阅范围内部署主文件。分配模块以你的 ACR 所在的资源组为目标。

主代表团档案

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

分配模块(acrDelegationAssignment.bicep)

将此文件与主文件一起保存为 acrDelegationAssignment.bicep

targetScope = 'resourceGroup'

param acrDefinitionResourceId string
param assignmentName string

resource acrDelegationAssignment 'Microsoft.ManagedServices/registrationAssignments@2022-10-01' = {
  name: assignmentName
  properties: {
    registrationDefinitionId: acrDefinitionResourceId
  }
}

部署到租户A。

部署账户必须在租户A的订阅上拥有相应权限。通过Azure CLI向租户A认证,然后在订阅范围内部署。 分配模块会自动将其作用域缩小到资源组:

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"}]'

你只需要部署这个代表团一次。 一旦安装好,后续模块部署时你就不需要再动租户A了。

步骤2:配置 bicepconfig.json

在你的 Bicep 项目中,配置bicepconfig.json以定义注册表别名并设置凭证提供者。 当你在管道中使用 Azure CLI 时,设置credentialPrecedence["AzureCLI"]

{
  "moduleAliases": {
    "br": {
      "RegistryAlias": {
        "registry": "myregistry.azurecr.io",
        "modulePath": "bicep/modules"
      }
    }
  },
  "cloud": {
    "credentialPrecedence": [
      "AzureCLI"
    ]
  }
}

在这种配置下,Bicep 在还原模块时使用 Azure CLI 令牌缓存,这也是你的管道中 az deployment 命令使用的同一个令牌。 由于Lighthouse委派将AcrPull授予租户B的服务主体,该令牌现在可以访问租户A的ACR。

步骤3:配置您的Azure DevOps流水线

同时使用针对 租户B 的服务连接来完成这两个任务。 租户 A 无需单独登录,Lighthouse 委派会以透明方式处理跨租户访问。

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

az deployment group create 命令:

  • 编译main.bicep,通过使用 Azure CLI 令牌(由 Lighthouse 委派允许)从租户 A 的 ACR 触发模块恢复。
  • 将生成的ARM模板部署到租户B的资源组

验证委派是否正常工作

如果灯塔委派未到位或被撤销,你可能会遇到类似错误:

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.

这个错误意味着编译过程中使用的令牌注册表上没有 AcrPull。 请确认:

  • 灯塔注册定义和分配已成功部署到租户A。
  • 委派中的 principalId 与你的管道所使用的服务连接的服务主体对象 ID 匹配。
  • credentialPrecedence 中的 bicepconfig.json 包括 "AzureCLI"(如果你使用 PowerShell,则为 "AzurePowerShell")。