skopeo standalone-sign 实战指南:不上传镜像,在本地为镜像清单生成 OpenPGP 签名
【免费下载链接】skopeoWork with remote images registries - retrieving information, images, signing content项目地址: https://gitcode.com/GitHub_Trending/sk/skopeo
导读
skopeo standalone-sign是 skopeo 提供的一个纯本地调试签名工具:它只要求你提供一个镜像 manifest 文件、一个 docker 引用和一个 GPG 密钥指纹,就能在不上传、不拉取镜像的情况下生成一份符合 containers-signature 规范的签名文件,随后可用skopeo standalone-verify在本地完成校验。阅读本文后,你将掌握该命令的完整语法、每个参数与选项的精确语义、底层签名调用链(源码级),并理解它与skopeo copy --sign-by的定位差异,能在调试签名流程、离线生成签名等特殊场景下正确使用它。
命令定位:本地调试工具,而非日常操作流程
根据 docs/skopeo-standalone-sign.1.md 的定义,skopeo standalone-sign是一个Debugging tool,它“Sign an image locally without uploading”(在本地为镜像签名而不上传)。
官方文档明确提醒:
This is primarily a debugging tool, useful for special cases, and usually should not be a part of your normal operational workflow; use
skopeo copy --sign-byinstead to publish and sign an image in one step.
即:日常发布镜像时,你不应该先手动签名再上传,而应使用 skopeo-copy(1) 的--sign-by选项在复制镜像的同时一步完成签名。standalone-sign只服务于两类特殊场景:
- 调试签名机制:验证 GPG 密钥、指纹、manifest 与签名之间的绑定关系是否符合预期;
- 离线/非交互签名:镜像清单已落在本地文件系统,希望在完全不接触网络的情况下生成签名。
在 cmd/skopeo/main.go 的命令注册表中,standaloneSignCmd()与standaloneVerifyCmd()一同被挂载到根命令下,二者配套使用,构成完整的“本地签名—本地验证”闭环。
命令语法与三个位置参数
命令的完整语法为:
skopeo standalone-sign [options] manifest docker-reference key-fingerprint --output|-o signature注意:--output/-o是必填项(下文会从源码验证这一点)。三个位置参数的含义如下:
| 位置参数 | 含义 | 说明 |
|---|---|---|
manifest | 镜像 manifest 文件的路径 | 一个包含镜像清单的本地文件,例如skopeo copy到dir:目录后生成的manifest.json |
docker-reference | 标识镜像的 docker 引用 | 用于把签名“绑定”到该镜像身份(如registry.example.com/example/busybox),签名验证时会严格比对 |
key-fingerprint | 用于签名的密钥身份 | 即本地 GPG 私钥的完整指纹(40 位十六进制),而非短 Key ID |
位置参数的含义
官方文档对三个参数的定义如下:
- manifest:Path to a file containing the image manifest —— 包含镜像清单的文件路径;
- docker-reference:A docker reference to identify the image with —— 用于标识镜像的 docker 引用;
- key-fingerprint:Key identity to use for signing —— 用于签名的密钥身份。
manifest文件与docker-reference是签名内容的一部分:签名不仅覆盖 manifest 字节本身,还同时绑定目标镜像引用,这正是 containers-signature 规范的核心——一份签名必须同时绑定“清单摘要”和“镜像身份”,防止签名被复制到其他镜像上冒用。
选项详解
参见 skopeo(1),全局选项(如--policy、--insecure-policy、--registries.d)放在子命令名之前;本命令自身的选项如下。
--help/-h
打印命令的使用说明(usage statement)。
--output/-ooutput file(必填)
将生成的签名写入_output file。虽然文档将其列为选项,但从源码看它是强制参数——缺少它命令直接报错退出(见下文源码剖析)。示例中签名文件的后缀常取.signature以示区分。
--passphrase-file=path
签名时使用的口令文件。官方文档给出明确警告:
The passphare to use when signing with the key ID from
--sign-by. Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
要点:
- 只读取文件的第一行作为口令,文件其余内容被忽略;
- 将口令明文存放在文件中,如果其他用户可读,则安全性堪忧;
- 除非万不得已,不要使用该选项。
对于未加密的测试密钥(如cmd/skopeo/fixtures中配套的测试密钥),可以完全不提供口令;对于有口令保护的私钥,交互式输入或 gpg-agent 是更安全的选择。仓库测试夹具中甚至准备了空口令文件cmd/skopeo/fixtures/empty.passphrase,用于验证“空口令”场景下签名流程依然可用。
实战示例:从 manifest 到签名文件的完整流程
官方文档给出的示例为:
$ skopeo standalone-sign busybox-manifest.json registry.example.com/example/busybox 1D8230F6CDB6A06716E414C1DB72F2188BB46CC8 --output busybox.signature $下面按完整工作流展开讲解每一步。
第一步:获取镜像 manifest 文件
standalone-sign不直接与远程仓库交互,因此你需要先用其他手段把 manifest 落到本地。最方便的方式是使用skopeo copy配合dir:transport:
$ mkdir -p /tmp/busybox-dir $ skopeo copy docker://busybox:latest dir:/tmp/busybox-dir $ ls /tmp/busybox-dir/ 2b8fd9751c4c0f5dd266fcae00707e67a2545ef34f9a29354585f93dac906749.tar manifest.json此时/tmp/busybox-dir/manifest.json就是签名命令所需的manifest文件。仓库内 cmd/skopeo/fixtures/image.manifest.json 提供了一个可用于复现的 Docker schema 2 清单样例(含config与三个layers,每个 blob 都有mediaType、size与digest)。
第二步:准备 GPG 私钥并确认指纹
签名依赖本机 GPG 密钥环(默认GNUPGHOME环境变量指向的目录)中的私钥。可用gpg --list-secret-keys --with-fingerprint查看完整指纹:
$ gpg --list-secret-keys --with-fingerprint sec rsa3072 2024-01-01 [SC] 1D8230F6CDB6A06716E414C1DB72F2188BB46CC8 uid [ultimate] Example User <user@example.com>注意必须使用40 位完整指纹。测试代码 cmd/skopeo/signing_test.go 中的常量也印证了这一点:
- 测试用完整指纹:
08CD26E446E2E95249B7A405E932F44B23E8DD43 - 对应的短 Key ID:
E932F44B23E8DD43
若在测试目录cmd/skopeo/fixtures中设置了GNUPGHOME,即可用上述指纹直接对样例 manifest 签名。
第三步:执行签名
$ skopeo standalone-sign \ /tmp/busybox-dir/manifest.json \ registry.example.com/example/busybox \ 1D8230F6CDB6A06716E414C1DB72F2188BB46CC8 \ --output busybox.signature命令成功时没有任何标准输出(静默成功),并在--output指定的路径生成签名文件。测试 cmd/skopeo/signing_test.go 对成功路径的断言是:out为空、错误为nil,且生成的签名可用signature.VerifyDockerManifestSignature重新验证,验证结果中的DockerReference与DockerManifestDigest均与输入一致。
第四步:本地验证签名(配套命令)
生成的签名可用skopeo standalone-verify在本地校验:
$ skopeo standalone-verify \ /tmp/busybox-dir/manifest.json \ registry.example.com/example/busybox \ 1D8230F6CDB6A06716E414C1DB72F2188BB46CC8 \ busybox.signature Signature verified using fingerprint 1D8230F6CDB6A06716E414C1DB72F2188BB46CC8, digest sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55输出会报告实际使用的验证指纹以及签名绑定的 manifest 摘要(DockerManifestDigest)。standalone-verify还支持--public-key-file指定公钥文件,以及用逗号分隔的指纹列表或any(表示信任公钥文件中的任意密钥)作为key-fingerprints,详见 docs/skopeo-standalone-verify.1.md。
源码级剖析:签名是如何完成的
命令的完整实现位于 cmd/skopeo/signing.go,核心流程清晰可读。
参数定义与命令注册
type standaloneSignOptions struct { output string // Output file path passphraseFile string // Path pointing to a passphrase file when signing }在standaloneSignCmd()(cmd/skopeo/signing.go)中注册两个 flag:
--output/-o,短选项别名o,帮助文本为 “output the signature toSIGNATURE”;--passphrase-file,帮助文本为 “file that contains a passphrase for the --sign-by key”。
执行逻辑与调用链
run()(cmd/skopeo/signing.go)按以下顺序工作:
- 参数校验:
len(args) != 3 || opts.output == ""时直接返回Usage: skopeo standalone-sign manifest docker-reference key-fingerprint -o signature。由此可见:位置参数恰好 3 个、且-o必须显式给出,否则命令拒绝执行(测试 cmd/skopeo/signing_test.go 用 6 组非法参数组合逐一验证了这一行为)。 - 读取 manifest:
os.ReadFile(manifestPath),文件不存在时返回Error reading <path>。 - 初始化 GPG 签名机制:
signature.NewGPGSigningMechanism()(来自go.podman.io/image/v5/signature包,对应 go.mod 中声明的依赖),失败时返回Error initializing GPG。这正是文档 NOTES 中所说“本命令面向 OpenPGP 本地签名”的落地实现。 - 读取口令:
cli.ReadPassphraseFile(opts.passphraseFile),内部实现“只取第一行”的语义。 - 生成签名:调用
signature.SignDockerManifestWithOptions(manifest, dockerReference, mech, fingerprint, &signature.SignOptions{Passphrase: passphrase})—— 这一步同时把 manifest 字节、docker-reference 与密钥指纹绑定进签名内容。 - 写文件:
os.WriteFile(opts.output, signature, 0o644),权限为0644,写入失败时返回Error writing signature to <path>。
测试 cmd/skopeo/signing_test.go 还验证了其余失败路径:不存在的 manifest、空 docker 引用(报 “empty signature content”)、未知密钥指纹、以及写入目标不可写(/dev/full)均会以非零状态退出。
指纹与摘要的可验证事实
仓库测试夹具中的样例清单(image.manifest.json)与签名(image.signature)配合固定密钥指纹,构成了一个可以反复复现的验证基准:
- manifest 摘要:
sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55 - 签名私钥指纹:
08CD26E446E2E95249B7A405E932F44B23E8DD43 - 集成测试 integration/signing_test.go 演示了完整的冒烟流程:动态生成 GPG 密钥 →
standalone-sign生成签名 →standalone-verify验证,输出匹配正则^Signature verified using fingerprint ..., digest ...$。
为什么日常推荐skopeo copy --sign-by而非 standalone-sign
原文档在 DESCRIPTION 中明确建议日常场景使用skopeo copy --sign-by。这是因为:
- 一步到位:复制镜像的同时完成签名与上传,签名与镜像总是保持一致,不存在“签了旧 manifest”的窗口期;
- 身份自动绑定:
--sign-by对应当前复制目标的destination-image生成 “simple signing” 签名(见 docs/skopeo-copy.1.md),无需手工维护 manifest 文件与引用的一致性; - 错误面更小:
standalone-sign要求你先正确导出 manifest、再精确指定指纹与引用,任何一步不一致都会导致签名与实际发布内容脱节。
底层实现上,copy的签名选项在 cmd/skopeo/utils.go 中定义:--sign-by(GPG 指纹)、--sign-by-sq-fingerprint(Sequoia-PGP 指纹)、--sign-by-sigstore(sigstore 参数文件)、--sign-by-sigstore-private-key(sigstore 私钥)。这些选项与--sign-passphrase-file配合使用,但同一时刻只能指定一个签名方式,且口令文件不存在会直接报错(测试见 cmd/skopeo/utils_test.go)。
结论:面向生产的发布签名一律走skopeo copy --sign-by;只有当你在排查签名格式、研究签名绑定关系,或需要完全离线地构造一份签名时,才使用standalone-sign。
使用边界与注意事项
根据 docs/skopeo-standalone-sign.1.md 的 NOTES 章节,还有两点必须明确:
- 仅适用于本地签名格式(如 OpenPGP):命令遵循 containers-signature(5) 规范(由
go.podman.io/image/v5/signature库实现),其他签名格式可能在未来加入。当前版本面向 GPG/OpenPGP 本地签名。 - 与 Docker Content Trust(DCT)无关:本命令不会与 Docker Content Trust(DCT)生成的工件产生任何交互。DCT 是 Docker 生态中另一套基于 notary 的签名体系,二者互不兼容,不要混用。
另外注意口令安全性:--passphrase-file只读取文件第一行,且口令文件若可被他人读取则形同虚设,官方建议“能不用就不用”。签名文件权限固定为0644,如对敏感环境有更严格的要求,可在生成后自行收紧。
相关命令与扩展阅读
- skopeo(1):全局选项(
--policy、--insecure-policy、--registries.d、--require-signed等)与全部子命令总览; - skopeo-copy(1):日常签名推荐路径
skopeo copy --sign-by的完整选项说明; - skopeo-standalone-verify.1.md:配套的本地签名验证命令;
- 实现与测试:cmd/skopeo/signing.go、cmd/skopeo/signing_test.go、integration/signing_test.go;
- 可复现的测试夹具:cmd/skopeo/fixtures/image.manifest.json、
image.signature、corrupt.signature、pubring.gpg、secring.gpg、empty.passphrase(均在cmd/skopeo/fixtures/目录下)。
关于签名格式的完整规范(containers-signature 的格式、字段与校验规则),请查阅容器镜像签名规范文档(containers-signature(5)),它由本命令所依赖的go.podman.io/image/v5/signature库实现并遵循。
【免费下载链接】skopeoWork with remote images registries - retrieving information, images, signing content项目地址: https://gitcode.com/GitHub_Trending/sk/skopeo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考