Skip to content

Project 6: Governance Framework ​

Overview ​

Implement an enterprise governance framework using Management Groups, Azure Policy, RBAC, and Cost Management. This project covers organizational hierarchy, compliance enforcement, and cost control.

Difficulty: Intermediate
Duration: 3-4 hours
Cost: Free (policies and RBAC are free)

Architecture Diagram ​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         MANAGEMENT GROUP HIERARCHY                               β”‚
β”‚                                                                                  β”‚
β”‚                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                               β”‚
β”‚                          β”‚   Root Management     β”‚                               β”‚
β”‚                          β”‚   Group (Tenant)      β”‚                               β”‚
β”‚                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                               β”‚
β”‚                                      β”‚                                           β”‚
β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”‚
β”‚              β”‚                       β”‚                       β”‚                   β”‚
β”‚              β–Ό                       β–Ό                       β–Ό                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚
β”‚  β”‚   mg-platform     β”‚  β”‚   mg-landing-zones β”‚  β”‚   mg-sandbox      β”‚           β”‚
β”‚  β”‚   (Platform)      β”‚  β”‚   (Workloads)      β”‚  β”‚   (Dev/Test)      β”‚           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚
β”‚            β”‚                      β”‚                                              β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”                                      β”‚
β”‚   β”‚                 β”‚    β”‚               β”‚                                       β”‚
β”‚   β–Ό                 β–Ό    β–Ό               β–Ό                                       β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                               β”‚
β”‚ β”‚mg-identityβ”‚ β”‚mg-managementβ”‚ β”‚mg-prodβ”‚ β”‚mg-nonprodβ”‚                            β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                               β”‚
β”‚                                                                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                            POLICY ASSIGNMENTS                                    β”‚
β”‚                                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                     ROOT MANAGEMENT GROUP POLICIES                         β”‚  β”‚
β”‚  β”‚                                                                            β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚  β”‚
β”‚  β”‚  β”‚ Allowed          β”‚  β”‚ Require Resource β”‚  β”‚ Audit VMs        β”‚        β”‚  β”‚
β”‚  β”‚  β”‚ Locations        β”‚  β”‚ Tags             β”‚  β”‚ Without Backup   β”‚        β”‚  β”‚
β”‚  β”‚  β”‚ (East US,        β”‚  β”‚ (Environment,    β”‚  β”‚                  β”‚        β”‚  β”‚
β”‚  β”‚  β”‚  West US 2)      β”‚  β”‚  Owner, Project) β”‚  β”‚                  β”‚        β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                     PRODUCTION POLICIES (Stricter)                         β”‚  β”‚
β”‚  β”‚                                                                            β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚  β”‚
β”‚  β”‚  β”‚ Require Private  β”‚  β”‚ Deny Public IP   β”‚  β”‚ Require TLS 1.2  β”‚        β”‚  β”‚
β”‚  β”‚  β”‚ Endpoints        β”‚  β”‚ on VMs           β”‚  β”‚ on Storage       β”‚        β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                     SANDBOX POLICIES (Relaxed)                             β”‚  β”‚
β”‚  β”‚                                                                            β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                               β”‚  β”‚
β”‚  β”‚  β”‚ Auto-Delete      β”‚  β”‚ Limit VM SKUs    β”‚                               β”‚  β”‚
β”‚  β”‚  β”‚ After 30 Days    β”‚  β”‚ (B-series only)  β”‚                               β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                               β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                RBAC MODEL                                        β”‚
β”‚                                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                     CUSTOM ROLES                                           β”‚  β”‚
β”‚  β”‚                                                                            β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚  β”‚
β”‚  β”‚  β”‚ Cost Manager     β”‚  β”‚ Network Operator β”‚  β”‚ VM Operator      β”‚        β”‚  β”‚
β”‚  β”‚  β”‚ (View costs,     β”‚  β”‚ (Manage NSGs,    β”‚  β”‚ (Start/Stop VMs, β”‚        β”‚  β”‚
β”‚  β”‚  β”‚  set budgets)    β”‚  β”‚  route tables)   β”‚  β”‚  no delete)      β”‚        β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                     ROLE ASSIGNMENTS                                       β”‚  β”‚
β”‚  β”‚                                                                            β”‚  β”‚
β”‚  β”‚  Platform Team     β†’ Owner @ mg-platform                                   β”‚  β”‚
β”‚  β”‚  Security Team     β†’ Security Reader @ Root MG                            β”‚  β”‚
β”‚  β”‚  Dev Team          β†’ Contributor @ mg-nonprod, Reader @ mg-prod           β”‚  β”‚
β”‚  β”‚  Finance Team      β†’ Cost Manager @ Root MG                               β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                            COST MANAGEMENT                                       β”‚
β”‚                                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                         BUDGETS                                            β”‚  β”‚
β”‚  β”‚                                                                            β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚  β”‚
β”‚  β”‚  β”‚ Monthly Prod     β”‚  β”‚ Monthly NonProd  β”‚  β”‚ Monthly Sandbox  β”‚        β”‚  β”‚
β”‚  β”‚  β”‚ Budget: $10,000  β”‚  β”‚ Budget: $5,000   β”‚  β”‚ Budget: $1,000   β”‚        β”‚  β”‚
β”‚  β”‚  β”‚                  β”‚  β”‚                  β”‚  β”‚                  β”‚        β”‚  β”‚
β”‚  β”‚  β”‚ Alerts: 50%,     β”‚  β”‚ Alerts: 50%,     β”‚  β”‚ Alerts: 80%      β”‚        β”‚  β”‚
β”‚  β”‚  β”‚ 75%, 90%, 100%   β”‚  β”‚ 75%, 90%         β”‚  β”‚ Action: Delete   β”‚        β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What You'll Learn ​

  • Design Management Group hierarchy
  • Create and assign Azure Policies
  • Implement custom RBAC roles
  • Configure budgets and cost alerts
  • Set up resource locks
  • Implement tag governance

