Cosmos DB RBAC-Access with Managed Identities

The public preview of role-based access control (RBAC) for the Azure Cosmos DB Core (SQL) API was announced yesterday at Microsoft Ignite. This does not only allow us to authenticate our requests with an Azure Active Directory identity based on roles, but also allows to audit the identities which accessed your data.

In this blog post I walk you through a complete example on how you can use Azure Cosmos DB with RBAC and managed identity. We will

  • Create following Azure Resources
    • Cosmos DB Account
    • Log Analytics Account
    • Azure Function App
  • Create a Azure Cosmos DB role with specific access permissions
  • Assign the CosmosDB role to a managed identity
  • Write an Azure Function App to access CosmosDB with managed identity

Create and configure Azure Resources

Open https://portal.azure.com and create a new resource group „cosmosrbac„.

Create a new Azure Cosmos DB Account in that resource group. For this sample I had been using the new serverless option. Use the DataExplorer (under Settings) to create a new Database „demodb“ and a Collection „democol„. I am using „/p“ as a generic partition key. This is especially useful, if you store different kind of documents in your collection which are partitioned differently. Also create a document with the „New document“ button since our permissions will be read only later.

{
"id":"001",
"p":"Test",
"First":"Andreas"
}

Now create a new Log Analytics Workspace and a Azure Function App (serverless) in the resource group „cosmosrbac„. I named those „cosmosrbaclog“ and „cosmosrbacfunc

Now lets configure the Azure Cosmos DB account so that all Dataplane Requests are audited in our Log Analytics Workspace. To do that select your Azure Cosmos DB resource, click on „Diagnostic settings“ in the section „Monitoring„. Add a new diagnostic setting where you just select „DataPlaneRequests“ and your created Log Analytics Workspace. I named that setting „SecurityLog“.

Diagnostic settings for Azure Cosmos DB account

In the portal select your Azure App Function and click on „Identity“ in the section „Settings„. Select „System assigned“ and turn the Status to „On„. Write down the given Object ID which represents the Object ID of the Service Principal in Azure AD. Read more about the managed identity types in the documentation here.

System assigned Identity for Azure Function App

Configure CosmosDB RBAC role and assign to identity

To create a new CosmosDB RBAC role we need to define which permissions the role consists of. This can be done by defining a JSON file. We will be creating a new role „MyReadOnlyRole“ with following permissions:

  • Microsoft.DocumentDB/databaseAccounts/readMetadata
  • Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read
  • Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/executeQuery
  • Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/readChangeFeed

You can find all available Cosmos DB permissions here.

In the Azure Portal open the cloud shell. You can find the cloud shell in the upper bar on the right side. I am using PowerShell version! If you use bash instead you might need to slightly adopt the statements below. To create and assign the role you need the latest Azure Cosmos DB az extension which you can install with this command:

az extension add --name cosmosdb-preview

Then start the nano editor and create a new file „roledefinition.json

nano roledefinition.json

Copy and paste the following json document and save the document by pressing CTRL+Q and selecting Y:

{
    "RoleName": "MyReadOnlyRole",
    "Type": "CustomRole",
    "AssignableScopes": ["/"],
    "Permissions": [{
        "DataActions": [
            "Microsoft.DocumentDB/databaseAccounts/readMetadata",
            "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read",
            "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/executeQuery",
            "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/readChangeFeed"
        ]
    }]
}

Now create the defined role with the following statements. Make sure you replace the values of $resourceGroupName and $accountName with those you selected:

$resourceGroupName='cosmosrbac'
$accountName='cosmosrbac'
az cosmosdb sql role definition create --account-name $accountName --resource-group $resourceGroupName --body roledefinition.json

To later get a list of your defined roles you can issue this statement:

az cosmosdb sql role definition list --account-name $accountName --resource-group $resourceGroupName

Write down the value of „name“ of the new created role definition as we will need it in our next step.

Now we assign this new role to our previously created system managed identity. Replace the $readOnlyRoleDefinitionId value with the value of the role-name (see above) and replace the $principalId value with the Object ID we got earlier when we configured our Azure Function App:

$resourceGroupName='cosmosrbac'
$accountName='cosmosrbac'
$readOnlyRoleDefinitionId = 'name of role definition'
$principalId = 'object id of system managed identity'

az cosmosdb sql role assignment create --account-name $accountName --resource-group $resourceGroupName --scope "/" -p $principalId --role-definition-id $readOnlyRoleDefinitionId

