Keep Credentials Secure using Azure Managed Service Identity

Microsoft has announced the General Availability for Managed Service Identity (MSI) for App Service and Azure Functions. MSI is a wonderful feature that helps keep credentials out of code.

As an example of how this can work, I want to modify a script created by fellow MVP Chris O'Brien. Chris has a terrific blog post about using the PnP PowerShell commands in an Azure Function. At the time he wrote it, the steps he included were the best practice. But with the GA of MSI, we can go even further. I encourage you to read Chris' post, then come back. ;)

Update: Vardhaman Deshpande has a step-by-step walkthrough of a function that uses MSI+KeyVault.

So, the script is reading the client id and secret from an environment variable. We can store these values securely in Azure Key Vault and them read them from there. We need to add MSI to the function app, Create a Key Vault, and grant permission to the vault for the identity associate with the Function App.

Add MSI to the Function App

In the Azure portal, navigate to the Function App. On the Platform featues page, locate the Managed Service identity link.

Azure Functions Platform Features

Register the Function App with Azure Active Directory by toggling the switch to On and click Save.

Managed Service Identity

Configure the Key Vault with secrets and Access Policy

Registering the Function App with Azure AD will result in a service principal being created. You will not see the principal in the Azure Portal, but you can verify it using the Azure AD graph API. However, the service principal is available to select when creating an Access Policy in Key Vault. For the policy, grant the Get permission on Secrets.

Azure KeyVault access policy

Next, add the AppID and AppSecret as Secrets in the Vault.

Azure KeyVault secrets

Use MSI in Function

With this configuration completed, we can change the PowerShell to read the secrets from the Vault. MSI offers a REST endpoint to get a token that is valid for the Key Vault. Using Chris' script as an example, we can then get the AppID and AppSecret using these commands:

$apiVersion = "2017-09-01"
$resourceURI = "https://vault.azure.net"
$tokenAuthURI = $env:MSI_ENDPOINT + `
    "?resource=$resourceURI&api-version=$apiVersion"
$tokenResponse = Invoke-RestMethod -Method Get `
    -Headers @{"Secret"="$env:MSI_SECRET"} -Uri $tokenAuthURI
$vaultToken = $tokenResponse.access_token

$KeyVaultUrl = "https://aad-dev-vault.vault.azure.net/secrets/AppID"
$KeyVaultSecret = (Invoke-WebRequest `
    -Uri "$KeyVaultURL?api-version=2016-10-01" -Method GET `
    -Headers @{Authorization="Bearer $vaultToken"} `
    -UseBasicParsing).content | ConvertFrom-Json
$appId = $KeyVaultSecret.value

$KeyVaultUrl = "https://aad-dev-vault.vault.azure.net/secrets/AppSecret"
$KeyVaultSecret = (Invoke-WebRequest `
    -Uri "$KeyVaultURL?api-version=2016-10-01" -Method GET `
    -Headers @{Authorization="Bearer $vaultToken"} `
    -UseBasicParsing).content | ConvertFrom-Json
$appSecret = $KeyVaultSecret.value

Now that the credentials are available, we continue to connect to SharePoint Online as Chris showed.

Connect-PnPOnline -AppId $appId -AppSecret $appSecret `
    -AADDomain $env:Tenant

Summary

Azure MSI enables developers and script writers to separate the credentials for a process from the code and environment in which the code executes. There is no need for the access policy of a Key Vault to include any user accounts outside of the tenant adminstrator.