

—
“恶意”这俩字,不仅在现实世界,在软件世界也是满满的。

来源 / 凡泰极客 (ID:finogeeks)
作者 / F1n0Geek
作为企业主管,你是否知道你的工程师随便编写的一个返回"Hello World"这么简单的微服务,后面居然依赖着上百个软件包、5万多行代码?你又是否知道这些软件包在开源世界的来源、它们能带来什么样的安全风暴?
某天,某银行被盗取大量数据、遭受巨大经济损失,并遭到消费者集体诉讼和监管天量罚单。
原因是技术系统用了某个开源代码包,该开源代码包原来是一个遭黑客污染过植入了后门的有毒组件。不小心误用这个代码包的,是IT部门某基层小弟所为。
新闻爆出,CIO一脸蒙圈,卒。
上述情形目前...暂时是,虚构的。会不会发生呢?概率不小。罪魁祸首,叫软件供应链攻击。这个问题的潜在风险,大到什么程度呢?
根据Gartner,至2025年全球有接近一半的企业会遭遇到。后果?对于企业数字化是巨大的爆雷,对于企业的技术高管,参照上述“CIO”。
今年初白宫都不得不召集会议讨论Log4J,拜登都得知道Java,是不是莫名魔幻?
但对于码农来说是不是略感自豪 - 相当于所在银行行长、公司CEO深入IT基层,亲自了解一个用Java写的、通常埋在十八层代码之下的日志工具,“并作出重要指示”的既视感...

接下来,本文尝试“贴心”的替百忙中未必有空学习了解“软件供应链”的领导们作一点点学习,总结一些科普性内容,描述一下问题、归纳一下挑战、整理一些概念、总结一些策略,但是难以高度浓缩,行文依然略长,感谢耐心读完的看官。
01
什么是软件世界的
“Supply Chain”
各行各业都有供应链,就像一家车厂,它生产整车,不可能从轮胎、车窗玻璃、车门把手做起。
正如物理世界的任何产业都有自己的产业链、供应链一样,在虚拟世界的软件业也一样,任何终极软件产品,都用到很多的虚拟“零部件” - 以代码库形态存在的组件、框架、工具,而且这些“零部件”本身也高度依赖其他“零部件”。
以银行为例,消费者、业务部门使用的软件,由银行的IT部门提供。但这些软件仅仅有小部分是IT自己开发的,更多是从第三方供应商采购。
而且,无论软件是自己开发,还是由系统集成商或软件厂商开发,都依赖于大量的更加基础的软件组件、项目、工具。
在这个时代,也许除了国防军工领域的软件,几乎不可能存在每一行代码都100%由自己开发的软件 - 工作量不允许、经济规律也不符合。
实际上,随便一个软件,按绝对代码行数来算,一个不小心有一半是第三方的,例如在一些微服务里,自己的代码只占10%,也一点都不奇怪。
02
“Hello,World”
后面可能有几万行代码
每一个软件工程师在学习一门新技术的时候,都会从最简单的“Hello,World”程序开始。
可是世界上确实没有免费午餐 - 表面越简单的东西背后的代价可能也越大。
例如,你想用JavaScript+Node.js开发一个只能对网络请求返回“Hello,World”回复的微服务,你决定采用一个最轻量简约的微服务框架ExpressJS - 一动手的瞬间,你的开发工具npm就给你从上游拉取100+软件包 - 54,000行代码拿去,不谢。
如果你想再玩点高级功能,例如添加一个MVC框架(例如Locomotive),你的这个“微”服务实际代码量马上升至220,000行 - 不好意思,起步价,哪怕你只写一行代码。
上述情况不限于JavaScript/Node.js,对其他语言环境也一样。
现在的计算机语言,Java、Golang、Rust、Ruby、Python、Julia... 不可胜数,都自带包管理工具、“零部件”中央仓库、以及庞大的开发生态。

