白板上画着一条横向时间轴,轴上钉着七张写有动作与时刻的小卡,右侧是一叠标注了归属的调用记录

一、真正的难点在事后,不在止血

据公开报道,有开发者工具平台披露发生过安全事件,影响范围被描述为按百分比计的一小部分控制台用户,那个百分比本身按公开报道看待,不作为本篇的判断依据;另有报道讲部分用户在上游模型服务商处的接口密钥外泄,账上额度在几小时内被消耗完。我们不推测攻击路径,不认定责任方,也不讨论赔付金额,这些都不在可核实的范围内。留下来的工程问题只有一个,而它很硬:怎么证明哪些消耗不是自己造成的。

密钥轮换能止血,但它不产生证据。等到要去上游申诉、要向客户解释、要在内部复盘定责时,能用的东西只有一样:事件窗口内每一条调用的记录,以及每条记录归属于谁。这件事的麻烦在于,它不是出事以后打开日志就能补的,而是必须在平常的调用链里就把字段留全;出事当天能拿出的证据量,等于三个月前设计日志时留下的字段数。

我们把这类事件的处置目标定成三层,顺序不能颠倒:先让继续流血的动作停下来,再让已经发生的事实固定成可复核的证据,最后才是恢复与切流量。多数团队的问题是把第二层当第三层做,等开始恢复时,证据已经被自己的处置动作覆盖掉了。

二、先冻结还是先留存:这两步为什么会互相破坏

冻结和留存是两个不同性质的动作。冻结是改变状态,把密钥吊销、把服务下线、把凭据失效;留存是复制状态,把日志、内存现场、队列积压、连接记录原样保存到只读位置。它们互相破坏的方式很具体。

吊销密钥的那一刻,很多系统里同时发生两件我们不想看到的事:与这个密钥绑定的会话上下文被清理,计费与控制台的明细查询窗口被收口。也就是说,止血动作会销毁一部分正在被调查的对象。反过来,如果为了留存先不动密钥,攻击流量继续消耗额度,每多留一分钟就多一分损失,而这段损失之后会全部落回自己账上。

处理这个矛盾的标准做法是把留存做到止血动作之前,但不必等全量留存。落地时按两步切:第一步做最小必要留存,耗时以分钟计,只拿四类东西,密钥与凭据的标识清单、事发前那一段的调用明细、上游侧的用量曲线截图、当前生效的配置快照。第二步立即执行冻结与轮换。剩下的深度留存在冻结之后再补,因为冻结后现场不会再变化,反而是安全窗口。

这里有一条容易被忽略的顺序要求:留存要在自己一侧完成,不要依赖上游控制台。上游的数据在事件期间既可能被清理,也可能因为对方系统繁忙而取不到,把它当作唯一来源,等于把证据的存亡交给别人。

三、事件窗口与时间轴模板

事件窗口不是一个点,而是一段有四个边界的时间。四个边界各有各的定法,定不准就会出现窗口外的正常消耗被一并申诉、窗口内的异常消耗没被覆盖。

时间点 界定方法 判定依据来自哪里
首次可疑调用 在调用明细里找最早一条与本方行为模式不符的记录 调用来源、客户端标识、请求体摘要
上游披露时点 以对方正式公告与工单回复为准,不以社区讨论为准 公告原文存档与工单编号
最后一个受影响凭据被吊销 全部相关凭据中最后一个失效的时刻,不是第一个 凭据管理系统的事件流水
窗口关闭 连续一段时间内未再出现可疑调用且流量已稳定 切流量后的对照监控

在这四个边界之外,时间轴模板要固定记七个必须留证的节点,缺一个都会导致事后无法解释。它应该是一张表而不是一段文字,因为文字里没有时间戳,事后复盘只能靠记忆。

序号 必须留证的节点 这一格要写什么
一 外部信号进入 谁在什么时候、通过什么渠道告知出了问题,原始公告或工单存档在哪
二 最小留存完成 留存了哪四类东西、存放在哪个只读位置、由谁执行
三 冻结执行 吊销了哪几把凭据、覆盖范围、执行时刻与执行人
四 首次异常消耗被观测 当时的额度水位与累计消耗,以及判定它异常的理由
五 上游侧用量取数 数据来自对方界面截图还是导出文件、取数时刻、是否可复核
六 切流量决策 决策人、决策依据、先切了哪部分、并行范围
七 通报发出 通报时间、接收范围、四段式内容存档、下次更新的约定

