4. OPA 策略语言 Rego 入门:Rego 基本语法、规则定义、数据查询、内置函数
Rego 是 OPA 的声明式策略语言,专门用于对结构化数据进行查询和策略评估。理解 Rego 是编写 Gatekeeper 约束模板的基础。本章将系统介绍 Rego 的核心概念,包括基本语法、规则定义、数据查询方法以及常用内置函数。
4.1 Rego 基本语法
Rego 的语法受 Datalog 启发,但针对 JSON/YAML 等现代数据格式进行了扩展。其核心思想是:策略即查询——你描述“允许什么”,而不是“拒绝什么”。
4.1.1 字面量与注释
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | "hello" |
双引号包裹 |
| 数字 | 42, 3.14 |
整数和浮点数 |
| 布尔值 | true, false |
小写 |
| 数组 | [1, 2, 3] |
方括号 |
| 对象 | {"key": "value"} |
花括号,键为字符串 |
| 集合 | {"a", "b"} |
花括号,无键值对 |
| 注释 | # 这是一行注释 |
井号开头 |
4.1.2 变量与赋值
Rego 中的变量以 _ 或小写字母开头。变量一旦绑定,在同一规则内不可重新赋值(不可变)。
# 变量绑定
name := "nginx"
port := 80
# 匿名变量(忽略值)
_ := input.replicas
关键区别:Rego 使用
:= 进行变量赋值和声明,使用 == 进行相等性比较,使用 = 进行统一(unification,同时完成赋值和比较)。
4.1.3 引用与点操作符
使用点操作符 . 访问嵌套数据,使用方括号 [] 访问数组元素或动态键。
# 访问对象字段
input.spec.containers[0].image
# 动态键访问
key := "name"
input.metadata[key]
4.2 规则定义
规则是 Rego 策略的基本单元。规则可以返回布尔值、集合、对象或字符串。规则分为完整规则和部分规则。
4.2.1 完整规则(Complete Rules)
完整规则返回单个值,通常用于布尔判断。
# 完整规则:判断镜像是否来自允许的仓库
allowed_registry := true {
startswith(input.spec.containers[_].image, "registry.example.com/")
}
# 等价写法(多条件与)
allowed_registry := true {
images := [img | img = input.spec.containers[_].image]
all(images, [img | startswith(img, "registry.example.com/")])
}
4.2.2 部分规则(Partial Rules)
部分规则返回一个集合或对象,常用于生成数据列表。
# 部分规则:收集所有非合规镜像
violation_images[image] {
image := input.spec.containers[_].image
not startswith(image, "registry.example.com/")
}
# 查询结果:violation_images 包含所有违规镜像名
4.2.3 默认规则与 else
使用 default 关键字为规则提供默认值,避免未定义错误。
default allowed := false
allowed := true {
input.role == "admin"
}
# 使用 else 分支
response := "deny" {
not allowed
} else := "allow" {
allowed
}
规则求值特点:Rego 规则体中的多个表达式是逻辑与(AND)关系;多个同名规则体之间是逻辑或(OR)关系。如果任一规则体求值为真,则规则整体为真。
4.3 数据查询
Rego 通过 input 和 data 两个全局变量访问外部数据。
4.3.1 input 与 data
| 变量 | 来源 | 典型用途 |
|---|---|---|
input |
每次查询传入的请求数据 | Kuberentes 准入审查的 AdmissionReview 对象 |
data |
OPA 内存中的全局数据缓存 | 约束模板参数、外部配置、集群资源清单 |
# 查询 input 中的镜像
image := input.spec.containers[0].image
# 查询 data 中的白名单
whitelist := data.whitelist.registries
# 组合查询
allowed := true {
image := input.spec.containers[_].image
registry := split(image, "/")[0]
registry == data.whitelist.registries[_]
}
4.3.2 迭代与推导
Rego 使用推导式(comprehension)进行集合和数组的迭代查询。
# 数组推导:提取所有镜像名
all_images := [img | img = input.spec.containers[_].image]
# 集合推导:去重镜像仓库
registries := {registry |
img := input.spec.containers[_].image
registry := split(img, "/")[0]
}
# 对象推导:构建镜像名到仓库的映射
image_registry := {img: registry |
img := input.spec.containers[_].image
registry := split(img, "/")[0]
}
4.3.3 存在性检查与 all/some
# 检查是否存在至少一个容器使用外部仓库
some_container_external := true {
some i
img := input.spec.containers[i].image
not startswith(img, "registry.example.com/")
}
# 使用 all 检查所有容器
all_internal := all(input.spec.containers, [c |
startswith(c.image, "registry.example.com/")
])
4.4 内置函数
Rego 提供了丰富的内置函数,用于字符串处理、集合操作、正则匹配、时间比较等。以下是 Gatekeeper 策略中最常用的内置函数分类。
4.4.1 字符串函数
| 函数 | 签名 | 说明 | 示例 |
|---|---|---|---|
startswith |
startswith(str, prefix) |
检查字符串是否以指定前缀开头 | startswith("nginx:1.21", "nginx") → true |
endswith |
endswith(str, suffix) |
检查字符串是否以指定后缀结尾 | endswith("image:v1.0", ":v1.0") → true |
contains |
contains(str, substr) |
检查字符串是否包含子串 | contains("docker.io/nginx", "docker") → true |
split |
split(str, delimiter) |
按分隔符分割字符串为数组 | split("a/b/c", "/") → ["a","b","c"] |
concat |
concat(delimiter, array) |
用分隔符连接数组为字符串 | concat(",", ["a","b"]) → "a,b" |
lower/upper |
lower(str) |
大小写转换 | lower("NGINX") → "nginx" |
trim |
trim(str, cutset) |
去除字符串两端指定字符 | trim("/nginx/", "/") → "nginx" |
4.4.2 正则表达式
# 使用 regex.match 进行模式匹配
import future.keywords.in
allowed := true {
img := input.spec.containers[_].image
regex.match(`^registry\.example\.com/[a-z0-9]+(:[a-zA-Z0-9._-]+)?$`, img)
}
# 使用 regex.find_n 提取匹配内容
matches := regex.find_n(`v[0-9]+\.[0-9]+`, input.spec.containers[_].image, -1)
4.4.3 集合与数组函数
| 函数 | 说明 | 示例 |
|---|---|---|
all(array, condition) |
检查数组中所有元素是否满足条件 | all(images, [i | startswith(i, "reg")]) |
any(array, condition) |
检查数组中是否存在至少一个元素满足条件 | any(images, [i | contains(i, "external")]) |
count(collection) |
返回集合/数组/字符串的元素个数 | count(input.spec.containers) > 0 |
union(set1, set2) |
返回两个集合的并集 | union({"a","b"}, {"b","c"}) → {"a","b","c"} |
intersection(set1, set2) |
返回两个集合的交集 | intersection({"a","b"}, {"b","c"}) → {"b"} |
4.4.4 类型检查与转换
# 类型检查
is_string(input.spec.containers[0].image) # 返回 true/false
is_array(input.spec.containers)
is_object(input.metadata)
# 类型转换
to_number("42") # 返回 42
to_string(42) # 返回 "42"
object.get(obj, key, default) # 安全访问对象字段,不存在时返回默认值
4.4.5 时间与编码函数
# 时间比较(用于 TTL 或过期策略)
time.now_ns() # 返回当前时间的纳秒数
time.parse_rfc3339_ns("2025-01-01T00:00:00Z") # 解析 RFC3339 时间
# Base64 编码/解码(用于处理 Secret 数据)
base64.decode("bmdpbng=") # 返回 "nginx"
base64.encode("nginx") # 返回 "bmdpbng="
# JSON 操作
json.marshal(input) # 将对象序列化为 JSON 字符串
json.unmarshal(str) # 将 JSON 字符串解析为对象
性能注意事项:在 Gatekeeper 约束模板中,避免在循环体内使用昂贵的正则表达式或大量
json.unmarshal 操作。建议将预计算逻辑放在 data 缓存中,减少每次准入审查的计算开销。
4.5 综合示例:镜像拉取策略
以下是一个完整的 Rego 策略示例,结合了规则定义、数据查询和内置函数,用于控制镜像只能从内部仓库拉取。
package image_pull_policy
# 从 data 中读取允许的仓库列表
allowed_registries := data.allowed_registries
# 默认拒绝
default allow := false
# 允许条件:所有容器镜像都来自允许的仓库
allow := true {
all_containers := input.spec.containers + input.spec.initContainers
all(all_containers, [container |
registry := split(container.image, "/")[0]
registry == allowed_registries[_]
])
}
# 违规详情(用于 Gatekeeper 拒绝消息)
violation[{"msg": msg}] {
not allow
msg := sprintf("镜像 %v 不在允许的仓库列表中", [input.spec.containers[_].image])
}
调试技巧:使用
opa eval 命令结合 --input 和 --data 参数可以快速测试 Rego 策略。例如:opa eval --input pod.json --data policy.rego "data.image_pull_policy"
4.6 本章小结
- 基本语法:Rego 使用声明式查询,变量不可变,通过
:=赋值,==比较,=统一。 - 规则定义:完整规则返回单个值,部分规则返回集合/对象;
default提供默认值,多个规则体之间是逻辑或。 - 数据查询:
input接收请求数据,data访问全局缓存;推导式用于迭代和转换。 - 内置函数:字符串函数、正则、集合操作、类型转换和时间编码函数是编写 Gatekeeper 策略的核心工具。