The Hierarchy of Controls (or how to stop devs from dropping prod)

Hillel Wayne

控制层级(或如何防止开发人员误删生产环境)

原文由 Hillel Wayne 发布,订阅该博客

前几天,一位机械工程师向我介绍了控制层级(Hierarchy of Controls,简称 HoC),这是工作场所安全中的一个重要概念。1

(来源)

为了保护人们免受危害,系统设计者应当尽可能采用最有效的控制措施。也就是说,消除优于替代,替代优于工程控制,以此类推。

我们能把控制层级应用到软件工程中吗?软件环境与物理环境不同,但或许其中有些思想值得借鉴。接下来,我们就以我还是初级开发时引发的一次生产故障为例,来尝试套用 HoC。

问题

大约十年前,我当时正尝试调试一个生产环境问题。我同时开着一个已 SSH 连入生产环境的终端和一个本地开发终端,两者并排显示,结果切错了窗口,执行了错误的查询。

就这样,我上了一堂新课:如何从备份中恢复生产数据库。

在工艺安全领域,危害是指任何可能导致伤害的因素:梯子让人可能坠落,液压机让人可能压伤手指。就我的情况而言,危害可能就是那个拥有不受限权限的生产环境终端——它让生产数据丢失成为了可能。而具体的伤害则包括直接删库(就像我干的那样)或在库中执行删除查询。接下来我会用这两种伤害来讨论不同类型的危害控制。

先说几点前提

控制层级原本是为了保护人不受机器伤害而设计的,而不是为了保护机器不受人伤害!我发现大多数概念迁移过来还算贴切,但在个人防护装备(PPE)这一层上遇到了一些问题。

另外,对于某项措施究竟属于哪一类,也有很多可争辩的空间。在写作本文的过程中,我也在试着厘清各个层级之间的本质区别。以下都只是我作为一个完全新手的个人理解。

最后,控制层级关注的是如何预防伤害,而非伤害发生后的恢复。因此,像“定期备份数据库”“事后进行故障复盘”这类软件最佳实践,并不属于这个层级的讨论范畴。

控制措施

以下所有引文均来自OSHA 控制层级工作表

消除

消除是指确保危害不再存在

消除是预防事故最直接的方式:根本不让危害存在。OSHA 的各种材料都用了同一个例子:想减少坠落伤害,就别让工人从事高空作业。在我们的例子里,消除危害就意味着去掉生产环境,或者干脆不要数据库。

这两种做法都不太现实,大多数关于控制层级的资料也都会很快指出,真正的消除往往是不可能的。我们之所以会接触危险材料,正是因为它们对流程至关重要。“消除”这一层最大的价值,或许在于提醒我们自问:这个危险的东西真的有必要存在吗?必要的危害无法消除,但非必要的可以。在 2020 年被正式淘汰之前,Adobe Flash 一直是最大的安全漏洞来源之一。想要保护自己,最简单的办法是什么?卸载 Flash。

替代

替代是指更换材料流程以降低危害。

与消除不同,替代保留了危害本身,但降低了它的危险程度。如果危害是有毒农药,我们可以换成毒性更低的;如果是噪音大的机器,就换成更安静的;如果危害是内存不安全的编程语言,那就改用 Rust。

针对我的问题,我能想到几种可能的替代方案。我们可以把生产终端替换成一个权限更弱的终端。比如,让某个“生产”服务器只能访问数据库的只读副本,这样删除查询不会起作用,即使删库也不会丢失数据。另一种方案是采用不可变记录系统,比如事件溯源模型。这时“删除数据”实际上变成了“向数据库追加一条删除记录”。意外的删除只需再追加一条“撤销删除”记录就能轻易恢复。

工程控制

工程控制通过防止危害与作业人员接触来减少暴露,但仍能让人们正常完成工作。

工程控制并不会降低危害本身的危险性,而是通过额外的物理设计来降低事故发生的风险和严重程度。实现方式有很多:可以减少人员接触危害的必要,可以降低危害触发事故的概率,也可以让事故更不容易造成伤害。

SawStop™ (来源)

