Connect Function App to CosmosDB with Managed Identity
Asked Answered
S

2

4

I'm trying to write a function in a Function App that manipulates data in a CosmosDB. I get it working if I drop the read-write key in the environment variables. To make it more robust I wanted it to work as a managed identity app. The app has the role 'DocumentDB Account Contributor' on the Cosmos DB.

However, the CosmosClient constructor doesn't accept a Credential and needs the read-write key. I've been chasing down the rabbit hole of azure.mgmt.cosmosdb.operations where there is a DatabaseAccountsOperations class with a list_keys() method. I can't find a neat way to access that function though. If I try to create that object (which requires poaching the config, serializer and deserializer from my dbmgmt object) it still requires the resourceGroupName and accountName.

I can't help but think that I've taken a wrong turn somewhere because this has to be possible in a more straightforward manner. Especially given that the JavaScript SDK references a more logical class CosmosDBManagementClient in line with the SubscriptionClient. However, I can't find that class anywhere on the python side. Any pointers?

from azure.identity import DefaultAzureCredential
from azure.cosmos import CosmosClient
from azure.mgmt.resource import SubscriptionClient
from azure.mgmt.cosmosdb import CosmosDB
from .cred_wrapper import CredentialWrapper

def main(req: func.HttpRequest) -> func.HttpResponse:
    request_body = req.get_body()
    # credential = DefaultAzureCredential()
    # https://gist.github.com/lmazuel/cc683d82ea1d7b40208de7c9fc8de59d
    credential = CredentialWrapper()  

    uri = os.environ.get('cosmos-db-uri')
    # db = CosmosClient(url=uri, credential=credential)  # Doesn't work, wants a credential that is a RW/R key
    # Does work if I replace it with my primary / secondary key but the goal is to remove dependence on that.

    subscription_client = SubscriptionClient(credential)
    subscription = next(subscription_client.subscriptions.list())

    dbmgmt = CosmosDB(credential, subscription.subscription_id)  # This doesn't accept the DB URI??
    operations = list(dbmgmt.operations.list())  # I see the list_keys() Operation there...

EDIT

A helpful soul provided a response here but removed it before I could even react or accept it as the answer. They pointed out that there is an equivalent python SDK and that from azure.mgmt.cosmosdb import CosmosDBManagementClient would do the trick.

From there, I was on my own as that resulted in

ImportError: cannot import name 'CosmosDBManagementClient' from 'azure.mgmt.cosmosdb'

I believe the root of the problem lies in an incompatibility of the package azure-mgmt. After removing azure-mgmt from my requirements.txt and only loading the cosmos and identiy related packages, the import error was resolved.

This solved 90% of the problem.

dbmgmt  = CosmosDBManagementClient(credential, subscription.subscription_id, c_uri)
print(dbmgmt.database_accounts.list_keys())
TypeError: list_keys() missing 2 required positional arguments: 'resource_group_name' and 'account_name'

Does one really need to collect each of these parameters? Compared to the example that reads a secret from a Vault it seems so convoluted.

Salters answered 13/5, 2021 at 1:16 Comment(5)
That helpful soul was Steve Zhao (God bless him :)). He did include code for CosmosDBManagementClient however that client is for performing management operations on your Cosmos DB account. What you're looking for is to perform data operations using managed identity. I pointed this out to him and that's why he deleted his answer.Shortterm
Also, your list_keys() method will only work as long as there is no management lock on your Cosmos DB account.Shortterm
Yes, both resource_group_name and account_name parameter is required in list_keys method. learn.microsoft.com/en-us/python/api/azure-mgmt-cosmosdb/…Retail
Thank you both for your helpful comments. After more digging it seems that managed identity access from a function app to cosmos is not yet support as of May 2021. MS Forum. From a SO etiquette, do I close my question with that answer or what is the way to go?Salters
@Salters You can post an answer to end this question.Retail
S
1

For other unfortunate ones looking to access CosmosDB with Managed Identity, it seems that this is, as of May 2021, not yet possible.

Source: Discussion on Github

Salters answered 19/5, 2021 at 1:46 Comment(0)
T
0

Update 12/05/2021 - I came here finding a solution for this with Javascript/Typescript. So leaving the answer here for others. I think that a similar approach could work for Python.

You can use RBAC for data plane operations with Managed Identities. Finding the documentation was difficult.

RBAC for Cosmos DB data plane operations with Managed Identities

Important - If you get the error Request blocked by Auth mydb : Request is blocked because principal [xxxxxx-6fad-44e4-98bc-2d423a88b65f] does not have required RBAC permissions to perform action Microsoft.DocumentDB/databaseAccounts/readMetadata on resource [/]. Don't use the Portal to assign roles, use the Azure CLI for CosmosDB. How to - creating a role assignment for a user/system MSI/user MSI is done using the Azure CosmosDB CLI

# Find the role ID:

resourceGroupName='<myResourceGroup>'
accountName='<myCosmosAccount>'
az cosmosdb sql role definition list --account-name $accountName --resource-group $resourceGroupName

# Assign to the MSI or user managed MSI:

readOnlyRoleDefinitionId = '<roleDefinitionId>' # as fetched above
principalId = '<aadPrincipalId>'
az cosmosdb sql role assignment create --account-name $accountName --resource-group $resourceGroupName --scope "/" --principal-id $principalId --role-definition-id $readOnlyRoleDefinitionId

Once this step is done, the code for connecting is very easy. Use the @azure/identity package's Default Credential. This works in Azure Function App with managed identity and on your laptop with VS code or with az login.

Docs for @azure/identity sdk

Examples of authentication with @azure/identity to get the credential object

Azure identity default credential

import { CosmosClient } from "@azure/cosmos";
import { DefaultAzureCredential, ManagedIdentityCredential, ChainedTokenCredential } from "@azure/identity";

const defaultCredentials = new DefaultAzureCredential();
const managedCredentials = new ManagedIdentityCredential();
const aadCredentials = new ChainedTokenCredential(managedCredentials, defaultCredentials);
        
client = new CosmosClient({
                endpoint: "https://mydb.documents.azure.com:443/",
                aadCredentials
            });
Tender answered 10/1, 2022 at 20:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.