SpiceDB New Enemy 测试套件深度解析:CockroachDB 上的时间戳乱序与事务重叠防护 SpiceDB New Enemy 测试套件深度解析CockroachDB 上的时间戳乱序与事务重叠防护【免费下载链接】spicedbOpen Source, Google Zanzibar-inspired database for scalably storing and querying fine-grained authorization data项目地址: https://gitcode.com/GitHub_Trending/sp/spicedb本文基于 e2e/newenemy/README.md 及其配套测试源码完整解析 SpiceDB 在 CockroachDB 后端上针对New Enemy Problem新敌人问题的端到端验证方案包括问题产生的根本原因、复现所需的条件与配置、防护手段事务重叠策略的实现原理以及整个测试套件的运行机制。读完本文你将理解为什么基于 CockroachDB 的 SpiceDB 需要--datastore-tx-overlap-strategy这类防护参数以及测试是如何在真实集群中验证未防护会出问题、防护后不再出问题这一结论的。一、背景什么是 New Enemy ProblemNew Enemy Problem 是分布式数据库用于承载细粒度授权数据时特有的一类一致性问题。它源自 Google Zanzibar 论文中的相关讨论授权系统在执行排除exclude与授予direct两类写入时客户端期望看到严格按因果顺序生效的结果——先排除、后授予则授予之后权限才出现反之先授予、后排除则排除之后权限立刻消失。在 Zanzibar 原始设计中这个问题由 Spanner 的TrueTime机制天然化解。SpiceDB 文档原文引述如下Spanners TrueTime mechanism assigns each ACL write a microsecond-resolution timestamp, such that the timestamps of writes reflect the causal ordering between writes, and thereby provide external consistency.即TrueTime 为每次 ACL 写入分配微秒级时间戳写入时间戳反映写入之间的因果顺序从而提供外部一致性external consistency。而CockroachDB 并不提供同样的保证它选择的方式是在后续读取重叠键overlapping keys时进行等待wait on subsequent reads of overlapping keys。因此当 SpiceDB 以 CockroachDB 为数据存储时New Enemy Problem 在理论上是有可能发生的这也正是本测试套件存在的原因。二、测试核心Schema 与三步操作序列测试围绕一个典型的排除 授予权限模型展开。README 给出了最小化的测试 SchemaZed 语言definition user {} definition resource { relation direct: user relation excluded: user permission allowed direct - excluded }其中allowed direct - excluded表示direct关系减去excluded关系即被排除的用户即使拥有 direct 关系也没有权限。在真实测试实现中Schema 被进一步泛化为四个命名空间[newenemy_test.go](https://link.gitcode.com/i/d78e6a0f8eb4dfa46c73d4773cdf28a8)中的schemaText模板definition {{.User}} {} definition {{.Blocklist}} { relation user: {{.User}} } definition {{.Allowlist}} { relation user: {{.User}} } definition {{.Resource}} { relation direct: {{.Allowlist}} relation excluded: {{.Blocklist}} permission allowed direct-user - excluded-user }测试通过唯一命名空间前缀生成器见 e2e/generator/names.go为每批测试生成互不冲突的命名空间与对象 ID确保不同测试轮次之间数据完全隔离。README 定义了三步操作序列这是触发问题的最小脚本exclude 写入resource:thegoods#excludeduser:1#...—— 将用户放入排除名单direct 写入resource:thegoods#directuser:1#...—— 同时将用户加入直接授权Check 校验resource:thegoods#alloweduser:1#...—— 在步骤 2 返回的 revision 上读取步骤 1、2 写入的 tuple判断该用户是否仍被授予权限。期望的行为是步骤 3 的结果为拒绝无权限因为excluded减去了direct。若结果为允许则说明发生了 New Enemy Problem。三、SQL 层每一步背后的数据库操作README 给出了每一步翻译为 CockroachDB SQL 的形态。注意一个细节README 中两条 INSERT 的 relation 字段实际是相互颠倒的示例中将第 1 步标为direct、第 2 步标为excluded但结合 newenemy_test.go 的generateTuples实现可以确认真实语义为第 1 步写入excluded关系blocklist 关联第 2 步写入direct关系allowlist 关联。下文按真实语义呈现。1. 写入排除 tupleexclude writeINSERT INTO relation_tuple (namespace,object_id,relation,userset_namespace,userset_object_id,userset_relation) VALUES (resource,thegoods,excluded,user,1,...) ON CONFLICT (namespace,object_id,relation,userset_namespace,userset_object_id,userset_relation) DO UPDATE SET timestamp now() RETURNING cluster_logical_timestamp()2. 写入直接授权 tupledirect writeINSERT INTO relation_tuple (namespace,object_id,relation,userset_namespace,userset_object_id,userset_relation) VALUES (resource,thegoods,direct,user,1,...) ON CONFLICT (namespace,object_id,relation,userset_namespace,userset_object_id,userset_relation) DO UPDATE SET timestamp now() RETURNING cluster_logical_timestamp()两条 INSERT 都以cluster_logical_timestamp()作为写入修订revision的返回来源——这正是问题观察的关键客户端拿到的是 CockroachDB 集群逻辑时钟生成的时间戳。ON CONFLICT ... DO UPDATE保证了 tuple 的幂等触写TOUCH 语义与测试中使用的OPERATION_TOUCH一致。3. 执行 Check快照读取SET TRANSACTION AS OF SYSTEM TIME 1631462510162458000; SELECT namespace, object_id, relation, userset_namespace, userset_object_id, userset_relation FROM relation_tuple WHERE namespace resource AND object_id thegoods AND relation excluded; SET TRANSACTION AS OF SYSTEM TIME 1631462510162458000; SELECT namespace, object_id, relation, userset_namespace, userset_object_id, userset_relation FROM relation_tuple WHERE namespace resource AND object_id thegoods AND relation direct;Check 通过AS OF SYSTEM TIME将两次读取固定在第 2 步写入返回的 revision 上测试中通过AtExactSnapshot: r2.WrittenAt指定见 newenemy_test.go模拟客户端在第 2 步返回后立即精确快照校验的场景。四、触发条件什么情况下时间戳会乱序New Enemy Problem 的发生前提是客户端按顺序观察到exclude write与direct write并以direct write返回的 revision 发起 Check却仍然被授予了权限。这只有在direct write返回的时间戳低于exclude write的时间戳时才可能发生——即两次写入的逻辑时间戳发生了乱序。SpiceDB 基于 CockroachDB 时这一乱序需要同时满足三个条件README 原文exclude write与direct write两次写入必须落在不同的 range数据范围上第 2 次写入所在 range 的 leader 节点不能与第 1 次写入所在 range 的任何 follower 同节点否则两个节点上的 timestamp cache 会同步逻辑时钟会正确排序事务两次写入由集群中两个不同节点接收且其中一个节点的时钟较慢存在时钟偏差。之所以可能成立是因为这两次写入所触及的键keys互不重叠README 强调 This is possible because the keys that are written in the transactions do not overlap.。当写入不重叠时CockroachDB 各 range 之间可以独立推进时钟无法像重叠写入那样通过后续读取等待来保证顺序。提高触发概率的集群配置为了在测试中稳定复现这些条件README 建议将 CockroachDB 配置为ALTER DATABASE spicedb CONFIGURE ZONE USING range_min_bytes 0, range_max_bytes 65536, num_replicas 1;这一配置的作用range_max_bytes 65536将 range 上限压到 64KiB尽可能缩小 range 尺寸从而显著提高不同写入落入不同 range 的概率num_replicas 1将副本数降为 1使得节点不可能同时持有某个 raft leader 的 follower直接瓦解条件 2 的防护。在 newenemy_test.go 中实际使用的配置还额外包含了gc.ttlseconds 10以加速过期数据回收并预先执行SET CLUSTER SETTING kv.range.backpressure_range_size_multiplier0关闭 range 背压确保小 range 不被合并。即使如此仍需人为放大条件README 特别指出即使在上述极端配置下要真正触发 New Enemy Problem测试还必须生成大量 tuple 集使其分散到多个 rangenewenemy_test.go中生成了 4000 组命名空间、数百组关系数据人为引入远超 CRDB 集群常态的时钟偏差与网络延迟。这里人为引入时钟偏差在源码中由 chaosd 工具完成cockroach.go 的TimeDelay通过chaosd attack clock对指定节点进程注入--time-offset-200ms的时钟偏移NetworkDelay则通过chaosd attack network delay在 loopback 上注入网络延迟。平台限制README 明确标注了该测试的运行平台限制Note: timechaos only works on amd64 and ptrace calls dont work in qemu, which means there is no way to run this test suite on an arm machine (like m1 mac).即时钟混沌注入timechaos仅支持 amd64 架构ptrace 调用在 qemu 模拟环境下不可用因此该测试套件无法在 ARM 机器如 Apple M1 Mac上运行。五、测试实现三组 SpiceDB 对照验证newenemy_test.go 的TestNoNewEnemy是整套测试的主入口其设计思路是对照实验在同一个 CockroachDB 集群上并行启动三组 SpiceDB 实例唯一区别是--datastore-tx-overlap-strategy参数实例overlap 策略预期行为vulnerableinsecure可以观测到 New Enemy Problem作为受害者基准reqprotectedrequest基于请求元数据的防护应无问题stdprotectedstatic静态键防护应无问题测试流程对应源码各阶段启动 3 节点 CRDB 集群cockroach.NewCluster(3)创建三个内存存储的 CockroachDB 节点执行迁移后分别建立直连newenemy_test.go启动三组 SpiceDB通过 spice.NewClusterFromCockroachCluster 挂到同一集群使用不同端口gRPC/dispatch/HTTP/metrics错开并各自传入 overlap 策略参数缩小 range 并填充数据执行上文 zone 配置然后生成 4000 组命名空间 Schema、以及每组 500 条关系数据确保数据铺满多个 range定位慢节点记录节点 2 的SHOW node_id作为慢时钟节点并通过SHOW RANGE FROM TABLE ... FOR ROW查询见getLeaderNode/getLeaderNodeForNamespacenewenemy_test.go反复挑选 leader 满足exclude 写入 leader 不在慢节点、direct 写入 leader 在慢节点的命名空间前缀注入时钟偏移对节点 1 施加-200ms的时钟延迟crdb.TimeDelay执行探测循环反复执行写入 exclude → 短睡 → 写入 direct → 在 direct 的 revision 上 Check同时监控两个 revision 是否出现z1.Revision.GreaterThan(z2.Revision)时间戳反转以及 Check 是否返回PERMISSIONSHIP_HAS_PERMISSION。统计学验证设计测试并非复现一次即通过而是采用采样 高置信度的统计方法statTest与iterationsForHighConfidencenewenemy_test.go先在未防护insecure实例上采集 5 个样本记录各自触发问题所需的尝试次数计算样本均值与标准差推导出达到 3σ 置信水平所需的尝试次数由-max-iterations标志封顶默认 10000 表示不设上限用同样的尝试次数去验证防护实例static/request——若防护生效这些次数内不应出现 New Enemy。这一设计同时回答了问题确实存在与防护确实有效两个命题。六、防护原理事务重叠Transaction Overlap策略New Enemy Problem 的根源是两次写入不重叠。SpiceDB 的 CockroachDB 驱动给出的解法详见 internal/datastore/crdb/README.md是让相关的写入事务发生重叠——为所有可能重叠的关系写入选择一个共同的数据库键在该键上执行写入从而强制事务在同一个 range 上串行化使 timestamp cache 与逻辑时钟能够正确排序。四种策略的取舍命令行参数--datastore-tx-overlap-strategy定义于 pkg/cmd/datastore/datastore.go仅对 CockroachDB 驱动生效支持四种取值策略行为保护范围代价insecure不写任何重叠键无防护无额外开销但存在 New Enemy 风险static每次写入都附加同一个静态键默认key可用--datastore-tx-overlap-key修改所有写入全局重叠所有写入串行化到同一 range吞吐代价最大request从 gRPC 请求元数据requestmeta.RequestOverlapKey读取重叠键携带相同请求元数据键的写入互相重叠由调用方控制保护粒度需业务侧配合prefix按命名空间前缀/之前部分生成重叠键同前缀命名空间内的写入重叠不允许使用无前缀命名空间见 keys.go 注释其中insecure是唯一不提供防护的选项启用时驱动会打印告警日志running in this mode is only safe when replicas nodescrdb.go即仅当副本数与节点数相等时才安全。源码实现overlapKeyer策略的核心实现在 internal/datastore/crdb/keys.go 中通过overlapKeyer接口抽象type overlapKeyer interface { addKey(keySet keySet, namespace string) }noOverlapKeyer不添加任何键对应insecure以及读路径appendStaticKey(key)闭包形式为每个命名空间都添加同一个静态键对应staticprefixKeyer从命名空间提取/前缀作为键对应prefixoverlapKeysFromContext从 gRPC 入站 metadata 中读取RequestOverlapKey并解析为键集合对应request。在 crdb.go 的驱动初始化中按策略装配keyer与keySetInit每次ReadWriteTx开始时初始化键集合事务内的每个关系写入都会通过 keyer 向集合补充重叠键事务提交前统一对这些键执行一次写入见crdb.go中for k : range rwt.overlapKeySet的批量写入逻辑从而保证同一事务触碰的所有重叠键发生物理重叠。注意static策略在未指定 overlap key 时会直接报错static tx overlap strategy specified without an overlap key防止误配置。测试中request策略实例的调用方在每次探测前通过 metadata 注入test-i形式的请求重叠键newenemy_test.go这正好演示了request模式的生产用法由业务侧为逻辑上相关的写入分配同一重叠键。七、构建与运行注意事项README 说明该测试在 CI 中运行并且从 head 构建 spicedb而非固定发布版本。由于测试代码的依赖始终指向最新代码e2e模块的go.mod/go.sum可能随时与主模块不同步。若出现同步问题可按 README 给出的方式修复cd e2e go get -d github.com/authzed/spicedb/cmd/spicedb/... go build github.com/authzed/spicedb/cmd/spicedb/... go mod tidy除此之外本地运行还需要满足以下前置可从源码推断可执行文件./cockroach、./spicedb与./chaosd位于工作目录cockroach.go 与 spicedb.go 均直接以相对路径调用chaosd的时钟/网络攻击需要sudo权限测试环境须为 amd64 Linux见上文平台限制数据库名固定为spicedbnetestdbName常量与 SpiceDB 实例通过--datastore-conn-uri指向同一数据库。八、小结通过 e2e/newenemy/README.md 与配套源码可以看到SpiceDB 针对 CockroachDB 的 New Enemy Problem 采取的是验证 防护双管齐下的策略验证在缩小 range、单副本、注入时钟偏差的极端配置下用统计采样证明insecure策略确实会出现先排除后授予却被放行的异常防护通过static/request/prefix三种事务重叠策略强制相关写入发生键重叠用同一套探测证明防护生效。这也解释了生产部署时的参数选择逻辑如果应用对 ACL 更新的顺序敏感例如先移除权限再发布内容的场景默认的static策略是安全兜底若接受一定风险并追求写入吞吐可评估insecurerequest与prefix则提供了介于两者之间的细粒度平衡。理解这套测试的触发条件与防护原理是在 CockroachDB 上正确调优 SpiceDB 一致性行为的关键前提。【免费下载链接】spicedbOpen Source, Google Zanzibar-inspired database for scalably storing and querying fine-grained authorization data项目地址: https://gitcode.com/GitHub_Trending/sp/spicedb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考