3. Rego 语言入门:语法基础、规则与表达式、数据与文档模型

Rego 是 OPA 的专用策略语言,脱胎于 Datalog,专为在复杂嵌套 JSON/YAML 数据上执行声明式查询与策略决策而设计。理解 Rego 的核心概念——文档模型、规则与表达式——是编写安全准入策略的前提。

3.1 数据与文档模型:一切皆为文档

Rego 将输入数据(input)、外部数据(data)以及策略输出都视为文档。文档是嵌套的键值对集合,类似 JSON 对象。OPA 通过查询这些文档来做出决策。

文档类型 说明 示例路径
input 每次策略评估时传入的请求数据(如 AdmissionReview) input.request.object.spec.containers[0].image
data OPA 加载的外部数据(如绑定到 OPA 的集群资源、配置清单) data.whitelist.registries
result 规则执行后生成的输出文档(通常是布尔值或对象) allow 或 deny[msg]
核心思维: 在 Rego 中,没有“函数调用”或“变量赋值”的传统概念,只有“文档查询”和“绑定”。你写的是“如果数据满足什么条件,则文档为真”。

下图展示了 OPA 在评估策略时,如何将 input 和 data 文档合并,通过 Rego 规则生成决策文档的过程。

Rego 文档模型:输入 → 规则 → 输出 input 文档 AdmissionReview Pod spec, labels data 文档 白名单、命名空间 集群配置 Rego 规则引擎 查询 & 绑定 输出文档 allow / deny[msg] 策略评估时 input 由 API Server 传入

3.2 Rego 语法基础

3.2.1 字面量与注释

Rego 支持字符串、数字、布尔值、null 以及数组/对象。注释使用 # 开头。

# 这是一个 Rego 注释
allowed_registry := "gcr.io"          # 字符串
max_replicas := 5                     # 数字
require_tls := true                   # 布尔值
blocked_tags := ["latest", "dev"]     # 数组
labels := {"team": "platform"}        # 对象

3.2.2 变量与引用

变量名以 _ 或字母开头,区分大小写。引用使用点号或方括号访问嵌套文档。

# 变量绑定
user := input.request.userInfo.username

# 引用嵌套字段
container_images := input.request.object.spec.containers[_].image

# 方括号访问动态键
metadata := input.request.object.metadata
name := metadata["name"]
注意: Rego 中的变量一旦绑定,在同一个规则体内不可重新赋值。这是声明式语言的特性,有助于避免副作用。

3.3 规则与表达式

规则是 Rego 的核心。一个规则定义了一个文档(通常是布尔值或集合)。规则体由一系列表达式组成,所有表达式都为真时,规则才为真。

3.3.1 完整规则与部分规则

规则类型 语法 输出 示例
完整规则 name := value { body } 单个值(布尔、字符串等) allow := true { ... }
部分规则 name[key] := value { body } 键值对集合 deny[msg] { ... }
默认规则 default name := value 为规则提供默认值 default allow := false
# 完整规则:默认拒绝
default allow := false

# 完整规则:允许条件
allow := true {
    input.request.operation == "CREATE"
    input.request.object.metadata.namespace == "production"
}

# 部分规则:收集拒绝消息
deny[msg] {
    input.request.operation == "DELETE"
    msg := sprintf("不允许删除命名空间 %v 中的资源", [input.request.object.metadata.namespace])
}

3.3.2 常用表达式与迭代

Rego 使用 some 关键字进行迭代,使用 ==、!=、<、> 等比较运算符。集合操作(in、union)非常高效。

# 迭代容器列表,检查镜像仓库
deny[msg] {
    some i
    container := input.request.object.spec.containers[i]
    not startswith(container.image, "gcr.io/my-project/")
    msg := sprintf("容器 %v 使用了未授权的镜像: %v", [container.name, container.image])
}

# 使用 in 检查是否在白名单中
allow := true {
    input.request.object.metadata.namespace in data.allowed_namespaces
}

3.4 内置函数与聚合

