Azure Function and User Assigned Managed Identities

Let’s talk about authentication between Azure Functions and resources used by Azure Functions and conclude with many poorly documented secrets about how to use User Assigned Managed Identity. When we build Azure functions, they usually need to authenticate against other Azure resources: Azure SQL Database, Storage Accounts, Service Bus and many more.

Each of these services have an authentication that we can call “Meh!”: Azure SQL has SQL Standard Logins, storage accounts have SAS tokens, service bus, shared access keys and so on. These are not the safest methods possible. If the key leaks, you will have a security problem because anyone with the key will be able to access the resource.

There are multiple solutions for this problem, some of them would pass through Key Vault, used to store secrets, keys and passwords. But let’s go directly to the best one: Remove the usage of keys at all. 

How the Azure Function can authenticate against other services without using any key?

We can assign an identity to the Azure Function and use this identity to authenticate on other resources. You can think about the identity as a special kind of Azure AD user. As with any other user, it can receive permissions in Azure resources, allowing your function to access the resources

Instead of using a shared key in your system or configuration files, using an identity leaves it up to Azure to manage the access from the function to other resources. There will be no secret access key which could be exposed in your system.

The two kinds of identity

The identity we set to a Function App (or other Azure Resource) is called a Managed Identity. There are two kinds of Managed Identity: The System Assigned Managed Identity and the User Assigned Managed Identity.

First of all, it’s important to understand that for both types, we are responsible to set the identity permissions, which is explained later.

The System Assigned Managed Identity is the perfect example of the KISS principle (Keep It Simple): You just turn it on in an Azure Function. The identity is created with the same name as your function app. You can give permissions to this identity and that’s it, your function will be able to access the resources you need.

When you delete the function app, the identity with all its permissions is deleted as well.

The User Assigned Managed Identity is a bit more complex. Why would you need a more complex solution?

If you have multiple Function Apps and all of them requires access to the same resources, using System Assigned Managed Identities becomes a problem. The management of permissions for each individual function identity is at least annoying, and it’s also prone to mistakes.

The solution is to create a custom identity and assign the same identity to all Function Apps which requires the same set of permissions. In this way, you manage the permissions on a single identity, instead of many.

This is the rule: If the permissions are needed by a single function, use a System Assigned Managed Identity and the identity lifecycle will be managed together the function lifecycle. If many functions need the same set of permissions, use a User Assigned Managed Identity and you will need to manage the permissions only for a single identity.

 

System Assigned Managed Identity

User Assigned Managed Identity

Usage

Used by one single function

Can be assigned to multiple Function Apps and other cloud apps

Lifecycle

Is created when turned on in the Function App and dropped together the Function App

Created and dropped independently of the Function App. Someone needs to manage the identity

How does it work

It simply works

It may require variables or properties to work, but these will not contain any sensitive information

Enabling System Assigned Managed Identity

As I mentioned before, these are very simple. You can just access the Identity tab and turn the identity on. The name of the identity is the name of the function app.


Enabling User Assigned Managed Identity

User Assigned Manage Identities have an independent life cycle. This means you need to create them directly from the Marketplace. To do this, locate the User Assigned Managed Identity in the marketplace resources and create it as illustrated on the following images:



The creation itself is a generally simple matter of choosing the resource group and name of the identity. Consider the company standards for naming and resource group distribution. Some questions you should ask:

  • Are you going to share this identity with objects in different resource groups?
  • Are you going to share this identity across different projects?
  • Are you going to share this identity across different environments (dev/uat/production)?

Of course, the more granular the identity permissions are, the better, but you are using this object because you want to share the same set of permissions across different App objects to begin with.


After creating the identity, you will be able to see it in the chosen resource group.


The next step is to assign the identity to the function app. The Identity tab has the option to include a User Assigned Managed Identity to the app. The following images illustrate the steps used for this.



As you may notice on the images, it’s possible to assign multiple identities to the same Function App. We can assign one System Assigned Managed Identity as well as multiple User Assigned Managed Identity. The usage of multiple identities is usually managed by code. In some situations, can be done by configurations as well.

The scenario with multiple identities, where maybe your security requires different identities to access each resource, goes beyond the subject of this blog.

Assigning permissions to the Identity

In this section, I will discuss the details of how the identity gets access to resources that you choose.

RBAC Permissions

