依赖项管理

本文档介绍了应用依赖项以及管理这些依赖项的最佳实践,包括漏洞监控、制品验证、减少依赖项占用空间和支持可重现的 build。

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

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

实现最佳实践的具体方法可能因制品格式和所用工具而异,但一般原则仍然适用。

直接依赖项和传递依赖项

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

直接依赖项
应用直接引用的软件组件。
传递依赖项
应用直接依赖项在功能上需要的软件组件。每个依赖项都可以有自己的直接和间接依赖项,从而形成一个传递性依赖项的递归树,所有这些依赖项都会影响应用。

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

在 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 控制台中的安全数据洞见
Google Cloud 可为 Cloud Build、Cloud Run 和 GKE 中的制品提供安全洞见,包括漏洞、依赖项信息、软件物料清单 (SBOM) 和 build 出处。其他 Google Cloud 服务也提供可在整个软件开发生命周期内提升安全状况的功能。如需了解详情,请参阅软件供应链安全概览
开源工具

有许多开源工具可供使用,包括:

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

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

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

  • Allstar:一款 GitHub 应用,可持续监控 GitHub 组织或代码库是否遵守已配置的政策。例如,您可以向 GitHub 组织应用一项政策,以检查组织外部是否有人拥有管理员或推送访问权限。

添加依赖项的方法

有几种常见方法可用于在应用中添加依赖项:

直接从公共来源安装
直接从 Docker Hub、npm、PyPI 或 Maven Central 等公共代码库安装开源依赖项。这种方法很方便,因为您无需维护外部依赖项。不过,由于您无法控制这些外部依赖项,因此您的软件供应链更容易受到开源供应链攻击。
在源代码库中存储依赖项的副本
此方法也称为“供应商化”。您无需在 build 期间从公共代码库安装外部依赖项,而是下载该依赖项并将其复制到项目源代码树中。您可以更好地控制所使用的供应商提供的依赖项,但存在以下几个缺点:
  • Vendored 依赖项会增加源代码库的大小,并引入更多 churn。
  • 您必须将相同的依赖项纳入每个单独的应用中。如果您的源代码库或构建流程不支持可重用的源模块,您可能需要维护多个依赖项副本。
  • 升级 vendored 依赖项可能更加困难。
将依赖项存储在私有注册表中

Artifact Registry 等私有注册表不仅可提供从公共代码库安装的便利性,还可让您控制依赖项。借助 Artifact Registry,您可以:

  • 集中管理所有应用的 build 工件和依赖项。
  • 将 Docker 和语言软件包客户端配置为以与公共代码库相同的方式与 Artifact Registry 中的私有代码库进行交互。
  • 更好地控制专用代码库中的依赖项:

    • 使用 Identity and Access Management 限制对每个代码库的访问权限。
    • 使用远程代码库缓存来自上游公共来源的依赖项,并扫描这些依赖项是否存在漏洞(私密预览版)。
    • 使用虚拟代码库将远程代码库和私有代码库分组到单个端点后面。为每个代码库设置优先级,以便在下载或安装制品时控制搜索顺序(私密预览版)。
  • 将 Artifact Registry 与其他 Google Cloud 服务(包括 Cloud Build、Cloud Run 和 Google Kubernetes Engine)搭配使用。在整个软件开发生命周期中使用自动漏洞扫描,生成 build 出处,控制部署,并查看有关安全状况的分析洞见。

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

固定版本

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

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

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

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

签名和哈希验证

您可以使用多种方法来验证用作依赖项的制品是否真实。

哈希验证

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

使用哈希验证需要信任您从制品库收到的哈希未被盗用。

签名验证

签名验证可为验证流程增加额外的安全性。 工件代码库、软件维护者或两者都可以对工件进行签名。

sigstore 等服务可让维护人员对软件制品进行签名,并让使用者验证这些签名。

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

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

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

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

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

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

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

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

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

移除未使用的依赖项

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

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

向应用添加新依赖项时,请务必谨慎。每种依赖项都有可能引入您无法完全控制的更多代码。在常规 linting 和测试流水线中,集成可审核需求文件的工具,以确定您是否实际使用或导入了依赖项。

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

漏洞扫描

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

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

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

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

后续步骤