Deep Dive

Multi-Cloud Governance Platform: Azure Arc + Policy + Cost Management + Landing Zones

Introduction: Governing the Multi-Cloud Enterprise

Most enterprises run workloads across Azure, AWS, on-premises data centers, and edge locations. Without unified governance, each environment becomes a silo with its own compliance gaps, cost overruns, and security blind spots. This deep dive builds a comprehensive governance platform using Azure as the control plane — extending Azure Arc to manage non-Azure resources, enforcing policies everywhere, standardizing deployments with Landing Zones, and providing a single pane of glass for cost visibility and optimization.

Multi-Cloud Governance Architecture

Prerequisites

  • Azure subscription with Owner access at Management Group level
  • Azure Arc-enabled servers agent access for on-premises/AWS VMs
  • AWS account with IAM permissions for Arc onboarding
  • Microsoft Entra ID Premium P2
  • Familiarity with Azure Resource Manager, Bicep, and Policy-as-Code

Phase 1: Azure Landing Zone Foundation

Management Group Hierarchy

targetScope = 'managementGroup'

// Root Management Group hierarchy
resource rootMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: 'mg-contoso-root'
  properties: {
    displayName: 'Contoso Enterprise'
  }
}

resource platformMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: 'mg-platform'
  properties: {
    displayName: 'Platform'
    details: { parent: { id: rootMg.id } }
  }
}

resource landingZonesMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: 'mg-landing-zones'
  properties: {
    displayName: 'Landing Zones'
    details: { parent: { id: rootMg.id } }
  }
}

resource sandboxMg 'Microsoft.Management/managementGroups@2021-04-01' = {
  name: 'mg-sandbox'
  properties: {
    displayName: 'Sandbox'
    details: { parent: { id: rootMg.id } }
  }
}

// Landing Zone sub-groups
var landingZoneTypes = ['corp', 'online', 'confidential']
resource lzSubGroups 'Microsoft.Management/managementGroups@2021-04-01' = [for lz in landingZoneTypes: {
  name: 'mg-lz-${lz}'
  properties: {
    displayName: 'LZ - ${toUpper(lz)}'
    details: { parent: { id: landingZonesMg.id } }
  }
}]

// Platform subscriptions
resource identitySub 'Microsoft.Management/managementGroups/subscriptions@2021-04-01' = {
  parent: platformMg
  name: 'identity-subscription-id'
}

resource connectivitySub 'Microsoft.Management/managementGroups/subscriptions@2021-04-01' = {
  parent: platformMg
  name: 'connectivity-subscription-id'
}

resource managementSub 'Microsoft.Management/managementGroups/subscriptions@2021-04-01' = {
  parent: platformMg
  name: 'management-subscription-id'
}

Landing Zone Subscription Vending

// modules/subscription-vending.bicep
targetScope = 'managementGroup'

@description('Landing zone request parameters')
param lzRequest object = {
  workloadName: ''
  environment: '' // dev, staging, prod
  costCenter: ''
  businessUnit: ''
  dataClassification: '' // public, internal, confidential
  owner: ''
}

// Create subscription (using Subscription Alias API)
resource subscription 'Microsoft.Subscription/aliases@2021-10-01' = {
  name: '${lzRequest.workloadName}-${lzRequest.environment}'
  properties: {
    displayName: '${lzRequest.businessUnit}-${lzRequest.workloadName}-${lzRequest.environment}'
    billingScope: '/billingAccounts/${billingAccountId}/enrollmentAccounts/${enrollmentAccountId}'
    workload: lzRequest.environment == 'prod' ? 'Production' : 'DevTest'
    additionalProperties: {
      managementGroupId: '/providers/Microsoft.Management/managementGroups/mg-lz-${lzRequest.dataClassification == 'confidential' ? 'confidential' : 'corp'}'
      tags: {
        CostCenter: lzRequest.costCenter
        BusinessUnit: lzRequest.businessUnit
        Environment: lzRequest.environment
        DataClassification: lzRequest.dataClassification
        Owner: lzRequest.owner
        ManagedBy: 'LandingZone-Automation'
      }
    }
  }
}

Landing Zone Architecture

Phase 2: Azure Policy-as-Code

Enterprise Policy Definitions

