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 planterraform 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 服务的最佳实践。
  • 确保资源配置符合高可用性和灾难恢复的要求。
  • 规则组织结构清晰,按服务和提供程序(如 azurermazapi)分类。

示例规则

  • 要求在生产环境中使用 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_balancerdeny_ 为前缀,说明该规则不通过会触发一个违例而非警告,规则的名称则是 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)的安全性,确保模块配置符合安全最佳实践。

规则特点

  • 涵盖身份验证、加密、网络安全等多个方面的安全配置。
  • 规则分为四个严重性级别:highmediumlowinfo,便于用户根据需求选择性地应用。
  • 灵感来源于 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 计划文件进行策略检查,请按照以下步骤操作:

  1. 生成 Terraform 计划文件的 JSON 表示
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json

这将生成一个名为 tfplan.json 的文件,包含了 Terraform 计划的详细信息。

  1. 使用 Conftest 运行策略检查

  2. 检查所有策略

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 模块治理建立起了一套可扩展、自动化的防护体系。这样,当我们的基础设施版图不断扩大时,依然能够做到有章可循,稳健前行。

results matching ""

    No results matching ""