Access Azure from HCP Terraform with OIDC federation
Securely access Azure from HCP Terraform using OIDC federation, eliminating the need to use long-lived credentials for authentication.
Storing long-lived Azure credentials poses a security risk. While HCP Terraform secures sensitive credentials as write-only variables, you must audit the usage of long-lived credentials to detect if they are compromised. Many organizations have a policy to block these types of credentials.
A more secure and better alternative is available for authentication: dynamic provider credentials on HCP Terraform. This feature allows Terraform to authenticate to Azure as a service principal through a native OpenID Connect (OIDC) integration. HCP Terraform obtains temporary credentials for each run, and discards the credentials when the run completes. These credentials allow you to call Azure APIs that the service principal has access to at runtime. These credentials are short-lived by design, so their usefulness to an attacker is limited.
In this blog post, we’ll explore dynamic credentials for Azure and walk you through the required steps to set this up for yourself.
» Tutorial: Dynamic credentials for Azure
For this tutorial, you will use HCP Terraform to configure dynamic credentials for Azure by setting up a trust relationship between HCP Terraform and Azure (Entra ID). This configuration allows HCP Terraform to authenticate with Azure and obtain temporary credentials for provisioning resources.
Configuring dynamic provider credentials consists of three high-level steps:
- Set up a trust relationship between HCP Terraform and Azure.
- Configure Azure platform access.
- Configure resources on HCP Terraform to use dynamic credentials.
To follow this tutorial, you should:
- Be authenticated to both Azure and HCP Terraform in your local terminal session
- Have permissions to create an app registration and service principal in Entra ID
- Have permissions to assign Azure RBAC roles for the service principal
» Set up a trust relationship between HCP Terraform and Azure
To interact with Azure, Entra ID, and HCP Terraform you will need to use the azurerm, azuread, and tfe providers in your Terraform configuration:
terraform {
required_providers {
azuread = {
source = "hashicorp/azuread"
version = "3.0.2"
}
azurerm = {
source = "hashicorp/azurerm"
version = "4.14.0"
}
tfe = {
source = "hashicorp/tfe"
version = "0.62.0"
}
}
}
provider "azuread" {}
provider "azurerm" {
features {}
subscription_id = "xxxxxxxxxx"
}
provider "tfe" {
organization = "xxxxxxxxxx"
}
All three providers perform implicit authentication with the credentials available in the environment at runtime.
You’ll authenticate to Azure and Entra ID using the Azure CLI. For guidance on setting up the Azure CLI, refer to the Azure CLI documentation. While there are alternative methods to authenticate to Azure and Entra ID, we prefer using the Azure CLI for its simplicity. For more details, check the documentation for the Azure or Entra ID providers.
Begin by creating an application in Entra ID and a corresponding service principal:
resource "azuread_application" "hcp_terraform" {
display_name = "hcp-terraform-azure-oidc"
}
resource "azuread_service_principal" "hcp_terraform" {
client_id = azuread_application.hcp_terraform.client_id
}
Below, you’ll start by establishing an OIDC trust relationship using a federated identity credentials resource. This resource is configured with an audience (api://AzureADTokenExchange
), an issuer (https://app.terraform.io
) and a subject.
A subject has the following format that includes details from your HCP Terraform environment:
organization:<name>:project:<name>:workspace:<name>:run_phase:<operation>
Below is a specific example of a plan operation in an HCP Terraform organization named “my-organization”. Also included is an example project named "my-project" and a workspace named "my-workspace":
organization:my-organization:project:my-project:workspace:my-workspace:run_phase:plan
It is a good practice to use a federated credential for a single purpose. It's also possible to use different service principals for Terraform plan and apply operations.
In this tutorial we use a single service principal with two different federated credentials, one for plan operations and one for apply operations.
First, you need to create federated credentials for the workspace’s plan operations:
# data source to reference the current hcp terraform organization
data "tfe_organization" "current" {}
resource "azuread_application_federated_identity_credential" "plan" {
application_id = azuread_application.hcp_terraform.id
display_name = "${azuread_application.hcp_terraform.display_name}-plan"
audiences = ["api://AzureADTokenExchange"]
issuer = "https://app.terraform.io"
description = "For HCP Terraform plan operations"
subject = join(":", [
"organization",
data.tfe_organization.current.name,
"project",
tfe_project.default.name,
"workspace",
tfe_workspace.default.name,
"run_phase",
"plan"
])
}
Next, create federated credentials for the workspace’s apply operations:
resource "azuread_application_federated_identity_credential" "apply" {
application_id = azuread_application.hcp_terraform.id
display_name = "${azuread_application.hcp_terraform.display_name}-apply"
audiences = ["api://AzureADTokenExchange"]
issuer = "https://app.terraform.io"
description = "For HCP Terraform apply operations"
subject = join(":", [
"organization",
data.tfe_organization.current.name,
"project",
tfe_project.default.name,
"workspace",
tfe_workspace.default.name,
"run_phase",
"apply"
])
}
» Configure Azure platform access
The Azure service principal currently has no permissions to perform any actions on Azure.
You should provide the service principal with one or more Azure RBAC roles. These allow it to perform the actions required by the Terraform configuration where it will be used.
For the purpose of this demo, give the service principal the built-in Storage Account Contributor role to allow it to create and manage storage accounts on Azure. You will also set up a custom role to allow it to create resource groups on Azure.
Our custom Resource Group Creator role
is defined and assigned to the service principal like this:
# data source for the current azure subscription
data "azurerm_subscription" "current" {}
resource "azurerm_role_definition" "resource_group_creator" {
name = "Resource Group Creator"
scope = data.azurerm_subscription.current.id
permissions {
actions = [
"*/read",
"Microsoft.Resources/subscriptions/resourceGroups/write",
]
}
assignable_scopes = [
data.azurerm_subscription.current.id,
]
}
resource "azurerm_role_assignment" "resource_group_creator" {
scope = data.azurerm_subscription.current.id
principal_id = azuread_service_principal.hcp_terraform.object_id
role_definition_id = azurerm_role_definition.resource_group_creator.role_definition_resource_id
}
Similarly, assigning the Storage Account Contributor
role to the service principal on the Azure subscription scope is done like this:
resource "azurerm_role_assignment" "storage_account_contributor" {
scope = data.azurerm_subscription.current.id
principal_id = azuread_service_principal.hcp_terraform.object_id
role_definition_name = "Storage Account Contributor"
}
» Configure resources on HCP Terraform to use dynamic credentials
First, we create the HCP Terraform project and workspace that will use the dynamic credentials. Alternatively, feel free to use an existing project in your organization.
resource "tfe_project" "default" {
name = "demo-project"
}
resource "tfe_workspace" "default" {
name = "demo-workspace"
project_id = tfe_project.default.id
}
When using dynamic provider credentials, there’s no need to include authentication configuration in the provider block of your Terraform configuration. However, you must tell the HCP Terraform workspace that dynamic credentials should be generated.
Do this by configuring the appropriate environment variables in the workspace: set TFC_AZURE_PROVIDER_AUTH
to true
and TFC_AZURE_RUN_CLIENT_ID
to the client ID of the service principal that HCP Terraform should use for authentication during runtime.
resource "tfe_variable" "tfc_azure_provider_auth" {
key = "TFC_AZURE_PROVIDER_AUTH"
value = "true"
category = "env"
workspace_id = tfe_workspace.default.id
}
resource "tfe_variable" "tfc_azure_run_client_id" {
sensitive = true
key = "TFC_AZURE_RUN_CLIENT_ID"
value = azuread_service_principal.hcp_terraform.client_id
category = "env"
workspace_id = tfe_workspace.default.id
}
Once configured, HCP Terraform automatically retrieves temporary credentials for the service principal and injects them via the workspace environment variables, allowing you to focus on building infrastructure without the need to manage authentication.
» Using dynamic provider credentials
Now you’re ready to use the established trust relationship to provision resources on Azure.
Using dynamic provider credentials, there’s no need to define anything within the provider itself. By sharing the variable set containing the Azure service principal information with an HCP Terraform workspace, you automatically provide that workspace with access to Azure.
HCP Terraform does this by interacting with Azure (Entra ID to be specific) at runtime to obtain temporary credentials using the environment variables from the shared variable set. This allows you to securely scale access management within HCP Terraform by delegating access from one workspace to another while precisely restricting Azure access to only what the service principal needs.
Using the service principal created earlier, which has been assigned the Storage Account Contributor role and a custom Resource Group Creator role, you can begin creating infrastructure on Azure.
A sample Terraform configuration that can be deployed from HCP Terraform using the established trust relationship is shown below. Note that the subject you configured for the federated credentials must match the organization, project, and workspace names where you deploy this configuration from.
provider "azurerm" {
features {}
use_cli = false
subscription_id = "xxxxxxx"
tenant_id = "xxxxxxx"
}
resource "random_string" "suffix" {
length = 10
upper = false
special = false
}
resource "azurerm_resource_group" "default" {
name = "rg-demo-${random_string.suffix.result}"
location = "westeurope"
}
resource "azurerm_storage_account" "test" {
name = "st${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.default.name
location = azurerm_resource_group.default.location
account_tier = "Standard"
account_replication_type = "LRS"
}
» Dynamic provider credentials at scale
To scale the solution described above, we recommend a pattern where one or more HCP Terraform workspaces configure dynamic provider credentials for other workspaces. This enables the platform team to create HCP Terraform workspaces with pre-configured Azure authentication, scoped to specific service principals, per team.
Create an HCP Terraform variable set for each workspace, as described in the discussion above. The variable set for each workspace has two environment variables. These are the same two environment variables from the demo above: TFC_AZURE_PROVIDER_AUTH
and TFC_AZURE_RUN_CLIENT_ID
. These credentials are injected into the provider to grant access to any Azure API permitted by the service principal’s permissions.
Below is an example configuration of a variable set for a team (e.g. “Team A”).
First, create the variable set:
resource "tfe_variable_set" "oidc_team_a_dev" {
name = "oidc-team-a-dev"
description = "OIDC federation configuration for team A (dev)"
organization = "XXXXXXXXXXXXXXX"
}
Next, set up the required environment variables and link them to the variable set:
resource "tfe_variable" "tfc_azure_provider_auth" {
key = "TFC_AZURE_PROVIDER_AUTH"
value = "true"
category = "env"
variable_set_id = tfe_variable_set.oidc_team_a_dev.id
}
resource "tfe_variable" "tfc_azure_run_client_id" {
sensitive = true
key = "TFC_AZURE_RUN_CLIENT_ID"
value = azuread_service_principal.team_a_dev.client_id
category = "env"
variable_set_id = tfe_variable_set.oidc_team_a_dev.id
}
Finally, share the variable set with Team A by connecting it to their development workspace. This ensures that the targeted workspace receives and uses the environment variables, allowing HCP Terraform to automatically obtain and inject the temporary credentials:
resource "tfe_workspace_variable_set" "oidc_team_a_dev" {
variable_set_id = tfe_variable_set.oidc_team_a_dev.id
workspace_id = "ws-XXXXXXXXXXXXXXX"
}
Set up similar resources for each team that should have access to Azure. You will also need to configure Azure RBAC permissions for each service principal.
» Learn more about OIDC federation using Microsoft Entra ID
For more on how to securely access Azure from HCP Terraform with OIDC federation, check out Use dynamic credentials with the Azure provider and the OIDC federation documentation. Find a more complete example of configuring the Azure OIDC identity provider on GitHub.
Sign up for the latest HashiCorp news
More blog posts like this one
Enabling fast, safe migration to HCP Terraform with Terraform migrate (tf-migrate)
There’s a faster, safer way to migrate your infrastructure state files from Terraform Community Edition to HCP Terraform and Terraform Enterprise.
Fannie Mae’s process for developing policy as code with Terraform Enterprise and Sentinel
Learn how to implement the policy as code development lifecycle used in the highly regulated cloud environments at Fannie Mae.
New Terraform integrations with Crowdstrike, Datadog, JFrog, Red Hat, and more
12 new Terraform integrations from 9 partners provide more options to automate and secure cloud infrastructure management.