OPA(Open Policy Agent)是一款开源的通用策略引擎,能够将授权、访问控制、合规检查等决策逻辑从业务代码中独立出来。OPA使用Rego作为策略编程语言,因此理解Rego语法,是编写OPA策略的基础。
Rego是一种声明式语言,它更关注“什么情况下允许某个结果成立”,而不是按照传统编程语言的方式描述详细执行步骤。下面从数据类型、变量、规则、集合、条件判断以及策略组合等方面介绍Rego的核心语法。
一、Rego基本概念
OPA接收到输入数据后,会根据策略文件中的规则进行计算。
一个典型的OPA输入可以表示为:
JSON{ "user": { "name": "zhangsan", "role": "admin" }, "action": "read", "resource": "article" }
在Rego中,可以通过input访问这份输入数据:
regopackage example allow if input.user.role == "admin"
这里的几个元素分别表示:
-
package example:声明策略所属的包。 -
input:OPA传入的外部输入。 -
input.user.role:读取输入中的用户角色。 -
allow:定义一个名为allow的规则。 -
if:表示规则成立所需要满足的条件。
当input.user.role等于admin时,allow规则成立。
二、Rego中的注释
Rego支持单行注释,使用#表示:
rego# 判断当前用户是否为管理员 allow if input.user.role == "admin"
建议在复杂策略中添加适当注释,尤其是权限判断、例外条件以及复杂集合操作。
三、Rego基本数据类型
Rego支持多种常见数据类型,主要包括字符串、数字、布尔值、数组、对象以及集合。
1. 字符串
字符串使用双引号:
regoname := "zhangsan" role := "admin"
字符串可以用于比较:
regoallow if input.user.role == "admin"
2. 数字
Rego支持整数和浮点数:
regoage := 18 score := 95.5
可以进行常见的数学运算:
regototal := 10 + 20
也可以进行大小比较:
regovalid if input.score >= 60
3. 布尔值
布尔值只有true和false:
regoenabled := true disabled := false
例如:
regoallow if input.user.enabled == true
4. 数组
数组使用方括号:
regoroles := ["admin", "editor", "viewer"]
可以通过索引访问:
regoroles[0]
也可以直接判断某个值是否存在:
rego"admin" in roles
5. 对象
对象使用键值结构:
regouser := { "name": "zhangsan", "role": "admin" }
访问对象属性时,可以使用点号:
regouser.role
也可以使用方括号:
regouser["role"]
6. 集合
集合与数组类似,但集合中的元素没有重复值,也没有固定顺序:
regoroles := {"admin", "editor", "viewer"}
集合适合表达“允许的值集合”:
regoallowed_roles := {"admin", "editor"} allow if input.user.role in allowed_roles
四、变量与赋值
Rego中的变量使用大写字母、下划线等形式表示,常见写法如下:
regoUser := input.user
需要注意,Rego中的变量绑定与传统编程语言中的变量赋值有所区别。
例如:
regorole := input.user.role allow if role == "admin"
这里的:=用于定义变量。
Rego还支持统一(unification)概念,例如:
regorole = "admin"
在不同语境下,=更多用于统一和比较求解,而:=通常用于局部变量或规则值的定义。编写现代Rego策略时,推荐优先使用:=表达明确的局部绑定。
五、规则是Rego的核心
Rego策略主要由规则组成。
最简单的规则:
regopackage example allow if input.user.role == "admin"
如果条件满足,allow成立;如果条件不满足,该规则不会产生结果。
规则可以包含多个条件:
regoallow if { input.user.role == "admin" input.action == "read" }
较新的Rego语法也推荐使用:
regoallow if input.user.role == "admin" input.action == "read"
但需要注意,多条件规则的具体写法应结合使用的OPA/Rego版本。对于需要兼容不同版本环境的项目,应根据实际OPA版本选择语法。
六、if条件判断
Rego使用条件表达式描述规则成立的前提。
例如:
regoallow if input.user.role == "admin"
多个条件可以组合:
regoallow if { input.user.role == "admin" input.action == "delete" }
这表示两个条件必须同时满足。
还可以进行数值判断:
regoallow if input.user.level >= 5
常见比较运算符包括:
== 等于 != 不等于 < 小于 <= 小于等于 > 大于 >= 大于等于
七、逻辑条件组合
Rego可以表达复杂的逻辑关系。
例如允许管理员或者审核员执行操作:
regoallow if input.user.role == "admin" allow if input.user.role == "auditor"
多个同名规则通常表示“满足其中一个即可”。
也可以将多个条件放到一个规则中:
regoallow if { input.user.enabled input.action == "read" }
这表示用户必须处于启用状态,同时操作类型必须为read。
八、数组遍历
Rego经常需要检查数组中的元素。
假设输入:
JSON{ "users": [ {"name": "zhangsan", "role": "admin"}, {"name": "lisi", "role": "viewer"} ] }
可以通过变量遍历:
regoadmin_user[name] if { some user in input.users user.role == "admin" name := user.name }
对于策略开发来说,理解some非常重要。
例如:
regosome user in input.users user.role == "admin"
表示从input.users中寻找一个满足条件的元素。
九、some关键字
some用于声明或遍历变量。
例如:
regoallow if { some role in input.user.roles role == "admin" }
如果用户角色列表中存在admin,规则即可成立。
也可以用于索引:
regosome i input.users[i].role == "admin"
这种写法适用于需要同时获得数组索引的场景。
十、in运算符
in是Rego中非常实用的成员判断操作符。
例如:
regoallowed_roles := {"admin", "editor"} allow if input.user.role in allowed_roles
也可以判断数组:
regoroles := ["admin", "editor", "viewer"] allow if "admin" in roles
相比手动编写多个==条件,in更适合表达“是否属于某个允许列表”。
十一、对象属性访问
假设输入如下:
JSON{ "request": { "user": { "name": "zhangsan", "department": "dev" } } }
可以这样访问:
regoinput.request.user.name
也可以使用:
regoinput["request"]["user"]["name"]
当属性名称比较固定时,点号方式更加简洁;当键名本身来自变量时,方括号形式更加灵活:
regokey := "name" value := input.request.user[key]
十二、默认规则
策略中经常需要定义默认值。
例如:
regodefault allow := false allow if input.user.role == "admin"
这里的含义是:如果没有其他规则使allow成立,那么默认结果为false。
这是权限控制策略中非常常见的写法。
完整示例:
regopackage authz default allow := false allow if input.user.role == "admin" allow if { input.user.role == "editor" input.action == "read" }
这样可以形成典型的“默认拒绝、满足条件才允许”的策略结构。
十三、规则返回值
Rego规则不仅可以返回布尔值,还可以返回字符串、数组、对象等数据。
例如:
regodecision := "allow" if input.user.role == "admin"
也可以构造对象:
regodecision := { "allowed": true, "reason": "administrator" } if input.user.role == "admin"
因此,OPA并不只是简单地返回true或者false,还可以输出结构化决策结果。
例如:
regodecision := { "allow": input.user.role == "admin", "user": input.user.name }
十四、集合推导
Rego支持集合推导,可以根据输入数据生成新的集合。
例如:
regoadmins := {name | some user in input.users user.role == "admin" name := user.name }
如果输入中存在多个管理员,则可以得到管理员名称集合。
数组推导也很常见:
regonames := [name | some user in input.users name := user.name ]
集合推导适合处理过滤、转换和提取等策略逻辑。
十五、函数
Rego允许定义函数,从而将重复的策略逻辑抽取出来。
例如:
regois_admin(user) if { user.role == "admin" }
使用时:
regoallow if is_admin(input.user)
函数也可以接收多个参数:
regocan_access(user, resource) if { user.role == "admin" resource.public == false }
调用:
regoallow if can_access(input.user, input.resource)
将复杂判断拆分成多个函数,可以提高策略文件的可读性和复用性。
十六、字符串处理
Rego提供了丰富的内置函数。
例如使用strings相关函数进行字符串处理:
regostarts_with(input.request.path, "/api/")
还可以进行字符串拼接:
regomessage := sprintf("user %s is not allowed", [input.user.name])
对于路径、用户名、资源名称以及HTTP请求信息等策略判断,字符串函数非常实用。
十七、常用内置函数
OPA提供了大量内置函数,用于集合、数组、字符串、类型判断以及网络地址等处理。
例如判断字符串是否包含某段内容:
regocontains(input.request.path, "/admin/")
判断数组长度:
regocount(input.user.roles) > 0
判断某个值的类型:
regois_string(input.user.name)
在实际开发中,可以通过OPA提供的内置函数文档查询具体函数及参数。不同OPA版本支持的函数可能存在差异,因此不要仅根据网络上的旧示例判断当前环境是否支持某个函数。
十八、import语法
Rego可以通过import引入其他命名空间。
例如:
regoimport rego.v1
现代Rego策略中经常显式导入rego.v1,以获得更加明确的语言行为和语法约束。
完整示例:
regopackage example import rego.v1 default allow := false allow if input.user.role == "admin"
对于新项目,建议根据当前OPA版本和官方推荐方式使用rego.v1。
十九、package语法
每个Rego策略文件通常都会定义一个package:
regopackage example.auth
package用于组织策略命名空间。
例如:
regopackage company.auth allow if input.user.role == "admin"
查询时可以使用对应路径访问规则。
合理设计package结构,有利于大型OPA项目进行策略模块化管理。
二十、Rego中的否定
Rego使用not表达否定。
例如:
regoallow if { input.user.enabled not input.user.blocked }
含义是用户必须启用,同时不存在blocked条件。
需要特别注意,Rego中的not属于“否定证明”语义,并不简单等同于传统编程语言里的布尔取反。在处理缺失数据时尤其要谨慎。
例如:
regonot input.user.blocked
可能意味着无法证明input.user.blocked存在并满足条件,而不一定代表明确存在一个值为false的blocked字段。
对于安全策略,应尽量明确区分“字段不存在”和“字段明确为false”的情况。
二十一、规则冲突与策略设计
Rego允许定义多个规则,但不同类型规则的组合方式需要特别注意。
例如:
regoallow if input.user.role == "admin" allow if input.user.role == "editor"
这种写法可以表达多个条件满足任意一个即可。
但如果定义的是返回具体值的规则,则需要考虑多个规则同时产生结果时是否会出现冲突。
因此,在设计复杂OPA策略时,应明确区分:
-
布尔型允许/拒绝规则
-
集合规则
-
对象结果规则
-
默认规则
-
辅助函数
对于大型策略,不建议把所有判断堆积在一个allow规则中,而应该根据业务含义进行拆分。
二十二、一个完整的OPA Rego示例
下面通过一个简单的API访问控制案例综合使用前面介绍的语法。
假设要求:
-
管理员可以访问所有资源;
-
普通用户只能读取公开资源;
-
被禁用的用户不能访问任何资源;
-
默认拒绝访问。
Rego策略可以写成:
regopackage example.authz import rego.v1 default allow := false allow if { input.user.enabled input.user.role == "admin" } allow if { input.user.enabled input.user.role == "user" input.action == "read" input.resource.public }
输入数据:
JSON{ "user": { "name": "zhangsan", "role": "user", "enabled": true }, "action": "read", "resource": { "name": "article", "public": true } }
执行策略后,allow会成立。
如果将:
JSON"action": "read"
修改成:
JSON"action": "delete"
普通用户对应的规则就无法满足,最终使用默认值:
false
这个例子体现了Rego策略的典型特点:输入数据负责描述请求上下文,规则负责描述决策条件,而OPA负责执行策略并产生结果。
二十三、Rego语法学习中的常见误区
1. 把Rego当作传统命令式语言
Rego主要采用声明式策略表达方式,不需要按照“先执行A,再执行B”的思路组织代码。
重点应该放在:
什么条件成立? 成立后产生什么结果?
而不是传统程序中的执行流程。
2. 混淆:=和=
对于新手来说,这是最常见的问题之一。
通常可以简单理解:
regox := 10
用于定义一个变量或规则值。
而:
regox = 10
更多用于统一、匹配等语义。
实际编写策略时,应结合具体上下文理解,而不是机械地将=当作传统赋值符号。
3. 忽略undefined
如果访问不存在的输入字段,规则不一定简单得到false。
例如:
regoallow if input.user.vip
当vip字段不存在时,相关表达式可能处于undefined状态。
因此,处理外部输入时,应充分考虑字段缺失、类型错误以及数据结构不完整等情况。
4. 直接照搬旧版Rego代码
OPA和Rego语法持续发展,网络上存在大量不同年代的示例。
尤其是规则语法、if、contains、every以及rego.v1相关内容,新旧版本之间可能存在差异。
实际项目中应该以当前使用的OPA版本及官方文档为准。
二十四、如何测试Rego策略
策略开发不能只依赖人工检查,建议通过OPA命令行和单元测试验证规则。
假设策略文件为:
authz.rego
可以使用OPA相关命令检查和执行策略。
策略测试文件通常以:
*_test.rego
命名。
例如:
regopackage example.authz import rego.v1 test_admin_allowed if { allow with input as { "user": { "role": "admin", "enabled": true }, "action": "delete", "resource": { "public": false } } }
通过测试可以验证管理员、普通用户、禁用用户以及异常输入等不同场景。
对于生产环境中的授权策略,建议为每个关键规则建立测试用例,避免修改一条策略后意外影响其他权限场景。
二十五、Rego策略的实际应用场景
Rego的价值并不局限于简单的登录权限判断,它可以用于多个领域。
例如:
API访问控制
根据用户、角色、HTTP方法和资源路径判断请求是否允许。
Kubernetes准入控制
检查Pod、Deployment等资源是否符合组织规范,例如禁止使用特权容器、限制镜像来源等。
云资源合规检查
判断云资源配置是否满足安全规范。
微服务授权
将授权逻辑从具体业务服务中抽离,由OPA统一进行策略决策。
CI/CD安全检查
在代码部署之前检查配置、镜像或基础设施定义是否满足策略要求。
这些场景的共同点是:业务系统提供结构化输入,Rego描述策略规则,OPA负责执行策略并返回决策结果。
二十六、Rego学习建议
学习OPA Rego时,可以按照以下顺序逐步掌握:
基本数据类型 ↓ input与data ↓ 变量与表达式 ↓ 规则 ↓ 默认规则 ↓ 数组、对象与集合 ↓ some与in ↓ 集合/数组推导 ↓ 函数 ↓ 内置函数 ↓ 策略测试 ↓ 复杂授权与合规策略
不要一开始就尝试编写大型授权策略。先从一个简单的allow规则开始,然后逐步加入角色、资源、操作类型、默认拒绝和异常场景,能够更容易理解Rego的声明式思维。
掌握Rego之后,还需要进一步理解OPA的输入、数据、查询以及策略评估机制。只有把Rego语法与OPA运行模型结合起来,才能真正编写出稳定、可维护的策略。
对于实际项目,还应重点关注策略测试、版本管理、策略模块化以及OPA版本兼容性。尤其是权限控制和安全合规场景,一条看似简单的规则也可能影响大量请求,因此应通过自动化测试验证不同输入下的决策结果。