4. Rego 语言入门:基本语法、规则定义、查询与数据模型
Rego 是 OPA 的声明式查询语言,专门用于定义策略规则。它脱胎于 Datalog,但扩展了对 JSON/YAML 等结构化数据的原生支持。理解 Rego 的核心概念——数据模型、规则与查询——是编写安全合规策略的基础。
4.1 数据模型:输入、数据与文档
Rego 将一切视为 文档(Document)。OPA 引擎在评估策略时,主要依赖两类文档:
| 文档类型 | 变量名 | 说明 | 示例(Serverless 流水线) |
|---|---|---|---|
| 输入文档 | input |
每次查询时由外部传入的临时数据,代表当前请求或事件。 | 流水线构建请求的 JSON:{"method":"POST","path":"/deploy","user":"ci-bot"} |
| 数据文档 | data |
OPA 启动时加载的持久化数据,包含策略规则本身和外部数据(如黑名单、配置)。 | 允许部署的命名空间列表:data.allowed_namespaces |
关键理解: 策略规则也是
data 文档的一部分。当你定义规则 allow { ... } 时,实际上是在 data 下创建了一个名为 allow 的文档。
4.2 基本语法与表达式
Rego 语法简洁,核心是 表达式(Expression) 的组合。表达式返回 true 或 false,多个表达式通过逻辑运算符连接。
- 字面量:字符串、数字、布尔值、
null。 - 变量:以
_或字母开头,如user、namespace。变量在规则中必须被赋值一次(单一赋值)。 - 引用:通过点号或方括号访问嵌套数据,如
input.method、data.roles["admin"]。 - 数组/对象复合值:
["a","b"]、{"key":"value"}。 - 比较运算符:
==、!=、<、>、<=、>=。 - 逻辑运算符:
not、and(默认,用分号或换行连接)、or(用else或括号显式表达)。
# 基本表达式示例
input.method == "POST"
input.user != "anonymous"
count(data.allowed_namespaces) > 0
4.3 规则定义:完整规则与部分规则
规则是 Rego 的核心构建块。根据返回值类型,规则分为两类:
| 规则类型 | 返回值 | 定义方式 | 典型用途 |
|---|---|---|---|
| 完整规则 | 单个布尔值或标量 | 规则名 := 表达式 |
全局决策(允许/拒绝) |
| 部分规则 | 一组值(集合或对象) | 规则名[键] := 值 { 条件 } |
生成可迭代的策略数据 |
4.3.1 完整规则(Complete Rules)
使用 := 定义,规则体中的所有表达式必须同时为真,规则才为 true。
# 完整规则:判断部署请求是否来自受信任的 CI 用户
allow_deploy := true {
input.user == "ci-bot"
input.method == "POST"
input.path == "/deploy"
}
# 查询:data.allow_deploy
# 当 input 满足所有条件时返回 true,否则返回 undefined(视为 false)
4.3.2 部分规则(Partial Rules)
使用 [...] 定义,可以为集合或对象生成多个值。每个满足条件的表达式都会产生一个结果。
# 部分规则:生成所有被允许部署的命名空间集合
allowed_namespaces[ns] {
ns := input.namespace
ns != "production"
ns != "critical"
}
# 查询:data.allowed_namespaces
# 返回:["staging", "dev"](假设 input.namespace 包含这些值)
4.4 查询:如何获取策略决策
查询是向 OPA 提问的方式。通过 REST API 或 CLI 执行查询,OPA 会评估所有相关规则并返回结果。
- 简单查询:直接请求规则文档,如
data.allow_deploy。 - 带变量的查询:使用变量绑定结果,如
data.allowed_namespaces[x]会返回所有满足条件的x值。 - 使用
with关键字:在查询中模拟input或data的值,用于测试。
# 使用 opa eval 命令进行查询
# 查询完整规则
opa eval --data policy.rego --input input.json "data.allow_deploy"
# 查询部分规则的所有值
opa eval --data policy.rego --input input.json "data.allowed_namespaces"
常见误区: 完整规则如果没有任何条件满足,返回的是
undefined 而非 false。在策略中,通常将 undefined 视为拒绝。建议显式定义 default allow = false 来避免歧义。
4.5 数据模型与规则关系图
下图展示了 OPA 数据模型、规则定义与查询执行之间的核心关系:
4.6 常用内置函数与迭代
Rego 提供了丰富的内置函数,用于处理字符串、数组、集合和时间。在 Serverless 流水线策略中,以下函数高频使用:
| 函数 | 签名 | 用途 | 示例 |
|---|---|---|---|
count |
count(x) |
返回数组/集合/对象的元素个数 | count(input.images) > 0 |
split |
split(str, delimiter) |
按分隔符拆分字符串为数组 | split(input.image_ref, ":")[0] |
glob.match |
glob.match(pattern, delimiters, match) |
通配符匹配,常用于路径或镜像名匹配 | glob.match("registry.example.com/*", [], input.image) |
time.now_ns |
time.now_ns() |
返回当前 Unix 纳秒时间戳 | time.now_ns() > input.expiry |
4.6.1 迭代与推导式
Rego 使用 推导式(Comprehension) 来迭代集合或数组,生成新的集合/对象。这是编写部分规则的核心技巧。
# 数组推导式:从 input.images 中提取所有包含 "vuln" 的镜像
vulnerable_images := [image |
image := input.images[_]
contains(image, "vuln")
]
# 集合推导式:生成所有被扫描出的高危 CVE 集合
high_cves := {cve |
some cve in input.scan_results.cves
cve.severity == "CRITICAL"
cve.id
}
# 对象推导式:构建镜像名到标签的映射
image_labels := {image: label |
some i in input.images
image := i.name
label := i.label
}
实践要点: 在 Serverless 流水线中,
input 通常包含构建元数据、镜像列表、环境变量等。使用推导式可以高效地从这些数据中提取出违反策略的元素,然后通过 count() 或 not 组合成最终的允许/拒绝决策。
4.7 规则组合与默认值
实际策略很少只包含一条规则。Rego 允许通过 规则组合 和 默认值 构建层次化的决策逻辑。
# 默认拒绝
default allow := false
# 规则 1:允许来自 CI 的部署
allow := true {
input.user == "ci-bot"
input.action == "deploy"
}
# 规则 2:允许来自审计员的只读操作
allow := true {
input.user == "auditor"
input.action == "read"
}
# 规则 3:拒绝使用过时的镜像标签
deny[msg] {
image := input.images[_]
not glob.match("*:latest", [], image)
msg := sprintf("镜像 %v 未使用 latest 标签,禁止部署", [image])
}
# 最终决策:allow 为 true 且没有 deny 消息
final_allow := true {
allow == true
count(deny) == 0
}
上述模式是 Serverless 流水线策略的典型结构:先定义默认拒绝,再列出允许条件,最后通过 deny 规则收集所有违规细节,确保策略的可审计性。