ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

状态化网关安全模型:buzz NIP-PL 推送网关的形式化验证与交付不变量

状态化网关安全模型:buzz NIP-PL 推送网关的形式化验证与交付不变量 状态化网关安全模型buzz NIP-PL 推送网关的形式化验证与交付不变量【免费下载链接】buzzA hive mind communication platform项目地址: https://gitcode.com/GitHub_Trending/buzz14/buzz本篇文章以 docs/formal/STATEFUL_GATEWAY.md 为主体结合 NIP-PL 规范草案、docs/formal/nip-pl/ 下的可执行形式化模型以及 0015 迁移 中的持久化权威面实现深入解析 buzz 公共推送网关public push gateway的有状态安全模型它持久化了哪些权威状态、在交付路径上强制执行哪八条安全不变量、如何用有界可执行模型与变异测试证明这些不变量成立以及该模型明确不承诺的边界。一、为什么网关必须是有状态的职责划分的起点在 buzz 的推送架构中kind:30350推送租约push lease被定义为安装级、会过期、可撤销的授权而不是一个普通的 APNs/FCM 注册回调。为了支撑这个模型交付路径被拆成两个逻辑组件它们拥有完全不同的状态归属公共网关public gateway持久化安装权威installation authority、加密的 APNs-token 托管encrypted APNs-token custody、中继委托relay delegations、重放预留replay reservations和端点配额endpoint quotas这些状态存放在 PostgreSQL 中中继relay作为执行者executor独立拥有租约匹配lease matching、事件授权event authorization、合并coalescing和持久化投递任务durable delivery jobs。也就是说中继负责该不该唤醒、唤醒什么网关只负责用哪个安装端点、以什么权威、发多少次。网关的有状态体现在它不能只做一个无状态转发层它必须记住每一个安装的权威链App Attest 密钥 → 安装句柄 → 委托 → 端点代际并把这些记忆作为每一次 APNs 投递的前置条件。该划分的规范来源见 NIP-PL.md 的 Public APNs Gateway Profile (Buzz, normative) 一节其中明确写道The gateway is stateful: it retains installation authority, encrypted APNs-token custody, relay delegations, replay reservations, and endpoint quotas. The relay remains the executor.这正是 STATEFUL_GATEWAY.md 开篇第一段所浓缩的内容。二、八条交付不变量有界可执行模型delivery.pySTATEFUL_GATEWAY.md 的核心主张是以上权威面可以被压缩成一个有界可执行模型bounded executable model存放在 docs/formal/nip-pl/delivery.py。该模型刻意排除中继匹配器尚未随本仓库交付只检查网关真正随代码交付的线性化规则。它验证的八条不变量如下。2.1 不变量 1投递必须使用密封进授权凭证的 NIP-98 签名者每一次投递请求都由中继携带 NIP-98Authorization: Nostr event-json头到达网关。网关在准入时必须校验该 NIP-98 事件的签名者必须与endpoint_grant中密封的中继签名公钥一致。在 delivery.py 中对应为relay ! self.relay拒绝条件——用relay-b伪造签名者身份的任何尝试都会被拒绝。NIP-PL 规范将这一条表述为网关校验 NIP-98 的签名、时间戳、方法、URL、payload且the event pubkey is the relay identity见 NIP-PL.md 的 Relay delivery 一节。2.2 不变量 2准入时刻安装、委托、epoch、generation 与两个过期时间全部有效delivery.py的admit()一次性检查if (self.revoked or relay ! self.relay or epoch ! self.epoch or generation ! self.generation or now self.installation_expires or now self.grant_expires or now request_expires or request_expires self.grant_expires or auth_id in self.auth_replays or request_id in self.request_replays): return False对应 delivery.pyepoch必须等于当前安装端点代际endpoint epochgeneration必须等于当前委托代际三条时间线同时存活now ≤ installation_expires、now ≤ grant_expires、now ≤ request_expires并且request_expires ≤ grant_expires请求过期不得越过委托过期。这与规范中now request.expires_at grant.expires_at的约束一致。explore()用product([False, True], repeat6)遍历 64 种布尔组合断言admitted expected即准入结果与六项条件全部满足严格等价。2.3 不变量 3吊销/轮换与准入由同一个持久化权威事务排序撤销revocation和端点轮换rotation与投递准入之间存在竞争谁先提交事务谁就决定结果。delivery.py通过permutations((admit, revoke))遍历两种交错断言result (actions[0] admit)——若 revoke 先提交后续 admit 必须失败。for actions in permutations((admit, revoke)): g Gateway(); result None for action in actions: result g.admit() if action admit else (g.revoke() or result) assert result (actions[0] admit)对应 delivery.py。规范侧的解释是a revocation or rotation commit that completes first prevents the old-capability send; a send admitted first may finish——即先到先得的线性化语义而不是投递总能赢或撤销总能赢。2.4 不变量 4NIP-98 事件 id 全部烧毁终态请求 id 保持烧毁瞬时请求 id 仅在处置后才释放这是网关重放防御replay protection的核心分两层NIP-98 授权事件层每一个被准入的 NIP-98 事件 id 永久烧毁auth_replays集合不可重放请求 id 层request_id是中继持久化任务 UUID同时也是 APNs 的apns-id。若投递结果是终态terminal如 APNs 接受或端点永久失效该request_id保持烧毁若结果是瞬时transient如503 retry则仅在处置完成之后释放该 id允许中继用一个新的 NIP-98 事件重试同一任务。delivery.py中finish()实现def finish(self, request_id, outcome): if outcome transient: self.request_replays.discard(request_id) elif outcome ! terminal: raise ValueError(outcome)explore()对terminal/transient两种结局分别断言auth id 始终不可复用request-1仅当结局为 transient 时才允许携带新 auth 重试。对应 delivery.py。2.5 不变量 5配额在每次准入尝试时扣费且永不退还admit()中self.quota 1与重放围栏replay fences发生在同一个提交内——注释写明One durable admission commit: both replay fences and non-refundable quota。即便 custody 失败见不变量 6导致本次尝试不发送配额也已经扣除。变异测试专门针对瞬时完成后退还配额这一诱惑性弱化做了检测见第三节。对应 delivery.py。2.6 不变量 6APNs-token 托管失败则不能发送如果托管层custody拿不到加密的 APNs token网关可以拒绝准入但绝不能带着残缺状态去发送。delivery.py中custody_okFalse时走finish(request_id, transient)并返回Falseif not custody_ok: self.finish(request_id, transient) return False这是托管失败与瞬时故障之间的映射请求 id 被释放以便重试但本次不允许发送。规范上网关对 APNs token 的保管是加密托管token_ciphertext见 0015 迁移 中的push_gateway_installations.token_ciphertext BYTEA列。2.7 不变量 7每一次真实发送的 body 都是 NIP-PL 注册的字节常量NIP-PL 的 APNs 传输 profile 规定应用 body 必须是精确的 UTF-8 字节常量{aps:{alert:{body:Reconnect to your relay now},mutable-content:1}}在 delivery.py 中定义为FIXED_BODY每一次发送都被断言为body FIXED_BODY在 Rust 网关实现中同样的常量以原始字节字符串形式存在 crates/buzz-push-gateway/src/model.rs。这条不变量的含义是中继请求、签名、授权凭证、端点、请求 id、过期时间、profile、provider 响应没有任何一项可以进入应用 body——APNs 拿到的永远是同一个重新连接唤醒信号杜绝了把推送通道变成影子数据流shadow feed的可能。2.8 不变量 8端点轮换后旧 epoch 的授权凭证无法复活rotate()将self.epoch 1随后explore()断言not g.admit(epoch1)且g.admit(epoch2)成功——旧 epoch 密封的endpoint_grant在轮换后立即失去投递权威。对应 delivery.py。这与规范的 Endpoint rotation 一节一致A successful atomic rotation invalidates every grant sealed to the old epoch并且返回200 {status:rotated}。三、变异测试给模型装上牙齿一个永远绿灯的模型没有价值。STATEFUL_GATEWAY.md 明确指出 delivery_mutation.py 会削弱signer、epoch、terminal-burn、quota 和 fixed-body 这五项检查并要求每个变异体mutant都被模型捕获# M1: omit signer confinement. g Gateway(); g.relay relay-b if g.admit(relayrelay-b): caught.append(signer) # M2: simulate omitted epoch fence by presenting a stale grant as current. g Gateway(); g.rotate(); g.epoch 1 if g.admit(epoch1): caught.append(epoch) # M3: remove terminal request burn. g Gateway(); assert g.admit(); g.request_replays.clear() if g.admit(auth_idauth-2): caught.append(terminal-burn) # M4: refund quota on transient completion. g Gateway(); assert g.admit(); g.finish(request-1, transient); g.quota - 1 if g.quota 0: caught.append(quota-refund) # M5: application body depends on relay input. mutant FIXED_BODY brelay-a if mutant ! FIXED_BODY: caught.append(fixed-body) expected {signer, epoch, terminal-burn, quota-refund, fixed-body} assert set(caught) expected对应 delivery_mutation.py。最终断言捕获集合与预期集合完全相等意味着五类最危险的规格弱化全部会产生可观测的违反violation模型并非空转。同理fixed_payload.py 用 8 类输入的笛卡尔积request body、NIP-98 header、grant、endpoint、profile、request id、expiry、provider 响应各 2 个取值穷举2^8 256种组合断言application_body(inputs) C恒成立形式化验证了APNs 应用 body 非干涉性noninterferencefixed_payload_mutation.py 则把每一类输入逐一注入 body 构造变异体断言每一类变异都被捕获。四、模型之外的兄弟模型租约接受与生命周期虽然 STATEFUL_GATEWAY.md 聚焦于网关权威面但 docs/formal/nip-pl/NOTE.md 说明整个形式化压力测试覆盖两个独立的已交付契约另一支是租约接受模型acceptance.py 穷举单个租约地址(author, 30350, d)的 7 个候选事件活跃租约、合法吊销墓碑、高代际/旧 created_at 的毒化事件、合法再激活、精确重放、NIP-01 平局、高 created_at/陈旧代际的纯 watermark 见证者的全部 5040 种排列验证五条不变量I1 无复活tombstone 之后旧事件不得复活、I2 watermark 单调且被拒事件不得改状态、I3 不得 watermark 毒化NIP-01 败者不得抬高 watermark、I4 stored 与 effective 状态永不背离、I5 重放窗口释放后重放仍被拒绝mutation_test.py 独立地执行两种最诱人的规格弱化只按 generation 接受丢弃 NIP-01 排序、只按 NIP-01 接受丢弃 generation watermark并断言两者都产生复活/毒化 bug 序证明双序必须同时满足spec 接受检查第 8 条是模型真正依赖的脊柱。这条双序规则在 NIP-PL.md 的 Acceptance and Origin Binding 中写为it wins exact NIP-01 addressable-event ordering AND itsgenerationis strictly greater than the internal generation watermark——失败任一即拒绝且不改任何状态从而让高代际、旧 created_at的恶意事件无法毒化 watermark。五、持久化权威面迁移 0015 的实际落库STATEFUL_GATEWAY.md 说网关把五类状态放在 PostgreSQL 中migrations/0015_push_gateway_authority.sql 就是这份状态的 DDL 证据。六张表与五类状态一一对应状态类别表关键约束一次性挑战push_gateway_challengeschallenge 哈希 32 字节带过期安装权威 token 托管push_gateway_installationsApp Attest 公钥唯一、token_ciphertext加密托管、token_fingerprint32 字节、endpoint_epoch 0、UNIQUE (app_profile, token_fingerprint)实现全局端点唯一中继委托push_gateway_delegationsUNIQUE (installation_id, relay_pubkey)、not_before expires_at、代际 0端点配额push_gateway_endpoint_quotasadmitted 0且永不为负永不退还的落库形态NIP-98 授权重放围栏push_gateway_delivery_auth_replays主键(relay_pubkey, auth_event_id)即每个事件 id 烧毁一次请求 id 重放围栏push_gateway_delivery_request_replays主键(relay_pubkey, request_id)终态保留、瞬时释放的持久化载体表底部统一插入_operator_global_tables并注明原因——这些表故意位于中继社区租约之外span relay communities一个安装可以委托给多个中继部署安装权威与端点配额是部署全局的。这与 0012_push_leases.sql租约事件本身、0013_push_endpoint_state.sql端点状态一起构成了整个推送栈的持久化基础。六、模型的诚实边界不承诺什么STATEFUL_GATEWAY.md 最后一段是模型的免责声明三件事它明确不覆盖不承诺 provider 投递的 exactly-onceAPNs 接受后、处置持久化前若发生崩溃语义保持 at-least-once请求过期时间request expiry为由此产生的重放预留设置了上界不建模 PostgreSQL 实现细节真实数据库的竞态、外键与保留策略由独立测试验证NOTE.md 明确Real PostgreSQL race/FK/retention tests validate the implementation separately不覆盖尚未交付的中继 matcher/worker租约匹配、事件授权、合并、持久化投递任务属于中继侧模型只针对网关确实交付的权威面。此外NOTE.md 的 Honest limits 还补充模型枚举的是有界抽象转移而非 SQL 调度或网络行为常量 payload 能防止内容泄露但无法隐藏唤醒的时机与频率——这正是 NIP-PL.md Privacy Considerations 中平台侧信息量退化为唤醒时序的流量分析这一结论的来源。七、运行模型本地复现验证NOTE.md 给出了五个模型的运行方式全部为纯 Python 标准库、无第三方依赖可在任意 Python 3 环境直接执行python3 acceptance.py python3 mutation_test.py python3 delivery.py python3 delivery_mutation.py python3 fixed_payload.py python3 fixed_payload_mutation.py其中delivery.py成功时输出stateful gateway invariants: HOLDdelivery_mutation.py成功时输出五个被捕获的变异体名称。acceptance.py输出7! permutations的穷举结论ALL INVARIANTS HOLD而mutation_test.py则展示 gen-only 与 nip01-only 两种弱化各自检测到的 bug 序数量直观说明双序接受的必要性。八、小结从八条不变量到可信推送路径整个状态化网关安全模型的逻辑可以压缩为一句话中继决定要不要唤醒网关决定有没有资格唤醒而资格由安装权威、代际、过期、重放围栏与配额在同一个持久化事务中裁定。八条不变量中1–3 管谁在什么时间有资格4–5 管每个 id 和每个配额只能用一次6–7 管发送行为与内容边界8 管状态演进不可回退。这些不变量不是文档里的一纸空谈——它们被 delivery.py 穷举、被 delivery_mutation.py 用变异体反证、被 0015 迁移 落库并被 crates/buzz-push-gateway/src/model.rs 中的字节常量在真实实现中强制执行。若你正在设计类似的平台推送 租约授权架构这八条不变量与其形式化验证方式是一份可以直接借鉴的安全骨架。【免费下载链接】buzzA hive mind communication platform项目地址: https://gitcode.com/GitHub_Trending/buzz14/buzz创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表