SoliDeoGloria.tech

Technology for the Glory of God

Use a Parameter to Assign User Assigned Managed Identities to Resources With Bicep

  • 3 minutes
Bicep to Managed Identity Icons

Isn’t that title a mouthful.

Coming from Terraform, there are somethings that seem strange in Bicep. One of those is the way that the Resource Manager API handles assigning User Assigned Managed Identities (UAMIs). If you look at the API documentation for a resource (in this case we are going to use an Event Hub Namespace, but this applies to all resources that can have a UAMI assigned) you will see that the userAssignedIdentities value of the identity property looks lkie this:

identity: {
  type: 'string'
  userAssignedIdentities: {
    {customized property}: {}
  }
}

What this means is we need to supply an object to the property where the key is the ID of the UAMI wrapped in braces ({}) and the value is an empty object. In Terraform, we can simply provide an array of objects and it handles the rest. So how can we replicate this behaviour in a Bicep module? Enter Bicep lambda functions. Lambda functions allow us to create a lambda expression to perform actions on an object or array. Common uses of lambda functions are to filter, map, or reduce an array, or to transform one into an object. In this case, we are going to use a lambda function to transform an array of UAMI IDs into the object that the API expects.

As mentioned above, we are going to use an Event Hub Namespace as an example.

param userAssignedIdentities array = []
var suffix = uniqueString(resourceGroup().id, deployment().name)

resource eventHubNamespace 'Microsoft.EventHub/namespaces@2024-05-01-preview' = {
  name: 'uami-evh-${suffix}'
  location: deployment().location
  sku: {
    name: 'Standard'
    tier: 'Standard'
  }
  identity: length(userAssignedIdentities) > 0
    ? {
        type: 'SystemAssigned, UserAssigned'
        userAssignedIdentities: reduce(userAssignedIdentities, {}, (result, id) => union(result, { '${id}': {} }))
      }
    : {
        type: 'SystemAssigned'
      }
}

After declaring the userAssignedIdentities parameter, we create a new Event Hub Namespace. When constructing the identity property, we first check if any userAssignedIdentities are provided. If we have any, we then use the reduce function to ‘reduce’ the array into an object. The three arguments to the reduce function are the array to reduce, the initial value of the result object, and the lambda function to apply to each element. In the lambda function, we declare two variables: result which stores the current state of the result object, and id which is the current element of the array (in our case the resource ID of the UAMI). We then create a new object, and use the union function to merge our constructed object into the result object.

In my situation, I needed to also include a couple of common identities - one for the encryption key, and the other for publishing metrics to Log Analytics. My updated code looks like this:

resource eventHubNamespace 'Microsoft.EventHub/namespaces@2024-05-01-preview' = {
  name: 'uami-evh-${suffix}'
  location: deployment().location
  sku: {
    name: 'Standard'
    tier: 'Standard'
  }
  identity: {
    type: 'SystemAssigned, UserAssigned'
    userAssignedIdentities: reduce(
      flatten([
        [
          '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/EncryptionId'
          '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/MonitoringId'
        ]
        userAssignedIdentities
      ]),
      {},
      (curr, id) => union(curr, { '${id}': {} })
    )
  }
}

The only differences here are the removal of the length check on the userAssignedIdentities parameter, and the addition of the flatten function to combine the common identities with the user supplied ones.