This chapter covers Policy as Code using Bicep and Terraform, a critical topic for the AZ-500 exam under Security Operations (Objective 4.3). Understanding how to enforce security policies through infrastructure-as-code (IaC) is essential for automating compliance and reducing manual errors. Approximately 10-15% of AZ-500 exam questions touch on policy enforcement, including Azure Policy, Blueprints, and IaC integration. This chapter will give you the deep technical knowledge needed to answer those questions confidently.
Jump to a section
Imagine a city where every construction project must comply with a detailed building code. Instead of having inspectors manually review each blueprint, the city creates a digital permit system that automatically checks plans against the code. Architects write their designs in a structured language (like Bicep or Terraform), which the system parses into a set of rules. The system then runs a series of automated checks: it verifies that the foundation depth meets the minimum 1.5 meters for clay soil, that fire escapes are within 30 meters of every point, and that electrical wiring uses gauge 12 or thicker. If any rule fails, the permit is denied with a specific error message. The city's code is stored in a Git repository, version-controlled, and any change to the code triggers a new automated review of all pending projects. This is exactly how Policy as Code works: infrastructure definitions (the blueprints) are validated against a set of policies (the building code) automatically, before any resources are provisioned. The policies are written in code, stored in source control, and enforced continuously. Just as the digital permit system prevents unsafe buildings, Policy as Code prevents non-compliant cloud resources from ever being created.
What Is Policy as Code and Why Does It Exist?
Policy as Code (PaC) is the practice of defining and enforcing security and compliance policies through machine-readable code, integrated into the infrastructure deployment pipeline. In Azure, this primarily involves Azure Policy, which can be used to audit or enforce rules on resources. Bicep and Terraform are declarative IaC languages that define Azure resources. When combined with Azure Policy, they enable 'shift-left' security: catching violations at design time (during template validation) rather than after deployment.
The need for PaC arises because manual compliance checks are slow, error-prone, and inconsistent. In enterprise environments with thousands of resources, manual review is impossible. By codifying policies, organizations can automatically prevent misconfigurations like open network security groups, unencrypted storage accounts, or public blob access. Azure Policy evaluates resources against policy definitions and can deny creation, audit existing resources, or append tags.
How Bicep and Terraform Work with Azure Policy
Bicep is a domain-specific language (DSL) for Azure Resource Manager (ARM) templates. It compiles to standard ARM JSON. Terraform is an open-source IaC tool by HashiCorp that uses its own state management and provider model. Both can be integrated with Azure Policy through: - Built-in policy definitions: Azure provides over 1,000 built-in policies covering security, networking, storage, compute, and more. - Custom policy definitions: Written in JSON using the Azure Policy definition schema. - Policy initiatives (set definitions): Groups of policies that enforce a common goal, such as 'ISO 27001' or 'CIS Benchmark'. - Policy assignments: Apply policies to a management group, subscription, or resource group.
Integration Mechanism:
1. Design-time validation: When using Bicep or Terraform, you can run az deployment group validate or terraform plan to check resources against Azure Policy before deployment. This requires the 'Guest Configuration' extension or using the Azure Policy as Code workflow.
2. Deployment-time enforcement: Azure Policy denies non-compliant resource creation at the moment of deployment. The IaC tool receives an HTTP 403 (Forbidden) error, and the deployment fails.
3. Post-deployment audit: Policies set to 'Audit' will mark resources as non-compliant but not block them. This is useful for brownfield environments.
Key Components and Defaults
Policy definition structure: Contains if (condition) and then (effect) blocks. Effects include: Deny, Audit, Append, AuditIfNotExists, DeployIfNotExists, Disabled, and Modify.
Default effect: If not specified, the policy does nothing (effectively Disabled). Most built-in policies default to Audit.
Policy rule conditions: Use operators like equals, like, contains, in, exists, allOf, anyOf, not. Conditions can evaluate resource properties, tags, locations, SKUs, etc.
Aliases: Resource property paths used in policy rules. For example, Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly.
Policy assignment parameters: Policies can have parameters (e.g., allowedLocations) that are set at assignment time.
Initiative parameters: Initiatives can group policies and pass shared parameters.
Configuration and Verification Commands
Azure CLI examples:
List built-in policies:
az policy definition list --query "[?policyType=='BuiltIn']" --output tableCreate a custom policy definition:
az policy definition create --name "deny-public-ip" --rules @policy.json --mode AllAssign a policy to a subscription:
az policy assignment create --name "deny-public-ip-assign" --policy "deny-public-ip" --scope /subscriptions/{subscriptionId}Validate a Bicep template against policies:
az deployment group validate --resource-group myRG --template-file main.bicepTerraform plan with policy check (using AzureRM provider):
terraform plan -var-file="prod.tfvars"Interaction with Related Technologies
Azure Blueprints: Blueprints can include policy assignments as artifacts. They provide a repeatable set of Azure resources and policies for compliance.
Azure DevOps / GitHub Actions: CI/CD pipelines can run policy validation as a gate. For example, using the AzurePolicyCheck@1 task in Azure DevOps.
Azure Resource Graph: Query resources for compliance state using KQL. Example: where type =~ 'microsoft.authorization/policyassignments'.
Azure Management Groups: Hierarchical structure for policy inheritance. Policies applied at a management group affect all child subscriptions.
Guest Configuration: Extends policy to inside VMs (OS configuration, installed software, etc.).
Policy as Code with Bicep: Specifics
Bicep supports the resource keyword to define policy definitions and assignments directly in templates. Example:
resource policyDef 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
name: 'deny-location'
properties: {
policyType: 'Custom'
mode: 'All'
displayName: 'Deny specific locations'
policyRule: {
if: {
field: 'location'
in: ['eastus', 'westus']
}
then: {
effect: 'Deny'
}
}
}
}Note: mode must be set correctly. All evaluates all resource properties, Indexed only for resource types that support tags and location.
Policy as Code with Terraform: Specifics
Terraform uses the azurerm_policy_definition resource. Example:
resource "azurerm_policy_definition" "deny_location" {
name = "deny-location"
policy_type = "Custom"
mode = "All"
display_name = "Deny specific locations"
policy_rule = <<POLICY_RULE
{
"if": {
"field": "location",
"in": ["eastus", "westus"]
},
"then": {
"effect": "Deny"
}
}
POLICY_RULE
}Terraform also has azurerm_policy_assignment and azurerm_policy_set_definition resources. The azurerm_policy_virtual_machine_configuration_assignment is used for Guest Configuration.
Advanced: Policy Exemptions and Exclusions
Exemptions: Can be created for a specific scope (e.g., a resource group) to waive a policy. Useful for temporary exceptions. Exemptions have an expiration date.
Exclusions: Within a policy assignment, you can exclude child scopes (e.g., exclude a specific resource group from a subscription-level assignment).
Compliance States
Azure Policy evaluates resources and reports one of the following states: - Compliant: Resource meets policy conditions. - Non-compliant: Resource violates policy. - Conflict: Two or more policies conflict (rare). - Error: Evaluation failed (e.g., invalid policy rule). - Not started: Evaluation pending.
Performance and Scale Considerations
Policy evaluation is near real-time for new resources. For existing resources, a compliance scan runs every 24 hours by default, but can be triggered on demand.
Large numbers of policies (hundreds) can slow down deployment. Azure Policy has limits: maximum 500 policy definitions per subscription, 200 initiatives per subscription.
Use DeployIfNotExists effects carefully as they trigger remediation tasks, which can be resource-intensive.
Security Implications
Policy definitions themselves should be protected. Only authorized users (e.g., Security Admin, Owner) should be able to create or modify policies.
A malicious policy with DeployIfNotExists could deploy unauthorized resources. Use Azure RBAC to restrict write access to policy resources.
Audit policies first before switching to Deny to avoid breaking existing deployments.
Exam Tip: Common Trap
A common exam question asks: 'Which effect should you use to block non-compliant resource creation?' The answer is Deny. But candidates often choose Audit because they think it's safer. Audit only logs violations; it does not block. Another trap: Append can modify resources (e.g., add tags) but does not block creation. For blocking, use Deny.
Define Policy Rules as Code
Start by writing policy definitions in JSON. Each definition has a `policyRule` with an `if` condition and `then` effect. The condition uses fields, aliases, and operators. For example, to deny storage accounts without HTTPS, the field is `Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly` with a condition `equals false`. The effect is `Deny`. Use the Azure portal or CLI to test the rule with sample resources. Ensure the `mode` is set to `All` for most security policies, or `Indexed` for location/tag policies. This step is crucial because a miswritten rule may not evaluate correctly, leading to false compliance.
Create Policy Definition in Azure
Deploy the policy definition to Azure using Bicep, Terraform, or Azure CLI. For Bicep, use the `Microsoft.Authorization/policyDefinitions` resource type. For Terraform, use `azurerm_policy_definition`. The definition is stored at a scope (management group, subscription, or resource group). Once created, it can be referenced by assignments. Use the `az policy definition create` command to verify. The definition ID follows the format `/providers/Microsoft.Authorization/policyDefinitions/{name}`. This ID is used in assignments.
Assign Policy to a Scope
Assign the policy definition to a management group, subscription, or resource group using Bicep, Terraform, or CLI. The assignment includes parameters (e.g., list of allowed locations). Use the `az policy assignment create` command. The assignment scope is specified with `--scope`. For example, `/subscriptions/12345/resourceGroups/myRG`. The assignment creates a compliance evaluation of existing resources. The evaluation runs asynchronously; you can check compliance via portal or Azure Resource Graph.
Integrate with IaC Pipeline
In your CI/CD pipeline (Azure DevOps, GitHub Actions), add a step to run `az deployment group validate` for Bicep or `terraform plan` for Terraform. This step checks the deployment against assigned policies. If any resource violates a policy with `Deny` effect, the validation fails and the pipeline stops. For `Audit` policies, the validation passes but compliance is flagged. Use the `AzurePolicyCheck` task in Azure DevOps to run policy checks before deployment. This ensures no non-compliant resources are deployed.
Monitor and Remediate Compliance
After deployment, monitor compliance using Azure Policy dashboard or Azure Resource Graph queries. For example: `policyResources | where type == 'microsoft.policyinsights/policystates'`. For non-compliant resources, you can trigger remediation tasks for policies with `DeployIfNotExists` or `Modify` effects. Remediation tasks can be run manually or automatically via policy assignment. Use the `az policy remediation create` command. Set up alerts for compliance state changes to quickly respond to violations.
Enterprise Scenario 1: Enforcing Encryption at Rest for All Storage Accounts
A financial services company must ensure all storage accounts have encryption enabled. They create a custom policy definition that checks the encryption.services.blob.enabled property. If it is false, the effect is Deny. They assign the policy at the subscription level. The DevOps team integrates this into their Terraform pipeline. When a developer tries to deploy a storage account without encryption, terraform plan succeeds but terraform apply fails with a 403 error from Azure Resource Manager. The error message includes the policy name and reason. The team then uses the Azure Policy compliance dashboard to audit existing storage accounts; those without encryption are listed as non-compliant. They run a remediation task that enables encryption via a DeployIfNotExists policy. This scenario is typical in regulated industries where encryption is mandatory.
Enterprise Scenario 2: Restricting VM SKUs to Approved Sizes
A large e-commerce company wants to control costs and ensure performance by allowing only specific VM SKUs (e.g., D2s_v3, D4s_v3). They create a custom policy using the Microsoft.Compute/virtualMachines/sku.name field. The condition uses notIn with a list of approved SKUs, and the effect is Deny. They assign the policy to the production subscription. The infrastructure team uses Bicep to define VMs. During deployment, the Bicep validation catches non-approved SKUs and fails the pipeline. Developers receive immediate feedback. The company also uses policy exemptions for a temporary test environment, with an expiration date. This prevents cost overruns and ensures consistency.
Common Misconfiguration: Policy Mode Mismatch
A common mistake is setting the policy mode to Indexed when the condition uses a non-indexed property (like encryption). This causes the policy to not evaluate the property, resulting in false compliance. Always use All mode for security-related policies. Another misconfiguration is forgetting to assign the policy to the correct scope; if assigned to a resource group instead of the subscription, resources in other resource groups are not evaluated. Also, using Append instead of Deny for blocking policies leads to resources being created but then modified, which may not be acceptable for security requirements.
What AZ-500 Tests on Policy as Code
AZ-500 Objective 4.3: 'Manage security posture by identifying and remediating vulnerabilities using Azure Policy and Azure Blueprints, including implementing Policy as Code with Bicep and Terraform.' Exam questions focus on:
Understanding the difference between Deny, Audit, Append, DeployIfNotExists, and Modify effects.
Knowing when to use custom vs. built-in policies.
How to integrate policy evaluation into CI/CD pipelines.
How to use Azure Blueprints to deploy policy assignments.
The role of management groups in policy inheritance.
Common Wrong Answers and Why Candidates Choose Them
Wrong: 'Use Audit effect to block non-compliant resources.' Candidates think Audit is a soft block, but it only logs. The correct effect for blocking is Deny.
Wrong: 'Policy assignments must be at the resource group level.' While possible, policies can be assigned at management group, subscription, and resource group. The exam tests inheritance from management groups.
Wrong: 'Custom policies override built-in policies.' There is no override; if both apply, the most restrictive effect wins? Actually, if two policies conflict, the resource is marked as 'Conflict'. The exam expects you to know that policies are additive.
Wrong: 'Policy evaluation happens only during deployment.' Azure Policy also evaluates existing resources on a schedule (every 24 hours) and on-demand.
Specific Numbers and Terms on the Exam
Maximum policy definitions per subscription: 500.
Maximum initiatives per subscription: 200.
Default compliance scan interval: 24 hours.
Policy definition mode: All vs Indexed. Exam tests which mode is appropriate for different conditions.
Policy effects: Know the exact behavior of each: Deny (blocks), Audit (logs), Append (adds fields), DeployIfNotExists (deploys resources), Modify (adds or alters tags/SKU).
Edge Cases and Exceptions
Exemptions: Can be used to waive a policy for a specific resource. They have an expiration date. Exam may ask: 'You need to temporarily allow a resource that violates policy. What should you do?' Answer: Create an exemption.
Policy with `DeployIfNotExists` effect: This effect requires a managed identity and can deploy resources. The exam may test that this effect does not block creation; it runs after the resource is created.
Initiative vs. Definition: An initiative groups multiple policies. The exam may ask: 'You need to enforce multiple related policies. What should you create?' Answer: An initiative (policy set definition).
How to Eliminate Wrong Answers
If the question asks about 'blocking' deployment, eliminate any option that uses Audit or Append.
If the question involves 'existing resources', remember that Deny only blocks new resources; for existing, you need DeployIfNotExists or manual remediation.
If the question mentions 'compliance dashboard', look for options involving Azure Policy compliance view or Azure Resource Graph.
If the question mentions 'CI/CD pipeline', look for options that involve az deployment validate or AzurePolicyCheck task.
Policy as Code enforces security rules at deployment time, preventing non-compliant resources.
Azure Policy effects: Deny (blocks), Audit (logs), Append (modifies), DeployIfNotExists (deploys), Modify (alters tags/SKU).
Policy definitions have a mode: All (evaluates all properties) or Indexed (only tags/location).
Policy assignments can be scoped to management groups, subscriptions, or resource groups.
Bicep and Terraform can both define and deploy policy definitions and assignments.
Integration with CI/CD pipelines catches violations before deployment.
Azure Policy compliance scan runs every 24 hours for existing resources.
Maximum 500 policy definitions and 200 initiatives per subscription.
Exemptions allow temporary waivers with expiration dates.
Use `DeployIfNotExists` to remediate non-compliant existing resources.
These come up on the exam all the time. Here's how to tell them apart.
Bicep
Native to Azure, compiles to ARM JSON.
Uses declarative syntax with modules.
State management is handled by Azure Resource Manager.
Policy definitions use `Microsoft.Authorization/policyDefinitions` resource type.
Supports `az deployment validate` for pre-flight checks.
Terraform
Cloud-agnostic, supports multiple providers.
Uses HashiCorp Configuration Language (HCL).
Maintains its own state file (local or remote).
Policy definitions use `azurerm_policy_definition` resource.
Supports `terraform plan` which can show policy violations if integrated.
Mistake
Azure Policy can only be applied at the subscription scope.
Correct
Azure Policy can be assigned at management group, subscription, and resource group scopes. Management group assignments are inherited by all child subscriptions.
Mistake
Audit effect blocks non-compliant resource creation.
Correct
Audit only logs the violation; it does not block. To block, use Deny effect.
Mistake
Custom policy definitions can override built-in policies.
Correct
Custom and built-in policies are evaluated independently. If both apply, the resource may be marked as non-compliant if it violates either. There is no precedence.
Mistake
Policy evaluation only happens at deployment time.
Correct
Azure Policy evaluates resources continuously. New resources are evaluated at creation; existing resources are scanned every 24 hours by default.
Mistake
Terraform and Bicep cannot create policy definitions.
Correct
Both Bicep and Terraform can define and deploy policy definitions and assignments using their respective resource declarations.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
Azure Policy evaluates resource properties and enforces rules (e.g., 'must have encryption'), while Azure RBAC controls who can perform actions on resources (e.g., 'only admins can delete VMs'). Policy is about what resources are allowed; RBAC is about who can manage them. Both are complementary and often used together.
Yes, Terraform has the `azurerm_policy_definition` resource. You can define the policy rule as a JSON string and deploy it. Similarly, `azurerm_policy_assignment` and `azurerm_policy_set_definition` are available.
You can use the Azure Policy compliance dashboard to test a policy on a subset of resources. Alternatively, use the `az policy definition create` with `--mode All` and then assign it to a test scope. You can also use the Azure Policy Test Tool in the portal or run `az policy state trigger-scan` to manually trigger evaluation.
If two policies assign different effects to the same resource property, the resource is marked as 'Conflict' in compliance state. Azure Policy does not resolve conflicts; you must modify the policies to remove the conflict.
Yes, Azure Policy evaluates any resource creation attempt, whether it comes from an IaC template, portal, CLI, or API. If the resource violates a Deny policy, the creation is blocked regardless of the method.
A policy initiative (also called a policy set definition) is a group of policy definitions that are assigned together to achieve a common goal, such as 'CIS Benchmark for Azure'. Initiatives can have parameters that are shared across included policies.
You can create an exemption for that resource. Exemptions can be applied at the resource, resource group, or subscription level and can have an expiration date. Alternatively, you can exclude child scopes when assigning the policy (e.g., exclude a specific resource group).
You've just covered Policy as Code with Bicep and Terraform — now see how well it sticks with free AZ-500 practice questions. Full explanations included, no account needed.
Done with this chapter?