Erik's Thoughts and Musings

Apple, DevOps, Technology, and Reviews

Azure Workload Identity Federation

I started working on switching out our Azure DevOps service connections to used federated workload identities. There is a good page and Video in Azure about how Workload Identity Federation works:

https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation

Basically the way it works is there is a trust that is setup between Azure DevOps and our Azure subscriptions by using these parameters and their examples:

  • Issuer URL: https://vstoken.dev.azure.com/abcdefc4-ffff-fff-...
  • Subject: sc://org/product/test-emartin-federated
  • Audience: api://AzureADTokenExchange (always)

You then tie that to a service principal in AD that will be used as the identity for doing actions in the subscription. Here is another resource about how to set it up using Terraform:

https://techcommunity.microsoft.com/t5/azure-devops-blog/introduction-to-azure-devops-workload-identity-federation-oidc/ba-p/3908687

Why use Workload identity federation? Up until now the only way to avoid storing service principal secrets for Azure DevOps pipelines was to use a self-hosted Azure DevOps agents with managed identities. Now with Workload identity federation we remove that limitation and enable you to use short-lived tokens for authenticating to Azure. This significantly improves your security posture and removes the need to figure out how to share and rotate secrets. Workload identity federation works with many Azure DevOps tasks, not just the Terraform ones we are focussing on in this article, so you can use it for deploying code and other configuration tasks. I encourage you to learn more about the supported tasks here.

What is Workload identity federation and how does it work Workload identity federation is an OpenID Connect implementation for Azure DevOps that allow you to use short-lived credential free authentication to Azure without the need to provision self-hosted agents with managed identity. You configure a trust between your Azure DevOps organisation and an Azure service principal. Azure DevOps then provides a token that can be used to authenticate to the Azure API.

Here is the terraform:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">=3.0.0"
    }
    azuredevops = {
      source = "microsoft/azuredevops"
      version = ">= 0.9.0"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azuredevops_project" "example" {
  name               = "Example Project"
  visibility         = "private"
  version_control    = "Git"
  work_item_template = "Agile"
  description        = "Managed by Terraform"
}

resource "azurerm_resource_group" "identity" {
  name     = "identity"
  location = "UK South"
}

resource "azurerm_user_assigned_identity" "example" {
  location            = azurerm_resource_group.identity.location
  name                = "example-identity"
  resource_group_name = azurerm_resource_group.identity.name
}

resource "azuredevops_serviceendpoint_azurerm" "example" {
  project_id                             = azuredevops_project.example.id
  service_endpoint_name                  = "example-federated-sc"
  description                            = "Managed by Terraform"
  service_endpoint_authentication_scheme = "WorkloadIdentityFederation"
  credentials {
    serviceprincipalid = azurerm_user_assigned_identity.example.client_id
  }
  azurerm_spn_tenantid      = "00000000-0000-0000-0000-000000000000"
  azurerm_subscription_id   = "00000000-0000-0000-0000-000000000000"
  azurerm_subscription_name = "Example Subscription Name"
}

resource "azurerm_federated_identity_credential" "example" {
  name                = "example-federated-credential"
  resource_group_name = azurerm_resource_group.identity.name
  parent_id           = azurerm_user_assigned_identity.example.id
  audience            = ["api://AzureADTokenExchange"]
  issuer              = azuredevops_serviceendpoint_azurerm.example.workload_identity_federation_issuer
  subject             = azuredevops_serviceendpoint_azurerm.example.workload_identity_federation_subject
}