1.6.11.1. Conftest
With the prevalence of Infrastructure as Code (IaC), ensuring configuration compliance and policy consistency in large-scale Terraform module governance has become a significant challenge. Conftest was born in this context as a Policy-as-Code tool. It is a command-line tool for writing tests against structured configuration data, relying on the Rego policy language provided by the CNCF open-source project Open Policy Agent (OPA). Simply put, Conftest allows us to write policy tests for configuration files (such as Terraform module code, Kubernetes configurations, Tekton Pipeline definitions, etc.) to automatically check if configurations meet predefined policy requirements before deployment. By integrating compliance policies into code testing, Conftest acts as a "gatekeeper" in infrastructure governance, helping teams discover non-compliant configurations before deployment and avoiding bringing hidden dangers into the production environment.
I have already given a relatively systematic introduction to the Rego rule language in Rego 101 Chinese Tutorial, so this book will not repeat the details of Rego.
Why do we need Conftest? In IaC scenarios, developers can write and modify Terraform configuration code very quickly, leading to frequent and large-scale configuration changes. Without effective compliance review mechanisms, resource configurations that do not meet security or specification requirements are likely to appear. For example, an infrastructure created by a Terraform configuration might miss necessary encryption settings or open network ports that should not be open. Finding such issues through manual code review alone is not only time-consuming and labor-intensive but also difficult to guarantee no omissions.
Conftest provides an automated solution: we can write organizational compliance requirements and best practices into executable policy code, and run these policy checks automatically with the tool every time the Terraform configuration changes. Once a configuration violating the policy is detected, Conftest will immediately issue a failure report, preventing non-compliant code from advancing to subsequent stages. In this way, Conftest shifts compliance checks left, enabling "early prevention and early detection" before code merge or deployment, significantly reducing the burden of human review and providing consistent policy guardrails for large-scale infrastructure code.
1.6.11.1.1. Practical application of Conftest in Terraform scenarios
Conftest works very flexibly with Terraform. The typical process involves using Terraform to generate a parsable plan file, and then using Conftest to test it against policy rules. Here is a basic workflow integrated with Terraform:
Generate Terraform execution plan JSON file: First, run the Terraform plan command to export the infrastructure change plan as a JSON format file. For example:
terraform init && terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
Through terraform plan and terraform show -json, we obtain detailed structured data output of the current Terraform changes. This tfplan.json file contains all information about resources that Terraform plans to create, modify, or destroy, providing basic data for policy checks.
Write policy tests (Rego files): Before starting testing, we need to write Rego rule files that represent compliance policies. For example, we can formulate a policy prohibiting the use of unapproved Terraform modules or mandating that resources include specific tags, or perhaps, all newly created AWS S3 buckets must have encryption and versioning enabled, otherwise returning a violation and breaking the pipeline. Although a deep understanding of Rego syntax helps in writing complex policies, in general cases, we can quickly get started writing these rules by borrowing from community examples without needing to master Rego.
Run Conftest for policy checking: With the Terraform plan JSON and policy files, we can use Conftest to test whether the configuration is compliant. Execute the command:
conftest test -p /path/to/policy/dir tfplan.json
Here, the -p parameter specifies the directory where policy files are located (by default, Conftest also searches for .rego policy files in the policy subdirectory of the current directory). Conftest will read the contents of tfplan.json, apply all policy rules we defined, and report every non-compliance. Example output result:
FAIL - tfplan.json - Module terraform-azurerm-compute.git?ref=tags/v0.1 is not in allowed range.
The hypothetical output above indicates that the test failed because the Terraform plan used a module version that is not allowed. For the same tfplan.json file, if we correct the configuration to comply with group policies and run the test again, we should get a result where all tests pass, for example:
1 tests, 1 passed, 0 warnings, 0 failures, 0 exceptions
Through such feedback, we can clearly know which parts violate the policy and what modifications need to be made in the Terraform configuration.
By practicing Conftest, we embed compliance policy verification into the Terraform module development process. This not only automates many tasks that originally required manual inspection but also makes policy execution consistent and traceable. Especially in scenarios with a large number of modules and multi-person collaboration, Conftest helps teams ensure that Terraform modules written by anyone follow the same security and specification guidelines.
In actual projects, we can integrate Conftest into the CI/CD pipeline to achieve continuous policy checking. For example:
- Automatic review in Pull Request stage: When someone submits a Pull Request for Terraform configuration, run
terraform planin CI and test the generated plan with Conftest. If the policy check fails, code merging is blocked. This is equivalent to automated code review, saving time for manual item-by-item inspection. - Integration with deployment process: Add a Conftest step in the pipeline before formal deployment to ensure that only Terraform plans passing all policy tests can execute
terraform apply. This adds insurance to the production environment, preventing non-compliant changes from being deployed online. - Cooperation with existing tools: If the team uses Terraform automation tools like HCP Terraform or Atlantis, they can be configured to call Conftest for policy verification after each plan. This way, the entire process from code submission, review to deployment is guarded by policies, truly achieving end-to-end compliance for infrastructure changes.
1.6.11.1.2. Integration of Conftest in AVM
AVM (Azure Verified Modules) uses the Conftest tool to automate compliance and policy checking in module pipelines. Specifically, AVM implements policy verification for Terraform modules through three parts: script encapsulation, Makefile integration, and GitHub Actions workflows.
- Script Encapsulation and Execution (
conftest.sh)
AVM provides a dedicated script conftest.sh, which encapsulates the process of executing Conftest tests on modules:
#!/usr/bin/env bash
set -e
has_error=false
if [ ! -d "examples" ]; then
echo "No \`examples\` folder found."
exit 0
fi
cd examples
for d in $(find . -maxdepth 1 -mindepth 1 -type d); do
if ls "$d"/*.tf > /dev/null 2>&1; then
cd "$d"
echo "==> Checking $d"
if [ -f ".e2eignore" ]; then
echo "==> Skipping $d due to .e2eignore file"
cd - >/dev/null 2>&1
continue
fi
# run pre.sh if it exists
if [ -f "./pre.sh" ]; then
echo "==> Running pre.sh"
chmod +x ./pre.sh
./pre.sh
fi
echo "==> Initializing Terraform..."
terraform init -input=false
echo "==> Running Terraform plan..."
terraform plan -input=false -out=tfplan.binary
echo "==> Converting Terraform plan to JSON..."
terraform show -json tfplan.binary > tfplan.json
mkdir -p ./policy/default_exceptions
curl -sS -o ./policy/default_exceptions/avmsec_exceptions.rego https://raw.githubusercontent.com/Azure/policy-library-avm/refs/heads/main/policy/avmsec/avm_exceptions.rego.bak
if [ -d "exceptions" ]; then
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy/Azure-Proactive-Resiliency-Library-v2 -p policy/aprl -p policy/default_exceptions -p exceptions tfplan.json || has_error=true
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy/avmsec -p policy/avmsec -p policy/default_exceptions -p exceptions tfplan.json || has_error=true
else
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy/Azure-Proactive-Resiliency-Library-v2 -p policy/aprl -p policy/default_exceptions tfplan.json || has_error=true
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy/avmsec -p policy/avmsec -p policy/default_exceptions tfplan.json || has_error=true
fi
# run post.sh if it exists
if [ -f "./post.sh" ]; then
echo "==> Running post.sh"
chmod +x ./post.sh
./post.sh
fi
cd - >/dev/null 2>&1
fi
done
cd ..
if [ "$has_error" = true ]; then
echo "At least one \`examples\` folder failed."
exit 1
fi
echo "All \`examples\` folders passed."
exit 0
For each example under examples, this script runs Terraform commands to generate a plan file in JSON format, and then runs the check rules defined in policy-library-avm against the plan.
Note that if the module maintainer decides to skip checking certain rules for a specific sample code, they can create an exceptions folder in that sample directory and define ignore rules in Rego within it, for example:
package Azure_Proactive_Resiliency_Library_v2
import rego.v1
exception contains rules if {
rules = ["use_nat_gateway_instead_of_outbound_rules_for_production_load_balancer", "storage_accounts_are_zone_or_region_redundant"]
}
This configuration can skip the use_nat_gateway_instead_of_outbound_rules_for_production_load_balancer and storage_accounts_are_zone_or_region_redundant rules within Azure_Proactive_Resiliency_Library_v2.
1.6.11.1.3. Makefile Integration (avmmakefile)
AVM's pipeline provides a standardized execution interface through a Makefile:
.PHONY: conftest
conftest:
@bash ./avm_scripts/conftest.sh
By defining a target named conftest, developers only need to execute the make conftest command to trigger compliance checks.
1.6.11.1.4. GitHub Actions Workflow Integration (test-examples-template.yml)
AVM seamlessly integrates Conftest into the CI pipeline, as reflected in the definition of the test-examples-template.yml file:
jobs:
conftest:
if: github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-latest
environment: test
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- name: Conftest
env:
TF_IN_AUTOMATION: 1
TF_VAR_enable_telemetry: false
SECRETS_CONTEXT: ${{ toJson(secrets) }}
VARS_CONTEXT: ${{ toJson(vars) }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
ARM_TENANT_ID_OVERRIDE: ${{ secrets.ARM_TENANT_ID_OVERRIDE }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
ARM_SUBSCRIPTION_ID_OVERRIDE: ${{ secrets.ARM_SUBSCRIPTION_ID_OVERRIDE }}
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_ID_OVERRIDE: ${{ secrets.ARM_CLIENT_ID_OVERRIDE }}
ARM_USE_OIDC: true
run: |
set -e
export REMOTE_SCRIPT="https://raw.githubusercontent.com/Azure/tfmod-scaffold/main/avm_scripts"
curl -H 'Cache-Control: no-cache, no-store' -sSL "$REMOTE_SCRIPT/prepare-credential.sh" -o prepare-credential.sh
source ./prepare-credential.sh
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/src -w /src -e TF_IN_AUTOMATION -e TF_VAR_enable_telemetry -e AVM_MOD_PATH=/src -e AVM_EXAMPLE=${{ matrix.example }} -e ARM_SUBSCRIPTION_ID -e ARM_TENANT_ID -e ARM_CLIENT_ID -e ARM_OIDC_REQUEST_TOKEN -e ARM_OIDC_REQUEST_URL -e ARM_USE_OIDC=true --env-file <(env | grep TF_VAR_ | grep -v ' "TF_VAR_') mcr.microsoft.com/azterraform:latest make conftest
This task first configures the required permissions (valid permissions are needed to create Terraform plan files) and then runs make conftest.
Through this pipeline implementation, Conftest policy checking is automatically integrated into the module submission check process. Every time a PR is submitted or pushed, after review by module maintainers, terraform plan can be run, and then policy checks are executed against the change plan, ensuring that the module always meets the organization's compliance standards and security requirements.
1.6.11.1.5. policy-library-avm
policy-library-avm is the inspection policy library currently maintained and executed by AVM. It is designed to inspect both AzureRM and AzAPI resources simultaneously (all rules provide corresponding implementations for both providers).
Currently, this rule library consists of two sub-libraries:
1.6.11.1.5.1. Azure-Proactive-Resiliency-Library-v2 (aprl for short)
Positioning: This rule package focuses on improving the reliability and resilience of Azure resources, aiming to help users build highly available and fault-tolerant infrastructure.
Rule Features:
- Covers best practices for multiple Azure services such as network, storage, and compute.
- Ensures resource configuration meets high availability and disaster recovery requirements.
- Clear rule organization structure, classified by service and provider (such as
azurermandazapi).
Example Rule:
- Requires the use of NAT Gateway instead of Load Balancer outbound rules in production environments to enhance network reliability.
package Azure_Proactive_Resiliency_Library_v2
import rego.v1
deny_use_nat_gateway_instead_of_outbound_rules_for_production_load_balancer contains reason if {
resource := data.utils.resource(input, "azurerm_lb_outbound_rule")[_]
reason := sprintf("Azure-Proactive-Resiliency-Library-v2/use_nat_gateway_instead_of_outbound_rules_for_production_load_lalancer: '%s' `azurerm_lb_outbound_rule` must not be used for production workloads: https://azure.github.io/Azure-Proactive-Resiliency-Library-v2/azure-resources/Network/loadBalancers/#use-nat-gateway-instead-of-outbound-rules-for-production-workloads", [resource.address])
}
In this code, package Azure_Proactive_Resiliency_Library_v2 indicates that this rule belongs to the package Azure_Proactive_Resiliency_Library_v2 (Rego code is typically organized by package). deny_use_nat_gateway_instead_of_outbound_rules_for_production_load_balancer has the deny_ prefix, indicating that failure of this rule will trigger a violation rather than a warning. The name of the rule is use_nat_gateway_instead_of_outbound_rules_for_production_load_balancer.
1.6.11.1.5.2. avmsec (Azure Verified Module Security Ruleset)
Positioning: This rule package focuses on the security of Azure Verified Modules (AVM), ensuring module configurations comply with security best practices.
Rule Features:
- Covers security configurations in multiple aspects such as authentication, encryption, and network security.
- Rules are divided into four severity levels:
high,medium,low, andinfo, allowing users to apply them selectively according to needs. - Inspired by BridgeCrew's Checkov built-in rules, combining industry security best practices.
Example Rule:
- Ensures that the
adminaccount for Azure Container Registry is disabled:
package avmsec
import rego.v1
valid_azurerm_container_registry_admin_account_disabled(resource) if {
resource.values.admin_enabled == false
}
valid_azurerm_container_registry_admin_account_disabled(resource) if {
not resource.values.admin_enabled == resource.values.admin_enabled
}
deny_AVM_SEC_137 contains reason if {
resource := data.utils.resource(input, "azurerm_container_registry")[_]
not valid_azurerm_container_registry_admin_account_disabled(resource)
reason := sprintf("avmsec/AVM_SEC_137: Ensure ACR admin account is disabled %s, https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/resource/azure/ACRAdminAccountDisabled.py", [resource.address])
}
1.6.11.1.5.3. Using Conftest to check policies against Terraform plan files
To use Conftest to check policies against Terraform plan files, follow these steps:
- Generate JSON representation of Terraform plan file:
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
This will generate a file named tfplan.json containing detailed information about the Terraform plan.
Run policy check with Conftest:
Check all policies:
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy tfplan.json
This command will apply all policies to check the tfplan.json file.
- Check only APRL policies:
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy/Azure-Proactive-Resiliency-Library-v2 tfplan.json
This command will apply only APRL policies to check the tfplan.json file.
- Check only avmsec policies:
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy/avmsec tfplan.json
This command will apply only avmsec policies to check the tfplan.json file.
1.6.11.1.5.4. Creating Exceptions for Policy Checks
In some cases, you may need to create exceptions for specific policies to temporarily bypass certain rule checks. This can be achieved by creating an exception.rego file. For example, to exclude the rule named "configure_aks_default_node_pool_zones", you can create a file with the following content:
package Azure_Proactive_Resiliency_Library_v2
import rego.v1
exception contains rules if {
rules = ["configure_aks_default_node_pool_zones"]
}
Then, use the following command to apply the policy check including the exception file:
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy/Azure-Proactive-Resiliency-Library-v2 -p policy -p exception.rego tfplan.json
For the avmsec policy package, you can create exceptions based on severity levels. For example, to apply only high severity level policies, you can create an exception.rego file with the following content:
package avmsec
import rego.v1
# Skip all policies except high severity
exception contains rules if {
rules = rules_below_high
}
Then, use a similar command to apply the policy check including the exception file.
1.6.11.1.6. Summary
In conclusion, Conftest tightly integrates policies with code, providing strong support for Terraform module governance. In large-scale infrastructure code practices, using tools like Conftest can significantly improve the efficiency and reliability of compliance checks. It transforms complex compliance requirements into executable tests in a straightforward way, allowing engineers to get immediate feedback and correct issues during daily development. Through continuous policy-as-code practices, we not only consolidate the security and standardization of infrastructure but also establish a scalable and automated defense system for enterprise Terraform module governance. This way, as our infrastructure footprint continues to expand, we can still follow rules and move forward steadily.