Skip to main content
Azure User-Assigned Managed Identity Federated Credentials

Azure User-Assigned Managed Identity Federated Credentials

·1047 words·5 mins
Mattias Fjellström
Author
Mattias Fjellström
Cloud architect · Author · HashiCorp Ambassador · Microsoft MVP

I am ashamed to admit that I had no idea user-assigned managed identities on Azure supported federated identity credentials. At least I had no idea up until December 9 when I was told about this during a presentation by Anton Ganhammar at the HUG event we had in Gothenburg that day.

Digging through the Azure provider for Terraform I discovered that this is indeed supported through the azurerm_federated_identity_credential resource, and that resource was added to the provider back in 2022. How have I missed this? It is a mystery!

Intrigued by the possibilities federated identity credentials comes with I wanted to try it out.

A big benefit is that the managed identity and the federated identity credential is configured solely as Azure resources, not Entra ID resources.

In this blog post I will use Terraform to configure a user-assigned managed identity with federated identity credentials. Then I will use these credentials from a GitHub Actions workflow to run Azure CLI commands. The work presented in this blog post is a different approach to achieve the same thing that I covered in an earlier blog post.

Initial configuration
#

In the following example I will build up a Terraform configuration that uses the Azure provider and the GitHub provider:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "4.56.0"
    }

    github = {
      source  = "integrations/github"
      version = "6.9.0"
    }
  }
}

provider "azurerm" {
  features {}

  subscription_id = var.azure_subscription_id
}

provider "github" {
  owner = var.github_handle
}

The Terraform configuration will need the following variables:

variable "azure_location" {
  description = "Azure location name"
  type        = string
}

variable "azure_subscription_id" {
  description = "Azure subscription ID"
  type        = string
}

variable "github_handle" {
  description = "GitHub username or organization name"
  type        = string
}

variable "github_repository" {
  description = "Existing GitHub repository name"
  type        = string
}

Configure a user-assigned managed identity with federated identity credentials
#

As with any other resource we must place a user-assigned managed identity inside a resource group. Use an existing resource group or create a new one:

resource "azurerm_resource_group" "default" {
  name     = "rg-mi-oidc"
  location = var.azure_location
}

The user-assigned managed identity is configured with a name, a location, and in which resource_group_name to place the resource:

resource "azurerm_user_assigned_identity" "default" {
  name                = "terraform"
  resource_group_name = azurerm_resource_group.default.name
  location            = var.azure_location
}

We should give the user-assigned managed identity one or more roles in our Azure subscription or at any other scope. For illustrative purposes I give my user-assigned identity the Contributor role on my subscription:

resource "azurerm_role_assignment" "contributor" {
  principal_id         = azurerm_user_assigned_identity.default.principal_id
  scope                = "/subscriptions/${var.azure_subscription_id}"
  role_definition_name = "Contributor"
}

When you configure federated identity credentials you do so for a given target platform. In my case I want to use this identity from a GitHub Actions workflow, so I must configure the federated identity credentials accordingly. Specifically:

  • The audience should include api://AzureADTokenExchange (this is technically an Azure detail, and it could be any value as long as I configure the same value on both sides - both on Azure and on GitHub)
  • The issuer should be https://token.actions.githubusercontent.com
  • The subject should be similar to repo:github-organization/repository-name:ref:refs/heads/main. The exact value depends on how you use these credentials. I will use it from my main branch in my repository.

With these details in mind I configure the federated identity credential using the azurerm_federated_identity_credential resource:

resource "azurerm_federated_identity_credential" "github" {
  name                = "github"
  resource_group_name = azurerm_resource_group.default.name
  parent_id           = azurerm_user_assigned_identity.default.id

  audience = ["api://AzureADTokenExchange"]
  issuer   = "https://token.actions.githubusercontent.com"
  subject  = "repo:${var.github_handle}/${var.github_repository}:ref:refs/heads/main"
}

My GitHub handle (or organization name) comes from the github_handle variable, and the repository name from the github_repository variable.

These credentials will work for the main branch, but not for any other branch. To support other branches I can create additional federated identity credentials.

Using the user-assigned managed identity from GitHub
#

To use the user-assigned managed identity federated identity credentials from a GitHub Actions workflow I first need to set a few GitHub Actions variables. Specifically I need my Azure subscription ID, my Azure tenant ID, and the user-assigned managed identity client ID. I can set these variables for my GitHub repository using Terraform:

data "github_repository" "default" {
  name = var.github_repository
}

resource "github_actions_variable" "arm_subscription_id" {
  repository    = data.github_repository.default.name
  variable_name = "ARM_SUBSCRIPTION_ID"
  value         = data.azurerm_client_config.current.subscription_id
}

resource "github_actions_variable" "arm_tenant_id" {
  repository    = data.github_repository.default.name
  variable_name = "ARM_TENANT_ID"
  value         = data.azurerm_client_config.current.tenant_id
}

resource "github_actions_variable" "arm_client_id" {
  repository    = data.github_repository.default.name
  variable_name = "ARM_CLIENT_ID"
  value         = azurerm_user_assigned_identity.default.client_id
}

With that in place I write my GitHub Actions workflow:

on:
  workflow_dispatch:

permissions:
  id-token: write

jobs:
  azure:
    runs-on: ubuntu-latest
    steps:
      - uses: azure/login@v2
        with:
          client-id: ${{ vars.ARM_CLIENT_ID }}
          tenant-id: ${{ vars.ARM_TENANT_ID }}
          subscription-id: ${{ vars.ARM_SUBSCRIPTION_ID }}
      - uses: azure/cli@v2
        with:
          azcliversion: latest
          inlineScript: |
            az account show

I use the azure/login@v2 action to authenticate to Azure. I pass in the client ID, tenant ID, and subscription ID. No more configuration is required to authenticate, it all works because of the federated identity credential I configured on Azure.

Then I use the azure/cli@v2 action to run an Azure CLI command. Since my user-assigned identity has the Contributor role I could in practice to anything in my Azure environment. I could also run Terraform from GitHub Actions targeting the Azure provider.

Conclusions and key-takeaways
#

As I mentioned in the introduction, a big benefit of using a managed identity with federated identity credentials is that both resources are configured using the Azure Resource Manager API and not the Graph API for Entra ID. In the configuration I only needed to use the azurerm provider (and the GitHub provider for GitHub-stuff).

Managing federated credentials as Azure resources simplifies the life of your platform teams, allowing them to manage these and have more insight into what credentials are currently configured. You do not need to provide each team with permissions to create and manage applications in Entra ID.

A drawback is that flexible federated identity credentials is not supported. In addition, you can only configure 20 federated identity credentials for a given managed identity. In practice this means you might have to manage multiple user-assigned managed identities to use more than 20 credentials.

I am still confused how this feature has escaped my knowledge during the past three years.

Related