2. OPA 基础回顾:Rego 语言核心语法、OPA 数据模型、Input 与 Data 文档、内置函数与聚合
在开始多集群策略迁移之前,必须夯实 OPA 策略引擎的核心基础。本章将系统回顾 Rego 语言的语法结构、OPA 的数据模型、Input/Data 文档的交互机制,以及常用的内置函数与聚合操作。这些知识是后续编写和迁移策略的基石。
2.1 Rego 语言核心语法
Rego 是一种为 OPA 量身定制的声明式查询语言,灵感来源于 Datalog。它专注于“策略即代码”,通过规则(Rule)和查询(Query)来表达策略逻辑。
2.1.1 基本结构与规则
Rego 文件由 包(Package) 和 规则(Rule) 组成。规则是 Rego 的核心,它定义了一个布尔表达式或一个值。
package example.allow
# 默认规则:默认拒绝
default allow = false
# 规则:如果用户是 admin,则允许
allow {
input.user.role == "admin"
}
# 规则:如果用户是 owner 且资源属于该用户,则允许
allow {
input.user.role == "owner"
input.resource.owner == input.user.name
}
关键概念: 在 Rego 中,规则名相同但条件不同的规则之间是 OR 关系。只要有一条规则的条件为真,整个规则集的结果就为真。默认规则
default allow = false 确保在没有规则匹配时返回 false。
2.1.2 迭代与推导
Rego 通过 迭代(Iteration) 和 推导(Comprehension) 来处理集合数据。推导类似于其他语言中的列表/集合/对象推导式。
# 列表推导:获取所有非 admin 用户的名称
non_admin_users := [name | name := input.users[_].name; input.users[_].role != "admin"]
# 对象推导:构建一个用户角色映射
user_roles := {name: role | name := input.users[_].name; role := input.users[_].role}
# 集合推导:获取所有唯一的命名空间
unique_namespaces := {ns | ns := input.deployments[_].namespace}
2.1.3 规则类型对比
| 规则类型 | 语法示例 | 返回值 | 典型用途 |
|---|---|---|---|
| 完整规则 | allow { ... } |
布尔值(true/false) | 授权决策、准入控制 |
| 部分规则 | roles[user] := role { ... } |
键值对映射 | 数据转换、角色映射 |
| 函数规则 | greet(name) = msg { ... } |
自定义返回值 | 复用逻辑、字符串处理 |
| 默认规则 | default allow = false |
固定值 | 设置安全默认值 |
2.2 OPA 数据模型:Input 与 Data 文档
OPA 的策略评估依赖于两个核心文档:Input 和 Data。理解它们的区别是编写正确策略的前提。
- Input 文档: 每次策略查询时由外部系统(如 Kubernetes API Server)传入的临时数据。它描述了当前请求的上下文,例如用户信息、操作类型、资源对象等。Input 是动态的、一次性的。
- Data 文档: OPA 内部维护的全局数据缓存。它通常包含集群状态、配置规则、外部数据(如 LDAP 用户组)等。Data 是静态的、持久化的,可以通过 API 或 Bundle 更新。
实战要点: 在 Kubernetes 准入控制场景中,
input 包含 AdmissionReview 请求的完整内容(如 Pod 的 YAML 定义),而 data 可以包含集群级别的白名单、命名空间标签、或从外部同步的合规规则库。
2.2.1 数据模型交互示例
# 检查 input 中的用户是否在 data 中的管理员列表中
allow {
input.user.name == data.admin_users[_]
}
# 检查 input 中的命名空间是否在 data 中的允许列表中
allow {
input.namespace == data.allowed_namespaces[_]
}
# 使用 data 中的规则进行复杂校验
deny[msg] {
input.kind == "Deployment"
not data.allowed_images[input.spec.template.spec.containers[_].image]
msg := sprintf("镜像 '%s' 不在允许列表中", [input.spec.template.spec.containers[_].image])
}
2.3 内置函数与聚合
OPA 提供了丰富的内置函数,用于字符串处理、正则匹配、时间操作、加密计算等。聚合函数则用于对集合数据进行统计和汇总。
2.3.1 常用内置函数分类
| 类别 | 函数示例 | 说明 |
|---|---|---|
| 字符串 | contains(str, substr)startswith(str, prefix)regex.match(pattern, str) |
字符串包含、前缀匹配、正则匹配 |
| 集合/数组 | count(collection)union(s1, s2)intersection(s1, s2) |
计数、并集、交集 |
| 对象 | object.get(obj, key, default)object.keys(obj) |
安全取值、获取键列表 |
| 编码 | base64.decode(str)json.marshal(value) |
Base64 解码、JSON 序列化 |
| 时间 | time.now_ns()time.parse_rfc3339(str) |
获取当前时间、解析时间字符串 |
2.3.2 聚合操作实战
聚合函数在策略中常用于资源配额校验、权限汇总等场景。
# 统计 input 中所有容器的资源请求总和
total_cpu_requests := sum([c.resources.requests.cpu | c := input.spec.template.spec.containers[_]])
# 检查是否所有容器都设置了资源限制
all_containers_have_limits := all([c.resources.limits | c := input.spec.template.spec.containers[_]])
# 检查是否存在任何特权容器
any_privileged := any([c.securityContext.privileged | c := input.spec.template.spec.containers[_]])
# 聚合规则:如果总 CPU 请求超过 4 核,则拒绝
deny[msg] {
total_cpu_requests > 4
msg := sprintf("CPU 请求总量 %.2f 超过限制 4 核", [total_cpu_requests])
}
注意: 聚合函数
sum、all、any 等要求输入必须是集合或数组。如果字段可能缺失,建议使用 object.get 或默认值来避免运行时错误。
2.4 OPA 策略评估流程(SVG 图)
下图展示了 OPA 在一次策略查询中的完整数据流,从外部请求到最终决策的生成过程。