Prerequisites ​

  • Azure subscription with Owner access
  • Understanding of Azure RBAC concepts
  • Azure CLI installed

Phase 1: Create Management Group Hierarchy ​

Step 1.1: Create Management Groups ​

bash
# Get tenant root management group ID
TENANT_ID=$(az account show --query tenantId -o tsv)

# Create Platform management group
az account management-group create \
  --name "mg-platform" \
  --display-name "Platform"

# Create Landing Zones management group
az account management-group create \
  --name "mg-landing-zones" \
  --display-name "Landing Zones"

# Create Sandbox management group
az account management-group create \
  --name "mg-sandbox" \
  --display-name "Sandbox"

echo "Top-level management groups created"

Step 1.2: Create Child Management Groups ​

bash
# Create child groups under Platform
az account management-group create \
  --name "mg-identity" \
  --display-name "Identity" \
  --parent "mg-platform"

az account management-group create \
  --name "mg-management" \
  --display-name "Management" \
  --parent "mg-platform"

# Create child groups under Landing Zones
az account management-group create \
  --name "mg-prod" \
  --display-name "Production" \
  --parent "mg-landing-zones"

az account management-group create \
  --name "mg-nonprod" \
  --display-name "Non-Production" \
  --parent "mg-landing-zones"

echo "Child management groups created"

Step 1.3: Move Subscription to Management Group ​

bash
# Get your subscription ID
SUBSCRIPTION_ID=$(az account show --query id -o tsv)

# Move subscription to Non-Production (for lab)
az account management-group subscription add \
  --name "mg-nonprod" \
  --subscription $SUBSCRIPTION_ID

# Verify
az account management-group subscription show \
  --name "mg-nonprod" \
  --subscription $SUBSCRIPTION_ID

Phase 2: Create Azure Policies ​

Step 2.1: Create Resource Group for Policy Testing ​

bash
RG_NAME="rg-governance-lab"
LOCATION="eastus"

az group create \
  --name $RG_NAME \
  --location $LOCATION \
  --tags Environment=Lab Project=Governance

Step 2.2: Assign Built-in Policies ​

Policy: Allowed Locations

bash
# Get policy definition ID
POLICY_DEF=$(az policy definition list \
  --query "[?displayName=='Allowed locations'].name" -o tsv)

# Assign at management group level
az policy assignment create \
  --name "allowed-locations" \
  --display-name "Allowed Locations" \
  --scope "/providers/Microsoft.Management/managementGroups/mg-landing-zones" \
  --policy $POLICY_DEF \
  --params '{"listOfAllowedLocations": {"value": ["eastus", "westus2", "centralus"]}}'

echo "Allowed locations policy assigned"

Policy: Require Tag on Resources

bash
# Assign require tag policy
az policy assignment create \
  --name "require-environment-tag" \
  --display-name "Require Environment Tag" \
  --scope "/providers/Microsoft.Management/managementGroups/mg-landing-zones" \
  --policy "871b6d14-10aa-478d-b590-94f262ecfa99" \
  --params '{"tagName": {"value": "Environment"}}'

