TT Lab
开始
学习 学习路径 课程

隔离网络镜像与私有 CA

带入包:从申请单到签名验证

在 TT Lab 中继续学习

目标

从导入申请单开始下载包,对相对路径的哈希清单签名,用不含私钥的介质搬运,在隔离网络一侧按先签名、后哈希的顺序验证,然后仅凭包完成安装并留下记录。

为什么重要

只附带哈希清单的 USB,能发现传输过程中的损坏,却发现不了调包——改动文件的人连清单一起改掉就行了。必须对清单签名,并提前通过另外的途径交付公钥,才能保证“这是谁制作的清单”。而且验证脚本在失败时必须以非 0 值结束,自动化才会停下来。这个 Pod 能访问互联网(80/443),所以承担下载端的角色,而安装步骤则在用关闭的代理封闭外部的状态下,仅凭包完成。

步骤

  1. 在 /root/intake/request.txt 中,把导入申请按 생태계 이름 버전(占位符依次为生态系统、名称和版本)写成两行:pypi requests 2.32.3、go rsc.io/quote v1.5.2。
  2. 在包目录 /root/intake/bundle/ 中下载 Python wheel(pypi/,包含依赖,仅 wheel)和 Go 模块代理(goproxy/,新模块缓存的 cache/download 内容)。
  3. 在包目录内,用相对路径生成 /root/intake/bundle/SHA256SUMS(清单和签名文件本身除外)。
  4. 创建 /root/intake/keys/intake.key(Ed25519 私钥)和 intake.pub,对 SHA256SUMS 签名,并把签名放在 /root/intake/bundle/SHA256SUMS.sig。
  5. 创建介质 /root/intake/media/bundle.tar(不放入私钥),在隔离网络一侧的 /root/intake/inside/ 中解压,并把公钥单独放在 /root/intake/inside/trusted.pub。
  6. 编写隔离网络一侧的验证脚本 /root/intake/verify.sh <번들> <공개키>(占位符依次为包目录和公钥)。必须先验证签名,再验证哈希,还要检查清单中没有的文件,失败时以非 0 值结束。
  7. 在外部被封闭的状态下,仅凭 /root/intake/inside/bundle 完成安装:在 venv /root/intake/venv 中安装 requests,并构建 /root/intake/goapp 中的 Go 程序(输出 rsc.io/quote 的 quote.Hello()),生成 /root/intake/bin/hello。
  8. 留下导入记录 /root/intake/record.json(键:request、files、sha256sums_sha256、signer_pub_sha256、verified、installed)。

参考

导入申请单要写准确的版本

在 /root/intake/request.txt 中写入 pypi requests 2.32.3 和 go rsc.io/quote v1.5.2 两行。

每行有生态系统、名称、版本三栏。如果使用范围(>=)或 latest,每次下载的结果都会不同。Go 模块的版本以 v 开头。

按申请单下载包

在 /root/intake/bundle/pypi/ 中下载 requests 2.32.3 及其依赖 wheel,在 /root/intake/bundle/goproxy/ 中下载 rsc.io/quote v1.5.2 的模块代理内容。

pip 要配合只下载 wheel 的选项使用 download;Go 则用新的模块缓存(GOMODCACHE)下载一次,再搬运其 cache/download 即可。Go 需要一个会用到所下载模块的小模块——请现在就创建第 7 步要用的 /root/intake/goapp。

相对路径的哈希清单

在包目录内,用相对路径生成 /root/intake/bundle/SHA256SUMS(清单和签名文件本身除外)。

进入包目录,用 find 收集文件并交给 sha256sum,路径就会以 ./ 开头。请按名称排除,使清单文件不包含它自己。生成之后,在同一目录中用 -c 确认。

对清单签名

创建 /root/intake/keys/intake.key(Ed25519)和 intake.pub,并把 SHA256SUMS 的签名放在 /root/intake/bundle/SHA256SUMS.sig。

用 OpenSSL 3 的 genpkey 生成 ed25519 密钥,用 pkey 提取公钥。Ed25519 直接接收原文,所以 pkeyutl 需要 -rawin。私钥放在包之外(keys/)。

不含私钥的介质,与单独交付的公钥

创建 /root/intake/media/bundle.tar 并解压到 /root/intake/inside/,公钥则单独放在 /root/intake/inside/trusted.pub。

tar 只装 bundle 这一个目录。解压后要生成 inside/bundle。如果公钥在介质里,调包的人只要放进自己的密钥就行了,所以要信任的密钥必须通过另外的途径提前到位。

先签名,后哈希——验证脚本

编写 /root/intake/verify.sh <번들> <공개키>(占位符依次为包目录和公钥)。按签名 → 哈希 → 清单中没有的文件的顺序检查,失败时以非 0 值结束。

评分器会把你的脚本对两份包副本(改动了文件的一个字节的,以及按该文件连清单也一并改过的)运行,看是否都被拒绝。第二份只看哈希的脚本是拦不住的。

仅凭包完成安装

在外部被封闭的状态下,仅凭 /root/intake/inside/bundle,在 venv /root/intake/venv 中安装 requests,并构建 /root/intake/goapp,生成 /root/intake/bin/hello。

安装之前,请先用第 6 步的脚本验证。pip 要让它不看索引、只看包目录;Go 则把包中的 goproxy 目录作为 file:// 代理,并用新的模块缓存构建。

留下导入记录

在 /root/intake/record.json 中写入 request(申请单各行的列表)、files(包清单中的文件数)、sha256sums_sha256、signer_pub_sha256(trusted.pub 文件的 sha256)、verified(true)和 installed(已安装内容的列表)。

值不要手抄,请从文件中计算。files 是 SHA256SUMS 的行数。installed 中,把 requests 和 Go 模块这样实际用于安装、构建的内容,写成 名称==版本 或 模块@版本 的形式。