【Azure】BicepでFunctionsにComosDBの組み込みロールを付与する

こんにちは、サイオステクノロジーの遠藤です。

今回はAzureのBicepを利用してCosmosDBとFunctionsのリソースを作成し、FunctionsのEntra IDに対してCosmosDBの操作が行えるロールを付与する方法について確認します。

Bicepにまだ慣れていない自分としてはいくつかハマったポイントがあったのでその部分に焦点を当てて解説していければと思います。

Azureのコントロール プレーンとデータ プレーン

まず今回の本題に入る前にコントロール プレーンとデータ プレーンについて確認します。

Azureの操作はコントロール プレーンデータ プレーンの2つのカテゴリに分類することが出来ます。

まずコントロールプレーンについてですが、サブスクリプション内のリソースを管理するために使用します。 そしてデータ プレーンは、リソースの種類のインスタンスによって公開される機能を使用するために使用します。

Azureが掲載している例だと、

  • コントロール プレーンを使用して仮想マシンを作成します。 仮想マシンを作成した後は、リモート デスクトップ プロトコル (RDP) などのデータ プレーン操作を通じて、仮想マシンと対話します。
  • コントロール プレーンを使用してストレージ アカウントを作成します。 ストレージ アカウント内のデータの読み取りと書き込みを行うには、データ プレーンを使用します。
  • コントロール プレーンを使って Azure Cosmos DB データベースを作成します。 Azure Cosmos DB データベースのデータに対してクエリを実行するには、データ プレーンを使います。

といった形で、リソースの作成といった操作ではコントロールプレーンが必要になり、実際に作成したリソースに対してデータ更新をかけるといった操作を行うにはデータプレーンのロールが必要になります。

今回は、FunctionsのアプリケーションからCosmosDBに対してデータの書き込みを行うといったシチュエーションを考えているため、Functionsに対してCosmosDBの操作を行えるデータプレーンのロールを持たせるといったことを行おうとしています。

ここからが私が少しハマったポイントなのですが、コントロールプレーンはAzureポータル上のRBACで付与することが可能なのですが、少なくとも私が試した限りはCosmosDBのデータプレーンの権限をAzureポータル上から付与したり確認が出来なさそうといったところです。

Microsoft Entra ID を使用して Azure Cosmos DB アカウントのロールベースのアクセス制御を構成する の公式ドキュメントでも

Q. Azure portal からロールの定義とロールの割り当てを管理することはできますか。

A. ロール管理の Azure portal サポートはまだ利用できません。

となっているので、2025/01/30現在は出来なさそうです。

今回付与するロール

CosmosDBで使用できる組み込みロールには以下の2つのロールが用意されています。

ID名前含まれるアクション
00000000-0000-0000-0000-000000000001Cosmos DB 組み込みデータ リーダーMicrosoft.DocumentDB/databaseAccounts/readMetadataMicrosoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/readMicrosoft.DocumentDB/databaseAccounts/sqlDatabases/containers/executeQueryMicrosoft.DocumentDB/databaseAccounts/sqlDatabases/containers/readChangeFeed
00000000-0000-0000-0000-000000000002Cosmos DB 組み込みデータ共同作成者Microsoft.DocumentDB/databaseAccounts/readMetadataMicrosoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*

データベースの書き込みを行うためには2つ目の「Cosmos DB 組み込みデータ共同作成者」のロールが必要となります。

Bicepを作成する

今回は以下のような形のディレクトリ階層でbicepコードを作成しました。

bicep-sample
├── modules
│   └── app
│       └── func.bicep
│   └── database
│       └── cosmos.bicep
└── main.bicep

まずCosmosDBをデプロイするためのモジュールです。

modules/database/cosmos.bicep

param location string
param accountName string
param databaseName string
param containerName string

resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' = {
  name: accountName
  location: location
  properties: {
    databaseAccountOfferType: 'Standard'
    locations: [
      {
        locationName: location
        failoverPriority: 0
        isZoneRedundant: false
      }
    ]
    capabilities: [
      {
        name: 'EnableServerless'
      }
    ]
  }
}

resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-11-15' = {
  name: databaseName
  parent: cosmosDbAccount
  properties: {
    resource: {
      id: databaseName
    }
  }
}

resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-11-15' = {
  name: containerName
  parent: database
  properties: {
    resource: {
      id: containerName
      partitionKey: {
        paths: [ 
          '/user_id'
        ]
        kind: 'Hash'
      }
    }
  }
}

output accountName string = cosmosDbAccount.name

次にfunctions用のモジュールですね。こちらはFunctionsをデプロイするクイックスタートをベースに作成しました。

