
后端认证鉴权云原生【免费下载链接】opaOpen Policy Agent (OPA) is an open source, general-purpose policy engine.项目地址https://gitcode.com/gh_mirrors/op/opa点击查看免费下载Open Policy AgentOPA的 SQL 数据过滤SQL Database Data Filtering集成让 SQL 数据库的客户端可以在查询前增强enhanceSQL 语句使查询结果严格服从 OPA 定义的授权策略。本文以主管Director能看到哪些员工的薪资为贯穿案例完整讲解如何编写数据过滤策略、利用 OPA 的部分求值partial evaluation推导出 SQLWHERE子句并把该过滤器应用到真实数据库查询中同时结合仓库源码与官方文档深入剖析 Compile API 的请求/响应格式、可翻译的 Rego 子集fragment边界以及条件允许、无条件允许、无条件拒绝三类结果的处理方式。读完本文你将具备把允许/拒绝式授权升级为行级数据过滤的完整落地能力。一、什么是 SQL 数据过滤从能否访问到能访问哪些行传统授权只回答一个二元问题某个主体能否对某资源执行某操作。例如Sally 能否从账户 058201 提款 5000 美元——答案是 allow 或 deny。这类问题称为授权求值Authorization evaluation。但很多业务场景搜索、列表、批量查询需要的是授权搜索Authorization search在不知道具体值的情况下问哪些数据是允许的。典型问题包括未知的 ActionJavier 能在升级工单上执行哪些操作未知的 Context徽章 #2541 在哪些时段可以进入储物间未知的 Subject谁有权审批超过 10000 美元的付款未知的 Resource主管能看到哪些员工的薪资授权搜索的答案是一组被过滤后的业务数据。SQL 数据过滤就是把这种哪几行可见的决策交给 OPA应用把用户上下文发给 OPAOPA 部分求值策略后返回一段 SQL 过滤器应用再把它拼进SELECT语句这段流程的完整讲解见 数据过滤概述官方系列文档从 SQL 数据过滤教程 开始逐步深入。二、核心原理部分求值与compile元数据注解SQLWHERE子句由 Rego 策略部分求值而来。所谓部分求值是指调用方声明某些输入或数据是unknown未知的OPA 只求值策略中不依赖未知值的部分把依赖未知值的表达式原样保留为残余条件。这些未知值就是你的过滤器所代表的数据片段。在数据过滤场景中约定用input.TABLE.COLUMN表示数据库行未知例如input.fruits.name指fruits表的name列。策略通过METADATA 注解声明哪些input路径是 unknown# METADATA # scope: package # compile: # unknowns: [input.fruits] package filters include if input.fruits.name banana当input.fruits被声明为 unknown 后OPA 在部分求值期间不会尝试解析input.fruits.name而是把它变成输出 SQL 中的列引用。用户的上下文如input.user.budget是已知值不在unknowns列表中会在部分求值期间被替换成具体值。compile注解支持的字段详见 策略语言文档的 Metadata compile 小节字段类型说明unknowns字符串列表部分求值期间视为未知的引用必须以input或data前缀开头。当 Compile API 请求未自带unknowns时使用。mask_rule字符串用于生成列掩码的规则引用。相对引用不带data前缀按所在包解析。可被请求体的options.maskRule覆盖。注解可以在rule、document、package、subpackages作用域声明且请求中显式提供的值优先于注解中的值。三、端到端教程主管能看到哪些员工的薪资下面完整复现 SQL 数据过滤教程 的全部步骤。数据集如下namedepartmentrolesalaryAliceengineeringdirector130000Bobengineeringengineer90000Carolengineeringengineer85000Davemarketingdirector120000Evemarketingmanager95000业务规则是主管只能看到本部门员工的薪资。3.1 创建并填充数据库把下面的 SQL 保存为employees.sqlCREATE TABLE employees (name TEXT, department TEXT, role TEXT, salary INTEGER); INSERT INTO employees VALUES (Alice, engineering, director, 130000); INSERT INTO employees VALUES (Bob, engineering, engineer, 90000); INSERT INTO employees VALUES (Carol, engineering, engineer, 85000); INSERT INTO employees VALUES (Dave, marketing, director, 120000); INSERT INTO employees VALUES (Eve, marketing, manager, 95000);加载该文件创建数据库sqlite3在 macOS 和大多数 Linux 发行版上预装sqlite3 company.db employees.sql3.2 编写策略input.employees被声明为unknown——它代表 OPA 尚未见过的数据库行input.user在查询时是known的其值会在部分求值期间被代入。把下面的 Rego 保存为policy.rego# METADATA # scope: package # compile: # unknowns: [input.employees] package filters include if { input.user.role director input.employees.department input.user.department }3.3 启动 OPAopa run --server policy.regoOPA 现在监听http://localhost:8181。3.4 向 OPA 请求 SQL 过滤器在另一个终端调用 compile 端点以登录用户作为 input。Alice 是工程部主管curl -s -X POST http://localhost:8181/v1/compile/filters/include \ -H Content-Type: application/json \ -H Accept: application/vnd.opa.sql.sqlitejson \ -d {input: {user: {name: alice, role: director, department: engineering}}}OPA 部分求值该策略的过程input.user.role director—— 两侧均为已知值条件为真被消费不出现在输出中input.employees.department input.user.department—— 左侧未知右侧已知值engineering被代入生成 SQL 条件。响应{ result: { query: WHERE employees.department engineering } }3.5 查询数据库把过滤器提取出来拼进 SQL 查询FILTER$(curl -s -X POST http://localhost:8181/v1/compile/filters/include \ -H Content-Type: application/json \ -H Accept: application/vnd.opa.sql.sqlitejson \ -d {input: {user: {name: alice, role: director, department: engineering}}} \ | jq -r .result.query) sqlite3 company.db SELECT name, salary FROM employees $FILTER;输出——Alice 看到工程部全部薪资namesalaryAlice130000Bob90000Carol85000Dave 是市场部主管同一策略给他生成不同的过滤器FILTER$(curl -s -X POST http://localhost:8181/v1/compile/filters/include \ -H Content-Type: application/json \ -H Accept: application/vnd.opa.sql.sqlitejson \ -d {input: {user: {name: dave, role: director, department: marketing}}} \ | jq -r .result.query) sqlite3 company.db SELECT name, salary FROM employees $FILTER;输出——Dave 看到市场部全部薪资namesalaryDave120000Eve950003.6 非主管被无条件拒绝Bob 是工程师而非主管。input.user.role director是已知且为假的任何规则体都不可能被满足——策略无条件拒绝curl -s -X POST http://localhost:8181/v1/compile/filters/include \ -H Content-Type: application/json \ -H Accept: application/vnd.opa.sql.sqlitejson \ -d {input: {user: {name: bob, role: engineer, department: engineering}}}响应中不存在query键{}query缺失意味着无条件拒绝应用应当直接返回零行不发数据库查询。警告确保安全默认值。OPA 返回过滤器但不负责执行它应用必须按预期使用。在本例中若用户不是主管任何规则体都无法满足OPA 返回无条件拒绝——即结果中缺少query键——应用应安全地返回零行。3.7 部分求值到底做了什么OPA 在input.user完全已知的情况下求值策略只涉及已知值的表达式如input.user.role director被完整求值并消费不出现在输出中只有涉及未知值input.employees的表达式作为残余条件存活下来再被翻译成 SQL。应用完全不需要知道策略如何决定哪些薪资可见它发送用户上下文收到一段 SQL 过滤器或一个拒绝结果去执行。3.8 无条件结果的三种处理方式OPA 响应含义应用动作{ query: WHERE ... }条件允许把过滤器追加到 SQL 查询中{ query: }无条件允许不带WHERE直接运行查询{}无条件拒绝返回零行跳过查询3.9 清理在 OPA 所在终端按CtrlC停止服务然后删除教程中创建的文件rm employees.sql policy.rego company.db四、深入理解部分求值如何逐条处理规则体评估数据过滤策略 一文给出了一个逐表达式推演的示例。基础策略如下# METADATA # scope: package # compile: # unknowns: # - input.users # - input.products package filters user : input.user include if { input.users.name user input.budget ! low } include if { input.users.name user input.budget low input.products.price 500 } include if input.products.price free给定输入{user: dana, budget: low}求值过程如下第一个include规则体input.users.name user中user已知为dana左侧input.users.name属于 unknowns因此贡献条件input.users.name dana。input.budget ! low全部为已知值且low ! low为假此规则路径被放弃本规则体已聚合的条件集被丢弃。第二个规则体再次得到input.users.name danainput.budget low全部已知且为真不新增条件input.products.price 500涉及未知值新增条件input.products.price 500。第三个规则体条件集重置input.products.price free单独成为一组条件。部分求值最终得到两组条件集input.users.name dana input.products.price 500input.products.price free翻译成 SQL 后再用OR合并users.name dana AND products.price 500products.price free(users.name dana AND products.price 500) OR products.price free五、可翻译的 Rego 子集fragment什么写法是合法的数据过滤策略必须满足两点一是正确表达行过滤逻辑二是能被翻译成目标表示如 SQL。这个可翻译的 Rego 子集俗称fragment详见 编写合法的数据过滤策略。核心原则是当只使用已知值时你可以使用全部 Rego只有涉及未知值时才有约束。5.1 未知值数据库行已知值请求上下文数据库行在策略求值期是未知的用input.TABLE.COLUMN表示用户信息通过input.user提供并在部分求值时被替换。例如input.user.budget会解析成具体数字。5.2 简单比较fragment 支持、!、、、、在未知值与已知值之间的比较。未知值在左在右都可以但关键是一侧必须已知package filters include if { input.fruits.name banana # LHS unknown, RHS constant input.fruits.price input.user.budget # LHS unknown, RHS known. input.user.fav_colour input.fruits.colour # LHS known, RHS unknown. }SQL 目标WHERE name banana AND price 10 AND colour yellow。而两侧都未知如input.fruits.name ! input.fruits.colour、或裸的未知值如input.fruits.price在大多数目标下不合法。例外SQL 翻译目标允许简单比较两侧都未知input.fruits.name ! input.fruits.colour可翻译为WHERE name colourSQL 和 UCAST/Prisma 目标还允许用通配符断言字段存在include if input.fruits.price _翻译为WHERE name IS NOT NULL注意示例中列名与语义的对应实际操作以你的表和列名为准。5.3 内置函数以下内置函数在特定约束下可翻译startswith、endswith、contains、k in ...注意不是k, v in ...且只能把未知值放在左侧package filters include if { startswith(input.fruits.name, ba) input.fruits.colour in {blue, green} }SQL 目标WHERE name LIKE ba% AND colour IN (blue, green)。下面的写法不合法把未知值放在右侧endswith(apple, input.fruits.name)、使用k, v in ...、或在未知值上使用不支持的 builtin如regexp.match。其他内置函数不能与未知值一起使用与已知值一起使用是允许的。5.4 规则与函数的使用限制可以用规则和函数组织策略但有两个限制every不能与未知值一起用default规则或函数不能与未知值组合。合法示例package filters include if user_in_corp include if { not user_in_corp apple_ish } # apple_ish rule does not use default or every apple_ish if input.fruits.name pineapple apple_ish if input.fruits.name apple # user_in_corp only uses known values default user_in_corp : false user_in_corp if endswith(lower(input.user.email), corp.com)default user_in_corp : false之所以合法是因为user_in_corp只使用已知值非法示例则是把default规则直接与未知值组合如default only_pineapples : false后接only_pineapples if input.fruits.name pineapple。5.5not表达式not只允许用于简单比较和上述内置函数不允许not与未知值或规则引用组合package filters include if { not input.fruits.name apple not input.fruits.colour in {blue, green} }SQL 目标WHERE (NOT name apple AND NOT colour IN (blue, green))。六、Compile APIAccept 头、请求体与响应格式数据过滤通过 Compile API 暴露端点格式为POST /v1/compile/{path:.}其中{path}是要编译的过滤规则例如编译data.filters.include规则就请求/v1/compile/filters/include。完整参考见 REST API 文档的 Compile API 一节。6.1 Accept 头控制目标响应格式同一个请求可以生成多种表示形式的过滤器原始 SQLWHERE子句或 UCAST 条件树OPA 用Accept头选择目标格式值响应结构说明多目标application/vnd.opa.multitargetjsonresult.{ucast,sqlserver,mysql,postgresql,sqlite}每个目标方言各一份部分求值结果。用请求体的options.targetDialects控制目标。UCASTapplication/vnd.opa.ucast.alljson、application/vnd.opa.ucast.minimaljson、application/vnd.opa.ucast.linqjson、application/vnd.opa.ucast.prismajsonresult.query描述查询为真时条件的 UCAST JSON 对象。SQLapplication/vnd.opa.sql.sqlserverjson、application/vnd.opa.sql.mysqljson、application/vnd.opa.sql.postgresqljson、application/vnd.opa.sql.sqlitejsonresult.query查询为真时条件的 SQL 等价字符串。这些常量在服务端源码 v1/server/compile_handler.go 中一一对应例如sqliteJSON application/vnd.opa.sql.sqlitejson并由targetDialect()函数映射为(sql, sqlite)等目标/方言组合compile_handler.go。e2e 测试 e2e/api/compile/e2e_test.go 对 PostgreSQL、SQLite、MySQL 等方言的 Accept 头均有实际请求覆盖。6.2 请求体字段字段类型必填说明inputany否部分求值和掩码规则求值期间使用的输入文档默认undefined。optionsobject[string, any]否部分求值期间使用的附加选项。options.disableInliningarray[string]否默认undefined规则引用列表。options.maskRulestring否用于生成列掩码的规则覆盖策略中定义的mask_rule注解。options.targetDialectsarray[string]取值ucastall、ucastminimal、ucastprisma、ucastlinq、sqlsqlserver、sqlmysql、sqlpostgresql使用multitarget时必填。其他目标下忽略。部分求值的输出目标。不同目标有不同约束。options.targetSQLTableMappingsobject[string, object[string, string]]否表与列的映射。参见 REST API 文档的映射示例。unknownsarray[string]否部分求值期间视为未知的 term默认[]。若未提供则回退到策略中的compile注解。请求示例PostgreSQL 目标POST /v1/compile/filters/include HTTP/1.1 Content-Type: application/json Accept: application/vnd.opa.sql.postgresqljson{ input: { favorite: pineapple } }配合策略include if input.fruits.name input.favoriteunknowns: [input.fruits]响应为{ result: { query: WHERE fruits.name Epineapple } }6.3 无条件结果结果中缺少query表示无条件拒绝{ result: {} }query为空字符串表示无条件包含{ result: { query: } }。6.4 表名与列名映射当 unknown 与表/列名不是一一对应时用targetSQLTableMappings做映射。例如把fruits表映射为fruit、name列映射为display_name{ input: { favorite: pineapple }, options: { targetSQLTableMappings: { postgresql: { fruits: { $self: fruit, name: display_name, price: price_tag }, price: { $table: fruits } } } } }策略include if input.fruits.name input.favorite加include if input.price free将翻译为WHERE fruit.display_name Epineapple AND fruit.price_tag Efree。多目标请求还可以按目标分别映射因为 SQL 表名未必符合某个 UCAST 消费库的需求。6.5 请求参数优先级与缓存从 compile_handler.go 的源码可以看到请求中显式提供的unknowns和options.maskRule总是优先于策略注解缺失时才通过compileFiltersUnknowns()compile_handler.go和compileFiltersMaskRule()compile_handler.go从注解提取并分别使用容量为 500 的缓存。这印证了文档中请求值优先于注解值的约定。七、进阶列掩码Column Masking行级过滤之外OPA 还能生成列掩码某行可能包含敏感列应用仍可向用户展示其余内容但敏感值在展示前应被隐藏或替换。掩码结果是一个嵌套对象{ TABLE: { COLUMN: { replace: { value: example_value_here } } } }目前只支持replace掩码函数值为空对象如users.email: {}表示显示原值。多表多列可同时存在详见 编写列掩码策略。典型的 default-deny 风格策略默认把tickets.description替换为descriptionadmin角色则放行原值package filters # METADATA # scope: document # description: Return all rows, for sake of the example. # compile: # unknowns: [input.tickets] # mask_rule: data.filters.masks default include : true # Mask all ticket descriptions by default. default masks.tickets.description.replace.value : description # Allow viewing the description if the user is an admin. masks.tickets.description.replace.value : {} if { admin in data.roles[input.tenant][input.user] }请求POST /v1/compile/filters/includeAccept: application/vnd.opa.ucast.alljson时普通 reader 会收到带替换值的masks对象admin 则收到description: {}显示原值。mask_rule也可以在请求体的options.maskRule中显式指定以覆盖注解。八、进阶UCAST 通用条件 AST除了原始 SQLOPA 还支持输出UCASTUniversal Conditions AST格式——一种用 JSON 表示的通用条件集可被多种数据源解释器消费。UCAST 树包含三类节点文档级条件节点对整个文档/表施加算子如 SQL 的 EXISTS当前解释器不使用、复合条件节点跨 N 个子节点施加算子、字段条件节点对字段和可选值施加算子。详见 UCAST 语法文档。UCAST 的展开语法示例字段节点{ type: field, field: products.price, operator: lte, value: 500 }以及省略冗余type/operator的简洁语法默认复合操作为and、默认字段操作为eq{ products.price: { lte: 500 } }字段算子包括eq、ne、lt、lte、gt、gte、in、nin、contains、startswith、endswithnot复合算子并非所有客户端都支持例如OpenPolicyAgent.Ucast.Linq就不支持。不同解释器对字段算子的支持矩阵可查阅该文档中的兼容性表格。九、生产落地建议与下一步使用语言 SDK 而非裸curl生产环境中推荐通过 生态页 中列出的 Go、Java、Python、JavaScript 等语言 SDK 调用 compile API它们提供类型化客户端。安全默认值OPA 只负责生成过滤器不负责执行。应用必须按三类无条件结果见 3.8 节设计兜底逻辑特别是无条件拒绝时直接返回零行。校验 fragment 约束策略中与未知值相关的表达式必须落在可翻译子集内简单比较、受限内置函数、受限规则结构否则编译请求会失败。继续深入可进一步阅读 部分求值推演、fragment 合法性、列掩码、UCAST 语法 以及 Compile API 完整参考并结合 服务端实现 与 e2e 测试 验证各目标方言的行为。赞分享后端认证鉴权云原生【免费下载链接】opaOpen Policy Agent (OPA) is an open source, general-purpose policy engine.项目地址https://gitcode.com/gh_mirrors/op/opa点击查看免费下载相关推荐OPA SQL 数据过滤实战用部分求值把 Rego 授权策略编译成 WHERE 子句OPA SQL 数据过滤实战用部分求值把 Rego 授权策略编译成 WHERE 子句 导读 本教程基于 Open Policy AgentOPA的 Dat后端认证鉴权云原生OPA 数据过滤策略的 Partial Evaluation 原理详解从 Rego 条件集到 SQL WHERE 子句OPA 数据过滤策略的 Partial Evaluation 原理详解从 Rego 条件集到 SQL WHERE 子句 导读 本文以 Open Policy后端认证鉴权云原生OPA 数据过滤 fragment编写可翻译为 SQL/UCAST 的合法 Rego 过滤策略OPA 数据过滤 fragment编写可翻译为 SQL/UCAST 的合法 Rego 过滤策略 导读 本文聚焦 Open Policy Agent OPA 数后端认证鉴权云原生创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考