3D render abstract digital visualization depicting neural networks and AI technology.

İstanbul’daki bir holding, 8 farklı şirketin Azure subscription’larını birleştirmişti. 14 subscription, 350+ resource group, 4.200+ kaynak. Denetim hep aynı şeyi söylüyordu: “Hangi VM’in hangi tag’i var, hangi storage account public access açık, hangi SQL Server’da audit kapalı?” Cevap her seferinde script çalıştırarak çıkıyordu, hiç gerçek zamanlı değildi. Azure Policy ile governance kurma projesi 4 ay sürdü, denetim sürecini günlerden saatlere indirdi. Bu yazı o projeden çıkardığım pratik notlar.

Management Group Hierarchy: Önce Yapı, Sonra Policy

Policy’leri tek tek subscription’a atamak hata. Önce Management Group hierarchy’sini düzgün kur:

Tenant Root Group
├── platform (CIO yönetimi)
│   ├── connectivity (hub vnets, ExpressRoute)
│   ├── identity (AAD-only resources)
│   └── management (log analytics, automation)
├── landingzones
│   ├── corp (kurumsal iş yükleri)
│   │   ├── corp-prod
│   │   ├── corp-nonprod
│   │   └── corp-dev
│   └── online (internet-facing iş yükleri)
│       ├── online-prod
│       └── online-nonprod
├── decommissioned (çıkış yapan kaynaklar)
└── sandbox (deney)

Bu yapı Microsoft Cloud Adoption Framework’ün önerdiği “Enterprise Scale” landing zone’a yakın. Holding’de 14 subscription bu hierarchy’ye yerleştirildi. Policy’ler en üst seviyeye atanır, alta inherit eder. İhtiyaca göre alt seviyede daha sıkı policy eklenir.

Initiative (Policy Set): Tek Tek Policy Yerine Paket

Sahada 50-100 ayrı policy yazmak ve atamak yerine, initiative (policy set) tasarla. Initiative bir grup policy’nin bundle’ı. Microsoft hazır initiative’ler veriyor (CIS Microsoft Azure Foundations Benchmark, ISO 27001, NIST SP 800-53), bunlardan başla, sonra custom initiative’lerinle özelleştir.

Holding’de uyguladığımız 4 ana custom initiative:

  1. “corp-baseline”: Tüm kaynaklarda zorunlu tag’ler (CostCenter, Owner, Environment, DataClass), allowed locations (turkeywest, turkeynorth, westeurope), allowed VM SKU’ları, public IP yasak.
  2. “prod-security-strict”: prod scope’ta. Storage account public access deny, SQL Server audit deployIfNotExists, NSG flow logs zorunlu, Key Vault soft delete + purge protection.
  3. “data-protection”: KVKK uyumu için. SQL TDE zorunlu, Storage encryption with customer-managed key, Key Vault firewall sadece selected networks.
  4. “cost-control”: VM SKU restriction, Premium SSD tier sadece prod’da, idle resource detection (custom Logic App + alert).

Policy Etkileri: Audit, Deny, DeployIfNotExists, Modify

Effect Açıklama Tipik kullanım
Audit Sadece raporlar, engellemez İlk gözlem fazı
Deny Kaynağı oluşmaktan engeller Public IP yasak, allowed locations
DeployIfNotExists Eksikse otomatik kurar Diagnostic settings, Defender for Cloud agent
Modify Mevcut ayarı değiştirir Tag ekleme, default değer atama
AuditIfNotExists Sadece eksikse loglar Vulnerability assessment yok mu
Append Property ekler (deprecated, Modify kullan) Eski projeler

Sahada uyguladığım sıra: Önce Audit, 30 gün gözlem, ekibin compliance raporlarına alışması, sonra Deny. DeployIfNotExists ve Modify policy’leri için Managed Identity ile remediation task çalıştırma şart.

Custom Policy Örneği: Storage Account Public Access Deny

