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.
To access the KeyVault we will use the DefaultAzureCredential 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:
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:
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.
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 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 task.
Good luck and let me know if you have any questions!
Top comments (2)
Good one !
But storing ClientId,TenantId,ClientSecret Id etc. in code is also not always safe. Please make sure to put them in your pipeline (azure DevOps/Jenkins etc.) configuration , and let the pipeline manage them for you.
Authenticate KeyVault via Azure CLI For development and debugging purposes so there is no need to store client/tenant/secret ids in code at all
Good point, I have stored other variables in my pipelines!