文章

Go-知识依赖管理2

在 GOMODULE 模式下,如果本地没有缓存,那么 go 命令将从版本控制系统中拉取模块,比如 github.com,bitbucket.org或golang.org 等。面对这么多的版本控制系统,考虑到不同国家和地区的网络状况,很可能出现模块下载缓慢或无法下载的情况。为了提高模块下载速度,Go 团队提供了模块镜像服务,即 proxy.golang.org。该服务通过缓存公开可获得的模块来为 Go 开发者服务,该服务实际上充当了众多版本控制系统的代理。_verifying module: checksum database disabled by gosumdb=off

Go-知识依赖管理2

文章信息

  • 原文链接:https://jiayq.blog.csdn.net/article/details/144210904
  • 发布时间:2024-12-03 11:52:21
  • 阅读量:1
  • 标签:#golang, #go, #GOPROXY, #GOSUMDB, #代理

摘要

文章浏览阅读1.5k次,点赞13次,收藏21次。在 GOMODULE 模式下,如果本地没有缓存,那么 go 命令将从版本控制系统中拉取模块,比如 github.com,bitbucket.org或golang.org 等。面对这么多的版本控制系统,考虑到不同国家和地区的网络状况,很可能出现模块下载缓慢或无法下载的情况。为了提高模块下载速度,Go 团队提供了模块镜像服务,即 proxy.golang.org。该服务通过缓存公开可获得的模块来为 Go 开发者服务,该服务实际上充当了众多版本控制系统的代理。_verifying module: checksum database disabled by gosumdb=off


Go-知识依赖管理2

  • 1. go.sum
    • 1.1 go.sum 文件记录
      • 1.2 生成
      • 1.3 校验
      • 1.4 校验和数据库
  • 2. 模块代理
    • 2.1 GOPROXY 介绍
      • 2.2 代理协议
        • 2.2.1 获取模块列表
          • 2.2.2 获取模块元素数据
          • 2.2.3 获取 go.mod 文件
          • 2.2.4 获取代码压缩包
          • 2.2.5 获取模块的最新可用版本
          • 2.2.6 下载过程
      • 2.3 观察下载步骤
  • 3. GOSUMDB 的工作机制
    • 3.1 环境变量
      • 3.2 GOSUMDB 的引入背景
        • 3.2.1 正确的模块版本
          • 3.2.2 go.sum 的不足
      • 3.3 GOSUMDB 的工作机制
        • 3.3.1 GOSUMDB 是什么
          • 3.3.2 校验流程
          • 3.3.3 数据库数据来源
  • 4. GOSUMDB 的实现原理
    • 4.1 数据库的数据结构
      • 4.2 数据库查询接口
        • 4.2.1 查询数据库整体信息
          • 4.2.2 查询模块版本 Hash
          • 4.2.3 查询任意节点
  • 5. 客户端审计
    • 5.1 确保对端规范
      • 5.2 确保对端没有被篡改
      • 5.3 整体校验过程
      • 5.4 缓存查询结果
  • 6. 第三方代理
    • 6.1 模块代理
        • 6.1.1 如何配置带三方代理
          • 6.1.2 可用的第三方代理
      • 6.2 校验和数据库代理
      • 6.3 安全性
  • 7. 私有模块
    • 7.1 私有模块配置
      • 7.2 私有代理场景

1. go.sum

为了确保一致性构建,Go 引入了 go.mod 文件来标记每个依赖包的版本。在构建过程中, go 命令会下载 go.mod 中的依赖包,下载的依赖包会缓存
在本地,以便下次构建。考虑到下载的依赖包有可能是被黑客恶意篡改的,以及缓存在本地的依赖包也有被篡改的可能,只有一个 go.mod 文件并不能
保证一致性构建。
为了解决 Go Module 的这个安全隐患, Go 开发团队在引入 go.mod 的同时引入了 go.sum 文件,用于记录每个依赖包的 Hash 值,在构建时,如果本地
的依赖包的 Hash 值与 go.sum 文件中记录的内部不一致,则会拒绝构建。

1.1 go.sum 文件记录

go.sum 文件中的每行记录由 module 名、版本和 Hash 值组成,并由空格分开:

1
2
3
4
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=

在 Go Module 机制下,需要同时使用依赖包的名称和版本才可以准确地描述一个依赖。
正常情况下,每个一依赖包版本会包含两条记录,第一条记录为该依赖包版本整体(所有文件)的Hash值,第二个记录仅表示该依赖包版本中 go.mod 文件的
Hash 值,如果该依赖包版本没有 go.mod 文件,则只有第一条记录。
在上面的例子中 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 表示的就是依赖包版本整体,
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 表示该依赖包版本中的 go.mod 文件。
依赖包版本中的任何一个文件(包括 go.mod ) 改动,都会改变其整体 Hash 值,此处在额外记录依赖包版本的 go.mod 文件主要是为了计算依赖树时不必
下载完整的依赖包版本,只根据 go.mod 即可计算依赖树。
每条记录中的 Hash 值前都有一个 表示 Hash 算法的 h1:,表示后面的 Hash 值是由算法 SHA-256 计算出来的。
自 Go Module 从 1.11 引入后,只有这一个算法。
go.sum 文件中记录的依赖包版本数量一般比 go.mod 文件中的要多,这是因为二者记录的粒度不同导致的。
go.mod 只需要记录直接依赖的依赖包版本,只在依赖包版本不包含 go.mod 文件时,才会记录间接依赖包版本。
而 go.sum 则是要记录构建用到的所有依赖包版本。

