从三孔插座到代码中的安全设计原理

梦康 2023-09-23 18:36:33 245

差点跑路

前几日和几个老朋友小聚。一朋友跟我们吐槽下面小弟干活不仔细,差点让自己跑路。

事情大概是这样,项目是 java web 工程,需求是增加一个反向代理的 nginx 。非常小的一个改动,项目发布上去之后,系统直接 502,不过还好有监控,快速回滚恢复了。

几个老朋友纷纷加入讨伐大军。
“不是分批发布吗,怎么没暂停了线上看看?”
“发布这么重要的事,你还交给小弟?我都是自己发布。”
“哎,我刚刚还表扬他干得不错,没想到给我捅娄子。预发看过了没问题,因为改动特别小,线上就大意了。中途居然不验证下,就恢复发布了……”

作为加班写 bug 科代表、提桶跑路创始人,我没有说话,我觉得他们在骂曾经的我。

我好奇的点

  1. 为什么预发测试是 ok 的,线上就直接 502 了呢?

因为预发系统和线上系统域名配置不一样,还有一些反向代理的域名也不一样,所以 nginx 配置预发线上是两个文件,测试了预发不等于线上就 ok,恰巧这次线上的配置文件就在更新的时候破坏了完整性。

  1. 线上全部 502 基本就是 nginx 配置有问题,为什么健康检查没拦截?

后来那位朋友给我的反馈是因为 nginx 反向代理的 springboot 应用,健康检查的是 springboot 的 http endpoint 而不是 nginx 对外提供的。

三孔插座原理

我记得我上大学时,老师给我们分享了一个安全上的案例,大概是说飞机上有两个很关键的插孔,每次起飞前都要人工区检查插得对不对,99.99%的时候都检查 ok,但是就是有一次,尽管是多人检查,就像我们 code review 一样,还是插反了。

最后的解决方案是什么呢?那就是 A B 两个插头和插孔设计的尺寸不一样。这不就我们的三孔插座么?只要你使用了三孔插座,火线、零线、地线的位置就不会出错。

《安全原理》的设计无处不在。和飞机事故一样,犯错的人不一定要是关键,今天他不犯错,换个人,发布频繁,肯定会再次踩坑。

解决问题的思路应该是从架构设计的角度是杜绝拦截人为因素导致的风险,以上面的案例

  1. nginx 配置最大化的合二为一,非同质化的可变配置可以拆分成两个,预发和线上一套的域名映射都可以通过 map 的方式进行管理;
  2. 健康检查的链路完整性,避免出现这种掩耳盗铃的健康检查卡点。

发散下

其实这个原则无处不在,有时候给兄弟们 review 代码的时候,对于一些接口的定义上,我质疑点是你这个接口定义完了,别人有没有可能用错?如果作为 code review 的人,看了你的方法定义,我都不知道怎用,还需要去看你文档,甚至有的需要去看你实现,那么这段代码是非常危险,非常糟糕的。

举个几个例子,一个算数工具类,相除

divide(int a, int b)

最好是注释是代码本身,如果能在参数定义上告诉调用者,是 a/b 还是 b/a 就尽量不要在注释和接口文档里告诉用户。

实际工作中,还遇到过一些分页方法,除了传 page,pageSize,还有个参数是 ids。我问这个 ids 是是查询这个分页的时候将其排除吗?结果被告知是这个方法是要么分页,要么根据 ids查询,这又涉及另一个外一个话题了,一个方法只做一件事,就不展开了。

这种方法是灾难性设计,最终出 bug 了,是定义者的问题还是调用者的问题呢?

希望大家在大到架构设计,小到函数定义,变量定义,都可以想想《三角插座》原理。