Policy as Code với OPA/Conftest: validate Terraform plan
🎯 Mục tiêu: Viết Rego policy ngăn deploy Azure Storage Account không có HTTPS-only và encryption, tích hợp vào CI pipeline qua Conftest.
🧰 Công cụ / nền tảng: Conftest, OPA, Terraform CLI, Git.
📦 Chuẩn bị: Cài Conftest (winget install Styra.Conftest hoặc download binary); cài Terraform CLI.
▶️ Các bước:
# BƯỚC 1: Tạo thư mục lab
mkdir governance-lab1 && cd governance-lab1
mkdir policy
# BƯỚC 2: Viết Terraform config cố ý vi phạm policy
cat > main.tf <<'EOF'
terraform {
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 3.0" }
}
}
resource "azurerm_storage_account" "bad_example" {
name = "stbadexample001"
resource_group_name = "rg-test"
location = "southeastasia"
account_tier = "Standard"
account_replication_type = "LRS"
# Vi phạm 1: enable_https_traffic_only = false (default false trong v3)
enable_https_traffic_only = false
# Vi phạm 2: min_tls_version không phải TLS1_2
min_tls_version = "TLS1_0"
# Vi phạm 3: blob_properties không có delete retention
}
EOF
# BƯỚC 3: Viết Rego policy
cat > policy/azure_storage.rego <<'EOF'
package main
import future.keywords.if
import future.keywords.in
# Deny storage account without HTTPS-only
deny contains msg if {
resource := input.resource_changes[_]
resource.type == "azurerm_storage_account"
resource.change.after.enable_https_traffic_only == false
msg := sprintf(
"DENY [CC6.1/A.9.4] Storage account '%s' must have HTTPS-only enabled",
[resource.address]
)
}
# Deny storage account with TLS < 1.2
deny contains msg if {
resource := input.resource_changes[_]
resource.type == "azurerm_storage_account"
not resource.change.after.min_tls_version == "TLS1_2"
msg := sprintf(
"DENY [CC6.8/A.14.2] Storage account '%s' must use min TLS 1.2 (got: %s)",
[resource.address, resource.change.after.min_tls_version]
)
}
# Warn storage account without delete retention
warn contains msg if {
resource := input.resource_changes[_]
resource.type == "azurerm_storage_account"
not resource.change.after.blob_properties
msg := sprintf(
"WARN [CC7.2] Storage account '%s' has no blob delete retention configured",
[resource.address]
)
}
EOF
# BƯỚC 4: Generate Terraform plan JSON (không cần Azure creds — dùng mock provider)
terraform init -backend=false
# Tạo plan (sẽ lỗi provider credentials, nhưng ta dùng plan JSON mock)
# Cách đơn giản hơn: tạo plan JSON mẫu trực tiếp
cat > tfplan.json <<'EOF'
{
"resource_changes": [
{
"address": "azurerm_storage_account.bad_example",
"type": "azurerm_storage_account",
"change": {
"actions": ["create"],
"after": {
"name": "stbadexample001",
"enable_https_traffic_only": false,
"min_tls_version": "TLS1_0"
}
}
}
]
}
EOF
# BƯỚC 5: Chạy Conftest validate
conftest test tfplan.json --policy policy/
# BƯỚC 6: Sửa vi phạm và test lại
cat > tfplan-fixed.json <<'EOF'
{
"resource_changes": [
{
"address": "azurerm_storage_account.good_example",
"type": "azurerm_storage_account",
"change": {
"actions": ["create"],
"after": {
"name": "stgoodexample001",
"enable_https_traffic_only": true,
"min_tls_version": "TLS1_2",
"blob_properties": { "delete_retention_policy": { "days": 7 } }
}
}
}
]
}
EOF
conftest test tfplan-fixed.json --policy policy/
✅ Kết quả mong đợi: Lần 1 (tfplan.json): Conftest in 2 dòng DENY màu đỏ (HTTPS + TLS) và 1 WARN — exit code 1 (pipeline sẽ fail). Lần 2 (tfplan-fixed.json): output 1 test, 0 failures — exit code 0 (pipeline pass).
🧹 Cleanup: cd .. && Remove-Item -Recurse governance-lab1.