3. OPA 与 Rego 语言入门

OPA(Open Policy Agent)是一个开源的、通用的策略引擎,它将策略从软件中解耦出来。Rego 是 OPA 的原生查询语言,专门用于定义和查询策略。理解 OPA 的架构和 Rego 的语法是构建基线核验策略的基础。

3.1 OPA 架构概述

OPA 采用客户端-服务器(C/S)架构,核心组件包括策略引擎、数据文档和查询接口。它不直接管理资源,而是通过“决策请求”来评估策略。

OPA 核心架构组件与数据流 外部系统 (K8s API Server / 应用) 查询请求 OPA 策略引擎 决策评估 (Rego 解释器) 决策响应 加载 策略包 (Policies) Rego 规则 / 模块 加载 数据文档 (JSON / YAML) 集群状态 / 配置 决策输出 allow / deny / warn 说明 OPA 不存储数据, 数据由外部系统推送或拉取。
架构要点:OPA 将策略决策与策略执行分离。外部系统(如 Kubernetes API Server)发送查询请求,OPA 引擎结合 Rego 规则和数据文档做出决策,返回 allow/deny 结果。

3.2 Rego 语言基本语法

Rego 是一种声明式查询语言,灵感来源于 Datalog。它专注于描述“策略是什么”,而不是“如何执行策略”。

3.2.1 基础规则与赋值

package example.auth

# 常量赋值
default allow = false

# 规则定义:如果用户是 admin,则允许访问
allow {
    input.user == "admin"
}

# 使用变量
has_permission {
    user := input.user
    user != ""
}

3.2.2 迭代与集合

package example.roles

# 定义用户角色数据
user_roles := {
    "alice": ["admin", "dev"],
    "bob": ["dev"],
    "charlie": ["viewer"]
}

# 规则:检查用户是否拥有特定角色
is_admin {
    roles := user_roles[input.user]
    "admin" == roles[_]
}

# 使用 some 关键字迭代
has_role(role) {
    some r
    r := user_roles[input.user][_]
    r == role
}

3.2.3 规则与查询

在 Rego 中,规则定义策略逻辑,查询用于评估规则并获取结果。

概念 描述 示例
完整规则 返回单个值(布尔、字符串、数字) allow := true { ... }
部分规则 返回一组值(集合、对象) violations[msg] { ... }
默认规则 为规则提供默认值,避免未定义 default allow = false
查询 在 OPA 中执行,返回规则结果 data.example.auth.allow
查询示例:使用 opa eval 命令或 REST API 查询规则结果。
# 命令行查询
opa eval --data policy.rego --input input.json "data.example.auth.allow"

# REST API 查询
curl -X POST http://localhost:8181/v1/data/example/auth/allow \
  -H "Content-Type: application/json" \
  -d '{"input": {"user": "admin"}}'

3.3 数据与文档模型

OPA 将外部数据视为文档,这些文档被加载到 OPA 的内存中,形成分层的数据树。Rego 规则通过 data 和 input 关键字访问这些文档。

3.3.1 数据文档模型

数据文档是静态的、全局的,通常由外部系统通过 API 推送或拉取。它们被组织成层次结构,类似于 JSON 对象。

# 数据文档示例 (data.cluster)
{
    "cluster": {
        "name": "production",
        "namespaces": ["default", "kube-system", "monitoring"],
        "nodes": [
            {"name": "node-1", "labels": {"env": "prod"}},
            {"name": "node-2", "labels": {"env": "prod"}}
        ]
    }
}

# Rego 规则访问数据
allowed_namespace {
    data.cluster.namespaces[_] == input.namespace
}

3.3.2 输入文档模型

输入文档是临时的、请求级别的,由外部系统在每次查询时提供。它描述了需要被评估的当前请求上下文。

# 输入文档示例 (input)
{
    "user": "alice",
    "action": "create",
    "resource": {
        "kind": "Pod",
        "name": "nginx-pod",
        "namespace": "default"
    }
}

# Rego 规则检查输入
deny[msg] {
    input.action == "delete"
    input.resource.kind == "Namespace"
    msg := "不允许删除命名空间"
}

3.3.3 文档模型对比

特性 数据文档 (data) 输入文档 (input)
生命周期 持久化,由外部系统管理 临时,每次查询时创建
作用域 全局,所有规则可见 请求级别,仅当前查询可见
更新方式 通过 OPA API 推送或拉取 由查询请求的 body 提供
典型用途 集群状态、角色定义、白名单 用户身份、操作类型、资源详情
注意:在 Gatekeeper 中,input 文档由准入 Webhook 请求自动生成,包含 review、parameters 等字段。不要手动构造 input,而是通过约束模板中的 violation 规则来访问。

3.4 综合示例:基线核验策略

以下是一个简单的 Rego 规则,用于核验 Pod 是否设置了资源限制,这是基线安全策略的一部分。

package k8sbaseline

# 默认允许
default deny = false

# 规则:如果 Pod 没有设置 resources.limits,则拒绝
deny[msg] {
    input.review.kind.kind == "Pod"
    container := input.review.object.spec.containers[_]
    not container.resources.limits
    msg := sprintf("容器 %v 必须设置资源限制", [container.name])
}

# 规则:如果 Pod 设置了 privileged 安全上下文,则拒绝
deny[msg] {
    input.review.kind.kind == "Pod"
    container := input.review.object.spec.containers[_]
    container.securityContext.privileged == true
    msg := sprintf("容器 %v 不允许使用特权模式", [container.name])
}
实践建议:在编写 Rego 规则时,始终从 input 文档入手,使用 opa fmt 格式化代码,并通过 opa test 编写单元测试来验证规则的正确性。