相比消除和替代,工程控制在创意发挥上有更大的空间。以下是一些思路:

  • 如果监控和可观测性做得更好,我可能根本不需要登录生产环境。
  • 通过更完善的权限策略,可以禁止在该环境中删库,或要求必须使用特殊的开发者密钥才能执行此类操作。
  • 或者,初级工程师本来就不该拥有生产环境的访问权限。如果我想调试什么,就必须请更有经验的人来操作。
  • 自动登出可以避免让一个空闲的生产终端一直开着,等着被我不小心通过 Alt-Tab 切进去。

不同的工程控制效果也大不相同。一个出了名的“弱”控制就是确认弹窗:

$ ./drop_db.sh

This will drop database `production`.
To confirm, type y: [y/N]

问题在于,如果我在本地环境中频繁执行这个操作,就会形成按 y 的肌肉记忆,等到在生产环境中也这么一按,这一天就毁了。

一个出了名的“强”控制则是“输入全名”确认框:2

$ ./drop_db.sh

This will drop database `production`.
To confirm, type `production`:

即使形成了肌肉记忆,记住的也是输入 local,这反而会阻止操作。如果你尝试在 GitHub 上删除一个仓库,就能看到一个真实的例子:

OSHA 举的一些工程控制的例子,在我看来倒更像是替代,反之亦然。我区分两者的经验法则是:工程控制可能会失效,而替代不会。如果权限配置错误,其实根本拦不住我删库怎么办?而如果环境只能访问只读副本,我就不可能凭空毁掉主库。希望如此吧。

不过,这个经验法则并不完美!比如把 C 换成 Rust 属于替代,但 Rust 的某些保障也是可以通过 unsafe 绕过的。

管理控制

管理控制通过改变工作方式或通过提供相关流程、培训或警告来为作业人员提供更多信息

工程控制改变的是技术,管理控制改变的是人。那些改变人与技术交互方式的控制,可能归入哪一类都说得通;我就曾为“输入全名”确认框到底算管理控制还是工程控制争论了很久。两种说法其实都有道理。

关于管理控制,这里有一些思路:

  • 公司规定初级工程师不得连接生产环境
  • 播放培训视频,展示删库是多么容易发生
  • 一次只打开一个终端窗口
  • 要求只有在与另一名开发者结对时才能连接生产环境
  • 减少加班和冲刺让工程师不再那么睡眠不足
  • 定期对运维问题进行推演。

OSHA 还把“自动告警”归为管理控制的一种。比如“登录生产环境时自动在 Slack 上发一条消息”就属于此类。

在层级中,管理控制排在工程控制之下,有几个原因。其一,工程控制是内嵌在系统中的,而管理控制是社会性的,需要对每个人进行培训才能让大家遵守。其二,在我见过的所有控制层级例子中,绕过工程控制需要费力,而遵守管理控制才需要费力。当你匆忙、健忘或注意力不集中时,就可能不会去遵守它们。

有些管理控制可以升级为工程控制。比如,“初级工程师不得 SSH 登录生产环境”这条公司规定属于管理控制,依赖每个人自觉遵守;而“初级工程师根本没有相应的 SSH 密钥”这种配置,就属于工程控制。

个人防护装备

个人防护装备(PPE)包括用于保护作业人员的服装和器具。

这是最低一级的控制:为人员提供装备以抵御危害。PPE 可以降低受伤的风险(穿着反光背心,被叉车撞到的概率会降低),也可以减轻伤害的严重程度(安全帽不会阻止物体砸下来,但能缓冲冲击)。

我认为 PPE 是在软件领域最不适用的一层。首先,控制层级的初衷是保护人,而在软件中我们想保护的是系统。那么,软件中的 PPE 究竟是人穿戴的、用来防止对系统造成损害,还是系统穿戴的、用来防止人带来损害?一个“人的 PPE”的例子可能是:生产终端用红色背景,开发终端用蓝色背景。而我能想到的所有“系统的 PPE”例子,严格来说都可以算作工程控制。

其次,PPE 之所以不是工程控制,是因为工程控制是对危害本身进行改造,而 PPE 是介于人与危害之间的第三方。但在软件中,介于人与软件之间的任何东西本身也是软件!即便是“用 Postman 而不是 curl”这样的例子,也更像是工程与管理的混合体,而非真正的 PPE。

