Managing access and governance in Azure can quickly become complex without automation. Using Terraform, we can build a scalable and repeatable setup that includes identity management, access control, and policy enforcement.
In this blog, we implement:
- Azure AD Users (dynamic with for_each)
- Azure AD Groups (Admin & Tester)
- RBAC Role Assignments
- Azure Storage Account
- Azure Policy Enforcement
π§© Architecture Overview
Azure AD Users → Azure AD Groups → RBAC → Azure Resource
↑
Azure Policy
π€ Step 1: Create Users using for_each
We define multiple users using a Terraform map and dynamically create them:
variable "users" {
type = map(object({
user_principal_name = string
display_name = string
password = string
}))
}This allows scalable identity creation.
π₯ Step 2: Create Groups
We define two groups:
- Admin Group → Full access
- Tester Group → Read-only access
π Step 3: Assign Users to Groups
Each user is mapped to a group, following best practices of group-based access control.
π️ Step 4: Deploy Azure Resource
We create:
- Resource Group
- Storage Account
π Step 5: RBAC (Access Control)
Roles are assigned at the resource level:
- Admin Group → Storage Account Contributor
- Tester Group → Storage Blob Data Reader
This ensures least privilege access.
π Step 6: Azure Policy (Governance)
We enforce a policy:
✔ Only allow Standard_LRS storage accounts
"effect": "deny"This prevents non-compliant resources.
Full Code:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
}
azuread = {
source = "hashicorp/azuread"
}
}
}
provider "azurerm" {
features {}
}
provider "azuread" {}
# -------------------------------------------------
# USERS (FOREACH)
# -------------------------------------------------
variable "users" {
type = map(object({
user_principal_name = string
display_name = string
password = string
}))
default = {
user1 = {
user_principal_name = "user1_demo@eduarng.com"
display_name = "User One Demo"
password = "TempPassword@12345!"
}
user2 = {
user_principal_name = "user2_demo@eduarng.com"
display_name = "User Two Demo"
password = "TempPassword@12345!"
}
}
}
# -------------------------------------------------
# CREATE USERS
# -------------------------------------------------
resource "azuread_user" "users" {
for_each = var.users
user_principal_name = each.value.user_principal_name
display_name = each.value.display_name
password = each.value.password
force_password_change = true
}
# -------------------------------------------------
# ADMIN GROUP
# -------------------------------------------------
resource "azuread_group" "admin_group" {
display_name = "Admin-Group"
security_enabled = true
}
# -------------------------------------------------
# TESTER GROUP
# -------------------------------------------------
resource "azuread_group" "tester_group" {
display_name = "Tester-Group"
security_enabled = true
}
# -------------------------------------------------
# GROUP MEMBERSHIP
# -------------------------------------------------
resource "azuread_group_member" "user1_admin" {
group_object_id = azuread_group.admin_group.object_id
member_object_id = azuread_user.users["user1"].object_id
}
resource "azuread_group_member" "user2_tester" {
group_object_id = azuread_group.tester_group.object_id
member_object_id = azuread_user.users["user2"].object_id
}
# -------------------------------------------------
# RESOURCE GROUP
# -------------------------------------------------
resource "azurerm_resource_group" "demo_rg" {
name = "rg-aad-rbac-policy-demo"
location = "East US"
}
# -------------------------------------------------
# STORAGE ACCOUNT
# -------------------------------------------------
resource "azurerm_storage_account" "storage" {
name = "aadstoragedemo12345"
resource_group_name = azurerm_resource_group.demo_rg.name
location = azurerm_resource_group.demo_rg.location
account_tier = "Standard"
account_replication_type = "LRS"
}
# -------------------------------------------------
# RBAC FOR ADMIN GROUP
# -------------------------------------------------
resource "azurerm_role_assignment" "admin_rbac" {
scope = azurerm_storage_account.storage.id
role_definition_name = "Storage Account Contributor"
principal_id = azuread_group.admin_group.object_id
}
# -------------------------------------------------
# RBAC FOR TESTER GROUP
# -------------------------------------------------
resource "azurerm_role_assignment" "tester_rbac" {
scope = azurerm_storage_account.storage.id
role_definition_name = "Storage Blob Data Reader"
principal_id = azuread_group.tester_group.object_id
}
# -------------------------------------------------
# AZURE POLICY DEFINITION
# -------------------------------------------------
resource "azurerm_policy_definition" "storage_policy" {
name = "restrict-storage-sku-policy"
policy_type = "Custom"
mode = "All"
display_name = "Allow only Standard_LRS Storage Accounts"
policy_rule = jsonencode({
if = {
allOf = [
{
field = "type"
equals = "Microsoft.Storage/storageAccounts"
},
{
field = "Microsoft.Storage/storageAccounts/sku.name"
notEquals = "Standard_LRS"
}
]
}
then = {
effect = "deny"
}
})
}
# -------------------------------------------------
# POLICY ASSIGNMENT
# -------------------------------------------------
resource "azurerm_resource_policy_assignment" "storage_policy_assign" {
name = "storage-policy-assignment"
resource_id = azurerm_storage_account.storage.id
policy_definition_id = azurerm_policy_definition.storage_policy.id
}
π― WHAT CHANGED
✔ user1 → Admin Group
✔ user2 → Tester Group
✔ RBAC roles aligned:
- Admin → Contributor-like access
- Tester → Read-only access
π― Final Outcome
With a single Terraform file, we achieve:
✔ Identity management (Azure AD)
✔ Access control (RBAC)
✔ Resource deployment
✔ Governance enforcement (Azure Policy)
π‘ Conclusion
This setup reflects a real-world enterprise model where:
- Access is controlled via groups
- Permissions follow least privilege
- Policies enforce compliance
Using Terraform ensures everything is automated, consistent, and reusable.
No comments:
Post a Comment