Terraform Patterns
Use Terraform to declare identities, role assignments, and federated credentials. Keep secrets out of variables, state outputs, pipeline variables, and logs.
Azure Verified Modules (Terraform Registry
Azure/avm-res-*) exist for the primary resource types used here (key vault, storage account, Cosmos DB account, user-assigned identity) and bundle secure defaults; prefer them for landing-zone-grade deployments, or use the primitive resources below when you need full control.
User-assigned managed identity
resource "azurerm_user_assigned_identity" "workload" {
name = "id-${var.service_name}-${var.environment_name}"
location = azurerm_resource_group.workload.location
resource_group_name = azurerm_resource_group.workload.name
}Role assignment
resource "azurerm_role_assignment" "blob_contributor" {
scope = azurerm_storage_account.storage.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = azurerm_user_assigned_identity.workload.principal_id
principal_type = "ServicePrincipal"
}Prefer role_definition_id for critical production modules when you need exact role pinning.
AKS federated identity credential
resource "azurerm_federated_identity_credential" "aks" {
name = "fic-${var.namespace}-${var.service_account_name}"
resource_group_name = azurerm_resource_group.identity.name
parent_id = azurerm_user_assigned_identity.workload.id
issuer = azurerm_kubernetes_cluster.aks.oidc_issuer_url
subject = "system:serviceaccount:${var.namespace}:${var.service_account_name}"
audience = ["api://AzureADTokenExchange"]
}GitHub Actions federated identity credential
resource "azurerm_federated_identity_credential" "github_prod" {
name = "fic-github-prod"
resource_group_name = azurerm_resource_group.identity.name
parent_id = azurerm_user_assigned_identity.deployment.id
issuer = "https://token.actions.githubusercontent.com"
subject = "repo:${var.github_org}/${var.github_repo}:environment:Production"
audience = ["api://AzureADTokenExchange"]
}Local auth posture
Provider property names can change by resource and provider version. Verify against the locked provider version, and use azapi_resource or azapi_update_resource when the AzureRM provider lacks a required property.
Examples of required posture for new projects:
resource "azurerm_storage_account" "storage" {
name = var.storage_account_name
resource_group_name = azurerm_resource_group.workload.name
location = azurerm_resource_group.workload.location
account_tier = "Standard"
account_replication_type = "LRS"
shared_access_key_enabled = false
}resource "azapi_update_resource" "cosmos_disable_local_auth" {
type = "Microsoft.DocumentDB/databaseAccounts@2024-05-15"
resource_id = azurerm_cosmosdb_account.account.id
body = {
properties = {
disableLocalAuth = true
}
}
}State and output safety
- Never output keys, SAS tokens, passwords, or credential-bearing connection strings.
- Mark any sensitive operational values as
sensitive = true, but do not treat that as adequate protection for secrets that should not exist. - Use remote state with encryption and restricted access.
- Use separate deployment identities and state workspaces per environment.