Published on

Key Vault integration for integration tests running in Azure DevOps

Authors
  • Name
    Markus Palme
    Twitter

Let's say your integration test needs to call an external API and that requires a secret stored in an Azure Key Vault. Outside the integration test, your code integrates Key Vault with DefaultAzureCredential, which relies on environment variables like AZURE_CLIENT_SECRET (discouraged) or az cli credentials (among other authentication methods).

An obvious solution is therefore to wrap dotnet test in an AzureCLI@2 task, which logs in with the service connection before running the script. This solution is described for example in this post by Marc Rufer. Unfortunately you lose the automatic test result publishing that the DotNetCoreCLI@2 task gives you for free. You can get it back by adding a PublishTestResults@2 step for the TRX files, but that is one more thing to maintain.

Instead there is a feature that was undocumented when I ran into it, and is still easy to miss today, that allows you to run the tests like this:

- task: DotNetCoreCLI@2
  displayName: Run Integration Tests
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)
  inputs:
    azureSubscription: 'Azure Tests'
    command: 'test'
    projects: '.../IntegrationTests.csproj'
  • azureSubscription: 'Azure Tests' tells the task to make the service connection with that name available to the process. The task exports three environment variables: AZURESUBSCRIPTION_CLIENT_ID, AZURESUBSCRIPTION_TENANT_ID and AZURESUBSCRIPTION_SERVICE_CONNECTION_ID.
  • SYSTEM_ACCESSTOKEN exposes the job's access token. The credential needs it to call the Azure DevOps OIDC endpoint and request a federated token for that service connection.

With that in place, we need a small helper method that checks the presence of the environment variables and constructs an AzurePipelinesCredential if present, otherwise falls back to the normal DefaultAzureCredential mentioned earlier:

public static TokenCredential GetAzureCredential()
{
    // Set by Azure Pipelines that want to run tests. This allows authenticating
    // against Azure Key Vault (and other Azure resources) in this execution environment.
    var systemAccessToken = Environment.GetEnvironmentVariable("SYSTEM_ACCESSTOKEN");

    // These environment variables are set by the dotnet task in azure pipelines
    // if the "azureSubscription" parameter is set to an Azure Resource Manager
    // service connection (with Federated Identity).
    var clientId = Environment.GetEnvironmentVariable("AZURESUBSCRIPTION_CLIENT_ID");
    var tenantId = Environment.GetEnvironmentVariable("AZURESUBSCRIPTION_TENANT_ID");
    var serviceConnectionId = Environment.GetEnvironmentVariable("AZURESUBSCRIPTION_SERVICE_CONNECTION_ID");

    return string.IsNullOrEmpty(systemAccessToken)
        ? new DefaultAzureCredential()
        : new AzurePipelinesCredential(tenantId, clientId, serviceConnectionId, systemAccessToken);
}

We now have to use this helper when adding the Key Vault configuration:

public static IConfigurationBuilder AddKeyVault(this IConfigurationBuilder configurationBuilder, Uri vaultUri)
{
    var credential = GetAzureCredential();
    return configurationBuilder.AddAzureKeyVault(vaultUri, credential);
}

Now you can access your Key Vault from the integration tests and don't lose any feature in the pipeline. This works because the .net CLI task supports this flow, but keep in mind that only workload identity federation is supported. There is some more documentation on the AzurePipelinesCredential on the Azure SDK blog, but it only shows the AzureCLI@2 task and says nothing about the DotNetCoreCLI@2 task or where the AZURESUBSCRIPTION_* variables come from. In fact, I had to look at the code of the DotNetCoreCLI task to see how and where it sets the required environment variables and how the pieces of the puzzle combine.

Comments

Loading comments…

Leave a comment