审计链
每一个与安全相关的操作都被加密链接。设计上即可发现篡改。
HMAC-SHA256 行级链接、只追加的数据库触发器,以及每个租户单调递增的序列号。三个独立的篡改信号在表结构里,而非事后加上去。
为什么这不是一个"功能"
我们的审计日志在行级别被加密链接
即使攻击者拥有数据库的完整写权限,也无法在不留下证据的情况下伪造或删除一行。我们使用与区块链账本相同的哈希链原语,在行级别应用,以 HMAC 密钥代替共识协议。
7 年。2,555 天。每个用户的每个与安全相关的操作,都被采集且可被验证。
工作原理
三个字段让每一行成为证据
audit_logs 表中的每一行都带有三个保护字段。表本身平平无奇;纪律在于每次插入时被计算并签名的内容。
sequence
按租户单调递增的整数。序列号出现缺口就意味着某行被删除了,验证器会标出第一个缺失的编号。
prev_hash
指向上一行的 row_hash。链断了就意味着有行被重排或外部插入。
row_hash
对 {tenant_id, sequence, prev_hash, actor_id, action, resource_type, resource_id, payload, created_at} 的规范化序列做 HMAC-SHA256,使用 AUDIT_HMAC_KEY 签名;该密钥由应用程序持有,不在数据库里。
三个篡改信号
我们能检测什么,以及怎么检测
| 攻击 | 我们检测到的现象 | 检测方式 |
|---|---|---|
| 行篡改 | 已存储的 row_hash 不再等于重新计算的 HMAC。 | 验证器沿链行走,从每行的规范化负载重新计算。 |
| 行删除 | 出现序列号缺口,且幸存行的 prev_hash 不再对应其真实前驱。 | 序列检查加链式检查;任一项都足以发现问题。 |
| 行插入或重排 | 被插入的行无法引用一个真实的 prev_hash,链在被插入行或其后的行处断裂。 | 链式行走在第一个无效 hash 处发现不连续。 |
纵深防御
三层分离,是刻意为之
只追加的强制
PostgreSQL 触发器拦截一切对 audit_logs 的 DELETE。只有在已设 SET LOCAL app.audit_retention_cleanup = on 的事务里才允许删除,而这个标记只由保留期清理流程持有。SET LOCAL 的作用域局限于事务,因此提交或回滚时会自动清除,无法泄漏到后续语句。
密钥分离
HMAC 密钥在应用配置里,而不在数据库里。即便数据库被以只读方式攻陷,也无法伪造有效行。即便写入级别的数据库被攻陷,下一次验证器运行也能发现:它使用 HMAC 密钥重新计算每一行,并把失败作为独立的审计事件呈现。
验证器独立
验证器使用应用持有的 HMAC 密钥对每个租户的链做端到端检查。它的结果本身就是审计链上的条目,因此一旦被停用或跳过,在发生的那一刻即可见。
记录了哪些事件
每一个触碰运营记录的事件
- 01 所有认证:登录、退出、MFA 注册、修改密码、找回密码
- 02 所有授权:角色变更、权限授予、API 密钥的创建与吊销
- 03 所有敏感管理操作:租户设置、保留期变更、RLS 上下文覆盖
- 04 所有工单签字,附签字载荷的 SHA-256
- 05 所有订阅与计费事件:webhook 接收、套餐变更、客户门户会话
- 06 所有接入源配置变更
- 07 所有 SSO 配置变更
- 08 所有导出:CSV、PDF、定时报表派送
- 09 所有批量删除与数据主体被遗忘权行使
保留期
2,555 天。与运营保留期相互独立。
RetentionConfig.AUDIT_LOG_DAYS 把链保留七年。Tenant.data_retention_days 的设计就是让运营数据独立可控,从而机器遥测可按可配置窗口清理,而审计链继续覆盖 SOC 2 CC2.2 证据期与财务相邻的保留规范。
合规映射
审计链回答了什么
SOC 2 CC6.1 与 CC7.2
防篡改记录与变更管理证据链。审计链就是您的 SOC 2 审计员在观察期内逐条复核的对象,以满足 CC6.1(逻辑访问)与 CC7.2(系统监控)。
ISO 27001 A.12.4
事件记录与操作员日志完整性。每一次管理员操作与异常都落到链上,带有时戳、归因,以及被操作的规范化载荷。
FDA 21 CFR Part 11 §11.10(e)
为电子记录提供安全、计算机生成、带时戳的审计轨迹。每个签名事件都具备可归因、同步、原始、准确与加盖时戳的属性,支持客户进行 21 CFR Part 11 的验证工作。
GDPR 第 30 条
处理活动记录。您的 DPO 可按需提取按租户切分的审计切片。audit_logs 中的个人数据仅限于用户标识与 IP 地址;更丰富的负载只携带重建事件所需的最小上下文。
欧盟 NIS2 指令
事件调查证据。成员国 CSIRT 与您自己的取证团队可以精确重建谁在什么时候做了什么,且这条链的完整性不依赖对 DBA 的信任。
验证器
被审计的审计验证
链验证以租户的 HMAC 密钥为每一行重新计算,返回带行数的 PASS,或在第一个断裂序列号上返回 FAIL。验证器的结果本身就是审计链上的条目。
节奏
当前通过管理员验证器端点按需触发。按日的定时验证将随平台 GA 一同上线。
结果
PASS 带行数,或者 FAIL 带链断裂处的第一个序列号。涉事的行本身作为证据保留。
源码
验证器的源码可供您的审计员逐行复核。我们会陪同到现场。
为什么这强于"我们有审计日志"
标准审计日志撑不过一个铁了心的操作员
普通审计表
易于追加,但可被修改。任何拥有数据库写权限的人都能改一行、删一行或插一行,而且没有任何数学信号表明此事发生。
WORM 存储
对删除有用,对插入或重排作用有限。存储层完整性抓不出那条本就不该存在的行。
Hash 链式日志(即本方案)
数学意义上的防篡改。在行级别检测篡改、删除与插入,以父级 hash 作为完整性锚点。
常见问题
如果某条 hash 不匹配,链会怎样?
验证器返回 FAIL,并指出链开始断裂处的第一个序列号。我们不会删除该行,证据要留下。失败本身作为独立的链条目被记录下来,并通过平台的事件响应路径浮现。
数据库管理员能否对链做手脚?
不能,除非他同时 (a) 绕过只追加触发器,(b) 拿到位于数据库之外的 HMAC 密钥,并且 (c) 为受影响租户重写之后的每一行。这三步本身都会留下取证痕迹。
能否为了 GDPR 被遗忘权删除某行?
audit_logs 中的个人数据已最小化为 user_id 与 IP。对用户记录行使被遗忘权不会移除审计历史,因为该处理建立在第 6(1)(c) 条法律义务与 (f) 合法利益之上。如果您的 DPO 要求,我们可以对 user_id 引用做假名化处理。
链会拖慢写入吗?
每次插入计算一次 HMAC,并读取一条前驱行,两者均有索引。在典型工作负载下成本可忽略,且验证器在主路径之外运行。