Azure Policy als Code organisieren: Mit Bicep, Terraform und Initiatives ein wartbares Regelwerk pflegen

7 Minuten Felix Burkhard
Wer Azure Policy im größeren Maßstab betreibt, kommt um Policy as Code kaum herum. Definitionen, Initiatives, Assignments und Ausnahmen sollten gemeinsam versioniert und als konsistentes Regelwerk verwaltet werden.

Viele Teams beginnen mit Azure Policy im Portal. Eine Definition wird angelegt, eine zweite kommt später dazu und ein Assignment entsteht, weil eine Prüfung schnell auf einer Subscription laufen soll. Für erste Regeln ist das praktikabel. Sobald mehrere Subscriptions, gemeinsame Baselines und begründete Ausnahmen hinzukommen, müssen Definitionen, Initiativen, Assignments und Ausnahmen als versionierter Code zusammenpassen, damit das Regelwerk nachvollziehbar bleibt.

Im ersten Teil der Reihe ging es um Begriffe und Grenzen von Azure Policy. Dieser Teil zeigt, wie du die Artefakte mit Bicep oder Terraform organisierst, Initiativen sinnvoll schneidest und Änderungen kontrolliert in den Betrieb bringst.

Warum Pflege ausschließlich im Portal nicht skaliert

Das Azure-Portal ist nützlich, um vorhandene Regeln zu verstehen oder Einstellungen zu prüfen. Für einen dauerhaft nachvollziehbaren Änderungsprozess braucht es jedoch zusätzliche Disziplin und Dokumentation. Manuelle Änderungen lassen sich schwer vergleichen, JSON-Unterschiede sind im Review unhandlich und Assignments können von Definitionen oder Ausnahmen abweichen. Das fällt oft erst auf, wenn niemand erklären kann, warum eine Regel heute anders wirkt als vor drei Wochen.

Laut den Policy-as-Code-Empfehlungen von Microsoft gehören Definitionen, Initiativen, Assignments und Ausnahmen in die Versionsverwaltung und sollten über einen kontrollierten Workflow ausgerollt werden. Änderungen an einer Definition können bei der nächsten Auswertung bestehende Assignments beeinflussen. Ein Pull Request macht Diff, Review, Testumfang und eine mögliche Rücknahme sichtbar, während ein einzelner Portal-Klick diese Informationen nicht automatisch festhält.

Welche Artefakte in das Repository gehören

Viele Teams versionieren zunächst nur Policy-Definitionen. Damit fehlt ein großer Teil des Regelwerks, denn wirksam wird Azure Policy erst durch die Kette aus Definitionen, Initiativen, Assignments, Parametern und bei Bedarf kontrolliert gepflegten Ausnahmen. Eine Definition ohne Assignment hat keine Wirkung auf eine Plattform.

Die Workflow-Übersicht von Microsoft beschreibt die Artefakte und ihren kontrollierten Rollout. Die folgende Struktur ist ein mögliches Muster. Wichtig ist weniger der Ordnername als die Frage, ob erkennbar bleibt, welche Regeln existieren, wie sie gebündelt sind, wo sie zugewiesen werden und welche Ausnahmen bewusst bestehen.

Ein mögliches Repository-Skelett sieht so aus.

policy/
  definitions/
    allowed-locations.bicep
    storage-public-access-audit.bicep
  initiatives/
    platform-baseline.bicep
  assignments/
    nonprod/
      platform-baseline.bicep
    prod/
      platform-baseline.bicep
  parameters/
    nonprod.json
    prod.json
  exemptions/
    prod/
      rg-legacy-waiver.bicep

Gerade Ausnahmen, in Azure Policy Exemptions genannt, fehlen in solchen Strukturen häufig. Sie müssen als Code sichtbar sein, wenn sie nachvollziehbar und reviewbar bleiben sollen. Sonst entsteht im Portal ein zweites Regelwerk ohne Pull Request und zuverlässige Dokumentation.

Wie du Initiativen fachlich sinnvoll schneidest

Mit mehreren Baselines reichen sauber abgelegte Definitions-Dateien nicht mehr aus, denn die Struktur der Initiativen entscheidet dann, ob das Regelwerk verständlich bleibt oder in lose Einzel-Assignments zerfällt. Eine Initiative verbindet Regeln mit gemeinsamem Rollout, gemeinsamen Parametern und vergleichbarer Ausnahmelogik.

Der Schnitt nach Organigramm statt nach technischem Ziel führt häufig zu schwer verständlichen Initiativen. Sinnvoller sind Baselines für Logging und Monitoring, Netzwerk-Exposition oder Storage- und Datenregeln, weil sie häufig Parameter, Rollout-Reihenfolge und Ausnahmelogik teilen.

