Enterprise Policy as Code (EPAC) — Policy Documentation

Enterprise Policy as Code (EPAC) — Policy Documentation

· 4 min read

EPAC Policy Documentation: Automatically Generate Compliance Reports

If you manage Azure policy via Enterprise Policy as Code (EPAC), it’s tempting to focus all your attention on the policy assignments themselves. But EPAC also has a built-in documentation mechanism that automatically generates overview reports of everything deployed in an environment. In this blog I’ll show you how to set it up and why it’s valuable — with examples from my own EPAC repository.

What does EPAC generate as documentation?

When you run the Build-PolicyDocumentation command, EPAC reads all active policy assignments in a PAC environment and generates Markdown or CSV files from them. These files contain:

  • An overview of all assigned policies and initiatives
  • Per assignment: name, scope, enforcement mode and parameters
  • Compliance frameworks that are enabled (e.g. CIS, NIST, ISO 27001)

The result is a readable overview that you can share with auditors, clients or colleagues — without them needing access to Azure.

The folder structure

Documentation configurations live in the Definitions/policyDocumentations/ folder. The structure follows the same pattern as policy assignments: a separate subfolder per PAC environment.

Definitions/
└── policyDocumentations/
    ├── cloudmartinpronk/
    │   └── documentation.jsonc
    ├── customer-a/
    │   └── documentation.jsonc
    └── customer-b/
        └── (still empty)

Each environment gets its own documentation.jsonc. This is intentional: an MSP managing multiple client tenants wants to be able to generate a separate report per client.

The documentation file explained

A minimal configuration looks like this:

{
  "$schema": "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-documentation-schema.json",
  "documentAssignments": {
    "documentationSpecifications": [
      {
        "fileNameStem": "cloudmartinpronk-policy-overview",
        "title": "Policy Overview – cloudmartinpronk"
      }
    ],
    "documentAllAssignments": [
      {
        "pacEnvironment": "cloudmartinpronk"
      }
    ]
  }
}

Let’s walk through the key parts:

$schema Always include this. The schema gives you IntelliSense in VS Code and prevents typos. EPAC has separate schemas for assignments, definitions and documentation.

documentationSpecifications This is where you configure the output file name and title. fileNameStem is the filename without extension — EPAC automatically appends .md or .csv depending on the output option.

documentAllAssignments This is the most powerful option: with pacEnvironment you say “document everything deployed in this environment”. EPAC then automatically reads all assignments, including scope and parameters. You don’t need to list a single assignment manually.

Linking to the PAC environments

The value of pacEnvironment in the documentation file corresponds to a pacSelector in global-settings.jsonc. In my case:

// global-settings.jsonc (simplified)
{
  "pacEnvironments": [
    { "pacSelector": "cloudmartinpronk", "tenantId": "c4b5e37b-..." },
    { "pacSelector": "customer-a",       "tenantId": "aaaaaaaa-..." },
    { "pacSelector": "customer-b",       "tenantId": "bbbbbbbb-..." }
  ]
}

Each documentation file references exactly one pacSelector. This keeps the relationship one-to-one and tells EPAC which tenant to query.

What’s in the generated reports?

For an environment like cloudmartinpronk — where, among other things, the CIS Microsoft Azure Foundations Benchmark v2.0.0 is assigned — the report would contain the following information:

Assignment NameDisplay NameScopeEnforcement
cmp-cis-benchmark[cloudmartinpronk] CIS Microsoft Azure Foundations Benchmark v2.0.0/managementGroups/c4b5e37b-…Default

Per initiative, the individual policy rules are also written out with their effect (Audit, Deny, DeployIfNotExists) and any parameters. This makes the report directly usable for compliance reviews.

Generating in the pipeline

Generating documentation belongs in your CI/CD pipeline, as a separate step after deployment. In Azure DevOps or GitHub Actions you add a step:

Build-PolicyDocumentation `
  -DefinitionsRootFolder "./Definitions" `
  -OutputFolder "./Output/Documentation" `
  -PacEnvironmentSelector "cloudmartinpronk"

You can then publish the output as a pipeline artifact or commit it directly to a docs/ folder in your repository. This way you always have an up-to-date snapshot of the compliance state of each tenant.

Tips for an MSP environment

If you manage multiple client tenants from a single EPAC repository, there are a few things worth locking in:

  1. One documentation file per environment, never shared. The coupling via pacEnvironment is strict. Never create one file that combines multiple environments; this leads to confusion in the output.
  2. Use fileNameStem with the client name as a prefix. Files like customer-a-policy-overview.md are easier to distinguish than policy-overview.md when you look at them side by side.
  3. Generate documentation on pull requests too. By also generating documentation in PR pipelines (but not deploying it) you immediately see what the impact of a change is on the compliance report, before anything is rolled out.
  4. Archive versions. Save generated reports with a date in the filename or in a separate archive/ folder. This gives you a historical trail for audits.

Conclusion

EPAC’s documentation functionality is straightforward to set up — a small JSONC file per environment is all it takes — but delivers considerable value. You automatically get readable compliance overviews without any manual effort, and they always stay in sync with what is actually deployed in Azure. For an MSP managing multiple tenants, this is one of the fastest ways to become audit-ready.