在介紹 Pulumi 測試時,有提到一種測試為「屬性測試」,可以用來測試所要建立的資源參數是否符合規範,也就是 Policy as Code 的功能。今天就來介紹 Pulumi 中,怎麼做 Policy as Code。
Policy as Code 是指將檢查資源符不符合某種規範變成以程式碼撰寫。例如檢查是否有將權限設定為 public read 的 s3 bucket、或是設定監控的 EC2 Instance。可以用程式碼的驗證規則來檢查這些資源是否符合特定的政策。如果不符合的話,可以回報哪些資源違反了政策。
而 Pulumi 也為了這個需求,製作了 Policy as Code 的工具——CrossGuard。
CrossGuard 可以使用跨程式語言撰寫 Policy as Code 的程式。不過雖說是跨語言,目前只支援了 JavaScript/TypeScript 與 Python。另外還支援使用 OPA (Open Policy Agent) 撰寫 Policy as Code。
Policy as Code 的程式碼是與 Pulumi 的 IaC 程式碼分開成不同專案,這樣可以讓我們定義一組 Policy,並套用至多個專案中重復使用。
而 Policy as Code 所使用的程式語言不需要與 IaC 專案的語言相同。也就是說,可以使用 Python 撰寫 Policy as Code,並套用到 Java 撰寫的 IaC Project 中。
首先建立一個全新的 Policy as Code 專案,使用 Pulumi CLI 就可以建立 Policy as Code 的專案了。與建立普通的 IaC 專案的區別在於,是使用 policy 子指令建立專案。
$ mkdir my-policy-as-code
$ cd my-policy-as-code
$ pulumi policy new aws-typescript
專案建立好後結構與一般的 IaC 專案很像,差別在於專案的檔名從 Pulumi.yaml 變成了 PulumiPolicy.yaml。
在 index.ts 中,會提供範例的 Policy as Code 的程式碼,該程式碼定義了 S3 bucket 中,不能有 public-read 的 Policy。
.
├── PulumiPolicy.yaml
├── index.ts
├── node_modules
├── package-lock.json
├── package.json
└── tsconfig.json
PolicyPack 是一組 Policy 的集合,做為一個可重複套用檢查的最小單元。一個 Pulumi Policy as Code 專案中只能有一個 PolicyPack。但一個 IaC 專案可以套用很多 PolicyPack 的檢查。
例如我可以在專案中套用 資安
相關的 PolicyPack、 成本管控
相關的 PolicyPack、合規
的 PolicyPack。
資安
相關的 PolicyPack 會檢查是否有不安全的 ACL、Security Group、IAM 權限是否有限制可存取的資源等成本控管
相關的 PolicyPack 可能可以檢查 stage 環境與 production 環境所使用的機器 Instance 是否符合規範合規
相關的 PolicyPack 可能會檢查是否可以開啟 Log 的服務都有開啟 Log 以供稽核等接著我們修改專案的 index.ts 的 PolickPack 的名稱為 my-aws-policies
。
new PolicyPack("my-aws-policies", {
policies: [
...
]
});
在 Pulumi 中,PolicyPack 內可以放很多 Policy。而 Policy 又分為兩種。ResourcePolicy 與 StackPolicy。
這裡的範例為檢查 VPC 是否有在 Tag 中設定 Name,因為沒有牽扯到其他資源,Tag 也是在建立資源時就會傳入的資訊屬於 Input 資料,因此適用 ResourcePolicy。
一個 Policy 的內容,需要填寫名稱、描述、驗證規則以及 enforcement level (政策遵從的等級)。
設定 Enforcement Level 的主要目的是在於告訴使用者違反這條規則的危害性高不高,主要分三種
驗證規則為一個函式,用來檢查資源是否符合規範,如果發現違反規範,則回報違反。
例如以下為一個驗證規則,如果傳入的資源 Type 為 VPC,且沒有在 Tag 中設定 Name 的話,就會使用 reportViolation 回報違反規則。
function (args: ResourceValidationArgs, reportViolation) {
if (args.type === "aws:ec2/vpc:Vpc") {
if ((args.props.tags || {})["Name"] === undefined) {
reportViolation("A 'Name' tag is required.");
}
}
},
以下為一個範例,會檢查 VPC 與 Subnet 是否都有在 Tag 中設定 Name。
{
name: "required-name-tag",
description: "A 'Name' tag is required for VPC, Subnet",
enforcementLevel: "advisory",
validateResource: [
function (args: ResourceValidationArgs, reportViolation) {
if (args.type === "aws:ec2/vpc:Vpc") {
if ((args.props.tags || {})["Name"] === undefined) {
reportViolation("A 'Name' tag is required.");
}
}
},
function (args: ResourceValidationArgs, reportViolation) {
if (args.type === "aws:ec2/subnet:Subnet") {
if ((args.props.tags || {})["Name"] === undefined) {
reportViolation("A 'Name' tag is required.");
}
}
},
]
},
另外 Pulumi 有提供一個型別友善的 Helper 函式,讓我們檢查資源不需要再去找他的 Type 名稱。除了可以使用型別的方式比對資源是否是某個型別,還會將 args 轉型成對應的資源型別。
{
name: "required-name-tag",
description: "A 'Name' tag is required for VPC, Subnet and EC2 instance",
enforcementLevel: "advisory",
validateResource: [
validateResourceOfType(aws.ec2.Vpc, (vpc: UnwrappedObject<aws.ec2.Vpc>, args, reportViolation) => {
if ((args.props.tags || {})["Name"] === undefined) {
reportViolation("A 'Name' tag is required.");
}
}),
validateResourceOfType(aws.ec2.Subnet, (vpc: UnwrappedObject<aws.ec2.Subnet>, args, reportViolation) => {
if ((args.props.tags || {})["Name"] === undefined) {
reportViolation("A 'Name' tag is required.");
}
}),
]
},
撰寫完了 Policy as Code 後,就可以將其套用至專案中了。在 Pulumi 中,是在 IaC 專案中執行 pulumi up
或是 pulumi preview
時,傳入要檢查的 PolicyPack 至該專案中。
這邊我就挑一個 Java 撰寫的 Pulumi 專案來執行 TypeScript 撰寫的 Policy。
$ cd day06-aws-vpc-java
$ pulumi up --policy-pack ../my-aws-policy
Type Name Plan
pulumi:pulumi:Stack day06-aws-vpc-java-dev
Policy Violations:
[advisory] my-aws-policies v0.0.1 required-name-tag (aws:ec2/vpc:Vpc: my-vpc)
A 'Name' tag is required for VPC, Subnet and EC2 instance
A 'Name' tag is required.
Resources:
19 unchanged
Policy Packs run:
Name Version
(../my-aws-policy) (local)
Do you want to perform this update? [Use arrows to move, type to filter]
yes
> no
details
可以從執行結果中看到,my-vpc 違反了 required-name-tag policy,不過因為這個 Policy 是 advisory 等級的,所以下方還是可以選擇 yes 執行資源的更新。
如果 enforcement level 設為 mandatory 的話,就會執行錯誤不給繼續執行資源更新了!
Diagnostics:
pulumi:pulumi:Stack (day06-aws-vpc-java-dev):
error: preview failed
Policy Violations:
[mandatory] my-aws-policies v0.0.1 required-name-tag (aws:ec2/vpc:Vpc: my-vpc)
A 'Name' tag is required for VPC, Subnet and EC2 instance
A 'Name' tag is required.
為了方便使用,Pulumi 官方有預先定義了很多常用的 Policy。主要是合規與 AWS 最佳實務的 Policy。只要安裝後,並選擇要用的 Policy,包裝成 PolicyPack,就可以直接使用。省去撰寫驗證程式的麻煩。
合規有關的 Polices 可以參考這篇文章:
Compliance Ready Policies。AWS 最佳實務可以參考 AWSGuard Policies 的文件內容。
最後,如果有購買 Pulumi Cloud 最高等級的方案「Business Critical」的話,可以將 Policy as Code 上傳至 Pulumi Cloud。經啟用後,組織內的專案都會自動套用啟用的 PolicyPack,不需要手動指定。
不過這是屬於最高級方案才有的功能... 平民版就還是要在 pulumi up
時自己指定 policy pack。