1

I have an Azure function that looks like this:

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

        var workspaceRequest = JsonConvert.DeserializeObject<Workspace>(content);
        if (workspaceRequest.name != null){     
                messageQueue.Add(JsonConvert.SerializeObject(workspaceRequest));                                                 
        } 
        else {
            response.status = "Error: Invalid Request";
            response.requestId=null;
        }
        return new OkObjectResult(JsonConvert.SerializeObject(response));  
    }

Everythings works well - the connection string is defined in my local.settings.json file like this:

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "WidgetStorageQueue": 

"DefaultEndpointsProtocol=https;AccountName=accountname;AccountKey=asdf+asdf+AStRrziLg==" } }

But now i've created a managed Identity and it has been assigned "Contributor" role on all resources inside the resource group. So I need to refactor this code to no longer use the connection string in local.settings / environment variables. But to use the managed id. Can you point me to an article or video that would put me on the right path? I actually prefer not to Azure key vault if possible.

Thanks.

EDIT 1

I've added the 2 packages referenced in the answer. This is what I have in my csproj file:

  <ItemGroup>
    <PackageReference Include="Azure.Data.Tables" Version="12.4.0" />
    <PackageReference Include="Azure.Storage.Queues" Version="12.10.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
    <PackageReference Include="Microsoft.Azure.Webjobs.Extensions.ServiceBus" Version="5.2.0" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage.Queues" Version="5.0.1" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </None>
  </ItemGroup>

And this is what my local.settings.json file looks like:

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "dotnet","WidgetStorageQueue__queueServiceUri":"https://mystorageaccountname.queue.core.windows.net"
  },
  "ConnectionStrings": {}
}

But I'm getting an error :

2022-05-05T19:30:00.774Z] Executed 'CreateWidgetWorkspace' (Failed, Id=asdf-a22b-asdf-asdf-asdf, Duration=6356ms)
[2022-05-05T19:30:00.777Z] System.Private.CoreLib: Exception while executing function: CreateWidgetWorkspace. Azure.Storage.Queues: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:asdf-8003-asdf-asdf-asdf
Time:2022-05-05T19:30:00.7494033Z
[2022-05-05T19:30:00.781Z] Status: 403 (Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.)
[2022-05-05T19:30:00.782Z] ErrorCode: AuthenticationFailed
[2022-05-05T19:30:00.784Z] 
[2022-05-05T19:30:00.785Z] Additional Information:
[2022-05-05T19:30:00.788Z] AuthenticationErrorDetail: Issuer validation failed. Issuer did not match.
[2022-05-05T19:30:00.790Z] 
[2022-05-05T19:30:00.791Z] Content:
[2022-05-05T19:30:00.793Z] <?xml version="1.0" encoding="utf-8"?><Error><Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:asdf-8003-asdf-asdf-asdf
Time:2022-05-05T19:30:00.7494033Z</Message><AuthenticationErrorDetail>Issuer validation failed. Issuer did not match.</AuthenticationErrorDetail></Error>      
[2022-05-05T19:30:00.795Z] 
[2022-05-05T19:30:00.796Z] Headers:
[2022-05-05T19:30:00.797Z] Server: Microsoft-HTTPAPI/2.0
[2022-05-05T19:30:00.801Z] x-ms-request-id: asdf-asdf-asdf-asdf-60671b000000
[2022-05-05T19:30:00.802Z] x-ms-error-code: AuthenticationFailed
[2022-05-05T19:30:00.809Z] Date: Thu, 05 May 2022 19:29:59 GMT
[2022-05-05T19:30:00.810Z] Content-Length: 422
[2022-05-05T19:30:00.811Z] Content-Type: application/xml
[2022-05-05T19:30:00.812Z] .

Here's my Azure Resource Group:

enter image description here

Here's the function app - you can see I have assigned a user-assigned managed identity to it:

enter image description here

And here are the RBAC roles assigned my managed identity: enter image description here

Questions

