依赖项管理

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

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

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

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

直接依赖项和传递依赖项

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

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

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

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

例如,请考虑 npm 模块 glob 版本 8.0.2。您可以在 package.json 文件中声明 npm 模块的直接依赖项。在 适用于全局通配符的 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:这是一个网站,提供有关开源软件的已知直接和间接依赖项、已知漏洞和许可信息。Open Source Insights 项目还将这些数据作为 Google Cloud 数据集提供。您可以使用 BigQuery 探索和分析数据。

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

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

  • Allstar:这款 GitHub 应用会持续监控 GitHub 组织或代码库,确保其遵守已配置的政策。例如,您可以对 GitHub 组织应用一项政策,以检查组织外部是否有拥有管理员权限或推送权限的协作者。

添加依赖项的方法

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

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

私有注册表(例如 Artifact Registry)可让您从公共代码库中轻松安装,并控制依赖项。借助 Artifact Registry,您可以:

  • 集中管理所有应用的构建工件和依赖项。
  • 将您的 Docker 和语言软件包客户端配置为与 Artifact Registry 中的私有代码库进行交互,就像与公共代码库进行交互一样。
  • 更好地控制私有代码库中的依赖项:

    • 使用 Identity and Access Management 限制对每个代码库的访问权限。
    • 使用远程代码库缓存来自上游公共源的依赖项,并扫描这些依赖项是否存在漏洞(仅限私享预览版)。
    • 使用虚拟代码库可在单个端点后面对远程代码库和私有代码库进行分组。为每个代码库设置优先级,以便在下载或安装工件时控制搜索顺序(Beta 版)。
  • 将 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 代码库整合到单个端点后面。您可以为上游代码库配置优先级,以便私有工件版本始终优先于同名公共工件。
  • 使用可信来源获取公共软件包和基础映像。

移除未使用的依赖项

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

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

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

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

漏洞扫描

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

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

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

您还可以使用按需扫描在本地扫描容器映像,检查是否存在操作系统GoJava 漏洞。这样,您就可以尽早识别漏洞,以便在将其存储在 Artifact Registry 中之前解决这些漏洞。

后续步骤