Thursday, 23 April 2020

Reading and Writing Azure KeyVault Secrets with C#

Every application has secrets of one kind or another; database connection strings, API keys or other credentials are all common examples and you can probably think of others without having to try too hard. So every application needs to be able to store, read, update and remove secrets. 

Azure KeyVault is the Azure store for securely storing and accessing secrets, in this blog I'm going to go through how to setup a C# application to get your secrets in and out of KeyVault.

So I've set up a KeyVault through the Azure portal, and I've added a secret to it.


The next thing we need to do is set up a principal in Azure Active Directory; this will give our application a context by which it will be able to access the KeyVault. This can also be done through the Azure portal by entering 'Active Directory' in the top search bar. Once in the AD blade, you need to create a new app registration, so click on 'App Registrations' in the menu and then click 'New Registration'. You'll need to enter at a minimum a name for your registration, and you can optionally enter a URI that receives an authentication token. I'm not entering one here as my demo will just be a console application, but you might need one depending on your individual applications. Note that if you do enter a URI, it must be either secured by SSL e.g. https, or it must be a http://localhost address.

Once the registration is created, you'll see it in the portal. You can see the clientId here, which we'll need later.
We'll also need to create a secret that our application will use to prove its identity to AzureAD when we try to request an authentication token. (Think of the clientId and secret as being like a username/password combination). Click on Certificates and Secrets in the menu, and then click the New Client Secret button. Enter a description and how long the secret should be valid for, and create the new secret. Like the clientId, we'll need the secret later, so make a note of it now as you will not be able to view it later on.
With the App Registration setup, we now need to associate that with the KeyVault. Return to your KeyVault in the portal, and click Access Policies in the menu. To allow the app to access the KeyVault, click the Add Access Policy link. Here you can set the permissions that your app will have against your vault. There are a set of predefined templates you can use, or you can assign individual permissions. For now, I just want to read and write secrets, so in the Secret Permissions dropdown I'll just select Get/List/Set/Delete.
With the permissions selected, you need to associate them with the principal we created in Active Directory. Click Select Principal to open the blade. You'll see a whole set of standard principals, so the easiest way to find the correct principal is to enter the name in the search box.
Once you've selected the principal, click Add to close the blade. And then click Save to save your application's access policy.


With all that prep work done, we can go write some code!

In Visual Studio, start a new .NET Core Console app, and add the Nuget packages Microsoft.Azure.KeyVault and Microsoft.IdentityModel.Clients.ActiveDirectory to it. 

The KeyVaultClient class is what we'll use to perform operations against the vault; it has a number of different constructors depending on your scenario, the one I'm using takes an AuthenticationCallback delegate. 
KeyVaultClient vaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(Program.GetToken));


The GetToken method's signature must match what the delegate expects - three string parameters for authority, resource and scope, and, probably because I don't deal with delegates very often, this was the part of this I found the most confusing as I couldn't see where they were coming from. The answer is that they are filled in by the callback at runtime, you don't need to supply the values yourself at any point. This is also where the clientId and secret we created earlier come in. Here's a sample GetToken method.
private static async Task GetToken(string authority, string resource, string scope)
{
    ClientCredential credential = new ClientCredential(clientId, clientSecret);

    var context = new AuthenticationContext(authority, TokenCache.DefaultShared);

    var result = await context.AcquireTokenAsync(resource, credential);

    return result.AccessToken;

}


Once we have a KeyVaultClient instance, we can start to query our vault. Your goto methods are most likely to be GetSecretsAsync, GetSecretAsync and SetSecretAsync, but there's a whole raft of methods for managing keys, secrets and certificates. GetSecretAsync returns a SecretBundle, of which the most relevant property is the actual value of the secret. Calling SetSecretAsync will create a new version of a secret; this will include creating the secret if it doesn't already exist in your vault, and it will also create a new version even if you set it to the same value as it already holds (GetSecretAsync also has an overload that allows you to specify a version identifier). So putting it all together here's what our console app looks like.

class Program
{
    static string clientId = "myClientId";
    static string clientSecret = "myClientSecret";

    static async Task Main(string[] args)
    {
        KeyVaultClient vaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(Program.GetToken));

        string vaultAddress = "https://myVaultUrl";
        string secretName = "secret1";

        var secret = await vaultClient.GetSecretAsync(vaultAddress, secretName);

        Console.WriteLine($"Current secret value: {secret.Value}");

        Console.ReadLine();

    }
            
    private static async Task GetToken(string authority, string resource, string scope)
    {
        ClientCredential credential = new ClientCredential(clientId, clientSecret);

        var context = new AuthenticationContext(authority, TokenCache.DefaultShared);

        var result = await context.AcquireTokenAsync(resource, credential);

        return result.AccessToken;
    }

}
And if I refer back to my vault, you can see that the value of secret1 is indeed 'it's a secret'

So, in this blog we've seen how to create a console application that authenticates against Azure Active Directory and then reads and writes secrets to and from Azure KeyVault. 

I've created a Github repo at https://github.com/philpursglove/KeyVaultDemo with a complete solution for listing, creating, updating and deleting your KeyVault secrets.

No comments: