Briefkasten
Briefkasten

Reputation: 1994

How do I configure my bicep scripts to allow a container app to pull an image from an ACR using managed identity using modules

I'm creating my azure Container Registry and my Azure Instance using modules. After creating those I want to assign the permission such that the Azure Container Instance can pull images.

main.bicep:

module dockerContainerRegistry 'modules/dockerContainerRegistry.bicep' = {
    name: 'deploy_dockerContainerRegistry'
    scope: resourceGroup
    [...]
    dependsOn: [
      network
    ]
  }

module dockerContainerInstance 'modules/dockerContainerGroups.bicep' = {
    name: 'deploy_dockerContainerInstance'
    scope: resourceGroup
    [...]
    dependsOn: [
      network
      dockerContainerRegistry
    ]
  }

// roleDefinitionId is the ID found here for AcrPull: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#acrpull
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(resourceGroup.id, dockerContainerRegistry, 'AcrPullSystemAssigned')
  scope:  container
  properties: {
    principalId: dockerContainerInstance.outputs.containerManagedIdentity
    principalType: 'ServicePrincipal'
    // acrPullDefinitionId has a value of 7f951dda-4ed3-4680-a7ca-43fe172d538d
    roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
  }
  dependsOn: [
    dockerContainerInstance,
    dockerContainerRegistry 
  ]
}

But it always fails with something like: Error BCP036: The property "scope" expected a value of type "resource | tenant" but the provided value is of type "string"

I also tried diffrent methods to get the resource of dockerContainerInstance or dockerContainerRegistry with

resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(resourceGroup.id, dockerContainerRegistry, 'AcrPullSystemAssigned')
  scope:  '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup.name}/providers/Microsoft.ContainerInstance/containerGroups/${dockerContainerInstance.name}'
[...]

or

   resource registry 'Microsoft.ContainerRegistry/registries@2021-06-01-preview' existing = {
     name: dockerContainerRegistryName
  }

but the error message is still the same.

I also tried to assign the permissions in an own module with:

dockerRolePullAssignment.bicep:

@description('The resourcegroup which contains the resources')
param targetResourceGroup string

@description('The name of the container registry')
param dockerContainerRegistryName string

@description('The name of the azure container instance')
param dockerContainerInstanceName string

@description('The principal ID of the Azure Container Instance')
param dockerContainerInstanceNamePrincipalId string


resource dockerContainerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = {
  name: dockerContainerRegistryName
  scope: resourceGroup(targetResourceGroup)
}

resource dockerContainerInstance 'Microsoft.ContainerInstance/containerGroups@2022-10-01-preview' existing = {
  name: dockerContainerInstanceName
  scope: resourceGroup(targetResourceGroup)
}



resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
  name: guid(dockerContainerRegistry.id, 'AcrPull')
  scope: dockerContainerRegistry
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') // AcrPull role
    principalId: dockerContainerInstanceNamePrincipalId
  }
}

But this fails with: dockerRolePullAssignment.bicep(28,10) : Error BCP139: A resource's scope must match the scope of the Bicep file for it to be deployable. You must use modules to deploy resources to a different scope.

I also looked up the following examples: https://azureossd.github.io/2023/01/03/Using-Managed-Identity-and-Bicep-to-pull-images-with-Azure-Container-Apps/ and an other SO but they do not use modules. Is it possible that this is not achievable with modules?

Upvotes: 1

Views: 592

Answers (1)

wenbo
wenbo

Reputation: 1506

Hi @Briefkasten sorry for the late response, really busy these days, I re-wrote a new sample based on the code you provided to clarify the behavior of scope. just copy and paste into local and run yourself. hope this can help.

network.bicep

@description('Location for all resources.')
param location string

var virtualNetworkName = 'vNet'
var subnetName = 'backendSubnet'

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' = {
  name: virtualNetworkName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: '10.0.2.0/24'
          delegations: [
            {
              name: 'CloudShellDelegation'
              properties: {
                serviceName: 'Microsoft.ContainerInstance/containerGroups'
              }
            }
          ]
        }
      }
    ]
  }
}

output subnetId string = virtualNetwork.properties.subnets[0].id

dockerContainerGroups.bicep

@description('Name for the container group')
param name string = 'acilinuxpublicipcontainergroup'

@description('Location for all resources.')
param location string

@description('Container image to deploy. Should be of the form repoName/imagename:tag for images stored in public Docker Hub, or a fully qualified URI for other registries. Images from private registries require additional registry credentials.')
param image string = 'mcr.microsoft.com/azuredocs/aci-helloworld'

