Azure Functions + Storage with Managed Identity via Bicep

Today I needed to migrate an Azure Functions project that used to connect to Azure Storage with a normal connection string. Since that’s prohibited by policy in my new Azure subscription, we needed to change that.

This also meant we needed to move the Function App to a dedicated plan since Managed Identity is not supported with Consumption or Elastic Premium plans.

Here’s the full bicep file for Storage, App Service Plan, Function app and required roles. Note that we’re deploying a Windows app service plan with .NET 9 Isolated.

var location = 'germanywestcentral'
var resourceName = 'myapp'

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: 'st${resourceName}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
    allowSharedKeyAccess: false
  }
}

resource hostingPlan 'Microsoft.Web/serverfarms@2024-04-01' = {
  name: 'asp${resourceName}'
  location: location
  sku: {
    name: 'B1'
  }
}

resource functionApp 'Microsoft.Web/sites@2024-04-01' = {
  name: 'func${resourceName}'
  location: location
  kind: 'functionapp'
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    serverFarmId: hostingPlan.id
    siteConfig: {
      alwaysOn: true
      appSettings: [
        {
          name: 'AzureWebJobsStorage__accountname'
          value: storageAccount.name
        }
        {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~4'
        }
        {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: 'dotnet-isolated'
        }
      ]
      ftpsState: 'FtpsOnly'
      minTlsVersion: '1.2'
      netFrameworkVersion: 'v9.0'
    }
    httpsOnly: true
  }
}

// role assignents for accessing the storage

var blobOwnerRoleId = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'
resource roleDefinitionBlobContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
  name: blobOwnerRoleId
}

resource roleAssignmentBlobContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(blobOwnerRoleId, functionApp.name)
  scope: storageAccount
  properties: {
    roleDefinitionId: roleDefinitionBlobContributor.id
    principalId: functionApp.identity.principalId
  }
}

var storageContributorRoleId = '17d1049b-9a84-46fb-8f53-869881c3d3ab'
resource roleDefinitionStorageContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
  name: storageContributorRoleId
}

resource roleAssignmentStorageContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(storageContributorRoleId, functionApp.name)
  scope: storageAccount
  properties: {
    roleDefinitionId: roleDefinitionStorageContributor.id
    principalId: functionApp.identity.principalId
  }
}

// required for storage queue triggers
var storageQueueContributorRoleId = '974c5e8b-45b9-4653-ba55-5f855dd0fb88'
resource roleDefinitionStorageQueueContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
  name: storageQueueContributorRoleId
}

resource roleAssignmentStorageQueueContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(storageQueueContributorRoleId, functionApp.name)
  scope: storageAccount
  properties: {
    roleDefinitionId: roleDefinitionStorageQueueContributor.id
    principalId: functionApp.identity.principalId
  }
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.