创建 Terraform 限制条件

准备工作

限制条件框架

gcloud beta terraform vet 使用限制条件框架政策,其中包括限制条件和限制条件模板。这两者的区别如下:

  • 限制条件模板类似于函数声明;它在 Rego 中定义规则,并选择性地接受输入参数。
  • 限制条件是一个文件,它引用限制条件模板并定义要传递给它的输入参数以及政策涵盖的资源。

这样可以避免重复。您可以使用通用政策编写限制条件模板,然后编写提供不同输入参数或不同资源匹配规则的任意数量的限制条件。

创建限制条件模板

如需创建限制条件模板,请按照以下步骤操作:

  1. 收集样本数据。
  2. 编写 Rego
  3. 测试 Rego。
  4. 设置限制条件模板框架。
  5. 内嵌 Rego。
  6. 设置限制条件。

收集样本数据

要编写限制条件模板,您需要具有可供操作的样本数据。基于 Terraform 的限制条件可处理资源变更数据,该数据来自 Terraform 方案 JSONresource_changes 键。

例如,您的 JSON 可能如下所示:

// tfplan.json
{
  "format_version": "0.2",
  "terraform_version": "1.0.10",
  "resource_changes": [
    {
      "address": "google_compute_address.internal_with_subnet_and_address",
      "mode": "managed",
      "type": "google_compute_address",
      "name": "internal_with_subnet_and_address",
      "provider_name": "registry.terraform.io/hashicorp/google",
      "change": {
        "actions": [
          "create"
        ],
        "before": null,
        "after": {
          "address": "10.0.42.42",
          "address_type": "INTERNAL",
          "description": null,
          "name": "my-internal-address",
          "network": null,
          "prefix_length": null,
          "region": "us-central1",
          "timeouts": null
        },
        "after_unknown": {
          "creation_timestamp": true,
          "id": true,
          "network_tier": true,
          "project": true,
          "purpose": true,
          "self_link": true,
          "subnetwork": true,
          "users": true
        },
        "before_sensitive": false,
        "after_sensitive": {
          "users": []
        }
      }
    }
  ],
  // other data
}

编写 Rego

获得样本数据后,您便可以使用 Rego 编写限制条件模板的逻辑。您的 Rego 必须具有 violations 规则。待审核的资源变更作为 input.review 提供。限制条件参数作为 input.parameters 提供。例如,如需要求 google_compute_address 资源必须具有允许的 address_type,则请编写如下代码:

# validator/tf_compute_address_address_type_allowlist_constraint_v1.rego
package templates.gcp.TFComputeAddressAddressTypeAllowlistConstraintV1

violation[{
  "msg": message,
  "details": metadata,
}] {
  resource := input.review
  resource.type == "google_compute_address"

  allowed_address_types := input.parameters.allowed_address_types
  count({resource.after.address_type} & allowed_address_types) >= 1
  message := sprintf(
    "Compute address %s has a disallowed address_type: %s",
    [resource.address, resource.after.address_type]
  )
  metadata := {"resource": resource.name}
}

为限制条件模板命名

上述示例使用名称 TFComputeAddressAddressTypeAllowlistConstraintV1。这是每个限制条件模板的唯一标识符。我们建议您遵循以下命名准则:

  • 常规格式:TF{resource}{feature}Constraint{version}。使用 CamelCase 格式。 (换句话说,每个单词的首字母都要大写)。
  • 对于单资源限制条件,请遵循 Terraform 提供程序的产品命名惯例。例如,对于 google_tags_tag,即使 API 名称为 resourcemanager,产品名称也为 tags
  • 如果模板适用于多种类型的资源,请省略资源部分并仅包含功能(例如:“TFAddressTypeAllowlistConstraintV1”)。
  • 版本号不遵循 semver 格式;它只是一个数字。这实际上会使模板的每个版本都成为唯一的模板。

我们建议您使用与限制条件模板名称匹配的 Rego 文件名,但要使用 snake_case 格式。换句话说,将名称转换为小写字母并使用 _ 分隔每个单词。对于上文中的示例,建议的文件名为 tf_compute_address_address_type_allowlist_constraint_v1.rego

测试 Rego

您可以使用 Rego Playground 手动测试 Rego。请务必使用非敏感数据。

我们建议您编写自动化测试。将收集的样本数据放入 validator/test/fixtures/<constraint filename>/resource_changes/data.json 中,并在测试文件中引用这些数据,如下所示:

# validator/tf_compute_address_address_type_allowlist_constraint_v1_test.rego
package templates.gcp.TFComputeAddressAddressTypeAllowlistConstraintV1

