授权策略证据模型与工具选型
某文件服务完成了统一登录改造,所有请求都能验证令牌,资源详情接口也会检查 viewer 角色。一次批量下载需求上线后,任务创建接口只验证“用户属于当前租户”,后台 worker 读取任务中的对象 ID 后直接访问存储。普通成员把另一个项目的对象 ID 放进任务,系统仍返回成功。登录证据是真的,租户也是真的,越权同样是真的:被保护动作、目标资源与执行点从未进入同一次授权决定。
另一套系统把项目成员关系同步到集中授权服务。管理员删除外包人员后,控制台显示关系已经移除,但 API 网关、SDK 和授权服务各自保存 allow 缓存,报表订阅还是一条早已建立的长连接。新请求偶尔拒绝,旧连接持续推送。团队只能证明“源关系已删除”,却不能证明哪个 policy revision、哪个 data snapshot、哪个 PEP 已经执行 deny,更无法回答撤销暴露了多久。
一次决定必须绑定四个请求对象
principal 是发起动作的主体,可以是用户、服务、设备或工作负载;它必须来自服务端验证过的身份上下文,而不是浏览器提交的 user_id。主体还可能需要身份保证等级、会话世代和租户,这些字段决定旧会话是否仍可复用授权缓存。认证成功只证明调用者身份,不证明其拥有任何资源权限。
action 应使用稳定的业务词汇,例如 document.read、invoice.approve、cluster.policy.create。HTTP GET、gRPC method 或消息 topic 只是传输入口,由 PEP 维护到业务 action 的唯一映射。批量下载、单文件下载和生成预签名地址可能都是不同 action;若三者共享一个含糊的 read,策略很难表达不同风险,审计也无法定位哪个副作用被允许。
resource 不是裸 ID。最少要有类型、服务端解析出的标识、tenant,以及会改变授权语义的业务版本。若合同从草稿变成已签署版本,旧 ACL 是否仍适用必须有明确答案。缓存键遗漏资源版本,会让旧内容上的 allow 被复用到新内容,这正是授权系统里的 New Enemy 类问题。
context 只容纳策略确实需要的动态事实:网络区、设备状态、用途、时间窗口、风险等级或操作来源。每个字段要标明来源和缺失语义。客户端传来的 purpose=support 不能直接获得客服权限;PEP 应从受信系统重算或验证。context 无法稳定规范化时,宁可禁用最终决定缓存,也不要对复杂对象做不可靠哈希。
{
"request_id": "opaque-request",
"tenant": "sandbox",
"principal": { "type": "user", "id": "alice", "session_epoch": 7 },
"action": "document.read",
"resource": { "type": "document", "id": "demo", "version": "v3" },
"context": { "purpose": "review", "network_zone": "corp" }
}这四个对象必须在 PEP 内规范化后再送入 PDP。主体、租户、action 或资源类型未知时,返回 deny 或显式 error;不能用默认角色、空资源或“没有规则所以继续”修补输入。输入合同本身就是第一道安全边界。
policy、data 与 decision 必须处于可解释快照
授权判断依赖两类输入。policy 描述规则、模型或 permission expression,data 提供主体属性、资源属性、组织关系、对象关系和环境事实。配置策略引擎常把 policy 与小规模数据一起分发;关系授权服务把关系元组保存在专用 datastore;嵌入式引擎由宿主加载策略和实体;Kubernetes 准入从 API 请求、对象和参数资源得到数据。
只有“策略文本”或“关系存在”都不足以重建决定。每次 PDP 响应至少带稳定 decision、低基数 reason code、不可变 policy/model revision、数据 snapshot 或 freshness 证据、decision ID 和错误类别。外部调用者不需要完整求值树,内部审计也不应保存原始 token、资源内容或可逆关系路径。
{
"decision": "deny",
"reason_code": "RELATION_NOT_FOUND",
"decision_id": "opaque-decision",
"policy_revision": "immutable-revision",
"freshness": { "mode": "at-least-required", "evidence": "opaque" },
"diagnostics": { "error_class": null },
"ttl_ms": 0
}deny 与 error 在执行层都不能放行,但必须分别计数。deny 表示在已知输入和可接受快照上明确拒绝;error 表示依赖超时、模型不合法、条件缺失或结果不可解释。若把 error 当 deny,运维看不到授权服务已经失效;若把 error 当 allow,故障会直接变成越权。
revision 还解决多副本争议。两个 PDP 对同一请求给出不同结果时,先比较 policy revision、data snapshot 和 consistency mode。版本不同就摘除旧副本或标记不可比,而不是做多数投票。授权不是最终一致的推荐结果,少数旧 allow 仍会泄露资源。
PDP 负责计算,PEP 才能阻止副作用
NIST 的 ABAC 模型把策略管理、信息提供、决策和执行拆为 PAP、PIP、PDP 与 PEP。对工程团队最重要的结论是:策略引擎的 allow/deny 不是业务执行证据。PDP 可以是进程内库、sidecar、独立服务或 API Server 内的求值器;PEP 则分布在每个真正控制资源的入口。
同步 HTTP 中间件只是最显眼的 PEP。RPC interceptor、GraphQL resolver、消息 consumer、批量 worker、定时任务、对象存储签名服务、管理 CLI、WebSocket 和流式订阅都可能产生等价副作用。项目应维护 entrypoint -> action -> resource resolver -> PEP 清单,并通过合同测试证明每个入口在副作用之前执行授权。
request -> authentication -> server-side normalization -> PDP call
-> allow -> PEP performs exactly the approved action
-> deny -> PEP rejects without side effect
-> error -> PEP fails closed for protected actions
-> audit -> decision and enforcement outcome are correlatedPEP 还负责 obligations。若 PDP 返回“只允许脱敏字段”“必须二次确认”之类义务,PEP 必须声明自己支持的有限集合;未知 obligation 直接拒绝。不要让 PDP 返回自由文本,再由不同应用自行解释安全行为。执行语义一旦分叉,审计里相同的 allow 会代表不同权限。
旁路验证比普通 deny 用例更重要。直接访问后端端口、调用批量接口、重放后台消息、复用旧预签名 URL 或保持已有长连接,都应得到与主入口一致的保护。无法封闭旁路时,应通过网络策略、服务身份和后端二次 PEP 缩小攻击面,而不是依赖“调用方约定不绕过”。
enforcement、audit 与 revocation 要闭合
执行日志应把 decision ID、PEP ID、action、资源类型、policy revision、freshness mode、执行结果和 trace ID 放在同一条低敏记录中。主体和资源标识使用分域、带密钥的不可逆伪名;bearer token、cookie、完整 JWT、原始 context、完整实体切片和关系路径禁止离开 PDP 信任域。下游日志平台再脱敏已经太晚,因为本地文件、队列和重试副本可能先泄露。
撤销不是“删除一条关系”或“发布一版 deny 策略”的瞬间动作。完成时间由发现、发布、应用、缓存和在途访问共同组成:
T_revoke = T_detect + T_publish + T_apply + T_cache + T_inflight
require p99(T_revoke) <= REO(action_class)REO 是按动作风险设定的撤销暴露目标。管理、密钥、导出等高风险动作通常最严,普通读取按数据敏感度决定。计时从权威撤销提交成功开始,到所有目标 PEP 对同一会话和已建立连接连续稳定拒绝为止。只看到关系库删除成功、某个新副本 deny 或新登录失败,都不能结束计时。
缓存键至少包含 tenant、principal、action、resource type/id/version、全部策略相关 context、session epoch、policy/model revision 与 freshness bound。allow cache 会延迟撤销,deny cache 会延迟新授权,二者需要不同目标。策略更新优先用 revision 切换实现逻辑失效;关系撤销使用有序、幂等、可重放事件或强一致读取兜底。网关、SDK、sidecar、PDP、CDN 和长连接的缓存都必须进入清单。
审计还要记录撤销事件的稳定 event ID、对象键、单调版本、接收顺序和幂等结果。延迟到达的旧 grant 不能覆盖较新的 revoke,重复 revoke 不能重新打开窗口。IETF 对安全事件与令牌撤销的保守处理可作为设计参照:RFC 8417 说明异步安全事件,RFC 7009 说明撤销传播窗口必须被最小化。
为什么四类工具不能横向乱比
配置合规、Kubernetes 准入、嵌入式应用授权和关系授权的共同点是“输入经过规则得到决定”,但这只是抽象外形。它们的执行时机、保护资源、数据形状、状态所有者和失败代价不同。把它们放进统一功能表,往往会得到“都支持 deny、审计、策略测试”的假等价。
配置合规工具的输入通常是文件或渲染产物,PEP 是本地命令、pre-commit 或 CI job。它擅长在变更进入交付链前发现违规;无法证明运行时对象没有漂移,也无法保护业务 API。它的撤销通常表现为阻止新制品或重新扫描,而不是终止一个用户会话。
Kubernetes 准入的 PEP 是 API Server admission 链,resource 是 Kubernetes 对象,请求发生在持久化之前。ValidatingAdmissionPolicy/CEL、Gatekeeper、Kyverno 可围绕对象、参数、审计或变更构建控制,但它们不会替应用判断 user:alice 能否读取 document:demo。准入 controller 失联还要考虑 failurePolicy、API Server 延迟和集群可用性。
嵌入式授权在应用进程内求值,适合低延迟角色、属性与领域规则。Cedar 明确提供语言和引擎,而不是开箱即用的网络服务、策略分发、HA 与租户控制平面;宿主要显式做 schema validation、快照装载和日志。Casbin 一类库还需 adapter、watcher 或服务化组件处理持久化和多实例传播。低延迟来自与应用共进程,也意味着策略错误与应用共享故障域。
关系授权把对象关系和反向查询作为核心能力。OpenFGA、SpiceDB 处理模型、relationship/tuple、Check 与 List/Lookup,并承担专用 datastore 与一致性问题。它们适合共享、继承和组织图,不是通用任意策略语言。OpenFGA 的一致性选择与 SpiceDB 的 ZedToken 也不是同一种证据,不能因为都提供“更一致的读”就混为一谈。
选型应把同一个风险映射到四类问题:决定发生在提交、API Server 还是业务请求时;资源是配置文件、集群对象还是业务对象;规则以属性为主还是关系图为主;是否需要反向列表;撤销必须多快可见;哪个 PEP 能真正阻止副作用。只有这些答案相同,延迟、DSL、部署复杂度和成本比较才有意义。
用最小正反实验验证证据链
建立隔离的测试服务,只有一个资源 document:demo 和一个动作 document.read。正向 fixture 给 Alice 精确 permit 或 viewer relation。请求必须经过真实 PEP,预期返回业务内容,并生成可关联的 request ID、decision ID、revision 和 enforcement outcome。随后删除 permit 或关系,以目标工具可用的新鲜度模式重新检查,直到稳定 deny。
反向实验依次注入:跨租户 resource、未知 action、缺失 context、错误主体类型、PDP 500、timeout、畸形 JSON、缺 revision、未知 obligation、直接后端访问和已建立长连接。每个用例的断言不是“状态码不是 200”,而是业务副作用计数保持不变,审计能区分 deny 与 error,且不存在未经过 PEP 的等价入口。
cases:
- id: allow-reader
principal: user:alice
action: document.read
resource: document:demo
expect: allow
- id: deny-cross-tenant
principal: user:alice
action: document.read
resource: document:other-tenant
expect: deny
- id: fail-closed-timeout
fault: pdp-timeout
expect: error-without-side-effect
- id: deny-after-revoke
operation: delete-viewer
expect: stable-deny-within-reo实验产物应保存引擎版本、不可变 revision、数据 snapshot 或一致性模式、输入摘要、样本数、预期、实际、退出码和失败列表。不要把网页截图当主要证据,也不要把示例结果写成生产基线。测试程序在隔离环境运行后才有实际输出;上面的 fixture 只是读者可实现的合同。
关系系统还要增加 List 与 Check 一致性用例:从 Alice 反查文档集合,再对每个候选执行同模型、同 snapshot 的 Check;分页期间 revision 改变时显式失败或重新开始。配置和准入工具则增加 parser、渲染产物、参数资源、存量审计与 admission 请求的差异用例。实验形状相似,证据含义仍不能互换。
项目接入要把授权变成窄接口
应用层建立单一 AuthorizationGateway,输入使用服务端类型而不是任意 map。主体由认证层提供,资源 resolver 从业务库读取 tenant 与版本,action 来自编译期枚举或集中映射,context provider 标明每个属性来源。业务 handler 只能消费 Allowed、Denied 或 Indeterminate,不能读取底层产品的随意 JSON 后自行解释。
缓存、重试和超时也归网关管理。Check 通常幂等,可以在总 deadline 内有限重试;关系写入、策略发布不能盲重试,必须有 operation ID 与幂等语义。高风险 action 不缓存最终 allow,低风险缓存 TTL 不超过 REO。PDP 响应缺 policy revision 或 freshness evidence 时,网关返回 Indeterminate,PEP 按失败关闭处理。
资源创建跨业务库与授权库时,用 outbox 保存授权意图。业务事务提交资源与 PENDING_AUTHZ 状态,consumer 幂等写关系或实体,确认后才对外可见。删除反向执行:先让 PEP 拒绝新访问,再撤销关系、终止连接、清理内容和缓存。这样能从 operation 状态判断卡在哪一层,而不是让请求线程做无事务保障的双写。
列表查询需要单独接口与容量池。先从关系系统获得候选再应用业务属性过滤,或让策略引擎消费受限候选;禁止先读全量业务数据再逐项远程 Check。单项授权与列表必须共享 tenant、model revision、context 和一致性语义,否则“列表看不到但直接 URL 能访问”或相反的分叉迟早出现。
选型从六个问题开始
第一个问题是执行时机:提交前、Kubernetes 持久化前、业务调用时还是多个时机都要保护。第二个问题是数据形状:规则主要依赖静态配置、对象属性还是大规模关系图。第三个问题是查询形状:只有单点 Check,还是必须支持 ListObjects、ListUsers、LookupResources 一类反向查询。
第四个问题是延迟与可用性。嵌入式或 sidecar PDP 减少网络跳转,控制服务断网时可继续使用批准快照,但实例 revision 收敛更难;集中 PDP 统一治理,却把网络、负载均衡和 datastore 放进关键路径。为每个 action 定义整体 deadline、故障时的 deny/error、允许的最大旧版本年龄和是否存在经过审批的低风险降级。
第五个问题是新鲜度。策略 bundle 最终一致、关系服务缓存、ZedToken、较高一致性读取和宿主快照不是同一种能力。候选工具必须证明“写后读”“撤销后拒绝”“分页同 snapshot”和“模型切换原子性”中真正需要的部分。无法提供可比较 freshness evidence 的两路影子决定,不能进入发布差异率。
第六个问题是控制面与退出。谁维护策略仓、签名、分发、关系数据、schema/model 迁移、决策日志、密钥、备份和审计?能否导出所有租户的不可变模型、关系、PEP action 映射和黄金语料?OPA 的架构入口可用于理解 PDP 与管理职责,OPA Management APIs;关系授权可分别查看 OpenFGA consistency 与 SpiceDB consistency;嵌入式语义可查看 Cedar authorization。这些入口帮助确认能力,不替团队完成选型证据。
容量与成本不能只看单次 Check
容量模型应分开统计策略求值 QPS、关系 Check QPS、List/Lookup QPS、关系写入速率、关系总量、策略或实体快照大小、图遍历深度与分支、PIP 调用、缓存命中、审计事件和撤销事件。List 的计算形状通常比 Check 更重;热门资源、大组织嵌套和条件关系会制造长尾,均匀随机压测很难暴露。
PEP timeout 预算要覆盖网络、PDP 排队、PIP/datastore、求值、序列化和有限重试。候选 p99 应留出业务执行余量,而不是吃满 endpoint deadline。影子发布使用独立有界队列和并发池,队列满时丢弃影子请求并告警,绝不能反压权威决策。记录采样率和丢弃率,否则漂亮的一致率可能只来自最容易的请求。
成本除了计算节点与数据库,还包括跨区流量、审计日志索引、备份、策略评审、模型迁移、业务 PEP 改造、值班、培训与退出演练。本地嵌入并非免费,它把分发、HA 和可观测成本转给平台;托管关系服务也不会替业务修复 action 映射与旁路。把责任成本显式列入选型,常常比比较许可证更接近真实总成本。
失败模式要从现象反推证据缺口
刚撤销仍 allow:核对权威撤销是否提交、请求使用哪个 consistency mode、命中的 policy/model revision、PEP/PDP/网关/SDK 缓存键,以及长连接是否重新授权。不要先清所有缓存或重启全部节点,那会破坏事故证据,也无法证明下一次撤销可靠。
同一请求在不同副本结果不同:先按 revision 和 data snapshot 分组,摘除未收敛副本;若 revision 相同,再查输入规范化、PIP 来源和时间敏感 context。多数投票不能解决授权正确性,因为一个旧 allow 就足以形成泄露。
PDP 健康但业务越权:优先查 PEP 清单、endpoint 到 action 映射、批量与后台路径、错误映射、对象存储直链和网络旁路。策略再精确也修不好未执行的入口。反之,策略变更后大量合法请求失败,要区分预期 allow -> deny、新 error 和输入字段漂移,而不是只看总体错误率。
List 与 Check 不一致:核对两者是否使用同一 tenant、model/schema、context、分页 snapshot 与候选过滤。分页跨 revision 时应中止并重新开始;业务后过滤不能重新放入关系系统已经拒绝的对象。配置合规通过而准入拒绝,则检查 parser、渲染版本、默认值、准入参数与实际对象,不要宣称两个工具互相矛盾。
决策日志出现 token 或 PII:立即停止日志出口、轮换受影响凭证、检查本地文件、上传 body、collector、重试队列和死信,再在 PDP 离开信任域前做字段白名单与 canary 扫描。只修改 SIEM 展示掩码不会清除已经复制的敏感数据。
权限敏感操作需要更窄的治理
策略仓提交、制品签名、发布审批、关系写入、模型创建、生产查询、日志读取、审计删除和紧急授权必须分权。能发布策略的人不应删除决策审计,业务服务只获得自身 tenant 和必要 action 的检查权限,迁移工具使用短期、可撤销身份。凭证放入工作负载身份或 Secret 系统,不能写进策略、关系条件和命令历史。
break-glass 不是 fail-open。它使用独立强身份、窄资源与 action、短时授权、双人批准、实时告警、完整审计和自动回收。普通 PDP 故障时不会自动触发紧急角色。演练还要证明紧急权限到期后网关、PDP 缓存和长连接都稳定拒绝,而不是只删除控制台记录。
策略或模型发布采用不可变 revision、签名制品和原子激活。候选版本先跑 fixture 与历史语料,再做只观测不执行的影子比较。差异至少分为 allow -> deny、deny -> allow、same -> error、reason 变化和延迟退化;一个未解释的 deny -> allow 就阻断灰度,总体一致率再高也不能抵消权限扩大。
高可用、迁移与退出都要保留单一权威
PDP 多副本、datastore 故障、控制服务断网和日志后端故障需要不同语义。新策略激活失败时继续使用上一批准 revision,并对旧版本年龄设上限;PIP 或关系库不可达时返回 error,不能把“查不到关系”当作普通 false 再被其他 permit 放行;日志后端可有界缓冲,缓冲耗尽后的高风险行为按合规等级预先决定。
迁移期间双写不等于双主。业务事实与 outbox 形成单一意图日志,旧新系统幂等消费;旧系统先保持执行权威,新系统只影子读。差异按 tenant、action、resource type 分批清零或审批后,再把可回滚的小单元切到新 PEP。回切前必须确认旧端追平切换期间的变更,不能把流量直接送回陈旧旧端。
退出包包含策略、schema/model、不可变 ID、引擎版本、制品摘要、全量关系或实体、删除墓碑、租户映射、PEP action 映射、上下文来源、缓存与一致性策略、黄金语料、审计保留和凭证撤销清单。导入新系统后比较总数、分组计数和摘要,再重放单项 Check、列表与撤销实验。旧凭证、网络入口、日志索引和备份按保留策略核销后,迁移才真正结束。
一份可以执行的选择记录
选择记录不要写“功能更全”或“社区更活跃”就结束。为每个候选填入同一业务风险下的执行时机、PEP 所在位置、policy/data 所有者、不可变 revision、列表能力、新鲜度证据、错误语义、撤销目标、p99 预算、敏感日志边界、迁移方式、退出格式与长期 owner。无法回答的格子就是试验任务,不是默认通过项。
最终决策至少能用一句完整因果链表述:因为受保护资源在业务调用时产生、规则主要依赖对象共享与组继承、必须支持反向列表且高风险撤销要满足明确 REO,所以选择集中式关系授权,并在 API、批量 worker 与长连接设置 PEP;属性风险规则若确有必要,再以失败关闭的固定顺序组合嵌入式或通用 PDP。这样的选择可以被实验推翻、被指标观察、被审计复核,也能在成本或需求变化时退出。
