# 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:

```plaintext
# 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:

```plaintext
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:

```plaintext
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`:

```plaintext
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.
    

```plaintext
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.
