ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

docker trust signer add 完全指南:为受信仓库添加镜像签名者

docker trust signer add 完全指南:为受信仓库添加镜像签名者
  • 容器运行时
  • 云原生

【免费下载链接】docker-ce

:warning: This repository is deprecated and will be archived (Docker CE itself is NOT deprecated) see the https://github.com/docker/docker-ce/blob/master/README.md :warning:

项目地址:https://gitcode.com/gh_mirrors/do/docker-ce
点击查看免费下载

本文围绕 Docker CE 官方文档 trust_signer_add.md 展开,系统讲解docker trust signer add命令的完整用法与底层实现:从签名者(signer)与仓库密钥体系的关系、--key公钥文件的准备,到单仓库、多仓库以及首次初始化仓库三种实战场景,再到该命令在 docker-ce 仓库中的源码级工作原理、名称校验规则与错误处理策略。读者学完后,能够独立完成"为已签名仓库新增签名者""初始化全新签名仓库"和"批量管理多个仓库签名者"三套完整操作,并理解其背后 TUF(The Update Framework)委托角色机制。

一、命令概览:签名者管理在 docker trust 体系中的位置

Docker Content Trust(DCT)借助 Notary 与 TUF 框架为镜像提供端到端的签名与校验能力。在这一体系中,签名者(signer)是"被允许对某个仓库的镜像 tag 进行签名"的实体,而docker trust signer add正是"把某个实体的公钥登记进指定仓库"的入口命令。

从命令树来看,trust命令组在 cmd.go 中注册了revoke、sign、key、signer与inspect五个子命令组,其中signer组又在 signer.go 下挂载了add与remove两个子命令:

docker trust signer add # 添加签名者(本文主题) docker trust signer remove # 移除签名者

docker trust signer add的命令格式如下(取自 trust_signer_add.md):

Usage: docker trust signer add [OPTIONS] NAME REPOSITORY [REPOSITORY...] Add a signer Options: --help Print usage -k, --key list Path to the signer's public key file

参数说明:

参数/选项说明
NAME签名者名称,即签名者在该仓库中的唯一标识
REPOSITORY [REPOSITORY...]一个或多个目标仓库(形如example/trust-demo),可一次性添加多个
-k, --key list签名者公钥文件的路径,必填;可多次指定以一次性登记多个公钥
--help打印命令帮助

注意:-k/--key传入的是签名者的公钥,用于在 Notary/TUF 元数据中建立"签名者角色"。真正用来签名镜像的私钥则由docker trust key generate/docker trust key load负责加载到本地信任密钥库,相关内容可参阅 trust_key_generate.md 与 trust_key_load.md。

二、前置准备:如何获取签名者的公钥文件

--key参数要求提供签名者公钥的 PEM 文件路径。Docker 官方推荐的生成方式是docker trust key generate:

$ docker trust key generate alice Generating key for alice... Enter passphrase for new alice key with ID 17acf3c: Repeat passphrase for new alice key with ID 17acf3c: Successfully generated and loaded private key. Corresponding public key available: alice.pub $ ls alice.pub

该命令会生成一对 ECDSA 密钥:私钥加密后加载进本地 docker trust 密钥库,公钥则以<NAME>.pub形式输出到当前目录(或通过--dir指定目录),此文件可直接作为docker trust signer add --key的输入。

从源码看,key_generate.go 中的generateKeyAndOutputPubPEM使用tufutils.GenerateKey(data.ECDSAKey)生成 ECDSA 密钥,并将 PEM Block 的 Header 写入role: <keyName>,最终输出PUBLIC KEY类型的 PEM 文件:

return pem.Block{ Type: "PUBLIC KEY", Headers: map[string]string{ "role": keyName, }, Bytes: pubKey.Public(), }, nil

如果你已有现成的 PEM 格式公钥文件(例如 OpenSSL 生成),同样可以直接传给--key。ingestPublicKeys(位于 signer_add.go)会以只读方式打开文件,用io.LimitReader将读取上限限制在 1 MiB,再通过tufutils.ParsePEMPublicKey解析 PEM 内容;若文件不存在或不是合法公钥,会分别报错unable to read public key from file或could not parse public key from file。对应测试见 signer_add_test.go 的TestIngestPublicKeys。

