4. Rego 语言入门(二):标量类型、复合类型与引用

在上一节中,我们搭建了 Rego 的运行环境并编写了第一条策略。本节将深入 Rego 的类型系统,这是编写正确、高效策略的基础。Rego 的类型系统分为标量类型和复合类型,理解它们的特性以及如何通过引用访问数据,是掌握 OPA 策略编写的关键一步。

4.1 标量类型:策略中的基本原子

标量类型是 Rego 中最基础的数据单元,它们不可再分。在策略评估中,标量类型常用于定义常量、阈值或简单的状态值。

类型 描述 示例 典型用途
null 空值,表示不存在或未定义 null 表示缺失的配置项或无效的输入
boolean 布尔值,真或假 true, false 策略决策结果(允许/拒绝)
number 数值,包含整数和浮点数 42, 3.14 端口号、资源配额、时间戳
string 字符串,由双引号包裹 "nginx:latest" 镜像名称、命名空间、标签值
避坑提示: Rego 中字符串必须使用双引号 ",单引号 ' 不被识别为字符串,会导致解析错误。数字类型在比较时遵循常规数学规则,但需注意浮点数精度问题。

4.2 复合类型:构建策略的数据结构

复合类型允许我们将多个标量值或嵌套的复合值组织在一起。Rego 提供了三种核心复合类型:对象(Object)、数组(Array) 和 集合(Set)。

4.2.1 对象(Object):键值对映射

对象类似于其他语言中的字典或哈希表,由键值对组成。键通常是字符串,值可以是任意类型。对象是 Rego 中最常用的数据结构,用于表示 Kubernetes 资源、配置文档等。

# 定义一个表示容器配置的对象
container_config := {
    "image": "nginx:1.21",
    "ports": [80, 443],
    "security_context": {
        "run_as_non_root": true,
        "read_only_root_filesystem": true
    }
}

4.2.2 数组(Array):有序列表

数组是有序的元素序列,索引从 0 开始。数组中的元素类型可以不一致,但在实际策略中通常保持类型一致以便于遍历。

# 定义一个数组,包含允许的镜像列表
allowed_images := ["nginx:1.21", "redis:6.2", "alpine:3.15"]

# 访问数组元素
first_image := allowed_images[0]  # 结果为 "nginx:1.21"

4.2.3 集合(Set):无序唯一元素集

集合是无序的、元素唯一的数据结构。集合中的元素不能重复,且没有索引。集合非常适合用于去重、成员资格检查等场景。

# 定义一个集合,包含禁止使用的端口
blocked_ports := {22, 3389, 3306}

# 检查某个端口是否在集合中
is_blocked := 22 in blocked_ports  # 结果为 true
关键区别: 对象使用 {} 且键值对用冒号分隔;数组使用 [];集合也使用 {} 但元素之间没有冒号。集合元素必须是标量类型或复合类型的引用,不能包含变量。

4.3 引用:访问嵌套数据的路径

引用是 Rego 中访问数据的方式,类似于文件系统中的路径。通过引用,我们可以从复杂的嵌套结构中提取所需的值。引用使用点号 . 或方括号 [] 进行导航。

# 假设输入数据如下
input := {
    "kind": "Pod",
    "metadata": {
        "name": "my-pod",
        "labels": {"app": "frontend", "env": "prod"}
    },
    "spec": {
        "containers": [
            {"name": "web", "image": "nginx:latest"},
            {"name": "sidecar", "image": "fluentd:latest"}
        ]
    }
}

# 引用示例
pod_name := input.metadata.name              # 结果为 "my-pod"
app_label := input.metadata.labels["app"]    # 结果为 "frontend"
first_container_image := input.spec.containers[0].image  # 结果为 "nginx:latest"
常见错误: 当引用的键或索引不存在时,Rego 不会抛出异常,而是返回 undefined。这可能导致策略意外通过或拒绝。建议在引用前使用 in 操作符或默认值进行安全检查。

4.4 类型系统全景图

下面的 SVG 图展示了 Rego 类型系统的完整层次结构,以及标量类型与复合类型之间的关系。

Rego 类型系统层次结构 Rego 数据类型 标量类型 复合类型 null boolean number string 对象 数组 集合 类型使用建议 • 标量类型:用于定义常量、阈值、状态标志 • 复合类型:对象用于结构化数据,数组用于有序列表,集合用于去重和成员检查

4.5 类型操作与模式匹配

Rego 支持通过模式匹配来解构复合类型,这在编写复杂策略时非常有用。以下是一些常见的操作模式:

  • 对象解构: 使用 some key, value 遍历对象键值对
  • 数组迭代: 使用 some i 获取索引,或直接使用 in 检查成员
  • 集合操作: 支持并集 &、交集 |、差集 - 操作
# 对象遍历示例
some image_name, image_config in input.images
# 检查所有容器镜像是否来自可信仓库
all_trusted := all(container in input.spec.containers | startswith(container.image, "registry.example.com/"))

# 集合运算示例
allowed_ports := {80, 443, 8080}
exposed_ports := {80, 22, 443}
violation_ports := exposed_ports - allowed_ports  # 结果为 {22}

4.6 引用与默认值

在实际策略中,输入数据可能不完整。使用 default 关键字可以为引用设置默认值,避免因数据缺失导致策略行为异常。

# 为可能缺失的字段设置默认值
default pod_security_level := "low"

# 如果 input.metadata.annotations["security-level"] 存在,则使用其值
pod_security_level := input.metadata.annotations["security-level"] {
    input.metadata.annotations["security-level"]
}

# 策略中使用默认值
deny[msg] {
    pod_security_level == "low"
    msg := "Pod 安全等级为低,拒绝部署"
}
最佳实践: 在编写生产级策略时,建议为所有可能缺失的引用设置默认值,或者使用 in 操作符先检查键是否存在。这可以避免因输入数据变化导致的策略意外失效。