az policy assignment create \
  --name "require-owner-tag" \
  --display-name "Require Owner Tag" \
  --scope "/providers/Microsoft.Management/managementGroups/mg-landing-zones" \
  --policy "871b6d14-10aa-478d-b590-94f262ecfa99" \
  --params '{"tagName": {"value": "Owner"}}'

echo "Tag policies assigned"

Step 2.3: Create Custom Policy Definition ​

Policy: Require TLS 1.2 on Storage Accounts

bash
cat > policy-require-tls12.json << 'EOF'
{
  "mode": "Indexed",
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Storage/storageAccounts"
        },
        {
          "field": "Microsoft.Storage/storageAccounts/minimumTlsVersion",
          "notEquals": "TLS1_2"
        }
      ]
    },
    "then": {
      "effect": "deny"
    }
  },
  "parameters": {}
}
EOF

# Create custom policy definition
az policy definition create \
  --name "require-storage-tls12" \
  --display-name "Require TLS 1.2 for Storage Accounts" \
  --description "Ensures all storage accounts use TLS 1.2 minimum" \
  --mode Indexed \
  --rules policy-require-tls12.json \
  --management-group "mg-landing-zones"

echo "Custom policy created"

Policy: Deny Public IP on VMs

bash
cat > policy-deny-public-ip.json << 'EOF'
{
  "mode": "All",
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Network/networkInterfaces"
        },
        {
          "count": {
            "field": "Microsoft.Network/networkInterfaces/ipConfigurations[*]",
            "where": {
              "field": "Microsoft.Network/networkInterfaces/ipConfigurations[*].publicIpAddress.id",
              "notEquals": ""
            }
          },
          "greater": 0
        }
      ]
    },
    "then": {
      "effect": "deny"
    }
  },
  "parameters": {}
}
EOF

# Create custom policy definition
az policy definition create \
  --name "deny-public-ip-on-nic" \
  --display-name "Deny Public IP on Network Interfaces" \
  --description "Prevents VMs from having public IP addresses" \
  --mode All \
  --rules policy-deny-public-ip.json \
  --management-group "mg-prod"

# Assign to production
az policy assignment create \
  --name "deny-public-ip-prod" \
  --display-name "Deny Public IP in Production" \
  --scope "/providers/Microsoft.Management/managementGroups/mg-prod" \
  --policy "/providers/Microsoft.Management/managementGroups/mg-prod/providers/Microsoft.Authorization/policyDefinitions/deny-public-ip-on-nic"

echo "Deny public IP policy created and assigned"

Step 2.4: Create Policy Initiative (Policy Set) ​

bash
cat > policy-initiative-security.json << 'EOF'
{
  "properties": {
    "displayName": "Security Baseline Initiative",
    "description": "Initiative for security baseline policies",
    "policyDefinitions": [
      {
        "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9",
        "policyDefinitionReferenceId": "audit-secure-transfer"
      },
      {
        "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/34c877ad-507e-4c82-993e-3452a6e0ad3c",
        "policyDefinitionReferenceId": "audit-storage-encryption"
      },
      {
        "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/5744710e-cc2f-4ee8-8809-3b11e89f4bc9",
        "policyDefinitionReferenceId": "audit-log-analytics"
      }
    ]
  }
}
EOF

# Create initiative at management group level
az policy set-definition create \
  --name "security-baseline-initiative" \
  --display-name "Security Baseline Initiative" \
  --description "Collection of security baseline policies" \
  --definitions '[
    {"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9"},
    {"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/34c877ad-507e-4c82-993e-3452a6e0ad3c"}
  ]' \
  --management-group "mg-landing-zones"

echo "Policy initiative created"

Phase 3: Implement Custom RBAC Roles ​

Step 3.1: Create VM Operator Role ​

bash
cat > role-vm-operator.json << 'EOF'
{
  "Name": "VM Operator",
  "Description": "Can start, stop, and restart VMs but cannot create or delete them",
  "Actions": [
    "Microsoft.Compute/virtualMachines/start/action",
    "Microsoft.Compute/virtualMachines/powerOff/action",
    "Microsoft.Compute/virtualMachines/restart/action",
    "Microsoft.Compute/virtualMachines/read",
    "Microsoft.Compute/virtualMachines/instanceView/read",
    "Microsoft.Network/networkInterfaces/read",
    "Microsoft.Network/publicIPAddresses/read"
  ],
  "NotActions": [],
  "DataActions": [],
  "NotDataActions": [],
  "AssignableScopes": [
    "/providers/Microsoft.Management/managementGroups/mg-landing-zones"
  ]
}
EOF

# Create the custom role
az role definition create --role-definition role-vm-operator.json