Role Based Access Control is the usual access control used for most Azure services. In our examples, I will illustrate Service Bus and Storage Accounts usage of RBAC permissions. Let’s consider our function will need access to a storage account and a service bus namespace. The identity given to the function needs to receive RBAC permission on both.

The method to set RBAC permissions on the services is always similar.

  1. Click Access Control (IAM)


  1. Click Role Assignments tab

     


  1. Click + Add button and choose: Add Role Assignment menu item


  1. Select the role you would like to assign and click Next button


  1. Select Managed Identity


There isn’t much different between selecting Managed Identity or User, Group or Service Principal. You can find the identity in both ways. The Managed Identity option only makes it easier to locate Managed Identities

  1. Click Select Members link, then on the Select Managed Identities tab, open the Managed Identity drop down

This drop down summarizes to you all available Managed Identities in the subscription. The System Assigned Managed Identities are related to azure objects, while User Assigned Managed Identities are independent.


  1. Select the identity you created


  1. Click the Review + Assign button

Service Bus

Service Bus has three main roles you would like to assign: Service Bus Data Owner, Service Bus Data Receiver and Service Bus Data Sender. Probably you will assign the last two to your functions, depending if they send data to service bus or receive data from service bus.


You can assign permissions on the service bus level, the topic level or queue level. This brings an interesting decision to be made: you can use a single identity with access to multiple queues and topics or use multiple identities, one for each set of resources a function need.

The latter is safer, of course. The first option, on the other hand, can lead to situations where a function has access to queues and topics it doesn’t need to access.

Here come the corporate procedures again: Does your company has a security team responsible for this? If they have, they should be thrilled to hear this explanation and move forward with the most secure option. Otherwise, no one will care about the additional security and will be annoyed by your proposal to make it more difficult to manage. In this case, the first option is better.

Setting the permissions use the traditional RBAC procedure, defining roles for identities on the correct object.

Storage Accounts

Storage Accounts have many different roles available because they offer different services: Blob, File, Queue and Table. In this way, you have roles intended to manage the storage account and roles intended to manage the data inside the storage account.


Storage accounts brings the same dilemma than the Service Bus: You can control the permissions in a granular way, giving the functions only the permissions needed, or you can give all the permissions to a single identity and set it on the functions. The granular way is always technically better for security, but it always depends if the company is willing to manage it correctly.

The granularity in the storage account can be controlled not only by service (blob, queue, table and files), but by each element on the services. For example, you can set RBAC permission on the level of reach blob container.

Key Vault

Key Vaults can use RBAC as permission control method, but the most common method for permission control are the access policies. On the Access configuration tab in a Key Vault, you can choose rather to use RBAC or access policies to control the permissions.


Let’s focus on the access policies option. You can create access policies giving permissions to access the Keys, Secrets and Certificates.


Once again, you face the granularity problem: If function A needs access to Secrets and function B needs access to Certificates, will you use a single identity with access to certificates and secrets, or would you use two identities, one with access to secrets and the other with access to certificates?

Key Vault granularity goes further: The access control is made by type of object: Key, Secrets or certificates. Once the permission is given, the function will have access to all secrets, or all keys or all certificates. What if the function needs access only to some keys, but not to others?

The solution is to use multiple key vaults, organizing the objects according the app which will access them. Only by doing so you can ensure each function will have access only to the information the function needs to use, and not to information related to other functions.

SQL Databases

The possibilities in relation to SQL Databases are wider. Some microservices implementation types would build one database for each microservice, isolating the data.

Others would implement a single database, and that’s when the same security granularity problem comes into focus: Would you use one single identity with read and write permission? Different identities for read and write? Different identities for each database schema? The same kind of considerations as all other resources. There is no right and wrong answer. There is the safer and less safe. Less safe doesn’t mean wrong. It does however mean a correct choice between risk and amount of management work is needed. The granularity choice is a complex one to be done by a skilled security team. The developer work is to introduce the best options when they are not yet being used.

Important Secret: The usual library to access SQL Server, System.Data.SqlClient, doesn’t support authentication with managed identities. You need to use a different library, Microsoft.Data.SqlClient. Luckily, the code is the same, the only change I identified was the support to managed identities authentication.

The permissions are set using TSQL and the security rules specific to SQL Server, they are not set using RBAC. You can find an example below:

Important but out of scope: You also need to deal with SQL Firewall, but this is out of the scope of this blog. You can take a look on the vide on https://www.youtube.com/watch?v=RKZy5MkqFDQ&list=PLNbt9tnNIlQ5pVwZFRVpoBG8uQTs8aIcz&index=26 . The same principles of Service Connection and Private Endpoints also applies to Key Vaults, Storage Accounts and Service Bus.

