3.4 依赖管理
每一种编程语言都有不同的管理第三方代码的方法。大多数语言会提供一个中央的包管理仓库,开发者可以把他们的代码上传到这里,也可以从这里获得别人的代码(Python有PyPI,Node.JS有npm,Ruby有RubyGems,Perl有CPAN,Rust有Cargo,等等)。在这一点上,Go有些与众不同:它直接从依赖的源代码仓库中导入和获取依赖。例如,发票应用导入了一个名为github.com/gorilla/mux(用于简化HTTP请求的路由)的包,这个包从它的原始代码仓库下载(链接3.16),而不是从包仓库下载。
无论用哪种方法管理依赖,这个过程都有下面两个缺点:
•可用性损失——依赖的源可能下线,也可能被开发者删除,或者尝试构建应用的服务器无法访问互联网。
•完整性损失——源代码可能被替换成了恶意代码。
因此,开发人员通常会将依赖锁定为特定版本,有时还会下载一份依赖的副本,并和项目存放在一起。这种实践被称为vendoring,其优势在于不用互联网连接就可以完成代码构建,因为所有的依赖都存储在本地。
依赖锁定和vendoring解决了可用性和完整性问题,但却迫使应用要定期更新本地的副本。如果没有合适的工具,开发人员常常会忘记更新,这将导致应用依赖过时代码,从而暴露出漏洞。
发票应用是一个极度简化的例子,尽管很小却也依赖了一些包,而这些包最好要及时更新。在这一节里,我们将首先讨论管理发票应用依赖的最佳方法,然后再探讨其他语言的解决方法。
3.4.1 Golang vendoring
有一些工具可以帮助管理Go的依赖:dep(链接3.17)、Godep(链接3.18)、Glide(链接3.19)、Govend(链接3.20),或者直接使用Go自带的标准vendoring支持。这些工具能帮助开发人员获取依赖的副本,并放到应用仓库中的一个名为vendor/的文件夹下。本节将使用govend来提供发票应用的依赖,并在CircleCI中检查这些依赖的状态。
依赖列表将被写到vendor.yml文件中,一起写入的还有每个依赖的提交哈希,这样可以轻松地找到提供的是依赖的哪个版本。发票应用的vendor.yml示例见代码清单3.29。
在vendoring完成初始化之后,让依赖保持与时俱进就成了首要任务。govend-u会获取依赖的最新版本,并更新到vendor.yml文件中。但这还需要手工操作,并且你极有可能会遗忘。
更新依赖应该始终被当成一次代码变更来对待,并且由开发人员来执行;但是,你可以在CircleCI的配置中加上几条命令,通过CI检查依赖的状态。
如果有可用的新版本依赖,govend-u会在CI执行的过程中发现它们,因为存在待定的变更,触发git diff会以返回码1退出。非零的返回码将导致CircleCI构建失败,并将问题通知给开发人员。这种方法和本章开头使用的基线扫描很像,每次应用(代码)在发生变更的时候都可以检查是否存在过时的依赖。
这种方法有一个缺点,如果应用(代码)没有发生变更,检测就不会进行。这对于处在维护模式的软件来说是个问题,它们好几个月才会发生一次变更。在第4章中,我们将讨论强制应用和基础设施定期重建的技术,它们将有助于解决这个问题。
3.4.2 Node.js的包管理
Node.js采用的是另一种不同的依赖管理机制,它依赖的是名为npm (Node Package Manager)的包管理系统。Node.js应用将依赖定义在package.json文件中,这个文件同样可以锁定依赖版本。
Node包管理器
npm是由Node.js的生态系统发展而来的,但是可以用于任何JavaScript应用。即便是没有使用Node.js,前端开发人员通常也会使用npm来管理JavaScript依赖。
和Go一样,有好几种工具可以管理Node.js依赖,但是它们却用不同的方式来检查存在漏洞的包。Node安全平台(Node Security Platform)提供了nsp,它可以检查项目的安全状态,nsp使用了多套已知安全漏洞的数据库来查找过时的及暴露出安全问题的包。代码清单3.31是对一个大型Node.js项目上的nsp执行的结果展示。
上面的输出结果告诉我们,项目中使用了一个叫作handlebars的依赖,它的版本设置成了2.0.0。但nsp知道这个早于4.0.0的包的所有版本都存在内容注入攻击的漏洞,并建议该项目应该将handlebars升级到更新的版本。
因为nsp是一个命令行工具,所以它可以很轻松地集成到CI平台中。在使用nsp时,开发者仍然需要手动更新依赖,但是其他一些像Greenkeeper.io这样的平台会在有可用更新的时候直接向项目代码仓库发起拉取请求。这是在项目长时间没有变更发生时防止依赖过时的一种方法。最终,同时使用nsp和Greenkeeper.io是保持Node.JS项目与时俱进的一个不错的方式。
3.4.3 Python requirements
Python使用的包管理系统和Node.js类似,叫作pip,它配置依赖的文件名为requirements.txt。开发者可以锁定这个文件中的依赖版本,这种方法常常被用来处理包的版本之间出现冲突的情况。
pip命令行工具提供了一个检测过时依赖的选项,其名字十分贴切,就叫作--outdated。它可以用来检查项目依赖的状态。代码清单3.32告诉我们在这个给定的项目中使用的一些包的版本,应该要更新它的requirements。同样地,这种检查可以用在CI中来跟踪过时的依赖。
然而,pip无法像nsp警告存在漏洞的包那样告诉我们,在过时的版本中是否存在安全问题。为此,链接3.21和链接3.22这样的在线服务提供了判断Python应用中是否存在漏洞的方法。图3.15展示了requires.io针对一个Python应用的检查执行界面,这个项目使用了存在漏洞的依赖。
图3.15 requires.io可以跟踪Python项目中存在漏洞的依赖。