During my last project I needed to run some integration test written in .Net Core 2.2 in an Azure Devops Pipeline.
The code needed some secrets from an Azure KeyVault and doing some other stuff on other Azure Resources using Azure Managed Identities for authentication on them.
In .Net Core you can easily accomplish this using the AppAuthentication Nuget library.
Here an example how to use this library for getting secrets etc.. from the KeyVault.
To run this code you need an Azure infrastructure where Managed Identities is enabled, like a VM, Azure Web App/Function App etc….
So, how can you run this code on Azure DevOps agents?
Let’s begin with the easy ones: hosted agents don’t have Azure Identities, so you would say it will never work. But reading the documentation about the AppAuthentication package I discovered the following sentence :
"For local development,
AzureServiceTokenProviderfetches tokens using Visual Studio, Azure command-line interface (CLI), or Azure AD Integrated Authentication. Each option is tried sequentially and the library uses the first option that succeeds. If no option works, an
AzureServiceTokenProviderExceptionexception is thrown with detailed information."
To make it work I used the Azure CLI task in ADO, using this task the AppAuthentication library will get implicitly the token and do the work. The only thing you must assure is that the ADO Azure Endpoint selected has the rights to do what is needed in the .Net code to run (like access policies to the KeyVault or other Azure Services).
To run the .NET code from the task I just put an inline script like this: “dotnet run <your CSPROJ file path>”
With Private agents we are most of the time more in control, unless the agents are created and maintained by a separate department, then maybe you’re not able to change the VM/Agent settings, but also for this there’s a solution, we will see soon.
For this blog article I make a difference between 3 kind of Private Agents:
- Azure VM/Agents without MSI
- Azure VM with MSI enabled and the identity has enough RBAC rights to do in Azure what you need
- Azure VM with MSI enabled but the identity is without enough RBAC rights and you can’t change them (or the department doesn’t want to)
Agents without MSI
This one is easy, the same rules and constraints as the Hosted Agents apply. Read from there to go further.
Azure VM with MSI enabled and the identity has enough rights
This one are slightly different from the rest. The AppAuthentication library will just run under the Agent’s MSI identity.
In this case you don’t need to run the code inside Azure CLI task, but just in the .NET Core CLI Task.
Azure VM with MSI enabled but the identity is without enough rights
This was the situation where it all started for me. I had an Agent with MSI enabled (an Azure VM) and this machine was managed from a separate department. The agents on the machine were shared with other teams, so changing the VM’s MSI identity or modifying its rights could have some consequences for other teams, and sharing a MSI with to much rights will give other teams access to resources they’re not allowed to.
I started to implement the solution I described with the Hosted Agents, but I was still getting (authorisation) errors. After some debugging, I discovered that even running in the Azure CLI the AppAuthentication library was using the Agent/VM MSI’s identity to access the Azure Resources.
Reading more in depth the library’s AppAuthentication documentation I discovered there was a way to bypass this behaviour, aka LocalDevelopment, where I can force the library to use the Azure CLI token.
I had to create a variable in the pipeline:
variables: AzureServicesAuthConnectionString: RunAs=Developer;DeveloperTool=AzureCli
And then provide to the Azure CLI task the environment setting AzureServicesAuthConnectionString, like:
- task: AzureCLI@1 inputs: azureSubscription: 'MyAzureSubscription' scriptLocation: 'inlineScript' inlineScript: 'dotnet test Integration.Tests/Integration.Tests.csproj --configuration Release' env: AzureServicesAuthConnectionString: $(AzureServicesAuthConnectionString)
After this, the .NET Code was running under the Azure CLI endpoint’s identity and everything was working fine!
Note: the endpoint should have enough Azure RBAC rights to do what you need in your .NET code to access your Azure Resources.
Here’s an example of my YAML pipeline:
stages: - stage: IntegrationTests jobs: - job: Test pool: name: YourPoolName variables: AzureServicesAuthConnectionString: RunAs=Developer;DeveloperTool=AzureCli steps: - task: AzureCLI@1 inputs: azureSubscription: 'MyAzureSubscription' scriptLocation: 'inlineScript' inlineScript: 'dotnet test Integration.Tests.csproj --configuration Release --logger trx' env: AzureServicesAuthConnectionString: $(AzureServicesAuthConnectionString) - task: PublishTestResults@2 inputs: testRunner: VSTest testResultsFiles: '**/*.trx'