1.6.11.1. Conftest
随着基础设施即代码(Infrastructure as Code, IaC)的普及,在大规模 Terraform 模块治理中确保配置合规和策略统一成为一项重大挑战。Conftest 正是在这样的背景下诞生的策略即代码工具。它是一款用于针对结构化配置数据编写测试的命令行工具,底层依赖 CNCF 开源项目 Open Policy Agent (OPA) 提供的 Rego 策略语言。简单来说,Conftest 允许我们为配置文件(例如 Terraform 模块代码、Kubernetes 配置、Tekton Pipeline 定义等)编写策略测试,在代码部署之前自动检查配置是否符合预先定义的策略要求。通过将合规策略融入代码测试,Conftest 在基础设施治理中充当“守门人”的角色,帮助团队在部署前及时发现违规配置,避免将隐患带入生产环境。
关于 Rego 这门规则语言,我在 Rego 101 中文教程中已经有过比较系统的介绍,所以本书不再对 Rego 进行赘述。
为什么需要 Conftest? 在 IaC 场景下,开发人员可以非常快速地编写和修改 Terraform 配置代码,导致配置变更频繁且规模庞大。如果缺乏有效的合规审查机制,容易出现一些不符合安全或规范要求的资源配置。例如,某个 Terraform 配置所创建的基础设施可能遗漏了必要的加密设置,或者打开了不该开放的网络端口。这类问题如果仅靠人工代码审查来发现,不仅耗时耗力,而且难以保证不遗漏。
Conftest 提供了一种自动化的方案:我们可以将组织的合规要求和最佳实践编写成可执行的策略代码,每次 Terraform 配置变更时都由工具自动执行这些策略检查。一旦检测到违背策略的配置,Conftest 会立刻发出失败报告,阻止不合规的代码推进到后续环节。通过这种方式,Conftest 将合规检查左移,在代码合并或部署前就能“早预防、早发现”,大幅降低人为审查的负担,为大规模基础设施代码提供一致的策略护栏。
1.6.11.1.1. Terraform 场景下的 Conftest 实践应用
Conftest 与 Terraform 的集成使用非常灵活,通常流程是利用 Terraform 生成可解析的计划文件,然后由 Conftest 依据策略规则进行测试。以下是一个与 Terraform 集成的基本工作流程:
生成 Terraform 执行计划的 JSON 文件:首先运行 Terraform 的计划命令,将基础设施更改计划导出为 JSON 格式文件。例如:
terraform init && terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
通过 terraform plan
和 terraform show -json
,我们获得了当前 Terraform 更改的详细结构化数据输出。这个 tfplan.json
文件包含了 Terraform 计划将创建、修改或销毁的所有资源信息,为策略检查提供了基础数据。
编写策略测试(Rego 文件):在开始测试前,我们需要编写代表合规策略的 Rego 规则文件。例如,我们可以制定一个策略,禁止使用未经批准的 Terraform 模块或强制要求资源包含特定标签,再或者,所有新建的 AWS S3 存储桶必须开启加密和版本控制,如果不符合则返回违例,打断流水线。虽然深入了解 Rego 语法有助于书写复杂策略,但一般情况下我们无需精通 Rego,即可借鉴社区示例快速上手编写这些规则。
运行 Conftest 进行策略检查:有了 Terraform 计划 JSON 和策略文件后,就可以使用 Conftest 来测试配置是否合规。执行命令:
conftest test -p /path/to/policy/dir tfplan.json
这里 -p
参数指定策略文件所在目录(默认情况下,Conftest 也会在当前目录的 policy
子目录搜索 .rego
策略文件)。Conftest 将读取 tfplan.json
的内容并应用我们定义的所有策略规则,对每一条不合规之处给出报告。输出结果示例:
FAIL - tfplan.json - Module terraform-azurerm-compute.git?ref=tags/v0.1 is not in allowed range.
上面这条假想的输出表明测试未通过,原因是 Terraform 计划中使用了版本不被允许的模块。针对同一个 tfplan.json
文件,如果我们修正了配置使其符合集团策略,再次运行测试就应得到所有测试通过的结果,例如:
1 tests, 1 passed, 0 warnings, 0 failures, 0 exceptions
通过这样的反馈,我们能够清楚知道哪些部分违背了策略,以及需要在 Terraform 配置中作出何种修改。
通过 Conftest 的实践,我们将合规策略的验证嵌入到了 Terraform 模块的开发流程中。这不仅自动化了许多原本需要人工检查的工作,也使策略执行变得一致可追溯。尤其在模块数量众多、多人协作的场景下,Conftest 帮助团队确保无论由谁编写的 Terraform 模块,都遵循相同的安全和规范准则。
在实际项目中,我们可以将 Conftest 融入 CI/CD 流水线,实现持续的策略检查。例如:
- Pull Request 阶段自动审查:当有人提交 Terraform 配置的 Pull Request 时,在 CI 中运行
terraform plan
并用 Conftest 对生成的计划进行测试。如果策略检查未通过,则阻止代码合并。这相当于自动化代码评审,节省了人为逐项检查的时间。 - 与部署流程集成:在正式部署前的流水线中加入 Conftest 步骤,确保只有通过所有策略测试的 Terraform 计划才能执行
terraform apply
。这为生产环境增加了一道保险,防止将不合规的更改部署上线。 - 配合现有工具:如果团队使用像 HCP Terraform 或是 Atlantis 这样的 Terraform 自动化工具,可以配置其在每次计划后调用 Conftest 进行策略验证。这样从提交代码、审核到部署的整个过程都被策略守护,真正实现基础设施变更的端到端合规。
1.6.11.1.2. AVM 中对 Conftest 的集成
AVM(Azure Verified Modules)利用 Conftest 工具,在模块流水线中实现合规性和策略检查的自动化。具体而言,AVM 通过脚本封装、Makefile 集成,以及 GitHub Actions 工作流三部分实现了对 Terraform 模块的策略验证。
- 脚本封装与执行(
conftest.sh
)
AVM 提供了一个专门的脚本 conftest.sh
,该脚本封装了对模块执行 Conftest 测试的流程:
#!/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
该脚本针对 examples
下每一个例子,运行 Terraform 命令后生成 Json 格式的计划文件,然后针对计划,运行 policy-library-avm
内定义的检查规则。
需要注意的是,假如模块维护者决定针对某个样例代码,跳过某些规则的检查,可以在该样例目录下建立 exceptions
文件夹,然后在其中用 Rego 定义忽略规则,比如:
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"]
}
该配置可以跳过 Azure_Proactive_Resiliency_Library_v2
内的 use_nat_gateway_instead_of_outbound_rules_for_production_load_balancer
以及 storage_accounts_are_zone_or_region_redundant
规则。
1.6.11.1.3. Makefile 集成(avmmakefile)
AVM 的流水线借助 Makefile 提供了标准化的执行接口:
.PHONY: conftest
conftest:
@bash ./avm_scripts/conftest.sh
通过定义一个名为 conftest
的目标,开发人员只需执行 make conftest
命令,即可触发合规检查。
1.6.11.1.4. GitHub Actions 工作流集成(test-examples-template.yml)
AVM 将 Conftest 无缝地整合到 CI 流水线中,这一点体现在 test-examples-template.yml
文件的定义中:
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
该任务会首先配置所需的权限(因为要创建 Terraform 计划文件需要配置有效权限),然后运行 make conftest
。
通过此流水线实现,Conftest 策略检查自动集成到模块的提交检查过程中,每次 PR 提交或推送时,在模块维护者审查通过后,可以运行 terraform plan
,然后针对变更计划执行策略检查,从而确保模块始终符合组织的合规标准与安全要求。
1.6.11.1.5. policy-library-avm
policy-library-avm
是目前 AVM 所维护和执行的检查策略库,它被设计成可以同时针对 AzureRM 以及 AzAPI 资源进行检查(所有规则都针对两种 Provider 提供了对应实现)。
目前该规则库由两个子库组成:
1.6.11.1.5.1. Azure-Proactive-Resiliency-Library-v2(简称 aprl)
定位:该规则包专注于提升 Azure 资源的可靠性和弹性,旨在帮助用户构建高可用、容错性强的基础设施。
规则特点:
- 涵盖网络、存储、计算等多个 Azure 服务的最佳实践。
- 确保资源配置符合高可用性和灾难恢复的要求。
- 规则组织结构清晰,按服务和提供程序(如
azurerm
和azapi
)分类。
示例规则:
- 要求在生产环境中使用 NAT 网关替代负载均衡器的出站规则,以增强网络的可靠性。
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])
}
这段代码中,package Azure_Proactive_Resiliency_Library_v2
说明该规则隶属于包 Azure_Proactive_Resiliency_Library_v2
(Rego 代码通常以包为单位进行组织),deny_use_nat_gateway_instead_of_outbound_rules_for_production_load_balancer
以 deny_
为前缀,说明该规则不通过会触发一个违例而非警告,规则的名称则是 use_nat_gateway_instead_of_outbound_rules_for_production_load_balancer
。
1.6.11.1.5.2. avmsec(Azure Verified Module Security Ruleset)
定位:该规则包聚焦于 Azure Verified Modules(AVM)的安全性,确保模块配置符合安全最佳实践。
规则特点:
- 涵盖身份验证、加密、网络安全等多个方面的安全配置。
- 规则分为四个严重性级别:
high
、medium
、low
和info
,便于用户根据需求选择性地应用。 - 灵感来源于 BridgeCrew 的 Checkov 内置规则,结合了业界的安全最佳实践。
示例规则:
- 确保 Azure Container Registry 关闭了
admin
账户:
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. 使用 Conftest 对 Terraform 计划文件进行策略检查
要使用 Conftest 对 Terraform 计划文件进行策略检查,请按照以下步骤操作:
- 生成 Terraform 计划文件的 JSON 表示:
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
这将生成一个名为 tfplan.json
的文件,包含了 Terraform 计划的详细信息。
使用 Conftest 运行策略检查:
检查所有策略:
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy tfplan.json
此命令将对 tfplan.json
文件应用所有策略进行检查。
- 仅检查 APRL 策略:
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy/Azure-Proactive-Resiliency-Library-v2 tfplan.json
此命令将仅对 tfplan.json
文件应用 APRL 策略进行检查。
- 仅检查 avmsec 策略:
conftest test --all-namespaces --update git::https://github.com/Azure/policy-library-avm.git//policy/avmsec tfplan.json
此命令将仅对 tfplan.json
文件应用 avmsec 策略进行检查。
1.6.11.1.5.4. 创建策略检查的例外(Exceptions)
在某些情况下,您可能需要为特定策略创建例外,以暂时绕过某些规则的检查。可以通过创建一个 exception.rego
文件来实现。例如,要排除名为 "configure_aks_default_node_pool_zones"
的规则,可以创建以下内容的文件:
package Azure_Proactive_Resiliency_Library_v2
import rego.v1
exception contains rules if {
rules = ["configure_aks_default_node_pool_zones"]
}
然后,使用以下命令应用策略检查并包含例外文件:
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
对于 avmsec
策略包,您可以根据严重性级别创建例外。例如,仅应用 high
严重性级别的策略,可以创建以下内容的 exception.rego
文件:
package avmsec
import rego.v1
# Skip all policies except high severity
exception contains rules if {
rules = rules_below_high
}
然后,使用类似的命令应用策略检查并包含例外文件。
1.6.11.1.6. 小结
总而言之,Conftest 将策略与代码紧密结合,为 Terraform 模块治理提供了强有力的支持。在大规模基础设施代码实践中,使用 Conftest 等工具可以大幅提升合规检查的效率和可靠性。它以深入浅出的方式将复杂的合规要求转化为可执行的测试,让工程师在日常开发中就能即时获得反馈并纠正问题。通过持续的策略即代码实践,我们不仅巩固了基础设施的安全和规范性,也为企业的 Terraform 模块治理建立起了一套可扩展、自动化的防护体系。这样,当我们的基础设施版图不断扩大时,依然能够做到有章可循,稳健前行。