4. Rego 语言基础(二):规则定义

在 OPA 策略引擎中,规则(Rule)是 Rego 语言的核心构造单元。规则定义了策略的评估逻辑,决定了输入数据如何被转换为决策结果。本章将深入讲解完整规则与部分规则的区别、默认规则的使用场景,以及否定与迭代的高级模式。

4.1 完整规则(Complete Rules)

完整规则将某个名称绑定到一个单一值上。当规则被评估时,它要么返回一个确定的值,要么不产生任何结果(未定义)。完整规则通常用于定义常量、配置项或布尔决策。

语法特征:规则头不包含索引变量,赋值运算符使用 := 或 =。
# 完整规则:返回一个字符串
allowed_log_level := "INFO"

# 完整规则:返回一个布尔值
allow_access := true

# 完整规则:基于条件返回布尔值
is_admin_access := input.user.role == "admin"

# 完整规则:返回一个对象
default_config := {
  "max_retries": 3,
  "timeout_ms": 5000,
  "audit_enabled": true
}

完整规则在策略中常用于定义全局策略开关或静态配置。在日志访问控制场景中,可以用完整规则定义“是否启用审计日志”或“默认拒绝策略”。

4.2 部分规则(Partial Rules)

部分规则将名称绑定到一个集合(Set)或对象(Object)上,通过索引变量生成多个条目。部分规则是 Rego 实现数据驱动策略的关键机制。

语法特征:规则头包含索引变量(方括号内),每个匹配条件生成一个集合元素或对象键值对。
# 部分规则:生成一个集合(Set)
allowed_log_sources[source] {
  source := input.log_sources[_]
  source.level in ["INFO", "WARN", "ERROR"]
}

# 部分规则:生成一个对象(Object)
log_access_permissions[user][action] {
  user := input.users[_]
  action := input.actions[_]
  user.role == "auditor"
  action in ["read", "export"]
}

# 部分规则:带条件的集合生成
high_risk_logs[log_id] {
  log := input.logs[_]
  log.severity == "CRITICAL"
  log.source_type == "security"
  log_id := log.id
}
特性 完整规则 部分规则
返回值类型 单一值(标量、布尔、对象、数组) 集合或对象(多值)
索引变量 无 有(方括号内)
评估结果 确定值或未定义 所有满足条件的条目
典型用途 策略开关、常量、布尔决策 白名单、权限矩阵、动态过滤
性能特征 O(1) 直接返回 O(n) 遍历数据源
实践建议:在日志访问控制策略中,使用完整规则定义“是否启用访问控制”(enforce_access_control := true),使用部分规则定义“允许访问的用户列表”(allowed_users[user] { ... })。这种组合既保证了性能,又提供了灵活性。

4.3 默认规则(Default Rules)

默认规则为可能未定义的规则提供安全回退值。当规则体中的所有条件都不满足时,默认规则确保策略不会返回未定义,而是返回一个预设的默认值。

# 默认规则:为布尔决策提供安全默认值
default allow := false

# 完整规则:只有在特定条件下才允许
allow {
  input.user.role == "admin"
  input.action == "read"
}

# 默认规则:为集合提供空集默认值
default allowed_log_types := set()

# 部分规则:动态生成允许的日志类型
allowed_log_types[log_type] {
  log_type := input.available_types[_]
  log_type in ["application", "security", "audit"]
}

# 默认规则:为对象提供空对象默认值
default user_permissions := {}

# 部分规则:生成用户权限映射
user_permissions[user][perm] {
  user := input.users[_]
  perm := user.permissions[_]
}
关键注意:默认规则只能为完整规则或部分规则提供默认值,不能为规则体中的局部变量设置默认值。默认值的数据类型必须与规则返回类型一致。

在日志访问控制中,默认规则是实施最小权限原则和默认拒绝策略的基础设施。没有显式允许的访问请求,都会被默认规则拒绝。

4.4 否定(Negation)

Rego 使用 not 关键字实现逻辑否定。否定在策略中用于表达排除条件或禁止规则。理解否定的语义对于编写正确的策略至关重要。

# 否定示例:禁止非管理员用户访问敏感日志
allow_access {
  input.user.role == "viewer"
  not input.log.severity == "CRITICAL"
}

# 否定与集合结合:排除特定来源的日志
allowed_logs[log] {
  log := input.logs[_]
  not log.source in ["internal_health", "metrics_pipeline"]
}

# 否定与部分规则结合:禁止特定用户组
denied_users[user] {
  user := input.users[_]
  user.department == "contractor"
  not user.clearance_level >= 3
}

# 否定与迭代结合:检查是否所有条件都满足
all_conditions_met {
  count(input.conditions) > 0
  not any_failed_condition
}

any_failed_condition {
  condition := input.conditions[_]
  condition.status == "failed"
}
否定语义:Rego 中的 not 遵循“封闭世界假设”——只有当被否定的表达式确定不成立时,否定才为真。如果被否定的表达式未定义(无法确定真假),否定也会失败。

否定在日志访问控制中的典型应用包括:排除内部监控流量、禁止外部 IP 访问审计日志、拒绝过期证书的请求等。

4.5 迭代(Iteration)

Rego 使用隐式迭代机制遍历集合和对象。迭代通常通过通配符 _ 或命名变量在数组/集合索引位置实现。理解迭代是编写高效策略的基础。

