How to run Azure Function locally with System Assigned Id?

181 Views Asked by At

I am trying to use System-Assigned managed identity to access Azure Cosmos DB data as described in the Microsoft Document: https://learn.microsoft.com/en-us/azure/cosmos-db/managed-identity-based-authentication

I have created System-Assigned Id in my Azure Function and assigned "Cosmos DB Built-in Data Reader" role assignment to the Cosmos DB (basically gave read permissions from that function).

I have then followed the code in the "(Optional) Run the function locally" section to try and run my function locally. However it fails with the following message:

Request blocked by Auth <my cosmos account> : Request is blocked because principal [89c3cdb1-438c-4e04-a16d-...........] does not have required RBAC permissions to perform action [Microsoft.DocumentDB/databaseAccounts/readMetadata] on resource [/]

The Principal Id returned in this message is different from the Principal Id of my azure function.

Obviously this happens because my local account has a different Principal Id from the System-Assigned principle id in my Function on Azure.

However I couldn't find anywhere on how to set up the same System-Assigned principle id locally.

Does anyone have an idea?

1

There are 1 best solutions below

0
Tanuki On

I have tried all the suggestions and unfortunately I was not able to run Azure Function locally with System Assigned Id.

The closes workaround I could find was to:

  • For deployment to Azure use System-Assigned identity
  • For running Function locally - just grant relevant permission to my local account principal Id that was failing authentication before (89c3cdb1-438c-4e04-a16d-...........)

So the full steps to make Azure Function run both in Azure and locally with minimum hassle are:

  1. Turn on System assigned identity in the Function app and copy Principal Id
  2. Grant permissions to System assigned identity with an Azure cli command like this:
az cosmosdb sql role assignment create \
    --resource-group $resourceGroupName \
    --account-name $cosmosName \
    --role-definition-name "Cosmos DB Built-in Data Contributor" \
    --principal-id <Principal Id from Step 1> \
    --scope $scope

Command is taken from Microsoft Document: https://learn.microsoft.com/en-us/azure/cosmos-db/managed-identity-based-authentication

  1. Run the function locally with DefaultAzureCredential. Something like this:

     var cosmosDbEndpoint = Environment.GetEnvironmentVariable("CosmosDbEndpoint", EnvironmentVariableTarget.Process);
     var cosmosClient = new CosmosClient(accountEndpoint: cosmosDbEndpoint, new DefaultAzureCredential());
    
  2. Copy the blocked principal Id of your local account in the error message Request is blocked because principal [<blocked principal Id>]

  3. Grant permissions to your local account with the same command as in step 2, just with a principal Id from step 5:

az cosmosdb sql role assignment create \
    --resource-group $resourceGroupName \
    --account-name $cosmosName \
    --role-definition-name "Cosmos DB Built-in Data Contributor" \
    --principal-id <Principal Id from Step 5> \
    --scope $scope

Of course you can set different definitions for those commands, but the principle is the same.


One more thing I would add is that running Function locally takes forever due to all the credential types it has to check.

So in order to minimise execution time its good to exclude all the unnecessary credential types. Lets say locally your account authenticates from Azure cli credentials, then you would create credentials like that:

var cosmosDbEndpoint = Environment.GetEnvironmentVariable("CosmosDbEndpoint", EnvironmentVariableTarget.Process);

var localDebugging = Environment.GetEnvironmentVariable("LocalDebugging");
var credentials = localDebugging is null or not "true" ?
    new DefaultAzureCredential() : // running on Azure 
    new DefaultAzureCredential(new DefaultAzureCredentialOptions() // running locally
    {
       ExcludeManagedIdentityCredential = true,
       // ExcludeAzureCliCredential = true,
       ExcludeAzureDeveloperCliCredential = true,
       ExcludeAzurePowerShellCredential = true,
       ExcludeEnvironmentCredential = true,

       ExcludeInteractiveBrowserCredential = true,
       ExcludeSharedTokenCacheCredential = true,
       ExcludeVisualStudioCodeCredential = true,
       ExcludeVisualStudioCredential = true,
       ExcludeWorkloadIdentityCredential = true,
    });

services.AddSingleton<CosmosClient>(sp => new CosmosClient(accountEndpoint: cosmosDbEndpoint, credentials));

Hope it helps somebody struggling with the same.