{
  "properties": {
    "displayName": "Enforce encryption at rest for all storage accounts",
    "description": "Ensures all storage accounts use customer-managed keys with Key Vault",
    "mode": "Indexed",
    "metadata": {
      "category": "Encryption",
      "version": "2.0.0"
    },
    "parameters": {
      "effect": {
        "type": "String",
        "defaultValue": "Deny",
        "allowedValues": ["Audit", "Deny", "Disabled"]
      },
      "minimumTlsVersion": {
        "type": "String",
        "defaultValue": "TLS1_2"
      }
    },
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Storage/storageAccounts"
          },
          {
            "anyOf": [
              {
                "field": "Microsoft.Storage/storageAccounts/encryption.keySource",
                "notEquals": "Microsoft.Keyvault"
              },
              {
                "field": "Microsoft.Storage/storageAccounts/minimumTlsVersion",
                "notEquals": "[parameters('minimumTlsVersion')]"
              },
              {
                "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
                "notEquals": true
              },
              {
                "field": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess",
                "notEquals": false
              }
            ]
          }
        ]
      },
      "then": {
        "effect": "[parameters('effect')]"
      }
    }
  }
}

Policy Initiative (Policy Set) for CIS Benchmark

{
  "properties": {
    "displayName": "CIS Microsoft Azure Foundations Benchmark v2.0",
    "description": "Enterprise compliance initiative covering CIS Azure controls",
    "metadata": {
      "category": "Compliance",
      "version": "2.0.0"
    },
    "policyDefinitions": [
      {
        "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/storage-cmk-policy",
        "parameters": { "effect": { "value": "Deny" } },
        "groupNames": ["CIS-2.1"]
      },
      {
        "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/sql-tde-policy",
        "parameters": { "effect": { "value": "DeployIfNotExists" } },
        "groupNames": ["CIS-4.1"]
      },
      {
        "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/nsg-flow-logs-policy",
        "parameters": { "effect": { "value": "DeployIfNotExists" } },
        "groupNames": ["CIS-6.4"]
      },
      {
        "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/keyvault-soft-delete-policy",
        "parameters": { "effect": { "value": "Deny" } },
        "groupNames": ["CIS-8.4"]
      }
    ],
    "policyDefinitionGroups": [
      { "name": "CIS-2.1", "displayName": "2.1 - Storage Account Security" },
      { "name": "CIS-4.1", "displayName": "4.1 - SQL Database Encryption" },
      { "name": "CIS-6.4", "displayName": "6.4 - Network Security Group Flow Logs" },
      { "name": "CIS-8.4", "displayName": "8.4 - Key Vault Configuration" }
    ]
  }
}

Policy Deployment Pipeline