# 基本迭代:遍历日志数组
high_severity_logs[log] {
  log := input.logs[_]  # 隐式迭代所有日志
  log.severity in ["HIGH", "CRITICAL"]
}

# 带索引的迭代:同时获取索引和值
indexed_logs[log_id] {
  log_id := input.logs[i].id  # i 是索引变量
  input.logs[i].timestamp >= "2024-01-01"
}

# 嵌套迭代:遍历用户和日志的组合
user_log_access[user][log_id] {
  user := input.users[_]
  log_id := input.logs[_].id
  user.role == "auditor"
}

# 迭代与聚合:统计满足条件的日志数量
critical_log_count := count([log | log := input.logs[_]; log.severity == "CRITICAL"])

# 迭代与条件过滤
filtered_logs[log] {
  log := input.logs[_]
  log.level != "DEBUG"
  log.source != "health_check"
}
迭代性能优化:在大型日志数据集上,优先使用 in 关键字和集合操作代替嵌套迭代。例如,input.logs[_].source in allowed_sources 比嵌套迭代更高效。

4.6 否定与迭代的组合模式

在实际策略中,否定和迭代经常组合使用,形成强大的数据过滤和决策模式。以下是日志访问控制中的典型组合模式:

# 模式1:排除迭代中的特定元素
allowed_log_entries[entry] {
  entry := input.logs[_]
  not entry.source in blocked_sources
  not entry.user in suspended_users
}

# 模式2:检查迭代中是否不存在特定条件
no_critical_errors {
  not any_critical_error
}

any_critical_error {
  log := input.logs[_]
  log.severity == "CRITICAL"
  log.status == "unresolved"
}

# 模式3:否定与部分规则结合实现黑名单
blocked_actions[action] {
  action := input.actions[_]
  not action in ["read", "list", "search"]
}

# 模式4:双重否定实现“所有条件都满足”
all_logs_processed {
  not any_unprocessed_log
}

any_unprocessed_log {
  log := input.logs[_]
  log.processed != true
}
常见陷阱:在否定中使用未定义的变量会导致策略评估失败。例如,not input.logs[_].source == "internal" 如果 input.logs 为空数组,否定会成功(因为没有日志不满足条件),这可能导致意外的策略放行。始终考虑空集合场景。
Rego 规则类型与评估流程 输入数据 (input) Rego 规则引擎 完整规则 单一值 / 布尔决策 部分规则 集合 / 对象(多值) 默认规则(安全回退) 未定义时提供默认值 策略决策结果 完整规则和部分规则均可配置默认规则,确保策略评估不会返回未定义

4.7 规则定义最佳实践

在云原生日志访问控制场景中,遵循以下最佳实践可以提升策略的可维护性和性能:

实践 说明 示例
默认拒绝 始终为布尔决策规则设置默认值 false default allow := false
显式否定 使用 not 明确表达排除条件,避免隐式否定 not input.user in blocked_users
迭代边界 在迭代前检查集合非空,避免意外结果 count(input.logs) > 0
规则粒度 将复杂策略拆分为多个小规则,提高可读性 is_admin + is_allowed_action
默认值类型 默认值的数据类型必须与规则返回类型严格一致 集合规则默认 set(),对象规则默认 {}
否定范围 否定应作用于最小表达式,避免过度否定 not (a and b) 不如 not a or not b
日志访问控制实战案例:以下策略组合展示了完整规则、部分规则、默认规则、否定与迭代的协同工作:
# 默认拒绝
default allow := false

# 完整规则:管理员完全访问
allow {
  input.user.role == "admin"
}

# 部分规则:审计员可读审计日志
allow {
  input.user.role == "auditor"
  input.log.type == "audit"
  input.action == "read"
}

# 否定规则:排除敏感日志
allow {
  input.user.role == "viewer"
  not input.log.severity in ["CRITICAL", "SECRET"]
  not input.log.source in ["security_events", "user_credentials"]
}

# 部分规则:动态生成允许的日志类型
allowed_log_types[log_type] {
  log_type := input.available_types[_]
  log_type in ["application", "system", "network"]
}
否定与迭代组合模式 日志数据源 input.logs[] 隐式迭代 logs[_] 遍历所有条目 否定过滤器 not 排除条件 否定 + 迭代组合模式 模式1:排除特定元素 not source in blocked_sources 模式2:检查不存在条件 not any_critical_error 模式3:黑名单生成 not action in allowed_actions 模式4:双重否定(全量检查) not any_unprocessed_log 组合模式在日志访问控制中用于实现白名单、黑名单、异常检测和全量验证

4.8 常见错误与调试

在编写 Rego 规则时,以下错误最为常见,需要特别注意:

  • 未定义变量否定:对未定义的变量使用 not 会导致策略评估失败,而非返回 true。
  • 默认值类型不匹配:为集合规则设置 default allow := false(布尔值)会导致类型错误。
  • 迭代空集合:对空数组进行迭代不会产生错误,但可能导致策略意外放行。
  • 否定范围过大:not (condition1 and condition2) 可能产生与预期不符的逻辑结果。
  • 规则名冲突:完整规则和部分规则不能同名,否则会导致编译错误。
调试技巧:使用 trace 包和 opa eval --explain=full 命令可以查看规则评估的详细路径,帮助定位否定和迭代中的逻辑错误。