1

I am trialling the use of Bicep and container apps in my organisation and we have separated out concerns within the SAME tenant but in different subscriptions like so:

  1. Development
  2. Production
  3. Management

I want to be able to deploy each of these subscriptions using Bicep scripts (individual ones per subscription) and ideally only use managed identity for security.

Within the management subscription we have an ACR which has the admin account intentionally disabled as I don't want to pull via username/password. Question one, should this be possible? As it seems that we should be able to configure an AcrPull role against the container app(s) without too much trouble.

The idea being that the moment the container app is deployed it pulls from the Acr and is actively useable. I don't want an intermediary such as Azure DevOps handling the orchestration for example.

In bicep I've successfully configured the workspace, container environment but upon deploying my actual app I'm a bit stuck - it fails for some incomprehensible error message which I'm still digging into. I've found plenty of examples using the admin/password approach but documentation for alternatives appears lacking which makes me worry if I'm after something that isn't feasible. Perhaps user identity is my solution?

My bicep script (whilst testing against admin/password) looks like this:

  name: containerAppName
  location: location

  identity: {
    type: 'SystemAssigned'
  }
  
  properties: {
    managedEnvironmentId: containerAppEnvId
    configuration: {
      secrets: [
        {
          name: 'container-registry-password'
          value: containerRegistry.listCredentials().passwords[0].value
        }
      ]
      ingress: {
        external: true
        targetPort: targetPort
        allowInsecure: false
        traffic: [
          {
            latestRevision: true
            weight: 100
          }
        ]
      }
      registries: [
        {
          server: '${registryName}.azurecr.io'
          username: containerRegistry.listCredentials().username
          passwordSecretRef: 'container-registry-password'
        }
      ]
    }
    template: {
      revisionSuffix: 'firstrevision'
      containers: [
        {
          name: containerAppName
          image: containerImage
          resources: {
            cpu: json(cpuCore)
            memory: '${memorySize}Gi'
          }
        }
      ]
      scale: {
        minReplicas: minReplicas
        maxReplicas: maxReplicas
      }
    }
  }
}

However this is following an admin/password approach. For using managed identity, firstly do I need to put a registry entry in there?

``` registries: [
        {
          server: '${registryName}.azurecr.io'
          username: containerRegistry.listCredentials().username
          passwordSecretRef: 'container-registry-password'
        }
      ]

If so, the listCredentials().username obviously won't work with admin/password disabled. Secondly, what would I then need in the containers section

containers: [
        {
          name: containerAppName
          image: containerImage ??
          resources: {
            cpu: json(cpuCore)
            memory: '${memorySize}Gi'
          }
        }
      ]

As there appears to be no mention of the need for pointing at a repository, or indeed specifying anything other than a password/admin account. Is it that my requirement is impossible as the container app needs to be provisioned before managed identity can be applied to it? Is this a chicken vs egg problem?

Thomas
  • 24,234
  • 6
  • 81
  • 125
The Senator
  • 5,181
  • 2
  • 34
  • 49

1 Answers1

2

You could use a user-assigned identity:

  1. Create a user assigned identity
  2. Grant permission to the user-assigned identity
  3. Assign the identity to the container app
# container-registry-role-assignment.bicep
param registryName string
param roleId string
param principalId string

// Get a reference to the existing registry
resource registry 'Microsoft.ContainerRegistry/registries@2021-06-01-preview' existing = {
  name: registryName
}

// Create role assignment
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
  name: guid(registry.id, roleId, principalId)
  scope: registry
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
    principalId: principalId
    principalType: 'ServicePrincipal'
  }
}

Then from your main:

param name string
param identityName string
param environmentName string
param containerImage string
param location string = resourceGroup().location

param containerRegistrySubscriptionId string = subscription().subscriptionId
param containerRegistryResourceGroupName string = resourceGroup().name
param containerRegistryName string

// Create identtiy
resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview'  = {
  name: identityName
  location: location
}

// Assign AcrPull permission
module roleAssignment 'container-registry-role-assignment.bicep' = {
  name: 'container-registry-role-assignment'
  scope: resourceGroup(containerRegistrySubscriptionId, containerRegistryResourceGroupName)
  params: {
    roleId: '7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull
    principalId: identity.properties.principalId
    registryName: containerRegistryName
  }
}

// Get a reference to the container app environment
resource managedEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = {
  name: environmentName
}

// create the container app
resource containerapp 'Microsoft.App/containerApps@2022-03-01' = {
  dependsOn:[
    roleAssignment
  ]
  name: name
  ...
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${identity.id}': {}
    }
  }
  properties: {
    managedEnvironmentId: managedEnvironment.id
    configuration: {
      ...
      registries: [
        {
          server: '${containerRegistryName}.azurecr.io'
          identity: identity.id
        }
      ]
    }
    template: {
      ...
      containers: [
        {
          name: name
          image: '${containerRegistryName}.azurecr.io/${containerImage}'
          ...
        }
      ]
    }
  }
}
Thomas
  • 24,234
  • 6
  • 81
  • 125
  • I feel like this is almost the right answer, it failed for me as the roleId in the above Azure Container Registry is in a different subscription to that of the container app. So how do I make that roleId correct in a different subscription? Your answer is great by the way, and I truly appreciate the time. I'm guessing actually there is a parameter on the subscriptionResourceId for that? – The Senator Nov 16 '22 at 10:19
  • Edited my answer so you can specify a different scope for the role module – Thomas Nov 16 '22 at 18:39
  • you sir are a genius, got it working following your advice. As an aside, do you know if this can be achieved using System Managed Identity? Or is this a proper chicken and egg situation? – The Senator Nov 20 '22 at 16:27
  • 1
    You could use system-assigned identity if you deploy a blank (with the out of the box public image) first otherwise it is a chicken an egg situation. – Thomas Nov 20 '22 at 19:15
  • 1
    In which case I'll stick with the User Assigned. I don't want to do a two phase release strategy. Thank you so much for your help! – The Senator Nov 21 '22 at 18:00