cosmosRoleDefinitionIds 以下からロールの付与処理を行っております。ロールを付与するにはMicrosoft.DocumentDB databaseAccounts/sqlRoleAssignments を利用することで付与することが出来ます。このあたりもコントロールプレーンとは書き方が違い、最初は書き方がわからなかったりとハマりやすいポイントだと感じました。

modules/app/func.bicep

param location string

@description('The name of the function app that you wish to create.')
param appName string = 'fnapp${uniqueString(resourceGroup().id)}'

@description('Storage Account type')
@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_RAGRS'
])
param storageAccountType string = 'Standard_LRS'

param cosmosDBAccountName string

param runtime string = 'node'
var functionAppName = appName
var hostingPlanName = appName
var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions'
var functionWorkerRuntime = runtime

resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: storageAccountType
  }
  kind: 'Storage'
  properties: {
    supportsHttpsTrafficOnly: true
    defaultToOAuthAuthentication: true
  }
}

resource hostingPlan 'Microsoft.Web/serverfarms@2021-03-01' = {
  name: hostingPlanName
  location: location
  sku: {
    name: 'Y1'
    tier: 'Dynamic'
  }
  properties: {}
}

resource func 'Microsoft.Web/sites@2021-03-01' = {
  name: functionAppName
  location: location
  kind: 'functionapp'
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    serverFarmId: hostingPlan.id
    siteConfig: {
      appSettings: [
        {
          name: 'AzureWebJobsStorage'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
        }
        {
          name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
        }
        {
          name: 'WEBSITE_CONTENTSHARE'
          value: toLower(functionAppName)
        }
        {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~4'
        }
        {
          name: 'WEBSITE_NODE_DEFAULT_VERSION'
          value: '~14'
        }
        {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: functionWorkerRuntime
        }
      ]
      ftpsState: 'FtpsOnly'
      minTlsVersion: '1.2'
    }
    httpsOnly: true
  }
}

var cosmosRoleDefinitionIds = [
  '00000000-0000-0000-0000-000000000002' 
]

var cosmosRoleAssignmentsToCreate =  [for cosmosRoleDefinitionId in cosmosRoleDefinitionIds:{
  name: guid(func.id, resourceGroup().id, cosmosRoleDefinitionId)
  roleDefinitionId: cosmosRoleDefinitionId
}]

resource cosmosDBAccount 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = {
  name: cosmosDBAccountName
}

resource cosmosDBRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-04-15' = [for cosmosRoleAssignmentToCreate in cosmosRoleAssignmentsToCreate: {
  name: cosmosRoleAssignmentToCreate.name
  parent: cosmosDBAccount
  properties:{
    principalId: func.identity.principalId
    roleDefinitionId: '/${subscription().id}/resourceGroups/${resourceGroup().name}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosDBAccount.name}/sqlRoleDefinitions/${cosmosRoleAssignmentToCreate.roleDefinitionId}'
    scope: cosmosDBAccount.id
  }
}]



最後にmain.bicepですね。上記2つのモジュールを呼び出す処理を入れます。

param location string = resourceGroup().location
param accountName string
param databaseName string
param containerName string

module cosmosdb 'modules/database/cosmos.bicep' = {
  name: 'cosmosdbModule'
  params: {
    location: location
    accountName: accountName
    databaseName: databaseName
    containerName: containerName
  }
}

module api 'modules/app/func.bicep' ={
  name: 'apiFunc'
  params: {
    location: location
    cosmosDBAccountName: cosmosdb.outputs.accountName
  }
}


権限が付与できているかの確認方法

では実際にbicepを走らせてみて、デプロイされたFunctionsに対して正しい権限が付与されているかを確認しましょう。

ここも個人的なハマりポイントで、上のほうでも書いていますが私調べではCosmosDBのデータプレーンの権限をAzureポータル上から確認することが出来ませんでした。

以下のazコマンドを実行することでCosmosDBアカウントに対してどのマネージドIDにどのデータプレーンのロールが付与されているかの確認をすることができます。

 az cosmosdb sql role assignment list --account-name <cosmosdbアカウント名>--resource-group <リソースグループ名>

まとめ

今回はBicepを利用してCosmosDBとFunctionsのリソースを作成し、FunctionsのEntraIDに対してCosmosDBの操作が行えるロールを付与する方法を確認しました。ロールを付与するための書き方がデータプレーンのロール用にしたり、Azureポータル上からは権限の付与が確認ができないなどハマりやすいポイントが多い部分だと思いますので同じく躓いた方の参考になればと思います!

Bicep自体は1コマンドでインフラ一式デプロイを行うことができ、非常に便利に感じているのでこれからも知見をためていきたいと思います。

ではまた~

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です