import data.test.fixtures.tf_compute_address_address_type_allowlist_constraint_v1_test.resource_changes as resource_changes

test_violation_with_disallowed_address_type {
  parameters := {
    "allowed_address_types": "EXTERNAL"
  }
  violations := violation with input.review as resource_changes[_]
    with input.parameters as parameters
  count(violations) == 1
}

将 Rego 和测试放在政策库的 validator 文件夹中。

设置限制条件模板框架

编写和测试 Rego 规则后,您必须将其封装为限制条件模板。限制条件框架使用 Kubernetes 自定义资源定义作为 Rego 政策的容器。

限制条件模板还会使用 OpenAPI V3 架构定义允许将哪些参数作为限制条件的输入。

对该框架使用与 Rego 相同的名称。特别是:

  • 使用与 Rego 相同的文件名。例如:tf_compute_address_address_type_allowlist_constraint_v1.yaml
  • spec.crd.spec.names.kind 必须包含模板名称
  • metadata.name 必须包含模板名称,但需用小写字母

将限制条件模板框架放置在 policies/templates 中。

对于上述示例,代码如下:

# policies/templates/tf_compute_address_address_type_allowlist_constraint_v1.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: tfcomputeaddressaddresstypeallowlistconstraintv1
spec:
  crd:
    spec:
      names:
        kind: TFComputeAddressAddressTypeAllowlistConstraintV1
      validation:
        openAPIV3Schema:
          properties:
            allowed_address_types:
              description: "A list of address_types allowed, for example: ['INTERNAL']"
              type: array
              items:
                type: string
  targets:
    - target: validation.resourcechange.terraform.cloud.google.com
      rego: |
            #INLINE("validator/tf_compute_address_address_type_allowlist_constraint_v1.rego")
            #ENDINLINE

内嵌 Rego

如果按照上文的示例操作,此时您的目录布局应如下所示:

| policy-library/
|- validator/
||- tf_compute_address_address_type_allowlist_constraint_v1.rego
||- tf_compute_address_address_type_allowlist_constraint_v1_test.rego
|- policies
||- templates
|||- tf_compute_address_address_type_allowlist_constraint_v1.yaml

如果您克隆了 Google 提供的政策库代码库,您便可以运行 make build 来使用 validator 中定义的 Rego 自动更新 policies/templates 中的限制条件模板。

设置限制条件

限制条件包含 gcloud beta terraform vet 正确地强制执行政策并报告违规行为所需的三项信息:

  • severitylowmediumhigh
  • match:用于确定限制条件是否应用于特定资源的参数。支持以下匹配参数:
    • addresses:使用 glob 样式匹配时要包含的资源地址的列表
    • excludedAddresses:(可选)使用 glob 样式匹配时要排除的资源地址的列表。
  • parameters:限制条件模板的输入参数的值。

确保 kind 包含限制条件模板名称。我们建议将 metadata.name 设置为描述性 slug。

例如,要使用上文中的示例限制条件模板仅允许 INTERNAL 地址类型,请编写如下代码:

# policies/constraints/tf_compute_address_internal_only.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: TFComputeAddressAddressTypeAllowlistConstraintV1
metadata:
  name: tf_compute_address_internal_only
spec:
  severity: high
  match:
    addresses:
    - "**"
  parameters:
    allowed_address_types:
    - "INTERNAL"

匹配示例:

地址匹配器 说明
`module.**` 任何模块中的所有资源
`module.my_module.**` 模块“my_module”中的所有内容
`**.google_compute_global_forwarding_rule.*` 任何模块中的所有 google_compute_global_forwarding_rule 资源
`module.my_module.google_compute_global_forwarding_rule.*` “my_module”中的所有 google_compute_global_forwarding_rule 资源

如果资源地址与 addressesexcludedAddresses 中的值匹配,则排除该资源。

限制

应用后的 Terraform 方案数据能最好地反映实际状态;但是,在许多情况下,应用后的状态可能是未知的,因为它是在服务器端计算的。

在验证政策时,构建 CAI 祖先路径是流程的一部分。如果遇到未知的项目 ID,它会使用提供的默认项目。如果未提供默认项目,则祖先路径默认为 organizations/unknown

您可以添加以下限制条件来禁止未知的祖先路径:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GCPAlwaysViolatesConstraintV1
metadata:
  name: disallow_unknown_ancestry
  annotations:
    description: |
      Unknown ancestry is not allowed; use --project=<project> to set a
      default ancestry
spec:
  severity: high
  match:
    ancestries:
    - "organizations/unknown"
  parameters: {}

支持的资源

您可以通过任何 Terraform 提供程序为任何 Terraform 资源创建资源变更限制条件。