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