Azure Devops - Managed Identity for Automation Tests
Tags: devops, azure, csharp, testing
Published: 2021-10-06 17:19:36

You're writing some integration tests and as part of doing so you need to check on some Azure Resources - perhaps look in a database, check service bus messages - you need access to some Azure infrastructure to assert that the expected things have happened. When running from Azure Devops, you can take advantage of a form of Managed Identity and avoid any connection strings in your test code

## Azure Devops

If you're writing Devops Pipelines, you can setup Service Connections in your project which [give you access to Azure subscriptions](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops). A service connection to Azure from Devops is associated with a Service Principal Name (SPN). 

Once you have a connection and SPN, your YAML pipelines can use this to authenticate with Azure when running certain tasks. A sneaky way of achieving identity management is possible that's really useful for integration testing.

## Testing task

For this to work, your test project needs to be a dotnet project and the tests should be able to execute by using `dotnet test`.

Within the test project, I'm going to assume you want to do a few things. 

* Have different configurations, depending on the environment you're testing & connect to different Azure resources, depending on the environment. 

* Inherit the Security context of the Service Connection, so no need to have any connection strings in your test pack. 

We can execute `dotnet test` and wrap it up with an AzureCLI task, passing in the service connection and environment details, like so

![YAML test task](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wclph5mqy2rb5vj9k695.png)

By doing this, we get some goodness for free. The AzureCLI task will authenticate with the service connection and obtain a token before running your script, and will perform tidy up afterwards - we get a form of token management for us. 

By passing in `ASPNETCORE_ENVIRONMENT` we can take decisions in code to [load up local settings files](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-5.0) and build our configuration values based on a various sources. You can then have appsettings files for the different environments, allowing you to specify different resources to interact with

```csharp
private IConfigurationRoot GetConfiguration(string outputPath)
{
    // Read the ASPNETCORE_ENVIRONMENT variable we passed in 
    var envName = EnvironmentData.AspNetCoreEnvironment; 

    return new ConfigurationBuilder()
        .SetBasePath(outputPath)
        .AddJsonFile("appsettings.json", true)
        .AddJsonFile($"appsettings.{envName}.json", true)
        .AddEnvironmentVariables()
        .Build();
}
```

OK - so you've authenticated with Azure by using the AzureCLI task - but how can you **use** the token in your code?

## Test Pack

This is the nice part & Microsoft have made this really easy for us with the [DefaultAzureCredential](https://docs.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet) class from the Azure.Identity package. As described, the class will check a number of places in order to try and obtain security context, and because we've authenticated by the AzureCLI task we'll get the token for our Service Connection. Nice!

In code, will look like so.

```csharp
private async Task<AccessToken> GetAccessToken()
{
    var dac = new DefaultAzureCredential(
        new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true });
        
    return await dac.GetTokenAsync(
        new Azure.Core.TokenRequestContext(new[] { $"https://management.azure.com/.default" }));
}
```
NB: I found I had to [ExcludeSharedTokenCacheCredential](https://github.com/Azure/azure-sdk-for-net/issues/17052#issuecomment-729998843) for consistent results. 

Once you're at this point, you'll find many of the Azure SDKs will have entry points that allow you to use the class directly. You can pass an instance of DefaultAzureCredential in to class - see [here for some examples](https://yourazurecoach.com/2020/08/13/managed-identity-simplified-with-the-new-azure-net-sdks/).

It's now just a case of making sure the SPN you're using has permissions to the things you want to integration test with. You might need to [perform some role assignments](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=current) to set these permissions up. 

## Wrapping it up

By setting up an SPN and service connection, we can execute our tests using the AzureCLI task, which will authenticate with azure and make a token available that we can then pick up from a c# test pack by using DefaultAzureCredential. 

*This is really powerful* - as it means that when running the tests locally, you can either have the tests run under your security context as your identity, or you can provide environment variables and acquire the context of an SPN. No change of code, and your test pack is completely portable and will happily run in Windows or Linux environments. 

This means we don't have any connection strings in our test solutions & we're using Access tokens and RBAC in Azure, which keeps configuration management simple and takes advantage of a consistent Azure security architecture. 

This is simple to get going and I found it really made our test interactions with Azure easy to manage.