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 规则生成决策文档的过程。
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 准入控制中的评估流程。
3.6 调试与测试
OPA 提供了 opa eval 和 opa test 命令,用于本地调试策略。在编写安全准入策略时,建议遵循以下工作流:
- 编写规则文件(如
policy.rego) - 准备测试数据(如
input.json) - 使用
opa eval验证:opa eval -i input.json -d policy.rego "data.knative.security.allow" - 编写单元测试(
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 阶段,确保每次策略变更都通过单元测试,避免将有缺陷的策略部署到生产环境。