{
  "properties": {
    "displayName": "Deny Storage Account public network access",
    "policyType": "Custom",
    "mode": "All",
    "metadata": {
      "category": "Storage",
      "version": "1.0.0"
    },
    "parameters": {
      "effect": {
        "type": "String",
        "defaultValue": "Deny",
        "allowedValues": ["Audit","Deny","Disabled"]
      }
    },
    "policyRule": {
      "if": {
        "allOf": [
          { "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
          { "field": "Microsoft.Storage/storageAccounts/publicNetworkAccess", "notEquals": "Disabled" }
        ]
      },
      "then": {
        "effect": "[parameters('effect')]"
      }
    }
  }
}

Bu policy initiative’e eklenir, prod scope’ta Deny modunda. Yeni storage account oluşturmaya çalışan biri public access açık bırakırsa hata alır.

DeployIfNotExists: Diagnostic Settings Otomasyonu

Holding’de ihtiyaç vardı: Tüm Key Vault’lar, Storage Account’lar, SQL Server’lar otomatik olarak diagnostic settings’i Log Analytics’e göndersin. Manuel olarak yapılan 350+ kaynak için. DeployIfNotExists ile otomatize ettik:

{
  "if": {
    "field": "type",
    "equals": "Microsoft.KeyVault/vaults"
  },
  "then": {
    "effect": "deployIfNotExists",
    "details": {
      "type": "Microsoft.Insights/diagnosticSettings",
      "existenceCondition": {
        "allOf": [
          {"field": "Microsoft.Insights/diagnosticSettings/logs.enabled", "equals": "true"},
          {"field": "Microsoft.Insights/diagnosticSettings/workspaceId", "equals": "[parameters('logAnalyticsWorkspaceId')]"}
        ]
      },
      "roleDefinitionIds": [
        "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293",
        "/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa"
      ],
      "deployment": { /* ARM template gömülü */ }
    }
  }
}

Policy assignment’ında Managed Identity oluşur, ona Log Analytics Contributor rolü atanır, eksik diagnostic settings için deployment otomatik tetiklenir.

Exemption: Policy’den İstisna Yönetimi

Mutlaka istisna gerekecek kaynaklar olur. Manuel “policy assignment exclude scope” eklemek dağınık; bunun yerine Azure Policy Exemption resource’unu kullan. Belirli bir kaynak veya scope için bir exemption oluştur, gerekçe ve süre belirt.

az policy exemption create 
  --name "legacy-storage-public-access-exemption" 
  --policy-assignment "/subscriptions/.../providers/Microsoft.Authorization/policyAssignments/corp-baseline" 
  --scope "/subscriptions/.../resourceGroups/rg-legacy-app/providers/Microsoft.Storage/storageAccounts/legacystorage01" 
  --exemption-category Waiver 
  --description "Legacy app dependency - migration planned 2026 Q3" 
  --expires-on "2026-09-30T00:00:00Z"

Bu yaklaşımla audit’te “neden bu storage public” sorusu sorulduğunda exemption resource’unda gerekçe + onay tarihi + bitiş tarihi var. Compliance ekibi memnun.

Compliance State ve Defender for Cloud Entegrasyonu

Policy assign ettikten sonra “compliance state” otomatik hesaplanır (genellikle 30 dk içinde, ama tam tarama 24 saat alabilir). Microsoft Defender for Cloud, Azure Policy uyum verisini kendi Secure Score’una katarak gösterir.

Holding’de aylık compliance raporu için workflow:

  1. Logic App her ayın 1’inde tetiklenir.
  2. Azure Resource Graph sorgusu çalıştırır:
PolicyResources
| where type == "microsoft.policyinsights/policystates"
| where properties.complianceState == "NonCompliant"
| summarize NonCompliantCount=count() by 
    SubscriptionId=tostring(properties.subscriptionId),
    PolicyName=tostring(properties.policyDefinitionName),
    ResourceType=tostring(properties.resourceType)
| order by NonCompliantCount desc
  1. Sonuç Excel’e dönüştürülür.
  2. Power BI dashboard’a feed edilir.
  3. Subscription owner’larına ve compliance ekibine email atılır.

Sahada Düşülen Beş Tuzak

  1. Tek seferde Deny moduyla başlamak: Mevcut kaynaklar uyumsuz olabilir. İlk başta Audit ile başla, gözlem yap, sonra Deny.
  2. Initiative parametrelerini hardcode’lamak: Allowed locations, allowed VM SKU’lar parameter olarak çıkar. Subscription’a göre farklı atanabilir hale getir.
  3. Remediation task’ı manuel tetikletmek: DeployIfNotExists policy mevcut kaynaklara otomatik applied olmaz, sadece yeni oluşan kaynaklar için. Mevcutları getirmek için “Create remediation task” gerekir, bunu CI/CD pipeline’a bağla.
  4. Custom policy version’lamayı atlamak: Policy değişikliği yapacaksan version artır, Git’te tut. ARM template / Bicep ile deploy et, “click ops” yapma.
  5. Compliance state’i kanıt olarak görmek: Compliance state bir rapor; gerçek audit için Activity Log + Resource Graph + Defender for Cloud verileri ile birleştir.

Bicep ile Policy as Code

Holding’de tüm policy’ler ve initiative’ler Bicep modülü olarak Git’te. Pipeline (Azure DevOps) main’e merge’de tenant root MG’ye deploy ediyor. Sample initiative deployment:

targetScope = 'managementGroup'

resource initiative 'Microsoft.Authorization/policySetDefinitions@2023-04-01' = {
  name: 'corp-baseline'
  properties: {
    displayName: 'Corp Baseline Governance'
    policyType: 'Custom'
    parameters: {
      allowedLocations: {
        type: 'Array'
        defaultValue: ['turkeywest','turkeynorth','westeurope']
      }
    }
    policyDefinitions: [
      {
        policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e56962a6-4747-49cd-b67b-bf8b01975c4c'
        parameters: {
          listOfAllowedLocations: { value: '[parameters('allowedLocations')]' }
        }
      }
      // ... diğer policy'ler
    ]
  }
}

resource assignment 'Microsoft.Authorization/policyAssignments@2023-04-01' = {
  name: 'corp-baseline-assign'
  properties: {
    policyDefinitionId: initiative.id
    enforcementMode: 'Default'
    parameters: {
      allowedLocations: {
        value: ['turkeywest','turkeynorth']
      }
    }
  }
}

Sonuç: Policy Tek Başına Bir Araç, Süreç Olarak Bakmak Lazım

Azure Policy denetim sürecini günlerden saatlere indirebilen güçlü bir araç. Ama “policy ekle, bitti” değil; sürekli tasarım, exemption yönetimi, false positive ayıklama, version’lama, Bicep ile IaC. Ekibinde Cloud Governance / FinOps rolü kuracaksan policy yönetimi onun temel sorumluluğu olur.

Holding’de 4 ay sonunda compliance score %58’den %94’e çıktı. Aylık denetim raporu otomatik üretiliyor. Yeni subscription onboarding’i 2 günden 2 saate indi (yeni MG’ye taşırsın, policy’ler inherit eder, ready). Yatırımın karşılığı tartışmasız.

CloudSpark olarak Azure Landing Zone + Policy as Code projelerinde end-to-end implementasyon hizmeti veriyoruz. Mevcut Azure ortamının governance assessment’ı için ücretsiz workshop sunuyoruz.

🇹🇷 Türkçe🇬🇧 English🇩🇪 Deutsch🇫🇷 Français🇸🇦 العربية🇷🇺 Русский🇪🇸 Español