echo "VM Operator role created"

Step 3.2: Create Cost Manager Role ​

bash
cat > role-cost-manager.json << 'EOF'
{
  "Name": "Cost Manager",
  "Description": "Can view costs and create budgets but cannot modify resources",
  "Actions": [
    "Microsoft.CostManagement/*/read",
    "Microsoft.CostManagement/budgets/*",
    "Microsoft.CostManagement/exports/*",
    "Microsoft.Consumption/*/read",
    "Microsoft.Resources/subscriptions/resourceGroups/read",
    "Microsoft.Support/*"
  ],
  "NotActions": [],
  "DataActions": [],
  "NotDataActions": [],
  "AssignableScopes": [
    "/providers/Microsoft.Management/managementGroups/mg-landing-zones"
  ]
}
EOF

# Create the custom role
az role definition create --role-definition role-cost-manager.json

echo "Cost Manager role created"

Step 3.3: Create Network Operator Role ​

bash
cat > role-network-operator.json << 'EOF'
{
  "Name": "Network Operator",
  "Description": "Can manage NSGs and route tables but not VNets",
  "Actions": [
    "Microsoft.Network/networkSecurityGroups/*",
    "Microsoft.Network/routeTables/*",
    "Microsoft.Network/virtualNetworks/read",
    "Microsoft.Network/virtualNetworks/subnets/read",
    "Microsoft.Network/networkInterfaces/read",
    "Microsoft.Network/publicIPAddresses/read"
  ],
  "NotActions": [
    "Microsoft.Network/networkSecurityGroups/delete"
  ],
  "DataActions": [],
  "NotDataActions": [],
  "AssignableScopes": [
    "/providers/Microsoft.Management/managementGroups/mg-landing-zones"
  ]
}
EOF

# Create the custom role
az role definition create --role-definition role-network-operator.json

echo "Network Operator role created"

Step 3.4: Assign Roles ​

bash
# Assign VM Operator role to a user or group
az role assignment create \
  --assignee "user@domain.com" \
  --role "VM Operator" \
  --scope "/providers/Microsoft.Management/managementGroups/mg-nonprod"

# Assign Cost Manager role
az role assignment create \
  --assignee "finance-team@domain.com" \
  --role "Cost Manager" \
  --scope "/providers/Microsoft.Management/managementGroups/mg-landing-zones"

# List role assignments
az role assignment list \
  --scope "/subscriptions/$SUBSCRIPTION_ID" \
  --output table

Phase 4: Configure Cost Management ​

Step 4.1: Create Budgets ​

bash
# Get subscription ID
SUBSCRIPTION_ID=$(az account show --query id -o tsv)

# Create monthly budget for production
az consumption budget create \
  --budget-name "budget-prod-monthly" \
  --amount 10000 \
  --category Cost \
  --time-grain Monthly \
  --start-date "$(date +%Y-%m-01)" \
  --end-date "2025-12-31" \
  --resource-group $RG_NAME

echo "Production budget created"

Step 4.2: Configure Budget Alerts (Portal) ​

Configure via Portal

Budget alerts with action groups are easier to configure via Azure Portal:

  1. Navigate to Cost Management + Billing β†’ Budgets
  2. Click on the budget
  3. Add alert conditions:
    yaml
    Alert 1:
      Type: Actual
      % of budget: 50
      Action group: email-alerts
    
    Alert 2:
      Type: Actual
      % of budget: 75
      Action group: email-alerts
    
    Alert 3:
      Type: Actual
      % of budget: 90
      Action group: email-alerts
    
    Alert 4:
      Type: Forecasted
      % of budget: 100
      Action group: email-and-teams-alerts

Step 4.3: Create Cost Allocation Tags ​

bash
# Define cost allocation tags
az tag create --name "CostCenter"
az tag create --name "Project"
az tag create --name "Owner"
az tag create --name "Environment"

# Add tag values
az tag add-value --name "CostCenter" --value "IT-001"
az tag add-value --name "CostCenter" --value "IT-002"
az tag add-value --name "CostCenter" --value "Marketing-001"

az tag add-value --name "Environment" --value "Production"
az tag add-value --name "Environment" --value "Development"
az tag add-value --name "Environment" --value "Test"

echo "Cost allocation tags created"

Phase 5: Implement Resource Locks ​

Step 5.1: Create Resource Locks ​

bash
# Create CanNotDelete lock on resource group
az lock create \
  --name "CannotDeleteLock" \
  --lock-type CanNotDelete \
  --resource-group $RG_NAME \
  --notes "Prevent accidental deletion of governance resources"

