Introduction

If you’ve ever tried to manage Azure Policy across multiple subscriptions, you’ve likely experienced the frustration: policies are random deployed across different scopes, no clear ownership, conflicting assignments.

Welcome to the world of Enterprise Policy as Code (EPAC).

EPAC isn’t just another tool, it’s a complete change in how we think about Azure governance. Instead of clicking through the portal or writing one-off scripts, EPAC uses the same appreach we apply to application code to our policy definitions. Version control. Code review. Automated testing. Predictable deployments.

In this first article of my EPAC series, I’ll explain why traditional policy management doesn’t work when it’s used on a large scale and how EPAC’s recommended way of managing things will change everything.

  • EPAC turns policy management into a versioned, reviewable, automated workflow.
  • Desired state removes drift and makes environments predictable.
  • Management group design is very important and should be planned in advance.

The Problem: Why Traditional Policy Management Breaks

Let’s be honest about what happens in most Azure environments:

The Portal Trap: Someone creates a policy in the portal. It works. They forget to document it. Six months later, no one knows why it exists or if it’s still needed.

The Scripts: A kind engineer writes a PowerShell script to set up policies. It only works the once. Then the requirements change, the script is split into two, and suddenly you have five versions of “the policy deployment script” across three repositories.

The Inheritance Nightmare: Policies assigned at the management group level can unexpectedly affect subscription-level assignments. Finding the problem is like going on an adventure.

The Drift Problem: Someone modifies a policy definition through the portal “just this once.” Now your source of truth is… somewhere.

These aren’t rare situations, they’re the normal state of policy management in organisations that have grown beyond a few subscriptions.

Enter EPAC: Desired State Management for Azure Policy

EPAC (Enterprise Policy as Code) is a PowerShell-based framework that treats your entire Azure Policy landscape as a desired state configuration. Instead of imperative commands (“create this policy,” “assign that initiative”), you declare what your policy environment should look like, and EPAC figures out how to get there.

Core Principles

1. Single Source of Truth

All policy definitions, initiatives, assignments, and exemptions live in a Git repository. If it’s not in the repo, it doesn’t exist, or shouldn’t exist.

epac-repo/
├── Definitions/
│ ├── policyDefinitions/
│ ├── policySetDefinitions/
│ └── policyAssignments/
├── Output/
└── Scripts/

2. Desired State, Not Imperative Commands

You don’t tell EPAC “create policy X.” You tell it “policy X should exist with these properties at this scope.” EPAC compares your desired state to the actual state in Azure.

3. Environment Promotion

Policies flow through environments (DEV → Non-Prod → PROD) just like application code. You test policy changes before they affect production workloads.

4. Full Lifecycle Management

EPAC handles:

  • Policy Definitions (custom policies)
  • Policy Set Definitions (initiatives)
  • Policy Assignments (with parameters and managed identity)
  • Policy Exemptions (time-bound and documented)
  • Role Assignments (for remediation tasks)

The EPAC Workflow

This is how EPAC changes your daily work:

Step 1: Define Your Policies in Code

{
"name": "require-resource-tags",
"properties": {
"displayName": "Require specific tags on resources",
"policyType": "Custom",
"mode": "Indexed",
"parameters": {
"tagName": {
"type": "String",
"metadata": {
"displayName": "Tag Name",
"description": "Name of the tag to require"
}
}
},
"policyRule": {
"if": {
"field": "[concat('tags[', parameters('tagName'), ']')]",
"exists": "false"
},
"then": {
"effect": "deny"
}
}
}
}

Step 2: Create Assignments

Assignments connect policies to scopes with specific parameters:

{
"nodeName": "/Root/",
"scope": {
"tenant1": [
"/providers/Microsoft.Management/managementGroups/production"
]
},
"assignment": {
"name": "require-cost-center-tag",
"displayName": "Require Cost Center Tag",
"description": "All resources must have a CostCenter tag"
},
"definitionEntry": {
"policyName": "require-resource-tags"
},
"parameters": {
"tagName": "CostCenter"
}
}

Step 3: Plan and Deploy

Terminal window
# Build the deployment plan
Build-DeploymentPlans -PacEnvironmentSelector "tenant1-prod"
# Review what will change
# (Output shows creates, updates, deletes)
# Deploy the changes
Deploy-PolicyPlan -PacEnvironmentSelector "tenant1-prod"