# azure-pipelines/policy-deployment.yaml
trigger:
  branches:
    include: [main]
  paths:
    include: [policies/**]

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: Validate
    jobs:
      - job: PolicyValidation
        steps:
          - task: AzurePowerShell@5
            displayName: 'Validate Policy Definitions'
            inputs:
              azureSubscription: 'governance-service-connection'
              ScriptType: InlineScript
              Inline: |
                $policyFiles = Get-ChildItem -Path "$(Build.SourcesDirectory)/policies/definitions" -Filter "*.json" -Recurse
                foreach ($file in $policyFiles) {
                    $policy = Get-Content $file.FullName | ConvertFrom-Json
                    Write-Host "Validating: $($policy.properties.displayName)"
                    
                    # Validate policy rule syntax
                    if (-not $policy.properties.policyRule) {
                        throw "Missing policyRule in $($file.Name)"
                    }
                    
                    # Check for required metadata
                    if (-not $policy.properties.metadata.version) {
                        throw "Missing version metadata in $($file.Name)"
                    }
                }
              azurePowerShellVersion: LatestVersion

  - stage: Deploy
    dependsOn: Validate
    jobs:
      - deployment: DeployPolicies
        environment: 'governance-prod'
        strategy:
          runOnce:
            deploy:
              steps:
                - task: AzureCLI@2
                  displayName: 'Deploy Policy Definitions'
                  inputs:
                    azureSubscription: 'governance-service-connection'
                    scriptType: bash
                    scriptLocation: inlineScript
                    inlineScript: |
                      for file in policies/definitions/*.json; do
                        name=$(basename "$file" .json)
                        az policy definition create \
                          --name "$name" \
                          --management-group "mg-contoso-root" \
                          --rules "$file" \
                          --mode "Indexed"
                      done

                - task: AzureCLI@2
                  displayName: 'Assign Policy Initiatives'
                  inputs:
                    azureSubscription: 'governance-service-connection'
                    scriptType: bash
                    scriptLocation: inlineScript
                    inlineScript: |
                      az policy assignment create \
                        --name "cis-benchmark-v2" \
                        --policy-set-definition "cis-azure-v2" \
                        --scope "/providers/Microsoft.Management/managementGroups/mg-contoso-root" \
                        --mi-system-assigned \
                        --location "eastus2" \
                        --enforcement-mode "Default"

Phase 3: Azure Arc for Multi-Cloud Management

Onboarding AWS EC2 Instances

# Generate Arc onboarding script for AWS fleet
$servicePrincipal = New-AzADServicePrincipal `
    -DisplayName "arc-onboarding-aws" `
    -Role "Azure Connected Machine Onboarding" `
    -Scope "/subscriptions/$subscriptionId/resourceGroups/rg-arc-servers"

# Create onboarding script for AWS instances
$onboardingScript = @"
#!/bin/bash
# Azure Arc onboarding for AWS EC2 instances

# Download and install Arc agent
wget https://aka.ms/azcmagent -O ~/install_linux_azcmagent.sh
sudo bash ~/install_linux_azcmagent.sh

# Connect to Azure Arc
sudo azcmagent connect \
    --resource-group "rg-arc-servers" \
    --tenant-id "$tenantId" \
    --location "eastus2" \
    --subscription-id "$subscriptionId" \
    --service-principal-id "$($servicePrincipal.AppId)" \
    --service-principal-secret "$servicePrincipalSecret" \
    --cloud "AzureCloud" \
    --tags "Source=AWS,Region=us-east-1,Environment=Production" \
    --correlation-id "$(New-Guid)"

# Verify connection
sudo azcmagent show
"@

# Deploy via AWS Systems Manager
$ssmCommand = @{
    DocumentName = "AWS-RunShellScript"
    Targets = @(@{Key="tag:ArcManaged"; Values=@("true")})
    Parameters = @{
        commands = @($onboardingScript)
    }
    MaxConcurrency = "50"
    MaxErrors = "10"
}

Arc-Enabled Policy Enforcement

{
  "properties": {
    "displayName": "Deploy Log Analytics agent to Arc-enabled servers",
    "policyType": "Custom",
    "mode": "Indexed",
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.HybridCompute/machines"
          },
          {
            "field": "Microsoft.HybridCompute/machines/osType",
            "equals": "linux"
          }
        ]
      },
      "then": {
        "effect": "DeployIfNotExists",
        "details": {
          "type": "Microsoft.HybridCompute/machines/extensions",
          "roleDefinitionIds": [
            "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293"
          ],
          "existenceCondition": {
            "allOf": [
              {
                "field": "Microsoft.HybridCompute/machines/extensions/type",
                "equals": "OmsAgentForLinux"
              },
              {
                "field": "Microsoft.HybridCompute/machines/extensions/provisioningState",
                "equals": "Succeeded"
              }
            ]
          },
          "deployment": {
            "properties": {
              "mode": "incremental",
              "template": {
                "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                "contentVersion": "1.0.0.0",
                "resources": [
                  {
                    "type": "Microsoft.HybridCompute/machines/extensions",
                    "apiVersion": "2022-12-27",
                    "name": "[concat(field('name'), '/OmsAgentForLinux')]",
                    "location": "[field('location')]",
                    "properties": {
                      "publisher": "Microsoft.EnterpriseCloud.Monitoring",
                      "type": "OmsAgentForLinux",
                      "autoUpgradeMinorVersion": true,
                      "settings": {
                        "workspaceId": "[parameters('logAnalyticsWorkspaceId')]"
                      },
                      "protectedSettings": {
                        "workspaceKey": "[parameters('logAnalyticsWorkspaceKey')]"
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      }
    }
  }
}

Azure Arc Multi-Cloud Management

Phase 4: Cost Management and FinOps

Cost Allocation and Showback

# Configure cost allocation rules
$costRule = @{
    name = "SharedInfraAllocation"
    description = "Allocate shared platform costs to business units"
    sourceScopes = @("/subscriptions/platform-sub-id")
    targetScopes = @(
        "/subscriptions/bu-engineering-sub-id",
        "/subscriptions/bu-marketing-sub-id",
        "/subscriptions/bu-finance-sub-id"
    )
    details = @{
        type = "Proportional"
        splitRule = @{
            property = "ResourceGroup"
            splitValues = @(
                @{ name = "rg-shared-networking"; percentage = 40 },
                @{ name = "rg-shared-security"; percentage = 35 },
                @{ name = "rg-shared-monitoring"; percentage = 25 }
            )
        }
    }
}

# Create budget alerts
$budget = @{
    name = "Q1-2026-Engineering"
    amount = 250000
    timeGrain = "Quarterly"
    timePeriod = @{
        startDate = "2026-01-01"
        endDate = "2026-03-31"
    }
    filter = @{
        tags = @{
            name = "BusinessUnit"
            values = @("Engineering")
        }
    }
    notifications = @{
        "Forecast80" = @{
            enabled = $true
            operator = "GreaterThan"
            threshold = 80
            thresholdType = "Forecasted"
            contactEmails = @("engineering-leads@contoso.com")
            contactRoles = @("Owner")
        }
        "Actual100" = @{
            enabled = $true
            operator = "GreaterThan"
            threshold = 100
            thresholdType = "Actual"
            contactEmails = @("cfo@contoso.com", "engineering-vp@contoso.com")
        }
    }
}

Automated Cost Optimization

from azure.mgmt.advisor import AdvisorManagementClient
from azure.mgmt.compute import ComputeManagementClient
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()

# Get cost optimization recommendations
advisor_client = AdvisorManagementClient(credential, subscription_id)

recommendations = advisor_client.recommendations.list(
    filter="Category eq 'Cost'"
)

savings_report = []
for rec in recommendations:
    savings_report.append({
        "resource": rec.resource_metadata.resource_id,
        "recommendation": rec.short_description.problem,
        "solution": rec.short_description.solution,
        "annual_savings": rec.extended_properties.get("annualSavingsAmount", "Unknown"),
        "impact": rec.impact
    })

# Auto-resize underutilized VMs
compute_client = ComputeManagementClient(credential, subscription_id)

for rec in savings_report:
    if "right-size" in rec["solution"].lower() and rec["impact"] == "High":
        resource_id = rec["resource"]
        vm_name = resource_id.split("/")[-1]
        rg_name = resource_id.split("/resourceGroups/")[1].split("/")[0]
        
        vm = compute_client.virtual_machines.get(rg_name, vm_name)
        
        # Log recommendation for approval workflow
        print(f"VM: {vm_name}")
        print(f"  Current size: {vm.hardware_profile.vm_size}")
        print(f"  Recommended: {rec['solution']}")
        print(f"  Annual savings: ${rec['annual_savings']}")

Cost Management Dashboard

Governance Maturity Model

Capability Level 1 (Ad-hoc) Level 2 (Managed) Level 3 (Optimized)
Policy Manual reviews Policy-as-Code deployed Auto-remediation enabled
Cost Reactive billing Budget alerts + showback Predictive optimization
Identity Per-resource RBAC Landing Zone RBAC templates PIM + JIT everywhere
Compliance Annual audits Continuous compliance monitoring Real-time remediation
Multi-Cloud Separate dashboards Arc-enabled monitoring Unified policy enforcement
Landing Zones Manual setup Subscription vending Self-service portal

Best Practices

  1. Start with management group hierarchy: This is the foundation — getting it wrong is expensive to fix
  2. Policy-as-Code in CI/CD: Never deploy policies manually — version control and test everything
  3. Tagging is governance: Enforce mandatory tags (CostCenter, Owner, Environment) from day one
  4. Subscription vending over shared subscriptions: Blast radius isolation is worth the management overhead
  5. Arc everything: On-premises servers, AWS VMs, edge devices — bring them all under Azure governance
  6. FinOps is a practice, not a tool: Combine Cost Management alerts with organizational accountability

Architecture Decision and Tradeoffs

When designing integrated solutions solutions with Azure + Power Platform, consider these key architectural trade-offs:

Approach Best For Tradeoff
Managed / platform service Rapid delivery, reduced ops burden Less customisation, potential vendor lock-in
Custom / self-hosted Full control, advanced tuning Higher operational overhead and cost

Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

Validation and Versioning

  • Last validated: April 2026
  • Validate examples against your tenant, region, and SKU constraints before production rollout.
  • Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

Security and Governance Considerations

  • Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
  • Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
  • Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

Cost and Performance Notes

  • Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
  • Baseline performance with synthetic and real-user checks before and after major changes.
  • Scale resources with measured thresholds and revisit sizing after usage pattern changes.

Official Microsoft References

Public Examples from Official Sources

Key Takeaways

  • Azure Landing Zones provide the standardized foundation every enterprise needs
  • Azure Policy enforces guardrails consistently across 1,000+ subscriptions
  • Azure Arc extends Azure governance to AWS, on-premises, and edge resources
  • Cost Management with FinOps practices prevents cloud bill shock
  • Policy-as-Code enables version control, testing, and audit trails for all governance rules

Further Reading

AI Assistant
AI Assistant

Article Assistant

Ask me about this article

AI
Hi! I'm here to help you understand this article. Ask me anything about the content, concepts, or implementation details.