@description('Port to open on the container and the public IP address.')
param port int = 80

@description('The number of CPU cores to allocate to the container.')
param cpuCores int = 1

@description('The amount of memory to allocate to the container in gigabytes.')
param memoryInGb int = 2

@description('The behavior of Azure runtime if container has stopped.')
@allowed([
  'Always'
  'Never'
  'OnFailure'
])
param restartPolicy string = 'Always'
param subnetId string

resource containerGroup 'Microsoft.ContainerInstance/containerGroups@2021-09-01' = {
  name: name
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    containers: [
      {
        name: name
        properties: {
          image: image
          ports: [
            {
              port: port
              protocol: 'TCP'
            }
          ]
          resources: {
            requests: {
              cpu: cpuCores
              memoryInGB: memoryInGb
            }
          }
        }
      }
    ]
    osType: 'Linux'
    restartPolicy: restartPolicy
    ipAddress: {
      type: 'Private'
      ports: [
        {
          port: port
          protocol: 'TCP'
        }
      ]
    }
    subnetIds: [
      {
        id: subnetId
      }
    ]
  }
}

output containerManagedIdentity string = containerGroup.identity.principalId

dockerContainerRegistry.bicep

@minLength(5)
@maxLength(50)
@description('Provide a globally unique name of your Azure Container Registry')
param acrName string = 'acr${uniqueString(resourceGroup().id)}'

@description('Provide a location for the registry.')
param location string

@description('Provide a tier of your Azure Container Registry.')
param acrSku string = 'Basic'

resource acrResource 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = {
  name: acrName
  location: location
  sku: {
    name: acrSku
  }
  properties: {
    adminUserEnabled: false
    publicNetworkAccess: 'Enabled'
  }
}

@description('Output the login server property for later use')
output loginServer string = acrResource.properties.loginServer

main.bicep

param resourceGroupNetwork string
param resourceGroupContainerRegistry string
param resourceGroupContainerInstance string
param location string
param acrName string

module network 'modules/network.bicep' = {
  scope: resourceGroup(resourceGroupNetwork)
  name: 'deploy_network'
  params: {
    location: location
  }
}

module dockerContainerRegistry 'modules/dockerContainerRegistry.bicep' = {
  name: 'deploy_dockerContainerRegistry'
  scope: resourceGroup(resourceGroupContainerRegistry)
  params: {
    acrName: acrName
    location: location
  }
}

module dockerContainerInstance 'modules/dockerContainerGroups.bicep' = {
  name: 'deploy_dockerContainerInstance'
  scope: resourceGroup(resourceGroupContainerInstance)
  params: {
    location: location
    subnetId: network.outputs.subnetId
  }
  dependsOn: [
    network
    dockerContainerRegistry
  ]
}

resource acrResource 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = {
  name: acrName
}


// roleDefinitionId is the ID found here for AcrPull: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#acrpull
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, 'anything1', 'anything2')
scope:  acrResource
properties: {
  principalId: dockerContainerInstance.outputs.containerManagedIdentity
  principalType: 'ServicePrincipal'
  // acrPullDefinitionId has a value of 7f951dda-4ed3-4680-a7ca-43fe172d538d
  roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
}
dependsOn: [
  acrResource
  dockerContainerInstance
  dockerContainerRegistry 
]
}

deploy.ps1

$resourceGroupNetwork = "wb-test-network-rg"
$resourceGroupContainerRegistry = "wb-test-acr-rg"
$resourceGroupContainerInstance = "wb-test-aci-rg"
## above resource group can be the same one.

$location = "eastus"

$deploymentName = "wbdeployment"
$param = @{
    resourceGroupNetwork = $resourceGroupNetwork
    resourceGroupContainerRegistry = $resourceGroupContainerRegistry
    resourceGroupContainerInstance = $resourceGroupContainerInstance
    acrName = "wbacr324"
    location = $location
}

New-AzResourceGroup -Name $resourceGroupNetwork -Location $location -Force
New-AzResourceGroup -Name $resourceGroupContainerRegistry -Location $location -Force
New-AzResourceGroup -Name $resourceGroupContainerInstance -Location $location -Force

New-AzResourceGroupDeployment -Name $deploymentName -ResourceGroupName $resourceGroupContainerRegistry -TemplateFile ".\main.bicep" -TemplateParameterObject $param

folder structure:

enter image description here

deployment result: all successed.

enter image description here enter image description here

Upvotes: 0

Related Questions