Step 4: Remediate Non-Compliant Resources

Terminal window
# Create remediation tasks for deployIfNotExists policies
Start-AzPolicyRemediation `
-Name "auto-remediation" `
-PolicyAssignmentId $assignmentId `
-ResourceDiscoveryMode ReEvaluateCompliance

Why Management Groups Matter

EPAC works best with a well-designed management group hierarchy. The Cloud Adoption Framework recommends a structure like this:

Tenant Root Group
├── Platform
│ ├── Management
│ ├── Connectivity
│ └── Identity
├── Landing Zones
│ ├── Corp
│ └── Online
├── Sandbox
└── Decommissioned

This structure enables:

  • Inheritance: Policies assigned at higher levels flow down
  • Exceptions: Lower scopes can add (but typically not remove) policies
  • Segmentation: Different policy sets for different workload types

EPAC leverages this hierarchy to manage policies at the appropriate scope. Organization-wide security policies go at the root. Workload-specific policies go at the landing zone level.

EPAC vs. Native Azure Policy Management

AspectPortal/CLIEPAC
Source of TruthAzure (scattered)Git repository
Change TrackingActivity logsGit history + PRs
RollbackManual recreationGit revert
Environment PromotionCopy/pasteAutomated pipeline
Drift DetectionManual auditEvery deployment
DocumentationExternal wikiCo-located with code
Code ReviewNot applicablePull requests

Getting Started: Prerequisites

Before diving into EPAC, ensure you have:

1. Management Group Structure

  • At minimum, separate production from non-production
  • Ideally, follow CAF recommendations

2. Azure AD App Registrations

EPAC needs service principals with appropriate permissions:

  • Reader at tenant root (to discover policies)
  • Resource Policy Contributor at deployment scopes
  • User Access Administrator for role assignments (remediation)

3. PowerShell Environment

Terminal window
# Install the EPAC module
Install-Module -Name EnterprisePolicyAsCode -Scope CurrentUser
# Verify installation
Get-Module -Name EnterprisePolicyAsCode -ListAvailable

4. Git Repository

  • Version control for your policy definitions
  • CI/CD pipeline capability (Azure DevOps or GitHub Actions)

Common Pitfalls to Avoid

Starting Too Big: Don’t try to migrate your entire policy landscape on day one. Start with a single management group and expand.

Ignoring Existing Policies: EPAC can coexist with portal-managed policies, but you need to decide. Adopt them into EPAC or mark them as “managed elsewhere.”

Skipping the Planning Phase: Build-DeploymentPlans exists for a reason. Always review the plan before deploying.

Underestimating Role Assignments: Policies with deployIfNotExists effects need managed identities with appropriate permissions. Plan these carefully.

What’s Next

In the next article, we’ll dive into EPAC pipelines—building a CI/CD workflow that takes your policies from pull request to production with appropriate gates and approvals.

We’ll cover:

  • GitHub Flow for policy changes
  • Azure DevOps pipeline configuration
  • Separation of concerns: who can modify what
  • Handling emergencies and rollbacks

Key Takeaways

  1. EPAC treats policies as code: Version control, code review, and automated deployment apply to governance, not just applications.

  2. Desired state eliminates drift: Declare what should exist. EPAC handles the delta.

  3. Management groups are foundational: A solid hierarchy makes policy management tractable.

  4. Start small, expand deliberately: Migrate one scope at a time to build confidence.

  5. Plan before you deploy: The planning phase catches mistakes before they impact production.


Sources

  1. Microsoft, “Enterprise Policy as Code,” Cloud Adoption Framework, https://learn.microsoft.com/azure/cloud-adoption-framework/ready/policy-management/enterprise-policy-as-code

  2. Microsoft, “Azure Policy Overview,” Azure Documentation, https://learn.microsoft.com/azure/governance/policy/overview

  3. Microsoft, “Management Groups,” Azure Governance Documentation, https://learn.microsoft.com/azure/governance/management-groups/overview

  4. Microsoft, “Cloud Adoption Framework Landing Zone Architecture, https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/

  5. Azure, “Enterprise Policy as Code Repository,” GitHub, https://github.com/Azure/enterprise-azure-policy-as-code

  6. Microsoft, “Remediate Non-Compliant Resources with Azure Policy,” Azure Documentation, https://learn.microsoft.com/azure/governance/policy/how-to/remediate-resources