1. 为什么需要策略门禁
一句话总结: 策略门禁把「代码评审才发现的规范问题」提前到「资源创建之前拦截」,是 IaC 安全治理从被动审计走向主动防线的关键一步。
terraform plan 只回答「会变成什么」,不回答「是否允许」。合规门禁在 plan 与 apply 之间加一道闸:违反策略就拒绝合并或拒绝执行。常见违规如「存储桶公开可读」「未启用加密」「权限过宽」,人工评审成本高且易漏。
# 门禁在流水线中的位置:plan → 策略校验 → 人工/自动批准 → apply
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
# 策略引擎对 plan.json 做判定
策略引擎分两类:在 Terraform Cloud 内跑的 Sentinel,与 独立运行的 OPA/Rego、terrascan。前者绑定平台,后者可嵌入任意 CI。
一句话:门禁不是「额外的检查」,而是把安全基线固化成可执行代码,让每个 plan 都过一遍规则。
2. Sentinel 策略编写
一句话总结: Sentinel 是 HashiCorp 的策略即代码语言,面向 tfplan 的 mock 数据做判定,策略文件以 allowed/denied 结果驱动门禁。
Sentinel 直接消费 Terraform Cloud 的 plan 数据。策略文件由规则(rule)组成,规则返回布尔值,最终 main = rule 决定通过与否。
# sentinel.hcl 注册策略入口
policy "require_encryption" {
source = "./require-encryption.sentinel"
enforcement_level = "hard-mandatory"
}
# require-encryption.sentinel
import "tfplan"
config = tfplan.module_paths
main = rule {
all tfplan.resources.aws_s3_bucket as _, bucket {
all bucket.change.after as attr, value {
attr not in ["server_side_encryption_configuration"] or
value != null
}
}
}
2.1 Sentinel 判定逻辑
Sentinel 的 all ... as ... { ... } 遍历资源集合,配合 enforcement_level 决定硬拦截还是仅告警。
| enforcement_level | 行为 |
|---|---|
| advisory | 只记录不阻断 |
| soft-mandatory | 记录并提示,人工可放行 |
| hard-mandatory | 直接阻断 apply |
policy "no_public_sg" {
source = "./no-public-sg.sentinel"
enforcement_level = "soft-mandatory"
}
一句话:Sentinel 强在「与 tfplan 原生集成」,弱在「只能在 Terraform Cloud 里跑」,本地/自托管 CI 更适合 OPA。
3. OPA / Rego 策略编写
一句话总结: OPA 是通用策略引擎,Rego 是其查询语言,把 tfplan JSON 喂给 OPA,用 deny 规则表达「什么情况不允许」。
OPA 与引擎解耦:任何 CI 都能调用 opa eval。把 terraform show -json 的输出作为 input,策略文件用 Rego 编写。
# policy/main.rego
package terraform
import rego.v1
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
resource.change.after.bucket == "*"
msg := sprintf("存储桶 %s 名称过宽", [resource.address])
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_security_group"
resource.change.after.ingress[_].cidr_blocks[_] == "0.0.0.0/0"
msg := sprintf("安全组 %s 对公网开放", [resource.address])
}
3.1 用 opa eval 校验
opa eval --data policy/main.rego --input plan.json \
"data.terraform.deny"
# 期望输出空数组表示通过
opa test policy/
Rego 的关键是「规则返回条件集合」:deny 规则一旦有成员就说明存在违规,返回结果直接供 CI 判定。
一句话:OPA 把策略从「某平台的私有格式」变成「通用可移植的 Rego」,一套策略可同时服务 Terraform、K8s、服务网格。
4. Terrascan 静态扫描
一句话总结: Terrascan 直接扫 IaC 源码而非 plan,内置数百条安全基线,适合「编写期」的快速反馈,可接入 pre-commit 与 CI。
Terrascan 不依赖云账号,读取 HCL 源码就出报告,缺陷是拿不到插值后的真实值,适合做静态基线。
terrascan scan -i terraform -d ./
# 指定规则与输出格式
terrascan scan -i terraform -d ./modules \
-o json > terrascan-report.json
# 只看违规列表
terrascan scan -i terraform -d ./ | \
jq '.results.violations[] | {resource: .resource_name, rule: .rule_name}'
4.1 接入 pre-commit
在提交前拦下明显违规,把反馈前置到最便宜的阶段。
# .pre-commit-config.yaml
repos:
- repo: https://github.com/tenable/terrascan
rev: v1.18.3
hooks:
- id: terrascan
args: ["scan", "-i", "terraform"]
一句话:Terrascan 是「源码级体检」,Sentinel/OPA 是「计划级裁决」,两者互补而非替代。
5. Checkov 与 Conftest 补充
一句话总结: Checkov 用 Python 与 Graph 框架扫描云资源与 Dockerfile,Conftest 用 OPA 校验配置,多工具叠加提高覆盖但不增加心智负担。
Checkov 覆盖面广(含 AWS/Azure/GCP/K8s/Dockerfile),自带数百条内置检查,也支持自定义 Python 检查。
# custom-check.py:禁止 MySQL 使用过旧版本
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
class MysqlVersion(BaseResourceCheck):
def __init__(self):
super().__init__(
name="MySQL 版本不得低于 8.0",
id="CKV_CUSTOM_001",
supported_resources=("aws_db_instance",),
)
def scan_resource_conf(self, conf):
version = conf.get("engine_version", [None])[0]
return not (version and version.startswith("5."))
check = MysqlVersion()
checkov -f main.tf --custom-check custom-check.py
checkov -d ./modules --quiet --compact
Conftest 复用 OPA 引擎,用 Rego 校验任意配置格式,适合「Terraform + K8s YAML + Dockerfile」混合仓库的统一门禁。
# policy/terraform.rego 拒绝未加密的磁盘
package main
import rego.v1
deny[msg] {
resource := input.resource_changes[_]
resource.type == "azurerm_linux_virtual_machine"
os_disk := resource.change.after.os_disk
not os_disk.managed_disk_type
msg := sprintf("VM %s 未指定托管磁盘类型", [resource.address])
}
conftest test main.tf -p policy/terraform.rego
# 通过时输出 PASS,违规时列出 deny 消息
一句话:工具不在多而在组合——Checkov 管广度、OPA 管定制、Terrascan 管源码反馈,流水线按「源码→计划」两级串联。
6. CI 门禁集成
一句话总结: CI 门禁把策略引擎串进流水线,plan 产物 → 策略校验 → 失败即阻断,GitHub Actions 上可用 OPA/Checkov 容器与审批保护。
一个典型 GitHub Actions 门禁:拉取代码后先跑静态扫描,plan 生成 JSON 后交给 OPA 判定,最后用 environment 审批保护 apply。
# .github/workflows/policy-gate.yml
name: policy-gate
on:
pull_request:
paths: ["**.tf"]
jobs:
policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Plan
run: |
terraform init
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
- name: OPA Policy Check
uses: open-policy-agent/opa@v1
with:
args: eval --data policy/main.rego --input plan.json "data.terraform.deny"
# 本地等价流程
terraform init
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
opa eval --data policy/ --input plan.json "data.terraform.deny"
6.1 环境级审批
apply 阶段用 GitHub environment 的 required reviewers 做人工放行,形成「策略自动拦截 + 关键变更人工审批」的双保险。
一句话:CI 门禁的价值在「自动且可复现」——同样的 plan 在任何机器上跑出同样的判定,杜绝「本地过、线上挂」。
7. 审批流与审计
一句话总结: 审批流解决「谁来放行」,审计解决「出了事查得清」,两者都要把策略版本与 plan 快照留存下来。
合规门禁只是起点,完整治理还要有:谁在什么策略版本下放行、plan 当时的完整内容、以及 apply 后的实际资源快照。
# 留存审计证据
jq -c '.resource_changes[].address' plan.json > changed-resources.txt
sha256sum plan.tfplan > plan.sha256
git tag "policy-approval-$(date +%Y%m%d-%H%M)"
| 环节 | 留存物 | 用途 |
|---|---|---|
| 策略版本 | 策略仓库 tag | 回溯当时规则 |
| 计划快照 | plan.tfplan + sha256 | 复核实际变更 |
| 审批记录 | PR 评论 / 审批人 | 责任可追溯 |
| 应用记录 | apply 日志 | 事后对账 |
一句话:审批与审计是「门禁的闭环」——没有审计的门禁只是形式,没有门禁的审计只是事后追责。
8. 总结
策略与合规门禁把「安全规范」从文档变成可执行代码:
| 工具 | 定位 | 接入点 |
|---|---|---|
| Sentinel | Terraform Cloud 原生策略 | tfplan 数据 |
| OPA/Rego | 通用策略引擎 | plan.json 任意 CI |
| Terrascan | 源码静态扫描 | pre-commit/CI |
| Checkov | 广度基线扫描 | 源码/镜像 |
| Conftest | 配置格式校验 | 混合仓库 |
| 审批流 | 人工放行关键变更 | apply 前 |
| 审计 | 留存证据可追溯 | 全程 |
一句话收尾:策略门禁不是「多买一个工具」,而是「把安全基线放进流水线的固定闸门」。从 Terrascan 的源码反馈,到 OPA 对 plan 的硬裁决,再到关键变更的人工审批,三级防线让「合规」成为每次发布默认发生的动作,而不是上线前的突击检查。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。