Workload identity federation for Azure deployments is now generally available

Eric van Wijk

In September, we announced the ability to configure Azure service connections that do not need a secret. Azure service connections that use workload identity federation are easier to manage and more secure. Many customers have adopted this feature and we’re excited to announce it is now generally available!

Image oidc collaboration

Improved security

Workload identity federation enforces how an identity can be used. The federation subject (sc://<org>/<project>/<service connection name>) configured on the App Registration or Managed Identity can only be used in Azure DevOps, by the service connection the federation is configured for. This provides a stricter constraint than a secret, which could unintentionally be leaked and used for other purposes or from other locations.

No more worries about expiring secrets

Configuration of an Azure service connection with workload identity federation is a one-time setup. You don’t have to worry about expiring secrets that have to be rotated in order for the service connection to stay operational.

Getting started

If you haven’t used Workload identity federation yet, you can take advantage of worry-free Azure service connections in the following ways:

To create a new Azure service connection using workload identity federation, select Workload identity federation (automatic) in the Azure service connection creation experience:

To convert a previously created Azure service connection created with a secret, select the “Convert” action after selecting the connection:

To convert multiple service connections that use a secret to use workload identity federation instead, you can use the below script as a basis:

#!/usr/bin/env pwsh
<# 
.SYNOPSIS 
    Convert multiple Azure Resource Manager service connection(s) to use Workload identity federation

.LINK
    https://aka.ms/azdo-rm-workload-identity-conversion

.EXAMPLE
    <script> -Project <project> -OrganizationUrl https://dev.azure.com/<organization>
#> 
#Requires -Version 7.3

param ( 
    [parameter(Mandatory=$true,HelpMessage="Name of the Azure DevOps Project")]
    [string]
    [ValidateNotNullOrEmpty()]
    $Project,

    [parameter(Mandatory=$true,HelpMessage="Url of the Azure DevOps Organization")]
    [uri]
    [ValidateNotNullOrEmpty()]
    $OrganizationUrl
) 
$apiVersion = "7.1"

#-----------------------------------------------------------
# Log in to Azure
az account show -o json 2>$null | ConvertFrom-Json | Set-Variable account
if (!$account) {
    az login --allow-no-subscriptions -o json | ConvertFrom-Json | Set-Variable account
}
$OrganizationUrl = $OrganizationUrl.ToString().Trim('/')

#-----------------------------------------------------------
# Retrieve the service connection
$getApiUrl = "${OrganizationUrl}/${Project}/_apis/serviceendpoint/endpoints?authSchemes=ServicePrincipal&type=azurerm&includeFailed=false&includeDetails=true&api-version=${apiVersion}"
az rest -u $getApiUrl -m GET --resource 499b84ac-1321-427f-aa17-267ca6975798 --query "sort_by(value[?authorization.scheme=='ServicePrincipal' && data.creationMode=='Automatic' && !(isShared && serviceEndpointProjectReferences[0].projectReference.name!='${Project}')],&name)" -o json `
        | Tee-Object -Variable rawResponse | ConvertFrom-Json | Tee-Object -Variable serviceEndpoints | Format-List | Out-String | Write-Debug
if (!$serviceEndpoints -or ($serviceEndpoints.count-eq 0)) {
    Write-Warning "No convertible service connections found"
    exit 1
}

foreach ($serviceEndpoint in $serviceEndpoints) {
    # Prompt user to confirm conversion
    $choices = @(
        [System.Management.Automation.Host.ChoiceDescription]::new("&Convert", "Converting service connection '$($serviceEndpoint.name)'...")
        [System.Management.Automation.Host.ChoiceDescription]::new("&Skip", "Skipping service connection '$($serviceEndpoint.name)'...")
        [System.Management.Automation.Host.ChoiceDescription]::new("&Exit", "Exit script")
    )
    $prompt = $serviceEndpoint.isShared ? "Convert shared service connection '$($serviceEndpoint.name)'?" : "Convert service connection '$($serviceEndpoint.name)'?"
    $decision = $Host.UI.PromptForChoice([string]::Empty, $prompt, $choices, $serviceEndpoint.isShared ? 1 : 0)

    if ($decision -eq 0) {
        Write-Host "$($choices[$decision].HelpMessage)"
    } elseif ($decision -eq 1) {
        Write-Host "$($PSStyle.Formatting.Warning)$($choices[$decision].HelpMessage)$($PSStyle.Reset)"
        continue 
    } elseif ($decision -ge 2) {
        Write-Host "$($PSStyle.Formatting.Warning)$($choices[$decision].HelpMessage)$($PSStyle.Reset)"
        exit 
    }

    # Prepare request body
    $serviceEndpoint.authorization.scheme = "WorkloadIdentityFederation"
    $serviceEndpoint.data.PSObject.Properties.Remove('revertSchemeDeadline')
    $serviceEndpoint | ConvertTo-Json -Depth 4 -Compress | Set-Variable serviceEndpointRequest
    $putApiUrl = "${OrganizationUrl}/${Project}/_apis/serviceendpoint/endpoints/$($serviceEndpoint.id)?operation=ConvertAuthenticationScheme&api-version=${apiVersion}"

    # Convert service connection
    az rest -u $putApiUrl -m PUT -b $serviceEndpointRequest --headers content-type=application/json --resource 499b84ac-1321-427f-aa17-267ca6975798 -o json `
            | ConvertFrom-Json | Set-Variable updatedServiceEndpoint

    $updatedServiceEndpoint | ConvertTo-Json -Depth 4 | Write-Debug
    if (!$updatedServiceEndpoint) {
        Write-Debug "Empty response"
        Write-Error "Failed to convert service connection '$($serviceEndpoint.name)'"
        exit 1
    }
    Write-Host "Successfully converted service connection '$($serviceEndpoint.name)'"
}

Resources

6 comments

Leave a comment

    • Eric van WijkMicrosoft employee 3

      Hi Alexandre, we will update those once the tool involved (AzCopy in this case) supports workload identity federation.

      UPDATE: The AzureFileCopy@6 task supports Workload identity federation.

  • David Corrigan 1

    What’s the timeline for allowing other service connection types to leverage OIDC tokens?

  • FelixTest1 1

    What’s the ETA to add support for Workload identity federation for Azure Container Registry to Docker Registry Service Connections?

  • Jan Steenbeek 0

    This is a very useful capability for us when deploying infrastructure and applications for our customers, thanks!

    I’m wondering if you have any thoughts on ways to mitigate the persistence of the permissions granted through this federated identity? Since the deployment identities often require some pretty big azure permissions, up to User Access Administrator in infra deployments, our customers require a JIT model. We would like to be able to use PIM to be able to request the permissions temporarily, but this is not available for service principals or managed identities.

    We currently work with manually updated group membership for these identities, but this is not ideal.

    • Eric van WijkMicrosoft employee 1

      Thanks for the suggestion. We wil take JIT models into consideration.

Feedback usabilla icon