依赖项管理

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

本文档介绍了应用依赖项以及管理它们的最佳做法,包括漏洞监控、工件验证、减少依赖项占用量以及支持可重现的构建。

软件依赖项是应用运行所需的软件,例如软件库或插件。当您编译代码、构建、运行、下载或安装软件时,可能会发生依赖项解析。

依赖项可以包括您创建的组件、专有的第三方软件和开源软件。您管理依赖项的方法可能会影响应用的安全性和可靠性。

有关实现最佳做法的具体细节可能因工件格式和您使用的工具而异,但一般原则仍然适用。

直接和传递依赖项

您的应用可以包含直接依赖项和传递依赖项:

直接依赖项
应用直接引用的软件组件。
传递依赖项
应用的直接依赖项功能所需的软件组件。每个依赖项都可以拥有自己的直接和间接依赖项,形成一个递归依赖项的递归树,所有此类依赖项都会影响应用。

不同的编程语言提供对依赖项及其关系的可见性。此外,在安装或部署软件包时,某些语言会使用软件包管理器解析依赖项树。

在 Node.js 生态系统中,npm 和 yarn 软件包管理器使用锁定文件来识别用于构建模块的依赖项版本,以及软件包管理器为模块的特定安装下载的依赖项版本。在 Java 等其他语言生态系统中,对依赖项自检的支持有限。此外,构建系统必须使用特定的依赖项管理器系统化地管理依赖项。

以 npm 模块 glob 8.0.2 版为例。您可以在文件 package.json 中声明 npm 模块的直接依赖项。在 glob 的 package.json 文件中dependencies 部分列出了已发布软件包的直接依赖项。devDepdencies 部分列出了 glob 的维护者和贡献者在本地开发和测试方面的依赖项

  • 在 npm 网站上,glob 页面会列出直接依赖项和开发依赖项,但不会指明这些模块是否也有自己的依赖项。

  • 您可以在 Open Source Insights 网站上找到有关 glob 的其他依赖项信息。glob 的依赖项列表同时包含直接依赖项和间接(传递)依赖项。

    传递依赖项可以位于依赖项树中的多个层中。例如:

    1. glob 8.0.2 直接依赖于 minimatch 5.0.1。
    2. minimatch 5.0.1 具有直接依赖项 brace-expression 2.0.1。
    3. brace-expression 2.0.1 直接依赖于 balanced-match 1.0.2。

如果无法查看间接依赖项,就很难找出并应对源自代码未直接引用的组件的漏洞和其他问题。

安装 glob 软件包时,npm 会解析整个依赖项树,并将特定下载版本的列表保存到 package.lock.json 文件中,以便您拥有所有依赖项的记录。在同一环境中的后续安装将检索相同的版本。

依赖项数据分析工具

以下工具可帮助您了解开源依赖项并评估项目的安全状况。这些工具可提供各种软件包格式的信息。

软件分发护盾
Google Cloud 上的全代管式软件供应链安全解决方案,可让您查看 Cloud Build、Cloud Run 和 GKE 中的工件的安全性数据分析,包括漏洞、依赖项信息(非公开预览版)和构建出处。Software Delivery Shield 还提供其他服务和功能,可改善整个软件开发生命周期的安全状况。
开源工具

有多种开源工具可供选择,包括:

  • 开源数据洞见:一个网站,提供有关开源软件的已知直接和间接依赖项、已知漏洞以及许可信息的信息。Open Source Insights 项目还会以 Google Cloud 数据集的形式提供这些数据。您可以使用 BigQuery 来探索和分析数据。

  • 开源漏洞数据库:一个可搜索的漏洞数据库,将其他数据库的漏洞聚合到一个位置。

  • 统计信息摘要:一种自动化工具,可用于识别 GitHub 项目中的有风险的软件供应链实践。它会对代码库执行检查,并为每项检查评分(0 到 10)。然后,您可以使用这些得分来评估项目的安全状况。

  • Allstar:一款 GitHub 应用,可持续监控 GitHub 组织或代码库以遵守已配置的政策。例如,您可以将政策应用于 GitHub 组织,以检查组织外部具有管理员或推送访问权限的协作者。

包含依赖项的方法

您可以通过以下几种常见方法在应用中添加依赖项:

直接从公开来源安装
直接从公共代码库(例如 Docker Hub、npm、PyPI 或 Maven Central)安装开源依赖项。这种方法很方便,因为您无需维护外部依赖项。但是,由于您无法控制这些外部依赖项,因此软件供应链更容易受到开源供应链攻击。
将依赖项副本存储在源代码库中
这种方法也称为供应商。您可以在构建期间下载外部依赖项并将其复制到项目源代码树中,而不是在构建过程中安装它。您可以更好地控制所用的供应商依赖项,但存在以下缺点:
  • 供应商依赖项会增加源代码库的大小,并导致更多流失。
  • 您必须为每个独立应用提供相同的依赖项。如果您的源代码库或构建流程不支持可重复使用的源代码模块,您可能需要维护依赖项的多个副本。
  • 升级供应商依赖项可能更加困难。