# Create ReadOnly lock on critical resources
# First create a test storage account
STORAGE_NAME="stgovtest$(date +%s | tail -c 8)"
az storage account create \
  --name $STORAGE_NAME \
  --resource-group $RG_NAME \
  --sku Standard_LRS \
  --location $LOCATION

# Apply CanNotDelete lock to storage account
az lock create \
  --name "ProtectStorage" \
  --lock-type CanNotDelete \
  --resource-group $RG_NAME \
  --resource-name $STORAGE_NAME \
  --resource-type "Microsoft.Storage/storageAccounts"

# List locks
az lock list --resource-group $RG_NAME --output table

Step 5.2: Test Lock Behavior ​

bash
# Try to delete the storage account (should fail)
az storage account delete \
  --name $STORAGE_NAME \
  --resource-group $RG_NAME \
  --yes

# Error: The scope '/subscriptions/.../storageAccounts/...' 
# cannot perform delete operation because following scope(s) are locked

Phase 6: Tag Governance ​

Step 6.1: Apply Tags with Policy Remediation ​

bash
# Create policy to inherit tags from resource group
az policy assignment create \
  --name "inherit-rg-tags" \
  --display-name "Inherit Tags from Resource Group" \
  --scope "/subscriptions/$SUBSCRIPTION_ID" \
  --policy "ea3f2387-9b95-492a-a190-fcdc54f7b070" \
  --params '{"tagName": {"value": "Environment"}}'

# Create remediation task
az policy remediation create \
  --name "remediate-environment-tag" \
  --policy-assignment "inherit-rg-tags" \
  --resource-group $RG_NAME

Step 6.2: Tag All Resources Script ​

bash
# Script to tag all resources in a resource group
TAG_KEY="Environment"
TAG_VALUE="Lab"

# Get all resources in resource group
RESOURCES=$(az resource list \
  --resource-group $RG_NAME \
  --query "[].id" -o tsv)

# Apply tags to each resource
for RESOURCE_ID in $RESOURCES; do
  az tag update \
    --resource-id "$RESOURCE_ID" \
    --operation merge \
    --tags $TAG_KEY=$TAG_VALUE
done

echo "Tags applied to all resources"

Phase 7: Compliance Monitoring ​

Step 7.1: Check Policy Compliance ​

bash
# Get overall compliance state
az policy state summarize \
  --management-group "mg-landing-zones" \
  --output table

# Get non-compliant resources
az policy state list \
  --management-group "mg-landing-zones" \
  --filter "complianceState eq 'NonCompliant'" \
  --output table

Step 7.2: Export Compliance Report ​

bash
# Export compliance data to CSV
az policy state list \
  --management-group "mg-landing-zones" \
  --query "[].{Resource:resourceId, Policy:policyAssignmentName, Compliance:complianceState}" \
  --output table > compliance-report.csv

# View compliance summary
az policy state summarize \
  --subscription $SUBSCRIPTION_ID \
  --query "results"

Governance Summary ​

ComponentPurposeScope
Management GroupsOrganizational hierarchyTenant-wide
Azure PolicyCompliance enforcementMG/Subscription/RG
Custom RBACLeast privilege accessMG/Subscription/RG
BudgetsCost controlSubscription/RG
Resource LocksPrevent accidental changesResource/RG
TagsCost allocation & organizationAll resources

Cleanup ​

bash
# Remove policy assignments
az policy assignment delete --name "allowed-locations" \
  --scope "/providers/Microsoft.Management/managementGroups/mg-landing-zones"

az policy assignment delete --name "require-environment-tag" \
  --scope "/providers/Microsoft.Management/managementGroups/mg-landing-zones"

# Remove locks
az lock delete --name "CannotDeleteLock" --resource-group $RG_NAME

# Delete resource group
az group delete --name $RG_NAME --yes --no-wait

# Note: Management groups and custom roles persist
# Delete them if needed:
# az account management-group delete --name "mg-sandbox"
# az role definition delete --name "VM Operator"

Key Takeaways ​

  1. Management Groups: Organize subscriptions hierarchically
  2. Policy Inheritance: Policies cascade down the hierarchy
  3. Least Privilege: Custom roles for specific tasks
  4. Cost Control: Budgets with alerts prevent surprises
  5. Resource Protection: Locks prevent accidental deletion
  6. Tag Governance: Enforce tagging for cost allocation

Next Steps ​

  • Implement Azure Blueprints
  • Set up Azure Lighthouse for multi-tenant management
  • Configure regulatory compliance initiatives
  • Implement Defender for Cloud recommendations

Released under the MIT License.