Azure Function secretless access to Azure Service Bus using Managed Identity
TLDR: Full code example on GitHub.
I believe that secretless is the way <insert Mandalorian meme>, and wanted to explore how this can be done for an Azure Function reading from an Azure Service Bus queue.
Last year (10/28/21) Microsoft announced GA for libraries supporting secretless configuration for Storage, Event Hubs and Service Bus via Azure Active Directory 🥳.
Now, before we start looking at some code.. Azure Functions running on .NET5/6 can be run in-process or out-of-process/isolated process. There are some differences there, but both are able to use secretless configuration. The notable difference is that they use different packages for binding extensions:
- In-process uses
Microsoft.Azure.WebJobs.Extensions.*
For accessing Azure Service Bus, add packageMicrosoft.Azure.WebJobs.Extensions.ServiceBus
> = 5.0.0. (Current version is 5.2.0). - Isolated uses
Microsoft.Azure.Functions.Worker.Extensions.*
For accessing Azure Service Bus, add packageMicrosoft.Azure.Functions.Worker.Extensions.ServiceBus
>= 5.0.0 (Currently in beta.6).
NB! The Microsoft roadmap states that only isolated process will be supported from .NET7 and onwards, so my code examples will be based on that.
Setting up the infrastructure
I'll need the following infrastructure in Azure, using naming conventions from the Microsoft docs:
- Resource group named
rg-demo
- Azure Function named
func-demo-westeu
Install packages:Azure.Identity
>= 1.5.0 (enable support for Managed identity)Microsoft.Azure.Functions.Worker.Extensions.ServiceBus
>= 5.0.0 (beta is ok) - User-Assigned Managed Identity named
id-demo-westeu
- Associate the Managed Identity with the Azure Function (two steps).
* Assign the Managed Identity to the function.
* In the function application settings, addAZURE_CLIENT_ID
with the clientId of the Managed Identity. - Azure Service Bus namespace named
sb-demo-westeu
- Azure Service Bus queue named
sbq-demo-westeu
- For the queue, assign role (using IAM)
Azure Service Bus Data Receiver
to the Managed Identity (id-demo-westeu
). Best practices dictate that it's always best to grant only the narrowest possible scope, so this should be scoped to the queue. (Not the namespace or resource group).
What happened here?
The Managed Identity now have access to read from the queue. Any app associated with this Managed Identity will have this access, meaning the Azure Function will have this access.
The code
The next step is to create the Azure Function that will use a ServiceBusTrigger to retrieve messages from the queue.
I create a new template Azure Function project (.NET 6 Isolated), with one function:
public class ProcessQueueItem
{
private readonly ILogger _logger;
public ProcessQueueItem(ILoggerFactory loggerFactory) => _logger = loggerFactory.CreateLogger<ProcessQueueItem>();
[Function("ProcessQueueItem")]
public void Run([ServiceBusTrigger("%QueueName%", Connection = "ServiceBusConnection")] string myQueueItem)
{
_logger.LogInformation($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
}
}
Nothing fancy here.. the magic lies in the connection string, which is specified like this:
<connection_name>__fullyQualifiedNamespace=<service_bus_namespace>.servicebus.windows.net
For my app that resolves to:ServiceBusConnection__fullyQualifiedNamespace=sb-demo-westeu.servicebus.windows.net
No secrets.
Testing
After deploying the function to Azure and specifying the Service Bus connection string in Application settings, I'm ready to test if the function is able to retrieve a message from the queue.
I use Service Bus Explorer to connect to the Azure Service Bus, and it has the capability of sending messages on the queue:
The log indicates that the message was processed successfully:
And there you go, the Azure Function has secretless access to the Azure Service Bus queue.
See the full code example on GitHub.
Error messages you might encounter
Microsoft.Azure.WebJobs.ServiceBus: Microsoft Azure WebJobs SDK ServiceBus connection string 'ServiceBusConnection' is missing or empty
This means that you are not using the correct package.
Be sure that the package Microsoft.Azure.Functions.Worker.Extensions.ServiceBus
is >= 5.0.0 (beta is ok).
For in-process functions the package Microsoft.Azure.WebJobs.Extensions.ServiceBus
must be >= 5.0.0.
'Listen' claim(s) are required to perform this operation
This means that the Managed Identity part is not working:
* Be sure that the package Azure.Identity
is installed >= 1.5.0
* That the Managed Identity has correct access/role.
* That the AZURE_CLIENT_ID
is correct in the application settings for the function.
Conclusion
By using the newest libraries from Microsoft, which supports secretless configuration, you can easily grant access to Azure resources by using Managed Identity.
The connection string will only contain the Azure Service Bus namespace, which can easily be set up using IaC provisioning.