We now can use this system managed identity from Azure Function App to access Cosmos DB with the permissions we defined for that role. To see all the assignments you made you execute this statement:

az cosmosdb sql role assignment list --account-name $accountName --resource-group $resourceGroupName

Access Azure CosmosDB with Azure Function App

Now start Visual Studio 2019 and create a new function app with a HTTP Trigger. Right click the new created project and select „Manage nuget packages…“. Make sure the checkbox „Include prerelease“ is checked! Add the following nuget packages to your solution:

  • Azure.Identity (Version 1.3.0)
  • Microsoft.Azure.Cosmos (Version 3.17.0-preview1)
Functionality is only in the preview of 3.17.0 but not in the final release!

Modify Function1.cs file as following. First replace the using statements with these:

using Azure.Identity;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks;

Add a person class which represents our documents in Azure Cosmos DB

public class Person
{
    public string Id { get; set; }
    public string p { get; set; }
    public string First { get; set; }
}

Inside the class „public static class Function1“ write following two function. One to read an item and another to write an item. Make sure that you replace <yourAccount> with the name of your Azure Cosmos DB account name:

[FunctionName("GetItem")]
public static async Task<IActionResult> GetItem(
  [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
   ILogger log)
{
  try
    {
      // Since we know that we have a managed identity we instatiate that directly
      // var tokenCredential = new DefaultAzureCredential();
      ManagedIdentityCredential tokenCredential = new ManagedIdentityCredential();
      CosmosClient client = new
       CosmosClient("https://<yourAccount>.documents.azure.com:443/", tokenCredential);
      Container container = client.GetContainer("demodb", "democol");
      ItemResponse<Person> res = await container.ReadItemAsync<Person>("001", new PartitionKey("Test"));
      return new OkObjectResult(JsonConvert.SerializeObject(res.Resource));
    }
    catch (Exception ex)
    {
      return new BadRequestObjectResult(ex.ToString());
    }
}
[FunctionName("CreateItem")]
public static async Task<IActionResult> CreateItem(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
  try
  {
    ManagedIdentityCredential tokenCredential = new ManagedIdentityCredential();
    CosmosClient client = 
      new CosmosClient("https://<yourAccount>.documents.azure.com:443/", tokenCredential);
    Container container = client.GetContainer("demodb", "democol");
    ItemResponse<Person> res = await container.CreateItemAsync<Person>(
	new Person() 
	{ Id = "002", 
	  p = "Test", 
          First = "Sonja" }, 
	new PartitionKey("Test"));
    return new OkObjectResult(JsonConvert.SerializeObject(res.Resource));
  }
  catch (Exception ex)
  {
    return new BadRequestObjectResult(ex.ToString());
  }
}

Compile the application and publish it to the Azure function app (Right click project – Publish). Open the Azure portal again and navigate to your Azure function app. Under the section „Function“ select „Functions„. Select each function and aquirce a function URL with the „Get function Url“ buttom in the upper bar.

Azure Functions deployed

Now use a tool like Postman to try to execute your functions. While the GetItem-Function will return a result

GetItem Function returning data from Azure Cosmos DB

the CreateItem-Function will return an RBAC access error.

CreateItem Function returns an error claiming that the principal does not have the required RBAC permissions

Analyzing Loganalytics Azure CosmosDB Dataplane

Navigate now to your Azure Log Analytics Workspace in the Azure Portal. Under the section „General“ select „Logs„. Issuing the following statement will show the failed access to the document:

AzureDiagnostics 
| where ResourceProvider == "MICROSOFT.DOCUMENTDB"
  and Category == "DataPlaneRequests"
  and ResourceGroup == "COSMOSRBAC"
  and requestResourceType_s == "Document"
| where OperationName == 'Create'
| summarize by statusCode_s, OperationName, aadPrincipalId_g, aadAppliedRoleAssignmentId_g

Replacing the Operation Name with „Read“ on the other hand will show all 200 success code for that given principal id.

AzureDiagnostics 
| where ResourceProvider == "MICROSOFT.DOCUMENTDB"
  and Category == "DataPlaneRequests"
  and ResourceGroup == "COSMOSRBAC"
  and requestResourceType_s == "Document"
| where OperationName == 'Read'
| summarize by statusCode_s, OperationName, aadPrincipalId_g, aadAppliedRoleAssignmentId_g

I hope you enjoyed our tour!

Kind regards
AndiP

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google Foto

Du kommentierst mit Deinem Google-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s