Sentinel 提供了強大的策略即程式碼(Policy-as-Code)能力,能有效管控 Terraform 基礎設施佈署。本文示範如何利用 Sentinel 撰寫策略,並結合 Terraform Cloud 和 GCP Workload Identity Provider 進行本地測試,確保基礎設施符合預期規範。文中詳述了 Sentinel 策略的單元測試最佳實踐,包含模擬資料的運用和測試框架的建置,同時也探討了邏輯短路機制如何提升效能。此外,文章也提供 GCP 動態憑證設定步驟,方便讀者整合 GCP 環境進行更實際的測試。最後,透過程式碼範例和圖表說明,更清晰地展示 Sentinel 測試框架的目錄結構和使用方法,幫助讀者快速上手 Sentinel 策略測試。

使用Sentinel進行Terraform的Policy-as-Code實踐與測試

Sentinel策略本地應用與測試

在前面的章節中,我們已經探討瞭如何使用Sentinel進行Policy-as-Code(PaC)的實踐。現在,我們將進一步深入瞭解如何在本地使用Terraform Cloud(TFC)來應用和測試Sentinel策略。

更新後的Sentinel應用輸出

$ sentinel apply -trace
...
Fail - eks.sentinel
Print messages:
Parameters:
p_eks_version = ["1.28"]
p_region = us-east-1
p_tf_version = 1.5.5
TFPlan Values:
EKS Version = 1.26
Region = us-east-1
Terraform Version = 1.5.5
EKS cluster module.eks.aws_eks_cluster.this[0] has invalid version: 1.26
eks.sentinel:48:1 - Rule "main"
Value:
false

邏輯短路的Sentinel規則

上述輸出結果顯示了一個失敗的驗證過程,並且展示了Sentinel規則所使用的短路邏輯。main規則包含了多個透過邏輯與(AND)運算組合起來的布林條件,這些條件來自於函式和其他策略規則。由於第一個布林條件(來自函式的傳回值)為假,其他透過邏輯與組合的條件就不會被執行。這種邏輯短路機制提高了效能。

測試Terraform與Sentinel

對於Terraform專案,應考慮以下幾種測試型別:

單元測試

單元測試是一種功能性測試,用於測試程式碼中最小的可邏輯隔離和企劃的部分。對於Terraform和Sentinel來說,隨著專案規模的增長,程式碼模組化變得越來越重要。模組化程式碼使得單元測試能夠在隔離的場景中測試獨立的元件。

整合測試

當程式碼被整合到更大的程式碼函式庫中時,需要在它將要執行的上下文中對新程式碼或變更的程式碼進行功能性測試,以確保它能夠正確工作並且不會破壞現有的程式碼。

合規性測試

合規性測試(也稱為一致性測試)是一種非功能性測試,用於確保程式碼和組態符合您所採用的規則和策略。合規性測試使您的系統執行在既定的邊界內。合規性測試與端對端測試有所重疊,因為合規性策略被應用於由IaC佈署的基礎設施。

端對端(e2e)測試

端對端測試是一種功能性測試,用於在獨立的環境中測試整個系統,執行所有定義的功能,並在額外的負載下進行最後的檢查,然後再將新的功能或變更釋出到生產環境中。

Sentinel策略的單元測試最佳實踐

在進行Sentinel策略的單元測試時,應遵循以下最佳實踐:

  • 為特定的控制和行為編寫策略。
  • 使用Sentinel模組來實作DRY(Don’t Repeat Yourself)原則,以重用和去重複程式碼。
  • 使用透過和失敗的Sentinel測試,並利用不同的模擬資料。
  • 採用一致的目錄和檔案結構,使用慣例而非組態。

Sentinel測試框架

Sentinel提供了一個內建的測試框架,與之前我們看到的OPA等PaC工具類別似。透過遵循檔案和目錄約定,可以簡化Sentinel測試。

.
├── common
│ └── functions.sentinel
├── mocks
│ ├── mock-tfconfig-v2.sentinel
│ ├── mock-tfconfig.sentinel
│ ├── mock-tfplan-v2.sentinel
│ ├── mock-tfplan.sentinel
│ ├── mock-tfrun.sentinel
│ ├── mock-tfstate-v2.sentinel
│ └── mock-tfstate.sentinel
├── required_labels.sentinel
├── sentinel.hcl
└── test
└── required-labels
├── fail.hcl
├── mock-tfplan-v2-fail.sentinel
├── mock-tfplan-v2-pass.sentinel
└── pass.hcl

