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/")
])
Rego 数据查询流程 input AdmissionReview 请求 data 全局缓存 / 约束参数 Rego 规则 查询处理步骤 1. 变量绑定 → 2. 表达式求值 → 3. 规则体逻辑与/或组合 → 4. 结果聚合 迭代推导、存在性检查、默认值处理 策略决策输出 true / false / 违规集合 / 消息

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 缓存中,减少每次准入审查的计算开销。
Rego 内置函数分类架构 Rego 内置函数 字符串处理 正则匹配 集合/数组 类型检查/转换 startswith endswith contains split / concat lower / upper trim regex.match regex.find_n regex.split regex.replace all / any count union intersection sort is_string / is_array to_number / to_string object.get time.now_ns base64.decode json.marshal

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 策略的核心工具。