建立 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.change.after.address_type} & allowed_address_types) >= 1
  message := sprintf(
    "Compute address %s has a disallowed address_type: %s",
    [resource.address, resource.change.after.address_type]
  )
  metadata := {"resource": resource.name}
}

為限制範本命名

上一個範例使用 TFComputeAddressAddressTypeAllowlistConstraintV1 這個名稱。這是每個限制範本的專屬 ID。建議您遵循下列命名規範:

  • 一般格式:TF{resource}{feature}Constraint{version}。使用 CamelCase。 (也就是說,每個新字詞都要大寫。)
  • 如果是單一資源限制,請遵循 Terraform 供應商 的產品命名慣例。舉例來說,雖然 API 名稱為 resourcemanager,但 google_tags_tag 的產品名稱為 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 提供的 policy-library 存放區,可以執行 make build,自動更新 policies/templates 中的限制範本,並使用 validator 中定義的 Rego。

設定限制

限制包含三種資訊,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 資源,建立資源變更限制。