Open-source tool · AZPaC
Azure Policy as Code (AZPaC)
Manage Azure Policy with Terraform and pull requests.
AZPaC manages Azure Policy with Terraform. You write a policy once, put it in an initiative and decide where it applies, all in files reviewed through a pull request. The Terraform engine is generic: you only edit policy JSON files and one tfvars file per initiative. It runs against a real tenant, and the pipelines deploy it.
- Built with
- Terraform (HCL)
- Pipelines
- 3 (Azure DevOps)
- Modules
- definition, initiative, assignment, exemption
- License
- MIT
Why it exists
Questions it answers
Who created this assignment, and why?
Every change is a pull request with history and a reviewer.
Why is a "temporary" exemption still here years later?
Every exemption needs an expiry date and an approver who is not the requester. The pipeline enforces both.
How do I turn enforcement on safely?
One switch per initiative: mode = "audit" or "enforce", changed in one file and approved by someone else.
What changed outside Terraform?
A nightly drift pipeline finds Portal edits and assignments or exemptions that Terraform cannot see.
Features
What it does
One switch for enforcement
- Write each effect once, as the final goal (for example Deny)
- Audit mode downgrades Deny to Audit and DeployIfNotExists to AuditIfNotExists
- Audit mode also sets the assignment to DoNotEnforce, a second safety layer
- Promoting is a one-word change
Scopes and exemptions
- Assign to management groups so new subscriptions inherit
- Per-subscription parameter overrides only where values differ
- Exemptions as Waiver or Mitigated, with owner, ticket and expiry
- Built-in policies referenced by GUID, custom ones from JSON files
Pipelines
- Definitions: validate, plan per initiative, then apply the saved plan after approval
- Drift: nightly check for Portal changes and unmanaged policy objects
- Remediation: weekly tasks that fix existing non-compliant resources
- Applies run one after another, so a failure stops the chain
Conventions
- Definitions filed by Azure category, shared between initiatives
- Names follow <Effect>-<Subject>-<What>, as in the Azure Landing Zone library
- The filename is the policy name and the reference ID
- Documented rollback, from seconds to minutes, without terraform destroy
How it fits together
Architecture
- 01Policy JSON + initiative tfvars in Git
- 02Pull request: validate and plan
- 03Approval
- 04Apply the saved plan
- 05Nightly drift + weekly remediation
Quick start
Try it
cd tf-policy
# set tenant and management group in globals.auto.tfvars
# point backend-config/INE01_backend.conf at your state storage
terraform init -backend-config=backend-config/INE01_backend.conf
terraform plan -var-file=initiatives/INE01.tfvars
terraform apply -var-file=initiatives/INE01.tfvarsaz policy definition show --name <guid> --query displayName -o tsvThe full setup guide is in the README on GitHub.
Before you start
Permissions needed
| Definitions, initiatives, assignments | Resource Policy Contributor on the management group |
| Terraform state | Storage Blob Data Contributor on the state storage account |
| Remediation (when enforcing) | User Access Administrator or Role Based Access Control Administrator, ideally on a separate service principal |
Good to know
Always start in audit mode: the list of failures is the list of things to fix or exempt before you enforce. Start with the small INE02 tagging initiative. Never use terraform destroy to fix a policy problem, because it removes every control and exemption for that initiative.