Based on my limited knowledge / reading, it feels like I should install Azure.Identity and create some sort of DefaultAzureCredential? https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme#specifying-a-user-assigned-managed-identity-with-the-defaultazurecredential

EDIT 2

The changes suggested in the answer basically work. To clarify, the setting in local.setting.json that actually works is this:

"[nameofConnectioninC#Method]__serviceUri":"https://[nameOfStorageAccount].queue.core.windows.net/"

This fails when you local debug, but when you publish everything, upstream tests work.

dot
  • 14,928
  • 41
  • 110
  • 218
  • are you having the issue locally ? the error ` Issuer validation failed. Issuer did not match.` indicates that you're connecting to a storage with wrong AAD tenant. – Thomas May 05 '22 at 22:24
  • @Thomas - yeah i'm trying to run the code locally to test, before I deploy. But it's connecting to upstream database / storage account. I found this article: https://www.michaelscollier.com/azure-function-secretless-extensions-first-experience/ which i'm trying to step through .. but my personal account .. which i'm not convinced is the one that's being used .. is global admin – dot May 05 '22 at 22:45
  • @Thomas - this is a remedial question... but when I'm debugging locally how can I tell which account is being picked up / used to log in? – dot May 05 '22 at 22:46
  • in visual studio, you can to `tools -> options -> azure service authentication` – Thomas May 06 '22 at 00:59
  • 1
    @Thomas I'm using vscode. i can't seem to find those items. but... i published the code changes upstream and tested end to end in the cloud and its mostly working. I have other logic that's failing but you've basically answered the original ?. So I won't clutter up this spot with multiple issues. I just accept the answer and post a new one. THANK YOU!!! – dot May 06 '22 at 12:18

1 Answers1

0

To use Identity-based connections with Queue Storage, you will need to:

  1. Update you application to use these Nuget Packages: Azure.Storage.Queues and Microsoft.Azure.WebJobs.Extensions.Storage.Queues.

  2. Create an app settings called <CONNECTION_NAME_PREFIX>__serviceUri:

    The data plane URI of the queue service to which you are connecting, using the HTTPS scheme.
    https://<storage_account_name>.queue.core.windows.net

    So in your case you need to create a setting called WidgetStorageQueue__serviceUri

  3. Grant permission to your function app identity to access queue storage. If you just need to send message to a queue, you could use the Storage Queue Data Message Sender role.

I've created a small function app that reproduces this use case.

csproj file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Azure.Storage.Queues" Version="12.9.0" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage.Queues" Version="5.0.0" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.0" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
</Project>

function file

using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace FunctionApp1
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            [Queue("widgetworkspaces"), StorageAccount("WidgetStorageQueue")] ICollector<string> queueCollector,
            ILogger log)
        {
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            queueCollector.Add(requestBody);
            return new OkObjectResult(requestBody);
        }
    }
}

settings file

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "WidgetStorageQueue__queueServiceUri": "https://<storage_account_name>.queue.core.windows.net"
  }
}

Thomas
  • 24,234
  • 6
  • 81
  • 125
  • thank you very much for the response! It seems though that tutorial uses Azure key vault. I was hoping to avoid creating a key vault. At least at this stage in the game. Trying to google for other examples, but if you have a link I'd appreciate it! – dot Apr 14 '22 at 13:31
  • Added more infos – Thomas Apr 14 '22 at 21:27
  • Did that work for you ? – Thomas Apr 19 '22 at 08:52
  • I'm just getting around to this now. So Sorry. Please see Edit 1 for details re: what's failing – dot May 05 '22 at 19:24
  • also can you confirm the entry in local.settings.json? In your itemized list, it seems like it should be __ServiceUri. But then in your code samples, it looks like its __queueServiceUri. I've tried both fwiw, and I get the same error thats listed in Edit 1. But i just want clarification on what it should be. Thanks! – dot May 05 '22 at 20:31
  • I think it should be __serviceUri, no? – dot May 05 '22 at 22:47