三、典型场景一:向已有签名仓库添加签名者

1. 查看仓库当前签名者状态

在添加之前,先用docker trust inspect --pretty确认目标仓库现状。假设仓库example/trust-demo已初始化,且已有签名者bob:

$ docker trust inspect --pretty example/trust-demo No signatures for example/trust-demo List of signers and their keys: SIGNER KEYS bob 5600f5ab76a2 Administrative keys for example/trust-demo: Repository Key: 642692c14c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4555b3c6ab02f71e Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949

其中Repository Key是仓库级签名密钥(对应 TUF 的targets角色),Root Key是仓库的根信任锚(对应 TUF 的root角色),SIGNER / KEYS列表则是已登记的签名者及其公钥 ID。

2. 执行添加命令

持有仓库私钥的管理员执行以下命令(alice.crt为 alice 的公钥 PEM 文件):

$ docker trust signer add alice example/trust-demo --key alice.crt Adding signer "alice" to example/trust-demo... Enter passphrase for repository key with ID 642692c: Successfully added signer: alice to example/trust-demo

流程要点:

  • 命令会提示输入仓库密钥(Repository Key)口令,因为添加签名者需要修改仓库的 targets 委托元数据并签名发布;
  • 提示中的642692c即上文docker trust inspect显示的 Repository Key 前 7 位 ID。

3. 验证添加结果

再次执行docker trust inspect --pretty,alice已出现在签名者列表中:

$ docker trust inspect --pretty example/trust-demo No signatures for example/trust-demo List of signers and their keys: SIGNER KEYS alice 05e87edcaecb bob 5600f5ab76a2 Administrative keys for example/trust-demo: Repository Key: 642692c14c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4555b3c6ab02f71e Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949

此后alice即可用自己的私钥通过docker trust sign为example/trust-demo的 tag 签名(签名流程详见 trust_sign.md)。

四、典型场景二:在全新仓库上首次添加签名者

当目标仓库尚未初始化时,docker trust signer add会自动完成仓库初始化,并同时建立 root key、repository key 与签名者角色。

1. 确认仓库不可访问

$ docker trust inspect --pretty example/trust-demo No signatures or cannot access example/trust-demo

2. 执行首次添加

$ docker trust signer add alice example/trust-demo --key alice.crt Initializing signed repository for example/trust-demo... Enter passphrase for root key with ID 748121c: Enter passphrase for new repository key with ID 95b9e55: Repeat passphrase for new repository key with ID 95b9e55: Successfully initialized "example/trust-demo" Adding signer "alice" to example/trust-demo... Successfully added signer: alice to example/trust-demo

与场景一相比,多出了三个阶段:

  1. 初始化提示:要求设置 root key(根密钥)口令——这是整个信任链的锚点,必须妥善保管;
  2. 仓库密钥创建:要求为新建的 repository key 设置并重复确认口令;
  3. 签名者登记:Successfully initialized之后,才进入正常的添加签名者流程。

3. 验证初始化结果

$ docker trust inspect --pretty example/trust-demo No signatures for example/trust-demo SIGNED TAG DIGEST SIGNERS List of signers and their keys: SIGNER KEYS alice 6d52b29d940f Administrative keys for example/trust-demo: Repository Key: 95b9e5565eac3ef5ec01406801bdfb70feb40c17808d2222427c18046eb63beb Root Key: 748121c14bd1461f6c58cb3ef39087c8fdc7633bb11a98af844fd9a04e208103

注意此时 Repository Key 与 Root Key 都是新建的(对应交互提示中的95b9e55与748121c)。

底层原理:未初始化仓库的自动初始化

从源码看,signer_add.go 的addSignerToRepo先通过notaryRepo.ListTargets()探测仓库是否已初始化;若抛出client.ErrRepoNotInitialized或client.ErrRepositoryNotExist,则进入初始化分支并打印Initializing signed repository for ...:

