4. Rego 语法入门:规则、表达式、迭代、内置函数,编写第一个拒绝策略

Rego 是 OPA 和 Gatekeeper 的策略描述语言,它脱胎于 Datalog,专为在复杂 JSON 数据上执行声明式查询而设计。理解 Rego 的核心语法是编写 Gatekeeper 约束模板的前提。本章将带你从零构建 Rego 的认知模型,并完成第一个拒绝策略。

4.1 Rego 的核心认知:查询而非编程

Rego 不是命令式语言(如 Python、Go),它没有“先做 A,再做 B”的控制流。Rego 是声明式查询语言:你定义一组规则(Rules),OPA 引擎根据输入数据(input)和策略数据(data)自动推导出所有满足条件的绑定。

特性 命令式语言 Rego(声明式)
核心思想 如何做(How) 是什么(What)
控制流 循环、条件分支、顺序执行 规则推导、回溯、绑定
副作用 允许修改变量、写文件 无副作用,变量只绑定一次
调试视角 单步跟踪 查询所有满足条件的解
思维转换提示: 在 Rego 中,= 不是赋值,而是合一(Unification)。你告诉引擎“变量 X 必须等于某个值”,引擎会尝试找到所有使规则成立的 X。

4.2 规则(Rules):策略的基本单元

规则是 Rego 的构建块。一个规则由一个头部(Head)和一个主体(Body)组成。主体中的表达式必须全部为真,规则头部才为真。

4.2.1 完整规则与部分规则

  • 完整规则(Complete Rule): 头部是一个布尔值或标量,主体决定其真值。
  • 部分规则(Partial Rule): 头部包含变量,引擎会为每个满足条件的变量生成一个集合或对象。
# 完整规则:返回布尔值
allow_all = true { true }

# 部分规则:返回一个集合,包含所有满足条件的 namespace
blocked_ns[name] {
    input.request.object.metadata.namespace == name
    name == "kube-system"
}

# 部分规则:返回一个对象
allowed_images[image] {
    image := input.request.object.spec.containers[_].image
    startswith(image, "registry.example.com/")
}

4.2.2 默认规则与否定

使用 default 关键字为规则设置默认值,避免规则未定义导致的意外结果。使用 not 进行否定判断。

# 默认规则:默认允许
default allow = true

# 否定规则:如果满足条件,则拒绝
deny[msg] {
    input.request.operation == "CREATE"
    not input.request.object.metadata.labels["owner"]
    msg := "所有资源必须包含 owner 标签"
}

4.3 表达式(Expressions):构建逻辑条件

Rego 表达式包括标量比较、模式匹配、复合表达式和函数调用。

表达式类型 示例 说明
标量比较 count > 3 支持 ==, !=, <, >, <=, >=
模式匹配 input.kind == "Pod" 精确匹配字符串或数字
复合表达式 a == b; c == d 分号表示逻辑 AND,换行也视为 AND
函数调用 startswith(name, "prod-") 调用内置函数,返回布尔值
常见陷阱: Rego 中 = 是合一,== 是比较。不要混淆。在规则头部使用 := 进行局部变量声明,在主体中使用 == 进行相等性检查。

4.4 迭代(Iteration):处理集合与数组

Rego 没有传统的 for 循环。迭代通过变量绑定和集合推导隐式完成。使用下划线 _ 忽略索引,使用 some 关键字显式声明迭代变量。

4.4.1 隐式迭代

# 遍历所有容器,检查镜像仓库
violation[{"msg": msg}] {
    container := input.request.object.spec.containers[_]
    not startswith(container.image, "allowed-registry.io/")
    msg := sprintf("容器 %v 使用了不允许的镜像仓库", [container.name])
}

4.4.2 使用 some 显式迭代

# 显式声明索引变量 i
deny[msg] {
    some i
    container := input.request.object.spec.containers[i]
    container.resources.limits.cpu == "0"
    msg := sprintf("容器 %v 的 CPU 限制不能为 0", [container.name])
}

4.4.3 集合推导与对象推导

# 集合推导:收集所有违反策略的镜像
bad_images := {image |
    image := input.request.object.spec.containers[_].image
    not startswith(image, "trusted.")
}