Rego 提供了丰富的内置函数,用于字符串处理、正则匹配、时间操作、加密校验等。在安全准入场景中,以下函数最常用:

函数 用途 安全准入示例
startswith(str, prefix) 检查字符串前缀 验证镜像仓库前缀
regex.match(pattern, str) 正则匹配 校验标签格式
count(collection) 计算集合元素数量 限制容器数量
sprintf(format, args) 格式化字符串 构造拒绝消息
glob.match(pattern, delimiters, match) Glob 模式匹配 匹配命名空间模式
# 使用正则表达式校验标签
deny[msg] {
    some k, v in input.request.object.metadata.labels
    not regex.match("^[a-zA-Z0-9._-]{1,63}$", v)
    msg := sprintf("标签 %v 的值 '%v' 不符合规范", [k, v])
}

# 使用 count 限制特权容器数量
deny[msg] {
    privileged_containers := [c | c := input.request.object.spec.containers[_]; c.securityContext.privileged]
    count(privileged_containers) > 0
    msg := "不允许运行特权容器"
}

3.5 完整策略示例:准入控制

以下是一个完整的 Rego 策略,用于 Knative Service 的安全准入控制。它结合了文档模型、规则和内置函数。

package knative.security

# 默认拒绝
default allow := false

# 允许条件:所有检查通过
allow := true {
    not deny[__]
}

# 规则 1:禁止使用 latest 标签
deny[msg] {
    some i
    container := input.request.object.spec.template.spec.containers[i]
    endswith(container.image, ":latest")
    msg := sprintf("容器 %v 禁止使用 latest 标签", [container.name])
}

# 规则 2:必须设置资源限制
deny[msg] {
    some i
    container := input.request.object.spec.template.spec.containers[i]
    not container.resources.limits
    msg := sprintf("容器 %v 必须设置资源 limits", [container.name])
}

# 规则 3:只允许从受信任仓库拉取镜像
deny[msg] {
    some i
    container := input.request.object.spec.template.spec.containers[i]
    not startswith(container.image, "gcr.io/trusted/")
    msg := sprintf("容器 %v 的镜像来源不被允许: %v", [container.name, container.image])
}
策略解读: 上述策略定义了一个 deny 集合。只要有一条 deny 规则被满足(即集合非空),allow 就为 false。这种“默认拒绝 + 显式拒绝”模式是 OPA 策略的标准写法。

下图展示了该策略在 Knative Service 准入控制中的评估流程。

Rego 策略评估流程:Knative Service 准入 input Knative Service Rego 规则引擎 deny[msg] (latest 标签) deny[msg] (资源限制) deny[msg] (镜像仓库) 输出文档 allow = true/false deny = [msg1, msg2] 评估结果:如果 deny 集合为空,则 allow = true,准入通过 如果 deny 集合非空,则 allow = false,准入拒绝并返回消息

3.6 调试与测试

OPA 提供了 opa eval 和 opa test 命令,用于本地调试策略。在编写安全准入策略时,建议遵循以下工作流:

  1. 编写规则文件(如 policy.rego)
  2. 准备测试数据(如 input.json)
  3. 使用 opa eval 验证:opa eval -i input.json -d policy.rego "data.knative.security.allow"
  4. 编写单元测试(policy_test.rego),使用 opa test 运行
# policy_test.rego 示例
package knative.security

test_allow_trusted_image {
    allow with input as {"request": {"object": {"spec": {"template": {"spec": {"containers": [{"name": "svc", "image": "gcr.io/trusted/my-app:v1", "resources": {"limits": {"cpu": "1"}}}]}}}}}}
}

test_deny_latest_tag {
    deny["容器 svc 禁止使用 latest 标签"] with input as {"request": {"object": {"spec": {"template": {"spec": {"containers": [{"name": "svc", "image": "gcr.io/trusted/my-app:latest", "resources": {"limits": {"cpu": "1"}}}]}}}}}}
}
最佳实践: 在 DevSecOps 流水线中,将 opa test 集成到 CI 阶段,确保每次策略变更都通过单元测试,避免将有缺陷的策略部署到生产环境。