Permify 属性访问控制(ABAC)实战指南:用 attributes 与 rules 构建基于属性的细粒度授权

Permify 属性访问控制(ABAC)实战指南:用 attributes 与 rules 构建基于属性的细粒度授权 Permify 属性访问控制ABAC实战指南用 attributes 与 rules 构建基于属性的细粒度授权【免费下载链接】permifyAn open-source authorization as a service inspired by Google Zanzibar, designed to build and manage fine-grained and scalable authorization systems for any application. — Permify is now part of FusionAuth 项目地址: https://gitcode.com/GitHub_Trending/pe/permify导读本文系统讲解 Permify 中基于属性访问控制Attribute-Based Access ControlABAC的设计思路与实战方法。你将学会如何在 Permify Schema 中通过attribute与rule两大核心构件描述用户、资源与环境的属性条件如地理位置、访问时段、账户余额、设备信息如何结合关系型授权ReBAC/RBAC组合出灵活且可追溯的访问策略以及如何使用validate命令与场景化 YAML 对 ABAC 模型进行自动化验证。读完本文你可以直接在自己的授权模型中落地基于布尔、字符串、整数、浮点数的条件判权。什么是 ABAC从游乐园到访问控制ABACAttribute-Based Access Control是一种基于属性做出访问决策的访问控制模型。它像一名保安根据请求者、目标资源乃至当前环境所具备的**特定特征attributes**来决定是否放行——这些属性可以归属于用户subject、资源object或环境environment其取值会直接影响一次访问请求的最终结果。用一个直观的类比来理解想象一个游乐园里有三个不同的游乐设施每个设施对游客有不同要求设施一身高必须超过 6 英尺设施二体重必须低于 200 磅设施三年龄必须在 1218 岁之间。ABAC 的工作方式与此完全相同——它检查你在用户、资源或环境上定义的某些品质是否满足预设条件满足则授予访问否则拒绝。为什么需要 ABAC答案很朴素有些场景用 ReBAC 或 RBAC 并不合适。就好比在炎热的沙漠公路上装雪地胎、或在暴风雪中换夏季胎——工具与场景不匹配。ABAC 适合以下典型场景地理限制Geographically Restricted像夜店门口只放行来自特定城镇的客人一样例如流媒体服务可以根据国家/地区决定哪些影片可见以符合各地的播放许可规则。时间限制Time-Based像家长为孩子设定电脑使用时间一样例如系统只允许在办公时段内执行某些操作。隐私法规合规Compliance with Privacy Regulations例如医院系统需要根据患者授权、访问目的以及访问者身份三重条件限制谁能查看患者数据。数值限额Limit Range用规则定义数量上限或区间例如银行系统对转账或取款金额设置限额。设备信息Device Information根据设备类型、操作系统版本、是否已安装最新安全补丁等设备属性控制访问。可见 ABAC 是一种更情境化contextual的授权方式你可以在应用的请求上下文中携带任意数据围绕主体subject与客体object所处的情境来定义访问权限。Permify 正是通过向 DSL 中引入attributes属性与rules规则两个构件来支撑这种能力。在 Permify DSL 中建模 ABAC定义属性Attributes属性用于以特定数据类型描述实体entity的属性特征。例如可以为组织实体定义一个ip_range属性其类型为字符串数组attribute ip_range string[]定义attribute时可选的全部数据类型如下// A boolean attribute type boolean // A boolean array attribute type. boolean[] // A string attribute type. string // A string array attribute type. string[] // An integer attribute type. integer // An integer array attribute type. integer[] // A double attribute type. double // A double array attribute type. double[]从仓库实现看这 8 种类型与pkg/attribute/attribute.go中TypeUrlToString所支持的 proto 值类型一一对应BooleanValue、BooleanArrayValue、StringValue、StringArrayValue、IntegerValue、IntegerArrayValue、DoubleValue、DoubleArrayValue编译阶段则由pkg/dsl/compiler/compiler.go的getArgumentTypeIfExist将 DSL 中的string/boolean/integer/double及数组后缀映射为对应的AttributeType枚举。定义规则Rules规则rule是允许你在模型中编写具体条件判断的结构可以将其理解为每种编程语言都有的简单函数它接受参数基于条件求值并返回true/false。下面的示例 Schema 中规则被用来判断给定 IP 地址是否落在指定的 IP 范围内entity user {} entity organization { relation admin user attribute ip_range string[] permission view check_ip_range(ip_range) or admin } rule check_ip_range(ip string, ip_range string[]) { context.data.ip in ip_range }Permify 的 Schema 语言基于 Common Expression Language (CEL) 设计因此语法与 C、Go、Java、TypeScript 中的等价表达式几乎一致。context是规则体内的保留变量指向请求中携带的上下文数据。从源码来看这一设计并非仅停留在文档层面在 编译器 中compileRule会为每个规则参数以及context变量创建 CEL 环境cel.Variable对规则表达式进行编译与类型检查并且强制要求规则表达式的输出类型必须是布尔值compiledExp.OutputType() ! cel.BoolType时直接报错。而在 check 引擎 的checkDirectCall中规则被还原为 CEL AST 并用prg.Eval(arguments)实际求值——参数值来自实体属性通过QueryAttributes读取与请求上下文context.data最终只有布尔结果为true才返回 ALLOWED。接下来我们通过几个常见用法来剖析 ABAC 的落地模式。布尔型条件True/False对于表示二值状态如 yes/no的属性Boolean是最佳选择entity post { attribute is_public boolean permission view is_public }布尔属性可以直接作为权限表达式使用无需额外编写规则这是布尔类型独有的便捷性。其实现由编译器的compileIdentifier见 compiler.go保证当标识符引用类型为属性且类型为boolean时它被编译为ComputedAttribute叶子节点执行阶段则由 checkComputedAttribute/checkDirectAttribute 读取属性值并直接判定——值解析为true即 ALLOWED否则 DENIED。文本与对象条件Text Object Based Conditions字符串型属性适用于多种需要文本信息参与判权的场景例如地理位置存储USA、EU、Asia等区域标识按地理位置控制访问设备类型存储mobile、desktop、tablet等设备类型时区存储EST、PST、GMT等时区标识星期存储Monday、Tuesday等按星期几控制资源访问。以下 Schema 在 v1.1 以上版本中可正常工作若你使用更早版本请参考版本差异文档 v1.0-v1.1 迁移指南。entity user {} entity organization { relation admin user attribute location string[] permission view check_location(location) or admin } rule check_location(location string[]) { context.data.current_location in location }数值条件Numerical Conditions整数Integers整数属性适用于以下数值判断场景年龄面向年龄受限的资源以整数属性存储年龄安全等级为不同安全级别的用户存储整数等级如 1、2、3数字越大级别越高资源大小或长度按文档长度、文件大小等整数属性控制访问版本号按软件版本或文档修订号等整数属性做决策。entity content { attribute min_age integer permission view check_age(min_age) } rule check_age(min_age integer) { context.data.age min_age }浮点数Doubles——精确数值信息Double属性适用于需要小数精度的数值判权场景用量限额如存储容量、下载数据量等需要十进制精度表示的限额交易金额金融系统中需要精确表示的交易金额如 $100.50用户评分如 5 分制下带小数的评分4.7地理位置坐标如经纬度这类通常带小数点的坐标值。entity user {} entity account { relation owner user attribute balance double permission withdraw check_balance(balance) and owner } rule check_balance(balance double) { (balance context.data.amount) (context.data.amount 5000) }综合用例详解用例一公开/私有仓库本模型中is_public被定义为布尔属性。由于属性是布尔类型它可以直接用于权限表达式无需编写规则entity user {} entity post { relation owner user attribute is_public boolean permission view is_public or owner permission edit owner }含义若仓库的is_public属性为true则所有人可查看若为false非公开则只有所有者此处为user:1可以查看。permission view is_public or owner表示只要仓库公开is_public为 true或者当前用户是仓库所有者即授予 view 权限。关系relationshipspost:1#owneruser:1属性attributespost:1$is_public|boolean:truePost View 的 Check 演进子查询→post:1#is_public→ true →post:1#adminuser:1→ true哈希前的请求键check*{snapshot}*{schema*version}*{context}_post:1$is_public→ truecheck*{snapshot}*{schema*version}*{context}_post:1#adminuser:1→ true这一模型在仓库的 public-private-repository.yaml 示例形态文件中也有完整对应实现。用例二按工作日放行本模型中要查看仓库必须满足当天不是周末且用户是所属组织的成员。entity user {} entity organization { relation member user attribute valid_weekdays string[] permission view is_weekday(valid_weekdays) and member } entity repository { relation organization organization permission view organization.view } rule is_weekday(valid_weekdays string[]) { context.data.day_of_week in valid_weekdays }该模型声明要获得仓库的view权限必须同时满足两个条件——根据上下文数据day_of_week判定的当前日期不是周末由is_weekday规则判定并且用户是拥有该仓库的组织的成员。关系organization:1#memberuser:1Organization View 的 Check 演进子查询→organization:1$is_weekday(valid_weekdays)→ true →organization:1#memberuser:1→ true哈希前的请求键check*{snapshot}*{schema*version}*{context}_organization:1$is_weekday(valid_weekdays)→ truecheck*{snapshot}*{schema*version}*{context}_post:1#memberuser:1→ true用例三银行系统取款限额该模型描述一个包含user与account两个实体的银行系统user银行的客户account银行账户拥有owner类型为user和balance账户内金额。entity user {} entity account { relation owner user attribute balance double permission withdraw check_balance(balance) and owner } rule check_balance(balance double) { (balance context.data.amount) (context.data.amount 5000) }check_balance 规则验证取款金额是否小于等于账户余额且不超过 5000单次取款上限。它接受两个参数取款金额amount来自请求上下文与账户当前余额balance来自账户属性。owner 检查验证请求取款的人是否为该账户的所有者。只有两个条件同时为真withdraw权限才会被授予——即用户必须是账户所有者且取款金额在账户余额之内且不超过 5000。关系account:1#owneruser:1属性account:1$balance|double:4000Account Withdraw 的 Check 演进子查询→account:1$check_balance(balance)→ true →account:1#owneruser:1→ true哈希前的请求键check*{snapshot}*{schema*version}*{context}_account:1$check_balance(balance)→ truecheck*{snapshot}*{schema*version}*{context}_account:1#owneruser:1→ true仓库中的 banking-system.yaml 给出了可运行的完整版本其中甚至包含两组断言场景账户所有者提取 3000 → 允许以及非所有者 steven 提取 3000 → 拒绝可直接用于验证模型行为。用例四层级化使用Hierarchical Usage在该模型中employee权限检查中使用的 subject 类型。此例中权限逻辑不依赖 employee 的任何关系因此任何 employee 主体都会得到相同结果organization代表整个组织拥有founding_year属性。当check_founding_year规则检查组织是否成立于 2000 年之后返回 true 时授予view权限department组织内的部门。拥有budget属性和指向父级organization的关系。当部门预算超过 10,000由check_budget规则判定并且organization.view权限为真时授予view权限。注意在此模型中权限可以引用更高级别的权限如organization.view。但你不能以这种方式使用某个关系的属性——例如不能直接在权限表达式中引用organization.founding_year。权限可以依赖关联实体的权限但不能直接依赖关联实体的属性。entity employee {} entity organization { attribute founding_year integer permission view check_founding_year(founding_year) } entity department { relation organization organization attribute budget double permission view check_budget(budget) and organization.view } rule check_founding_year(founding_year integer) { founding_year 2000 } rule check_budget(budget double) { budget 10000 }关系department:1#organizationorganization:1属性department:1$budget|double:20000organization:1$founding_year|integer:2021求值过程→department:1$check_budget(budget)→ true→organization:1$check_founding_year(founding_year)→ true→department:1#view→ true多组织关联的并集语义如果某个部门关联了多个组织organization.view会在所有关联组织上分别求值。例如department:1同时关联organization:1与organization:2只要其中一个通过view遍历依然成功——因为relation.permission对关联实体采用并集union语义。示例数据department:1#organizationorganization:1department:1#organizationorganization:2department:1$budget|double:20000organization:1$founding_year|integer:2021organization:2$founding_year|integer:1990求值结果department:1$check_budget(budget)→ trueorganization:1$check_founding_year(founding_year)→ trueorganization:2$check_founding_year(founding_year)→ falsedepartment:1#organization.view→ truedepartment:1#view→ trueABAC 访问检查的求值过程以 IP 范围检查模型为例观察一次完整的 ABAC 判权流程模型entity user {} entity organization { relation admin user attribute ip_range string[] permission view check_ip_range(ip_range) or admin } rule check_ip_range(ip_range string[]) { context.data.ip in ip_range }此例中的context指请求内部的上下文。你可以在请求中携带任何类型的数据并在模型中引用它。例如context: { data: { ip_address: 187.182.51.206, day_of_week: monday }, }关系organization:1#adminuser:1属性organization:1$ip_range|string[]:[187.182.51.206, 250.89.38.115]Check 请求{ entity: { type: organization, id: 1 }, permission: view, subject: { type: user, id: 1 }, context: { data: { ip_address: 187.182.51.206 } } }Organization View 的 Check 演进子查询→organization:1$check_ip_range(context.ip_address,ip_range)→ true →organization:1#adminuser:1→ true缓存机制缓存通过对数据库快照snapshot、Schema 版本schema version以及子查询进行哈希作为键并存入其结果因此与关系型子查询的缓存方式完全一致。例如哈希前的请求键check*{snapshot}*{schema*version}*{context}_organization:1#adminuser:1→ truecheck*{snapshot}*{schema*version}*{context}_organization:1$check_ip_range(ip_range)→ true从实现上看规则子查询与关系子查询在 check 引擎 中走的是同一条invoke通路checkCall将权限名替换为规则名check.go#L509-L523随后checkDirectCall读取规则定义、拉取计算属性、构建context.data并以 CEL 求值属性查询同样支持从请求上下文属性contextual attributes与持久化存储双源合并因此请求级上下文数据可以与存储中的实体属性一起参与判权。如何使用 ABAC安装与模型验证安装 Permifydocker pull ghcr.io/permify/permify:latest验证 YAML 结构Permify 的校验文件由以下顶层字段构成schema: - {string schema} relationships: - entity_name:entity_id#relationsubject_type:subject_id attributes: - entity_name:entity_id#attributeattribute_type:attribute_value scenarios: - name: name description: description checks: - entity: entity_name:entity_id subject: subject_name:subject_id context: tuples: [] attributes: [] data: key: {value} assertions: permission: result entity_filters: - entity_type: entity_name subject: subject_name:subject_id context: tuples: [] attributes: [] data: key: {value} assertions: permission: result_array subject_filters: - subject_reference: subject_name entity: entity_name:entity_id context: tuples: [] attributes: [] data: key: {value} assertions: permission: result_array注意context中的data字段可以以键值对形式赋予任意期望值稍后可在模型中通过request.key引用该值。校验文件中的示例context: tuples: [] attributes: [] data: day_of_week: saturday该 YAML 片段指定了一个不包含元组与属性的校验上下文data字段表示当天为星期六。模型中的示例permission delete is_weekday(valid_weekdays)模型中设置了一条delete权限规则它调用is_weekday函数并传入关联实体上的valid_weekdays属性值。若is_weekday([monday, tuesday, wednesday, thursday, friday])为 true则授予 delete 权限。创建校验文件下面是一个同时覆盖属性、规则、关系、上下文数据与多类断言的完整校验文件示例schema: - entity user {} entity organization { relation member user attribute credit integer permission view check_credit(credit) and member } entity repository { relation organization organization attribute is_public boolean attribute valid_weekdays string[] permission view is_public permission edit organization.view permission delete is_weekday(valid_weekdays) } rule check_credit(credit integer) { credit 5000 } rule is_weekday(valid_weekdays string[]) { context.data.day_of_week in valid_weekdays } relationships: - organization:1#memberuser:1 - repository:1#organizationorganization:1 attributes: - organization:1$credit|integer:6000 - repository:1$is_public|boolean:true scenarios: - name: scenario 1 description: test description checks: - entity: repository:1 subject: user:1 context: assertions: view: true - entity: repository:1 subject: user:1 context: tuples: [] attributes: [] data: day_of_week: saturday assertions: view: true delete: false - entity: organization:1 subject: user:1 context: assertions: view: true entity_filters: - entity_type: repository subject: user:1 context: assertions: view: [1] subject_filters: - subject_reference: user entity: repository:1 context: assertions: view: [1] edit: [1]运行验证命令docker run -v {your_config_folder}:/config ghcr.io/permify/permify-beta:latest validate /config/validation.yaml该命令在仓库中的实现位于 pkg/cmd/validate.go其内部流程与上述 YAML 结构一一对应解析文件file.NewDecoderFromURL→ 加载并解析 Schemaschema.NewSchemaLoaderparser.NewParser→ 编译校验compiler.NewCompiler(true, sch).Compile()→ 写入实体定义 → 逐个校验并写入关系validate.go#L150-L186与属性validate.go#L191-L227→ 对每个场景执行checks权限检查、entity_filtersLookupEntity与subject_filtersLookupSubject断言最终输出SUCCESS或带颜色标注的FAILED明细。需要理解的两点属性字符串的规范格式校验文件中entity_name:entity_id$attribute|attribute_type:attribute_value的写法与运行时完全一致由 pkg/attribute/attribute.go#L22-L131 中的Attribute函数解析数组类型用逗号分隔元素并通过ValidateValue校验值与声明的属性类型匹配断言的三种能力checks验证某个主体对某个实体是否拥有某权限布尔结果entity_filters验证某个主体在给定条件下能访问哪些实体实体 ID 数组subject_filters验证哪些主体能访问某个实体主体 ID 数组——分别对应Check、LookupEntity、LookupSubject三个 API。总结ABAC 为 Permify 的授权模型补上了情境判权这一环attribute负责以 8 种类型承载实体属性数据rule基于 CEL 编写可复用的布尔条件二者既可独立工作如布尔属性直接用于权限表达式也可与relation组合如check_balance(balance) and owner形成属性 关系的混合授权。借助validate命令与场景化 YAML你可以在不启动服务的情况下对模型进行回归验证确保每个属性条件、每个上下文数据在判权时都符合预期。【免费下载链接】permifyAn open-source authorization as a service inspired by Google Zanzibar, designed to build and manage fine-grained and scalable authorization systems for any application. — Permify is now part of FusionAuth 项目地址: https://gitcode.com/GitHub_Trending/pe/permify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考