上图为每年各语言新增软件包数量,其中npm生态2019年新增超过30万个。
上图缺乏新兴语言如Golang、Rust等的数据,但这些语言生态中的“零配件”数量的快速增长,对于开发工程师来说,都是可直接感知的。
相比之下,Java、Ruby的组件生态增长率较低,但很大程度因为它们已经增长了近二十年,基数已经极其庞大。
03
根源:全球化的开源大生产
开源软件运动如火如荼的进行了二十四五年(如果从1998年2月3日在硅谷的一次会议中首次提出“open source”一说开始算 - 当时互联网先驱Netscape刚刚宣布开放他们的浏览器源码),极大程度的改变了软件业的面貌。
当前全球企业超过90%直接或者间接甚至在无意识中使用了开源技术。
可以说,今天我们开发的几乎每一套软件,都是全球化生产协作的结果。
例如无数Java工程项目几乎必然依赖的基本日志工具Log4J,最早在二十一年前由瑞士程序员Ceki Gülcü个人独立开发提供;多少互联网公司牛哄哄声称自主研发的浏览器,内核来自谷歌的Chromium;再有技术能耐的银行证券金融巨头的网站和用户端,也离不开React、Vue、Electron、Qt等前端基础框架;马斯克的SpaceX火箭和Tesla汽车,操作系统和软件工具也离不开Linux和GCC编译器。
计算机领域有一句格言,“"All problems in computer science can be solved by another level of indirection",被称之为“软件工程基本定理”("fundamental theorem of software engineering"),分层(layering)- 这就是计算机领域解决问题的一个典型的、最根本的思维方法。
这很好的解释了软件供应链的需要 - 从硬件、网络层、操作系统、虚拟化管理软件、中间件、开发工具、开发框架、前端组件、辅助代码库,到编译器、解析器、运行时,层层叠叠,每一层面对自己的领域、解决好自己的问题,最终到达面向终极用户的应用软件,再大的公司、再牛的工程师,都难以自己包打天下。
全球化和开源运动相辅相成,而软件技术人则在这个开源世界的“乌托邦”相互成就。
但问题也就来了:在你的供应链上的各种“零部件”,不仅面临“依赖地狱”的挑战,你甚至还无法知道这些零部件的依赖传递关系、不了解它们的作者和来源。
04
虚拟世界的供应链
远比物理世界的无序凌乱
一套软件的供应链,如果把它画出来,很有可能是这样的:

产品P,依赖于子系统/组件/基础库A、B、C、D、E,其中A又依赖于F、G、H...,B则依赖于G、H、X、Y、Z,而G又可能依赖于H... 有些情况下,还可能出现循环依赖 - A依赖于B,B依赖于C,而C依赖于A... 这就是传说中的dependency hell(“依赖地狱”)。
可是这还只是复杂性的一个维度!每一个软件零部件还有不断变化中的版本:产品P版本1.2必须依赖于零部件A的版本2.5.1、而该版本又必须建立在零部件B的版本1.0.3基础上...
某些物理世界的产业链中,产品和零配件也可以类比“版本”一说,例如2022年版的某型号汽车,可能采用2021年版的某汽配件X,但该型号汽车的2021年版则可能采用X略有规格变化的更早期版本...
但是,物理世界的零配件,不可能像虚拟世界的零配件那么频繁的发布版本 - 由主版本、次版本、修订号等繁复的组合而成(见“语义化规范”);而且也不可能像软件组件那样存在“循环依赖”的风险。
可以说,今天的绝大部分开发者,根本搞不清楚自己的软件供应链里都有谁,因为他们所用到的很多技术组件都是他人提供的,这些“上游”组件本身又用了什么其他组件、工具,开发者们往往不知道也不关注。
可以说,我们在生产一个软件产品的时候,很可能“非自愿”的集成了大量“上游”、“上游的上游”、“上游的上游的上游”的半成品。
相比之下,物理世界的产业链没有这种不可控的问题存在 - 特斯拉、比亚迪的每一辆车,用到的每一个零配件的生产商都是100%确定的。
05
软件供应链的四大风险
企业的基层开发人员,往往并不关注(也难以有能力和工具去评估)自己的代码所间接、被动引用的第三方代码包的来源、质量、安全风险。
传统企业的IT主管、CIO们,焦点在业务科技赋能、企业科技战略布局等宏观事情上,或者在生产环境质量保障、紧急情况的灭火上,越是高级管理层,越有可能已经远离一线的战阵,十年以上技术管理资历的管理层,很可能在充当码农手写代码的当年,像npm、cargo、gradle之类今天无处不在的“软件零部件”管理工具(package manager)还没有成型或出现,对一个“Hello,World”微服务后面可能隐藏着几万行代码这种情况,未必有亲身体验,对其中的风险了解不足,基层工程师采用一个开源包只是一行命令一个回车的举手之劳,足以让坐在IT金字塔顶层的CIO们坐在火坑上而不自知。
对于企业来说,当前软件供应链起码面临四类风险:

06
Supply Chain Attack
软件供应链攻击

07
软件供应链里的“康帅博”
Low但致命的那种

推荐阅读
《ToB月报丨融资总金额达56.54亿;阿里、360、腾讯加快企业服务步伐》
