RBAC with Azure Cosmos DB for NoSQL

Stop using access keys to access containers and items in your Cosmos DB account. Use RBAC instead! In this blog post, I’ll show you how to configure RBAC for better security and access control.

Role-Based Access Control

Key-based authentication in Cosmos DB for NoSQL has been a common and default way to access your account. Honestly, it’s the easiest way to use the account, and all tools support this method. The problem with keys is that they grant full access to your account. Any application using access keys can read and write data to all your databases and containers. This isn’t necessarily a problem if the keys are kept secure and you have only few applications. However, if the keys are compromised, a malicious user will have full control of your Cosmos DB account. This means they could delete all your containers and databases instead of just reading and writing data.

Fortunately, there’s a better way: Role-Based Access Control (RBAC) with Microsoft Entra ID. With RBAC, you can define roles and assign this roles to identities to grant access to your data. Unlike access keys, RBAC allows for more fine-grained access control. You can grant read or write permissions to specific applications or users at the database and container level. This follows the principle of least privilege (PoLP), a key concept in information security.

Disable key-based authentication

First thing what you need to do is to disable key-based authentication in your Cosmos DB account.

To disable access keys, you just need to set disableLocalAuth to true in Bicep module.

resource account 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' = {
  name: name
  location: location
  kind: 'GlobalDocumentDB'
  properties: {
    disableLocalAuth: true
    ...
  }
}

Built-in roles

Cosmos DB have two built-in data-plane roles that you can use out of the box. You can use this roles to give read or write access to all databases in account. The tricky here is that you don’t see these roles anywhere in Azure portal so you can’t assign role to application or user. So only way to assign role is to use Bicep or Azure CLI.

You can use Azure CLI to list all possible roles that are available in Cosmos DB. This will show the built in roles and custom roles that you might have defined.

az cosmosdb sql role definition list \
    --account-name cosmos-myapp-dev \
    --resource-group rg-myapp-dev 

The command will returns following JSON:

[
  {
    "assignableScopes": [
      "/subscriptions/0c135d87-xxxx-xxxx-xxxx-9ed1b4db58d1/resourceGroups/rg-myapp-dev /providers/Microsoft.DocumentDB/databaseAccounts/cosmos-myapp-dev"
    ],
    "id": "/subscriptions/0c135d87-xxxx-xxxx-xxxx-9ed1b4db58d1/resourceGroups/rg-myapp-dev /providers/Microsoft.DocumentDB/databaseAccounts/cosmos-myapp-dev/sqlRoleDefinitions/00000000-0000-0000-0000-000000000001",
    "name": "00000000-0000-0000-0000-000000000001",
    "permissions": [
      {
        "dataActions": [
          "Microsoft.DocumentDB/databaseAccounts/readMetadata",
          "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/executeQuery",
          "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/readChangeFeed",
          "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read"
        ],
        "notDataActions": []
      }
    ],
    "resourceGroup": "rg-myapp-dev",
    "roleName": "Cosmos DB Built-in Data Reader",
    "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions",
    "typePropertiesType": "BuiltInRole"
  },
  {
    "assignableScopes": [
      "/subscriptions/0c135d87-xxxx-xxxx-xxxx-9ed1b4db58d1/resourceGroups/rg-myapp-dev /providers/Microsoft.DocumentDB/databaseAccounts/cosmos-myapp-dev"
    ],
    "id": "/subscriptions/0c135d87-xxxx-xxxx-xxxx-9ed1b4db58d1/resourceGroups/rg-myapp-dev /providers/Microsoft.DocumentDB/databaseAccounts/cosmos-myapp-dev/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002",
    "name": "00000000-0000-0000-0000-000000000002",
    "permissions": [
      {
        "dataActions": [
          "Microsoft.DocumentDB/databaseAccounts/readMetadata",
          "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*",
          "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*"
        ],
        "notDataActions": []
      }
    ],
    "resourceGroup": "rg-myapp-dev",
    "roleName": "Cosmos DB Built-in Data Contributor",
    "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions",
    "typePropertiesType": "BuiltInRole"
  }
]

Let’s explain the JSON output a bit more. The output shows that there are two roles: Cosmos DB Built-in Data Reader and Cosmos DB Built-in Data Contributor. The permission property defines the actions allowed for each role. For example, the Reader role has permissions like containers/executeQuery, containers/readChangeFeed, and containers/items/read, which allow reading data from all containers in your account. The Contributor role, on the other hand, has containers/* and containers/items/*, which provide write access to the data. A full list of possible data actions can be found here.

Using these role names and IDs, we can assign built-in roles to managed identities using a Bicep module. The example below demonstrates how to use these built-in roles to grant access using the data-role-assignment Bicep module.

module cosmosdbDataContributorRoleAssignment '../../modules/cosmos-db/data-role-assignment.bicep' = {
  name: 'cosmosdbDataContributorRoleAssignment'
  params: {
    principalIds: [deployer().objectId]
    accountName: cosmosdb.outputs.name
    builtInRoleName: 'Cosmos DB Built-in Data Contributor'
  }
  scope: rg
}

Custom role definition

For more fine-grained access control, you can create custom roles to define specific permissions.

The assignableScopes parameter determines the scope at which the role provides access. In the following example, the Posts Container Reader custom role grants read access exclusively to the posts container. This example uses the custom-role-definition Bicep module.

module postsContainerReaderCustomRoleDefinition '../../modules/cosmos-db/custom-role-definition.bicep' = {
  name: 'postsContainerReaderCustomRoleDefinition'
  params: {
    roleName: 'Posts Container Reader'
    accountId: cosmosdb.outputs.id
    assignableScopes: [
      '${cosmosdb.outputs.id}/dbs/blog/colls/posts'
    ]
    dataActions: [
      'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/executeQuery'
      'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/readChangeFeed'
      'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read'
    ]
  }
  scope: rg
}

Once the role is defined, you can assign it to managed identity using the data-role-assignment Bicep module:

module postsContainerReaderRoleAssignment '../../modules/cosmos-db/data-role-assignment.bicep' = {
  name: 'postsContainerReaderRoleAssignment'
  params: {
    principalIds: [deployer().objectId]
    accountName: cosmosdb.outputs.name
    customRoleDefinitionId: postsContainerReaderCustomRoleDefinition.outputs.id
    subScope: '/dbs/blog/colls/posts'
  }
  scope: rg
}

Summary

Access keys provide full control over your Cosmos DB account, making them a security risk if compromised. Instead of using keys, you should implement Role-Based Access Control (RBAC) with Microsoft Entra ID to enforce fine-grained permissions.

By using RBAC, you enhance security, follow the principle of least privilege (PoLP), and gain better control over who can access your Cosmos DB data.

You can find the full code example in my GitHub repo: http://github.com/matonen/blog-examples.