User assigned managed identity with azure function - is it possible? - azure

I was able set up System assigned managed identity for function that listens service bus: I turned on System Assigned identity in my function, changed connection string to 'Endpoint=my_endpoint;Authentication=ManagedIdentity' and assigned a role for function to use service bus.
My function code snippet is as follows:
[FunctionName("MyAwesomeFunction")]
public static async Task RunAsync([ServiceBusTrigger("myawesome-queue", Connection = "MyAwesomeConn")] string queueItem)
{
// func code
}
Now I'm trying to do the same but using User assigned identity: I created managed identity, added it to function and assigned a role for it in service bus. But looks like it doesn't work - messages don't get to my queue. Any advice? Thanks.

ManagedIdentity is not supported by service bus and Queue trigger. You have to use a complete connection string with a shared access signature.
As documented here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus#add-to-your-functions-app

Related

Azure.Messaging.ServiceBus Create a ServiceBusClient using a System Assigned Managed Identity

I'm migrating a servicebus client application from Microsoft.Azure.ServiceBus to use the current library Azure.Messaging.ServiceBus.
The application is a Worker Process running on a virtual machine in windows azure.
The VM has a system assigned managed identity which grants it access to service bus and we have been using it successfully with the old library for over a year.
On the old library we created a client using this connection string
Endpoint=sb://MyNamespace.servicebus.windows.net/;Authentication=Managed Identity
When I put that connection string into the constructor of Azure.Messaging.ServiceBus.ServiceBusClient I get the following error
The connection string used for an Service Bus client must specify the Service Bus namespace host and either a Shared Access Key (both the name and value) OR a Shared Access Signature to be valid. (Parameter 'connectionString')
I've been trawling through documents for some time now with no progress.
Is there anyway to make this work?
Ideally I would continue to use the connection string - developer machines do not have system assigned ID's so we develop with key based connection strings and let devops swap in the correct prod connection string.
UPDATE
Following on from Jesse's answer managed identity has to go trough a separate constructor which requires a namespace instead of an endpoint and an instance of ManagedIdentityCredential.
As I mentioned not all environments where we deploy have managed aged identities, some require a SharedAccessKey based connection string.
Instead introducing new "identity type" configuration parameters into our build process I've used a factory method to parse the connection string and call the correct constructor overload. Where its a managed identity It extracts the namespace from the endpoint setting.
I Hope its useful for others
private static ServiceBusClient CreateServiceBusClient(string connectionString)
{
var cs = new DbConnectionStringBuilder();
cs.ConnectionString = connectionString;
if (cs.ContainsKey("Authentication") &&
"Managed Identity".Equals(cs["Authentication"].ToString(), StringComparison.OrdinalIgnoreCase))
{
string endpoint = cs["Endpoint"].ToString() ?? String.Empty;
if (endpoint.StartsWith(#"sb://", StringComparison.OrdinalIgnoreCase)) endpoint = endpoint.Substring(5);
if (endpoint.EndsWith(#"/")) endpoint = endpoint.Substring(0, endpoint.Length - 1);
return new ServiceBusClient(endpoint, new ManagedIdentityCredential());
}
return new ServiceBusClient(connectionString);
}
it needs the Azure.Identity package and the namespace System.Data.Common for the connection string builder.
The clients in the Azure.Messaging.ServiceBus package support connection strings only in the format that the Azure portal returns them.
The ;Authentication=Managed Identity token that you've included in your connection string is not a known token and is ignored, so the client does not have the information needed to perform authorization. A managed identity cannot be specified via connection string.
To use a managed identity, you'll use one of the constructor overloads that accepts a fully qualified namespace and a TokenCredential. An example can be found in the package Overview docs. Any of the Azure.Identity credentials can be used; you may want to take take a look at the managed identity section of the Azure.Identity overview.

How to read connection string from key vault for Service Bus?

Here is how I instantiate the client in my Configure method:
services.AddSingleton<ServiceBusClient>(x => new ServiceBusClient(configuration.GetSection("ServiceBus:ConnectionString").Value, serviceBusClientOptions));
And this how my appsettings looks like:
{
"ServiceBus:ConnectionString": "#Microsoft.KeyVault(VaultName=MyVaultName;SecretName=MySecretName)"
}
However, I am getting the following exception:
The connection string used for an Service Bus client must specify the Service Bus namespace host and either a Shared Access Key (both the name and value) OR a Shared Access Signature to be valid. (Parameter 'connectionString'
What am I missing here?
Have you created a managed identity for you application and added access policies such that your app can GET this secret value from key vault?
Check out the official documentaion for this here : https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references
Also on a side note, have you tried directly adding the secret value as the appsetting value instead of referencing it from KV and see if that worked? (if yes then definitely its a permissions issue and NOT a problem with your C# app code.

Using an Azure function to read a Service Bus Message with Managed Identity

I'm trying to use Managed Identity to authenticate my Azure function against Azure Service Bus, however, I feel like I'm missing a key piece of the puzzle. I'm using the following package:
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="4.2.1" />
And the function looks like this:
[Function("MyFunction")]
public void Run([ServiceBusTrigger("myqueue", Connection = "")] string message, FunctionContext context)
{
var logger = context.GetLogger("MyFunction");
logger.LogInformation($"C# ServiceBus queue trigger function processed message: {message}");
Running this just tells be that the connection string is empty - which, to be fair, it is. But I thought that using Managed Identity, I'd be able to authenticate myself without using a connection string (i.e., I could just tell it to use DefaultAzureCredentials, and perhaps the namespace).
Am I correct in my assumption? If so, is there a new / different library to use?

App service to app service auth in Azure using Managed Identity

I have set up two App Services in Azure. 'Parent' and 'Child', both expose API endpoints.
Child has endpoint 'Get'.
Parent has endpoints 'Get' and 'GetChild' (which calls 'Get' on Child using HttpClient).
I want all Child endpoints to require auth via Managed Identity and AAD, and I want all Parent endpoints to allow anonymous. However in Azure I want to set the Parent App Service to have permission to call the Child App Service. Therefore Child endpoints are only accessible by using Parent endpoints (or if you have permissions on a user account to directly use Child).
In the Azure Portal:
Authentication/Authorization
I have enabled 'App Service Authentication' on both App Services.
Child is set to 'Log in with AAD'.
Parent is set to 'Allow Anonymous requests'.
Both have AAD configured under 'Authentication Providers'.
Identity
Set to 'On' for both App Services
Access control (IAM)
Child has Parent as Role Assignment, Type = "App Service or Function App" and Role = "Contributer"
With all the above setup:
Calling Child -> Get, requires me to log in
Calling Parent -> Get, returns the expected response of 200 OK
Calling Parent -> GetChild, returns "401 - You do not have permission to view this directory or page"
Without the use of Client ids/Secrets/Keys/etc, as I thought the idea behind Managed Identity was to throw that all out the window, given all the above, should Parent be able to call Child? And if so, what have I setup wrong?
Calling Parent -> GetChild, returns "401 - You do not have permission to view this directory or page"
Without the use of Client ids/Secrets/Keys/etc, as I thought the idea
behind Managed Identity was to throw that all out the window, given
all the above, should Parent be able to call Child? And if so, what
have I setup wrong?
There are two things that I notice with current setup.
1. Acquire a token using Managed Identity to call "Child" service endpoint from "Parent"
Managed Identity only provides your app service with an identity (without the hassle of governing/maintaining application secrets or keys). This identiy can then be used to acquire tokens for different Azure Resources.
But it is still your App's responsibility to make use of this identity and acquire a token for relevant resource. In this case the relevant resource will be your "Child" API. I think this is probably the part you are missing right now.
Relevant documentation on Microsoft Docs - How to use managed identities for App Service and Azure Functions > Obtain tokens for Azure resources
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.KeyVault;
// ...
var azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://vault.azure.net");
// change this to use identifierUri for your child app service.
// I have used the default value but in case you've used a different value, find it by going to Azure AD applications > your app registration > manifest
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://<yourchildappservice>.azurewebsites.net");
This C#/.NET sample uses Microsoft.Azure.Services.AppAuthentication nuget package and acquires a token for Azure Key Vault. In your case, you will replace https://vault.azure.net with the identifierUri for your "Child" service. It's usually set to https://<yourappservicename>.azurewebsites.net by default, but you can find it's value by going to Azure AD applications and then finding the relevant app registration > manifest. You could also use applicationId for the target application (i.e. "Child") to acquire the token.
In case you're not using C#/.NET, same Microsoft Docs link above also has guidance on how to acuqire token using Managed Identity and REST based calls from any platform. Using REST Protocol
Here is a blog post that also gives a good walk through - Call Azure AD protected website using Managed Service Identity (MSI)
2. Azure RBAC Role Assignments are different from Azure AD roles that you may want to use
I see that you have assigned contributor role to Parent App Service's identity from IAM. This role assignment works for Azure RBAC and help in giving permissions for managing the resources, but Azure AD role claims work differently.
If what you were looking to do is to assign a role to parent app, which can be checked in child app and only then allow the calls there is a different way of setting this up.
I should first mention that this role based setup is for a little advanced scenario and not really mandatory to do. You should be able to call "Child" service from "Parent" once you follow the steps in point 1 described above.
Now once the call from Parent to Child is working, you may want to limit the access to Child app service to only "Parent" or a few valid applications. Here are two approaches to achieve that.
Both the approaches are explained on Microsoft Docs here - Microsoft identity platform and the OAuth 2.0 client credentials flow
Relate SO Posts and Blog
Is there a way to secure an Azure Function that will only be called from a specific Azure Logic App?
Azure Active Directory - How to restrict Backend API App Registration to a specific client App Registration
https://joonasw.net/view/calling-your-apis-with-aad-msi-using-app-permissions
Approach 1 - Use Access Control Lists
When your "Child" API receives a token, it can decode the token and extract the client's application ID from the appid and iss claims. Then it compares the application against an access control list (ACL) that it maintains.
Depending on your requirement, API might grant only a subset of full permissions or all permissions to a specific client.
Approach 2 - Use Application Permissions or Roles
Configure your child API application to expose a set of application permissions (or roles).
This approach is a little more declarative, as you define an application permission that needs to be assigned to any application that can call your child-api.
Navigate to Azure Active Directory > App Registrations > App registration for your child-api app > Manifest
Add a new application role.. using json like this:
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "Can invoke my API",
"id": "fc803414-3c61-4ebc-a5e5-cd1675c14bbb",
"isEnabled": true,
"description": "Apps that have this role have the ability to invoke my child API",
"value": "MyAPIValidClient"
}]
Assign the app permission to your frontend app
New-AzureADServiceAppRoleAssignment -ObjectId <parentApp.ObjectId> -PrincipalId <parentApp.ObjectId> -Id "fc803414-3c61-4ebc-a5e5-cd1675c14bbb" -ResourceId <childApp.ObjectId>
Now, in the auth token received by your child api, you can check that the role claims collection must contain a role named "MyAPIValidClient" otherwise you can reject the call with Unauthorized exception.
To expand on the accepted answer.
You need to define an "App Role" in the target app registration's manifest. This is the app registration which is used to represent the resource (API App Service).
Then you use the Azure CLI to grant permission for that "App Role" to the Enterprise App (The one generated when you setup a managed identity for the client app). See the "APIs and other Azure AD registered applications" in this article for detailed steps https://blog.yannickreekmans.be/secretless-applications-add-permissions-to-a-managed-identity/
You can retrieve the token using the following once the permissions have been granted. The code snippet below uses Azure.Identity which is now the recommended library for Managed Identity in Azure.
public class AzureAdTokenRetriever : IAzureAdTokenRetriever
{
private readonly ILogger<AzureAdTokenRetriever> logger;
private readonly IMemoryCache inMemoryCache;
public AzureAdTokenRetriever(
ILogger<AzureAdTokenRetriever> logger,
IMemoryCache inMemoryCache)
{
this.logger = logger;
this.inMemoryCache = inMemoryCache;
}
public async Task<string> GetTokenAsync(string resourceId, string scope = "/.default")
{
var resourceIdentifier = resourceId + scope;
if (inMemoryCache.TryGetValue(resourceIdentifier, out var token))
{
this.logger.LogDebug("Token for {ResourceId} and {Scope} were fetched from cache", resourceId, scope);
return (string)token;
}
var tokenCredential = new DefaultAzureCredential();
var accessToken = await tokenCredential.GetTokenAsync(
new TokenRequestContext(new [] { resourceIdentifier }), CancellationToken.None)
.ConfigureAwait(false);
// Set cache options with expiration 5 minutes before the token expires
var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(accessToken.ExpiresOn.AddMinutes(-5));
inMemoryCache.Set(resourceIdentifier, accessToken.Token, cacheEntryOptions);
this.logger.LogDebug("Token for {ResourceId} and {Scope} saved in cache with expiration of {TokenExpiry}",
resourceId, scope, cacheEntryOptions.AbsoluteExpiration);
return accessToken.Token;
}
}

Is there a way to secure an Azure Function that will only be called from a specific Azure Logic App?

I understand that Azure Functions are potentially open endpoints on the internet if I read Microsoft’s documentation correctly and per conversations with a friend who has some experience working with web development paradigms that Azure Functions leverages. A cursory reading of security forums and stack overflow questions on the topic leads me to understand at least a couple options of securing them namely
Azure Active Directory
Shared Access Signatures (SAS) and
Azure Virtual Networks.
Context/ What does my Azure Function do? It manages a blob container related to an ETL of vendor data from a SFTP source to a SQL Endpoint which this ETL utilizes an intermediary blob container for file transfer and long term cold storage of source data. The Azure Function moves the blobs from one container to an archive container after they have been loaded to the SQL endpoint. Why Azure Function to manage the blob containers?
SSIS lacks ability to perform blob manipulation (i.e copy and delete)
Logic App lacks ability to perform a join (of files loaded to SQL endpoint and file names in blob container)
An example of one of the functions is shown here below:
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net;
using Microsoft.WindowsAzure.Storage.Blob;
using System.Collections.Generic;
using System.Text;
namespace AFA_ArchiveBlob
{
public static class HttpTrigger_BlobInput
{
[FunctionName("HttpTrigger_BlobInput")]
public static async Task<HttpResponseMessage> Run(
//public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "{name}")] HttpRequest req,
string name,
ILogger log,
[Blob("{name}/blobname",FileAccess.ReadWrite,Connection = "AzureWebJobsStorage")] CloudBlobContainer myCloudBlobContainer
)
{
//Execution Logged.
log.LogInformation($"HttpTrigger_BlobInput - C# HTTP trigger function processed a request.");
//Run the query against the blob to list the contents.
BlobContinuationToken continuationToken = null;
List<IListBlobItem> results = new List<IListBlobItem>();
do
{
var response = await myCloudBlobContainer.ListBlobsSegmentedAsync(continuationToken);
continuationToken = response.ContinuationToken;
results.AddRange(response.Results);
}
while (continuationToken != null);
//Query the names of the blobs. Todo: can this be a single line linq query select instead?
List<string> listBlobNames = new List<string>();
foreach (CloudBlockBlob b in results)
{
listBlobNames.Add(b.Name);
}
//Serialize the list of blob names to json for passing to function caller via return statement
var jsonReturn = JsonConvert.SerializeObject(listBlobNames);
log.LogInformation("Returning the following JSON");
log.LogInformation(jsonReturn);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(jsonReturn, Encoding.UTF8, "application/json")
};
}
}
}
Firstly, even though using keys might be convenient, I see that official documentation advises against using keys to secure function endpoint in production scenarios.
I suggest it would be a better choice to go with Azure Active Directory for security.. as explained here Secure an HTTP endpoint in production
How to Implement
I see two possible approaches:
1. Simple Approach: Check that calling application is your Azure logic app specifically
Enable Azure Active Directory Authentication for your Azure Function App. You can simply use Express settings (with create a new Azure AD app)
Enable Managed Service Identity for your Logic App.
Find out appid for Managed Service Identity associated with your logic app.. go to Azure Portal > Azure Active Directory > Enterprise Applications > All Applications > Relevant Service Principal (Explained in more detail with screenshots in another SO post here)
Authenticate your logic app to Azure function using Managed Service Identity as explained here.. Authenticate with managed identity in logic app.. note that resource being accessed will be your Azure function.
In your function code, now you can check that appid claim in access token should exactly match the appid for logic app (i.e. logic app is the one calling your function).. otherwise you can reject the call with Unauthorized exception.
2. A more declarative Approach: Have an application permission defined for Azure function app and check for this permission/role being present in auth token from client calling your function
This approach is a little more declarative, as you define an application permission that needs to be assigned to any application that can call your Azure function.
Enable Azure Active Directory Authentication for your Azure Function App. You can simply use Express settings (with create a new Azure AD app)
Now go to Azure Active Directory > App Registrations > App registration for your function app > Manifest
Add a new application role.. using json like this:
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "Can invoke my function",
"id": "fc803414-3c61-4ebc-a5e5-cd1675c14bbb",
"isEnabled": true,
"description": "Apps that have this role have the ability to invoke my Azure function",
"value": "MyFunctionValidClient"
}]
Enable Managed Service Identity for your Logic App.
Find out appid for Managed Service Identity associated with your logic app.. as already explained in approach 1 above
Assign the app permission to this managed service identity..
New-AzureADServiceAppRoleAssignment -ObjectId <logicappmsi.ObjectId> -PrincipalId <logicappmsi.ObjectId> -Id "fc803414-3c61-4ebc-a5e5-cd1675c14bbb" -ResourceId <yourfunctionaadapp.ObjectId>
Authenticate your logic app to Azure function using Managed Service Identity.. as already explained in approach 1 above
Now, in the auth token received by your function, you can check that the role claims collection must contain a role named "MyFunctionValidClient" otherwise you can reject the call with Unauthorized exception.
In addition to the above steps explained by #Rohit Below step is important:
Go to Host.json of the function.
Default authLevel : "function" should be changed to "authLevel": "anonymous".
This does not mean anyone can access the function as with Log on AD sign-in authentication required sign user however with managed identity in logic app function authenticate with service principle.

Resources