A short guide on how to use Azure functions with Exchange module, using managed identity for authentication

EXO V3 (more info here) has recently become generally available, and with it comes support for Managed Identity sign in from Azure Functions

I would like to share how you can utilize Azure Functions on linux with a user-assigned managed identity and how to set everything up from scratch.

Here’s what the setup will look like:

  1. Create and set up the function app (Linux, PowerShell 7.2)
  2. Create managed identity (instead of system-assigned, user assigned can be used with multiple azure resources)
  3. Add permissions to the managed identity
  4. Test Exchange connectivity in a sample function

Create the function app

  1. Go to the azure portal, type “function app” in the search, then click function app
  2. Click Create, choose a name and resource group. For details we will be using the following runtimes:
    • Runtime stack: PowerShell Core
    • Operating system: Linux
    • Plan type: Consumption

After creating, you’ll now need to set it up so it has the right modules.

  1. Open the function app in the Azure portal
  2. Click on “App files”
  3. Switch the active file to “requirements.psd1”
  4. Uncomment the Az line (or, use Az.Accounts with version 2.*) and add this: ‘ExchangeOnlineManagement’ = ‘3.0.0’. It should look like this:
# This file enables modules to be automatically managed by the Functions service.
# See https://aka.ms/functionsmanageddependency for additional information.
@{
    # For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'. 
    # To use the Az module in your function app, please uncomment the line below.
    'Az' = '8.*'
    'ExchangeOnlineManagement' = '3.0.0'
}

Save the file, then restart the function app from the overview pane.

Create the managed identity

These steps involve a mixture of using the Azure portal and PowerShell.

  1. From the azure portal, search for “managed identities” and click the first result
  2. From the list, click Create. Choose a region matching the function app and give it a name and resource group, then create it.
  3. Go back to the function app, click Identity -> User assigned -> Add, then add your new resource.

Next you’ll need to add the appropriate Exchange permissions.

  1. Go to Azure AD -> Roles and administrators -> Exchange administrator
  2. Add the managed identity as an exchange administrator
  3. Now you’ll need to add Exchange.ManageAsApp to the managed identity.

Here’s an example script snippet for adding Exchange.ManageAsApp using AzureAD powershell (deprecated, I should update this!)

$DisplayNameOfMSI="CHANGE ME!"
$ExchangeOnlineAppId = "00000002-0000-0ff1-ce00-000000000000"
$Permission = "Exchange.ManageAsApp"

$MSI = (Get-AzureADServicePrincipal -Filter "displayName eq '$DisplayNameOfMSI'")
$GraphServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$ExchangeOnlineAppId'"
$AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $Permission -and $_.AllowedMemberTypes -contains "Application"}
New-AzureAdServiceAppRoleAssignment -ObjectId $MSI.ObjectId -PrincipalId $MSI.ObjectId -ResourceId $GraphServicePrincipal.ObjectId -Id $AppRole.Id

Now that you’ve added the required permissions, give it a few minutes before moving on. Exchange takes a little while to catch up with new permissions.

In your function app’s profile (function app -> app files -> profile), use the following code:

if ($env:MSI_SECRET) {
    Disable-AzContextAutosave -Scope Process | Out-Null
    Connect-AzAccount -Identity -AccountId "YourManagedIdentityClientId" | Out-Null
    Connect-ExchangeOnline -ManagedIdentity -ManagedIdentityAccountId "YourManagedIdentityClientId" -Organization "YourTenantDomain" -SkipLoadingFormatData -ShowBanner:$false
}

In the above, change “YourManagedIdentityClientId” and “YourTenantDomain” with your relevant details. You can find the managed identity client id in azure portal -> Managed Identities -> managed identity resource -> overview -> Client Id

Create the function

Go to the Functions pane in your function app, then create a new timer function. Replace the original code with the following:

param($Timer)
$messageTrace = Get-MessageTrace
$messageTrace | ConvertTo-Json

Save the function, click “Test/Run”, then watch the logs. You should see message trace data appear in the logs.

That’s it! With this technique, you could extend your Exchange security in a variety of ways. For example, if you’d like to have message trace data queryable from Log analytics, you can now do this fairly easily (it can almost work like an alternative to Microsoft Defender for Office P2 license!).

Troubleshooting

If you have trouble getting the function app to run, there’s one likely problem: It’s taking a really long time to install the depedencies. Here are a few solutions:

  1. First option: Manually upload the function app depedencies:
    • Install the required modules: Az, ExchangeOnlineManagement on another machine (do it on Linux if you’re using a Linux function app, Windows if on Windows etc.)
    • Copy the module files from userfolder/PowerShell/Modules into the function apps file share in this folder: StorageAccountName/FileShareName/.local/share/powershell/Modules
  2. Second option: Wait a really long time. I have seen it take 2-3 hours.
  3. Scope the Az module in your function app’s requirements.psd1 to something smaller:Az.Accounts instead of Az.*