4. Rego策略语言入门:语法基础、规则定义、匹配与迭代、内置函数、测试策略
Rego 是 OPA 的原生策略语言,专为在复杂 JSON/YAML 数据上表达声明式策略而设计。理解 Rego 的核心概念是编写合规校验规则的前提。本节将系统讲解 Rego 的语法基础、规则定义方式、数据匹配与迭代机制、常用内置函数以及策略测试方法。
4.1 Rego 语法基础
Rego 是一种声明式查询语言,其核心思想是:策略即代码。Rego 文件通常以 .rego 为后缀,每个文件属于一个 包(package)。
| 概念 | 说明 | 示例 |
|---|---|---|
| 包声明 | 每个 Rego 文件第一行定义所属包,用于组织策略和避免命名冲突 | package kubernetes.admission |
| 标量值 | 支持字符串、数字、布尔值、null | "prod", 3, true, null |
| 复合值 | 对象(object)和数组(array),对应 JSON 的键值对和列表 | {"name": "nginx"}, [1,2,3] |
| 变量 | 以 _ 或字母开头,用于绑定数据或中间结果 |
input, namespace, _ |
| 引用 | 通过点号或方括号访问嵌套数据 | input.request.object.metadata.name |
| 注释 | 使用 # 进行单行注释 |
# 这是一条注释 |
关键理解:Rego 中的变量一旦被赋值,在同一规则内不可重新赋值。这种不可变性保证了策略的确定性,避免了副作用。
4.2 规则定义
规则是 Rego 策略的基本单元。规则分为 完整规则 和 部分规则,以及 默认规则。
4.2.1 完整规则
完整规则返回一个单一的布尔值或标量。当规则体中的所有表达式都为真时,规则为真。
package example
# 完整规则:判断环境是否为生产环境
is_production {
input.environment == "prod"
}
# 完整规则:判断镜像标签是否包含版本号
has_version_tag {
contains(input.image, ":")
}
4.2.2 部分规则
部分规则返回一个集合或对象,可以带参数。常用于生成可迭代的数据结构。
package example
# 部分规则:返回所有允许的命名空间列表
allowed_namespaces = ["default", "kube-system", "production"]
# 部分规则:返回禁止的容器镜像前缀
blocked_image_prefixes = {"docker.io/private/", "gcr.io/internal/"}
# 带参数的部分规则:判断某个命名空间是否在允许列表中
namespace_allowed(ns) {
ns == allowed_namespaces[_]
}
4.2.3 默认规则
使用 default 关键字为规则设置默认值,避免规则未定义时产生未定义结果。
package example
# 默认规则:默认允许通过
default allow = true
# 实际拒绝规则:当违反条件时覆盖默认值
allow = false {
input.request.operation == "DELETE"
input.request.object.metadata.namespace == "kube-system"
}
最佳实践:在 Operator 发布合规校验中,通常使用
default allow = true 作为白名单策略的起点,然后通过拒绝规则(allow = false { ... })来拦截违规操作。
4.3 匹配与迭代
Rego 通过 表达式匹配 和 迭代 机制处理集合数据。这是 Rego 最强大的特性之一。
4.3.1 数组/集合迭代
使用 some 关键字或匿名变量 _ 遍历数组或集合。
package example
# 使用 some 迭代数组
has_privileged_container {
some i
input.containers[i].securityContext.privileged == true
}
# 使用匿名变量 _ 迭代集合
uses_blocked_registry {
input.image = blocked_image_prefixes[_]
}
4.3.2 对象迭代
遍历对象的键值对,使用 some k, v 语法。
package example
# 检查所有标签是否包含 required 前缀
all_labels_have_prefix {
some key, value
input.metadata.labels[key] = value
startswith(key, "app.kubernetes.io/")
}
4.3.3 复合条件匹配
Rego 允许在一条规则中组合多个条件,所有条件必须同时满足。
package example
# 拒绝:在非生产命名空间中创建特权容器
deny[msg] {
input.request.operation == "CREATE"
input.request.object.metadata.namespace != "production"
some i
input.request.object.spec.containers[i].securityContext.privileged == true
msg = sprintf("命名空间 %v 不允许创建特权容器", [input.request.object.metadata.namespace])
}
4.4 内置函数
Rego 提供了丰富的内置函数,用于字符串处理、正则匹配、集合操作、时间比较等。以下是 Operator 发布合规校验中最常用的内置函数。
| 类别 | 函数 | 说明 | 示例 |
|---|---|---|---|
| 字符串 | contains(s, substr) |
判断字符串是否包含子串 | contains(input.image, ":latest") |
startswith(s, prefix) |
判断字符串是否以指定前缀开头 | startswith(input.namespace, "prod-") |
|
sprintf(format, values) |
格式化字符串,用于生成错误消息 | sprintf("镜像 %v 不允许", [input.image]) |
|
| 正则 | re_match(pattern, value) |
正则匹配,返回布尔值 | re_match("^v[0-9]+\\.[0-9]+$", input.version) |
regex.split(pattern, s) |
按正则分割字符串 | regex.split("/", input.image) |
|
| 集合 | count(collection) |
返回集合元素数量 | count(input.containers) > 1 |
union(sets) |
合并多个集合 | union({allowed_ports, extra_ports}) |
|
| 时间 | time.now_ns() |
返回当前时间的纳秒时间戳 | time.now_ns() |
time.parse_rfc3339_ns(s) |
解析 RFC3339 时间字符串为纳秒 | time.parse_rfc3339_ns(input.creationTimestamp) |
|
| 对象 | object.get(obj, key, default) |
安全获取对象属性,避免未定义错误 | object.get(input, ["metadata", "labels"], {}) |
注意:在 Rego 中,访问不存在的对象属性会导致规则未定义(undefined),而不是返回 null。使用
object.get() 或 _ 匿名变量可以安全处理缺失字段。
4.5 测试策略
OPA 提供了 opa test 命令,支持对 Rego 策略进行单元测试和集成测试。测试文件通常以 _test.rego 结尾。
4.5.1 测试规则结构
测试规则以 test_ 开头,使用 input 模拟请求数据,通过断言验证策略输出。
package example_test
import data.example
# 测试:生产环境允许创建 Deployment
test_prod_deployment_allowed {
input := {
"request": {
"operation": "CREATE",
"object": {
"metadata": {
"namespace": "production",
"name": "web-app"
},
"spec": {
"containers": [{"image": "nginx:1.21"}]
}
}
}
}
example.allow == true with input as input
}
# 测试:非生产环境拒绝特权容器
test_nonprod_privileged_denied {
input := {
"request": {
"operation": "CREATE",
"object": {
"metadata": {"namespace": "staging"},
"spec": {
"containers": [{
"image": "nginx:1.21",
"securityContext": {"privileged": true}
}]
}
}
}
}
count(example.deny) > 0 with input as input
}
4.5.2 测试执行与覆盖率
使用 opa test 命令运行测试,支持覆盖率报告。
# 运行所有测试
opa test ./policies/ -v
# 运行测试并生成覆盖率报告
opa test ./policies/ --coverage --format=json
# 输出示例
# PASS: 3/3
# coverage: 85.7% of statements
4.5.3 测试最佳实践
- 边界覆盖:为每个规则编写至少一个通过用例和一个拒绝用例。
- 数据模拟:使用完整的
input结构,模拟真实的 AdmissionReview 请求。 - 负面测试:测试缺失字段、空数组、null 值等异常输入。
- 集成测试:结合
data.mock模拟外部数据源(如 OPA Bundle 中的参考数据)。 - 持续集成:将
opa test集成到 CI 流水线,确保策略变更不破坏现有规则。
实战建议:在 Operator 发布合规项目中,建议为每个
.rego 文件创建对应的 _test.rego 文件。测试覆盖率应保持在 80% 以上,特别是针对 deny 和 allow 核心规则的测试必须完整。