Eine große Initiative für Regionen, Backups, Storage, Netzwerke und Spezialfälle wirkt zunächst bequem. Später ist sie schwer zu erklären und kontrolliert zu ändern, besonders wenn die Regeln unterschiedliche Rollouts haben, bei verschiedenen Verantwortlichen liegen oder fachlich nicht zusammengehören.

Verwende wenige stabile Initiative-Parameter und sprechende Namen für policyDefinitionReferenceId. Overrides, nonComplianceMessages und Exemptions innerhalb einer Initiative beziehen sich auf diese Referenzen. Wechselnde oder kryptische Namen erschweren spätere Ausnahmen und Rollouts.

Built-in- und Custom-Policies lassen sich mischen. Built-ins sparen Pflegeaufwand, Custom-Policies schließen projektspezifische Lücken. Zusammen gehören sie nur dann in eine Initiative, wenn sie ein verständliches technisches Ziel abbilden und keine zufällige Sammlung verfügbarer Regeln ergeben.

Bicep: Wann es stark ist und worauf du achten musst

Bicep passt, wenn dein restliches Azure-Deployment bereits auf ARM und Bicep basiert. Die nativen Ressourcentypen für policyDefinitions, policySetDefinitions und policyAssignments passen in denselben Delivery-Stack. Du bleibst nah am Azure-Ressourcenmodell und kannst Referenzen zwischen Definition, Initiative und Assignment direkt modellieren.

Definitionen auf Management-Group-Ebene, Assignments auf Subscriptions oder Resource Groups und ihre Verbindungen lassen sich in Bicep gut abbilden. Die Ressourcen-IDs müssen zum jeweiligen Scope passen, denn Built-in-Definitionen liegen tenantweit, eigene Definitionen meist auf Subscription- oder Management-Group-Scope. managementGroupResourceId, subscriptionResourceId und tenantResourceId liefern IDs für unterschiedliche Scopes. Ein falscher Scope fällt oft erst beim Deployment auf. Plane deshalb die erforderlichen Berechtigungen für Management Group und Ziel-Subscription ein und teste beide Deployment-Scopes früh.

Das folgende Beispiel zeigt eine bestehende Definition, eine Initiative und ein Assignment auf einer Subscription. Die Initiative reicht den Parameter für erlaubte Regionen an die Definition weiter. Die stabile policyDefinitionReferenceId ist später die Grundlage für gezielte Exemptions, Overrides und nonComplianceMessages.

targetScope = 'managementGroup'

param targetSubscriptionId string

// Ressourcen-ID einer bestehenden Built-in- oder Custom-Policy-Definition.
param allowedLocationsDefinitionId string

resource platformBaseline 'Microsoft.Authorization/policySetDefinitions@2023-04-01' = {
  name: 'platform-baseline'
  properties: {
    policyType: 'Custom'
    displayName: 'Platform Baseline'
    parameters: {
      allowedLocations: {
        type: 'Array'
      }
    }
    policyDefinitions: [
      {
        policyDefinitionId: allowedLocationsDefinitionId
        policyDefinitionReferenceId: 'allowedLocations'
        parameters: {
          allowedLocations: {
            value: '[parameters(''allowedLocations'')]'
          }
        }
      }
    ]
  }
}

resource platformBaselineAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
  name: 'platform-baseline-nonprod'
  scope: subscription(targetSubscriptionId)
  properties: {
    displayName: 'Platform Baseline für Non-Prod'
    policyDefinitionId: platformBaseline.id
    parameters: {
      allowedLocations: {
        value: [
          'westeurope'
          'northeurope'
        ]
      }
    }
  }
}

allowedLocationsDefinitionId verweist auf eine vorhandene Policy-Definition. Die Initiative bildet die fachliche Baseline, während die Definition einzeln wartbar bleibt. Das Assignment setzt nur Parameter, die auf seinem Scope variieren.

Bicep wird unübersichtlich, wenn vollständige JSON-Blöcke inline wachsen. Halte Definitionen klein, setze Parameter gezielt ein und bündele Speziallogik nicht für mehrere Regeln in einer Datei. So bleibt das Regelwerk auch bei wachsenden Anforderungen nachvollziehbar.

Terraform: Wo es überzeugt und welche Eigenheiten wichtig sind

Terraform passt, wenn deine Plattform providerübergreifend oder bereits stark terraformlastig aufgebaut ist. Dann folgt Azure Policy denselben Plan- und Apply-Prozessen, Review-Mustern und Teamkonventionen wie der Rest der Infrastruktur, was besonders Teams hilft, die nicht nur Azure-Ressourcen verwalten.

Die relevanten Ressourcen sind überschaubar. Custom-Definitionen werden über azurerm_policy_definition gepflegt, Initiativen über azurerm_policy_set_definition. Für Assignments gibt es scope-spezifische Ressourcen wie azurerm_subscription_policy_assignment und azurerm_management_group_policy_assignment. Das unterstützt klar geschnittene Module, verlangt aber Disziplin bei JSON-Fragmenten und Provider-Eigenheiten.