将依赖项存储在私有注册表中
私有注册表(例如 Artifact Registry)可让您通过公共代码库轻松安装,以及控制您的依赖项。借助 Artifact Registry,您可以:
  • 集中管理所有应用的构建工件和依赖项。
  • 将您的 Docker 和语言包客户端配置为与 Artifact Registry 中的私有代码库进行交互,就像处理公共代码库一样。
  • 更好地控制私有代码库中的依赖项:
  • 使用 Identity and Access Management 限制对每个代码库的访问权限。
  • 使用远程代码库缓存来自上游公共来源的依赖项,并扫描它们以查找漏洞(非公开预览版)。
  • 使用虚拟代码库将远程代码库和私有代码库分组到单个端点后。在每个代码库上设置优先级,以控制下载或安装工件(非公开预览版)的搜索顺序。
  • 可轻松地将 Artifact Registry 与 Software Delivery Shield(包括 Cloud Build、Cloud Run 和 Google Kubernetes Engine)中的其他 Google Cloud 服务搭配使用。在整个软件开发生命周期中使用自动漏洞扫描,生成 build 出处、控制部署,以及查看有关您的安全状况的数据分析。

请尽可能为您的依赖项使用私有注册表。如果您无法使用私有注册表,请考虑添加依赖项,以便控制软件供应链中的内容。

固定版本

版本固定意味着将应用依赖项限制为特定版本或版本范围。理想情况下,您可以固定依赖项的单个版本。

固定依赖项版本有助于确保应用 build 可重现。不过,这也意味着您的构建不会包含对依赖项的更新,包括安全修复、问题修复或改进。

您可以使用自动化依赖项管理工具来缓解源代码代码库中的依赖项问题,从而缓解这一问题。这些工具会根据需要更新您的要求文件,以升级依赖项,通常包括变更日志信息或其他详细信息。

版本固定仅适用于直接依赖项,而不适用于传递依赖项。例如,如果您固定了软件包 my-library 的版本,则图钉会限制 my-library 的版本,但不会限制 my-library 具有依赖项的软件版本。您可以使用锁定文件限制某些语言的软件包的依赖项树。

签名和哈希验证

您可以使用多种方法来验证您用作依赖项的工件的真实性。

哈希验证

哈希值是生成的用作文件的唯一标识符的值。您可以将工件的哈希值与工件提供商计算的哈希值进行比较,以确认文件的完整性。哈希验证可帮助您通过中间人攻击或破解工件库来识别依赖项的替换、篡改或损坏。

使用哈希验证时,您需要确保从工件代码库接收的哈希不会受到影响。

签名验证

签名验证提高了验证流程的安全性。工件代码库和/或软件维护者都可以为工件签名。

sigstore 等服务为维护者为软件工件签名以及使用方验证这些签名提供了一种方式。

Binary Authorization 可以验证部署到 Google Cloud 运行时环境的容器映像是否已根据各种标准进行证明签名。

锁定文件和已编译的依赖项

锁定文件是完全解析的要求文件,确切地说明了应为应用安装的每个依赖项的版本。锁定文件通常由安装工具自动生成,它会将版本固定、签名或哈希验证与应用的完整依赖项树结合使用。

安装工具通过完全解析顶级依赖项的所有下游传递依赖项来创建依赖项树,然后在锁定文件中添加依赖项树。因此,只能安装这些依赖项,从而使构建更具可重现性和一致性。

混合专用依赖项和公共依赖项

现代云原生应用通常依赖于开源第三方代码以及闭源内部库。借助 Artifact Registry,您可以在多个应用中共享业务逻辑,并重复使用相同的工具来安装外部和内部库。

但是,在混合专用和公共依赖项时,软件供应链更容易受到依赖项混淆攻击。通过将与您的内部项目同名的项目发布到开源代码库,攻击者可能会利用配置有误的安装程序来安装恶意代码,而不是您的内部依赖项。

为避免依赖项依赖项攻击,您可以采取以下措施:

  • 通过将依赖项添加到锁定文件中来验证依赖项的签名或哈希。
  • 将第三方依赖项和内部依赖项的安装分为两个不同的步骤。
  • 将手动创建的第三方依赖项明确拉取到私有代码库,或使用拉取代理进行镜像。Artifact Registry 远程代码库是上游公共代码库的拉取代理。
  • 使用虚拟代码库将远程远程和标准 Artifact Registry 代码库整合到单个端点后。您可以为上游代码库配置优先级,以便您的私有工件版本始终优先于同名的公共工件。
  • 为公共软件包和基础映像使用可信来源。

移除未使用的依赖项

随着需求的变化和应用的演变,您可能会更改或停止使用某些依赖项。继续在您的应用中安装未使用的依赖项会增加您的依赖项占用空间,并增加遭受这些依赖项漏洞的风险。

让应用在本地运行后,常见的做法是在开发过程中安装的每个依赖项复制到应用的要求文件中。然后,部署具有所有这些依赖项的应用。此方法有助于确保已部署的应用正常工作,但也有可能引入生产环境中不需要的依赖项。

向应用添加新依赖项时要小心谨慎。每个应用都可能会引入更多您无法完全控制的代码。在常规的 lint 和测试流水线中,集成一些工具以审核要求文件,以确定您是否实际使用或导入依赖项。

某些语言具有可帮助您管理依赖项的工具。例如,您可以使用 Maven Dependency 插件来分析和管理 Java 依赖项。

漏洞扫描

快速响应依赖项中的漏洞有助于您保护软件供应链。

漏洞扫描可让您自动且一致地评估依赖项是否向应用引入漏洞。漏洞扫描工具会使用锁定文件来准确确定您依赖的工件,并在出现新漏洞时通知您,有时甚至会建议升级路径。

例如,Container Analysis 可识别容器映像中的 OS 软件包漏洞。该工具可以在映像上传到 Artifact Registry 时扫描映像,并在推送映像后最长 30 天内持续监控这些映像,以找出新的漏洞。

您还可以使用按需扫描功能,在本地扫描容器映像是否存在 OSGoJava 漏洞。这样,您就可以及早发现漏洞,以便在将漏洞存储到 Artifact Registry 之前解决这些问题。

后续步骤