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])
}
Rego 规则匹配与迭代流程 输入数据 (input) Rego 规则引擎 策略决策 (allow/deny) some i / 匿名变量 _ 对象迭代 (some k, v) 复合条件匹配 匹配与迭代关键点: • some i:显式声明循环变量,适用于需要索引的场景 • 匿名变量 _:隐式迭代,只关心是否存在匹配项 • 复合条件:多个表达式在同一规则体中,逻辑与关系 • 集合推导:deny[msg] 形式可生成多个拒绝消息

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 核心规则的测试必须完整。

4.6 本章小结