3. Rego 语言基础(一):Rego 语法入门、变量、标量、复合值、引用与表达式
Rego 是 OPA 的策略语言,专为在复杂 JSON/YAML 数据中表达断言而设计。它并非通用编程语言,而是一种声明式查询语言。理解 Rego 的语法基础,是编写日志访问控制策略的第一步。本章将带你掌握 Rego 的核心词法单元与基本数据类型。
3.1 Rego 语法入门:声明式与逻辑编程
Rego 的语法深受 Datalog 和 Prolog 影响,其核心思想是:你定义“规则”(Rule),OPA 引擎负责“查询”(Query)并返回结果。在日志访问控制场景中,规则通常描述“谁在什么条件下可以访问哪些日志”。
关键概念: Rego 中没有“if-else”控制流,而是使用“规则体”(Rule Body)中的表达式串联(AND)与并联(OR)来实现逻辑判断。所有规则默认都是“真”的,只有当规则体中的条件全部满足时,规则才被激活。
一个最简单的 Rego 规则结构如下:
# 规则定义
allow = true { # 规则头:规则名 = 值
input.user == "admin" # 规则体:条件表达式
input.action == "read" # 条件之间是 AND 关系
}
# 查询
result = allow with input as {"user": "admin", "action": "read"}
# 输出: result = true
3.2 变量(Variables)
Rego 中的变量以大写字母或下划线开头。变量用于绑定数据,并在规则体内传递值。变量一旦绑定,在同一个规则体内不可重新赋值(不可变)。
| 特性 | 说明 | 示例 |
|---|---|---|
| 命名规则 | 必须以大写字母或下划线开头 | User, _role, X |
| 不可变性 | 变量一旦赋值,在规则体内不能修改 | X := 1 后不能再 X := 2 |
| 匿名变量 | 使用 _ 忽略不需要的值 |
data.users[_].name |
| 作用域 | 变量在定义它的规则体内可见 | 不同规则间的变量不共享 |
避坑指南: 初学者常犯的错误是使用小写字母开头定义变量(如
user)。在 Rego 中,小写开头的标识符会被解析为引用(Reference),而非变量,这会导致意外的查询结果。
3.3 标量(Scalars)与复合值(Composite Values)
Rego 支持所有常见的 JSON 数据类型,并在此基础上扩展了集合(Set)。
3.3.1 标量类型
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | "admin", "log-reader" |
双引号包裹,支持转义 |
| 数字 | 42, 3.14 |
整数和浮点数 |
| 布尔值 | true, false |
逻辑真/假 |
| 空值 | null |
表示无值 |
3.3.2 复合值类型
| 类型 | 语法 | 示例 | 说明 |
|---|---|---|---|
| 对象(Object) | {} |
{"name": "alice", "role": "admin"} |
键值对,键必须是字符串 |
| 数组(Array) | [] |
["read", "write"] |
有序列表,索引从0开始 |
| 集合(Set) | {} 且元素唯一 |
{"admin", "editor"} |
无序、无重复元素的集合 |
注意: 集合(Set)与对象(Object)都使用花括号
{}。区别在于:集合的元素是单一值(如字符串、数字),而对象的元素是键值对。Rego 引擎会根据上下文自动区分。
3.4 引用(References)
引用用于访问嵌套在对象或数组中的数据。引用使用点号(.)或方括号([])进行导航。
- 点号访问:
input.user.name等价于input["user"]["name"] - 方括号访问: 支持变量作为键,如
input[Key] - 数组索引:
data.logs[0]访问第一个日志条目 - 迭代访问: 使用变量遍历数组或集合,如
data.logs[i]
# 示例数据
input := {
"user": "bob",
"action": "delete",
"target": {
"namespace": "production",
"resource": "pod"
}
}
# 引用示例
user_name := input.user # "bob"
namespace := input.target.namespace # "production"
action := input["action"] # "delete"
# 使用变量作为键
Key := "target"
target_obj := input[Key] # {"namespace": "production", "resource": "pod"}
3.5 表达式(Expressions)
表达式是规则体的基本组成单元。每个表达式必须求值为 true,规则体才成立。常见的表达式类型包括:
| 表达式类型 | 运算符/语法 | 示例 |
|---|---|---|
| 比较 | ==, !=, <, >, <=, >= |
input.role == "admin" |
| 成员关系 | in, 数组/集合成员检查 |
"read" in input.actions |
| 赋值 | := (局部变量), = (统一) |
X := 10, X = 10 |
| 否定 | not |
not input.user == "guest" |
| 逻辑与 | 同一规则体中的多个表达式(隐式 AND) | a > 1; b < 5 |
| 逻辑或 | 多个同名规则体(隐式 OR) | allow { ... } 和 allow { ... } |
重要区别:
:= 用于赋值,将右侧值绑定到左侧变量;= 用于统一,它既可以是赋值,也可以是等式检查,具体取决于变量是否已绑定。在策略编写中,推荐优先使用 := 以避免歧义。
3.6 知识结构图:Rego 基础语法全景
下图展示了 Rego 基础语法中各元素之间的关系,以及它们在日志访问控制策略中的典型应用位置。
3.7 实战示例:日志访问控制中的基础语法
将上述语法应用于一个简单的日志访问控制场景:只允许 admin 角色的用户读取 production 命名空间的日志。
# 输入数据
input := {
"user": "alice",
"role": "admin",
"action": "read",
"log": {
"namespace": "production",
"level": "error",
"message": "Out of memory"
}
}
# 策略规则
default allow = false
allow = true {
input.role == "admin" # 比较表达式
input.action == "read" # 比较表达式
input.log.namespace == "production" # 引用 + 比较
}
# 更灵活的版本:使用变量和成员关系
allowed_roles := {"admin", "supervisor"} # 集合
allowed_namespaces := ["production", "staging"] # 数组
allow = true {
Role := input.role # 变量赋值
Role in allowed_roles # 成员关系表达式
input.action == "read"
input.log.namespace in allowed_namespaces # 成员关系表达式
}
实践要点: 在编写日志访问控制策略时,建议将角色、命名空间等条件抽象为变量或集合,便于维护和复用。使用
in 表达式可以显著简化多值匹配逻辑。