# 对象推导:构建 key-value 映射
image_map := {name: image |
    container := input.request.object.spec.containers[_]
    name := container.name
    image := container.image
}

4.5 内置函数(Built-in Functions):策略的瑞士军刀

Rego 提供了丰富的内置函数,覆盖字符串、数组、集合、加密、HTTP 请求等。以下是 Gatekeeper 策略中最常用的几类:

类别 函数 用途
字符串 startswith, endswith, contains, regex.match 模式匹配、前缀/后缀检查
集合 intersection, union, subset 集合运算,用于标签或权限检查
数组 array.concat, count, sort 数组合并、计数、排序
对象 object.get, object.keys, object.union 安全访问嵌套字段、合并对象
加密 base64.decode, sha256 解码 Secret、校验摘要
HTTP http.send 外部数据查询(需谨慎使用)
# 使用 regex.match 进行复杂模式匹配
violation[{"msg": msg}] {
    name := input.request.object.metadata.name
    not regex.match(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`, name)
    msg := sprintf("资源名称 %v 不符合 DNS-1123 子域名规范", [name])
}

# 使用 object.get 安全访问嵌套字段
deny[msg] {
    labels := object.get(input.request.object.metadata, "labels", {})
    owner := object.get(labels, "owner", "")
    owner == ""
    msg := "资源缺少 owner 标签"
}

4.6 编写第一个拒绝策略:禁止特权容器

现在,我们将上述知识整合,编写一个完整的 Rego 策略:拒绝所有设置了 privileged: true 的 Pod。

4.6.1 策略逻辑

  1. 遍历 Pod 中的所有容器。
  2. 检查每个容器的 securityContext.privileged 字段。
  3. 如果为 true,则生成一条拒绝消息。

4.6.2 完整 Rego 代码

package admission.k8s.deny_privileged

# 默认允许
default allow = true

# 拒绝规则:如果任何容器设置了 privileged=true
deny[msg] {
    container := input.request.object.spec.containers[_]
    container.securityContext.privileged == true
    msg := sprintf("容器 '%v' 设置了 privileged=true,这是不允许的", [container.name])
}

# 显式允许规则(可选,用于覆盖 deny)
allow = false {
    deny[_]
}

4.6.3 测试输入与输出

使用 OPA 的 eval 命令测试:

# 测试输入(简化)
{
  "request": {
    "object": {
      "spec": {
        "containers": [
          {
            "name": "web",
            "securityContext": {"privileged": true}
          }
        ]
      }
    }
  }
}

# 预期输出
{
  "deny": ["容器 'web' 设置了 privileged=true,这是不允许的"],
  "allow": false
}
关键点回顾:
  • 使用 container := input.request.object.spec.containers[_] 隐式迭代所有容器。
  • 使用 == true 进行布尔值比较。
  • 使用 sprintf 构建动态错误消息。
  • deny 规则返回一个集合,allow 规则通过检查 deny 是否有元素来决定是否拒绝。

4.7 Rego 策略执行流程

下图展示了从 Kubernetes 准入请求到 Rego 策略评估的完整数据流:

Rego 策略执行流程(禁止特权容器示例) K8s API 准入请求 Gatekeeper 拦截 OPA 引擎评估 输入 input 注入 Rego 规则匹配 输出 deny/allow 准入控制器决策 说明:Rego 规则通过 隐式迭代遍历所有容器

4.8 本章小结

  • Rego 是声明式查询语言,核心是规则推导而非命令执行。
  • 规则由头部和主体组成,支持完整规则和部分规则。
  • 表达式通过合一、比较和函数调用构建逻辑条件。
  • 迭代通过变量绑定和集合推导隐式完成,无需显式循环。
  • 内置函数极大简化了字符串、集合、对象等常见操作。
  • 第一个拒绝策略展示了如何将上述概念组合成一个可运行的 Gatekeeper 策略。
下一步实践: 在本地安装 OPA,使用 opa eval 命令测试本章的示例代码。尝试修改策略,增加对 privileged: false 的显式允许逻辑,观察输出变化。