Secure Azure Function-to-Function authentication without the need for credentials
Here at endjin we spend a lot of time working with data, and securing that data is top on our list of priorities . Therefore, anything we can do to reduce the need for storing access keys is a huge win! (Here is a guest blog from Barry Smart at Hymans Robertson which details our Swiss Cheese Model around security and compliance.)
In Azure, many services now have the ability to authenticate via a Managed Identity. This is essentially a service principal specific to that service, which you can then give access to various other resources. The applications can attempt to access resources using their associated identity, without the need for any kind of password or key.
These managed identities can then be given access to necessary resources. For example, they can be granted roles and added to access control lists in ADLS Gen2 accounts, or the ability to access keys in key vault. This means that data can be securely accessed without needing to store connection strings or app passwords.
Specifically though, in this post I'm going to outline how to give a function the ability to call into another function without needing to store the function keys.
The process
Say we have one function (lets call it the AutoTriggerFunction) which is on a timer trigger, and as part of the triggered process it needs to call out into another function to do part of the work. However, we don't want just anyone to be able to call into this second function, only those who have been given access (lets call this second function the ExclusiveFunction). The ExclusiveFunction will be HTTP triggered, but we need to be able to prove that the incoming request came from the AutoTriggerFunction as expected.
We can do that in a few steps:
- Enable a system provided identity on the AutoTriggerFunction.
This sets up an identity for the AutoTriggerFunction, which means that it can authenticate as itself in order to make requests. Practically, this means that it will then have an AAD managed identity associated with it, which it can then use to access resources.
- Enable easy auth on the ExclusiveFunction
Enabling easy auth means that when requests come in to the ExclusiveFunction, they must carry an authorisation header which has been validated against the function's authorisation service in order to be let through to the actual HTTP endpoint. This way, requests from services which have not been registered with the application will not be allowed to trigger the ExclusiveFunction.
To set up easy auth you go into the authorization section of the platform features, and turn on authentication via AAD. You then either set up a new, or select an existing, AAD application which will be used to control the authentication. Users can then be registered with this AAD application, which will allow them to retrieve valid auth tokens.
- Adding the managed identity as a user of the AAD application
We then need to register the managed identity for the AutoTriggerFunction with the AAD application which is managing the authentication for the ExclusiveFunction. Once the managed identity has been registered, valid auth tokens can be retrieved by the AutoTriggerFunction's identity and used to make authenticated HTTP requests to the client.
- Retrieve an access token from AAD
The AutoTriggerFunction
can then request an access token from AAD for the ExclusiveFunction's authentication AAD application. Inside the calling AutoTriggerFunction
's code this can be done using:
Where AzureServiceTokenProvider
is part of the Microsoft.Azure.Services.AppAuthentication
NuGet package. Here I'm just retrieving a single access token, these will be valid for a certain amount of time (usually an hour), which means if you have long running operations the access token could expire before the work is complete. To deal with this it is better to use an ITokenProvider
, but I won't get into that here.
If the function has identity enabled, when you set the AzureServicesAuthConnectionString
app setting to an empty string, it will automatically authenticate using its own managed identity. (Alternatively, you can use this setting to connect via a service principal, but that's a topic for another post). The AAD client ID variable here will be the ID of the AAD application you have used to secure the ExclusiveFunction.
You then use the bearer token returned to hit the endpoint for the ExclusiveFunction.
- Make sure that the authorization level of the HTTP endpoint is set to anonymous.
When you set up an HTTP trigger, the Run
method looks like this:
public static class ExclusiveFunction
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, ILogger log)
{
...
}
}
Here the AuthorizationLevel
can be set to Anonymous
, Function
, System
, Admin
or User
. You might think (I know I did) that because the AutoTriggerFunction is authenticating via an identity, you would need an authorization level of user. However, this feature currently isn't fully implemented, it may work in future but for now we need to use Anonymous
authorization level.
Luckily, when using easy auth, all authentication is done at the level of the functions application and the request will only be allowed to make it through to the trigger endpoint if it has come from a user which is registered with the AAD application used for authorization. Therefore, at the level of the function HTTP endpoint, the authorization can be set to anonymous as the security checks have already been carried out.
And with that the AutoTriggerFunction can now securely access the ExclusiveFunction, purely using its own managed identity, and without the need for it to store the ExclusiveFunction's keys!