if _, err = notaryRepo.ListTargets(); err != nil { switch err.(type) { case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist: fmt.Fprintf(cli.Out(), "Initializing signed repository for %s...\n", repoName) if err := getOrGenerateRootKeyAndInitRepo(notaryRepo); err != nil { return trust.NotaryError(repoName, err) } fmt.Fprintf(cli.Out(), "Successfully initialized %q\n", repoName) ...

其中getOrGenerateRootKeyAndInitRepo定义于 helpers.go:优先复用本地已有的 root key(getOrGenerateNotaryKey),否则生成新的 root key,随后调用notaryRepo.Initialize([]string{rootKey.ID()}, data.CanonicalSnapshotRole)完成初始化——注意它显式使用远程托管的 snapshot key(CanonicalSnapshotRole),这意味着快照角色密钥由 Notary 服务端管理,简化了客户端密钥管理负担。

五、典型场景三:一次命令向多个仓库添加签名者

REPOSITORY [REPOSITORY...]支持一次传入多个仓库,适合批量登记同名签名者。

1. 场景现状

两个仓库example/trust-demo与example/trust-demo2均已初始化且存在已签名 tagv1(由bob签名):

$ docker trust inspect --pretty example/trust-demo SIGNED TAG DIGEST SIGNERS v1 74d4bfa917d55d53c7df3d2ab20a8d926874d61c3da5ef6de15dd2654fc467c4 bob List of signers and their keys: SIGNER KEYS bob 5600f5ab76a2 Administrative keys for example/trust-demo: Repository Key: ecc457614c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4555b3c6ab02f71e Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949
$ docker trust inspect --pretty example/trust-demo2 SIGNED TAG DIGEST SIGNERS v1 74d4bfa917d55d53c7df3d2ab20a8d926874d61c3da5ef6de15dd2654fc467c4 bob List of signers and their keys: SIGNER KEYS bob 5600f5ab76a2 Administrative keys for example/trust-demo2: Repository Key: ece554f14c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4553d2ab20a8d9268 Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949

2. 一次添加两个仓库

$ docker trust signer add alice example/trust-demo example/trust-demo2 --key alice.crt Adding signer "alice" to example/trust-demo... Enter passphrase for repository key with ID 95b9e55: Successfully added signer: alice to example/trust-demo Adding signer "alice" to example/trust-demo2... Enter passphrase for repository key with ID ece554f: Successfully added signer: alice to example/trust-demo2

命令会依次处理每个仓库,且因为两个仓库的 Repository Key 不同,会分别提示对应的口令。

3. 验证批量结果

$ docker trust inspect --pretty example/trust-demo SIGNED TAG DIGEST SIGNERS v1 74d4bfa917d55d53c7df3d2ab20a8d926874d61c3da5ef6de15dd2654fc467c4 bob List of signers and their keys: SIGNER KEYS alice 05e87edcaecb bob 5600f5ab76a2
$ docker trust inspect --pretty example/trust-demo2 SIGNED TAG DIGEST SIGNERS v1 74d4bfa917d55d53c7df3d2ab20a8d926874d61c3da5ef6de15dd2654fc467c4 bob List of signers and their keys: SIGNER KEYS alice 05e87edcaecb bob 5600f5ab76a2

两个仓库的签名者列表中都出现了alice,说明批量添加成功。

底层原理:best effort 顺序处理

源码中 addSigner 对每个仓库执行独立的addSignerToRepo,逐仓库打印Adding signer ... to <repo>...与Successfully added signer: <name> to <repo>,并采用**尽力而为(best effort)**策略:某个仓库失败不中断后续仓库,失败仓库被记录到errRepos切片,全部处理完后统一返回Failed to add signer to: <repo1>, <repo2>错误。这正是文档中"best effort basis"的源码对应。

六、best effort 行为与失败场景

当多个仓库中存在无权限或不存在等失败情况时,命令依然会继续处理其余仓库,最后汇总错误:

$ docker trust signer add alice example/unauthorized example/authorized --key alice.crt Adding signer "alice" to example/unauthorized... you are not authorized to perform this operation: server returned 401. Adding signer "alice" to example/authorized... Enter passphrase for repository key with ID c6772a0: Successfully added signer: alice to example/authorized Failed to add signer to: example/unauthorized

观察要点:

  • 对example/unauthorized的添加因 401(未授权)失败,但命令并未中断,而是继续处理example/authorized;
  • 授权仓库成功添加后,命令返回值为非零错误并列出全部失败仓库;
  • 失败时签名者不会被登记到example/unauthorized,因为 Notary 元数据的发布(notaryRepo.Publish())只有在服务端鉴权通过时才会成功。

该行为与源码中addSigner的循环结构一一对应:循环内errRepos = append(errRepos, repoName)收集失败项,循环结束后return fmt.Errorf("Failed to add signer to: %s", strings.Join(errRepos, ", "))统一报错(signer_add.go)。

七、签名者命名规则与常见错误

docker trust signer add对NAME有严格校验,规则定义于 signer_add.go:

var validSignerName = regexp.MustCompile(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString

即:以一个小写字母或数字开头,后续可包含小写字母、数字、下划线_与连字符-。相关校验逻辑如下:

检查项触发条件报错信息
最少参数未提供NAME REPOSITORYrequires at least 2 argument
缺少公钥未提供--keypath to a public key must be provided using the --key flag
保留关键字NAME为releasesreleases is a reserved keyword, please use a different signer name
非法字符如ali/ce(含斜杠)signer name "ali/ce" must start with lowercase alphanumeric characters...
大写字母如Alice同上(必须以小写字母或数字开头)
首字符非法如_alice同上
公钥文件不存在--key指向不存在的文件unable to read public key from file: ...
公钥文件内容非法PEM 中无有效公钥could not parse public key from file: ...: no valid public key found

这些规则的边界情形在 signer_add_test.go 的TestTrustSignerAddErrors中均有对应测试用例(含not-enough-args、no-key、reserved-releases-signer-add、disallowed-chars、no-upper-case、start-with-letter)。

为何releases是保留字?因为 TUF 委托角色targets/releases是"已发布版本"的汇聚角色——所有已发布的 tag 签名都会同步写入该角色(见 sign.go 中addStagedSigner同时向targets/<signer>与targets/releases添加委托)。docker trust inspect中 "Repo Admin" 对应的正是targets/targets/releases角色(见 helpers.go)。因此自定义签名者名不能与releases冲突。

八、源码级原理:一次 signer add 的完整执行链路

将前文各场景串联起来,docker trust signer add alice example/trust-demo --key alice.crt的完整链路如下:

  1. 命令解析(signer_add.go):Cobra 解析参数,options.signer = args[0]、options.repos = args[1:],--key通过opts.NewListOpts收集为可重复选项;
  2. 名称与公钥校验(addSigner):正则校验validSignerName、拒绝releases、要求--key非空,随后ingestPublicKeys读取并解析 PEM 公钥为 TUFdata.PublicKey;
  3. 逐仓库处理(addSignerToRepo):
    • 通过trust.GetImageReferencesAndAuth解析镜像引用与认证信息,以trust.ActionsPushAndPull权限创建 Notary 客户端(cli.NotaryClient);
    • notaryRepo.ListTargets()探测仓库状态,若未初始化则走getOrGenerateRootKeyAndInitRepo自动初始化;
    • 构造委托角色名targets/<signer>(data.RoleName(path.Join(data.CanonicalTargetsRole.String(), signerName)));
    • addStagedSigner将签名者公钥写入委托角色并暂存变更;
    • notaryRepo.Publish()将元数据签名并发布到 Notary 服务端;
  4. 结果汇总:全部仓库处理完后,若有失败项则返回Failed to add signer to: ...。

其中addStagedSigner(sign.go)完成两个关键动作:

func addStagedSigner(notaryRepo client.Repository, newSigner data.RoleName, signerKeys []data.PublicKey) error { // create targets/<username> if err := notaryRepo.AddDelegationRoleAndKeys(newSigner, signerKeys); err != nil { return err } if err := notaryRepo.AddDelegationPaths(newSigner, []string{""}); err != nil { return err } // create targets/releases if err := notaryRepo.AddDelegationRoleAndKeys(trust.ReleasesRole, signerKeys); err != nil { return err } return notaryRepo.AddDelegationPaths(trust.ReleasesRole, []string{""}) }

即:新签名者的公钥会被同时登记到两个 TUF 委托角色——targets/<signer>(签名者专属角色)与targets/releases(已发布版本汇聚角色),后者保证了该签名者签出的 tag 能被全局识别为"已发布"。这就是为什么添加成功后,docker trust inspect --pretty既能列出签名者,又能在SIGNERS列中关联到已签名 tag 的原因。

九、配合使用:添加签名者之后的完整工作流

docker trust signer add只是签名者生命周期管理的起点,与之配套的常用命令如下:

命令作用参考文档
docker trust key generate NAME生成密钥对并加载私钥trust_key_generate.md
docker trust key load KEYFILE加载已有私钥到信任密钥库trust_key_load.md
docker trust signer add NAME REPO [REPO...] --key pub.pem添加签名者(本文)trust_signer_add.md
docker trust sign IMAGE:TAG用签名者/管理员私钥签名 tagtrust_sign.md
docker trust inspect --pretty REPO查看签名者、已签名 tag 与管理密钥trust_inspect.md
docker trust signer remove NAME REPO [REPO...]移除签名者trust_signer_remove.md
docker trust revoke IMAGE:TAG撤销某个 tag 的签名trust_revoke.md

典型完整流程:先用docker trust key generate alice生成 alice 的密钥对 → 用docker trust signer add alice example/trust-demo --key alice.pub登记公钥 → alice 用docker trust sign example/trust-demo:v1签名镜像 → 团队用docker trust inspect --pretty example/trust-demo校验签名者与摘要 → 成员离职后用docker trust signer remove alice example/trust-demo回收权限。

若某个签名者不再需要,其移除会涉及 "是否最后一个 release 签名者" 的确认提示(-f, --force可跳过确认),实现细节见 signer_remove.go 的isLastSignerForReleases与 trust_signer_remove.md。

十、使用建议与注意事项

  1. 公钥文件来源:优先使用docker trust key generate产出的<NAME>.pub,其 PEM Header 中携带role标记,与 Notary 客户端解析逻辑完全兼容;外部生成的公钥同样可用,但需确保为标准 PEMPUBLIC KEY格式且大小不超过 1 MiB 读取上限。
  2. 口令安全:root key 是整条信任链的根锚,一旦丢失将无法管理仓库信任;repository key 口令每次添加签名者都会用到,建议由团队密钥管理流程统一保管。
  3. 批量操作的预期:多仓库添加是 best effort 的,失败仓库会在命令末尾统一列出;脚本化调用时应检查命令退出码与输出中的Failed to add signer to:汇总。
  4. 命名规范:签名者名一律小写字母/数字开头,可含-与_,且不可使用保留字releases。
  5. 与 DCT 联动:只有客户端开启 Content Trust(环境变量DOCKER_CONTENT_TRUST=1)时,镜像 pull/push 才会强制执行签名校验,签名者登记的效果才会最终体现在供应链安全上。

结语

docker trust signer add是 Docker Content Trust 中"授权签名实体"的核心管理入口。本文从官方文档的三个实战场景出发,结合 docker-ce 仓库中的 signer_add.go、signer.go、helpers.go、sign.go 等源码与 signer_add_test.go 测试用例,完整还原了从"公钥准备 → 名称校验 → 委托角色登记 → 元数据发布"的全链路实现。掌握了它,你就掌握了受信镜像仓库"谁能签名"这一关键权限维度的增删管理方法,可以据此构建多人协作的镜像签名治理流程。

  • 容器运行时
  • 云原生

【免费下载链接】docker-ce

:warning: This repository is deprecated and will be archived (Docker CE itself is NOT deprecated) see the https://github.com/docker/docker-ce/blob/master/README.md :warning:

项目地址:https://gitcode.com/gh_mirrors/do/docker-ce
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进