將TFC連線到GCP使用動態憑證

為了生成模擬資料,再次使用了TFC,但這次使用了Cloud Foundation Fabric GitHub專案中的動態憑證提供者功能。具體來說,使用了GCP Workload Identity Provider,使得外部應用程式能夠使用由GPC工作負載身份提供的短期憑證。

使用GCP Workload Identity Provider

首先,使用gcloud auth application-default login登入GCP,以便在本地開發中使用應用程式預設憑證(ADC)。

# gcloud login
$ gcloud auth application-default login
...
Credentials saved to file:
[/Users/me/.config/gcloud/application_default_credentials.json]
These credentials will be used by any library that requests
Application Default Credentials (ADC).
...

然後,使用GCP Workload Identity Provider Terraform專案來安裝所需的憑證,以便TFC雲端專案和工作區能夠使用GCP專案。

# terraform.auto.tfvars file
billing_account = "<GCP_BILLING_ACCT>"
project_create = false
project_id = "<GCP_PROEJCT_ID>"
parent = null
tfc_organization_id = "org-..."
tfc_workspace_id = "ws-..."
workload_identity_pool_id = "<GCP_ID_POOL>"
workload_identity_pool_provider_id = "<GCP_ID_PROVIDER>"
issuer_uri = "https://app.terraform.io/"

設定TFC工作區環境變數

在TFC工作區中新增環境變數,如圖12-2所示。

成功執行規格計劃

使用動態憑證提供者與GCP專案進行握手,成功執行了一個小型計劃,以建立Google Storage儲存桶,如圖12-3所示。

隨著雲端運算和基礎設施即程式碼(IaC)的發展,Terraform和Sentinel的使用將越來越廣泛。未來,我們可以期待更多關於Terraform和Sentinel的新功能和最佳實踐出現,以幫助開發人員和維運人員更好地管理和保護他們的基礎設施。

程式碼範例:使用Sentinel模擬資料進行測試

# mock-tfplan-v2.sentinel
mock "tfplan/v2" {
  module {
    source = "./mocks/mock-tfplan-v2.sentinel"
  }
}

內容解密:

此範例展示瞭如何使用Sentinel的模擬資料進行測試。mock "tfplan/v2"區塊定義了一個模擬的Terraform計劃版本2資料來源,module區塊指定了模擬資料的來源檔案。這使得開發人員可以在沒有實際Terraform計劃資料的情況下測試Sentinel策略。

圖表翻譯:Sentinel測試框架目錄結構

  graph LR;
    A[test] --> B[required-labels];
    B --> C[fail.hcl];
    B --> D[pass.hcl];
    B --> E[mock-tfplan-v2-fail.sentinel];
    B --> F[mock-tfplan-v2-pass.sentinel];

圖表翻譯: 此圖表展示了Sentinel測試框架的目錄結構。其中,test目錄包含了required-labels子目錄,後者包含了多個用於測試的檔案,包括失敗和透過的測試案例,以及對應的模擬資料檔案。這種結構使得測試組織清晰,易於維護。

使用 Sentinel 進行 Terraform IaC 策略測試與開發

前言

本文將詳細介紹如何使用 HashiCorp Sentinel 進行 Terraform 基礎設施即程式碼(IaC)的策略測試與開發。Sentinel 是一種策略即程式碼框架,用於強制執行組織的治理和安全策略。本文將重點介紹如何撰寫和測試 Sentinel 策略,以確保 Terraform 組態符合組織的標準。

編寫 required-labels Sentinel 策略

以下是一個範例 Sentinel 策略,用於驗證 Google Cloud Storage Bucket 是否具有必要的標籤:

# required-labels policy
import "tfplan/v2" as tfplan
import "types"
import "common-functions" as cf

param p_required_labels default ["billing", "env", "owner"]

print("Parameters:\np_required_labels =", p_required_labels, "\n")

