Authenticate to Azure DevOps as a managed identity
Recently I have been fiddling with the Azure DevOps tooling, especially playing with authentication. Yup, I know how to have fun 🤓
After a post about workload identity federation and Terraform, let me share some tips about managed identities.
If you are not familiar with managed identities, in short it allows to assign identities to Azure resources and let them authenticate without credentials. Go learn about this here.
Add a managed identity in Azure DevOps
To run the snippets shared in this post, you’ll need an Azure resource with a managed identity and shell access. For instance, I have used a virtual machine with a system assigned managed identity.
You will also need to add the managed identity as a user in your Azure DevOps organization, this can be done by following the steps here.
Once this is ready, you can connect to the resource and start by getting an access token.
Get an access token
To authenticate against any Azure DevOps tool, we need an access token. How to get one is documented in the official docs, but let me highlight two tips about this.
Using Azure CLI
The first tip is the magic guid 499b84ac-1321-427f-aa17-267ca6975798
. It’s the application id of Azure DevOps’s app registration from Microsoft’s tenant. Every Entra ID tenant (including yours) has an enterprise application (service principal) linked to this app registration, thus this guid is common to everyone.
You can verify this with following command:
az ad sp show --id 499b84ac-1321-427f-aa17-267ca6975798
and see the properties of the service principal
Using Azure CLI, we can pass this id to the --ressource
parameter of the az account get-access-token
command:
1
2
az login --identity --allow-no-subscriptions
token=$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)
By default this command returns an access token to use with Azure, but specifying the --resource
parameter like this gives a token for Azure DevOps.
The
--allow-no-subscriptions
flag passed to theaz login
command is necessary if your managed identity doesn’t have access to any Azure subscription
If you don’t want to use a guid, you can use
https://app.vssps.visualstudio.com/
for the--resource
parameter (this value comes from theservicePrincipalNames
property of the service principal).
Using authorization endpoint
Second tip, if you don’t want to use Azure CLI, there is an authorization endpoint that you can call from a VM linked to a managed identity (I haven’t tested it on another type of resource yet).
The key here is to use the resource
query string parameter with the same guid as with the Azure CLI. You can do it like this in PowerShell:
1
2
$response = Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=499b84ac-1321-427f-aa17-267ca6975798' -Header @{ Metadata = $true }
$token = ($response.Content | ConvertFrom-Json).access_token
Or from a Linux shell:
1
2
response=$(curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=499b84ac-1321-427f-aa17-267ca6975798' -H Metadata:true -s)
token=$(echo $response | jq -r '.access_token')
Now that we have an access token stored in a $token
variable, let’s see how to use it.
Use the token
Using the token is not complicated, basically what’s not clear in the official docs is that the access token can be used like a PAT (Personal Access Token).
With the CLI
When working with the Azure DevOps CLI, there are two options to authenticate:
- You can pipe the access token to the
az devops login
command, like this in PowerShell:$token | az devops login
or like this from a Linux shell:echo $token | az devops login
- Or you can set the
AZURE_DEVOPS_EXT_PAT
environment variable and that’s it ($env:AZURE_DEVOPS_EXT_PAT = $token
in PowerShell,export AZURE_DEVOPS_EXT_PAT="$token"
in a Linux shell.)
And then you can start running commands.
With the REST API
This is pretty straightforward as the access token has to be set in the Authorization
header, for instance in PowerShell (don’t forget to replace the {organization}
placeholder with your organization’s name):
1
Invoke-WebRequest -Headers @{ Authorization = "Bearer $token" } -Uri "https://dev.azure.com/{organization}/_apis/projects?api-version=7.2-preview.4"
Or from a Linux shell:
1
curl -s -H "Authorization: Bearer $token" "https://dev.azure.com/{organization}/_apis/projects?api-version=7.2-preview.4"
As a self-hosted agent
Finally, when configuring a self-hosted agent for Azure Pipelines, you can use the access token as if it was a PAT.
For a Windows agent, set the auth
and token
parameters of the config.cmd
script:
1
.\config.cmd --auth pat --token $token
For a Linux agent, same thing with the config.sh
script:
1
./config.sh --auth pat --token "$token"
Refer to the official docs for the rest of the configuration depending on your scenario.
Wrapping-up
This concludes this post about managed identity authentication and Azure DevOps. This was a short one as I don’t want to repeat what you can find in the official docs, just highlight a few tips that I think can save some time.
I hope this helps, thanks for reading 🤓