Identity based triggers and bindings in Azure functions

Cheranga Hatangala
Cheranga
Published in
8 min readNov 13, 2021

--

Azure functions are getting improved, and updated more frequently now. Recently it has been upgraded to support identity based triggers and bindings.

The Azure function will be provided only the required roles to access the resource so that its more secure. Although, still its always best to access sensitive data through a key vault if you cannot do so using identity based connections.

For an example, if you are implementing a service bus triggered Azure function without identity based connections, the connection to the topic had to be saved in a key vault and then provide access to the Azure function app to access the key vault through managed identity. But with the new changes now, you can use RBAC to provide access to the function app directly to access the service bus topic securely.

Context

In this demo we’ll be creating a service bus triggered Azure function with identity support.

All the code for this can be found in GitHub.

Once we deploy the Azure functions, and if we didn’t use identity based connections the configuration section in the Azure function app will look like below.

The setting AzureWebJobsStorageis required by Azure functions to perform normal operations, and as you can see the connection string to the storage account is visible. This is not secure.

Also if you can refer the other configurations marked in red boxes they represent the read/write access to the service bus to be used in the Azure functions. Although they are securely accessed through a key vault, it would be better if the function app itself have the required access to the service bus so that a key vault will be unnecessary in this case, and it will be more secure.

“More secure” also means that the actual value in the key vault can be anything, and it might not be the correct access connection string or it could be tampered. But with managed identity once the access is provided with the correct roles you don’t need to worry anymore.

Design Principles

When we design and build a solution we would like to use good design principles. Especially if you are following the S.O.L.I.D principles you will be decoupling responsibilities across multiple contexts. One context you will be considering here is decoupling infrastructure related implementations such as publishing to a service bus, event hub or to a database. Simply you should not be implementing it in the same presentation layer . That is, in Azure functions perspective where you define your Azure functions.

But with Azure functions output binding support, you will be tempted to bind the infrastructure component and then pass it down the layers where the actual operation happens.

This is how you would create a function which uses output binding and passes it through the lower layers.

The messages is an output binding and it’s passed down to the lower layers ( IReceiveOrderRequestHandler in the application/domain layer) to perform actions of it. This creates an unnecessary coupling in the application/domain layer where it directly coupled with a library and, the only reason to do this is to use the output binding support.

The Azure SDKs are frequently updating with improved features and support. In here we’ll be using the required SDK support in the infrastructure layer and, of course we’ll be using the identity support which are already available in those SDKs.

Clean Architecture

The solution is designed based on the clean architecture patterns and the solution is organized as below.

Infrastructure components

Let’s emphasize on the infrastructure layer and as illustrated above the ReceiverOrderFunction will need to publish a message to the service bus topic. The publication of this message is implemented in the infrastructure layer.

IMessagePublisher and MessagePublisher

The ServiceBusClient is from the nuget package Microsoft.Azure.WebJobs.Extensions.ServiceBus and you can register the client as a dependency in the Startup class.

When registering the service bus client we are explicitly stating to avoid the other authentication mechanisms. This is important so that the code will use only Azure CLI, and managed identity.

Azure Functions (service bus triggered function)

The service bus triggered function will be using identity based connectivity to get triggered. As you could see the code is not different to an implementation without identity based. It will be at the deployment stage that these access rights to be provided. This will be explained towards the end of this article.

Setting up your local environment

Since the application needs access to both read and write from the service bus topic/subscription both the application and the user of the IDE needs to have Azure Service Bus Data Receiver and Azure Service Bus Data Sender roles assigned to them.

I was able to successfully “publish” messages to the topic using managed identity from my local environment but unfortunately I wasn’t able to “trigger” the service bus triggered Azure function in my local environment. So I had to use the service bus connection string “only” in my local environment.

The local.settings.json should be set as below,

CI/CD

Build Pipeline

In here we are using a YAML based multi-stage pipeline to build and deploy the solution to Azure. The resources to be deployed are declared using Azure Bicep .

The CI/CD pipeline, and how it’s organized is explained below,

pipeline.yaml

This contains all the stages and the environments which the pipeline will build and deploy the solution.

build.yaml

This is the build pipeline. All the steps required to build and test the solution along with the artifacts to be used in the deployment pipeline.

  • Builds and restores the solution.
  • Run the tests in the solution if there are any.
  • Create the function app artifact to be deployed into Azure.
  • Create the Bicep templates as an artifact so that it can be referenced from the deployment pipeline.

Variables

In this folder we will include all the variables to be used in the CI/CD pipeline. The variable files are named after the environment they will be used. There’s a file called common.yaml where you can define all the variables which can be shared among all the environments. The benefit of having such a file is that, then in the environment variable files you only need to define the variables which are specific to that environment or if there are values which you would like to be overridden from the common .

The common.yaml variable file,

The dev.yaml variable file with it’s own variable declarations and the overridden value from the common.yaml variable file,

Although we deploy only to the development environment (dev) you can add any environments as you would like.

Resources

All the resources which will be used to deploy this solution has been organized into their own folder and inside it contains the Azure Bicep template which will be used to deploy the resource.

For an example the ApplicationInsights folder contains the bicep template which will be used to provision an application insights instance.

Importantly when you are deploying the Azure function app settings you could notice that compared to the local.settings.json it uses “__” (double underscore). This is because in the bicep templates you cannot use “:” since it’s special but, delightfully you don’t want to change any of your code.

main.bicep

This is the master bicep template which will orchestrate all the other bicep templates which will be used to deploy the Azure resources. As you could see we use bicep modules feature to reference the resource templates and define dependencies. Azure Bicep has made this really convenient with the modules support.

You can do the same using ARM templates as well, but then you’ll need to use “linked ARM templates”, and to do that you’ll need to upload all the ARM templates to a storage location and access them through a SAS token in the deployment pipeline. This can be completely avoided by using bicep modules.

Providing RBAC access

Once all the resources are provisioned now the deployment pipeline needs to provide RBAC to the resources to collaborate.

  • Need to assign Storage Blob Data Owner for the function app and its staging slot to access the storage account using managed identity.
  • Need to assign both Service Bus Data Receiver and Service Bus Data Sender roles to the function app so that it can publish and trigger from the service bus namespace topic and subscription.
  • These are done through Azure CLI in the deployment pipeline. Please refer the github repository but the code snippet which does this is shown below.

When we provision the function app and its staging slot the deployment will output the resource ids of them as shown below.

So in the script above it uses this deployment as a reference and get the resource ids of both the function app and its staging slot, and then assign the required roles.

Deployed Azure function app and its settings

The configuration settings of the deployed Azure function app will be like below,

As you can notice instead of AzureWebJobsStorage now it’s set as AzureWebJobsStorage_accountName and, as explained earlier the function app will have access to the storage account with that name using managed identity.

The configuration settings which are marked in a “yellow” coloured box represents the service bus namespace. As explained earlier, the ServiceBusConfig__FullyQualifiedNamespace will be used in the class which resides in the infrastructure of the project where as ServiceBusConnection__fullyQualifiedNamespace will be used by the service bus triggered function.

--

--