This is part four in my series on Key Vault. This time I'll look at two different ways of accessing you Azure Key Vault

ClientId/ApplicationId vs Certificate based

In the previous blog post, I wrote how to access your Key Vault via a ClientId and ApplicationId. What we are doing is getting a token from Azure AD, that can be used to access the Key Vault. The ClientId/ApplicationId approach is the easiest to implement and understand, as you find the values in Azure AD, and then use them in your application afterwards. 

ClientId/ApplicationId is also the method that I'll fall back to whenever I'm not running via Azure or has access/permissions to install and access certificates.

Certificates

In Azure AD, you can make a certificate that will represent your Service Principal. The Service Principal is the user identity we are using to access Azure resources. That means that if you have a certificate for your Service Principal, we can get the token, without ever exposing a ClientId/ApplicationId. The trouble with ClientId/ApplicationId, is that it needs to be stored somewhere, I found that adding it as an AppSetting via the Azure Portal works nice, but if we are not in an Azure context, then the ClientId/ApplicationId will most likely be stored directly in the .config file. Those are stored via git, and shared among all developers, and then we are back at less secure state. Were the .config file to be compromised by someone else, they would be able to access everything the Service Principal has access to.

By using a certificate, we still need to have two values, but now it's the ApplicationId and the Thumbprint to the certificate. This means that if a .config file was compromised, it couldn't be used to access anything, unless they also found the certificate and had it installed. This makes the solution very flexible, as it enables us to use Key Vault in almost any situation.

  • If you have access to the VM that runs the code, you can install the certificate on the machine and make sure the identity running the app has access to the certificate.
  • If you are hosting via Azure in a Web App or the like, access to the certificate can be done directly in the Azure portal, so your application will have access to the certificate.
  • If you are hosting outside of azure in another hosted environment, it is likely that you are not able to install a certificate, and then we can fall back to the ClientId/ApplicationId combination.

Accessing with a certificate

To access with a certificate, we first need to create a certificate that can be used with our Service Principal. This can be done via Power Shell by the following script:

$PfxFilePath = 'mikkelhmKeys.pfx'
$CerFilePath = 'mikkelhmKeys.cer'
$DNSName = 'dnsname-can-be-whatever'
$Password = 'password'

$StoreLocation = 'CurrentUser' #be aware that LocalMachine requires elevated privileges
$CertBeginDate = Get-Date
$CertExpiryDate = $CertBeginDate.AddYears(1)

$SecStringPw = ConvertTo-SecureString -String $Password -Force -AsPlainText 
$Cert = New-SelfSignedCertificate -DnsName $DNSName -CertStoreLocation "cert:\$StoreLocation\My" -NotBefore $CertBeginDate -NotAfter $CertExpiryDate -KeySpec Signature
Export-PfxCertificate -cert $Cert -FilePath $PFXFilePath -Password $SecStringPw 
Export-Certificate -cert $Cert -FilePath $CerFilePath

check it at GitHub

The script generates a .cer and a .pfx file. We use the .cer file to upload in Azure AD, and associate it with the Service Principal we created to access the Key Vault (same place as in the previous blog post where we added a token to the Service Principal). Once the certificate has been uploaded, we need to update our code. We need to be able to find the certificate on the machine executing the code. The following will find the certificate by thumbprint, in a given certificate store(CurrentUser or LocalMachine is available).

private static X509Certificate2 FindCertificateByThumbprint(string findValue, StoreLocation storeLocation)
{
    X509Store store = new X509Store(StoreName.My, storeLocation);
    try
    {
        store.Open(OpenFlags.ReadOnly);
        X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindByThumbprint, findValue, false);
        if (col.Count == 0)
            return null;
        return col[0];
    }
    finally
    {
        store.Close();
    }
}

With the above code, we will be able to use almost the same as when we are using ClientId/ApplicationId to get the Azure AD access token. 

private static ClientAssertionCertificate GetKeyVaultCertificate()
{
    var thumbprint = ConfigurationManager.AppSettings["KeyVaultThumbprint"];
    var clientId = ConfigurationManager.AppSettings["KeyVaultApplicationId"];
    var storeLocation = StoreLocation.LocalMachine;
    var clientAssertionCertPfx = FindCertificateByThumbprint(thumbprint, storeLocation);
    return new ClientAssertionCertificate(clientId, clientAssertionCertPfx);
}

public static async Task GetKeyVaultAccessToken(string authority, string resource, string scope)
{
    var cert = GetKeyVaultCertificate();
    var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
    var result = await context.AcquireTokenAsync(resource, cert);
    return result.AccessToken;
}

I've added the above methods to a helper class called CertificateBasedAccessHelper, getting the Key Vault client can now be done via

KeyVaultClient keyVaultClient = new KeyVaultClient(CertificateBasedAccessHelper.GetKeyVaultAccessToken);
var secretValut = keyVaultClient.GetSecretAsync("[url-to-secret]")
    // Don't try this at home :)
    .GetAwaiter().GetResult().Value;

Check it in full on GitHub

Choosing which access strategy to use

In general, I'd always recommend using the certificated based approach. The reason is that I find it a bit more secure. You'd need to know a little about certificates, like how to create and renew them. 

The great thing about choosing a strategy is that you are not excluding one strategy by choosing another. Even though I'd always try to use the certificate, in some circumstances it's not possible to install and access a certificate. In those cases, just fallback to ClientId/ApplicationId, and it will work just as fine.

References