Skip to content

IDToken - Azure

Azure supports federation with external identity providers through a feature called Federated Credentials. Using this, you can allow identities managed by an external identity provider to perform actions in your Azure subscription.

In this scenario the external identity provider is Concourse and the identities are teams/pipelines/jobs. This means you are able to grant a specific pipeline or job permission to perform actions in Azure (like deploying something), all without managing service principal secrets or dealing with long-lived credentials.

Create App Registration

First, create an EntraID App Registration along with the service principal that represents it. This app registration and its service principal are what your pipeline will act as once it exchanges a Concourse-issued JWT for an Azure token.

terraform {
  required_providers {
    azuread = {
      source  = "hashicorp/azuread"
      version = "3.0.2"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "4.15.0"
    }
  }
}

provider "azurerm" {
  features {}
}

variable "azure_concourse_url" {
  description = "Concourse URL"
  type        = string

  default = "https://ci.concourse-ci.org"
}

resource "azuread_application_registration" "pipeline_app" {
  display_name = "concourse-deploy"
}

resource "azuread_service_principal" "pipeline_sp" {
  client_id = azuread_application_registration.pipeline_app.client_id
}

Create Federated Credential

Next, create a federated credential for the app registration you just created.

  1. Set issuer to the external URL of your Concourse server (or the --oidc-issuer-url if you're using a separate OIDC issuer - see Configuring a Separate OIDC Issuer).
  2. Set subject to main/deploy-to-azure. If you use the subject_scope setting to change the contents of your sub-claim, change this value here accordingly.
  3. For audiences, you can choose any string you like, but using a value like api://AzureADTokenExchange is recommended.

Note

You have to use the same string later in the configuration of your idtoken var source.

resource "azuread_application_federated_identity_credential" "concourse_federation" {
  application_id = azuread_application_registration.pipeline_app.id
  display_name   = "concourse-oidc"
  description    = "Federated credential trusting JWTs issued by Concourse"

  issuer  = var.azure_concourse_url
  subject = "main/deploy-to-azure"
  audiences = [
    "api://AzureADTokenExchange"
  ]
}

Info

This will allow ONLY that specific pipeline (and any instanced versions of it) to obtain a token for this identity using a JWT.

Assign Role Permissions

Now, assign the identity of the app registration an RBAC role that defines what it's allowed to do in your Azure subscription.

data "azurerm_resource_group" "target" {
  name = "example-resources"
}

resource "azurerm_role_assignment" "pipeline_access" {
  scope                = data.azurerm_resource_group.target.id
  role_definition_name = "Contributor"
  principal_id         = azuread_service_principal.pipeline_sp.object_id
}

Use within the Pipeline

Your pipeline can now use the az CLI to log in to Azure using a JWT issued by Concourse, via az login's federated-token support:

var_sources:
  - name: azuretoken
    type: idtoken
    config:
      audience: [ "api://AzureADTokenExchange" ]

jobs:
  - name: azure-deploy
    plan:
      - task: login
        config:
          platform: linux
          image_resource:
            type: registry-image
            source: { repository: mcr.microsoft.com/azure-cli }
          run:
            path: bash
            args:
              - -e
              - -c
              - |
                az login --service-principal \
                  -u <client_id of your app registration> \
                  --tenant <tenant_id of your app registration> \
                  --federated-token ((azuretoken:token))
                echo "You are now authenticated with Azure. Do something with it!"