Automating Azure Storage Accounts and Managed Identities with Terraform

Search for a command to run...

No comments yet. Be the first to comment.
Summary Based on the Cloud Adoption Framework (CAF) and Well-Architected Framework (WAF), this project automates the audit of an entire Azure tenant in minutes, using AI and a sovereign environment to

Summary The N1 agent transforms how DevOps teams interact with Kubernetes infrastructures by combining conversational prompts and advanced automation, all powered by artificial intelligence. Concretel

We had the opportunity to attend Kubernetes Community Days (KCD) Suisse Romande, held at CERN in Geneva, on December 4th and 5th. These two days were full of discussions, workshops (we had the opportunity to participate to the Exoscale capture the fl...

With the recent annoncements regarding the Gateway APIs and the end of the Ingress Nginx maintenance, some aspects (historically managed by the Ingress Nginx) need to be re-adapted, we’ll shre one of those today: the TLS certificates management throu...

The CNCF has recently announced a major transition: Ingress Nginx, the “de facto” considered ingress controller, will officially reach end-of-life in March 2026. Official source here : https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/#:...

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.
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.
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.
for_eachWe use the for_each loop in Terraform to automatically deploy Storage Accounts and their containers for each environment defined in terraform.tfvars.
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
}
}
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.
For each environment, a User Assigned Managed Identity is created and assigned to the corresponding resources.
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
}
Finally, we assign the necessary roles to each identity, enabling access to storage containers as required for each environment.
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
]
}
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.