Das folgende reduzierte Beispiel zeigt eine Definition mit ihrem Assignment. Der Anwendungsfall bleibt derselbe wie bei Bicep, die Initiative ist hier bewusst ausgeblendet. So bleibt sichtbar, wie Terraform Policy-Parameter als JSON weitergibt.

resource "azurerm_policy_definition" "allowed_locations" {
  name         = "allowed-locations-core"
  policy_type  = "Custom"
  mode         = "Indexed"
  display_name = "Nur freigegebene Regionen verwenden"

  metadata = jsonencode({
    category = "General"
  })

  parameters = jsonencode({
    allowedLocations = {
      type = "Array"
      metadata = {
        displayName = "Erlaubte Azure-Regionen"
      }
    }
  })

  policy_rule = jsonencode({
    if = {
      allOf = [
        {
          field   = "location"
          "notIn" = "[parameters('allowedLocations')]"
        },
        {
          field       = "location"
          "notEquals" = "global"
        }
      ]
    }
    then = {
      effect = "audit"
    }
  })
}

resource "azurerm_subscription_policy_assignment" "allowed_locations_nonprod" {
  name                 = "allowed-locations-nonprod"
  display_name         = "Erlaubte Regionen für Non-Prod"
  subscription_id      = "/subscriptions/00000000-0000-0000-0000-000000000000"
  policy_definition_id = azurerm_policy_definition.allowed_locations.id

  parameters = jsonencode({
    allowedLocations = {
      value = ["westeurope", "northeurope"]
    }
  })
}

Terraform ist passend, wenn ein einheitlicher Infrastructure-as-Code-Workflow wichtiger ist als maximale Nähe zum Azure-Ressourcenmodell. Bestehende Portal-Assignments musst du importieren, bevor Terraform sie verwalten kann. Unterschiede zwischen generischen und scope-spezifischen Ressourcen, Idempotenz und Drift gehören in den Testplan, weil kleine Abweichungen in Definition oder Scope große operative Wirkung haben können.

Versionsstrategie, Reviews und Promotion durch Umgebungen

Versionierte Dateien reichen nicht aus, denn eine Änderung an einer Definition oder Initiative gehört in einen Pull Request und zunächst in einen nicht-produktiven Validierungs-Scope. Dort prüfst du ihre Wirkung auf bestehende Ressourcen, bevor sie einen breiteren Scope erreicht. Ein späterer Teil der Reihe behandelt den Rollout mit Tiers, Effect-spezifischer Aktivierung, Gates, notScopes und Exemptions.

Bei Built-in-Definitionen ist Versionierung ein zusätzlicher Prüfpunkt. Microsoft beschreibt, wie sich Assignments auf bestimmte Major- oder Minor-Versionen festlegen lassen. Entscheide den verwendeten Versionspfad bewusst und prüfe Änderungen vor einem breiten Einsatz. Die Safe-Deployment-Practices von Microsoft zeigen einen geeigneten Rahmen dafür.

Auch Exemptions brauchen denselben Prozess. Eine Ausnahme sollte mit Scope, Begründung, Ablaufdatum und Review im Code sichtbar sein. Sonst kann etwa eine Ausnahme für eine Legacy-Resource-Group nach dem Projektabschluss im Portal bestehen bleiben und in Vergessenheit geraten.

Ein wartbares Regelwerk entsteht im Alltag

Bei Azure Policy als Code steht Wartbarkeit vor der Werkzeugwahl. Versioniere alle relevanten Artefakte, wähle Bicep oder Terraform passend zum Plattform-Stack und mache Scopes, Parameter und Ausnahmen im Code sichtbar. Dann lassen sich Regeln testen, erklären und ändern, ohne dass einzelne Portal-Änderungen zu Wissenslücken führen.

Beginne mit einer kleinen Baseline, deren Regeln, Assignments und Ausnahmen ein Team erklären kann. Ein der nächste Teil der Serie zeigt, wie du Regeln sicher ausrollst und welche Probleme Scopes, Vererbung, notScopes und Exemptions im Betrieb verursachen können.

Wie pflegt ihr Azure Policy heute, noch im Portal oder bereits in einem nachvollziehbaren IaC-Workflow? Schau gerne bei uns auf LinkedIn vorbei und diskutiere mit.

Autoren

Felix Burkhard

Felix Burkhard

Felix ist Solution Architect mit Fokus auf skalierbare Azure-IoT-Lösungen, agile Entwicklungsprozesse und DevOps. Er verbindet tiefes technisches Verständnis mit pragmatischer Umsetzung und schafft so nachhaltigen Geschäftsnutzen.