Coder Perfect

How can I archive an Azure IoT Hub storage account in Bicep?

Problem

I’m attempting to deploy an IoT Hub and a storage account using Azure Resource Manager and bicep. IoT Hub includes the ability to archive all messages by storing them in a storage account. The IoT Hub should use a User-assigned Managed Identity to access the storage account.

All of this would be deployed in a single ARM deployment written in bicep. The issue is putting up the archive custom route and installing the IoT Hub with a User-assigned Identity. I’m getting the following error:

{
    "code": "DeploymentFailed",
    "message": "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.",
    "details": [
        {
            "code": "400140",
            "message": "endpointName:messageArchive, exceptionMessage:Invalid operation: Managed identity is not enabled for IotHub ... errorcode: IH400140."
        }
    ]
}

This is how my biceps file looks.

resource messageArchive 'Microsoft.Storage/storageAccounts@2021-04-01' = {
  name: 'messagearchive4631'
  location: resourceGroup().location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_GRS'
  }
  properties: {
    accessTier: 'Hot'
    supportsHttpsTrafficOnly: true
  }
}

resource messageArchiveBlobService 'Microsoft.Storage/storageAccounts/blobServices@2021-04-01' = {
  name: 'default'
  parent: messageArchive
  resource messageArchiveContainer 'containers@2021-02-01' = {
    name: 'iot-test-4631-container'
    properties: {
      publicAccess: 'None'
    }
  }
}

resource iotIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
  name: 'iot-test-access-archive-4631'  
  location: resourceGroup().location
}

resource iotAccesToStorage 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = {
  name: guid(extensionResourceId(messageArchive.id, messageArchive.type, 'iot-test-access-archive-4631'))
  scope: messageArchive
  properties: {
    roleDefinitionId: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe'
    principalId: iotIdentity.properties.principalId
    description: 'Allow acces for IoT Hub'
  }
}

resource iothub 'Microsoft.Devices/IotHubs@2021-03-31' = {
  name: 'iot-test-4631'
  location: resourceGroup().location
  sku: {
    name: 'B1'
    capacity: 1
  }
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities:{
      '${iotIdentity.id}': {}
    }
  }
  dependsOn:[
    iotAccesToStorage
  ]
  properties: {
    features: 'None'
    eventHubEndpoints: {
      events: {
        retentionTimeInDays: 1
        partitionCount: 4
      }
    }
    routing: {
      endpoints: {
        storageContainers: [
          {
            name: 'messageArchive'
            endpointUri: 'https://messagearchive4631.blob.core.windows.net/'
            containerName: 'iot-test-4631-container'
            batchFrequencyInSeconds: 100
            maxChunkSizeInBytes: 104857600
            encoding: 'Avro'
            fileNameFormat: '{iothub}/{YYYY}/{MM}/{DD}/{HH}/{mm}_{partition}.avro'
            authenticationType: 'identityBased'
          }
        ]
      }
      routes: [
        {
          name: 'EventHub'
          source: 'DeviceMessages'
          endpointNames: [
            'events'
          ]
          isEnabled: true
        }
        {
          name: 'messageArchiveRoute'
          source: 'DeviceMessages'
          endpointNames: [
            'messageArchive'
          ]
          isEnabled: true
        }
      ]
      fallbackRoute: {
        source: 'DeviceMessages'
        endpointNames: [
          'events'
        ]
        isEnabled: true
      }
    }
  }
}

In IoT Hub, I tried removing the message routing block.

endpoints: {
  storageContainers: [
    {
      name: 'messageArchive'
      endpointUri: 'https://messagearchive4631.blob.core.windows.net/'
      containerName: 'iot-test-4631-container'
      batchFrequencyInSeconds: 100
      maxChunkSizeInBytes: 104857600
      encoding: 'Avro'
      fileNameFormat: '{iothub}/{YYYY}/{MM}/{DD}/{HH}/{mm}_{partition}.avro'
      authenticationType: 'identityBased'
    }
  ]
}

and only use it once This deployment works. If I then include the message routing block and deploy it again, then it works as expected.

Is this something that can be done in a single deployment?

Asked by Alexander S.

Solution #1

I came up with the solution on my own. I’m using a user-assigned Managed Identity and therfore I was missing this in IoT Hub endpoint storage container configuration:

authenticationType: 'identityBased'
identity: {
   userAssignedIdentity: iotIdentity.id
}

This is the complete IoT Hub endpoint configuration.

endpoints: {
  storageContainers: [
    {
      name: 'RawDataStore'
      endpointUri: 'https://${nameRawDataStore}.blob.${environment().suffixes.storage}/'
      containerName: nameIotHub
      batchFrequencyInSeconds: 100
      maxChunkSizeInBytes: 104857600
      encoding: 'Avro'
      fileNameFormat: '{iothub}/{YYYY}/{MM}/{DD}/{HH}/{mm}_{partition}.avro'
      authenticationType: 'identityBased'
      identity: {
        userAssignedIdentity: iotIdentity.id
      }
    }
  ]
}

Answered by Alexander S.

Post is based on https://stackoverflow.com/questions/68846449/how-to-deploy-an-azure-iot-hub-storage-account-archiving-in-bicep