The secrets of User Assigned Managed Identity

We would expect that User Assigned Managed Identity would just work, exactly as System Assigned Managed Identity. Unfortunately, that’s not so simple. There are many secrets to make User Assigned Managed Identity work. These secrets are not well documented and are different for each service.

Service Bus

In order to work with Service Bus, a User Assigned Managed Identity requires two configuration variables to be created: Azure_Client_ID and AZURE_TENANT_ID.

The first contains the Client Id generated to identify the Managed Identity, the second is the tenant id of the managed identity.



 

Without these variables you will get an error message inside Application Insights which will be similar to this:

“Azure.Identity.CredentialUnavailableException: EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot”

The documentation doesn’t explain much, especially because we were expecting it to work out of the box, but these variables are needed. Create the variables and it will work.

Key Vault

While the System Assigned Managed Identity simply works, a User Assigned Managed Identity requires a special property in the Function App object. The property is called keyVaultReferenceIdentity and needs to contain the object Id of the Managed Identity.

It would be simple if we could set this property on the UI, but this property isn’t available even on Azure CLI. We need to make a REST PATCH call to Azure API to set this property.

The code below is built to be called in BASH, you can use Cloud Shell for this:

The first statement retrieves the app resource id, while the second completes the PATCH call to set the property.

If you don’t set this property, the Function App will use the System Assigned Managed Identity or it will complain the identity is not enabled.

It’s not only for Function Apps

App Services, Virtual Machines, Container Apps or AKS, all kind of apps running on Azure can receive a managed identity and identify themselves to other services using this managed identity. It’s possible that each specific combination has its own tricks to work, such as the two I explained above.

This increases the entire security of the entire environment.

The corporate problems: Spreading the correct solution

Among corporations, we always face many challenges to implement mentality-changing solutions. The users spent too many years using passwords, keys and secrets and sometimes it may be challenging to explain why using identities is better.

Usually, you will face outdated standards and “we always did like this” approaches. My suggestion is to follow the guidance of the corporation you work for, but not accept it quietly, look for openings to improve it. Let’s analyze some options.

Expose problems with existing patterns

Sometimes, corporations create patterns for software development and these patterns live beyond their creator. The spread of such patterns create repetition and over time usually lacks a check if they are still the correct option.

The solution is to find the correct person open to hear about how it really works and analyze the best way to implement it. When attempting to change any kind of security practices, you should consider socializing the solution with people on the security team.

Introduce the Idea of Layered Security

Layered Security is a security approach to protect your applications from attack. The core idea is to protect every layer of your application and not only the entry point.

Companies not familiar with this concept may see this as additional work. They may think it’s enough to have a firewall and may consider not important to protect the access from one internal resource to another. For these companies, a mind changing may be needed, introducing the importance of Layered Security.

The concept of Layered Security changes this. The point is: If a hacker manages to cross your firewall, or find another entry point, they should face more security restrictions. If you don’t use the Layered Security approach and a hacker work around your entry point, they will be in, with access to all your information.

It’s the role of a skilled security professional to define the security guidelines to be followed by the company. But when the best security approaches are not in place, is the role of the developer to talk to these security professionals to highlight what could be better. Usually, they should like very much to know about additional security configuration they have available for the security guidelines.

Spread the knowledge among the team

It’s very common to work with development teams with very uneven knowledge about Function Apps and really much of the cloud functionality available. It can be useful for teams to organize knowledge sharing sessions to increase the knowledge of the team, establishing higher levels of software development standards.

The most secure method to authenticate functions and the methods to choose between User or System assigned managed identity is a great subject for a knowledge sharing session, especially since the answer requires knowledge of the exact problems you are attempting to solve.

Work around permission issues

The work to manage RBAC (Role Based Access Control) permissions require a higher-level permission than contributor. In order to manage these permissions, you need to have User Access Administrator permission.

Security and Cloud teams will typically have some resistance to provide these permissions and without understanding their purpose, they are right. The only way to allow you to work without providing these permissions is to provide you with a sandbox to work, where you have these permissions, and make all the deployment using DevOps pipelines, ensuring the correct configuration in UAT and Production environments.

Conclusion

Ensuring the best security possible is important and usually a forgotten subject. Many developers think “if it works, it’s fine”, but there are many ways that a working application may be a high risk for security.