表里的时刻统一用同一套时间基准,并明确记录用的是本地时间还是标准时间,避免跨时区协作时出现两条对不上的轴。窗口内的每条争议消耗,事后都应能挂到这七行中的某一行上。

四、调用日志里必须提前留的字段

事后能证明什么,取决于事前留了什么。我们要求的字段不多,但每一个都对应一句将来要说的话。

密钥标识而不是密钥值。这句话要重复强调,因为日志里出现凭据本身是另一个事故。标识要足以定位到具体的一把、绑定哪个服务账号、由谁在什么时候签发。

调用来源与传输路径。要能区分是本方服务器直接发起,还是经由内部代理、跳板或第三方网关转发。这一项直接决定异常流量能不能被证明为外来。

客户端标识与调用形态。同一个密钥从不同客户端来,行为差异是可辨识的,包括调用库版本、并发形态、请求节奏。

请求体摘要与规模。不记原文,记摘要与长度,用来论证某个异常调用不属于本方的使用形态。

幂等键与请求编号。追回损失时要逐条对应,一句这笔不是我们做的必须落到具体某条记录上。

用量与计费口径。每次调用留下的计量单位要能换算成账,否则无法把消耗金额摊到具体动作上。

字段设计上有两个反模式。一是只留成功请求,异常与拒绝的调用不留,结果是攻击流量在日志里看起来是空白的,反而无法证明有外来调用。二是保留期太短,默认几天就滚动,等上游开始处理申诉时数据已经没了,保留期至少要覆盖对账周期。

五、与上游对账的三档结论

对账不是要一个总结果,而是把窗口内的每条消耗归到三档之一。三档的判据要提前写成可执行的规则,否则会变成谁声音大谁说了算。

档位 判据 处理方式
可证明异常 来源或客户端形态与本方基线不符,且时间落在窗口内,有对应凭据已被吊销 逐条列出提交申诉,附取数来源与换算口径
可证明正常 来源、形态、幂等键都能对应到本方在运行的任务,且有变更单或调度记录 主动排除,写明排除理由,不混入申诉
无法判定 字段缺失或来源相同,无法区分是本方偶发行为还是外来调用 单列成待确认项,估出金额范围,不宣称已确认

第三档最容易被处理掉,常见做法是把它并进正常或者并进异常。这两种并法都会带来后患:并进异常会让申诉的可信度整体下降,对方一看就知道你在凑数;并进正常则意味着自己承担了无法证明的部分,日后如果同批数据被对方认定异常,会出现口径不一致。正确做法是把它单独列出来,标明缺哪一项证据,并且把这项缺失写进复盘改进项。

三档之外还要留一份换算表:计量单位、单价口径、汇率或者结算币种的处理方式。金额只在双方都认可的口径下才有讨论意义,口径先写好,谈判才不会变成重新算账。

六、切流量的顺序与通报的写法

切流量不能凭手快。我们的次序按三把尺子排:业务重要性、可否降级、替代容量。先切可以降级的、有替代通道的、并且切完不会触发对外服务中断的部分,用来验证新凭据与新链路是否干净;核心链路放在最后,因为一旦第一批量出来仍有异常,还能整体回退。反过来先切核心链路,遇到新凭据同样受影响时,就没有可退的位置了。

切换期间要专门处理双重计费问题。新旧两条链路并行时,同一个业务动作可能被两边各调用一次,账单上出现一份凭空多出的消耗,而这段时间恰好还在事件窗口内,很容易被误算进异常部分。做法是并行期给所有调用打统一的批次标记,让这段用量在账上自成一段,事后单独核对,并与时间轴表里的切换决策时刻对应起来。

通报口径分对内对外两套,但结构一样:事实、影响、已采取、待确认。四段都要写实。事实只说已核实的部分,来源写清是本方日志还是对方公告;影响要给出范围与量级的边界,宁可写区间不写结论;已采取列动作与时刻,不写效果承诺;待确认逐条列出,并给下次更新的时间约定。最不该出现的一句话是“未发现数据泄露”,这类表述在事后一定会被推翻,因为调查未完成时没人有资格下这个结论。

这类事件的处置能力,平时看不出来,出事当天就见分晓。洹拓内部把它归到一项很具体的准备上:每个接入的上游都要能在一小时内回答当前有多少把凭据、分别绑了哪些服务、日志里能不能区分内外来源。答不出这三问的团队,出事时能做的只有止血,剩下的只能接受。