'Azure Managed ID: how to convert azure function from connection string to managed id
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:
Here's the function app - you can see I have assigned a user-assigned managed identity to it:
And here are the RBAC roles assigned my managed identity:
Questions
Based on my limited knowledge / reading, it feels like I should install Azure.Identity and create some sort of DefaultAzureCredential? https://docs.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.
Solution 1:[1]
To use Identity-based connections with Queue Storage, you will need to:
Update you application to use these Nuget Packages: Azure.Storage.Queues and Microsoft.Azure.WebJobs.Extensions.Storage.Queues.
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.netSo in your case you need to create a setting called
WidgetStorageQueue__serviceUri
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"
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |