This article is contributed. See the original author and article here.
Welcome! This post is going to cover the end-to-end process of configuring and using Workload identity federation in Azure DevOps for your Terraform deployments. We’ll cover:
- Why use Workload identity federation
- What is Workload identity federation and how does it work
- How can your organisation join the Workload identity federation preview
- How to configure Workload identity federation using the Azure DevOps Terraform provider
- How to use Workload identity federation in an Azure DevOps Pipeline with the Terraform task
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.
You can read more about how Workload identity federation works here.
In the first iteration Workload identity federation for Azure DevOps works within the scope of a Service Connection. A new type of Azure Resource Manager Service Connection is available that enables this:
Any task that supports a Service Connection and has been updated to use Workload identity federation can then use your configured trust to interact with Azure resources.
On the Azure side we need to configure Federated Credentials on an App Registration or User Assigned Managed Identity service principal. There are a few settings needed for this, which you can find in the Azure DevOps Service Connection or you can figure out up front:
Issuer URL
: This is the a URL in the formathttps://vstoken.dev.azure.com/
where organisation-id is the GUID of your Azure DevOps organisation. For examplehttps://vstoken.dev.azure.com/f66a4bc2-08ad-4ec0-a25e-e769dab3b294
.Subject identifier
: This is the mapping to your service connection in the formatsc:////
where organisation-name is your Azure DevOps organisation name, project-name is your Azure DevOps project name and service-connection-name is the name of your service connection. For examplesc://my-organisation/my-project/my-service-connection
.Audience
: This is alwaysapi://AzureADTokenExchange
.
Here is what it looks like in the Azure Portal:
Once we have the service connection and federated credentials configured, we can use the service connection to authenticate to Azure. You’ll just need to grant your service principal some permissions in the subscription.
How can your organisation join the Workload identity federation preview
The preview is open to anyone, you can input the name of your Azure DevOps organisation into this form to sign up. Once signed up, you’ll have access to the feature flag and you’ll find it under the organisation ‘Preview Features’ menu. Turn it on and you’ll see the new Azure Resource Manager Service Connection types. You can find lots more information here.
How to configure Workload identity federation using the Azure DevOps Terraform provider
The Azure DevOps Terraform provider has been updated to support the creation of Workload identity federation Service Connections. The documentation can be found here.
The following example shows how to setup a Service Connection and User Assigned Managed Identity with Federated Credentials:
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
}
The items of note are:
- We have to supply the client id of our service principal in the
credentials
block service connection, so it knows which service principal is configured with federation. - We supply the string
WorkloadIdentityFederation
in theservice_endpoint_authentication_scheme
attribute to tell it the type of service connection we want to create. - We use the
workload_identity_federation_issuer
andworkload_identity_federation_subject
outputs of our service connection to populate the federated credentials. This is a shortcut for getting this information, which you of course get from elsewhere if you prefer.
Once you run this Terraform and create these resources you’ll be ready to use the Service Connection in your Azure DevOps Pipeline.
How to use Workload identity federation in an Azure DevOps Pipeline with the Terraform task
We have updated the two most popular Terraform Tasks to support Workload identity federation, these are:
- Microsoft DevLabs Terraform: DevLabs Terraform
- Jason Johnson (formerly Charles Zipp) Azure Pipelines Terraform Tasks: Azure Pipelines Terraform Tasks
The following example shows how to use the Microsoft DevLabs task in an Azure DevOps Pipeline:
jobs:
- deployment: deploy
displayName: Deploy with Terraform
pool:
vmImage: ubuntu-latest
environment: dev
strategy:
runOnce:
deploy:
steps:
- checkout: self
displayName: Checkout Terraform Module
- task: TerraformInstaller@0
displayName: Install Terraform
inputs:
terraformVersion: 'latest'
- task: TerraformTaskV4@4
displayName: Terraform Init
inputs:
provider: 'azurerm'
command: 'init'
workingDirectory: '$(workingDirectory)'
backendServiceArm: '${{ variables.serviceConnection }}'
backendAzureRmResourceGroupName: '$(BACKEND_AZURE_RESOURCE_GROUP_NAME)'
backendAzureRmStorageAccountName: '$(BACKEND_AZURE_STORAGE_ACCOUNT_NAME)'
backendAzureRmContainerName: '$(BACKEND_AZURE_STORAGE_ACCOUNT_CONTAINER_NAME)'
backendAzureRmKey: 'terraform.tfstate'
env:
ARM_USE_AZUREAD: true # This environment variable tells the backend to use AzureAD auth rather than trying a create a key. It means we can limit the permissions applied to the storage account and container to least priviledge: https://developer.hashicorp.com/terraform/language/settings/backends/azurerm#use_azuread_auth
- task: TerraformTaskV4@4
displayName: Terraform Apply
inputs:
provider: 'azurerm'
command: 'apply'
workingDirectory: '$(workingDirectory)'
commandOptions: '-auto-approve -var="resource_group_name=$(AZURE_RESOURCE_GROUP_NAME)"'
environmentServiceNameAzureRM: '${{ variables.serviceConnection }}'
env:
ARM_USE_AZUREAD: true
Right now you are probably thinking “how is this any different to what I do now?” and you’d be right to think that because there is no difference. The Workload identity federation implementation is abstracted away into the choice of Service Connection type. The task knows based on the type of Service Connection how to authenticate to Azure and set the relevant environment variables for you.
If you want to know how you can do it yourself outside of the terraform task, you can see an example of using the Azure CLI task here and here.
Conclusion
We’ve shown you how to configure and use Workload identity federation for Azure DevOps, we want you to SIGN UP FOR THE PREVIEW and start using it right away.
If you want a more in depth example, you can refer to this sample repository.
Thanks for reading, feel free to ask questions.
Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.
Recent Comments