4. Gatekeeper 核心概念:约束模板、约束与数据注入
在深入实战之前,必须透彻理解 Gatekeeper 的三个核心抽象:约束模板(ConstraintTemplate)、约束(Constraint) 以及 数据注入(data.inventory)。这三者构成了策略定义、策略实例化与策略上下文获取的完整链路。理解它们的协作关系,是编写可维护、可复用的安全基线门禁策略的前提。
4.1 约束模板(ConstraintTemplate)—— 策略的“类”
约束模板是 Gatekeeper 策略的蓝图,它使用 Rego 语言(OPA 的策略语言)定义了一类规则的逻辑骨架。你可以将其类比为面向对象编程中的类(Class):它描述了“要检查什么”以及“如何判断违规”,但不包含具体的参数值(如哪些命名空间、哪些标签)。
4.1.1 模板结构解剖
一个标准的约束模板 YAML 包含以下核心字段:
| 字段路径 | 类型 | 说明 |
|---|---|---|
apiVersion |
string | 固定为 templates.gatekeeper.sh/v1 |
kind |
string | 固定为 ConstraintTemplate |
spec.crd.spec.names.kind |
string | 该模板实例化后对应的约束资源名称(如 K8sRequiredLabels) |
spec.targets[0].rego |
string | Rego 策略代码,包含 violation 规则 |
spec.targets[0].libs |
[]string | (可选)引用的 Rego 库代码,用于复用辅助函数 |
4.1.2 Rego 规则核心:violation 块
Rego 代码中最重要的部分是 violation[{"msg": msg}] 块。当该块的条件为真时,Gatekeeper 判定为违规,并拒绝变更或生成审计告警。
# 示例:检查必须包含 "security-tier" 标签的约束模板片段
violation[{"msg": msg}] {
# 从 input.review.object 中提取待审查的资源
obj := input.review.object
# 检查是否缺少 required-label
not obj.metadata.labels["security-tier"]
# 构造违规消息
msg := sprintf("资源 %v 缺少必需的标签 'security-tier'", [obj.metadata.name])
}
input.review 是 Gatekeeper 注入的审查上下文,包含待操作资源的完整对象(.object)以及旧对象(.oldObject,用于更新操作)。不要直接使用 input 的顶层字段,必须通过 input.review 访问。
4.2 约束(Constraint)—— 策略的“实例”
约束是约束模板的具体实例,相当于面向对象中的对象(Object)。它通过 spec.match 字段指定该策略作用于哪些资源,并通过 spec.parameters 字段向模板注入具体的参数值(如允许的标签列表、禁止的镜像仓库等)。
4.2.1 约束的匹配机制
spec.match 支持多种匹配维度,形成灵活的策略作用域:
- kinds:匹配资源类型(如 Pod、Deployment、Service)及其 API 组。
- namespaces:匹配命名空间列表。
- labelSelector:匹配资源标签或命名空间标签。
- namespaceSelector:匹配命名空间的标签选择器。
- scope:匹配集群作用域(Cluster)或命名空间作用域(Namespaced)资源。
# 约束实例:要求生产命名空间下的 Pod 必须包含 "security-tier" 标签
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels # 对应模板中 spec.crd.spec.names.kind
metadata:
name: prod-pod-must-have-security-tier
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "prod-frontend"
- "prod-backend"
parameters:
labels:
- key: "security-tier"
allowedRegex: "^critical|high$" # 注入给模板的参数
4.3 数据注入(data.inventory)—— 策略的“上下文”
很多安全基线策略需要跨资源查询,例如:“检查所有 Pod 是否引用了已存在的 Secret”或“确保 Ingress 引用的 Service 存在于同一命名空间”。此时,仅靠 input.review 中的单个资源对象是不够的。Gatekeeper 提供了 data.inventory 机制,将集群中已有的资源缓存为可查询的数据集。
4.3.1 数据注入的工作原理
- 缓存范围:Gatekeeper 通过
--audit-from-cache和--cache-used-for参数控制哪些资源被缓存到data.inventory中。 - 查询方式:在 Rego 中通过
data.inventory.namespace[ns_name][resource_type][resource_name]路径访问缓存数据。 - 典型场景:验证网络策略是否覆盖了所有 Pod、检查 RBAC 绑定是否引用了有效的 ServiceAccount。
# 使用 data.inventory 检查 Pod 引用的 ConfigMap 是否存在
violation[{"msg": msg}] {
pod := input.review.object
volume := pod.spec.volumes[_]
volume.configMap
cm_name := volume.configMap.name
# 从缓存中查询该命名空间下是否存在该 ConfigMap
not data.inventory.namespace[pod.metadata.namespace]["v1"]["ConfigMap"][cm_name]
msg := sprintf("Pod %v 引用了不存在的 ConfigMap %v", [pod.metadata.name, cm_name])
}
data.inventory 是全量缓存,对于大规模集群(数千个资源),频繁查询可能导致 Gatekeeper 性能下降。建议在 Rego 中使用 data.inventory 时,优先通过命名空间和资源类型缩小查询范围,避免全量遍历。
4.4 三者的协作关系(SVG 架构图)
下图展示了约束模板、约束与数据注入在 Gatekeeper 策略生命周期中的协作流程:
4.5 核心概念对比表
| 维度 | 约束模板(ConstraintTemplate) | 约束(Constraint) | 数据注入(data.inventory) |
|---|---|---|---|
| 类比 | 类(Class) | 对象(Instance) | 数据库视图(View) |
| 职责 | 定义规则逻辑(Rego) | 定义规则作用域与参数 | 提供跨资源查询能力 |
| 是否可复用 | 是,一个模板可被多个约束引用 | 是,同一模板可实例化为不同约束 | 全局缓存,所有模板可查询 |
| 变更频率 | 低(规则逻辑稳定后很少改) | 中(随安全基线水位调整) | 自动同步集群状态 |
| 性能影响 | 低(仅编译一次) | 中(匹配规则影响审查路径) | 高(全量缓存,需谨慎查询) |
4.6 实战中的常见误区
- 误区一:在约束模板中硬编码命名空间或标签值。 正确做法:模板只定义逻辑,所有可变参数通过
input.parameters从约束注入。 - 误区二:在 Rego 中直接遍历
data.inventory的所有命名空间。 正确做法:先通过input.review.object.metadata.namespace缩小范围,再查询具体资源类型。 - 误区三:约束的
spec.match与spec.parameters混淆。 记住:match决定“对谁生效”,parameters决定“生效时的具体规则值”。
package your_package_name,并使用 import future.keywords 启用 Rego 未来关键字(如 in),使代码更简洁可读。