logo
HomeWorkBlog

AzureAD 3.7: Dynamic credentials with Flexible Federated Identity

HashiCorp’s AzureAD provider introduces a new resource in the 3.7 release: azuread_application_flexible_federated_identity_credential. This unlocks dynamic credentials using flexible claim matching, so you can scope access to specific runs, workspaces, or phases for terraform cloud.

What’s new

  • Flexible claim matching: Match on claims like sub using expressions (e.g., by organization, project, workspace, and run phase).
  • Wildcard TFC integration: Ability to wildcard within the sub.

Quick walkthrough (HCP Terraform)

The snippet below creates:

  • An Azure AD application and service principal
  • A subscription‑scoped role assignment
  • A TFC workspace to consume the credentials

Setup Azure Application

terraform {
  required_providers {
    azuread = {
      source  = "hashicorp/azuread"
      version = ">= 3.7.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 3.0.0"
    }
    tfe = {
      source  = "hashicorp/tfe"
      version = ">= 0.53.0"
    }
  }
}

provider "azurerm" {
  features {}
}

data "azuread_client_config" "current" {}
data "azurerm_subscription" "current" {}

resource "azuread_application" "tfc_application" {
  display_name = "tfc-application"
  owners       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal" "tfc_service_principal" {
  client_id = azuread_application.tfc_application.client_id
}

resource "azurerm_role_assignment" "tfc_role_assignment" {
  scope                = data.azurerm_subscription.current.id
  principal_id         = azuread_service_principal.tfc_service_principal.object_id
  role_definition_name = "Contributor"
}

variable "tfe_organization" {
  type = string
}

data "tfe_project" "default" {
  name         = "Default Project"
  organization = var.tfe_organization
}

resource "tfe_workspace" "team_1" {
  name         = "workspace-team-1"
  project_id   = data.tfe_project.default.id
  organization = var.tfe_organization
}

Enable variables in your TFC workspace

Set the required environment variables so that runs authenticate via Workload Identity:

resource "tfe_variable" "enable_azure_provider_auth" {
  workspace_id = tfe_workspace.team_1.id

  key         = "TFC_AZURE_PROVIDER_AUTH"
  value       = "true"
  category    = "env"
  description = "Enable the Workload Identity integration for Azure."
}

resource "tfe_variable" "tfc_azure_client_id" {
  workspace_id = tfe_workspace.team_1.id

  key         = "TFC_AZURE_RUN_CLIENT_ID"
  value       = azuread_application.tfc_application.id
  category    = "env"
  description = "The Azure Client ID runs will use to authenticate."
}

# Optional: override the default audience used in tokens
resource "tfe_variable" "tfc_azure_audience" {
  workspace_id = tfe_workspace.team_1.id

  key         = "TFC_AZURE_WORKLOAD_IDENTITY_AUDIENCE"
  value       = "api://AzureADTokenExchange"
  category    = "env"
  description = "The value to use as the audience claim in run identity tokens."
}

resource "tfe_variable" "arm_subscription_id" {
  workspace_id    = tfe_workspace.team_1.id

  key             = "ARM_SUBSCRIPTION_ID"
  value           = data.azurerm_subscription.current.subscription_id
  category        = "env"
}

resource "tfe_variable" "arm_tenant_id" {
  workspace_id = tfe_workspace.team_1.id

  key             = "ARM_TENANT_ID"
  value           = data.azurerm_subscription.current.tenant_id
  category        = "env"
}

Configure single flexible credential (wildcard)

Historically, Terraform Cloud runs often required separate federated identity credentials per run_phase (for example, one for plan and another for apply). With flexible federated identity credentials, you can reduce this management overhead by using the restricted expression language to wildcard the sub claim and authorize multiple phases under a single credential using the matches operator. See Microsoft’s guidance for supported issuers and operators. Microsoft Entra flexible federated identity credentials (preview)

Before: per‑phase credentials
resource "azuread_application_federated_identity_credential" "tfc_federated_credential_plan" {
  application_id = azuread_application.tfc_application.id
  display_name   = "tfc-federated-credential-plan"
  audiences      = ["api://AzureADTokenExchange"]
  issuer         = "https://app.terraform.io"
  subject        = "organization:${var.tfe_organization}:project:${data.tfe_project.default.name}:workspace:${tfe_workspace.team_1.name}:run_phase:plan"
}

resource "azuread_application_federated_identity_credential" "tfc_federated_credential_apply" {
  application_id = azuread_application.tfc_application.id
  display_name   = "tfc-federated-credential-apply"
  audiences      = ["api://AzureADTokenExchange"]
  issuer         = "https://app.terraform.io"
  subject        = "organization:${var.tfe_organization}:project:${data.tfe_project.default.name}:workspace:${tfe_workspace.team_1.name}:run_phase:apply"
}
After: Single Wildcarded Credential
resource "azuread_application_flexible_federated_identity_credential" "plan_and_apply" {
  application_id             = azuread_application.tfc_application.id
  display_name               = "tfc-federated-flexible-credential"
  audience                   = "api://AzureADTokenExchange"
  issuer                     = "https://app.terraform.io"
  claims_matching_expression = "claims['sub'] matches 'organization:${var.tfe_organization}:project:${data.tfe_project.default.name}:workspace:${tfe_workspace.team_1.name}:run_phase:*'"
}