1.2 生成

在开发某个项目时,在 GOMODULE 模式下引入一个新的依赖时,通常会使用 go get 命令获取该依赖:
在这里插入图片描述

go get 命令首先会将该依赖包下载到本地缓存目录$GOPATH/pkg/mod/cache/download 下,该依赖包是一个后缀为 .zip 的压缩包
在这里插入图片描述

go get 下载完成后会对该 .zip 报做 Hash 运算,并将结果存放在后缀为 .ziphash 的文件中。
如果在项目的根目录下执行 go get 命令,则 go get 还会同步更新 go.mod 和 go.sum 文件。 go.mod 中记录的是依赖名及其版本。

1
2
3
4
require (
	github.com/google/uuid v1.6.0
	golang.org/x/text v0.19.0
)

go.sum 文件中则会记录依赖包的 Hash 值(同时还有依赖包中 go.mod 的 Hash 值)

1
2
3
4
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=

在更新 go.sum 之前,为了确保下载的依赖包是真实可靠的, go 命令在下载完依赖包后还会咨询 GOSUMDB 环境变量所指示的服务器,以得到一个权威的依赖包版本 Hash 值。
如果 go 命令计算出的依赖包版本的 Hash 值与 GOSUMDB 服务器给出的 Hash 值不一致,则 go 命令将拒绝向下执行,一不会更新 go.sum 文件。
go.sum 存在的意义在于,我们希望别人或者在别的环境中构建项目时所使用的依赖包必须与 go.sum 中记录的是完全一致的,从而达到一致构建的目的。

1.3 校验

在获得某项目的源代码并尝试在本地构建,go 命令会从本地缓存中查找所有 go.mod 中记录的依赖包,并计算本地依赖包的 Hash 值,然后与 go.sum 中的记录进行对比,即检测本地缓存中使用的依赖包版本
是否满足项目 go.sum 文件的期望。
如果校验失败,则说明本地缓存目录中依赖包版本的 Hash 值和项目中 go.sum 种记录的 Hash 值不一致, go 命令将拒绝构建,这就是 go.sum 存在的意义,即如果不使用期望的版本呢,就不能构建。
当检验失败时,有必要确认到底是本地缓存错了,还是 go.sum 记录错了。 二者都可能出错,本地缓存目录中的依赖包版本有可能被有意或无意地修改过, go.sum 中记录的 Hash 值也可能被篡改。
当校验失败时,go 命令倾向于倾向于相信 go.sum ,因为一个新的依赖包版本在被添加到 go.sum 前是经过 GOSUMDB(校验和数据库) 验证的。此时即便系统中配置了 GOSUMDB(校验和数据库),go 命令也不会查询该数据库。

1.4 校验和数据库

环境变量 GOSUMDB 标识一个 checksum database,即校验和数据库,实际上是一个 Web 服务器,该服务器提供查询依赖包版本的 Hash 值的服务。
该数据库中记录了很多依赖包版本的 Hash 值,比如 Google 官方的 sum.golang.org 记录了所有可公开获得的依赖包版本。除了使用官方的数据库,
还可以指定自行搭建的数据库,甚至干脆禁用(export GOSUMDB=off).
如果系统配置了 GOSUMDB ,那么在依赖包版本被写入 go.sum 之前会向该数据库查询该依赖包版本的 Hash 值并进行二次校验,校验无误后再写入。
如果系统仅用了 GOSUMDB ,那么在依赖包版本被写入 go.sum 之前则不会进行二次校验,go 命令会相信所有下载的依赖包,并把其 Hash 值记录到 go.sum 中。

2. 模块代理

2.1 GOPROXY 介绍

在 GOMODULE 模式下,如果本地没有缓存,那么 go 命令将从版本控制系统中拉取模块,比如 github.com,bitbucket.org或golang.org 等。
面对这么多的版本控制系统,考虑到不同国家和地区的网络状况,很可能出现模块下载缓慢或无法下载的情况。
为了提高模块下载速度,Go 团队提供了模块镜像服务,即 proxy.golang.org 。该服务通过缓存公开可获得的模块来为 Go 开发者服务,该服务实际上充当了众多版本控制系统的代理。
go 命令通过环境变量 GOPROXY 读取镜像服务器地址,改变了默认指向官方的镜像服务地址

本文由作者按照 CC BY 4.0 进行授权