Azure Functions: Access KeyVault Secrets with a Cert-secured Service Principal

Azure Functions is one of those services in Azure that is seeing a massive amount of uptake. People are using it for so many things, some of which require access to sensitive information at runtime.

At time of writing this post there is a pending Feature Request for Functions to support storing configuration items in Azure KeyVault. If you can’t wait for that Feature to drop here’s how you can achieve this today.

Step 1: Create a KeyVault and Register Secrets

I’m not going to step through doing this in detail as the documentation for KeyVault is pretty good, especially for adding Secrets or Keys. For our purposes we are going to store a password in a Secret in KeyVault and have the most recent version of it be available from this URI:

https://mytestvault.vault.azure.net/secrets/remotepassword

Step 2: Setup a Cert-secured Service Principal in Azure AD

a. Generate a self-signed certificate

This certificate will be used for our Service Principal to authorise itself when calling into KeyVault. You’ll notice that I’m putting a -1 day “start of” validity period into this certificate. This allows us to deal with the infrastructure running at UTC (which my location isn’t) and avoid not being able to access the certificate until UTC matches our local timezone.

# Requires PowerShell to be run as Admin-level user.
New-SelfSignedCertificate CertStoreLocation cert:\localmachine\my Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" `
Subject "cn=mydemokvcert" KeyDescription "Used to access Key Vault" `
NotBefore (Get-Date).AddDays(-1) NotAfter (Get-Date).AddYears(2)
# PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\my
#
#Thumbprint Subject
#———- ——-
# C6XXXXXX53E8DXXXX2B217F6CD0A4A0F9E5390A5 CN=mydemokvcert
#
$pwd = ConvertTo-SecureString String "YOUR_RANDOM_PASSWORD" Force AsPlainText
# Export cert to PFX – uploaded to Azure App Service
Export-PfxCertificate cert cert:\localMachine\my\C6XXXXXX53E8DXXXX2B217F6CD0A4A0F9E5390A5 `
FilePath keyvaultaccess03.pfx Password $pwd
# Directory: C:\WINDOWS\system32
#
#Mode LastWriteTime Length Name
#—- ————- —— —-
#-a—- 14/11/2016 16:06 2565 keyvaultaccess03.pfx
#
# Export Certificate to import into the Service Principal
Export-Certificate Cert cert:\localMachine\my\C6XXXXXX53E8DXXXX2B217F6CD0A4A0F9E5390A5 `
FilePath keyvaultaccess03.crt
#####
# Prepare Cert for use with Service Principal
#####
$x509 = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$x509.Import("keyvaultaccess03.crt")
$credValue = [System.Convert]::ToBase64String($x509.GetRawCertData())
# should match our certificate entries above.
$validFrom = [System.DateTime]::Now.AddDays(-1)
$validTo = [System.DateTime]::Now.AddYears(2)

b. Create Service Principal with Cert Authentication

This step requires you to log into an Azure Subscription that is tied to the target Azure AD instance in which you wish to register the Service Principal. Your user must also have sufficient privileges to create new users in Azure AD – if it doesn’t this step will fail.

##
# Create new Service Principal with Cert configured
##
LoginAzureRmAccount SubscriptionId XXXXXXXXXXXXXXXXXXXX86b9ebca2d13
# $credValue comes from the previous script and contains the X509 cert we wish to use.
# $validFrom comes from the previous script and is the validity start date for the cert.
# $validTo comes from the previous script and is the validity end data for the cert.
$adapp = New-AzureRmADApplication DisplayName "KeyVault Reader – Cert" HomePage "https://keyvaultreadr/" `
IdentifierUris "https://keyvaultreadr/" CertValue $credValue `
StartDate $validFrom EndDate $validTo
#
# DisplayName : KeyVault Reader – Cert
# ObjectId : XXXXXXXX-XXXX-XXXX-XXXX-1029a4c5be13
# IdentifierUris : {https://keyvaultreadr/}
# HomePage : https://keyvaultreadr/
# Type : Application
# ApplicationId : XXXXXXXX-XXXX-XXXX-XXXX-b1aa47a95554
# AvailableToOtherTenants : False
# AppPermissions :
# ReplyUrls : {}
#
New-AzureRmADServicePrincipal ApplicationId $adapp.ApplicationId
# DisplayName Type ObjectId
# ———– —- ——–
# KeyVault Reader – Cert ServicePrincipal XXXXXXXX-XXXX-XXXX-XXXX-11b962b59eef
####
# Grant Service Principal Read-Only on Secrets in our KeyVault
####
Set-AzureRmKeyVaultAccessPolicy VaultName 'mytestvault' ResourceGroupName 'your-awesome-rg' `
ServicePrincipalName $adapp.ApplicationId.Guid `
PermissionsToSecrets get
##
# Print Out the Service Principal's App ID (GUID) to use later in our Function setup.
##
$adapp.ApplicationId

view raw
Create-CertSP.ps1
hosted with ❤ by GitHub

At this point we now have a Vault, a Secret, and a Service Principal that has permissions to read Secrets from our Vault.

Step 3: Add Cert to App Service

In order for our Function App(s) to utilise this Service Principal and its certificate to access KeyVault we need to upload the PFX file we created in 2.a above into the App Service in which our Functions live. This is just as you would do if this App Service was running a Web App but without the need to bind it to anything. The official Azure documentation on uploading certs is good so I won’t duplicate the instructions here.

Watch out – Gotcha!

Once you’ve uploaded your certificate you do need to do one item to ensure that your Function code can read the certificate from store. You do this by adding an Application Setting “WEBSITE_LOAD_CERTIFICATES” and either specify just the thumbprint of your certificate or put “*” to specify any certificate held in the store.

Step 4: Function App KeyVault and Service Principal Setup

a. Nuget Packages

Accessing KeyVault with a Service Principal in Functions requires us to load some Nuget packages that contain the necessary logic to authenticate with Azure AD and to call KeyVault. We do this by adding the following to our Function App’s project.json.

{
"frameworks": {
"net46": {
"dependencies": {
"Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.1",
"Microsoft.IdentityModel.Logging": "1.0.0",
"Microsoft.Azure.Common": "2.1.0",
"Microsoft.Azure.KeyVault": "1.0.0",
}
}
}
}

view raw
project.json
hosted with ❤ by GitHub

b. KeyVault Client CSX
Now let’s go ahead and drop in our KeyVault “client” that wraps all code for accessing KeyVault in a single CSX (note that this is mostly inspired by other code that shows you how to do this for Web Apps).

#r "System.Runtime"
#r "System.Threading.Tasks"
using System;
using System.Threading.Tasks;
using System.Web.Configuration;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Azure.KeyVault;
using System.Security.Cryptography.X509Certificates;
public static string GetKeyVaultSecret(string secretNode)
{
var secretUri = string.Format("{0}{1}", "https://mytestvault.vault.azure.net/secrets/", secretNode);
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessToken));
return keyVaultClient.GetSecretAsync(secretUri).Result.Value;
}
private static async Task<string> GetAccessToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, GetCert());
if (result == null)
throw new InvalidOperationException("Failed to obtain the JWT token");
return result.AccessToken;
}
private static ClientAssertionCertificate GetCert()
{
// could read following values from App Settings if you wanted to
var clientAssertionCertPfx = FindCertificateByThumbprint("C6XXXXXX53E8DXXXX2B217F6CD0A4A0F9E5390A5");
// the left-hand GUID here is the output of $adapp.ApplicationId in our Service Principal setup script
return new ClientAssertionCertificate("XXXXXXXX-XXXX-XXXX-XXXX-e643a85c7c19", clientAssertionCertPfx);
}
private static X509Certificate2 FindCertificateByThumbprint(string findValue)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindByThumbprint, findValue, false);
if (col == null || col.Count == 0)
{
return null;
}
return col[0];
}
finally
{
store.Close();
}
}

view raw
keyvaultclient.csx
hosted with ❤ by GitHub

Step 5: Use in a Function

As we’ve encapsulated everything to do with KeyVault into a CSX we can retrieve a secret from KeyVault in a Function using a single call once we’ve imported our client code.

#load "keyvaultclient.csx"
public static void Run(TraceWriter log)
{
var secretStringClearText = GetKeyVaultSecret("remotepassword");
log.Info(secretStringClearText);
}

view raw
run.csx
hosted with ❤ by GitHub

Happy (Secure) Days!

5 thoughts on “Azure Functions: Access KeyVault Secrets with a Cert-secured Service Principal

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s