我能想到两个 PPE 更有意义的场景。一是安全领域,安全浏览器、2FA、密码管理器等都可视为 PPE。二是用于缓解软件开发者常见职业伤害的 PPE,比如腕管综合征、背痛和视疲劳。这些场景之所以“行得通”,是因为它们确实涉及人的受伤,而这正是控制层级最初要解决的问题。

PPE 在层级中排在管理控制之下,是因为员工需要纪律和培训才能有效使用 PPE。在现实世界中,PPE 往往笨重、不舒适,而且 90% 的时间里其实并没有在保护你(因为你本来就不处于危险中)。一篇关于建筑事故的论文发现,在所研究的事故中,65% 的受伤工人当时并未佩戴 PPE。要最大化 PPE 的效益,就必须对人员进行培训并强制执行,而这本身就意味着已经需要有管理控制作为前提。

关于控制层级的其他说明

控制措施应当组合使用

层级越高的控制措施,在消除危险方面越有效,但也越难实现。层级越低的措施效果越弱,但更灵活、成本也更低。

软件天生擅长工程控制

这个想法我还在仔细琢磨,但大致是这样的:在现实世界中,人们通过“自然界面”与危害交互,基本上就是作为空间中的一个物体去接触它。如果我在操作液压机,默认情况下我就能把手伸进去。我们需要向系统中额外添加物理实体来防止伤害发生,或是培训人们如何正确使用这些物理实体。

而在软件中,所有界面都是人为构建的:危害源于我们主动添加的可能造成损害的能力。因此,我们更容易以另一种方式构建界面,从而削弱造成伤害的能力,或是将管理控制以约束的形式强制执行。

这让我想起之前在一家公司时的经历。我们的使用模式决定了在非高峰时段部署新版本最安全。“只能在非高峰时段部署”是一项管理控制。于是我们在部署脚本里加了一行检查:判断当前运行时间,如果是高峰时段就直接报错。这就是把一项管理控制转化为了工程控制。3

而且,现实世界中的工程控制成本高昂,这也是人们会选择管理控制和 PPE 的一个重要原因。但软件的修改速度远比物理系统快、成本也远比其低,这让工程控制在软件中变得更加有效。

(我认为对替代措施来说,也有类似的道理。)

控制措施本身也可能成为危害源

如果你去看我上面链接的OSHA 工作表,会在这里看到一个有趣的部分:

(来源)

任何一种控制方法都可能给工作场所带来新的风险。大量的管理类告警会导致告警疲劳,让人错过真正关键的提醒。禁止人员进入某个仓库,可能会迫使他们绕道经过另一种危害。反光背心也可能被机器卷入。必须从整体上考虑整个系统的安全性:局部的安全改进可能会在别处引发问题。

在软件中我们也能看到类似情况。Lorin Hochstein 有一个演讲,讲的就是 Netflix 的许多故障恰恰是由那些本想保护系统的软件所引发的。

我的那些控制措施又会如何带来新的危害呢?

  • 用追加式数据库进行替代,可能需要大量的重新培训和软件改造,从而带来更多出错和产生新缺陷的空间
  • 严格的访问策略可能会在我试图修复线上问题时拖慢进度,让问题变得更严重
  • 过多的管理控制可能会让人“走过场”、机械地执行,反而不再对潜在危险保持警惕。

我发现,识别控制措施可能带来的新危害,要比识别已有的危害困难得多。


以上就是控制层级的精要。我觉得这是个很棒的想法!

如果你喜欢这篇文章,欢迎订阅我的Newsletter!我每周都会在上面发布新的文章。

我为企业提供形式化方法培训,帮助软件开发变得更快、更便宜、更安全。了解更多请点击这里


  1. 有些地方把它称为危害控制层级(HoHC),但像 NIOSH 和 OSHA 这样的机构则称之为 HoC。我觉得这就像我们说“解释型语言”而不是“解释型编程语言”一样:面向专业人士写作时,主题是默认明确的,但面向大众时就需要说得更清楚。[返回]
  2. 我没有找到这种控制措施的通用名称。我在 Discord 上问过,大家的叫法各不相同。[返回]
  3. 是的,当时是有 --force 参数的。[返回]

本文章由 muse-spark-1.2-contributor 进行翻译

评论