4. ConstraintTemplate 入门:Rego 语言基础与第一个禁止特权容器模板

ConstraintTemplate 是 OPA Gatekeeper 的核心扩展点,它使用 Rego 语言定义策略逻辑。本章将带你从零开始掌握 Rego 基础语法,并亲手编写第一个 ConstraintTemplate——禁止创建特权容器。

4.1 Rego 语言核心概念

Rego 是一种为策略即代码(Policy as Code)设计的声明式查询语言,由 Open Policy Agent 项目创建。与传统的命令式编程不同,Rego 专注于“数据是什么”而非“如何获取数据”。

概念 说明 类比
规则(Rule) 定义策略逻辑的基本单元,形如 rule_name { condition } 类似编程语言中的函数
查询(Query) 对规则求值,返回 true/false 或数据集合 类似 SQL 的 SELECT
文档(Document) Rego 将输入数据视为嵌套文档,通过点号访问 类似 JSON 对象
迭代(Iteration) 使用 some 关键字遍历集合 类似 for-each 循环
默认值(Default) 为规则设置默认返回值,避免未定义 类似变量初始化
💡 关键理解: Rego 中所有规则默认是“与”关系(AND)。当规则体中有多个条件时,必须全部满足规则才为 true。如果需要“或”关系,需定义多条同名规则。

4.2 Rego 基础语法速览

以下是最常用的 Rego 语法模式,掌握它们即可编写 90% 的 Gatekeeper 策略。

4.2.1 变量与赋值

# 变量赋值使用 :=
name := "privileged-container"

# 使用 input 引用传入的 Kubernetes 资源
container := input.spec.containers[_]

4.2.2 条件判断

# 比较运算符:==, !=, <, >, <=, >=
container.securityContext.privileged == true

# 集合成员检查
container.securityContext.capabilities.add[_] == "SYS_ADMIN"

4.2.3 迭代与集合

# 使用 some 遍历数组
some container in input.spec.containers

# 使用下划线通配符
container.securityContext.capabilities.add[_] == "NET_ADMIN"

4.2.4 规则定义

# 完整规则:violation 是 Gatekeeper 约定的规则名
violation[{"msg": msg}] {
    container := input.spec.containers[_]
    container.securityContext.privileged == true
    msg := "特权容器被禁止"
}
⚠️ 常见陷阱: Rego 中 = 是统一(unification)操作符,:= 是赋值操作符。在规则体内,:= 只能对变量赋值一次;== 用于比较。初学者最容易混淆这三者的用法。

4.3 ConstraintTemplate 结构解析

一个完整的 ConstraintTemplate 包含三个主要部分:

  1. metadata:模板名称和标签
  2. spec.crd.spec:定义约束(Constraint)的 OpenAPI v3 模式,即约束可以接收哪些参数
  3. spec.targets:指定目标平台(如 admission.k8s.gatekeeper.sh)和 Rego 策略代码
ConstraintTemplate 三层结构 metadata name: k8sblockprivileged · 模板标识与标签 spec.crd.spec OpenAPI v3 Schema · 定义约束可接收的参数(如 exemptNamespaces) spec.targets target: admission.k8s.gatekeeper.sh rego: | (策略代码主体)

4.4 编写第一个 ConstraintTemplate:禁止特权容器

下面我们逐步构建一个完整的 ConstraintTemplate,用于拒绝所有设置了 privileged: true 的 Pod 创建请求。

4.4.1 完整模板代码

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sblockprivileged
spec:
  crd:
    spec:
      names:
        kind: K8sBlockPrivileged
      validation:
        openAPIV3Schema:
          type: object
          properties:
            exemptNamespaces:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sblockprivileged

        violation[{"msg": msg}] {
            container := input.review.object.spec.containers[_]
            container.securityContext.privileged == true
            msg := sprintf("容器 '%v' 设置了 privileged=true,特权容器被禁止", [container.name])
        }

        violation[{"msg": msg}] {
            container := input.review.object.spec.initContainers[_]
            container.securityContext.privileged == true
            msg := sprintf("初始化容器 '%v' 设置了 privileged=true,特权容器被禁止", [container.name])
        }

        violation[{"msg": msg}] {
            container := input.review.object.spec.ephemeralContainers[_]
            container.securityContext.privileged == true
            msg := sprintf("临时容器 '%v' 设置了 privileged=true,特权容器被禁止", [container.name])
        }

4.4.2 代码逐段解读

代码段 作用
package k8sblockprivileged 声明 Rego 包名,必须与 ConstraintTemplate 名称一致(小写)
violation[{"msg": msg}] Gatekeeper 约定的规则入口,返回违规消息数组
input.review.object.spec.containers[_] 遍历 Pod 中的所有容器
container.securityContext.privileged == true 检查特权标志是否为 true
sprintf(...) 格式化输出违规消息,包含容器名称
📌 设计要点: 我们为 containers、initContainers、ephemeralContainers 分别定义了三条规则。这是为了覆盖 Kubernetes 中所有容器类型,避免攻击者通过 init 容器或临时容器绕过策略。

4.5 创建对应的约束(Constraint)

ConstraintTemplate 只是策略模板,还需要创建具体的约束实例来激活策略:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockPrivileged
metadata:
  name: block-privileged-global
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces:
      - kube-system
      - gatekeeper-system
  parameters:
    exemptNamespaces:
      - istio-system

约束通过 match 字段指定作用范围,通过 parameters 传递模板中定义的参数。

4.6 验证与测试

部署完成后,尝试创建一个特权容器来验证策略是否生效:

# 测试:创建特权容器(应被拒绝)
kubectl run test-privileged --image=nginx --privileged=true

# 预期输出:
# Error: admission webhook "validation.gatekeeper.sh" denied the request:
# [block-privileged-global] 容器 'test-privileged' 设置了 privileged=true,特权容器被禁止
Gatekeeper 准入控制流程 用户 kubectl API Server Gatekeeper 拒绝创建 privileged=true 违规 403 Forbidden

4.7 常见问题与排错

  • 约束未生效:检查 Gatekeeper 是否已部署并配置了 ValidatingWebhookConfiguration
  • Rego 语法错误:使用 kubectl describe constrainttemplate k8sblockprivileged 查看错误日志
  • 误拦截正常 Pod:利用 excludedNamespaces 或 exemptNamespaces 参数添加白名单
  • 容器类型遗漏:务必覆盖 containers、initContainers、ephemeralContainers
🔧 调试技巧: 使用 kubectl gatekeeper trace 命令(需安装 Gatekeeper CLI 插件)可以实时跟踪 Rego 策略的求值过程,精确定位违规原因。