作為基礎設施的編撰工具,Pulumi 額外提供了基於屬性測試 (Property testing) 概念的 Policy as Code 工具 (“CrossGuard”)。基於這個工具,專案團隊或者是組織可以為所有採用 Pulumi 所撰寫的基礎設施程式,建立獨立於所有 IaC 專案的普遍規則 (比方說資源區域、數量、網路設定、IAM 等),團隊亦可基於此再添加關於專案的額外限制。透過建立完成的可測試規則,開發者可以在本地端、持續整合流水線、以及在最後佈署前(已經產生基於各公雲或基礎設施服務的組態檔時)進行測試確認,避免需要透過人工檢閱比對,或者是已經佈署完畢後再進行確效,所產生的人力或資源的浪費。

本篇文章作為 Policy as Code 系列文章的起頭,會採用 Python 建立一個簡單的 Policy as Code 專案,為稍後建立基礎設施全面性的規則作暖身。

在開始之前

  1. 擁有一個 GCP 的帳號,更重要的是,它必須設定好如何付 $$
  2. 安裝 Google Cloud SDK,並且確認已經設定好 application-default
  3. Python 為 3.x <== 這非常重要!!

場景說明

組織開始於台灣建立服務,最初會透過直接使用虛擬機器,來運行一些服務,針對這些虛擬機器有以下的需求:

  1. 所有的虛擬機器都必須位於台灣的機房,以便符合台灣業者資料不外流的市場安全期待;
  2. 所有虛擬機器能夠開放的連接埠埠號不能夠大於 1024

開始著手實作 Policy as Code 程式

Step 1. 初始你的 Policy as Code 專案

利用 Pulumi 建立 Policy as Code 專案,有兩種方式:

  1. 建立新且獨立的規則專案
    由於規則專案能夠獨立地提供給組織內所有的專案進行檢測,因此它的開發並沒有非要處於哪個 IaC 專案下。開發者可以透過以下的指令進行規則專案的建立。
    pulumi policy new [type e.g. gcp_python] #本範例是基於 GCP 與 Python 進行實作。
    
  2. 於現有專案中添加規則實作
    直接於現有的 IaC 專案中加入規則實作,並且重複使用建立好的 python 環境。步驟如下:
    a. 添加新的套件pulumi_policy於專案的 requirements.txt 中,並且執行 pip3 install -r requirements.txt
    b. 建立一個新的資料夾 policy_tests(名字請按團隊所建立的規則或需求而定)
    c. 於新資料夾中,新增檔案 PulumiPolicy.yml,並且添加以下內容:
         runtime:
           name: python
           options:
             virtualenv: ../venv
         version: 0.0.1
         description: A minimal Policy Pack for GCP using Python.
    

    主要是為整個規則集設定運行環境的資訊。範例採用的是python,並且重複利用專案的虛擬環境,這個環境的相關檔案位於專案父目錄的 venv 資料夾中。versiondescription 請按照團隊版號規則與期待的描述即可。

Step 2.載入必要套件

當步驟一的組態設定完成後,便可以著手進行實作。最簡便的開始方式是建立 __main__.py 檔案,並且載入以下的套件。

from pulumi_policy import (
    EnforcementLevel,
    PolicyPack,
    ReportViolation,
    ResourceValidationArgs,
    ResourceValidationPolicy,
)
  • PolicyPack:用來匯整所有的規則之用,相當於規則的註冊表。
  • EnforcementLevel:用來定義所建立的規則被違反時,Pulumi 應該採取哪種應對層級的反應,可以直接設定在PolicyPack上,也能設定在個別的規則上。 應對層級總共有三種:

    EnforcementLevel.MANDATORY : 識別違反並且中止相關更新流程至實際佈署環境 EnforcementLevel.ADVISORY : 識別違反並且產出警告訊息
    EnforcementLevel.DISABLED : 忽略此規則

  • ReportViolation : 用於實作警告訊息
  • ResourceValidationArgs : 載有請求資源的相關屬性內容,會透過方法傳呼傳入,檢測主要是依據此物件內的資訊進行。
  • ResourceValidationPolicy : 用來檢查某特定資源,不過主要用來檢查給定的請求資訊,並非實際建立於雲端服務的資源資訊。針對有相依性的資源或者是需要針對建立後的資源進行檢查,則最好採用StackValidationPolicy類別。

Step 3.實作規則

範例的作法是採用兩部分構成一條規則。第一個部分是檢測的相關實作,第二部分則是規則本身的宣告。

#region policy : only in TW
def compute_instance_located_in_tw_validator(args: ResourceValidationArgs, report_violation: ReportViolation):
    if args.resource_type == "gcp:compute/instance:Instance":
        zone=args.props["zone"]
        if not zone.startswith("asia-east1"):
            report_violation(f"Expected zone is asia-east1, but the instance is in {zone}")

