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_IDPhase 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=GovernanceStep 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 tablePhase 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:
- Navigate to Cost Management + Billing β Budgets
- Click on the budget
- 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 tableStep 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 lockedPhase 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_NAMEStep 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 tableStep 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 β
| Component | Purpose | Scope |
|---|---|---|
| Management Groups | Organizational hierarchy | Tenant-wide |
| Azure Policy | Compliance enforcement | MG/Subscription/RG |
| Custom RBAC | Least privilege access | MG/Subscription/RG |
| Budgets | Cost control | Subscription/RG |
| Resource Locks | Prevent accidental changes | Resource/RG |
| Tags | Cost allocation & organization | All 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 β
- Management Groups: Organize subscriptions hierarchically
- Policy Inheritance: Policies cascade down the hierarchy
- Least Privilege: Custom roles for specific tasks
- Cost Control: Budgets with alerts prevent surprises
- Resource Protection: Locks prevent accidental deletion
- 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