Store your automation credentials in Azure KeyVault
Tags: azure, testing, keyvault, dotnet
Published: 2021-09-24 18:47:08

Note: updated on 4/30/24 with a more complete solution

There's a common problem storing login credentials for automation that runs in Production or near-Production (aka Stage), where do you store them? You absolutely shouldn't check them into source code! I guess that you could encrypt them somehow but a better solution is to store them in Azure KeyVault and pull them out when you need them. So how can you do this? 

Let's consider the two locations where automation will run:

1) In an IDE like Visual Studio (aka local) when developing tests.
2) In a pipeline like Azure DevOps to run tests on a schedule.

They both represent different authentication challenges. First let's tackle the local Visual Studio scenario.

In Azure create a new KeyVault for your test credentials and create a couple of secrets that represent a username and password. The secrets could also represent a ClientId and ClientSecret if you're creating tokens.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9cik4ovejzboi8c2f8r.png)
  
To access the KeyVault we will use the [DefaultAzureCredential](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet) class. This class provides an authentication workflow and tries different authentication methods automatically. In our case the class will use *VisualStudioCredential* to access the KeyVault from our local machine. To enable this option you need to sign in to Visual Studio with your Azure account at Tools/Options/Azure Service Authentication/Account Selection:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f37zmuiuqqaxuegesh9c.png)

To get the KeyVault values you need to add the following packages to your code to support using DefaultAzureCredential:

*using Azure.Identity;*
*using Azure.Security.KeyVault.Secrets;*

Here's some sample code to get the KeyVault data. We check if our tests are running locally using a bool to get the VS edition. Then if we are running local we create a new *SecretClient* using *DefaultAzureCredential* and we query the KeyVault for the secrets we're looking for. 

```
// Check if automation is running in VS or from cmd line (aka pipeline)
var isRunningInVS = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("VisualStudioEdition"))

// Get KeyVault Uri and credential for local run
string kvUri = "https://myKeyVault.vault.azure.net";
var clientKV = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());

var values = new List<KeyValuePair<string, string>>();
values.Add(new KeyValuePair<string, string>("grant_type", _GrantType));                

// Get API Credentials
if (authMode == AuthMode.Candidates)
{
    if (isRunningInVS)
    {
        clientKV = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
        _ClientId = (await clientKV.GetSecretAsync(ClientIdName)).Value.Value;
        _ClientSecret = (await clientKV.GetSecretAsync(ClientSecretName)).Value.Value;
    }

    values.Add(new KeyValuePair<string, string>("client_id", _ClientId));
    values.Add(new KeyValuePair<string, string>("client_secret", _ClientSecret));
    values.Add(new KeyValuePair<string, string>("audience", _AudiencePayor));
}
```
You now can securely pull data from KeyVault when running tests locally. 

The second challenge is how does automation pull credential data when the same tests are running in a pipeline? In the case of using Azure DevOps you can store your credentials securely in Azure Library Variable Groups.

Create a Library Variable Group specific to your environment. The below example shows one for Development. You can then add your credential values and mask out any critical values such as ClientSecret:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6locrffgsmbd4l1iz3ak.png)

Note that Library Variable Groups do have an option to directly link to your KeyVault but at this time I didn't have permissions to get it to work correctly.

Next is to access the Variable Group values in your pipeline yaml. Under the *variables* section, specify the *group* value as the name of your ADO Library Variable Group. When running the pipeline it will have access to the data in the variable group. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0tnepq8xx6a639f7x1tb.png)

Note in the above example *$(AuthorizationClientId)* is the value that is pulled from the variable group and assigned to the yaml variable named *Authorization.ClientId"*. You now have secure access to your credentials in your pipeline.

There's a few other pipeline steps that I won't go over in detail but here's a few tips:

Tip1: All of your variables for your automation should be stored in an appSettings.json file that is typically read during startup using the [ConfigurationBuilder] (https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.configurationbuilder?view=net-8.0&viewFallbackFrom=dotnet-plat-ext-8.0) class. Remember not to store credentials in these files!

Tip2: At pipeline runtime you can substitute variables like we just created from the Library Variable Groups above using the [FileTransform@1](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/file-transform-v1?view=azure-pipelines) task.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qgvh9jqyv8sh5ep7yreb.png)

Good luck and let me know if you have any questions!