一次线上 数据脱敏 故障的复盘记录
事情过去几天了,想想还是记一下。不是那种“高度重视、举一反三”的复盘,就是给自己和后来人提个醒:脱敏这种看起来不起眼的逻辑,真出事的时候一点都不小。
事情是怎么发现的
下午两点多,客服在群里问了一句:“刚导出的用户列表,手机号怎么是完整的?”我当时还没太当回事,以为是谁把测试环境的文件发过去了。结果点开她发的截图一看,还真是明文手机号,中间四位没打星号。再一问,运营那边也导了两份,同样的问题。
我头皮一麻,赶紧去看脱敏服务的日志。服务本身没挂,健康检查全是绿的,错误日志也少得可怜,只有几条 mask rule miss 的 warning,混在一堆访问日志里,平时根本不会有人注意。说白了,它没报错,它只是把不该放过的字段放过去了。
先止损,再说别的
第一反应是回滚。前几天刚上了一个脱敏规则重构版本,把原来写死在代码里的正则和字段名匹配改成了配置化。当时觉得挺优雅,现在看就是给自己挖坑。
回滚命令倒不复杂:
kubectl -n data-mask rollout undo deploy/data-mask-api
kubectl -n data-mask rollout status deploy/data-mask-api
回滚完之后,我先让客服和运营把已经导出的文件全部删掉,能撤回的撤回,撤回不了的就登记一下。另一边让负责同步任务的同事把下游测试库先停掉,别继续往里灌了。后来统计了一下,大概两个小时里导出了 17 份文件,涉及 3200 多条手机号,测试库那边也有几张表进了明文数据。量不算特别大,但足够让人难受。
根因其实不复杂
问题出在新规则只做了精确字段名匹配。旧代码里有一层兜底逻辑,字段名只要包含 phone、mobile、tel 这类关键词,就会走手机号脱敏。重构的时候我觉得“精确匹配更可控”,就把兜底去掉了,规则大概长这样:
rules:
- field: phone
type: mobile
keep_prefix: 3
keep_suffix: 4
- field: id_card
type: idcard
keep_prefix: 6
keep_suffix: 4
看着挺清楚,对吧?问题是上游字段名根本不统一。有的叫 phone,有的叫 mobile,有的叫 contact_phone,还有一个嵌套在 user.contact.mobile 里。新规则只认 phone 和 id_card,其他一律原样返回。更坑的是,脱敏服务对嵌套 JSON 只处理第一层,user.contact.mobile 这种压根没进规则匹配。
排查的时候我用日志里的字段名做了个统计:
grep -i "mask rule miss" mask.log \
| awk '{print $6}' \
| sort | uniq -c \
| sort -nr
排前面的就是 mobile、contact_phone、user.contact.mobile。看到这个结果,基本就不用再往下猜了。
几个让我后怕的细节
第一,测试数据没覆盖到这些字段名。我们造测试数据的时候习惯用 phone,因为看着顺眼,结果线上真实字段名比测试数据野多了。
第二,上线前没有做新旧版本双跑对比。其实只要把同一批样本分别丢给旧版和新版, diff 一下输出,很容易就能发现新版少脱了哪些字段。当时赶时间,觉得“规则都配了,能有多大事”,就跳过了。
第三,监控只监控了服务存活和接口耗时,没有监控脱敏命中率。如果加一个 mask_hit_rate 指标,或者对“未匹配任何规则的敏感字段”打点,这事可能十分钟内就能发现,而不是等客服来问。
后来补了哪些东西
紧急处理完之后,我们补了几件事,不复杂,但都是能落地的:
- 把字段名枚举扫了一遍,所有包含
phone、mobile、tel、idcard、id_card、email的字段全部加进规则,并且恢复关键词兜底匹配。精确规则优先,兜底规则保底。 - 对嵌套 JSON 改成递归处理,不再只扫第一层。
- 加了一个默认拒绝策略:导出任务里如果出现未匹配任何脱敏规则的字段,并且字段名命中敏感词库,直接拒绝导出并告警。宁可错杀,不能放过。
- 上线前强制双跑对比,脚本大概这样:
python compare_mask.py \
--old old_mask_result.json \
--new new_mask_result.json \
--fail-on-diff
- 监控里加了脱敏命中率和未匹配敏感字段数,阈值先设得保守一点,后面再慢慢调。
最后
这事说到底不是什么高深的技术问题,就是重构的时候把一条不起眼的兜底逻辑弄丢了,测试没覆盖到,监控也没兜住。脱敏服务平时不出声,一出事就是直接往外吐明文。以后再做这种“看起来只是配置化”的改动,我大概会先问自己一句:旧代码里那些丑但有用的兜底,是不是被我顺手删了?
就这些,记录一下,免得下次又踩。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。