依赖项管理

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

软件依赖项是应用正常运行所需的一段软件,如软件库或插件。您可以在编译代码、构建、运行、下载或安装软件时解析依赖项。

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

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

直接依赖项和传递依赖项

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

直接依赖项
应用直接引用的软件组件。
传递依赖项
应用的直接依赖项功能所需的软件组件。每个依赖项都可以有自己的直接和间接依赖项,从而创建会影响应用的传递依赖项的递归树。

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

在 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 文件中,以便您有所有依赖项的记录。后续在同一环境中安装时,将检索相同的版本。

用于获取依赖项数据分析的工具

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

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

有很多开源工具可以使用,包括:

  • Open Source Insights:一个网站,提供有关已知直接和间接依赖项、已知漏洞和开源软件许可信息的网站。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 限制对每个代码库的访问。
  • 使用远程代码库缓存来自上游公共来源的依赖项,并扫描它们是否存在漏洞(非公开预览版)。
  • 使用虚拟代码库将远程代码库和私有代码库分组到单个端点后面。为每个代码库设置优先级,以便在下载或安装工件(非公开预览版)时控制搜索顺序。
  • 通过 Software Delivery Shield 轻松将 Artifact Registry 与其他 Google Cloud 服务(包括 Cloud Build、Cloud Run 和 Google Kubernetes Engine)搭配使用。在软件开发生命周期中使用自动漏洞扫描、生成构建出处、控制部署,以及查看有关安全状况的数据洞见。

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

固定版本

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

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

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

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

签名和哈希验证

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

哈希验证

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

使用哈希验证需要确信您从工件代码库中收到的哈希未遭到入侵。

签名验证

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

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

Binary Authorization 可以验证部署到 Google Cloud 运行时环境的容器映像是否已使用符合各种条件的证明进行签名。

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

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

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

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

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

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

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

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

移除未使用的依赖项

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

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

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

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

漏洞扫描

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

借助漏洞扫描,您可以持续自动评估您的依赖项是否会将漏洞引入您的应用。漏洞扫描工具会使用锁定文件来准确确定您依赖的工件,并在出现新漏洞时通知您,有时甚至会提供建议的升级路径。

例如,Artifact Analysis 可识别容器映像中的操作系统软件包漏洞。它可以在映像上传到 Artifact Registry 时对其进行扫描,并在推送映像后长达 30 天的时间内持续监控以发现新漏洞。

您还可以使用按需扫描在本地扫描容器映像,以检查是否存在 OSGoJava 漏洞。这样,您就能及早发现漏洞,在将其存储在 Artifact Registry 中之前加以解决。

后续步骤