Automating Azure Storage Accounts and Managed Identities with Terraform

In this example, we use Terraform to automate the creation of Azure Storage Accounts and their containers for each environment (production, pre-production, and non-production). The goal is to implement a scalable, secure, and segmented architecture where each environment is isolated and has its own User Assigned Managed Identity. This identity enables precise access control, which is essential for meeting enterprise security and compliance requirements.
Final Objective: Target Structure for Storage Accounts and Managed Identities
The final objective of this configuration is to create a structure for each environment with storage accounts and dedicated storage containers, each associated with a specific managed identity. This setup is designed to meet the following needs:
Data Security and Isolation: Each environment (production, pre-production, non-production) has its own storage account, containers, and a dedicated managed identity to isolate data and control access.
Flexibility and Scalability: Using dynamic variables and loops (
for_each), this configuration allows us to add new environments or containers easily without complex code modifications.Compliance and Best Practices: Separating resources by environment with dedicated identities aligns with security standards for access management and data isolation.
Structure and Configuration: Overview
The target structure is defined in the terraform.tfvars file, which specifies the storage account names and containers for each environment, along with the names of the managed identities:
# Storage accounts and their containers, must be ordered by environment
storage_accounts = {
"prd" = {
sa_name = "saprojectprd001"
containers = ["container-xyz-prd"]
}
"ppd" = {
sa_name = "saprojectppd001"
containers = ["container-xyz-ppd"]
}
"npd" = {
sa_name = "saprojectnpd001"
containers = ["container-xyz-dev", "container-xyz-qas", "container-xyz-sbx", "container-xyz-poc"]
}
}
# User Assigned Managed Identity
identity_names = {
"prd" = "id-project-prd-chn-001"
"ppd" = "id-project-ppd-chn-001"
"npd" = "id-project-npd-chn-001"
}
Production (prd): A storage account (
saprojectprd001) with a container for production data (container-xyz-prd) and a specific managed identity (id-project-prd-chn-001).Pre-production (ppd): A storage account (
saprojectppd001) with a container for pre-production data (container-xyz-ppd) and a specific managed identity (id-project-ppd-chn-001).Non-Production (npd): A storage account (
saprojectnpd001) with multiple containers (container-xyz-dev,container-xyz-qas, etc.) for development and testing environments and a dedicated managed identity (id-project-npd-chn-001).
This organization provides environment-specific isolation while centralizing resources for non-production environments.
Implementation in Terraform
1. Creating Storage Accounts and Containers with for_each
We use the for_each loop in Terraform to automatically deploy Storage Accounts and their containers for each environment defined in terraform.tfvars.
Deploying Storage Accounts
The following code shows the creation of storage accounts, where each environment has its own account:
resource "azurerm_storage_account" "sa_account" {
for_each = var.storage_accounts
name = each.value.sa_name
location = azurerm_resource_group.this_001.location
resource_group_name = azurerm_resource_group.this_001.name
account_tier = var.sa_account_tier
account_replication_type = var.sa_account_replication_type
public_network_access_enabled = var.sa_account_public_network_access_enabled
allow_nested_items_to_be_public = var.st_nested_public
min_tls_version = var.st_min_tls_version
queue_properties {
logging {
delete = var.st_logging_delete
read = var.st_logging_read
write = var.st_logging_write
version = var.st_logging_version
retention_policy_days = var.st_logging_retention
}
}
blob_properties {
delete_retention_policy {
days = var.st_soft_delete_retention
}
}
tags = {
Projet = var.tags_project
Facturation = var.tags_facturation
Souscription = var.tag_sub_re_chn_dig
}
}
Creating Containers in Each Storage Account
For the containers, we use flatten to combine each storage account and its containers into a single, manageable list:
locals {
storage_containers = flatten([
for env, sa in var.storage_accounts : [
for container in sa.containers : {
sa_name = sa.sa_name
container = container
env = env
}
]
])
}
resource "azurerm_storage_container" "sc_account" {
for_each = { for idx, container in local.storage_containers : "${container.sa_name}-${container.container}" => container }
name = each.value.container
storage_account_name = azurerm_storage_account.sa_account[each.value.env].name
container_access_type = "private"
depends_on = [
azurerm_storage_account.sa_account,
azurerm_private_endpoint.st_pep,
azurerm_private_dns_a_record.st-record
]
}
The for_each loop automatically deploys each container for the corresponding storage account based on the environment.
2. Creating and Assigning Managed Identities per Environment
For each environment, a User Assigned Managed Identity is created and assigned to the corresponding resources.
Defining Managed Identities
Each managed identity is created according to its name specified in identity_names:
resource "azurerm_user_assigned_identity" "this" {
for_each = var.identity_names
resource_group_name = azurerm_resource_group.this_001.name
location = azurerm_resource_group.this_001.location
name = each.value
}
Assigning Roles to Identities
Finally, we assign the necessary roles to each identity, enabling access to storage containers as required for each environment.
- Storage Blob Data Contributor: This allows each identity to access storage blobs.
resource "azurerm_role_assignment" "storage_blob_data_contributor" {
for_each = {
for env, id_name in var.identity_names : env => flatten([
for container in var.storage_accounts[env].containers : {
sa_name = var.storage_accounts[env].sa_name
container = container
}
])
}
principal_id = azurerm_user_assigned_identity.this[each.key].principal_id
scope = azurerm_storage_account.sa_account[each.key].id
role_definition_name = "Storage Blob Data Contributor"
depends_on = [
azurerm_storage_account.sa_account,
azurerm_storage_container.sc_account
]
}
Conclusion
In summary, this Terraform configuration allows us to deploy Storage Accounts and Managed Identities in an automated and segmented way for each environment. It ensures data separation, access isolation, and provides maximum flexibility to add or modify environments effortlessly. This architecture offers a robust and scalable solution for managing Azure resources and access controls.






