带入包:从申请单到签名验证
目标
从导入申请单开始下载包,对相对路径的哈希清单签名,用不含私钥的介质搬运,在隔离网络一侧按先签名、后哈希的顺序验证,然后仅凭包完成安装并留下记录。
为什么重要
只附带哈希清单的 USB,能发现传输过程中的损坏,却发现不了调包——改动文件的人连清单一起改掉就行了。必须对清单签名,并提前通过另外的途径交付公钥,才能保证“这是谁制作的清单”。而且验证脚本在失败时必须以非 0 值结束,自动化才会停下来。这个 Pod 能访问互联网(80/443),所以承担下载端的角色,而安装步骤则在用关闭的代理封闭外部的状态下,仅凭包完成。
步骤
- 在
/root/intake/request.txt中,把导入申请按생태계 이름 버전(占位符依次为生态系统、名称和版本)写成两行:pypi requests 2.32.3、go rsc.io/quote v1.5.2。 - 在包目录
/root/intake/bundle/中下载 Python wheel(pypi/,包含依赖,仅 wheel)和 Go 模块代理(goproxy/,新模块缓存的 cache/download 内容)。 - 在包目录内,用相对路径生成
/root/intake/bundle/SHA256SUMS(清单和签名文件本身除外)。 - 创建
/root/intake/keys/intake.key(Ed25519 私钥)和intake.pub,对SHA256SUMS签名,并把签名放在/root/intake/bundle/SHA256SUMS.sig。 - 创建介质
/root/intake/media/bundle.tar(不放入私钥),在隔离网络一侧的/root/intake/inside/中解压,并把公钥单独放在/root/intake/inside/trusted.pub。 - 编写隔离网络一侧的验证脚本
/root/intake/verify.sh <번들> <공개키>(占位符依次为包目录和公钥)。必须先验证签名,再验证哈希,还要检查清单中没有的文件,失败时以非 0 值结束。 - 在外部被封闭的状态下,仅凭
/root/intake/inside/bundle完成安装:在 venv/root/intake/venv中安装 requests,并构建/root/intake/goapp中的 Go 程序(输出 rsc.io/quote 的quote.Hello()),生成/root/intake/bin/hello。 - 留下导入记录
/root/intake/record.json(键:request、files、sha256sums_sha256、signer_pub_sha256、verified、installed)。
参考
- Ed25519 密钥:
openssl genpkey -algorithm ed25519 -out <키>(占位符为密钥文件)、公钥:openssl pkey -in <키> -pubout -out <공개키>(占位符依次为密钥文件和公钥文件) - 签名:
openssl pkeyutl -sign -inkey <키> -rawin -in <파일> -out <서명>(占位符依次为密钥文件、待签名文件和签名文件)、验证:openssl pkeyutl -verify -pubin -inkey <공개키> -rawin -in <파일> -sigfile <서명>(占位符依次为公钥文件、被签名文件和签名文件) - 常见错误 1:用绝对路径生成哈希清单。在隔离网络一侧解压到其他目录,就会全部失效。
- 常见错误 2:从哈希开始验证。连清单一起被调包的包会通过。
- 常见错误 3:把私钥或要信任的公钥也放进介质。
导入申请单要写准确的版本
在 /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 模块这样实际用于安装、构建的内容,写成 名称==版本 或 模块@版本 的形式。