作為基礎設施的編撰工具,Pulumi 額外提供了基於屬性測試 (Property testing) 概念的 Policy as Code 工具 (“CrossGuard”)。基於這個工具,專案團隊或者是組織可以為所有採用 Pulumi 所撰寫的基礎設施程式,建立獨立於所有 IaC 專案的普遍規則 (比方說資源區域、數量、網路設定、IAM 等),團隊亦可基於此再添加關於專案的額外限制。透過建立完成的可測試規則,開發者可以在本地端、持續整合流水線、以及在最後佈署前(已經產生基於各公雲或基礎設施服務的組態檔時)進行測試確認,避免需要透過人工檢閱比對,或者是已經佈署完畢後再進行確效,所產生的人力或資源的浪費。
本篇文章作為 Policy as Code 系列文章的起頭,會採用 Python 建立一個簡單的 Policy as Code 專案,為稍後建立基礎設施全面性的規則作暖身。
在開始之前
- 擁有一個 GCP 的帳號,更重要的是,它必須設定好如何付 $$
- 安裝 Google Cloud SDK,並且確認已經設定好 application-default
- Python 為 3.x <== 這非常重要!!
場景說明
組織開始於台灣建立服務,最初會透過直接使用虛擬機器,來運行一些服務,針對這些虛擬機器有以下的需求:
- 所有的虛擬機器都必須位於台灣的機房,以便符合台灣業者資料不外流的市場安全期待;
- 所有虛擬機器能夠開放的連接埠埠號不能夠大於 1024
開始著手實作 Policy as Code 程式
Step 1. 初始你的 Policy as Code 專案
利用 Pulumi 建立 Policy as Code 專案,有兩種方式:
- 建立新且獨立的規則專案
由於規則專案能夠獨立地提供給組織內所有的專案進行檢測,因此它的開發並沒有非要先處於哪個 IaC 專案下。開發者可以透過以下的指令進行規則專案的建立。pulumi policy new [type e.g. gcp_python] #本範例是基於 GCP 與 Python 進行實作。
- 於現有專案中添加規則實作
直接於現有的 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
資料夾中。version
與description
請按照團隊版號規則與期待的描述即可。
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 的組織中,以便作為組織內所有專案的合規與安全需求。