compute_instance_located_in_tw=ResourceValidationPolicy(
    name="compute_instance_located_in_tw",
    description="Due to the concerns about data privacy and compliance, all compute instances should be located within TW.",
    validate=compute_instance_located_in_tw_validator,
)

#allowed port policy : not allow (> 1024)
def compute_firewall_rule_validator(args: ResourceValidationArgs, report_violation: ReportViolation):
    if args.resource_type == "gcp:compute/firewall:Firewall":
        allows=args.props["allows"]
        for rule in allows:
            for port in rule.get("ports"):
                if int(port)>1024:
                    report_violation(f"accessible port number is too big! {port}")

compute_firewall_rule = ResourceValidationPolicy(
    name="compute_firewall_rule",
    description="Accessible port number should not be larger than 1024. If necessary, please confirm the security guy to change the policies for this project",
    validate=compute_firewall_rule_validator,
)

以虛擬機器的建置區域規則為例。第一部分為實際上進行資源建立區域檢測的方法實作compute_instance_located_in_tw_validator;第二部分則為規則本身compute_instance_located_in_tw,它定義了規則的名稱、描述、與檢測方法的實作,這邊也能進行對應層級(EnforcementLevel)的設定,此處設定的優先權將高於設定於PolicyPack的應對層級。整個規則的意思是,強迫所有預期建立的虛擬機器都必須**明確地(顯式地)**指定資源創建的位置於asia_east1

Step 4.註冊規則

當規則建立完畢後,便需要將規則註冊至規則集內。此處的範例將不再進一步展示規則群組(Policy Group)。它可以將相同堆疊(Stack,一個堆疊對應至一個實際佈署的環境) 對應至相關的規則集上。舉個例子來說,正式環境需要進行檢測的規則可能相對於開發測試環境的規則,要來得多且嚴謹。

PolicyPack(
    name="intro-policy-check",
    enforcement_level=EnforcementLevel.MANDATORY,
    policies=[
        compute_instance_located_in_tw,
        compute_firewall_rule,
    ],
)

範例程式宣告所實作的兩個規則均已註冊在規則集內,除個別規則有進行設定外,一旦發生違反則拋出錯誤,並且停止佈署流程。

Step 5.執行測試

這邊簡單地透過執行pulumi up --policy-pack policy_tests來進行檢測與佈署。

$ pulumi up -y --policy-pack policy_tests
    Previewing update (dev):
        Type                     Name                    Plan       Info
    +   pulumi:pulumi:Stack      iac_intro_w_pulumi-dev  create
    +   ├─ gcp:compute:Address   addr-4-intro            create
    +   ├─ gcp:compute:Network   network-4-intro         create
    +   ├─ gcp:compute:Firewall  firewall-4-intro        create
    +   └─ gcp:compute:Instance  instance-4-intro        create

    Resources:
        + 5 create

    Policy Packs run:
        Name                               Version
        intro-policy-check (policy_tests)  (local)

    Updating (dev):
        Type                     Name                    Status      Info
    +   pulumi:pulumi:Stack      iac_intro_w_pulumi-dev  created
    +   ├─ gcp:compute:Address   addr-4-intro            created
    +   ├─ gcp:compute:Network   network-4-intro         created
    +   ├─ gcp:compute:Firewall  firewall-4-intro        created
    +   └─ gcp:compute:Instance  instance-4-intro        created

    Outputs:
        instance_external_ip  : "35.206.226.215"
        instance_name: {...

    Resources:
        + 5 created

    Policy Packs run:
        Name                               Version
        intro-policy-check (policy_tests)  (local)

    Duration: 1m3s

由於是透過update來運行pulumi程式,因此預期pulumi會在檢測通過且程式無誤情況下,實際發生佈署。此處可以發現有兩個時間點進行規則的檢測,一次發生在preview,一次發生在update。換言之,檢測發生在進行佈署前與佈署後,以便能夠盡早防止未合規的資源被建立,而即便有些檢測需在資源建立後,也能夠透過工具檢出,並且發出錯誤。

後記

有別於一般單元測試的事例型測試,屬性測試在意的是輸入、輸出與居中條件的特徵是否滿足預期需求,而基礎設施的合規與安全需求正適合此類的檢測,它通常基於一系列預期的資源屬性。Pulumi 所提供的 Policy as Code 工具,不僅可以透過如 Python 的程式語言進行實作外,也能將此類實作上傳至 Pulumi 的組織中,以便作為組織內所有專案的合規與安全需求。


1. Photo by Joe Woods from Unsplash
2. iac_intro_w_pulumi from Github