建立 Terraform 限制

事前準備

約束條件架構

gcloud beta terraform vet 會使用 Constraint Framework 政策,其中包含限制限制範本。兩者之間的差異如下:

  • 限制範本就像函式宣告一樣,會在 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 供應商的產品命名慣例。舉例來說,對於 google_tags_tag,產品名稱為 tags,但 API 名稱為 resourcemanager
  • 如果範本適用於多種資源類型,請省略資源部分,只包含功能 (例如「TFAddressTypeAllowlistConstraintV1」)。
  • 版本號碼不遵循 semver 格式,只是單一數字。這樣一來,每個範本版本都會成為獨立的範本。

建議您為 Rego 檔案使用與限制範本名稱相符的名稱,但請使用蛇形命名法。換句話說,請使用 _ 將名稱轉換為小寫字母分隔的字詞。在上述範例中,建議的檔案名稱為 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,自動更新 policies/templates 中的限制範本,並使用 validator 中定義的 Rego。

設定限制條件

限制包含 gcloud beta terraform vet 需要的三項資訊,才能正確執行並回報違規情形:

  • severitylowmediumhigh
  • match:用於判斷限制是否適用於特定資源的參數。支援下列比對參數:
    • addresses:要納入的資源位址清單,使用 glob 樣式比對
    • excludedAddresses:(選用) 要排除的資源位址清單,使用 glob 樣式比對。
  • parameters:約束範本的輸入參數值。

請確認 kind 包含限制範本名稱。建議將 metadata.name 設為描述性短代碼。

舉例來說,如要只允許使用上述範例限制範本的 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 資源建立資源變更限制。