# Validate google storage bucket labels
invalid_buckets = func() {
    bads = []
    buckets = filter tfplan.resource_changes as _, resource_changes {
        resource_changes.type is "google_storage_bucket" and
        resource_changes.mode is "managed" and
        (resource_changes.change.actions contains "create" or
        resource_changes.change.actions contains "update") and
        resource_changes.provider_name is "registry.terraform.io/hashicorp/google"
    }
    print("Found", length(buckets), "bucket(s)")
    for buckets as address, b {
        print("Bucket:", address, "labels =", b.change.after.labels)
        if (types.type_of(b.change.after.labels) is "undefined") or
           (not cf.list_in_list(p_required_labels, keys(b.change.after.labels))) {
            append(bads, address)
        }
    }
    return bads
}

main = rule {
    length(invalid_buckets()) is 0
}

內容解密:

  1. 匯入必要的模組tfplan/v2 用於存取 Terraform 計劃資料,types 提供型別檢查功能,common-functions 包含自定義函式。
  2. 定義引數p_required_labels 指定必要的標籤名稱,預設為 ["billing", "env", "owner"]
  3. 過濾資源:使用 filter 表示式篩選出需要評估的 google_storage_bucket 資源。
  4. 驗證標籤:檢查資源的標籤是否包含必要的標籤名稱。
  5. 主規則:如果沒有無效的 Bucket,則策略透過。

list_in_list 函式實作

以下是一個自定義函式,用於檢查一個列表中的所有元素是否都存在於另一個列表中:

# list_in_list function
import "types"

list_in_list = func(l1, l2) {
    if types.type_of(l1) is "undefined" {
        print("List 1 is undefined.")
        return false
    }
    if types.type_of(l2) is "undefined" {
        print("List 2 is undefined.")
        return false
    }
    if length(l1) is 0 {
        print("Length of list 1 is 0.")
        return false
    }
    if length(l1) > length(l2) {
        print("Length of list 1 is greater than length of list 2.")
        return false
    }
    for l1 as _, value {
        if l2 not contains value {
            print(value, "not found in list 2.")
            return false
        }
    }
    return true
}

內容解密:

  1. 型別檢查:確保輸入列表不是 undefined
  2. 長度檢查:如果第一個列表為空或長度大於第二個列表,則傳回 false
  3. 元素檢查:遍歷第一個列表,檢查每個元素是否存在於第二個列表中。

設定 Sentinel 組態檔案

以下是一個範例 Sentinel 組態檔案,用於設定策略的執行:

# sentinel.hcl configuration file
import "module" "common-functions" {
    source = "./common/functions.sentinel"
}

mock "tfplan/v2" {
    module {
        source = "./mocks/mock-tfplan-v2.sentinel"
    }
}

policy "required-labels" {
    source = "./required-labels.sentinel"
    enforcement_level = "hard-mandatory"
    params = {
        "p_required_labels" = ["billing", "env", "owner"]
    }
}

內容解密:

  1. 匯入模組:載入自定義函式模組。
  2. 模擬資料:設定模擬的 Terraform 計劃資料。
  3. 策略設定:指定策略檔案、執行級別和引數。

編寫和執行 Sentinel 測試

以下是一個範例測試組態檔案,用於測試上述 Sentinel 策略:

# required-labels/fail.hcl
import "module" "common-functions" {
    source = "../../common/functions.sentinel"
}

mock "tfplan/v2" {
    module {
        source = "./mock-tfplan-v2-fail.sentinel"
    }
}

test {
    rules = {
        main = false
    }
}

param "p_required_labels" {
    value = ["billing", "env", "owner"]
}
# required-labels/pass.hcl
import "module" "common-functions" {
    source = "../../common/functions.sentinel"
}

mock "tfplan/v2" {
    module {
        source = "./mock-tfplan-v2-pass.sentinel"
    }
}

test {
    rules = {
        main = true
    }
}

param "p_required_labels" {
    value = ["billing", "env", "owner"]
}

內容解密:

  1. 匯入模組:與策略檔案相同,載入必要的模組。
  2. 模擬資料:使用不同的模擬資料檔案來測試透過和失敗的情況。
  3. 測試規則:指定預期的測試結果。