Search code examples
azureazure-functionsazure-table-storageazure-managed-identity

Azure.Data.Tables method - how to change from using shared key + Uri to managed Id?


I have an Azure function (HTTP Trigger) that writes to a queue, but also to a storage table. It's been working fine but now I need to move everything over to use managed identities.

I was able to change the HTTP trigger to use a __serviceUri env variable to connect instead of using a connection string. But now I need to figure out how to update my logic that writes to the storage table.

Here's the relevant code.
Http Trigger

[FunctionName("Createwidget")]
    public async Task<IActionResult> Createwidget(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "toys/widgets")] HttpRequest req,
         [Queue("widgets"), StorageAccount("ExtStorageQueue")] ICollector<string> messageQueue,
        ILogger log)
    {           
        widgetResponse response = new widgetResponse();
        var content = await new StreamReader(req.Body).ReadToEndAsync();
        log.LogInformation($"Received following payload: {content}");

        var widgetRequest = JsonConvert.DeserializeObject<widget>(content);
        if (widgetRequest.name != null){     
                
                messageQueue.Add(JsonConvert.SerializeObject(widgetRequest));  
                // this is where I need to update the code              
                response = await storage.ProvisioningRequest(widgetRequest, req.HttpContext.Items["MS_AzureFunctionsRequestID"].ToString(), "enqueued");                
        } 
        else {
            response.status = "Error: Invalid Request";
            response.requestId=null;
        }
        return new OkObjectResult(JsonConvert.SerializeObject(response));  
    }

This is a snippet from storage.ProvisioningRequest()

GetStorageAccountConnectionData
var serviceClient = new TableServiceClient(
new Uri(connection.storageUri),
new TableSharedKeyCredential(connection.storageAccountName, connection.storageAccountKey));
var tableClient = serviceClient.GetTableClient(connection.tableName);
await tableClient.CreateIfNotExistsAsync();

This is how I create the connection:

    private void GetStorageAccountManagedIDConnection()
    {
        var azureWebJobsStorage = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
        String[] accountDetailsArray = azureWebJobsStorage.Split(";");
        String[] accountNameArray = accountDetailsArray[1].Split("=");
        connection.storageAccountName = accountNameArray[1];

        var storageAccount= Environment.GetEnvironmentVariable("ExtStorageQueue");
        String[] storageAccountDetailsArray = storageAccount.Split(";");
        String[] accountKeyDetailsArray = storageAccountDetailsArray[2].Split("AccountKey=");
        string storageKey = accountKeyDetailsArray[1];
        connection.tableName = Environment.GetEnvironmentVariable("squeueTable");
        connection.storageUri = $"https://{connection.storageAccountName}.table.core.windows.net/{connection.tableName}";
        connection.storageAccountKey = storageKey;
    }

Here's what my local.settings.json file looks like:

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=mystorageAccountName;EndpointSuffix=core.windows.net;AccountKey=supersecretKey/asdf+asdf==",
    "squeueTable": "provisionedWidgets",
    "ExtStorageQueue__serviceUri": "https://mystorageAccountName.queue.core.windows.net",
   
  },
  "ConnectionStrings": {}
}

is there a way to do this? I've been reading https://learn.microsoft.com/en-us/dotnet/api/azure.data.tables.tableclient?view=azure-dotnet#constructors and i don't see any constructors that allow me to use a managed id

Any help would be appreciated

EDIT 1

Here's the simplified method that creates the connection config data:

    private void GetStorageAccountConnectionData()
    {
        //TODO:  remove hardcoded values and extract from local.settings.json instead.  For testing purposes only to see how serivceUri works.
        connection.storageAccountName = "mystorageaccountname";
        connection.tableName = Environment.GetEnvironmentVariable("StorageTableName");
        connection.storageTableUri = $"https://{connection.storageAccountName}.table.core.windows.net/{connection.tableName}";
        connection.storageQueueUri = $"https://{connection.storageAccountName}.queue.core.windows.net/";
    }

and this is the logic that consumes it:

           GetStorageAccountConnectionData();
              var serviceClient = new TableServiceClient(new Uri(connection.storageTableUri), new DefaultAzureCredential());
            var tableClient = serviceClient.GetTableClient(connection.tableName);
            TableEntity origEntity = tableClient.GetEntity<TableEntity>(
                                ENQUEUED_PARTITION,
                                notification.requestId);

No compile errors but the storage table is not written to.
I'm currently trying to see about creating debug messages / logs since I can't get my local debug environment working

EDIT 2

I finally got my local env set up so that I can debug. So the logic that writes to my storage queue works but when I try to write to the storatge table, I can now see what the full error is. This is the error:

Failed to create storage record:Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.                                                                                                                                                                    ure.
RequestId:asdf-0002-asdf-6574-asdf
Time:2022-05-10T13:45:24.6319496Z
Status: 403 (Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.)
ErrorCode: AuthenticationFailed

Content:
{"odata.error":{"code":"AuthenticationFailed","message":{"lang":"en-US","value":"Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including 
is formed correctly including the signature.\nRequestId:9f775fd8-0002-002c-6574-648209000000\nTime:2022-05-10T13:45:24.6319496Z"}}}

Headers:
Server: Microsoft-HTTPAPI/2.0
x-ms-request-id: asdf-asdf-asdf-asdf
x-ms-error-code: REDACTED
Date: Tue, 10 May 2022 13:45:23 GMT
Content-Length: 299
Content-Type: application/json

Also just to be explicit, the same behavior happens when I test in the cloud too. So regardless of whether I call

 POST https://myfunctionapp.azurewebsites.net/widgets/workspaces

or

 POST http://localhost:7071/widgets/workspaces

it's the same behavior. The storage queue is updated but not the table.

As far as the managed id is concerned, it now has the following permissions:

enter image description here


Solution

  • You need add a reference to these packages:

    You function app will need Storage Account Contributor or Contributor role at the storage level (Not sure to be honest).

    Here is a snippet of how your code could looks like:

    var serviceClient = new TableServiceClient(
      new Uri(connection.storageUri), new DefaultAzureCredential());
    var tableClient = serviceClient.GetTableClient (connection.tableName);
    await tableClient.CreateIfNotExistsAsync();